public function execute() { $action = InfoAction::newFromContext($this->getContext()); $action->doAction(); $this->setAction($action); $this->content = $this->initContent(); }
function showPage() { if ($this->minimal) { // Even more minimal -- we're in a machine API // and don't want to flood the output. $this->extraHeaders(); $this->showContent(); } else { parent::showPage(); } // We don't want to have any more output after this exit; }
/** * Purge caches on page update etc * * @param $title Title object * @todo Verify that $title is always a Title object (and never false or null), add Title hint to parameter $title */ public static function onArticleEdit( $title ) { // Invalidate caches of articles which include this page DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' ); // Invalidate the caches of all pages which redirect here DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' ); // Purge squid for this page only $title->purgeSquid(); // Clear file cache for this page only HTMLFileCache::clearFileCache( $title ); InfoAction::invalidateCache( $title ); }
/** * @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; }
/** * Purge caches on page update etc * * @param Title $title * @param Revision|null $revision Revision that was just saved, may be null */ public static function onArticleEdit(Title $title, Revision $revision = null) { // Invalidate caches of articles which include this page DeferredUpdates::addUpdate(new HTMLCacheUpdate($title, 'templatelinks')); // Invalidate the caches of all pages which redirect here DeferredUpdates::addUpdate(new HTMLCacheUpdate($title, 'redirect')); MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle($title); // Purge CDN for this page only $title->purgeSquid(); // Clear file cache for this page only HTMLFileCache::clearFileCache($title); $revid = $revision ? $revision->getId() : null; DeferredUpdates::addCallableUpdate(function () use($title, $revid) { InfoAction::invalidateCache($title, $revid); }); }
/** * @param $title Title * @param $revision Revision * @param $fname string * @return void */ public static function runForTitleInternal(Title $title, Revision $revision, $fname) { wfProfileIn($fname); $content = $revision->getContent(Revision::RAW); if (!$content) { // if there is no content, pretend the content is empty $content = $revision->getContentHandler()->makeEmptyContent(); } // Revision ID must be passed to the parser output to get revision variables correct $parserOutput = $content->getParserOutput($title, $revision->getId(), null, false); $updates = $content->getSecondaryDataUpdates($title, null, false, $parserOutput); DataUpdate::runUpdates($updates); InfoAction::invalidateCache($title); wfProfileOut($fname); }
/** * Be careful to never throw exceptions from here if we're already * on the Error500Page. e.g. if content of FooPage is empty, this throws * an exception but then index.php instantiates a new page (Error500Page), * which does have content. So any exception thrown from here (either directly * or indirectly from an Action class), should either be caught or made sure * that it doesn't occurr for a Error500Page). */ public function output() { $this->execute(); if (!$this->getContent()) { throw new SwarmException("Page `content` must not be empty."); } if (!$this->getTitle()) { throw new SwarmException("Page `title` must not be empty."); } if (headers_sent($filename, $linenum)) { throw new SwarmException("Headers already sent in `{$filename}` on line {$linenum}."); } header("Content-Type: text/html; charset=utf-8"); $request = $this->getContext()->getRequest(); // ProjectsAction could throw an exception, which needs to be caught here, // since Error500Page (exception handler) also uses Page::output() eventually. // @todo: Find a cleaner way to deal with exceptions in the final page out, // because page output is also used on the Error500Page. $projects = array(); if (!isset($this->exceptionObj)) { try { $projectsActionContext = $this->getContext()->createDerivedRequestContext(array("action" => "projects", "sort" => "name", "sort_oder" => "asc")); $projectsAction = ProjectsAction::newFromContext($projectsActionContext); $projectsAction->doAction(); $projects = $projectsAction->getData(); } catch (Exception $e) { $pageObj = Error500Page::newFromContext($this->getContext()); $pageObj->setExceptionObj($e); $pageObj->output(); exit; } } ?> <!DOCTYPE html> <html lang="en" dir="ltr"> <head> <?php foreach ($this->metaTags as $metaTag) { echo "\t" . html_tag("meta", $metaTag) . "\n"; } $subTitleSuffix = $this->getSubTitle() ? ": {$this->getSubTitle()}" : ""; $htmlTitle = $this->getTitle() . $subTitleSuffix . " - " . $this->getContext()->getConf()->web->title; $displayTitleHtml = $this->getDisplayTitleHtml(); ?> <title><?php echo htmlentities($htmlTitle); ?> </title> <link rel="stylesheet" href="<?php echo swarmpath("css/bootstrap.min.css"); ?> "> <link rel="stylesheet" href="<?php echo swarmpath("css/testswarm.css"); ?> "> <script src="<?php echo swarmpath("js/jquery.js"); ?> "></script> <script src="<?php echo swarmpath("js/bootstrap-dropdown.js"); ?> "></script> <script>window.SWARM = <?php $infoAction = InfoAction::newFromContext($this->getContext()); $infoAction->doAction(); echo json_encode($infoAction->getData()); ?> ;</script><?php foreach ($this->styleSheets as $styleSheet) { echo "\n\t" . html_tag("link", array("rel" => "stylesheet", "href" => $styleSheet)); } foreach ($this->headScripts as $headScript) { echo "\n\t" . html_tag("script", array("src" => $headScript)); } ?> </head> <body> <div class="navbar navbar-fixed-top"> <div class="navbar-inner"> <div class="container"> <a class="brand" href="<?php echo swarmpath(""); ?> "><?php echo htmlspecialchars($this->getContext()->getConf()->web->title); ?> </a> <div class="nav-collapse"> <ul class="nav"> <li><a href="<?php echo swarmpath(""); ?> ">Home</a></li> <li class="dropdown" id="swarm-projectsmenu"> <a href="<?php echo swarmpath("projects"); ?> " class="dropdown-toggle" data-toggle="dropdown" data-target="#swarm-projectsmenu"> Projects <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><a href="<?php echo swarmpath("projects"); ?> ">All projects</a></li> <li class="divider"></li> <li class="nav-header">Projects</li> <?php foreach ($projects as $project) { ?> <li><a href="<?php echo htmlspecialchars(swarmpath("user/{$project["name"]}")); ?> "><?php echo htmlspecialchars($project["name"]); ?> </a></li> <?php } ?> </ul> </li> <li><a href="<?php echo swarmpath("scores"); ?> ">Scores</a></li> <li><a href="<?php echo swarmpath("about"); ?> ">About</a></li> </ul> <ul class="nav pull-right"> <?php if ($request->getSessionData("username") && $request->getSessionData("auth") == "yes") { $username = htmlspecialchars($request->getSessionData("username")); ?> <li><a href="<?php echo swarmpath("user/{$username}"); ?> ">Hello, <?php echo $username; ?> !</a></li> <li><a href="<?php echo swarmpath("run/{$username}"); ?> ">Join the Swarm</a></li> <li><a href="<?php echo swarmpath("logout"); ?> ">Logout</a></li> <?php } else { ?> <li><a href="<?php echo swarmpath("login"); ?> ">Login</a></li> <li><a href="<?php echo swarmpath("signup"); ?> ">Signup</a></li> <?php } ?> </ul> </div><!--/.nav-collapse --> </div> </div> </div> <div class="container"> <div class="hero-unit"> <h1><?php echo $displayTitleHtml; ?> </h1> </div> <?php echo $this->getContent(); ?> <hr> <footer class="swarm-page-footer"> <p>Powered by <a href="//github.com/jquery/testswarm">TestSwarm</a>: <a href="//github.com/jquery/testswarm">Source Code</a> | <a href="//github.com/jquery/testswarm/issues">Issue Tracker</a> | <a href="//github.com/jquery/testswarm/wiki">About</a> | <a href="//twitter.com/testswarm">Twitter</a> </p> </footer> </div> <script src="<?php echo swarmpath("js/pretty.js"); ?> "></script> <script src="<?php echo swarmpath("js/testswarm.js"); ?> "></script><?php foreach ($this->bodyScripts as $bodyScript) { echo "\n\t" . html_tag("script", array("src" => $bodyScript)); } if ($this->getContext()->getConf()->debug->dbLogQueries) { $queryLog = $this->getContext()->getDB()->getQueryLog(); $queryLogHtml = '<hr><h3>Database query log</h3><div class="well"><ul class="unstyled">'; foreach ($queryLog as $i => $queryInfo) { if ($i !== 0) { $queryLogHtml .= '<hr>'; } $queryLogHtml .= '<li>' . '<pre>' . htmlspecialchars($queryInfo["sql"]) . '</pre>' . '<table class="table table-bordered table-condensed"><tbody><tr>' . '<td>Caller: <code>' . htmlspecialchars($queryInfo["caller"]) . '</code></td>' . '<td>Num rows: <code>' . htmlspecialchars($queryInfo["numRows"]) . '</code></td>' . '<td>Insert ID: <code>' . htmlspecialchars($queryInfo["insertId"]) . '</code></td>' . '<td>Affected rows: <code>' . htmlspecialchars($queryInfo["affectedRows"]) . '</code></td>' . '<td>Query time: <code>' . htmlspecialchars(substr($queryInfo["queryTime"], 0, 8)) . '</code></td>' . '</tr></table>' . '</li>'; } $queryLogHtml .= '</ul>'; echo $queryLogHtml; } ?> </body> </html> <?php // End of Page::output }
/** * @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; }
function showFooter() { if ($this->desktopMode == false) { parent::showFooter(); } }
/** * Be careful to never throw exceptions from here if we're already * on the Error500Page. e.g. if content of FooPage is empty, this throws * an exception but then index.php instantiates a new page (Error500Page), * which does have content. So any exception thrown from here (either directly * or indirectly from an Action class), should either be caught or made sure * that it doesn't occurr for a Error500Page). */ public function output() { $this->execute(); if (!$this->getContent()) { throw new SwarmException('Page `content` must not be empty.'); } if (!$this->getTitle()) { throw new SwarmException('Page `title` must not be empty.'); } if (headers_sent($filename, $linenum)) { throw new SwarmException("Headers already sent in `{$filename}` on line {$linenum}."); } header('Content-Type: text/html; charset=UTF-8'); $frameOptions = $this->getFrameOptions(); if ($frameOptions) { header('X-Frame-Options: ' . $frameOptions, true); } $context = $this->getContext(); $request = $context->getRequest(); $auth = $context->getAuth(); // ProjectsAction could throw an exception, which needs to be caught here, // since Error500Page (exception handler) also uses Page::output() eventually. // @todo: Find a cleaner way to deal with exceptions in the final page out, // because page output is also used on the Error500Page. $projects = array(); if (!isset($this->exceptionObj)) { try { $projectsAction = ProjectsAction::newFromContext($context); $projectsAction->doAction(); $projects = $projectsAction->getData(); } catch (Exception $e) { $pageObj = Error500Page::newFromContext($context); $pageObj->setExceptionObj($e); $pageObj->output(); exit; } } ?> <!DOCTYPE html> <html lang="en" dir="ltr" class="no-js"> <head> <?php foreach ($this->metaTags as $metaTag) { echo "\t" . html_tag('meta', $metaTag) . "\n"; } $subTitleSuffix = $this->getSubTitle() ? ": {$this->getSubTitle()}" : ""; $htmlTitle = $this->getTitle() . $subTitleSuffix . ' - ' . $context->getConf()->web->title; $displayTitleHtml = $this->getDisplayTitleHtml(); ?> <title><?php echo htmlentities($htmlTitle); ?> </title> <link rel="stylesheet" href="<?php echo swarmpath('external/bootstrap/css/bootstrap.css'); ?> "> <link rel="stylesheet" href="<?php echo swarmpath('external/bootstrap/css/bootstrap-responsive.css'); ?> "> <link rel="stylesheet" href="<?php echo swarmpath('css/testswarm.css'); ?> "> <script> (function (h) { h.className = h.className.replace(/\bno-js\b/,'js')})(document.documentElement); SWARM = <?php $infoAction = InfoAction::newFromContext($context); $infoAction->doAction(); echo json_encode2($infoAction->getData()); ?> ; SWARM.auth = <?php echo json_encode2($auth); ?> ; </script> <?php foreach ($this->styleSheets as $styleSheet) { echo "\t" . html_tag('link', array('rel' => 'stylesheet', 'href' => $styleSheet)) . "\n"; } foreach ($this->headScripts as $headScript) { echo "\t" . html_tag('script', array('src' => $headScript)) . "\n"; } ?> </head> <body> <div class="navbar navbar-fixed-top"> <div class="navbar-inner"> <div class="container"> <a class="brand" href="<?php echo swarmpath(''); ?> "><?php echo htmlspecialchars($context->getConf()->web->title); ?> </a> <ul class="nav"> <?php echo $this->getPageLink('home', 'Home'); ?> <li class="dropdown<?php if (strpos($this->getSelfPath(), 'projects') === 0) { echo ' active'; } ?> "> <a href="<?php echo swarmpath('projects'); ?> " class="dropdown-toggle" data-toggle="dropdown"> Projects <b class="caret"></b> </a> <ul class="dropdown-menu"> <?php echo $this->getPageLink('projects', 'All projects'); ?> <li class="divider"></li> <li class="nav-header">Projects</li> <?php foreach ($projects as $project) { echo $this->getPageLink("project/{$project['id']}", $project['displayTitle']); } ?> </ul> </li> <?php echo $this->getPageLink('clients', 'Clients'); ?> <?php echo $this->getPageLink('info', 'Info'); ?> </ul> <ul class="nav pull-right"> <?php if ($auth) { ?> <li><a href="<?php echo htmlspecialchars(swarmpath("project/{$auth->project->id}")); ?> "><?php echo htmlspecialchars($auth->project->display_title); ?> </a></li> <li><a href="<?php echo swarmpath("addjob"); ?> ">Add job</a></li> <li><a href="<?php echo swarmpath('logout'); ?> " class="swarm-logout-link">Logout</a></li> <?php } else { echo $this->getPageLink('login', 'Login'); } ?> </ul> </div> </div> </div> <div class="container"> <div class="hero-unit"> <h1><?php echo $displayTitleHtml; ?> </h1> </div> <?php echo $this->getContent(); ?> <hr> <footer class="swarm-page-footer"> <p>Powered by <a href="https://github.com/jquery/testswarm">TestSwarm</a>: <a href="https://github.com/jquery/testswarm">Source Code</a> | <a href="https://github.com/jquery/testswarm/issues">Issue Tracker</a> | <a href="https://github.com/jquery/testswarm/wiki">About</a> | <a href="https://twitter.com/testswarm">Twitter</a> </p> </footer> </div> <script src="<?php echo swarmpath('external/jquery/jquery.js'); ?> "></script> <script src="<?php echo swarmpath('external/bootstrap/js/bootstrap-dropdown.js'); ?> "></script> <script src="<?php echo swarmpath('js/pretty.js'); ?> "></script> <script src="<?php echo swarmpath('js/testswarm.js'); ?> "></script><?php foreach ($this->bodyScripts as $bodyScript) { echo "\n\t" . html_tag('script', array('src' => $bodyScript)); } if ($context->getConf()->debug->dbLogQueries) { $queryLog = $context->getDB()->getQueryLog(); $queryLogHtml = '<hr><h3>Database query log</h3><div class="well"><ul class="unstyled">'; foreach ($queryLog as $i => $queryInfo) { if ($i !== 0) { $queryLogHtml .= '<hr>'; } $queryLogHtml .= '<li>' . '<pre>' . htmlspecialchars($queryInfo["sql"]) . '</pre>' . '<table class="table table-bordered table-condensed"><tbody><tr>' . '<td>Caller: <code>' . htmlspecialchars($queryInfo['caller']) . '</code></td>' . '<td>Num rows: <code>' . htmlspecialchars($queryInfo['numRows']) . '</code></td>' . '<td>Insert ID: <code>' . htmlspecialchars($queryInfo['insertId']) . '</code></td>' . '<td>Affected rows: <code>' . htmlspecialchars($queryInfo['affectedRows']) . '</code></td>' . '<td>Query time: <code>' . htmlspecialchars(substr($queryInfo['queryTime'], 0, 8)) . '</code></td>' . '</tr></table>' . '</li>'; } $queryLogHtml .= '</ul>'; echo $queryLogHtml; } ?> </body> </html> <?php // End of Page::output }
function showAuthorized() { $title = null; $msg = null; if ($this->app->name == 'anonymous') { $title = _('You have successfully authorized the application'); $msg = _('Please return to the application and enter the following security code to complete the process.'); } else { $title = sprintf(_('You have successfully authorized %s'), $this->app->name); $msg = sprintf(_('Please return to %s and enter the following security code to complete the process.'), $this->app->name); } if ($this->reqToken->verified_callback == 'oob') { $pin = new ApiOauthPinAction($title, $msg, $this->reqToken->verifier, $this->desktopMode()); $pin->showPage(); } else { // NOTE: This would only happen if an application registered as // a web application but sent in 'oob' for the oauth_callback // parameter. Usually web apps will send in a callback and // not use the pin-based workflow. $info = new InfoAction($title, $msg, $this->oauthTokenParam, $this->reqToken->verifier); $info->showPage(); } }
/** * @param Title $title * @return bool */ protected function runForTitle(Title $title) { $page = WikiPage::factory($title); if (!empty($this->params['triggeringRevisionId'])) { // Fetch the specified revision; lockAndGetLatest() below detects if the page // was edited since and aborts in order to avoid corrupting the link tables $revision = Revision::newFromId($this->params['triggeringRevisionId'], Revision::READ_LATEST); } else { // Fetch current revision; READ_LATEST reduces lockAndGetLatest() check failures $revision = Revision::newFromTitle($title, false, Revision::READ_LATEST); } if (!$revision) { $this->setLastError("Revision not found for {$title->getPrefixedDBkey()}"); return false; // 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) { // FIXME: This code probably shouldn't be here? // Needed by things like Echo notifications which need // to know which user caused the links update if ($update instanceof LinksUpdate) { 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); } } } $latestNow = $page->lockAndGetLatest(); if (!$latestNow || $revision->getId() != $latestNow) { // Do not clobber over newer updates with older ones. If all jobs where FIFO and // serialized, it would be OK to update links based on older revisions since it // would eventually get to the latest. Since that is not the case (by design), // only update the link tables to a state matching the current revision's output. $this->setLastError("page_latest changed from {$revision->getId()} to {$latestNow}"); return false; } DataUpdate::runUpdates($updates); InfoAction::invalidateCache($title); return true; }
/** * @param Title $title * @return bool */ protected function runForTitle(Title $title) { $services = MediaWikiServices::getInstance(); $stats = $services->getStatsdDataFactory(); $lbFactory = $services->getDBLoadBalancerFactory(); $ticket = $lbFactory->getEmptyTransactionTicket(__METHOD__); $page = WikiPage::factory($title); $page->loadPageData(WikiPage::READ_LATEST); // Serialize links updates by page ID so they see each others' changes $dbw = $lbFactory->getMainLB()->getConnection(DB_MASTER); /** @noinspection PhpUnusedLocalVariableInspection */ $scopedLock = LinksUpdate::acquirePageLock($dbw, $page->getId(), 'job'); // Get the latest ID *after* acquirePageLock() flushed the transaction. // This is used to detect edits/moves after loadPageData() but before the scope lock. // The works around the chicken/egg problem of determining the scope lock key. $latest = $title->getLatestRevID(Title::GAID_FOR_UPDATE); if (!empty($this->params['triggeringRevisionId'])) { // Fetch the specified revision; lockAndGetLatest() below detects if the page // was edited since and aborts in order to avoid corrupting the link tables $revision = Revision::newFromId($this->params['triggeringRevisionId'], Revision::READ_LATEST); } else { // Fetch current revision; READ_LATEST reduces lockAndGetLatest() check failures $revision = Revision::newFromTitle($title, false, Revision::READ_LATEST); } if (!$revision) { $stats->increment('refreshlinks.rev_not_found'); $this->setLastError("Revision not found for {$title->getPrefixedDBkey()}"); return false; // just deleted? } elseif ($revision->getId() != $latest || $revision->getPage() !== $page->getId()) { // Do not clobber over newer updates with older ones. If all jobs where FIFO and // serialized, it would be OK to update links based on older revisions since it // would eventually get to the latest. Since that is not the case (by design), // only update the link tables to a state matching the current revision's output. $stats->increment('refreshlinks.rev_not_current'); $this->setLastError("Revision {$revision->getId()} is not current"); return false; } $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/replica DB 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 $stats->increment('refreshlinks.update_skipped'); return true; } if ($page->getTouched() >= $this->params['rootJobTimestamp'] || $opportunistic) { // Cache is suspected to be up-to-date. As long as the cache rev ID matches // and it reflects the job's triggering change, then it is usable. $parserOutput = ParserCache::singleton()->getDirty($page, $parserOptions); if (!$parserOutput || $parserOutput->getCacheRevisionId() != $revision->getId() || $parserOutput->getCacheTime() < $skewedTimestamp) { $parserOutput = false; // too stale } } } // Fetch the current revision and parse it if necessary... if ($parserOutput) { $stats->increment('refreshlinks.parser_cached'); } else { $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()); } $stats->increment('refreshlinks.parser_uncached'); } $updates = $content->getSecondaryDataUpdates($title, null, !empty($this->params['useRecursiveLinksUpdate']), $parserOutput); foreach ($updates as $key => $update) { // FIXME: This code probably shouldn't be here? // Needed by things like Echo notifications which need // to know which user caused the links update if ($update instanceof LinksUpdate) { $update->setRevision($revision); 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); } } } foreach ($updates as $update) { $update->setTransactionTicket($ticket); $update->doUpdate(); } InfoAction::invalidateCache($title); return true; }