function setUp() { global $wgContLang, $wgUser, $wgLanguageCode; $wgContLang = Language::factory($wgLanguageCode); $this->popts = ParserOptions::newFromUserAndLang($wgUser, $wgContLang); $this->pcache = ParserCache::singleton(); }
/** * returns the xml output of the sequence with all wiki-text templates/magic words swapped out * also resolves all image and media locations with absolute paths. */ function getSequenceSMIL() { global $wgParser, $wgOut, $wgUser, $wgEnableParserCache; //temporally stop cache: $wgEnableParserCache = false; $parserOptions = ParserOptions::newFromUser($wgUser); $parserOptions->addExtraKey('mv:seq-xml'); //differentiate the articles xml from article if ($wgEnableParserCache) { $mvParserCache = ParserCache::singleton(); $parserOutput = $mvParserCache->get($this, $parserOptions); if ($parserOutput != false) { return $parserOutput->getText(); } } //get the high level sequence description: $this->getSequenceHLRD(); $this->parseHLRD_DOM(); //this is the heavy lifting of the getSequenceSMIL function: $this->resolveHLRD_to_SMIL(); //@@todo get parser Output Object (maybe cleaner way to do this? //maybe parser cache is not the right place to cache the sequence xml? ) $parserOutput = $wgParser->parse('', $this->mTitle, $parserOptions); //output header: $parserOutput->mText .= $this->smilDoc->saveXML(); //save to cache if parser cache enabled: if ($wgEnableParserCache) { $mvParserCache->save($parserOutput, $this, $parserOptions); } return $parserOutput->getText(); }
/** * Purges the cache of a page */ public function execute() { $params = $this->extractRequestParams(); $continuationManager = new ApiContinuationManager($this, array(), array()); $this->setContinuationManager($continuationManager); $forceLinkUpdate = $params['forcelinkupdate']; $forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate']; $pageSet = $this->getPageSet(); $pageSet->execute(); $result = $pageSet->getInvalidTitlesAndRevisions(); foreach ($pageSet->getGoodTitles() as $title) { $r = array(); ApiQueryBase::addTitleInfo($r, $title); $page = WikiPage::factory($title); $page->doPurge(); // Directly purge and skip the UI part of purge(). $r['purged'] = true; if ($forceLinkUpdate || $forceRecursiveLinkUpdate) { if (!$this->getUser()->pingLimiter('linkpurge')) { $popts = $page->makeParserOptions('canonical'); # Parse content; note that HTML generation is only needed if we want to cache the result. $content = $page->getContent(Revision::RAW); $enableParserCache = $this->getConfig()->get('EnableParserCache'); $p_result = $content->getParserOutput($title, $page->getLatest(), $popts, $enableParserCache); # Update the links tables $updates = $content->getSecondaryDataUpdates($title, null, $forceRecursiveLinkUpdate, $p_result); DataUpdate::runUpdates($updates); $r['linkupdate'] = true; if ($enableParserCache) { $pcache = ParserCache::singleton(); $pcache->save($p_result, $page, $popts); } } else { $error = $this->parseMsg(array('actionthrottledtext')); $this->setWarning($error['info']); $forceLinkUpdate = false; } } $result[] = $r; } $apiResult = $this->getResult(); ApiResult::setIndexedTagName($result, 'page'); $apiResult->addValue(null, $this->getModuleName(), $result); $values = $pageSet->getNormalizedTitlesAsResult($apiResult); if ($values) { $apiResult->addValue(null, 'normalized', $values); } $values = $pageSet->getConvertedTitlesAsResult($apiResult); if ($values) { $apiResult->addValue(null, 'converted', $values); } $values = $pageSet->getRedirectTitlesAsResult($apiResult); if ($values) { $apiResult->addValue(null, 'redirects', $values); } $this->setContinuationManager(null); $continuationManager->setContinuationIntoResult($apiResult); }
/** * Get template and image versions from parsing a revision * @param Page $article * @param Revision $rev * @param User $user * @param string $regen use 'regen' to force regeneration * @return array( templateIds, fileSHA1Keys ) * templateIds like ParserOutput->mTemplateIds * fileSHA1Keys like ParserOutput->mImageTimeKeys */ public static function getRevIncludes(Page $article, Revision $rev, User $user, $regen = '') { global $wgParser, $wgMemc; wfProfileIn(__METHOD__); $versions = false; $key = self::getCacheKey($article->getTitle(), $rev->getId()); if ($regen !== 'regen') { // check cache $versions = FlaggedRevs::getMemcValue($wgMemc->get($key), $article, 'allowStale'); } if (!is_array($versions)) { // cache miss $pOut = false; if ($rev->isCurrent()) { $parserCache = ParserCache::singleton(); # Try current version parser cache (as anon)... $pOut = $parserCache->get($article, $article->makeParserOptions($user)); if ($pOut == false && $rev->getUser()) { // try the user who saved the change $author = User::newFromId($rev->getUser()); $pOut = $parserCache->get($article, $article->makeParserOptions($author)); } } // ParserOutput::mImageTimeKeys wasn't always there if ($pOut == false || !FlaggedRevs::parserOutputIsVersioned($pOut)) { $title = $article->getTitle(); $pOpts = ParserOptions::newFromUser($user); // Note: tidy off $pOut = $wgParser->parse($rev->getText(), $title, $pOpts, true, true, $rev->getId()); } # Get the template/file versions used... $versions = array($pOut->getTemplateIds(), $pOut->getFileSearchOptions()); # Save to cache (check cache expiry for dynamic elements)... $data = FlaggedRevs::makeMemcObj($versions); $wgMemc->set($key, $data, $pOut->getCacheExpiry()); } else { $tVersions =& $versions[0]; // templates # Do a link batch query for page_latest... $lb = new LinkBatch(); foreach ($tVersions as $ns => $tmps) { foreach ($tmps as $dbKey => $revIdDraft) { $lb->add($ns, $dbKey); } } $lb->execute(); # Update array with the current page_latest values. # This kludge is there since $newTemplates (thus $revIdDraft) is cached. foreach ($tVersions as $ns => &$tmps) { foreach ($tmps as $dbKey => &$revIdDraft) { $title = Title::makeTitle($ns, $dbKey); $revIdDraft = (int) $title->getLatestRevID(); } } } wfProfileOut(__METHOD__); return $versions; }
/** * Get template and image versions from parsing a revision * @param Page $article * @param Revision $rev * @param User $user * @param string $regen use 'regen' to force regeneration * @return array( templateIds, fileSHA1Keys ) * templateIds like ParserOutput->mTemplateIds * fileSHA1Keys like ParserOutput->mImageTimeKeys */ public static function getRevIncludes(Page $article, Revision $rev, User $user, $regen = '') { global $wgMemc; wfProfileIn(__METHOD__); $key = self::getCacheKey($article->getTitle(), $rev->getId()); if ($regen === 'regen') { $versions = false; // skip cache } elseif ($rev->isCurrent()) { // Check cache entry against page_touched $versions = FlaggedRevs::getMemcValue($wgMemc->get($key), $article); } else { // Old revs won't always be invalidated with template/file changes. // Also, we don't care if page_touched changed due to a direct edit. $versions = FlaggedRevs::getMemcValue($wgMemc->get($key), $article, 'allowStale'); if (is_array($versions)) { // entry exists // Sanity check that the cache is reasonably up to date list($templates, $files) = $versions; if (self::templatesStale($templates) || self::filesStale($files)) { $versions = false; // no good } } } if (!is_array($versions)) { // cache miss $pOut = false; if ($rev->isCurrent()) { $parserCache = ParserCache::singleton(); # Try current version parser cache for this user... $pOut = $parserCache->get($article, $article->makeParserOptions($user)); if ($pOut == false) { # Try current version parser cache for the revision author... $optsUser = $rev->getUser() ? User::newFromId($rev->getUser()) : 'canonical'; $pOut = $parserCache->get($article, $article->makeParserOptions($optsUser)); } } // ParserOutput::mImageTimeKeys wasn't always there if ($pOut == false || !FlaggedRevs::parserOutputIsVersioned($pOut)) { $content = $rev->getContent(Revision::RAW); if (!$content) { // Just for extra sanity $pOut = new ParserOutput(); } else { $pOut = $content->getParserOutput($article->getTitle(), $rev->getId(), ParserOptions::newFromUser($user)); } } # Get the template/file versions used... $versions = array($pOut->getTemplateIds(), $pOut->getFileSearchOptions()); # Save to cache (check cache expiry for dynamic elements)... $data = FlaggedRevs::makeMemcObj($versions); $wgMemc->set($key, $data, $pOut->getCacheExpiry()); } wfProfileOut(__METHOD__); return $versions; }
protected function setUp() { global $wgLanguageCode, $wgUser; parent::setUp(); $langObj = Language::factory($wgLanguageCode); $this->setMwGlobals(array('wgContLang' => $langObj, 'wgUseDynamicDates' => true)); $this->popts = ParserOptions::newFromUserAndLang($wgUser, $langObj); $this->pcache = ParserCache::singleton(); }
function setUp() { ParserTest::setUp(); //reuse setup from parser tests global $wgContLang, $wgUser, $wgLanguageCode; $wgContLang = Language::factory($wgLanguageCode); $this->popts = new ParserOptions($wgUser); $this->pcache = ParserCache::singleton(); }
/** * Purges the cache of a page */ public function execute() { global $wgUser; $params = $this->extractRequestParams(); if (!$wgUser->isAllowed('purge') && !$this->getMain()->isInternalMode() && !$this->getMain()->getRequest()->wasPosted()) { $this->dieUsageMsg(array('mustbeposted', $this->getModuleName())); } $forceLinkUpdate = $params['forcelinkupdate']; $result = array(); foreach ($params['titles'] as $t) { $r = array(); $title = Title::newFromText($t); if (!$title instanceof Title) { $r['title'] = $t; $r['invalid'] = ''; $result[] = $r; continue; } ApiQueryBase::addTitleInfo($r, $title); if (!$title->exists()) { $r['missing'] = ''; $result[] = $r; continue; } $context = $this->createContext(); $context->setTitle($title); $article = Article::newFromTitle($title, $context); $article->doPurge(); // Directly purge and skip the UI part of purge(). $r['purged'] = ''; if ($forceLinkUpdate) { if (!$wgUser->pingLimiter()) { global $wgParser, $wgEnableParserCache; $popts = new ParserOptions(); $p_result = $wgParser->parse($article->getContent(), $title, $popts); # Update the links tables $u = new LinksUpdate($title, $p_result); $u->doUpdate(); $r['linkupdate'] = ''; if ($wgEnableParserCache) { $pcache = ParserCache::singleton(); $pcache->save($p_result, $article, $popts); } } else { $this->setWarning($this->parseMsg(array('actionthrottledtext'))); $forceLinkUpdate = false; } } $result[] = $r; } $apiResult = $this->getResult(); $apiResult->setIndexedTagName($result, 'page'); $apiResult->addValue(null, $this->getModuleName(), $result); }
public function execute() { $pages = $this->getOption('maxpages'); $dbr = $this->getDB(DB_REPLICA); $totalsec = 0.0; $scanned = 0; $withcache = 0; $withdiff = 0; while ($pages-- > 0) { $row = $dbr->selectRow('page', '*', ['page_namespace' => $this->getOption('namespace'), 'page_is_redirect' => 0, 'page_random >= ' . wfRandom()], __METHOD__, ['ORDER BY' => 'page_random']); if (!$row) { continue; } ++$scanned; $title = Title::newFromRow($row); $page = WikiPage::factory($title); $revision = $page->getRevision(); $content = $revision->getContent(Revision::RAW); $parserOptions = $page->makeParserOptions('canonical'); $parserOutputOld = ParserCache::singleton()->get($page, $parserOptions); if ($parserOutputOld) { $t1 = microtime(true); $parserOutputNew = $content->getParserOutput($title, $revision->getId(), $parserOptions, false); $sec = microtime(true) - $t1; $totalsec += $sec; $this->output("Parsed '{$title->getPrefixedText()}' in {$sec} seconds.\n"); $this->output("Found cache entry found for '{$title->getPrefixedText()}'..."); $oldHtml = trim(preg_replace('#<!-- .+-->#Us', '', $parserOutputOld->getText())); $newHtml = trim(preg_replace('#<!-- .+-->#Us', '', $parserOutputNew->getText())); $diff = wfDiff($oldHtml, $newHtml); if (strlen($diff)) { $this->output("differences found:\n\n{$diff}\n\n"); ++$withdiff; } else { $this->output("No differences found.\n"); } ++$withcache; } else { $this->output("No parser cache entry found for '{$title->getPrefixedText()}'.\n"); } } $ave = $totalsec ? $totalsec / $scanned : 0; $this->output("Checked {$scanned} pages; {$withcache} had prior cache entries.\n"); $this->output("Pages with differences found: {$withdiff}\n"); $this->output("Average parse time: {$ave} sec\n"); }
protected function getText($title) { $titleObject = Title::newFromText($title); $article = new Article($titleObject); // make sure the article exists. if ($article->getId() == 0) { return null; } // prepare the parser cache for action. $parserCache =& ParserCache::singleton(); global $wgUser; $parserOutput = $parserCache->get($article, $wgUser); // did we find it in the parser cache? if ($parserOutput !== false) { return $parserOutput->getText(); } // no... that's too bad; go the long way then. $rev = Revision::newFromTitle($titleObject); if (is_object($rev)) { return $this->parse($titleObject, $rev->getText()); } return null; }
/** * @return bool */ function fallback() { $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions ); if ( $this->parserOutput === false ) { wfDebugLog( 'dirty', "dirty missing\n" ); wfDebug( __METHOD__ . ": no dirty cache\n" ); return false; } else { wfDebug( __METHOD__ . ": sending dirty output\n" ); wfDebugLog( 'dirty', "dirty output {$this->cacheKey}\n" ); $this->isDirty = true; return true; } }
/** * This is the default action of the index.php entry point: just view the * page of the given title. */ public function view() { global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects; # Get variables from query string # As side effect this will load the revision and update the title # in a revision ID is passed in the request, so this should remain # the first call of this method even if $oldid is used way below. $oldid = $this->getOldID(); $user = $this->getContext()->getUser(); # Another whitelist check in case getOldID() is altering the title $permErrors = $this->getTitle()->getUserPermissionsErrors('read', $user); if (count($permErrors)) { wfDebug(__METHOD__ . ": denied on secondary read check\n"); throw new PermissionsError('read', $permErrors); } $outputPage = $this->getContext()->getOutput(); # getOldID() may as well want us to redirect somewhere else if ($this->mRedirectUrl) { $outputPage->redirect($this->mRedirectUrl); wfDebug(__METHOD__ . ": redirecting due to oldid\n"); return; } # If we got diff in the query, we want to see a diff page instead of the article. if ($this->getContext()->getRequest()->getCheck('diff')) { wfDebug(__METHOD__ . ": showing diff page\n"); $this->showDiffPage(); return; } # Set page title (may be overridden by DISPLAYTITLE) $outputPage->setPageTitle($this->getTitle()->getPrefixedText()); $outputPage->setArticleFlag(true); # Allow frames by default $outputPage->allowClickjacking(); $parserCache = ParserCache::singleton(); $parserOptions = $this->getParserOptions(); # Render printable version, use printable version cache if ($outputPage->isPrintable()) { $parserOptions->setIsPrintable(true); $parserOptions->setEditSection(false); } elseif (!$this->isCurrent() || !$this->getTitle()->quickUserCan('edit', $user)) { $parserOptions->setEditSection(false); } # Try client and file cache if (!$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched()) { if ($wgUseETag) { $outputPage->setETag($parserCache->getETag($this, $parserOptions)); } # Use the greatest of the page's timestamp or the timestamp of any # redirect in the chain (bug 67849) $timestamp = $this->mPage->getTouched(); if (isset($this->mRedirectedFrom)) { $timestamp = max($timestamp, $this->mRedirectedFrom->getTouched()); # If there can be more than one redirect in the chain, we have # to go through the whole chain too in case an intermediate # redirect was changed. if ($wgMaxRedirects > 1) { $titles = Revision::newFromTitle($this->mRedirectedFrom)->getContent(Revision::FOR_THIS_USER, $user)->getRedirectChain(); $thisTitle = $this->getTitle(); foreach ($titles as $title) { if (Title::compare($title, $thisTitle) === 0) { break; } $timestamp = max($timestamp, $title->getTouched()); } } } # Is it client cached? if ($outputPage->checkLastModified($timestamp)) { wfDebug(__METHOD__ . ": done 304\n"); return; # Try file cache } elseif ($wgUseFileCache && $this->tryFileCache()) { wfDebug(__METHOD__ . ": done file cache\n"); # tell wgOut that output is taken care of $outputPage->disable(); $this->mPage->doViewUpdates($user, $oldid); return; } } # Should the parser cache be used? $useParserCache = $this->mPage->shouldCheckParserCache($parserOptions, $oldid); wfDebug('Article::view using parser cache: ' . ($useParserCache ? 'yes' : 'no') . "\n"); if ($user->getStubThreshold()) { $this->getContext()->getStats()->increment('pcache_miss_stub'); } $this->showRedirectedFromHeader(); $this->showNamespaceHeader(); # Iterate through the possible ways of constructing the output text. # Keep going until $outputDone is set, or we run out of things to do. $pass = 0; $outputDone = false; $this->mParserOutput = false; while (!$outputDone && ++$pass) { switch ($pass) { case 1: Hooks::run('ArticleViewHeader', array(&$this, &$outputDone, &$useParserCache)); break; case 2: # Early abort if the page doesn't exist if (!$this->mPage->exists()) { wfDebug(__METHOD__ . ": showing missing article\n"); $this->showMissingArticle(); $this->mPage->doViewUpdates($user); return; } # Try the parser cache if ($useParserCache) { $this->mParserOutput = $parserCache->get($this, $parserOptions); if ($this->mParserOutput !== false) { if ($oldid) { wfDebug(__METHOD__ . ": showing parser cache contents for current rev permalink\n"); $this->setOldSubtitle($oldid); } else { wfDebug(__METHOD__ . ": showing parser cache contents\n"); } $outputPage->addParserOutput($this->mParserOutput); # Ensure that UI elements requiring revision ID have # the correct version information. $outputPage->setRevisionId($this->mPage->getLatest()); # Preload timestamp to avoid a DB hit $cachedTimestamp = $this->mParserOutput->getTimestamp(); if ($cachedTimestamp !== null) { $outputPage->setRevisionTimestamp($cachedTimestamp); $this->mPage->setTimestamp($cachedTimestamp); } $outputDone = true; } } break; case 3: # This will set $this->mRevision if needed $this->fetchContentObject(); # Are we looking at an old revision if ($oldid && $this->mRevision) { $this->setOldSubtitle($oldid); if (!$this->showDeletedRevisionHeader()) { wfDebug(__METHOD__ . ": cannot view deleted revision\n"); return; } } # Ensure that UI elements requiring revision ID have # the correct version information. $outputPage->setRevisionId($this->getRevIdFetched()); # Preload timestamp to avoid a DB hit $outputPage->setRevisionTimestamp($this->getTimestamp()); # Pages containing custom CSS or JavaScript get special treatment if ($this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage()) { wfDebug(__METHOD__ . ": showing CSS/JS source\n"); $this->showCssOrJsPage(); $outputDone = true; } elseif (!Hooks::run('ArticleContentViewCustom', array($this->fetchContentObject(), $this->getTitle(), $outputPage))) { # Allow extensions do their own custom view for certain pages $outputDone = true; } elseif (!ContentHandler::runLegacyHooks('ArticleViewCustom', array($this->fetchContentObject(), $this->getTitle(), $outputPage))) { # Allow extensions do their own custom view for certain pages $outputDone = true; } break; case 4: # Run the parse, protected by a pool counter wfDebug(__METHOD__ . ": doing uncached parse\n"); $content = $this->getContentObject(); $poolArticleView = new PoolWorkArticleView($this->getPage(), $parserOptions, $this->getRevIdFetched(), $useParserCache, $content); if (!$poolArticleView->execute()) { $error = $poolArticleView->getError(); if ($error) { $outputPage->clearHTML(); // for release() errors $outputPage->enableClientCache(false); $outputPage->setRobotPolicy('noindex,nofollow'); $errortext = $error->getWikiText(false, 'view-pool-error'); $outputPage->addWikiText('<div class="errorbox">' . $errortext . '</div>'); } # Connection or timeout error return; } $this->mParserOutput = $poolArticleView->getParserOutput(); $outputPage->addParserOutput($this->mParserOutput); if ($content->getRedirectTarget()) { $outputPage->addSubtitle("<span id=\"redirectsub\">" . $this->getContext()->msg('redirectpagesub')->parse() . "</span>"); } # Don't cache a dirty ParserOutput object if ($poolArticleView->getIsDirty()) { $outputPage->setSquidMaxage(0); $outputPage->addHTML("<!-- parser cache is expired, " . "sending anyway due to pool overload-->\n"); } $outputDone = true; break; # Should be unreachable, but just in case... # Should be unreachable, but just in case... default: break 2; } } # Get the ParserOutput actually *displayed* here. # Note that $this->mParserOutput is the *current*/oldid version output. $pOutput = $outputDone instanceof ParserOutput ? $outputDone : $this->mParserOutput; # Adjust title for main page & pages with displaytitle if ($pOutput) { $this->adjustDisplayTitle($pOutput); } # For the main page, overwrite the <title> element with the con- # tents of 'pagetitle-view-mainpage' instead of the default (if # that's not empty). # This message always exists because it is in the i18n files if ($this->getTitle()->isMainPage()) { $msg = wfMessage('pagetitle-view-mainpage')->inContentLanguage(); if (!$msg->isDisabled()) { $outputPage->setHTMLTitle($msg->title($this->getTitle())->text()); } } # Check for any __NOINDEX__ tags on the page using $pOutput $policy = $this->getRobotPolicy('view', $pOutput); $outputPage->setIndexPolicy($policy['index']); $outputPage->setFollowPolicy($policy['follow']); $this->showViewFooter(); $this->mPage->doViewUpdates($user, $oldid); $outputPage->addModules('mediawiki.action.view.postEdit'); }
public function execute() { // The data is hot but user-dependent, like page views, so we set vary cookies $this->getMain()->setCacheMode('anon-public-user-private'); // Get parameters $params = $this->extractRequestParams(); $text = $params['text']; $title = $params['title']; $page = $params['page']; $oldid = $params['oldid']; if (!is_null($page) && (!is_null($text) || $title != "API")) { $this->dieUsage("The page parameter cannot be used together with the text and title parameters", 'params'); } $prop = array_flip($params['prop']); $revid = false; // The parser needs $wgTitle to be set, apparently the // $title parameter in Parser::parse isn't enough *sigh* global $wgParser, $wgUser, $wgTitle, $wgEnableParserCache; $popts = new ParserOptions(); $popts->setTidy(true); $popts->enableLimitReport(); $redirValues = null; if (!is_null($oldid) || !is_null($page)) { if (!is_null($oldid)) { // Don't use the parser cache $rev = Revision::newFromID($oldid); if (!$rev) { $this->dieUsage("There is no revision ID {$oldid}", 'missingrev'); } if (!$rev->userCan(Revision::DELETED_TEXT)) { $this->dieUsage("You don't have permission to view deleted revisions", 'permissiondenied'); } $text = $rev->getText(Revision::FOR_THIS_USER); $titleObj = $rev->getTitle(); $wgTitle = $titleObj; $p_result = $wgParser->parse($text, $titleObj, $popts); } else { if ($params['redirects']) { $req = new FauxRequest(array('action' => 'query', 'redirects' => '', 'titles' => $page)); $main = new ApiMain($req); $main->execute(); $data = $main->getResultData(); $redirValues = @$data['query']['redirects']; $to = $page; foreach ((array) $redirValues as $r) { $to = $r['to']; } } else { $to = $page; } $titleObj = Title::newFromText($to); if (!$titleObj) { $this->dieUsage("The page you specified doesn't exist", 'missingtitle'); } $articleObj = new Article($titleObj); if (isset($prop['revid'])) { $oldid = $articleObj->getRevIdFetched(); } // Try the parser cache first $p_result = false; $pcache = ParserCache::singleton(); if ($wgEnableParserCache) { $p_result = $pcache->get($articleObj, $wgUser); } if (!$p_result) { $p_result = $wgParser->parse($articleObj->getContent(), $titleObj, $popts); if ($wgEnableParserCache) { $pcache->save($p_result, $articleObj, $popts); } } } } else { $titleObj = Title::newFromText($title); if (!$titleObj) { $titleObj = Title::newFromText("API"); } $wgTitle = $titleObj; if ($params['pst'] || $params['onlypst']) { $text = $wgParser->preSaveTransform($text, $titleObj, $wgUser, $popts); } if ($params['onlypst']) { // Build a result and bail out $result_array['text'] = array(); $this->getResult()->setContent($result_array['text'], $text); $this->getResult()->addValue(null, $this->getModuleName(), $result_array); return; } $p_result = $wgParser->parse($text, $titleObj, $popts); } // Return result $result = $this->getResult(); $result_array = array(); if ($params['redirects'] && !is_null($redirValues)) { $result_array['redirects'] = $redirValues; } if (isset($prop['text'])) { $result_array['text'] = array(); $result->setContent($result_array['text'], $p_result->getText()); } if (!is_null($params['summary'])) { $result_array['parsedsummary'] = array(); $result->setContent($result_array['parsedsummary'], $wgUser->getSkin()->formatComment($params['summary'], $titleObj)); } if (isset($prop['langlinks'])) { $result_array['langlinks'] = $this->formatLangLinks($p_result->getLanguageLinks()); } if (isset($prop['categories'])) { $result_array['categories'] = $this->formatCategoryLinks($p_result->getCategories()); } if (isset($prop['links'])) { $result_array['links'] = $this->formatLinks($p_result->getLinks()); } if (isset($prop['templates'])) { $result_array['templates'] = $this->formatLinks($p_result->getTemplates()); } if (isset($prop['images'])) { $result_array['images'] = array_keys($p_result->getImages()); } if (isset($prop['externallinks'])) { $result_array['externallinks'] = array_keys($p_result->getExternalLinks()); } if (isset($prop['sections'])) { $result_array['sections'] = $p_result->getSections(); } if (isset($prop['displaytitle'])) { $result_array['displaytitle'] = $p_result->getDisplayTitle() ? $p_result->getDisplayTitle() : $titleObj->getPrefixedText(); } if (isset($prop['headitems'])) { $result_array['headitems'] = $this->formatHeadItems($p_result->getHeadItems()); } if (isset($prop['headhtml'])) { $out = new OutputPage(); $out->addParserOutputNoText($p_result); $result_array['headhtml'] = array(); $result->setContent($result_array['headhtml'], $out->headElement($wgUser->getSkin())); } if (!is_null($oldid)) { $result_array['revid'] = intval($oldid); } $result_mapping = array('redirects' => 'r', 'langlinks' => 'll', 'categories' => 'cl', 'links' => 'pl', 'templates' => 'tl', 'images' => 'img', 'externallinks' => 'el', 'sections' => 's', 'headitems' => 'hi'); $this->setIndexedTagNames($result_array, $result_mapping); $result->addValue(null, $this->getModuleName(), $result_array); }
/** * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Purges pages that include this page if the text was changed here. * Every 100th edit, prune the recent changes table. * * @param Revision $revision * @param User $user User object that did the revision * @param array $options Array of options, following indexes are used: * - changed: boolean, whether the revision changed the content (default true) * - created: boolean, whether the revision created the page (default false) * - moved: boolean, whether the page was moved (default false) * - oldcountable: boolean, null, or string 'no-change' (default null): * - boolean: whether the page was counted as an article before that * revision, only used in changed is true and created is false * - null: if created is false, don't update the article count; if created * is true, do update the article count * - 'no-change': don't update the article count, ever */ public function doEditUpdates(Revision $revision, User $user, array $options = array()) { $options += array('changed' => true, 'created' => false, 'moved' => false, 'oldcountable' => null); $content = $revision->getContent(); // Parse the text // Be careful not to do pre-save transform twice: $text is usually // already pre-save transformed once. if (!$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag('vary-revision')) { wfDebug(__METHOD__ . ": No prepared edit or vary-revision is set...\n"); $editInfo = $this->prepareContentForEdit($content, $revision, $user); } else { wfDebug(__METHOD__ . ": No vary-revision, using prepared edit...\n"); $editInfo = $this->mPreparedEdit; } // Save it to the parser cache. // Make sure the cache time matches page_touched to avoid double parsing. ParserCache::singleton()->save($editInfo->output, $this, $editInfo->popts, $revision->getTimestamp(), $editInfo->revid); // Update the links tables and other secondary data if ($content) { $recursive = $options['changed']; // bug 50785 $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, $recursive, $editInfo->output); foreach ($updates as $update) { if ($update instanceof LinksUpdate) { $update->setRevision($revision); } DeferredUpdates::addUpdate($update); } } Hooks::run('ArticleEditUpdates', array(&$this, &$editInfo, $options['changed'])); if (Hooks::run('ArticleEditUpdatesDeleteFromRecentchanges', array(&$this))) { // Flush old entries from the `recentchanges` table if (mt_rand(0, 9) == 0) { JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newPurgeJob()); } } if (!$this->exists()) { return; } $id = $this->getId(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); if ($options['oldcountable'] === 'no-change' || !$options['changed'] && !$options['moved']) { $good = 0; } elseif ($options['created']) { $good = (int) $this->isCountable($editInfo); } elseif ($options['oldcountable'] !== null) { $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable']; } else { $good = 0; } $edits = $options['changed'] ? 1 : 0; $total = $options['created'] ? 1 : 0; DeferredUpdates::addUpdate(new SiteStatsUpdate(0, $edits, $good, $total)); DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content)); // If this is another user's talk page, update newtalk. // Don't do this if $options['changed'] = false (null-edits) nor if // it's a minor edit and the user doesn't want notifications for those. if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) { $recipient = User::newFromName($shortTitle, false); if (!$recipient) { wfDebug(__METHOD__ . ": invalid username\n"); } else { // Allow extensions to prevent user notification // when a new message is added to their talk page if (Hooks::run('ArticleEditUpdateNewTalk', array(&$this, $recipient))) { if (User::isIP($shortTitle)) { // An anonymous user $recipient->setNewtalk(true, $revision); } elseif ($recipient->isLoggedIn()) { $recipient->setNewtalk(true, $revision); } else { wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n"); } } } } if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) { // XXX: could skip pseudo-messages like js/css here, based on content model. $msgtext = $content ? $content->getWikitextForTransclusion() : null; if ($msgtext === false || $msgtext === null) { $msgtext = ''; } MessageCache::singleton()->replace($shortTitle, $msgtext); } if ($options['created']) { self::onArticleCreate($this->mTitle); } elseif ($options['changed']) { // bug 50785 self::onArticleEdit($this->mTitle, $revision); } }
/** * @param Article $article * @param User $user * * @return bool True if successful, else false. */ public function tryParserCache(&$article, $user) { $parserCache = ParserCache::singleton(); $parserOutput = $parserCache->get($article, $user); if ($parserOutput !== false) { $this->addParserOutput($parserOutput); return true; } else { return false; } }
/** * @param Title $title * @return bool */ protected function runForTitle(Title $title = null) { $linkCache = LinkCache::singleton(); $linkCache->clear(); if (is_null($title)) { $this->setLastError("refreshLinks: Invalid title"); return false; } // Wait for the DB of the current/next slave DB handle to catch up to the master. // This way, we get the correct page_latest for templates or files that just changed // milliseconds ago, having triggered this job to begin with. if (isset($this->params['masterPos']) && $this->params['masterPos'] !== false) { wfGetLB()->waitFor($this->params['masterPos']); } $page = WikiPage::factory($title); // Fetch the current revision... $revision = Revision::newFromTitle($title, false, Revision::READ_NORMAL); if (!$revision) { $this->setLastError("refreshLinks: Article not found {$title->getPrefixedDBkey()}"); return false; // XXX: what if it was just deleted? } $content = $revision->getContent(Revision::RAW); if (!$content) { // If there is no content, pretend the content is empty $content = $revision->getContentHandler()->makeEmptyContent(); } $parserOutput = false; $parserOptions = $page->makeParserOptions('canonical'); // If page_touched changed after this root job (with a good slave lag skew factor), // then it is likely that any views of the pages already resulted in re-parses which // are now in cache. This can be reused to avoid expensive parsing in some cases. if (isset($this->params['rootJobTimestamp'])) { $skewedTimestamp = wfTimestamp(TS_UNIX, $this->params['rootJobTimestamp']) + 5; if ($page->getLinksTimestamp() > wfTimestamp(TS_MW, $skewedTimestamp)) { // Something already updated the backlinks since this job was made return true; } if ($page->getTouched() > wfTimestamp(TS_MW, $skewedTimestamp)) { $parserOutput = ParserCache::singleton()->getDirty($page, $parserOptions); if ($parserOutput && $parserOutput->getCacheTime() <= $skewedTimestamp) { $parserOutput = false; // too stale } } } // Fetch the current revision and parse it if necessary... if ($parserOutput == false) { $start = microtime(true); // Revision ID must be passed to the parser output to get revision variables correct $parserOutput = $content->getParserOutput($title, $revision->getId(), $parserOptions, false); $ellapsed = microtime(true) - $start; // If it took a long time to render, then save this back to the cache to avoid // wasted CPU by other apaches or job runners. We don't want to always save to // cache as this can cause high cache I/O and LRU churn when a template changes. if ($ellapsed >= self::PARSE_THRESHOLD_SEC && $page->isParserCacheUsed($parserOptions, $revision->getId()) && $parserOutput->isCacheable()) { $ctime = wfTimestamp(TS_MW, (int) $start); // cache time ParserCache::singleton()->save($parserOutput, $page, $parserOptions, $ctime, $revision->getId()); } } $updates = $content->getSecondaryDataUpdates($title, null, false, $parserOutput); DataUpdate::runUpdates($updates); InfoAction::invalidateCache($title); return true; }
/** * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Purges pages that include this page if the text was changed here. * Every 100th edit, prune the recent changes table. * * @private * @param $revision Revision object * @param $user User object that did the revision * @param $options Array of options, following indexes are used: * - changed: boolean, whether the revision changed the content (default true) * - created: boolean, whether the revision created the page (default false) * - oldcountable: boolean or null (default null): * - boolean: whether the page was counted as an article before that * revision, only used in changed is true and created is false * - null: don't change the article count */ public function doEditUpdates(Revision $revision, User $user, array $options = array()) { global $wgDeferredUpdateList, $wgEnableParserCache; wfProfileIn(__METHOD__); $options += array('changed' => true, 'created' => false, 'oldcountable' => null); $text = $revision->getText(); # Parse the text # Be careful not to double-PST: $text is usually already PST-ed once if (!$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag('vary-revision')) { wfDebug(__METHOD__ . ": No prepared edit or vary-revision is set...\n"); $editInfo = $this->prepareTextForEdit($text, $revision->getId(), $user); } else { wfDebug(__METHOD__ . ": No vary-revision, using prepared edit...\n"); $editInfo = $this->mPreparedEdit; } # Save it to the parser cache if ($wgEnableParserCache) { $parserCache = ParserCache::singleton(); $parserCache->save($editInfo->output, $this, $editInfo->popts); } # Update the links tables $u = new LinksUpdate($this->mTitle, $editInfo->output); $u->doUpdate(); wfRunHooks('ArticleEditUpdates', array(&$this, &$editInfo, $options['changed'])); if (wfRunHooks('ArticleEditUpdatesDeleteFromRecentchanges', array(&$this))) { if (0 == mt_rand(0, 99)) { // Flush old entries from the `recentchanges` table; we do this on // random requests so as to avoid an increase in writes for no good reason global $wgRCMaxAge; $dbw = wfGetDB(DB_MASTER); $cutoff = $dbw->timestamp(time() - $wgRCMaxAge); $dbw->delete('recentchanges', array("rc_timestamp < '{$cutoff}'"), __METHOD__); } } $id = $this->getId(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); if (0 == $id) { wfProfileOut(__METHOD__); return; } if (!$options['changed']) { $good = 0; $total = 0; } elseif ($options['created']) { $good = (int) $this->isCountable($editInfo); $total = 1; } elseif ($options['oldcountable'] !== null) { $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable']; $total = 0; } else { $good = 0; $total = 0; } $wgDeferredUpdateList[] = new SiteStatsUpdate(0, 1, $good, $total); $wgDeferredUpdateList[] = new SearchUpdate($id, $title, $text); # If this is another user's talk page, update newtalk. # Don't do this if $options['changed'] = false (null-edits) nor if # it's a minor edit and the user doesn't want notifications for those. if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) { if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this))) { $other = User::newFromName($shortTitle, false); if (!$other) { wfDebug(__METHOD__ . ": invalid username\n"); } elseif (User::isIP($shortTitle)) { // An anonymous user $other->setNewtalk(true); } elseif ($other->isLoggedIn()) { $other->setNewtalk(true); } else { wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n"); } } } if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) { MessageCache::singleton()->replace($shortTitle, $text); } if ($options['created']) { self::onArticleCreate($this->mTitle); } else { self::onArticleEdit($this->mTitle); } wfProfileOut(__METHOD__); }
/** * Get data of requested article. * @param Title $title * @param boolean $noImages * @return array */ private function getData(Title $title, $noImages) { global $wgMemc, $wgUseTidy, $wgMFTidyMobileViewSections, $wgMFMinCachedPageSize, $wgMFSpecialCaseMainPage; $wp = $this->makeWikiPage($title); if ($this->followRedirects && $wp->isRedirect()) { $newTitle = $wp->getRedirectTarget(); if ($newTitle) { $title = $newTitle; $this->getResult()->addValue(null, $this->getModuleName(), array('redirected' => $title->getPrefixedText())); if ($title->getNamespace() < 0) { $this->getResult()->addValue(null, $this->getModuleName(), array('viewable' => 'no')); return array(); } $wp = $this->makeWikiPage($title); } } $latest = $wp->getLatest(); if ($this->file) { $key = wfMemcKey('mf', 'mobileview', self::CACHE_VERSION, $noImages, $latest, $this->noTransform, $this->file->getSha1(), $this->variant); $cacheExpiry = 3600; } else { if (!$latest) { // https://bugzilla.wikimedia.org/show_bug.cgi?id=53378 // Title::exists() above doesn't seem to always catch recently deleted pages $this->dieUsageMsg(array('notanarticle', $title->getPrefixedText())); } $parserOptions = $this->makeParserOptions($wp); $parserCacheKey = ParserCache::singleton()->getKey($wp, $parserOptions); $key = wfMemcKey('mf', 'mobileview', self::CACHE_VERSION, $noImages, $latest, $this->noTransform, $parserCacheKey); } $data = $wgMemc->get($key); if ($data) { wfIncrStats('mobile.view.cache-hit'); return $data; } wfIncrStats('mobile.view.cache-miss'); if ($this->file) { $html = $this->getFilePage($title); } else { $parserOutput = $this->getParserOutput($wp, $parserOptions); $html = $parserOutput->getText(); $cacheExpiry = $parserOutput->getCacheExpiry(); } if (!$this->noTransform) { $mf = new MobileFormatter(MobileFormatter::wrapHTML($html), $title); $mf->setRemoveMedia($noImages); $mf->filterContent(); $mf->setIsMainPage($this->mainPage && $wgMFSpecialCaseMainPage); $html = $mf->getText(); } if ($this->mainPage || $this->file) { $data = array('sections' => array(), 'text' => array($html), 'refsections' => array()); } else { $data = array(); $data['sections'] = $parserOutput->getSections(); $sectionCount = count($data['sections']); for ($i = 0; $i < $sectionCount; $i++) { $data['sections'][$i]['line'] = $title->getPageLanguage()->convert($data['sections'][$i]['line']); } $chunks = preg_split('/<h(?=[1-6]\\b)/i', $html); if (count($chunks) != count($data['sections']) + 1) { wfDebugLog('mobile', __METHOD__ . "(): mismatching number of " . "sections from parser and split on page {$title->getPrefixedText()}, oldid={$latest}"); // We can't be sure about anything here, return all page HTML as one big section $chunks = array($html); $data['sections'] = array(); } $data['text'] = array(); $data['refsections'] = array(); foreach ($chunks as $chunk) { if (count($data['text'])) { $chunk = "<h{$chunk}"; } if ($wgUseTidy && $wgMFTidyMobileViewSections && count($chunks) > 1) { $chunk = MWTidy::tidy($chunk); } if (preg_match('/<ol\\b[^>]*?class="references"/', $chunk)) { $data['refsections'][count($data['text'])] = true; } $data['text'][] = $chunk; } if ($this->usePageImages) { $image = $this->getPageImage($title); if ($image) { $data['image'] = $image->getTitle()->getText(); } } } $data['lastmodified'] = wfTimestamp(TS_ISO_8601, $wp->getTimestamp()); // Page id $data['id'] = $wp->getId(); $user = User::newFromId($wp->getUser()); if (!$user->isAnon()) { $data['lastmodifiedby'] = array('name' => $wp->getUserText(), 'gender' => $user->getOption('gender')); } else { $data['lastmodifiedby'] = null; } $data['revision'] = $title->getLatestRevID(); if (isset($parserOutput)) { $languages = $parserOutput->getLanguageLinks(); $data['languagecount'] = count($languages); $data['displaytitle'] = $parserOutput->getDisplayTitle(); // @fixme: Does no work for some extension properties that get added in LinksUpdate $data['pageprops'] = $parserOutput->getProperties(); } else { $data['languagecount'] = 0; $data['displaytitle'] = $title->getPrefixedText(); $data['pageprops'] = array(); } if ($title->getPageLanguage()->hasVariants()) { $data['hasvariants'] = true; } // Don't store small pages to decrease cache size requirements if (strlen($html) >= $wgMFMinCachedPageSize) { // store for the same time as original parser output $wgMemc->set($key, $data, $cacheExpiry); } return $data; }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); $title = Title::newFromText($params['page']); if (!$title) { $this->dieUsageMsg('invalidtitle', $params['page']); } $isSafeAction = in_array($params['paction'], self::$SAFE_ACTIONS, true); $availableNamespaces = $this->veConfig->get('VisualEditorAvailableNamespaces'); if (!$isSafeAction && (!isset($availableNamespaces[$title->getNamespace()]) || !$availableNamespaces[$title->getNamespace()])) { $this->dieUsage("VisualEditor is not enabled in namespace " . $title->getNamespace(), 'novenamespace'); } $parserParams = array(); if (isset($params['oldid'])) { $parserParams['oldid'] = $params['oldid']; } $html = $params['html']; if (substr($html, 0, 11) === 'rawdeflate,') { $deflated = base64_decode(substr($html, 11)); wfSuppressWarnings(); $html = gzinflate($deflated); wfRestoreWarnings(); if ($deflated === $html || $html === false) { $this->dieUsage("HTML provided is not properly deflated", 'invaliddeflate'); } } wfDebugLog('visualeditor', "called on '{$title}' with paction: '{$params['paction']}'"); switch ($params['paction']) { case 'parse': case 'metadata': // Dirty hack to provide the correct context for edit notices global $wgTitle; // FIXME NOOOOOOOOES $wgTitle = $title; RequestContext::getMain()->setTitle($title); // Get information about current revision if ($title->exists()) { $latestRevision = Revision::newFromTitle($title); if ($latestRevision === null) { $this->dieUsage('Could not find latest revision for title', 'latestnotfound'); } $revision = null; if (!isset($parserParams['oldid']) || $parserParams['oldid'] === 0) { $parserParams['oldid'] = $latestRevision->getId(); $revision = $latestRevision; } else { $revision = Revision::newFromId($parserParams['oldid']); if ($revision === null) { $this->dieUsage('Could not find revision ID ' . $parserParams['oldid'], 'oldidnotfound'); } } $restoring = $revision && !$revision->isCurrent(); $baseTimestamp = $latestRevision->getTimestamp(); $oldid = intval($parserParams['oldid']); // If requested, request HTML from Parsoid/RESTBase if ($params['paction'] === 'parse') { $content = $this->requestRestbase('GET', 'page/html/' . urlencode($title->getPrefixedDBkey()) . '/' . $oldid, array()); if ($content === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } } } else { $content = ''; $baseTimestamp = wfTimestampNow(); $oldid = 0; $restoring = false; } // Get edit notices $notices = $title->getEditNotices(); // Anonymous user notice if ($user->isAnon()) { $notices[] = $this->msg('anoneditwarning', '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}', '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}')->parseAsBlock(); } // Old revision notice if ($restoring) { $notices[] = $this->msg('editingold')->parseAsBlock(); } // New page notices if (!$title->exists()) { $notices[] = $this->msg($user->isLoggedIn() ? 'newarticletext' : 'newarticletextanon', wfExpandUrl(Skin::makeInternalOrExternalUrl($this->msg('helppage')->inContentLanguage()->text())))->parseAsBlock(); // Page protected from creation if ($title->getRestrictions('create')) { $notices[] = $this->msg('titleprotectedwarning')->parseAsBlock(); } } // Look at protection status to set up notices + surface class(es) $protectedClasses = array(); if (MWNamespace::getRestrictionLevels($title->getNamespace()) !== array('')) { // Page protected from editing if ($title->isProtected('edit')) { # Is the title semi-protected? if ($title->isSemiProtected()) { $protectedClasses[] = 'mw-textarea-sprotected'; $noticeMsg = 'semiprotectedpagewarning'; } else { $protectedClasses[] = 'mw-textarea-protected'; # Then it must be protected based on static groups (regular) $noticeMsg = 'protectedpagewarning'; } $notices[] = $this->msg($noticeMsg)->parseAsBlock() . $this->getLastLogEntry($title, 'protect'); } // Deal with cascading edit protection list($sources, $restrictions) = $title->getCascadeProtectionSources(); if (isset($restrictions['edit'])) { $protectedClasses[] = ' mw-textarea-cprotected'; $notice = $this->msg('cascadeprotectedwarning')->parseAsBlock() . '<ul>'; // Unfortunately there's no nice way to get only the pages which cause // editing to be restricted foreach ($sources as $source) { $notice .= "<li>" . Linker::link($source) . "</li>"; } $notice .= '</ul>'; $notices[] = $notice; } } // Permission notice $permErrors = $title->getUserPermissionsErrors('create', $user); if ($permErrors && !$title->exists()) { $notices[] = $this->msg('permissionserrorstext-withaction', 1, $this->msg('action-createpage')) . "<br>" . call_user_func_array(array($this, 'msg'), $permErrors[0])->parse(); } // Show notice when editing user / user talk page of a user that doesn't exist // or who is blocked // HACK of course this code is partly duplicated from EditPage.php :( if ($title->getNamespace() == NS_USER || $title->getNamespace() == NS_USER_TALK) { $parts = explode('/', $title->getText(), 2); $targetUsername = $parts[0]; $targetUser = User::newFromName($targetUsername, false); if (!($targetUser && $targetUser->isLoggedIn()) && !User::isIP($targetUsername)) { // User does not exist $notices[] = "<div class=\"mw-userpage-userdoesnotexist error\">\n" . $this->msg('userpage-userdoesnotexist', wfEscapeWikiText($targetUsername)) . "\n</div>"; } elseif ($targetUser->isBlocked()) { // Show log extract if the user is currently blocked $notices[] = $this->msg('blocked-notice-logextract', $targetUser->getName())->parseAsBlock() . $this->getLastLogEntry($targetUser->getUserPage(), 'block'); } } // Blocked user notice if ($user->isBlockedFrom($title) && $user->getBlock()->prevents('edit') !== false) { $notices[] = call_user_func_array(array($this, 'msg'), $user->getBlock()->getPermissionsError($this->getContext()))->parseAsBlock(); } // Blocked user notice for global blocks if (class_exists('GlobalBlocking')) { $error = GlobalBlocking::getUserBlockErrors($user, $this->getRequest()->getIP()); if (count($error)) { $notices[] = call_user_func_array(array($this, 'msg'), $error)->parseAsBlock(); } } // HACK: Build a fake EditPage so we can get checkboxes from it $article = new Article($title); // Deliberately omitting ,0 so oldid comes from request $ep = new EditPage($article); $req = $this->getRequest(); $req->setVal('format', 'text/x-wiki'); $ep->importFormData($req); // By reference for some reason (bug 52466) $tabindex = 0; $states = array('minor' => false, 'watch' => false); $checkboxes = $ep->getCheckboxes($tabindex, $states); // HACK: Find out which red links are on the page // We do the lookup for the current version. This might not be entirely complete // if we're loading an oldid, but it'll probably be close enough, and LinkCache // will automatically request any additional data it needs. $links = array(); $wikipage = WikiPage::factory($title); $popts = $wikipage->makeParserOptions('canonical'); $cached = ParserCache::singleton()->get($article, $popts, true); $links = array('missing' => array(), 'known' => $restoring || !$cached ? array() : 1); if ($cached) { foreach ($cached->getLinks() as $namespace => $cachedTitles) { foreach ($cachedTitles as $cachedTitleText => $exists) { $cachedTitle = Title::makeTitle($namespace, $cachedTitleText); if (!$cachedTitle->isKnown()) { $links['missing'][] = $cachedTitle->getPrefixedText(); } elseif ($links['known'] !== 1) { $links['known'][] = $cachedTitle->getPrefixedText(); } } } } // Add information about current page if (!$title->isKnown()) { $links['missing'][] = $title->getPrefixedText(); } elseif ($links['known'] !== 1) { $links['known'][] = $title->getPrefixedText(); } // On parser cache miss, just don't bother populating red link data $result = array('result' => 'success', 'notices' => $notices, 'checkboxes' => $checkboxes, 'links' => $links, 'protectedClasses' => implode(' ', $protectedClasses), 'watched' => $user->isWatched($title), 'basetimestamp' => $baseTimestamp, 'starttimestamp' => wfTimestampNow(), 'oldid' => $oldid); if ($params['paction'] === 'parse') { $result['content'] = $content; } break; case 'parsefragment': $wikitext = $params['wikitext']; if ($params['pst']) { $wikitext = $this->pstWikitext($title, $wikitext); } $content = $this->parseWikitextFragment($title, $wikitext); if ($content === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } else { $result = array('result' => 'success', 'content' => $content); } break; case 'serialize': if ($params['cachekey'] !== null) { $content = $this->trySerializationCache($params['cachekey']); if (!is_string($content)) { $this->dieUsage('No cached serialization found with that key', 'badcachekey'); } } else { if ($params['html'] === null) { $this->dieUsageMsg('missingparam', 'html'); } $content = $this->postHTML($title, $html, $parserParams, $params['etag']); if ($content === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } } $result = array('result' => 'success', 'content' => $content); break; case 'diff': if ($params['cachekey'] !== null) { $wikitext = $this->trySerializationCache($params['cachekey']); if (!is_string($wikitext)) { $this->dieUsage('No cached serialization found with that key', 'badcachekey'); } } else { $wikitext = $this->postHTML($title, $html, $parserParams, $params['etag']); if ($wikitext === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } } $diff = $this->diffWikitext($title, $wikitext); if ($diff['result'] === 'fail') { $this->dieUsage('Diff failed', 'difffailed'); } $result = $diff; break; case 'serializeforcache': if (!isset($parserParams['oldid'])) { $parserParams['oldid'] = Revision::newFromTitle($title)->getId(); } $key = $this->storeInSerializationCache($title, $parserParams['oldid'], $html, $params['etag']); $result = array('result' => 'success', 'cachekey' => $key); break; case 'getlanglinks': $langlinks = $this->getLangLinks($title); if ($langlinks === false) { $this->dieUsage('Error querying MediaWiki API', 'api-langlinks-error'); } else { $result = array('result' => 'success', 'langlinks' => $langlinks); } break; } $this->getResult()->addValue(null, $this->getModuleName(), $result); }
/** * @deprecated * * @param $article Article * @return Boolean: true if successful, else false. */ public function tryParserCache(&$article) { wfDeprecated(__METHOD__); $parserOutput = ParserCache::singleton()->get($article, $article->getParserOptions()); if ($parserOutput !== false) { $this->addParserOutput($parserOutput); return true; } else { return false; } }
/** * Fetches a given article from the parser cache * * @return $result string * @param $article Object */ protected function fetchParserCache(&$article, &$id) { global $wgUser; $parserCache =& ParserCache::singleton(); $po = $parserCache->get($article, $wgUser); if (is_object($po)) { $id = $po->getCacheTime(); return $po->getText(); } return null; }
public function execute() { // Get parameters $params = $this->extractRequestParams(); $text = $params['text']; $title = $params['title']; $page = $params['page']; if (!is_null($page) && (!is_null($text) || $title != "API")) { $this->dieUsage("The page parameter cannot be used together with the text and title parameters", 'params'); } $prop = array_flip($params['prop']); global $wgParser, $wgUser; if (!is_null($page)) { $titleObj = Title::newFromText($page); if (!$titleObj) { $this->dieUsageMsg(array('missingtitle', $page)); } // Try the parser cache first $articleObj = new Article($titleObj); $pcache =& ParserCache::singleton(); $p_result = $pcache->get($articleObj, $wgUser); if (!$p_result) { $p_result = $wgParser->parse($articleObj->getContent(), $titleObj, new ParserOptions()); global $wgUseParserCache; if ($wgUseParserCache) { $pcache->save($p_result, $articleObj, $wgUser); } } } else { $titleObj = Title::newFromText($title); if (!$titleObj) { $titleObj = Title::newFromText("API"); } $p_result = $wgParser->parse($text, $titleObj, new ParserOptions()); } // Return result $result = $this->getResult(); $result_array = array(); if (isset($prop['text'])) { $result_array['text'] = array(); $result->setContent($result_array['text'], $p_result->getText()); } if (isset($prop['langlinks'])) { $result_array['langlinks'] = $this->formatLangLinks($p_result->getLanguageLinks()); } if (isset($prop['categories'])) { $result_array['categories'] = $this->formatCategoryLinks($p_result->getCategories()); } if (isset($prop['links'])) { $result_array['links'] = $this->formatLinks($p_result->getLinks()); } if (isset($prop['templates'])) { $result_array['templates'] = $this->formatLinks($p_result->getTemplates()); } if (isset($prop['images'])) { $result_array['images'] = array_keys($p_result->getImages()); } if (isset($prop['externallinks'])) { $result_array['externallinks'] = array_keys($p_result->getExternalLinks()); } if (isset($prop['sections'])) { $result_array['sections'] = $p_result->getSections(); } $result_mapping = array('langlinks' => 'll', 'categories' => 'cl', 'links' => 'pl', 'templates' => 'tl', 'images' => 'img', 'externallinks' => 'el', 'sections' => 's'); $this->setIndexedTagNames($result_array, $result_mapping); $result->addValue(null, $this->getModuleName(), $result_array); }
function getCachedWork() { global $wgOut; $parserCache = ParserCache::singleton(); $this->mArticle->mParserOutput = $parserCache->get($this->mArticle, $this->parserOptions); if ($this->mArticle->mParserOutput !== false) { wfDebug(__METHOD__ . ": showing contents parsed by someone else\n"); $wgOut->addParserOutput($this->mArticle->mParserOutput); # Ensure that UI elements requiring revision ID have # the correct version information. $wgOut->setRevisionId($this->mArticle->getLatest()); return true; } return false; }
/** * Retrieves an article parsed output either from parser cache or by * parsing it again. If parsing again, stores it back into parser cache. * * @param $title Article title object. * @param $feed Whether the result should be part of a feed. * @return Two-element array containing the article and its parser output. * * @note Mw1.16+ provides Article::getParserOptions() and * Article::getParserOutput(), that could be used here in the future. * The problem is that getParserOutput() uses ParserCache exclusively, * which means that only ParserOptions control the key used to store * the output in the cache and there is no hook yet in * ParserCache::getKey() to set these extra bits (and the * 'PageRenderingCache' hook is not useful here, it is in the wrong * place without access to the parser options). This is certainly * something that should be fixed in the future. FIXME * * @note This function makes a clone of the parser if * $wgWikilogCloneParser is set, but cloning the parser is not * officially supported. The problem here is that we need a different * parser that we could mess up without interfering with normal page * rendering, and we can't create a new instance because of too many * broken extensions around. Check self::parserSanityCheck(). */ public static function parsedArticle( Title $title, $feed = false ) { global $wgWikilogCloneParser; global $wgUser, $wgEnableParserCache; global $wgParser, $wgParserConf; static $parser = null; $article = new Article( $title ); # First try the parser cache. $useParserCache = $wgEnableParserCache && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 && $article->exists(); $parserCache = ParserCache::singleton(); # Parser options. $parserOpt = ParserOptions::newFromUser( $wgUser ); $parserOpt->setTidy( true ); if ( $feed ) { $parserOpt->setEditSection( false ); $parserOpt->addExtraKey( "WikilogFeed" ); } else { $parserOpt->enableLimitReport(); } if ( $useParserCache ) { # Look for the parsed article output in the parser cache. $parserOutput = $parserCache->get( $article, $parserOpt ); # On success, return the object retrieved from the cache. if ( $parserOutput ) { return array( $article, $parserOutput ); } } # Enable some feed-specific behavior. if ( $feed ) { $saveFeedParse = WikilogParser::enableFeedParsing(); $saveExpUrls = WikilogParser::expandLocalUrls(); } # Get a parser instance, if not already cached. if ( is_null( $parser ) ) { if ( !StubObject::isRealObject( $wgParser ) ) { $wgParser->_unstub(); } if ( $wgWikilogCloneParser ) { $parser = clone $wgParser; } else { $class = $wgParserConf['class']; $parser = new $class( $wgParserConf ); } } $parser->startExternalParse( $title, $parserOpt, Parser::OT_HTML ); # Parse article. $arttext = $article->fetchContent(); $parserOutput = $parser->parse( $arttext, $title, $parserOpt ); # Save in parser cache. if ( $useParserCache && $parserOutput->getCacheTime() != -1 ) { $parserCache->save( $parserOutput, $article, $parserOpt ); } # Restore default behavior. if ( $feed ) { WikilogParser::enableFeedParsing( $saveFeedParse ); WikilogParser::expandLocalUrls( $saveExpUrls ); } return array( $article, $parserOutput ); }
/** * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Purges pages that include this page if the text was changed here. * Every 100th edit, prune the recent changes table. * * @param Revision $revision * @param User $user User object that did the revision * @param array $options Array of options, following indexes are used: * - changed: boolean, whether the revision changed the content (default true) * - created: boolean, whether the revision created the page (default false) * - moved: boolean, whether the page was moved (default false) * - restored: boolean, whether the page was undeleted (default false) * - oldrevision: Revision object for the pre-update revision (default null) * - oldcountable: boolean, null, or string 'no-change' (default null): * - boolean: whether the page was counted as an article before that * revision, only used in changed is true and created is false * - null: if created is false, don't update the article count; if created * is true, do update the article count * - 'no-change': don't update the article count, ever */ public function doEditUpdates(Revision $revision, User $user, array $options = []) { global $wgRCWatchCategoryMembership, $wgContLang; $options += ['changed' => true, 'created' => false, 'moved' => false, 'restored' => false, 'oldrevision' => null, 'oldcountable' => null]; $content = $revision->getContent(); $logger = LoggerFactory::getInstance('SaveParse'); // See if the parser output before $revision was inserted is still valid $editInfo = false; if (!$this->mPreparedEdit) { $logger->debug(__METHOD__ . ": No prepared edit...\n"); } elseif ($this->mPreparedEdit->output->getFlag('vary-revision')) { $logger->info(__METHOD__ . ": Prepared edit has vary-revision...\n"); } elseif ($this->mPreparedEdit->output->getFlag('vary-revision-id') && $this->mPreparedEdit->output->getSpeculativeRevIdUsed() !== $revision->getId()) { $logger->info(__METHOD__ . ": Prepared edit has vary-revision-id with wrong ID...\n"); } elseif ($this->mPreparedEdit->output->getFlag('vary-user') && !$options['changed']) { $logger->info(__METHOD__ . ": Prepared edit has vary-user and is null...\n"); } else { wfDebug(__METHOD__ . ": Using prepared edit...\n"); $editInfo = $this->mPreparedEdit; } if (!$editInfo) { // Parse the text again if needed. Be careful not to do pre-save transform twice: // $text is usually already pre-save transformed once. Avoid using the edit stash // as any prepared content from there or in doEditContent() was already rejected. $editInfo = $this->prepareContentForEdit($content, $revision, $user, null, false); } // Save it to the parser cache. // Make sure the cache time matches page_touched to avoid double parsing. ParserCache::singleton()->save($editInfo->output, $this, $editInfo->popts, $revision->getTimestamp(), $editInfo->revid); // Update the links tables and other secondary data if ($content) { $recursive = $options['changed']; // bug 50785 $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, $recursive, $editInfo->output); foreach ($updates as $update) { if ($update instanceof LinksUpdate) { $update->setRevision($revision); $update->setTriggeringUser($user); } DeferredUpdates::addUpdate($update); } if ($wgRCWatchCategoryMembership && $this->getContentHandler()->supportsCategories() === true && ($options['changed'] || $options['created']) && !$options['restored']) { // Note: jobs are pushed after deferred updates, so the job should be able to see // the recent change entry (also done via deferred updates) and carry over any // bot/deletion/IP flags, ect. JobQueueGroup::singleton()->lazyPush(new CategoryMembershipChangeJob($this->getTitle(), ['pageId' => $this->getId(), 'revTimestamp' => $revision->getTimestamp()])); } } Hooks::run('ArticleEditUpdates', [&$this, &$editInfo, $options['changed']]); if (Hooks::run('ArticleEditUpdatesDeleteFromRecentchanges', [&$this])) { // Flush old entries from the `recentchanges` table if (mt_rand(0, 9) == 0) { JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newPurgeJob()); } } if (!$this->exists()) { return; } $id = $this->getId(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); if ($options['oldcountable'] === 'no-change' || !$options['changed'] && !$options['moved']) { $good = 0; } elseif ($options['created']) { $good = (int) $this->isCountable($editInfo); } elseif ($options['oldcountable'] !== null) { $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable']; } else { $good = 0; } $edits = $options['changed'] ? 1 : 0; $total = $options['created'] ? 1 : 0; DeferredUpdates::addUpdate(new SiteStatsUpdate(0, $edits, $good, $total)); DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content)); // If this is another user's talk page, update newtalk. // Don't do this if $options['changed'] = false (null-edits) nor if // it's a minor edit and the user doesn't want notifications for those. if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) { $recipient = User::newFromName($shortTitle, false); if (!$recipient) { wfDebug(__METHOD__ . ": invalid username\n"); } else { // Allow extensions to prevent user notification // when a new message is added to their talk page if (Hooks::run('ArticleEditUpdateNewTalk', [&$this, $recipient])) { if (User::isIP($shortTitle)) { // An anonymous user $recipient->setNewtalk(true, $revision); } elseif ($recipient->isLoggedIn()) { $recipient->setNewtalk(true, $revision); } else { wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n"); } } } } if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) { // XXX: could skip pseudo-messages like js/css here, based on content model. $msgtext = $content ? $content->getWikitextForTransclusion() : null; if ($msgtext === false || $msgtext === null) { $msgtext = ''; } MessageCache::singleton()->replace($shortTitle, $msgtext); if ($wgContLang->hasVariants()) { $wgContLang->updateConversionTable($this->mTitle); } } if ($options['created']) { self::onArticleCreate($this->mTitle); } elseif ($options['changed']) { // bug 50785 self::onArticleEdit($this->mTitle, $revision); } ResourceLoaderWikiModule::invalidateModuleCache($this->mTitle, $options['oldrevision'], $revision, wfWikiID()); }
/** Lightweight method to get the parser output for a page, checking the parser cache * and so on. Doesn't consider most of the stuff that Article::view is forced to * consider, so it's not appropriate to use there. */ function getParserOutput($oldid = null) { global $wgEnableParserCache, $wgUser, $wgOut; // Should the parser cache be used? $useParserCache = $wgEnableParserCache && intval($wgUser->getOption('stubthreshold')) == 0 && $this->exists() && $oldid === null; wfDebug(__METHOD__ . ': using parser cache: ' . ($useParserCache ? 'yes' : 'no') . "\n"); if ($wgUser->getOption('stubthreshold')) { wfIncrStats('pcache_miss_stub'); } $parserOutput = false; if ($useParserCache) { $parserOutput = ParserCache::singleton()->get($this, $this->getParserOptions()); } if ($parserOutput === false) { // Cache miss; parse and output it. $rev = Revision::newFromTitle($this->getTitle(), $oldid); return $this->getOutputFromWikitext($rev->getText(), $useParserCache); } else { return $parserOutput; } }
/** * @param Title $title * @return bool */ protected function runForTitle(Title $title) { // Wait for the DB of the current/next slave DB handle to catch up to the master. // This way, we get the correct page_latest for templates or files that just changed // milliseconds ago, having triggered this job to begin with. if (isset($this->params['masterPos']) && $this->params['masterPos'] !== false) { wfGetLB()->waitFor($this->params['masterPos']); } // Fetch the current page and revision... $page = WikiPage::factory($title); $revision = Revision::newFromTitle($title, false, Revision::READ_NORMAL); if (!$revision) { $this->setLastError("refreshLinks: Article not found {$title->getPrefixedDBkey()}"); return false; // XXX: what if it was just deleted? } $content = $revision->getContent(Revision::RAW); if (!$content) { // If there is no content, pretend the content is empty $content = $revision->getContentHandler()->makeEmptyContent(); } $parserOutput = false; $parserOptions = $page->makeParserOptions('canonical'); // If page_touched changed after this root job, then it is likely that // any views of the pages already resulted in re-parses which are now in // cache. The cache can be reused to avoid expensive parsing in some cases. if (isset($this->params['rootJobTimestamp'])) { $opportunistic = !empty($this->params['isOpportunistic']); $skewedTimestamp = $this->params['rootJobTimestamp']; if ($opportunistic) { // Neither clock skew nor DB snapshot/slave lag matter much for such // updates; focus on reusing the (often recently updated) cache } else { // For transclusion updates, the template changes must be reflected $skewedTimestamp = wfTimestamp(TS_MW, wfTimestamp(TS_UNIX, $skewedTimestamp) + self::CLOCK_FUDGE); } if ($page->getLinksTimestamp() > $skewedTimestamp) { // Something already updated the backlinks since this job was made return true; } if ($page->getTouched() >= $skewedTimestamp || $opportunistic) { // Something bumped page_touched since this job was made // or the cache is otherwise suspected to be up-to-date $parserOutput = ParserCache::singleton()->getDirty($page, $parserOptions); if ($parserOutput && $parserOutput->getCacheTime() < $skewedTimestamp) { $parserOutput = false; // too stale } } } // Fetch the current revision and parse it if necessary... if ($parserOutput == false) { $start = microtime(true); // Revision ID must be passed to the parser output to get revision variables correct $parserOutput = $content->getParserOutput($title, $revision->getId(), $parserOptions, false); $elapsed = microtime(true) - $start; // If it took a long time to render, then save this back to the cache to avoid // wasted CPU by other apaches or job runners. We don't want to always save to // cache as this can cause high cache I/O and LRU churn when a template changes. if ($elapsed >= self::PARSE_THRESHOLD_SEC && $page->shouldCheckParserCache($parserOptions, $revision->getId()) && $parserOutput->isCacheable()) { $ctime = wfTimestamp(TS_MW, (int) $start); // cache time ParserCache::singleton()->save($parserOutput, $page, $parserOptions, $ctime, $revision->getId()); } } $updates = $content->getSecondaryDataUpdates($title, null, !empty($this->params['useRecursiveLinksUpdate']), $parserOutput); foreach ($updates as $key => $update) { if ($update instanceof LinksUpdate) { if (!empty($this->params['triggeredRecursive'])) { $update->setTriggeredRecursive(); } if (!empty($this->params['triggeringUser'])) { $userInfo = $this->params['triggeringUser']; if ($userInfo['userId']) { $user = User::newFromId($userInfo['userId']); } else { // Anonymous, use the username $user = User::newFromName($userInfo['userName'], false); } $update->setTriggeringUser($user); } if (!empty($this->params['triggeringRevisionId'])) { $revision = Revision::newFromId($this->params['triggeringRevisionId']); if ($revision === null) { $revision = Revision::newFromId($this->params['triggeringRevisionId'], Revision::READ_LATEST); } $update->setRevision($revision); } } } DataUpdate::runUpdates($updates); InfoAction::invalidateCache($title); return true; }
static function clearCache() { if (self::$cache_control) { global $parserMemc; $parserCache = ParserCache::singleton(); $key = $parserCache->getKey(self::$article, self::$user); $parserMemc->delete($key); if (method_exists('Article', 'doPurge')) { self::$article->doPurge(); } else { WikiPage::factory(self::$title)->doPurge(); } } }
/** * Show the new revision of the page. */ function renderNewRevision() { global $wgOut, $wgUser; wfProfileIn(__METHOD__); # Add "current version as of X" title $wgOut->addHTML("<hr class='diff-hr' />\n\t\t<h2 class='diff-currentversion-title'>{$this->mPagetitle}</h2>\n"); # Page content may be handled by a hooked call instead... if (wfRunHooks('ArticleContentOnDiff', array($this, $wgOut))) { # Use the current version parser cache if applicable $pCache = true; if (!$this->mNewRev->isCurrent()) { $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection(false); $pCache = false; } $this->loadNewText(); $wgOut->setRevisionId($this->mNewRev->getId()); if ($this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage()) { // Stolen from Article::view --AG 2007-10-11 // Give hooks a chance to customise the output // @TODO: standardize this crap into one function if (wfRunHooks('ShowRawCssJs', array($this->mNewtext, $this->mTitle, $wgOut))) { // Wrap the whole lot in a <pre> and don't parse $m = array(); preg_match('!\\.(css|js)$!u', $this->mTitle->getText(), $m); $wgOut->addHTML("<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n"); $wgOut->addHTML(htmlspecialchars($this->mNewtext)); $wgOut->addHTML("\n</pre>\n"); } } elseif ($pCache) { $article = new Article($this->mTitle, 0); $pOutput = ParserCache::singleton()->get($article, $wgOut->parserOptions()); if ($pOutput) { $wgOut->addParserOutput($pOutput); } else { $article->doViewParse(); } } else { $wgOut->addWikiTextTidy($this->mNewtext); } if (!$this->mNewRev->isCurrent()) { $wgOut->parserOptions()->setEditSection($oldEditSectionSetting); } } # Add redundant patrol link on bottom... if ($this->mRcidMarkPatrolled && $this->mTitle->quickUserCan('patrol')) { $sk = $wgUser->getSkin(); $token = $wgUser->editToken($this->mRcidMarkPatrolled); $wgOut->preventClickjacking(); $wgOut->addHTML("<div class='patrollink'>[" . $sk->link($this->mTitle, wfMsgHtml('markaspatrolleddiff'), array(), array('action' => 'markpatrolled', 'rcid' => $this->mRcidMarkPatrolled, 'token' => $token)) . ']</div>'); } wfProfileOut(__METHOD__); }
/** * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Every 1000th edit, prune the recent changes table. * * @private * @param $text New text of the article * @param $summary Edit summary * @param $minoredit Minor edit * @param $timestamp_of_pagechange Timestamp associated with the page change * @param $newid rev_id value of the new revision * @param $changed Whether or not the content actually changed */ function editUpdates($text, $summary, $minoredit, $timestamp_of_pagechange, $newid, $changed = true) { global $wgDeferredUpdateList, $wgMessageCache, $wgUser, $wgParser; wfProfileIn(__METHOD__); # Parse the text $options = new ParserOptions(); $options->setTidy(true); $poutput = $wgParser->parse($text, $this->mTitle, $options, true, true, $newid); # Save it to the parser cache $parserCache =& ParserCache::singleton(); $parserCache->save($poutput, $this, $wgUser); # Update the links tables $u = new LinksUpdate($this->mTitle, $poutput); $u->doUpdate(); if (wfRunHooks('ArticleEditUpdatesDeleteFromRecentchanges', array(&$this))) { wfSeedRandom(); if (0 == mt_rand(0, 999)) { # Periodically flush old entries from the recentchanges table. global $wgRCMaxAge; $dbw =& wfGetDB(DB_MASTER); $cutoff = $dbw->timestamp(time() - $wgRCMaxAge); $recentchanges = $dbw->tableName('recentchanges'); $sql = "DELETE FROM {$recentchanges} WHERE rc_timestamp < '{$cutoff}'"; $dbw->query($sql); } } $id = $this->getID(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); if (0 == $id) { wfProfileOut(__METHOD__); return; } $u = new SiteStatsUpdate(0, 1, $this->mGoodAdjustment, $this->mTotalAdjustment); array_push($wgDeferredUpdateList, $u); $u = new SearchUpdate($id, $title, $text); array_push($wgDeferredUpdateList, $u); # If this is another user's talk page, update newtalk # Don't do this if $changed = false otherwise some idiot can null-edit a # load of user talk pages and piss people off, nor if it's a minor edit # by a properly-flagged bot. if ($this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed && !($minoredit && $wgUser->isAllowed('nominornewtalk'))) { if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this))) { $other = User::newFromName($shortTitle); if (is_null($other) && User::isIP($shortTitle)) { // An anonymous user $other = new User(); $other->setName($shortTitle); } if ($other) { $other->setNewtalk(true); } } } if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) { $wgMessageCache->replace($shortTitle, $text); } wfProfileOut(__METHOD__); }