/** * Execute a special page path. * The path may contain parameters, e.g. Special:Name/Params * Extracts the special page name and call the execute method, passing the parameters * * Returns a title object if the page is redirected, false if there was no such special * page, and true if it was successful. * * @param Title $title * @param IContextSource $context * @param bool $including Bool output is being captured for use in {{special:whatever}} * * @return bool */ public static function executePath(Title &$title, IContextSource &$context, $including = false) { // @todo FIXME: Redirects broken due to this call $bits = explode('/', $title->getDBkey(), 2); $name = $bits[0]; if (!isset($bits[1])) { // bug 2087 $par = null; } else { $par = $bits[1]; } $page = self::getPage($name); if (!$page) { $context->getOutput()->setArticleRelated(false); $context->getOutput()->setRobotPolicy('noindex,nofollow'); global $wgSend404Code; if ($wgSend404Code) { $context->getOutput()->setStatusCode(404); } $context->getOutput()->showErrorPage('nosuchspecialpage', 'nospecialpagetext'); return false; } if (!$including) { // Narrow DB query expectations for this HTTP request $trxLimits = $context->getConfig()->get('TrxProfilerLimits'); $trxProfiler = Profiler::instance()->getTransactionProfiler(); if ($context->getRequest()->wasPosted() && !$page->doesWrites()) { $trxProfiler->setExpectations($trxLimits['POST-nonwrite'], __METHOD__); $context->getRequest()->markAsSafeRequest(); } } // Page exists, set the context $page->setContext($context); if (!$including) { // Redirect to canonical alias for GET commands // Not for POST, we'd lose the post data, so it's best to just distribute // the request. Such POST requests are possible for old extensions that // generate self-links without being aware that their default name has // changed. if ($name != $page->getLocalName() && !$context->getRequest()->wasPosted()) { $query = $context->getRequest()->getQueryValues(); unset($query['title']); $title = $page->getPageTitle($par); $url = $title->getFullURL($query); $context->getOutput()->redirect($url); return $title; } else { $context->setTitle($page->getPageTitle($par)); } } elseif (!$page->isIncludable()) { return false; } $page->including($including); // Execute special page $page->run($par); return true; }
/** * Generate help for the specified modules * * Help is placed into the OutputPage object returned by * $context->getOutput(). * * Recognized options include: * - headerlevel: (int) Header tag level * - nolead: (bool) Skip the inclusion of api-help-lead * - noheader: (bool) Skip the inclusion of the top-level section headers * - submodules: (bool) Include help for submodules of the current module * - recursivesubmodules: (bool) Include help for submodules recursively * - helptitle: (string) Title to link for additional modules' help. Should contain $1. * - toc: (bool) Include a table of contents * * @param IContextSource $context * @param ApiBase[]|ApiBase $modules * @param array $options Formatting options (described above) * @return string */ public static function getHelp(IContextSource $context, $modules, array $options) { global $wgContLang; if (!is_array($modules)) { $modules = array($modules); } $out = $context->getOutput(); $out->addModuleStyles('mediawiki.hlist'); $out->addModuleStyles('mediawiki.apihelp'); if (!empty($options['toc'])) { $out->addModules('mediawiki.toc'); } $out->setPageTitle($context->msg('api-help-title')); $cache = ObjectCache::getMainWANInstance(); $cacheKey = null; if (count($modules) == 1 && $modules[0] instanceof ApiMain && $options['recursivesubmodules'] && $context->getLanguage() === $wgContLang) { $cacheHelpTimeout = $context->getConfig()->get('APICacheHelpTimeout'); if ($cacheHelpTimeout > 0) { // Get help text from cache if present $cacheKey = wfMemcKey('apihelp', $modules[0]->getModulePath(), (int) (!empty($options['toc'])), str_replace(' ', '_', SpecialVersion::getVersion('nodb'))); $cached = $cache->get($cacheKey); if ($cached) { $out->addHTML($cached); return; } } } if ($out->getHTML() !== '') { // Don't save to cache, there's someone else's content in the page // already $cacheKey = null; } $options['recursivesubmodules'] = !empty($options['recursivesubmodules']); $options['submodules'] = $options['recursivesubmodules'] || !empty($options['submodules']); // Prepend lead if (empty($options['nolead'])) { $msg = $context->msg('api-help-lead'); if (!$msg->isDisabled()) { $out->addHTML($msg->parseAsBlock()); } } $haveModules = array(); $html = self::getHelpInternal($context, $modules, $options, $haveModules); if (!empty($options['toc']) && $haveModules) { $out->addHTML(Linker::generateTOC($haveModules, $context->getLanguage())); } $out->addHTML($html); $helptitle = isset($options['helptitle']) ? $options['helptitle'] : null; $html = self::fixHelpLinks($out->getHTML(), $helptitle, $haveModules); $out->clearHTML(); $out->addHTML($html); if ($cacheKey !== null) { $cache->set($cacheKey, $out->getHTML(), $cacheHelpTimeout); } }
/** * @param IContextSource $context * @return array */ static function getTimezoneOptions(IContextSource $context) { $opt = array(); $localTZoffset = $context->getConfig()->get('LocalTZoffset'); $timeZoneList = self::getTimeZoneList($context->getLanguage()); $timestamp = MWTimestamp::getLocalInstance(); // Check that the LocalTZoffset is the same as the local time zone offset if ($localTZoffset == $timestamp->format('Z') / 60) { $timezoneName = $timestamp->getTimezone()->getName(); // Localize timezone if (isset($timeZoneList[$timezoneName])) { $timezoneName = $timeZoneList[$timezoneName]['name']; } $server_tz_msg = $context->msg('timezoneuseserverdefault', $timezoneName)->text(); } else { $tzstring = sprintf('%+03d:%02d', floor($localTZoffset / 60), abs($localTZoffset) % 60); $server_tz_msg = $context->msg('timezoneuseserverdefault', $tzstring)->text(); } $opt[$server_tz_msg] = "System|{$localTZoffset}"; $opt[$context->msg('timezoneuseoffset')->text()] = 'other'; $opt[$context->msg('guesstimezone')->text()] = 'guess'; foreach ($timeZoneList as $timeZoneInfo) { $region = $timeZoneInfo['region']; if (!isset($opt[$region])) { $opt[$region] = array(); } $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection']; } return $opt; }
/** * Really send a mail. Permissions should have been checked using * getPermissionsError(). It is probably also a good * idea to check the edit token and ping limiter in advance. * * @param array $data * @param IContextSource $context * @return Status|string|bool Status object, or potentially a String on error * or maybe even true on success if anything uses the EmailUser hook. */ public static function submit(array $data, IContextSource $context) { $config = $context->getConfig(); $target = self::getTarget($data['Target']); if (!$target instanceof User) { // Messages used here: notargettext, noemailtext, nowikiemailtext return $context->msg($target . 'text')->parseAsBlock(); } $to = MailAddress::newFromUser($target); $from = MailAddress::newFromUser($context->getUser()); $subject = $data['Subject']; $text = $data['Text']; // Add a standard footer and trim up trailing newlines $text = rtrim($text) . "\n\n-- \n"; $text .= $context->msg('emailuserfooter', $from->name, $to->name)->inContentLanguage()->text(); $error = ''; if (!Hooks::run('EmailUser', array(&$to, &$from, &$subject, &$text, &$error))) { return $error; } if ($config->get('UserEmailUseReplyTo')) { /** * Put the generic wiki autogenerated address in the From: * header and reserve the user for Reply-To. * * This is a bit ugly, but will serve to differentiate * wiki-borne mails from direct mails and protects against * SPF and bounce problems with some mailers (see below). */ $mailFrom = new MailAddress($config->get('PasswordSender'), wfMessage('emailsender')->inContentLanguage()->text()); $replyTo = $from; } else { /** * Put the sending user's e-mail address in the From: header. * * This is clean-looking and convenient, but has issues. * One is that it doesn't as clearly differentiate the wiki mail * from "directly" sent mails. * * Another is that some mailers (like sSMTP) will use the From * address as the envelope sender as well. For open sites this * can cause mails to be flunked for SPF violations (since the * wiki server isn't an authorized sender for various users' * domains) as well as creating a privacy issue as bounces * containing the recipient's e-mail address may get sent to * the sending user. */ $mailFrom = $from; $replyTo = null; } $status = UserMailer::send($to, $mailFrom, $subject, $text, array('replyTo' => $replyTo)); if (!$status->isGood()) { return $status; } else { // if the user requested a copy of this mail, do this now, // unless they are emailing themselves, in which case one // copy of the message is sufficient. if ($data['CCMe'] && $to != $from) { $cc_subject = $context->msg('emailccsubject')->rawParams($target->getName(), $subject)->text(); // target and sender are equal, because this is the CC for the sender Hooks::run('EmailUserCC', array(&$from, &$from, &$cc_subject, &$text)); $ccStatus = UserMailer::send($from, $from, $cc_subject, $text); $status->merge($ccStatus); } Hooks::run('EmailUserComplete', array($to, $from, $subject, $text)); return $status; } }
/** * This function commits all DB changes as needed before * the user can receive a response (in case commit fails) * * @param IContextSource $context * @since 1.27 */ public static function preOutputCommit(IContextSource $context) { // Either all DBs should commit or none ignore_user_abort(true); $config = $context->getConfig(); $factory = wfGetLBFactory(); // Check if any transaction was too big $limit = $config->get('MaxUserDBWriteDuration'); $factory->forEachLB(function (LoadBalancer $lb) use($limit) { $lb->forEachOpenConnection(function (IDatabase $db) use($limit) { $time = $db->pendingWriteQueryDuration(); if ($limit > 0 && $time > $limit) { throw new DBTransactionError($db, wfMessage('transaction-duration-limit-exceeded', $time, $limit)->text()); } }); }); // Commit all changes $factory->commitMasterChanges(__METHOD__); // Record ChronologyProtector positions $factory->shutdown(); wfDebug(__METHOD__ . ': all transactions committed'); DeferredUpdates::doUpdates('enqueue', DeferredUpdates::PRESEND); wfDebug(__METHOD__ . ': pre-send deferred updates completed'); // Set a cookie to tell all CDN edge nodes to "stick" the user to the // DC that handles this POST request (e.g. the "master" data center) $request = $context->getRequest(); if ($request->wasPosted() && $factory->hasOrMadeRecentMasterChanges()) { $expires = time() + $config->get('DataCenterUpdateStickTTL'); $request->response()->setCookie('UseDC', 'master', $expires, array('prefix' => '')); } // Avoid letting a few seconds of slave lag cause a month of stale data if ($factory->laggedSlaveUsed()) { $maxAge = $config->get('CdnMaxageLagged'); $context->getOutput()->lowerCdnMaxage($maxAge); $request->response()->header("X-Database-Lagged: true"); wfDebugLog('replication', "Lagged DB used; CDN cache TTL limited to {$maxAge} seconds"); } }
/** * @param string $url * @param IContextSource $context * @return string|bool Either "local" or "remote" if in the farm, false otherwise */ private static function getUrlDomainDistance($url, IContextSource $context) { static $relevantKeys = ['host' => true, 'port' => true]; $infoCandidate = wfParseUrl($url); if ($infoCandidate === false) { return false; } $infoCandidate = array_intersect_key($infoCandidate, $relevantKeys); $clusterHosts = array_merge([$context->getConfig()->get('CanonicalServer')], $context->getConfig()->get('LocalVirtualHosts')); foreach ($clusterHosts as $i => $clusterHost) { $parseUrl = wfParseUrl($clusterHost); if (!$parseUrl) { continue; } $infoHost = array_intersect_key($parseUrl, $relevantKeys); if ($infoCandidate === $infoHost) { return $i === 0 ? 'local' : 'remote'; } } return false; }
/** * Build the input form * * @return string HTML form */ function buildForm() { $user = $this->mContext->getUser(); $output = $this->mContext->getOutput(); $lang = $this->mContext->getLanguage(); $cascadingRestrictionLevels = $this->mContext->getConfig()->get('CascadingRestrictionLevels'); $out = ''; if (!$this->disabled) { $output->addModules('mediawiki.legacy.protect'); $output->addJsConfigVars('wgCascadeableLevels', $cascadingRestrictionLevels); $out .= Xml::openElement('form', array('method' => 'post', 'action' => $this->mTitle->getLocalURL('action=protect'), 'id' => 'mw-Protect-Form')); } $out .= Xml::openElement('fieldset') . Xml::element('legend', null, wfMessage('protect-legend')->text()) . Xml::openElement('table', array('id' => 'mwProtectSet')) . Xml::openElement('tbody'); $scExpiryOptions = wfMessage('protect-expiry-options')->inContentLanguage()->text(); $showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled; // Not all languages have V_x <-> N_x relation foreach ($this->mRestrictions as $action => $selected) { // Messages: // restriction-edit, restriction-move, restriction-create, restriction-upload $msg = wfMessage('restriction-' . $action); $out .= "<tr><td>" . Xml::openElement('fieldset') . Xml::element('legend', null, $msg->exists() ? $msg->text() : $action) . Xml::openElement('table', array('id' => "mw-protect-table-{$action}")) . "<tr><td>" . $this->buildSelector($action, $selected) . "</td></tr><tr><td>"; $mProtectexpiry = Xml::label(wfMessage('protectexpiry')->text(), "mwProtectExpirySelection-{$action}"); $mProtectother = Xml::label(wfMessage('protect-othertime')->text(), "mwProtect-{$action}-expires"); $expiryFormOptions = ''; if ($this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity') { $timestamp = $lang->timeanddate($this->mExistingExpiry[$action], true); $d = $lang->date($this->mExistingExpiry[$action], true); $t = $lang->time($this->mExistingExpiry[$action], true); $expiryFormOptions .= Xml::option(wfMessage('protect-existing-expiry', $timestamp, $d, $t)->text(), 'existing', $this->mExpirySelection[$action] == 'existing') . "\n"; } $expiryFormOptions .= Xml::option(wfMessage('protect-othertime-op')->text(), "othertime") . "\n"; foreach (explode(',', $scExpiryOptions) as $option) { if (strpos($option, ":") === false) { $show = $value = $option; } else { list($show, $value) = explode(":", $option); } $show = htmlspecialchars($show); $value = htmlspecialchars($value); $expiryFormOptions .= Xml::option($show, $value, $this->mExpirySelection[$action] === $value) . "\n"; } # Add expiry dropdown if ($showProtectOptions && !$this->disabled) { $out .= "\n\t\t\t\t\t<table><tr>\n\t\t\t\t\t\t<td class='mw-label'>\n\t\t\t\t\t\t\t{$mProtectexpiry}\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td class='mw-input'>" . Xml::tags('select', array('id' => "mwProtectExpirySelection-{$action}", 'name' => "wpProtectExpirySelection-{$action}", 'tabindex' => '2') + $this->disabledAttrib, $expiryFormOptions) . "</td>\n\t\t\t\t\t</tr></table>"; } # Add custom expiry field $attribs = array('id' => "mwProtect-{$action}-expires") + $this->disabledAttrib; $out .= "<table><tr>\n\t\t\t\t\t<td class='mw-label'>" . $mProtectother . '</td> <td class="mw-input">' . Xml::input("mwProtect-expiry-{$action}", 50, $this->mExpiry[$action], $attribs) . '</td> </tr></table>'; $out .= "</td></tr>" . Xml::closeElement('table') . Xml::closeElement('fieldset') . "</td></tr>"; } # Give extensions a chance to add items to the form wfRunHooks('ProtectionForm::buildForm', array($this->mArticle, &$out)); $out .= Xml::closeElement('tbody') . Xml::closeElement('table'); // JavaScript will add another row with a value-chaining checkbox if ($this->mTitle->exists()) { $out .= Xml::openElement('table', array('id' => 'mw-protect-table2')) . Xml::openElement('tbody'); $out .= '<tr> <td></td> <td class="mw-input">' . Xml::checkLabel(wfMessage('protect-cascade')->text(), 'mwProtect-cascade', 'mwProtect-cascade', $this->mCascade, $this->disabledAttrib) . "</td>\n\t\t\t\t</tr>\n"; $out .= Xml::closeElement('tbody') . Xml::closeElement('table'); } # Add manual and custom reason field/selects as well as submit if (!$this->disabled) { $mProtectreasonother = Xml::label(wfMessage('protectcomment')->text(), 'wpProtectReasonSelection'); $mProtectreason = Xml::label(wfMessage('protect-otherreason')->text(), 'mwProtect-reason'); $reasonDropDown = Xml::listDropDown('wpProtectReasonSelection', wfMessage('protect-dropdown')->inContentLanguage()->text(), wfMessage('protect-otherreason-op')->inContentLanguage()->text(), $this->mReasonSelection, 'mwProtect-reason', 4); $out .= Xml::openElement('table', array('id' => 'mw-protect-table3')) . Xml::openElement('tbody'); $out .= "\n\t\t\t\t<tr>\n\t\t\t\t\t<td class='mw-label'>\n\t\t\t\t\t\t{$mProtectreasonother}\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class='mw-input'>\n\t\t\t\t\t\t{$reasonDropDown}\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class='mw-label'>\n\t\t\t\t\t\t{$mProtectreason}\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class='mw-input'>" . Xml::input('mwProtect-reason', 60, $this->mReason, array('type' => 'text', 'id' => 'mwProtect-reason', 'maxlength' => 180)) . "</td>\n\t\t\t\t</tr>"; # Disallow watching is user is not logged in if ($user->isLoggedIn()) { $out .= "\n\t\t\t\t<tr>\n\t\t\t\t\t<td></td>\n\t\t\t\t\t<td class='mw-input'>" . Xml::checkLabel(wfMessage('watchthis')->text(), 'mwProtectWatch', 'mwProtectWatch', $user->isWatched($this->mTitle) || $user->getOption('watchdefault')) . "</td>\n\t\t\t\t</tr>"; } $out .= "\n\t\t\t\t<tr>\n\t\t\t\t\t<td></td>\n\t\t\t\t\t<td class='mw-submit'>" . Xml::submitButton(wfMessage('confirm')->text(), array('id' => 'mw-Protect-submit')) . "</td>\n\t\t\t\t</tr>\n"; $out .= Xml::closeElement('tbody') . Xml::closeElement('table'); } $out .= Xml::closeElement('fieldset'); if ($user->isAllowed('editinterface')) { $title = Title::makeTitle(NS_MEDIAWIKI, 'Protect-dropdown'); $link = Linker::link($title, wfMessage('protect-edit-reasonlist')->escaped(), array(), array('action' => 'edit')); $out .= '<p class="mw-protect-editreasons">' . $link . '</p>'; } if (!$this->disabled) { $out .= Html::hidden('wpEditToken', $user->getEditToken(array('protect', $this->mTitle->getPrefixedDBkey()))); $out .= Xml::closeElement('form'); } return $out; }
/** * Return the legend displayed within the fieldset * @todo This should not be static, then we can drop the parameter * @todo Not called by anything, should be called by doHeader() * * @param IContextSource $context The object available as $this in non-static functions * @return string */ public static function makeLegend(IContextSource $context) { $user = $context->getUser(); # The legend showing what the letters and stuff mean $legend = Html::openElement('dl') . "\n"; # Iterates through them and gets the messages for both letter and tooltip $legendItems = $context->getConfig()->get('RecentChangesFlags'); if (!($user->useRCPatrol() || $user->useNPPatrol())) { unset($legendItems['unpatrolled']); } foreach ($legendItems as $key => $item) { # generate items of the legend $label = isset($item['legend']) ? $item['legend'] : $item['title']; $letter = $item['letter']; $cssClass = isset($item['class']) ? $item['class'] : $key; $legend .= Html::element('dt', array('class' => $cssClass), $context->msg($letter)->text()) . "\n" . Html::rawElement('dd', array(), $context->msg($label)->parse()) . "\n"; } # (+-123) $legend .= Html::rawElement('dt', array('class' => 'mw-plusminus-pos'), $context->msg('recentchanges-legend-plusminus')->parse()) . "\n"; $legend .= Html::element('dd', array('class' => 'mw-changeslist-legend-plusminus'), $context->msg('recentchanges-label-plusminus')->text()) . "\n"; $legend .= Html::closeElement('dl') . "\n"; # Collapsibility $legend = '<div class="mw-changeslist-legend">' . $context->msg('recentchanges-legend-heading')->parse() . '<div class="mw-collapsible-content">' . $legend . '</div>' . '</div>'; return $legend; }
/** * @param IContextSource $context * @return array */ static function getTimezoneOptions(IContextSource $context) { $opt = array(); $localTZoffset = $context->getConfig()->get('LocalTZoffset'); $timestamp = MWTimestamp::getLocalInstance(); // Check that the LocalTZoffset is the same as the local time zone offset if ($localTZoffset == $timestamp->format('Z') / 60) { $server_tz_msg = $context->msg('timezoneuseserverdefault', $timestamp->getTimezone()->getName())->text(); } else { $tzstring = sprintf('%+03d:%02d', floor($localTZoffset / 60), abs($localTZoffset) % 60); $server_tz_msg = $context->msg('timezoneuseserverdefault', $tzstring)->text(); } $opt[$server_tz_msg] = "System|{$localTZoffset}"; $opt[$context->msg('timezoneuseoffset')->text()] = 'other'; $opt[$context->msg('guesstimezone')->text()] = 'guess'; if (function_exists('timezone_identifiers_list')) { # Read timezone list $tzs = timezone_identifiers_list(); sort($tzs); $tzRegions = array(); $tzRegions['Africa'] = $context->msg('timezoneregion-africa')->text(); $tzRegions['America'] = $context->msg('timezoneregion-america')->text(); $tzRegions['Antarctica'] = $context->msg('timezoneregion-antarctica')->text(); $tzRegions['Arctic'] = $context->msg('timezoneregion-arctic')->text(); $tzRegions['Asia'] = $context->msg('timezoneregion-asia')->text(); $tzRegions['Atlantic'] = $context->msg('timezoneregion-atlantic')->text(); $tzRegions['Australia'] = $context->msg('timezoneregion-australia')->text(); $tzRegions['Europe'] = $context->msg('timezoneregion-europe')->text(); $tzRegions['Indian'] = $context->msg('timezoneregion-indian')->text(); $tzRegions['Pacific'] = $context->msg('timezoneregion-pacific')->text(); asort($tzRegions); $prefill = array_fill_keys(array_values($tzRegions), array()); $opt = array_merge($opt, $prefill); $now = date_create('now'); foreach ($tzs as $tz) { $z = explode('/', $tz, 2); # timezone_identifiers_list() returns a number of # backwards-compatibility entries. This filters them out of the # list presented to the user. if (count($z) != 2 || !array_key_exists($z[0], $tzRegions)) { continue; } # Localize region $z[0] = $tzRegions[$z[0]]; $minDiff = floor(timezone_offset_get(timezone_open($tz), $now) / 60); $display = str_replace('_', ' ', $z[0] . '/' . $z[1]); $value = "ZoneInfo|{$minDiff}|{$tz}"; $opt[$z[0]][$display] = $value; } } return $opt; }
/** * This function commits all DB changes as needed before * the user can receive a response (in case commit fails) * * @param IContextSource $context * @since 1.27 */ public static function preOutputCommit(IContextSource $context) { // Either all DBs should commit or none ignore_user_abort(true); $config = $context->getConfig(); $factory = wfGetLBFactory(); // Commit all changes $factory->commitMasterChanges(__METHOD__, array('maxWriteDuration' => $config->get('MaxUserDBWriteDuration'))); // Record ChronologyProtector positions $factory->shutdown(); wfDebug(__METHOD__ . ': all transactions committed'); DeferredUpdates::doUpdates('enqueue', DeferredUpdates::PRESEND); wfDebug(__METHOD__ . ': pre-send deferred updates completed'); // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that handles this // POST request (e.g. the "master" data center). Also have the user briefly bypass CDN so // ChronologyProtector works for cacheable URLs. $request = $context->getRequest(); if ($request->wasPosted() && $factory->hasOrMadeRecentMasterChanges()) { $expires = time() + $config->get('DataCenterUpdateStickTTL'); $options = array('prefix' => ''); $request->response()->setCookie('UseDC', 'master', $expires, $options); $request->response()->setCookie('UseCDNCache', 'false', $expires, $options); } // Avoid letting a few seconds of slave lag cause a month of stale data. This logic is // also intimately related to the value of $wgCdnReboundPurgeDelay. if ($factory->laggedSlaveUsed()) { $maxAge = $config->get('CdnMaxageLagged'); $context->getOutput()->lowerCdnMaxage($maxAge); $request->response()->header("X-Database-Lagged: true"); wfDebugLog('replication', "Lagged DB used; CDN cache TTL limited to {$maxAge} seconds"); } }
/** * This function commits all DB changes as needed before * the user can receive a response (in case commit fails) * * @param IContextSource $context * @since 1.27 */ public static function preOutputCommit(IContextSource $context) { // Either all DBs should commit or none ignore_user_abort(true); // Commit all changes and record ChronologyProtector positions $factory = wfGetLBFactory(); $factory->commitMasterChanges(); $factory->shutdown(); wfDebug(__METHOD__ . ' completed; all transactions committed'); // Set a cookie to tell all CDN edge nodes to "stick" the user to the // DC that handles this POST request (e.g. the "master" data center) $request = $context->getRequest(); $config = $context->getConfig(); if ($request->wasPosted() && $factory->hasOrMadeRecentMasterChanges()) { $expires = time() + $config->get('DataCenterUpdateStickTTL'); $request->response()->setCookie('UseDC', 'master', $expires, array('prefix' => '')); } // Avoid letting a few seconds of slave lag cause a month of stale data if ($factory->laggedSlaveUsed()) { $maxAge = $config->get('CdnMaxageLagged'); $context->getOutput()->lowerCdnMaxage($maxAge); $request->response()->header("X-Database-Lagged: true"); wfDebugLog('replication', "Lagged DB used; CDN cache TTL limited to {$maxAge} seconds"); } }