/**
  * Hook-Handler for 'ArticleSave' (MediaWiki). Validates provided mapping syntax for Emoticons.
  * @param Article $oArticle The article object being saved
  * @param User $oUser The user object saving the article
  * @param string $sText The new article text
  * @param string $sSummary The article summary (comment)
  * @param bool $bIsMinor Minor flag
  * @param bool $bIsWatch Watch flag
  * @param int $iSection Number of edited section
  * @param int &$iFlags
  * @param Status $oStatus The Status object
  * @global MWMemcached $wgMemc The MediaWiki Memcached object
  * @return mixed Boolean true if syntax is okay or the saved article is not the MappingSourceArticle, String 'error-msg' if an error occurs.
  */
 public function onArticleSave($oArticle, $oUser, $sText, $sSummary, $bIsMinor, $bIsWatch, $iSection, &$iFlags, $oStatus)
 {
     global $wgMemc;
     $oMappingSourceTitle = Title::newFromText('bs-emoticons-mapping', NS_MEDIAWIKI);
     if (!$oMappingSourceTitle->equals($oArticle->getTitle())) {
         return true;
     }
     $aLines = explode("\n", $sText);
     foreach ($aLines as $iLineNumber => $sLine) {
         $iLineNumber++;
         $sLine = trim($sLine);
         //Remove leading space
         if (empty($sLine)) {
             continue;
         }
         //Empty line?
         if ($sLine[0] == '#') {
             continue;
         }
         //Comment line?
         $aEmoticonHash = preg_split('/ +/', $sLine);
         $oErrorView = new ViewErrorMessage();
         if (!isset($aEmoticonHash[1])) {
             $oErrorView->addData(array(wfMessage('bs-emoticons-error-validation-missing-symbol', $iLineNumber, $aEmoticonHash[0])->plain()));
             return $oErrorView->execute();
         }
         if (preg_match('#^.*?\\.(jpg|jpeg|gif|png)$#si', $aEmoticonHash[0]) === 0) {
             //$oStatus->fatal ( 'edit-no-change' );
             $oErrorView->addData(array(wfMessage('bs-emoticons-error-validation-imagename', $iLineNumber, $aEmoticonHash[0])->plain()));
             return $oErrorView->execute();
         }
         foreach ($aEmoticonHash as $sPart) {
             if ($sPart == $aEmoticonHash[0]) {
                 continue;
             }
             //Skip imagename
             $iSymbolLength = strlen($sPart);
             if ($iSymbolLength < 2 || $iSymbolLength > 10) {
                 $oErrorView->addData(array(wfMessage('bs-emoticons-error-validation-symbol', $iLineNumber, $sPart)));
                 return $oErrorView->execute();
             }
         }
     }
     BsCacheHelper::invalidateCache(BsCacheHelper::getCacheKey('BlueSpice', 'Emoticons'));
     return true;
 }
 /**
  * Renders the blog. Called by parser function for bs:blog tag and also from Blog::onUnknownAction.
  * @param string $input Inner HTML of bs:blog tag. Not used.
  * @param array $args List of tag attributes.
  * @param Parser $parser MediaWiki parser object
  * @return string HTML output that is to be displayed.
  */
 public function onBlog($input, $args, $parser)
 {
     $oTitle = null;
     if ($parser instanceof Parser) {
         $oTitle = $parser->getTitle();
         $parser->disableCache();
     } else {
         $oTitle = $this->getTitle();
     }
     $sKey = BsCacheHelper::getCacheKey('BlueSpice', 'Blog', $oTitle->getArticleID());
     $aData = BsCacheHelper::get($sKey);
     if ($aData !== false) {
         return $aData;
     }
     // initialize local variables
     $oErrorListView = new ViewTagErrorList($this);
     BsExtensionManager::setContext('MW::Blog::ShowBlog');
     // get all config options
     $iShowLimit = BsConfig::get('MW::Blog::ShowLimit');
     //$blogShowTrackback    = BsConfig::get('MW::Blog::ShowTrackback');  // see comment below
     $bShowPermalink = BsConfig::get('MW::Blog::ShowPermalink');
     $bShowInfo = BsConfig::get('MW::Blog::ShowInfo');
     $sSortBy = BsConfig::get('MW::Blog::SortBy');
     $bMoreInNewWindow = BsConfig::get('MW::Blog::MoreInNewWindow');
     $bShowAll = BsConfig::get('MW::Blog::ShowAll');
     $bMoreAtEndOfEntry = BsConfig::get('MW::Blog::MoreAtEndOfEntry');
     $bShowNewEntryField = BsConfig::get('MW::Blog::ShowNewEntryField');
     $bNewEntryFieldPosition = BsConfig::get('MW::Blog::NewEntryFieldPosition');
     $sImageRenderMode = BsConfig::get('MW::Blog::ImageRenderMode');
     $sImageFloatDirection = BsConfig::get('MW::Blog::ThumbFloatDirection');
     $iMaxEntryCharacters = BsConfig::get('MW::Blog::MaxEntryCharacters');
     // Trackbacks are not supported the way we intend it to be. From http://www.mediawiki.org/wiki/Manual:$wgUseTrackbacks
     // When MediaWiki receives a trackback ping, a box will show up at the bottom of the article containing a link to the originating page
     //if (!$wgUseTrackbacks)
     $bShowTrackback = false;
     // get tag attributes
     $argsIShowLimit = BsCore::sanitizeArrayEntry($args, 'count', $iShowLimit, BsPARAMTYPE::NUMERIC | BsPARAMOPTION::DEFAULT_ON_ERROR);
     $argsSCategory = BsCore::sanitizeArrayEntry($args, 'cat', false, BsPARAMTYPE::STRING);
     $argsINamespace = BsNamespaceHelper::getNamespaceIndex(BsCore::sanitizeArrayEntry($args, 'ns', NS_BLOG, BsPARAMTYPE::STRING));
     $argsBNewEntryField = BsCore::sanitizeArrayEntry($args, 'newentryfield', $bShowNewEntryField, BsPARAMTYPE::BOOL);
     $argsSNewEntryFieldPosition = BsCore::sanitizeArrayEntry($args, 'newentryfieldposition', $bNewEntryFieldPosition, BsPARAMTYPE::STRING);
     $argsSImageRenderMode = BsCore::sanitizeArrayEntry($args, 'imagerendermode', $sImageRenderMode, BsPARAMTYPE::STRING);
     $argsSImageFloatDirection = BsCore::sanitizeArrayEntry($args, 'imagefloatdirection', $sImageFloatDirection, BsPARAMTYPE::STRING);
     $argsIMaxEntryCharacters = BsCore::sanitizeArrayEntry($args, 'maxchars', $iMaxEntryCharacters, BsPARAMTYPE::INT);
     $argsSSortBy = BsCore::sanitizeArrayEntry($args, 'sort', $sSortBy, BsPARAMTYPE::STRING);
     $argsBShowInfo = BsCore::sanitizeArrayEntry($args, 'showinfo', $bShowInfo, BsPARAMTYPE::BOOL);
     $argsBMoreInNewWindow = BsCore::sanitizeArrayEntry($args, 'moreinnewwindow', $bMoreInNewWindow, BsPARAMTYPE::BOOL);
     $argsBShowPermalink = BsCore::sanitizeArrayEntry($args, 'showpermalink', $bShowPermalink, BsPARAMTYPE::BOOL);
     $argsModeNamespace = BsCore::sanitizeArrayEntry($args, 'mode', null, BsPARAMTYPE::STRING);
     if ($argsModeNamespace === 'ns' && is_object($oTitle)) {
         $argsINamespace = $oTitle->getNamespace();
     }
     // validate tag attributes
     $validateIShowLimit = BsValidator::isValid('ArgCount', $argsIShowLimit, array('fullResponse' => true));
     if ($validateIShowLimit->getErrorCode()) {
         $oErrorListView->addItem(new ViewTagError($validateIShowLimit->getI18N()));
     }
     if ($argsSCategory) {
         $validateSCategory = BsValidator::isValid('Category', $argsSCategory, array('fullResponse' => true));
         if ($validateSCategory->getErrorCode()) {
             $oErrorListView->addItem(new ViewTagError($validateSCategory->getI18N()));
         }
     }
     $oValidationResult = BsValidator::isValid('SetItem', $argsSImageRenderMode, array('fullResponse' => true, 'setname' => 'imagerendermode', 'set' => array('full', 'thumb', 'none')));
     if ($oValidationResult->getErrorCode()) {
         $oErrorListView->addItem(new ViewTagError($oValidationResult->getI18N()));
     }
     $oValidationResult = BsValidator::isValid('SetItem', $argsSImageFloatDirection, array('fullResponse' => true, 'setname' => 'imagefloatdirection', 'set' => array('left', 'right', 'none')));
     if ($oValidationResult->getErrorCode()) {
         $oErrorListView->addItem(new ViewTagError($oValidationResult->getI18N()));
     }
     $oValidationResult = BsValidator::isValid('SetItem', $argsSSortBy, array('fullResponse' => true, 'setname' => 'sort', 'set' => array('title', 'creation')));
     if ($oValidationResult->getErrorCode()) {
         $oErrorListView->addItem(new ViewTagError($oValidationResult->getI18N()));
     }
     // if there are errors, abort with a message
     if ($oErrorListView->hasEntries()) {
         return $oErrorListView->execute();
     }
     if (BsConfig::get('MW::Blog::ShowTagFormWhenNotLoggedIn') != true) {
         $oPermissionTest = Title::newFromText('PermissionTest', $argsINamespace);
         if (!$oPermissionTest->userCan('edit')) {
             $argsBNewEntryField = false;
         }
     }
     // get array of article ids from Blog/subpages
     $oBlogTitle = Title::makeTitleSafe($oTitle->getNamespace(), 'Blog');
     $aSubpages = $oBlogTitle->getSubpages();
     $iLimit = 0;
     // for later use
     $aArticleIds = array();
     foreach ($aSubpages as $oSubpage) {
         $aArticleIds[] = $oSubpage->getArticleID();
         $iLimit++;
         // for later use
     }
     if (count($aArticleIds) < 1) {
         $aArticleIds = 0;
     }
     $aTables = array('page');
     $aFields = array('entry_page_id' => 'page_id');
     $aConditions = array();
     $aOptions = array();
     $aJoins = array();
     $dbr = wfGetDB(DB_SLAVE);
     // get blog entries
     if ($argsSSortBy == 'title') {
         $aOptions['ORDER BY'] = 'page_title ASC';
     } else {
         //Creation: Also fetch possible custom timestamps from page_props table
         $aOptions['ORDER BY'] = 'entry_timestamp DESC';
         $aOptions['GROUP BY'] = 'page_id';
         global $wgDBtype;
         switch ($wgDBtype) {
             case 'oracle':
                 $aFields['entry_timestamp'] = "NVL( pp_value, rev_timestamp )";
                 $aConditions[] = "NVL( pp_value, rev_timestamp ) < " . wfTimestampNow();
                 break;
             case 'mssql':
                 $aFields['entry_timestamp'] = "ISNULL( pp_value, rev_timestamp )";
                 $aConditions[] = "ISNULL( pp_value, rev_timestamp ) < " . wfTimestampNow();
                 break;
             case 'postgres':
                 $aFields['entry_timestamp'] = "NULLIF( pp_value, rev_timestamp )";
                 $aConditions[] = "NULLIF( pp_value, rev_timestamp ) < " . wfTimestampNow();
                 break;
             default:
                 //MySQL, SQLite
                 //use pp_value if exists
                 $aFields['entry_timestamp'] = "IFNULL( pp_value, rev_timestamp )";
                 //also do not list future entries
                 $aConditions[] = "IFNULL( pp_value, rev_timestamp ) < " . wfTimestampNow();
         }
         $aTables[] = 'revision';
         $aTables[] = 'page_props';
         $aConditions[] = 'rev_page = page_id';
         $aJoins['page_props'] = array('LEFT JOIN', "pp_page = rev_page AND pp_propname = 'blogtime'");
     }
     if ($argsSCategory) {
         $aTables[] = 'categorylinks';
         $aConditions['cl_to'] = $argsSCategory;
         $aConditions[] = 'cl_from = page_id';
     } else {
         if ($argsModeNamespace === 'ns') {
             $aConditions['page_id'] = $aArticleIds;
         }
         $aConditions['page_namespace'] = $argsINamespace;
     }
     $res = $dbr->select($aTables, $aFields, $aConditions, __METHOD__, $aOptions, $aJoins);
     $iNumberOfEntries = $dbr->numRows($res);
     $iLimit = $iNumberOfEntries;
     //All
     // Sole importance is the existence of param 'showall'
     $paramBShowAll = $this->getRequest()->getFuzzyBool('showall', false);
     if ($paramBShowAll == false) {
         $iLimit = $argsIShowLimit;
     }
     // abort if there are no entries
     if ($iNumberOfEntries < 1) {
         $oBlogView = new ViewBlog();
         $oBlogView->setOption('shownewentryfield', $argsBNewEntryField);
         $oBlogView->setOption('newentryfieldposition', $argsSNewEntryFieldPosition);
         $oBlogView->setOption('namespace', BsNamespaceHelper::getNamespaceName($argsINamespace));
         if ($argsSCategory) {
             $oBlogView->setOption('blogcat', $argsSCategory);
         }
         // actually create blog output
         $sOut = $oBlogView->execute();
         $sOut .= wfMessage('bs-blog-no-entries')->plain();
         return $sOut;
     }
     $oBlogView = new ViewBlog();
     // prepare views per blog item
     $iLoop = 0;
     foreach ($res as $row) {
         // prepare data for view class
         $oEntryTitle = Title::newFromID($row->entry_page_id);
         if (!$oEntryTitle->userCan('read')) {
             $iNumberOfEntries--;
             continue;
         }
         $bMore = false;
         $aContent = preg_split('#<(bs:blog:)?more */>#', BsPageContentProvider::getInstance()->getContentFromTitle($oEntryTitle));
         if (sizeof($aContent) > 1) {
             $bMore = true;
         }
         $aContent = trim($aContent[0]);
         // Prevent recursive rendering of blog tag
         $aContent = preg_replace('/<(bs:)blog[^>]*?>/', '', $aContent);
         // Thumbnail images
         $sNamespaceRegEx = implode('|', BsNamespaceHelper::getNamespaceNamesAndAliases(NS_IMAGE));
         switch ($argsSImageRenderMode) {
             case 'none':
                 $aContent = preg_replace('/(\\[\\[(' . $sNamespaceRegEx . '):[^\\|\\]]*)(\\|)?(.*?)(\\]\\])/', '', $aContent);
                 break;
             case 'full':
                 // do nothing
                 break;
             case 'thumb':
             default:
                 $aContent = preg_replace('/(\\[\\[(' . $sNamespaceRegEx . '):[^\\|\\]]*)(\\|)?(.*?)(\\]\\])/', "\$1|thumb|{$argsSImageFloatDirection}\$3\$4|150px\$5", $aContent);
                 break;
         }
         if (strlen($aContent) > $argsIMaxEntryCharacters) {
             $bMore = true;
         }
         $aContent = BsStringHelper::shorten($aContent, array('max-length' => $argsIMaxEntryCharacters, 'ignore-word-borders' => false, 'position' => 'end'));
         $resComment = $dbr->selectRow('revision', 'COUNT( rev_id ) AS cnt', array('rev_page' => $oEntryTitle->getTalkPage()->getArticleID()));
         $iCount = $resComment->cnt;
         // set data for view class
         $oBlogItemView = new ViewBlogItem();
         // use magic set
         $oBlogItemView->setOption('showInfo', $argsBShowInfo);
         $oBlogItemView->setOption('showLimit', $argsIShowLimit);
         $oBlogItemView->setOption('showTrackback', $bShowTrackback);
         $oBlogItemView->setOption('showPermalink', $argsBShowPermalink);
         $oBlogItemView->setOption('moreInNewWindow', $argsBMoreInNewWindow);
         $oBlogItemView->setOption('showAll', $bShowAll);
         $oBlogItemView->setOption('moreAtEndOfEntry', $bMoreAtEndOfEntry);
         $oBlogItemView->setOption('more', $bMore);
         //TODO: magic_call?
         if ($argsModeNamespace === 'ns') {
             $sTitle = substr($oEntryTitle->getText(), 5);
         } else {
             $sTitle = $oEntryTitle->getText();
         }
         $aTalkParams = array();
         if (!$oEntryTitle->getTalkPage()->exists()) {
             $aTalkParams = array('action' => 'edit');
         }
         $oRevision = Revision::newFromTitle($oEntryTitle);
         $oBlogItemView->setTitle($sTitle);
         $oBlogItemView->setRevId($oRevision->getId());
         $oBlogItemView->setURL($oEntryTitle->getLocalURL());
         $oBlogItemView->setTalkURL($oEntryTitle->getTalkPage()->getLocalURL($aTalkParams));
         $oBlogItemView->setTalkCount($iCount);
         $oBlogItemView->setTrackbackUrl($oEntryTitle->getLocalURL());
         if ($bShowInfo) {
             $oFirstRevision = $oEntryTitle->getFirstRevision();
             $sTimestamp = $oFirstRevision->getTimestamp();
             $sLocalDateTimeString = BsFormatConverter::timestampToAgeString(wfTimestamp(TS_UNIX, $sTimestamp));
             $oBlogItemView->setEntryDate($sLocalDateTimeString);
             $iUserId = $oFirstRevision->getUser();
             if ($iUserId != 0) {
                 $oAuthorUser = User::newFromId($iUserId);
                 $oBlogItemView->setAuthorPage($oAuthorUser->getUserPage()->getPrefixedText());
                 $oBlogItemView->setAuthorName($this->mCore->getUserDisplayName($oAuthorUser));
             } else {
                 $oBlogItemView->setAuthorName($oFirstRevision->getUserText());
             }
         }
         $oBlogItemView->setContent($aContent);
         $oBlogView->addItem($oBlogItemView);
         $iLoop++;
         if ($iLoop >= $iLimit) {
             break;
         }
     }
     $dbr->freeResult($res);
     // prepare complete blog output
     if ($bShowAll && !$paramBShowAll && $iNumberOfEntries > $argsIShowLimit) {
         $oBlogView->setOption('showall', true);
     }
     $oBlogView->setOption('shownewentryfield', $argsBNewEntryField);
     $oBlogView->setOption('newentryfieldposition', $argsSNewEntryFieldPosition);
     $oBlogView->setOption('namespace', BsNamespaceHelper::getNamespaceName($argsINamespace, false));
     $oBlogView->setOption('blogcat', $argsSCategory);
     if ($argsModeNamespace === 'ns') {
         $oBlogView->setOption('parentpage', 'Blog/');
     }
     // actually create blog output
     $sOut = $oBlogView->execute();
     //Use cache only in NS_BLOG - there is curently no functionality to
     //figure out in what type of blog tag a entry is showen and why
     //(coditions). Possible blog by categories or subpages...
     //Needs rework.
     if (in_array($oTitle->getNamespace(), array(NS_BLOG, NS_BLOG_TALK))) {
         $aKey = array($sKey);
         $sTagsKey = BsCacheHelper::getCacheKey('BlueSpice', 'Blog', 'Tags');
         $aTagsData = BsCacheHelper::get($sTagsKey);
         if ($aTagsData !== false) {
             if (!in_array($sKey, $aTagsData)) {
                 $aTagsData = array_merge($aTagsData, $aKey);
             }
         } else {
             $aTagsData = $aKey;
         }
         BsCacheHelper::set($sTagsKey, $aTagsData, 60 * 1440);
         // one day
         BsCacheHelper::set($sKey, $sOut, 60 * 1440);
         // one day
     }
     return $sOut;
 }
 /**
  * Invalidates cache for authors
  * @param WikiPage $wikiPage
  * @param User $user
  * @param Content $content
  * @param type $summary
  * @param type $isMinor
  * @param type $isWatch
  * @param type $section
  * @param type $flags
  * @param Status $status
  * @return boolean
  */
 public static function onPageContentSave($wikiPage, $user, $content, $summary, $isMinor, $isWatch, $section, $flags, $status)
 {
     BsCacheHelper::invalidateCache(BsCacheHelper::getCacheKey('BlueSpice', 'Authors', $wikiPage->getTitle()->getArticleID()));
     return true;
 }
 public static function deleteResponsibleEditorsFromCache($iArticleId)
 {
     BsCacheHelper::invalidateCache(BsCacheHelper::getCacheKey('ResponsibleEditors', 'getResponsibleEditorsByArticleId', (int) $iArticleId));
 }
 /**
  * Invalidates user sidebar cache
  * @param Article $article
  * @param User $user
  * @param Content $content
  * @param type $summary
  * @param type $isMinor
  * @param type $isWatch
  * @param type $section
  * @param type $flags
  * @param Revision $revision
  * @param Status $status
  * @param type $baseRevId
  * @return boolean
  */
 public static function onPageContentSaveComplete($article, $user, $content, $summary, $isMinor, $isWatch, $section, $flags, $revision, $status, $baseRevId)
 {
     if (!$article->getTitle()->equals(Title::newFromText($user->getName() . '/Sidebar', NS_USER))) {
         return true;
     }
     $aKeys = array(BsCacheHelper::getCacheKey('BlueSpice', 'UserSidebar', $article->getTitle()->getPrefixedDBkey()), BsCacheHelper::getCacheKey('BlueSpice', 'UserSidebar', 'default'));
     BsCacheHelper::invalidateCache($aKeys);
     return true;
 }
 public function invalidateCache()
 {
     BsCacheHelper::invalidateCache($this->getCacheKey('NavigationSitesData'));
 }
 public static function invalidateShoutBoxCache($iArticleId)
 {
     // A better solution might be to store all possible limits and their values in one key.
     for ($iLimit = 0; $iLimit < 300; $iLimit++) {
         BsCacheHelper::invalidateCache(BsCacheHelper::getCacheKey('BlueSpice', 'ShoutBox', $iArticleId, $iLimit));
     }
     BsCacheHelper::invalidateCache(BsCacheHelper::getCacheKey('BlueSpice', 'ShoutBox', 'totalCount' . $iArticleId));
 }
 /**
  *
  * @param Title $oTitle
  * @return array An Array of Title objects
  */
 public function getTitleListFromTitle($oTitle)
 {
     $sKey = BsCacheHelper::getCacheKey('BlueSpice', 'WantedArticle', $oTitle->getPrefixedText());
     $aData = BsCacheHelper::get($sKey);
     if ($aData !== false) {
         wfDebugLog('BsMemcached', __CLASS__ . ': Fetching WantedArticle list from cache');
         $aTitleList = $aData;
     } else {
         wfDebugLog('BsMemcached', __CLASS__ . ': Fetching WantedArticle list from DB');
         $oArticleContent = BsPageContentProvider::getInstance()->getContentFromTitle($oTitle);
         $aTitleList = array();
         $aLines = explode("\n", $oArticleContent);
         foreach ($aLines as $sLine) {
             $sLine = trim($sLine);
             if (empty($sLine) || $sLine[0] != '*') {
                 continue;
             }
             $aMatches = array();
             #*[[Title]] --[[Spezial:Beiträge/0:0:0:0:0:0:0:1|0:0:0:0:0:0:0:1]] 12:31, 7. Jan. 2013 (AST)
             #*[[Title2]]--[[Benutzer:WikiSysop|WikiSysop]] ([[Benutzer Diskussion:WikiSysop|Diskussion]]) 17:47, 4. Jan. 2013 (AST)
             preg_match('#\\*.*?\\[\\[(.*?)\\]\\]( ?--\\[\\[.*?:(.*?/)?(.*?)\\|.*?\\]\\].*?\\)? (\\(.*?\\))? ?(.*?))?$#si', $sLine, $aMatches);
             if (empty($aMatches) || !isset($aMatches[1])) {
                 continue;
             }
             $sTitle = $aMatches[1];
             $sUsername = isset($aMatches[4]) ? $aMatches[4] : '';
             $sSignature = isset($aMatches[2]) ? $aMatches[2] : '';
             $oT = Title::newFromText($sTitle);
             if ($oT === null) {
                 continue;
             }
             $aTitleList[] = array('title' => $oT, 'username' => $sUsername, 'signature' => $sSignature);
         }
         BsCacheHelper::set($sKey, $aTitleList);
     }
     return $aTitleList;
 }