public function enableCXBetaFeature() { $user = $this->getUser(); $out = $this->getOutput(); $user->setOption('cx', '1'); // Promise to persist the setting post-send DeferredUpdates::addCallableUpdate(function () use($user) { $user->saveSettings(); }); $out->addModules('ext.cx.beta.notification'); }
public function execute() { $user = $this->getUser(); if ($this->getRequest()->wasPosted()) { $user->setNewtalk(false); } else { DeferredUpdates::addCallableUpdate(function () use($user) { $user->setNewtalk(false); }); } $this->getResult()->addValue(null, $this->getModuleName(), 'success'); }
public function execute($par) { $this->useTransactionalTimeLimit(); $this->checkReadOnly(); $this->setHeaders(); $this->outputHeader(); $request = $this->getRequest(); $target = !is_null($par) ? $par : $request->getVal('target'); // Yes, the use of getVal() and getText() is wanted, see bug 20365 $oldTitleText = $request->getVal('wpOldTitle', $target); $this->oldTitle = Title::newFromText($oldTitleText); if (!$this->oldTitle) { // Either oldTitle wasn't passed, or newFromText returned null throw new ErrorPageError('notargettitle', 'notargettext'); } if (!$this->oldTitle->exists()) { throw new ErrorPageError('nopagetitle', 'nopagetext'); } $newTitleTextMain = $request->getText('wpNewTitleMain'); $newTitleTextNs = $request->getInt('wpNewTitleNs', $this->oldTitle->getNamespace()); // Backwards compatibility for forms submitting here from other sources // which is more common than it should be.. $newTitleText_bc = $request->getText('wpNewTitle'); $this->newTitle = strlen($newTitleText_bc) > 0 ? Title::newFromText($newTitleText_bc) : Title::makeTitleSafe($newTitleTextNs, $newTitleTextMain); $user = $this->getUser(); # Check rights $permErrors = $this->oldTitle->getUserPermissionsErrors('move', $user); if (count($permErrors)) { // Auto-block user's IP if the account was "hard" blocked DeferredUpdates::addCallableUpdate(function () use($user) { $user->spreadAnyEditBlock(); }); throw new PermissionsError('move', $permErrors); } $def = !$request->wasPosted(); $this->reason = $request->getText('wpReason'); $this->moveTalk = $request->getBool('wpMovetalk', $def); $this->fixRedirects = $request->getBool('wpFixRedirects', $def); $this->leaveRedirect = $request->getBool('wpLeaveRedirect', $def); $this->moveSubpages = $request->getBool('wpMovesubpages', false); $this->deleteAndMove = $request->getBool('wpDeleteAndMove') && $request->getBool('wpConfirm'); $this->moveOverShared = $request->getBool('wpMoveOverSharedFile', false); $this->watch = $request->getCheck('wpWatch') && $user->isLoggedIn(); if ('submit' == $request->getVal('action') && $request->wasPosted() && $user->matchEditToken($request->getVal('wpEditToken'))) { $this->doSubmit(); } else { $this->showForm(array()); } }
public function doUpdate() { global $wgSiteStatsAsyncFactor; $this->doUpdateContextStats(); $rate = $wgSiteStatsAsyncFactor; // convenience // If set to do so, only do actual DB updates 1 every $rate times. // The other times, just update "pending delta" values in memcached. if ($rate && ($rate < 0 || mt_rand(0, $rate - 1) != 0)) { $this->doUpdatePendingDeltas(); } else { // Need a separate transaction because this a global lock DeferredUpdates::addCallableUpdate([$this, 'tryDBUpdateInternal']); } }
public function testDoUpdates() { $updates = array('1' => 'deferred update 1', '2' => 'deferred update 2', '3' => 'deferred update 3', '2-1' => 'deferred update 1 within deferred update 2'); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['1']; }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['2']; DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['2-1']; }); }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates[3]; }); $this->expectOutputString(implode('', $updates)); DeferredUpdates::doUpdates(); }
public function testDoUpdatesCLI() { $this->setMwGlobals('wgCommandLineMode', true); $updates = array('1' => 'deferred update 1', '2' => 'deferred update 2', '2-1' => 'deferred update 1 within deferred update 2', '3' => 'deferred update 3'); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['1']; }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['2']; DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['2-1']; }); }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates[3]; }); $this->expectOutputString(implode('', $updates)); DeferredUpdates::doUpdates(); }
/** * Render the special page * @param string $par parameter submitted as subpage */ function executeWhenAvailable($par) { // Anons don't get a watchlist $this->requireLogin('mobile-frontend-watchlist-purpose'); $ctx = MobileContext::singleton(); $this->usePageImages = !$ctx->imagesDisabled() && defined('PAGE_IMAGES_INSTALLED'); $user = $this->getUser(); $output = $this->getOutput(); $output->addModules('skins.minerva.special.watchlist.scripts'); // FIXME: Loads twice with JS enabled (T87871) $output->addModuleStyles(array('skins.minerva.special.watchlist.styles', 'mobile.pagelist.styles', 'mobile.pagesummary.styles')); $req = $this->getRequest(); $this->view = $req->getVal('watchlistview', 'a-z'); $this->filter = $req->getVal('filter', 'all'); $this->fromPageTitle = Title::newFromText($req->getVal('from', false)); $output->setPageTitle($this->msg('watchlist')); // This needs to be done before calling getWatchlistHeader $this->updateStickyTabs(); if ($this->optionsChanged) { DeferredUpdates::addCallableUpdate(function () use($user) { $user->saveSettings(); }); } if ($this->view === 'feed') { $output->addHtml($this->getWatchlistHeader($user)); $output->addHtml(Html::openElement('div', array('class' => 'content-unstyled'))); $this->showRecentChangesHeader(); $res = $this->doFeedQuery(); if ($res->numRows()) { $this->showFeedResults($res); } else { $this->showEmptyList(true); } $output->addHtml(Html::closeElement('div')); } else { $output->redirect(SpecialPage::getTitleFor('EditWatchlist')->getLocalURL()); } }
public function testDoUpdatesCLI() { $this->setMwGlobals('wgCommandLineMode', true); $updates = ['1' => "deferred update 1;\n", '2' => "deferred update 2;\n", '2-1' => "deferred update 1 within deferred update 2;\n", '2-2' => "deferred update 2 within deferred update 2;\n", '3' => "deferred update 3;\n", '3-1' => "deferred update 1 within deferred update 3;\n", '3-2' => "deferred update 2 within deferred update 3;\n", '3-1-1' => "deferred update 1 within deferred update 1 within deferred update 3;\n", '3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n"]; wfGetLBFactory()->commitMasterChanges(__METHOD__); // clear anything DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['1']; }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['2']; DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['2-1']; }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['2-2']; }); }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['3']; DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['3-1']; DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['3-1-1']; }); }); DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['3-2']; DeferredUpdates::addCallableUpdate(function () use($updates) { echo $updates['3-2-1']; }); }); }); $this->expectOutputString(implode('', $updates)); DeferredUpdates::doUpdates(); }
/** * Keeps track of recently used message groups per user. */ public static function trackGroup(MessageGroup $group, User $user) { if ($user->isAnon()) { return true; } $groups = $user->getOption('translate-recent-groups', ''); if ($groups === '') { $groups = array(); } else { $groups = explode('|', $groups); } if (isset($groups[0]) && $groups[0] === $group->getId()) { return true; } array_unshift($groups, $group->getId()); $groups = array_unique($groups); $groups = array_slice($groups, 0, 5); $user->setOption('translate-recent-groups', implode('|', $groups)); // Promise to persist the data post-send DeferredUpdates::addCallableUpdate(function () use($user) { $user->saveSettings(); }); return true; }
/** * Attempts to clean up broken items */ private function cleanupWatchlist() { if (!count($this->badItems)) { return; // nothing to do } $user = $this->getUser(); $badItems = $this->badItems; DeferredUpdates::addCallableUpdate(function () use($user, $badItems) { $store = MediaWikiServices::getInstance()->getWatchedItemStore(); foreach ($badItems as $row) { list($title, $namespace, $dbKey) = $row; $action = $title ? 'cleaning up' : 'deleting'; wfDebug("User {$user->getName()} has broken watchlist item " . "ns({$namespace}):{$dbKey}, {$action}.\n"); $store->removeWatch($user, new TitleValue((int) $namespace, $dbKey)); // Can't just do an UPDATE instead of DELETE/INSERT due to unique index if ($title) { $user->addWatch($title); } } }); }
/** * Set up all member variables using a database query. * @throws MWException * @return bool True on success, false on failure. */ protected function initialize() { if ($this->mName === null && $this->mID === null) { throw new MWException(__METHOD__ . ' has both names and IDs null'); } elseif ($this->mID === null) { $where = ['cat_title' => $this->mName]; } elseif ($this->mName === null) { $where = ['cat_id' => $this->mID]; } else { # Already initialized return true; } $dbr = wfGetDB(DB_REPLICA); $row = $dbr->selectRow('category', ['cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files'], $where, __METHOD__); if (!$row) { # Okay, there were no contents. Nothing to initialize. if ($this->mTitle) { # If there is a title object but no record in the category table, # treat this as an empty category. $this->mID = false; $this->mName = $this->mTitle->getDBkey(); $this->mPages = 0; $this->mSubcats = 0; $this->mFiles = 0; # If the title exists, call refreshCounts to add a row for it. if ($this->mTitle->exists()) { DeferredUpdates::addCallableUpdate([$this, 'refreshCounts']); } return true; } else { return false; # Fail } } $this->mID = $row->cat_id; $this->mName = $row->cat_title; $this->mPages = $row->cat_pages; $this->mSubcats = $row->cat_subcats; $this->mFiles = $row->cat_files; # (bug 13683) If the count is negative, then 1) it's obviously wrong # and should not be kept, and 2) we *probably* don't have to scan many # rows to obtain the correct figure, so let's risk a one-time recount. if ($this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0) { $this->mPages = max($this->mPages, 0); $this->mSubcats = max($this->mSubcats, 0); $this->mFiles = max($this->mFiles, 0); DeferredUpdates::addCallableUpdate([$this, 'refreshCounts']); } return true; }
/** * Schedule a deferred callable that will check if a pingback should be * sent and (if so) proceed to send it. */ public static function schedulePingback() { DeferredUpdates::addCallableUpdate(function () { $instance = new Pingback(); if ($instance->shouldSend()) { $instance->sendPingback(); } }); }
/** * Processes notifications for a newly-created EchoEvent * * @param $event EchoEvent to do notifications for * @param $defer bool Defer to job queue */ public static function notify($event, $defer = true) { if ($defer) { // defer job insertion till end of request when all primary db transactions // have been committed DeferredUpdates::addCallableUpdate(function () use($event) { global $wgEchoCluster; $params = array('event' => $event); if (wfGetLB()->getServerCount() > 1) { $params['mainDbMasterPos'] = wfGetLB()->getMasterPos(); } if ($wgEchoCluster) { $lb = wfGetLBFactory()->getExternalLB($wgEchoCluster); if ($lb->getServerCount() > 1) { $params['echoDbMasterPos'] = $lb->getMasterPos(); } } $title = $event->getTitle() ? $event->getTitle() : Title::newMainPage(); $job = new EchoNotificationJob($title, $params); JobQueueGroup::singleton()->push($job); }); return; } // Check if the event object has valid event type. Events with invalid // event types left in the job queue should not be processed if (!$event->isEnabledEvent()) { return; } // Only send web notification for welcome event if ($event->getType() == 'welcome') { self::doNotification($event, $event->getAgent(), 'web'); } else { // Get the notification types for this event, eg, web/email global $wgEchoDefaultNotificationTypes; $notifyTypes = $wgEchoDefaultNotificationTypes['all']; if (isset($wgEchoDefaultNotificationTypes[$event->getType()])) { $notifyTypes = array_merge($notifyTypes, $wgEchoDefaultNotificationTypes[$event->getType()]); } $notifyTypes = array_keys(array_filter($notifyTypes)); $users = self::getUsersToNotifyForEvent($event); $blacklisted = self::isBlacklisted($event); foreach ($users as $user) { //XXCHANGEDXX - allow anons [sc|rs] // Notification should not be sent to anonymous user // if ( $user->isAnon() ) { // continue; // } if ($blacklisted && !self::isWhitelistedByUser($event, $user)) { continue; } wfRunHooks('EchoGetNotificationTypes', array($user, $event, &$notifyTypes)); foreach ($notifyTypes as $type) { self::doNotification($event, $user, $type); } } } }
/** * Register the change of watch status */ protected function updateWatchlist() { global $wgUser; if (!$wgUser->isLoggedIn()) { return; } $user = $wgUser; $title = $this->mTitle; $watch = $this->watchthis; // Do this in its own transaction to reduce contention... DeferredUpdates::addCallableUpdate(function () use($user, $title, $watch) { if ($watch == $user->isWatched($title, WatchedItem::IGNORE_USER_RIGHTS)) { return; // nothing to change } WatchAction::doWatchOrUnwatch($watch, $title, $user); }); }
public function beginPrimaryAuthentication(array $reqs) { $req = AuthenticationRequest::getRequestByClass($reqs, PasswordAuthenticationRequest::class); if (!$req) { return AuthenticationResponse::newAbstain(); } if ($req->username === null || $req->password === null) { return AuthenticationResponse::newAbstain(); } $username = User::getCanonicalName($req->username, 'usable'); if ($username === false) { return AuthenticationResponse::newAbstain(); } $fields = ['user_id', 'user_password', 'user_password_expires']; $dbr = wfGetDB(DB_REPLICA); $row = $dbr->selectRow('user', $fields, ['user_name' => $username], __METHOD__); if (!$row) { return AuthenticationResponse::newAbstain(); } $oldRow = clone $row; // Check for *really* old password hashes that don't even have a type // The old hash format was just an md5 hex hash, with no type information if (preg_match('/^[0-9a-f]{32}$/', $row->user_password)) { if ($this->config->get('PasswordSalt')) { $row->user_password = "******"; } else { $row->user_password = "******"; } } $status = $this->checkPasswordValidity($username, $req->password); if (!$status->isOK()) { // Fatal, can't log in return AuthenticationResponse::newFail($status->getMessage()); } $pwhash = $this->getPassword($row->user_password); if (!$pwhash->equals($req->password)) { if ($this->config->get('LegacyEncoding')) { // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted // Check for this with iconv $cp1252Password = iconv('UTF-8', 'WINDOWS-1252//TRANSLIT', $req->password); if ($cp1252Password === $req->password || !$pwhash->equals($cp1252Password)) { return $this->failResponse($req); } } else { return $this->failResponse($req); } } // @codeCoverageIgnoreStart if ($this->getPasswordFactory()->needsUpdate($pwhash)) { $pwhash = $this->getPasswordFactory()->newFromPlaintext($req->password); \DeferredUpdates::addCallableUpdate(function () use($pwhash, $oldRow) { $dbw = wfGetDB(DB_MASTER); $dbw->update('user', ['user_password' => $pwhash->toString()], ['user_id' => $oldRow->user_id, 'user_password' => $oldRow->user_password], __METHOD__); }); } // @codeCoverageIgnoreEnd $this->setPasswordResetFlag($username, $status, $row); return AuthenticationResponse::newPass($username); }
/** * Save namespace preferences when we're supposed to * * @return bool Whether we wrote something */ protected function saveNamespaces() { $user = $this->getUser(); $request = $this->getRequest(); if ($user->isLoggedIn() && $user->matchEditToken($request->getVal('nsRemember'), 'searchnamespace', $request) && !wfReadOnly()) { // Reset namespace preferences: namespaces are not searched // when they're not mentioned in the URL parameters. foreach (MWNamespace::getValidNamespaces() as $n) { $user->setOption('searchNs' . $n, false); } // The request parameters include all the namespaces to be searched. // Even if they're the same as an existing profile, they're not eaten. foreach ($this->namespaces as $n) { $user->setOption('searchNs' . $n, true); } DeferredUpdates::addCallableUpdate(function () use($user) { $user->saveSettings(); }); return true; } return false; }
/** * @param string $method One of (doPrepare,doSecure,doPublish,doClean) * @param array $params Method arguments * @return Status */ protected function doDirectoryOp($method, array $params) { $status = Status::newGood(); $realParams = $this->substOpPaths($params, $this->backends[$this->masterIndex]); $masterStatus = $this->backends[$this->masterIndex]->{$method}($realParams); $status->merge($masterStatus); foreach ($this->backends as $index => $backend) { if ($index === $this->masterIndex) { continue; // already done } $realParams = $this->substOpPaths($params, $backend); if ($this->asyncWrites) { DeferredUpdates::addCallableUpdate(function () use($backend, $method, $realParams) { $backend->{$method}($realParams); }); } else { $status->merge($backend->{$method}($realParams)); } } return $status; }
/** * 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); }); }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); $page = Title::newFromText($params['page']); if (!$page) { $this->dieUsageMsg('invalidtitle', $params['page']); } $availableNamespaces = $this->veConfig->get('VisualEditorAvailableNamespaces'); if (!isset($availableNamespaces[$page->getNamespace()]) || !$availableNamespaces[$page->getNamespace()]) { $this->dieUsage("VisualEditor is not enabled in namespace " . $page->getNamespace(), 'novenamespace'); } $parserParams = array(); if (isset($params['oldid'])) { $parserParams['oldid'] = $params['oldid']; } $html = $params['html']; if (substr($html, 0, 11) === 'rawdeflate,') { $html = gzinflate(base64_decode(substr($html, 11))); } 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($page, $html, $parserParams, $params['etag']); if ($wikitext === false) { $this->dieUsage('Error contacting the Parsoid/RESTbase server', 'docserver'); } } $saveresult = $this->saveWikitext($page, $wikitext, $params); $editStatus = $saveresult['edit']['result']; // Error if ($editStatus !== 'Success') { $result = array('result' => 'error', 'edit' => $saveresult['edit']); if (isset($saveresult['edit']['spamblacklist'])) { $matches = explode('|', $saveresult['edit']['spamblacklist']); $matcheslist = $this->getLanguage()->listToText($matches); $result['edit']['sberrorparsed'] = $this->msg('spamprotectiontext')->parse() . ' ' . $this->msg('spamprotectionmatch', $matcheslist)->parse(); } // Success } else { if (isset($saveresult['edit']['newrevid'])) { $newRevId = intval($saveresult['edit']['newrevid']); if ($this->veConfig->get('VisualEditorUseChangeTagging')) { // Defer till after the RC row is inserted // @TODO: doEditContent should let callers specify desired tags DeferredUpdates::addCallableUpdate(function () use($newRevId) { ChangeTags::addTags('visualeditor', null, $newRevId, null); }); } } else { $newRevId = $page->getLatestRevId(); } // Return result of parseWikitext instead of saveWikitext so that the // frontend can update the page rendering without a refresh. $result = $this->parseWikitext($page, $newRevId); if ($result === false) { $this->dieUsage('Error contacting the Parsoid/RESTBase server', 'docserver'); } $result['isRedirect'] = $page->isRedirect(); if (class_exists('FlaggablePageView')) { $view = FlaggablePageView::singleton(); // Defeat !$this->isPageView( $request ) || $request->getVal( 'oldid' ) check in setPageContent $view->getContext()->setRequest(new DerivativeRequest($this->getRequest(), array('diff' => null, 'oldid' => '', 'action' => 'view') + $this->getRequest()->getValues())); // The two parameters here are references but we don't care // about what FlaggedRevs does with them. $outputDone = null; $useParserCache = null; $view->setPageContent($outputDone, $useParserCache); $view->displayTag(); } $result['contentSub'] = $this->getOutput()->getSubtitle(); $lang = $this->getLanguage(); if (isset($saveresult['edit']['newtimestamp'])) { $ts = $saveresult['edit']['newtimestamp']; $result['lastModified'] = array('date' => $lang->userDate($ts, $user), 'time' => $lang->userTime($ts, $user)); } if (isset($saveresult['edit']['newrevid'])) { $result['newrevid'] = intval($saveresult['edit']['newrevid']); } $result['result'] = 'success'; } $this->getResult()->addValue(null, $this->getModuleName(), $result); }
/** * @param string $method * @return bool */ protected function doWrite($method) { $ret = true; $args = func_get_args(); array_shift($args); foreach ($this->caches as $i => $cache) { if ($i == 0 || !$this->asyncWrites) { // First store or in sync mode: write now and get result if (!call_user_func_array(array($cache, $method), $args)) { $ret = false; } } else { // Secondary write in async mode: do not block this HTTP request $logger = $this->logger; DeferredUpdates::addCallableUpdate(function () use($cache, $method, $args, $logger) { if (!call_user_func_array(array($cache, $method), $args)) { $logger->warning("Async {$method} op failed"); } }); } } return $ret; }
/** * Reset the notification timestamp of this entry * * @param bool $force Whether to force the write query to be executed even if the * page is not watched or the notification timestamp is already NULL. * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed. * @mode int $mode WatchedItem::DEFERRED/IMMEDIATE */ public function resetNotificationTimestamp($force = '', $oldid = 0, $mode = self::IMMEDIATE) { // Only loggedin user can have a watchlist if (wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed('editmywatchlist')) { return; } if ($force != 'force') { $this->load(); if (!$this->watched || $this->timestamp === null) { return; } } $title = $this->getTitle(); if (!$oldid) { // No oldid given, assuming latest revision; clear the timestamp. $notificationTimestamp = null; } elseif (!$title->getNextRevisionID($oldid)) { // Oldid given and is the latest revision for this title; clear the timestamp. $notificationTimestamp = null; } else { // See if the version marked as read is more recent than the one we're viewing. // Call load() if it wasn't called before due to $force. $this->load(); if ($this->timestamp === null) { // This can only happen if $force is enabled. $notificationTimestamp = null; } else { // Oldid given and isn't the latest; update the timestamp. // This will result in no further notification emails being sent! $notificationTimestamp = Revision::getTimestampFromId($title, $oldid); // We need to go one second to the future because of various strict comparisons // throughout the codebase $ts = new MWTimestamp($notificationTimestamp); $ts->timestamp->add(new DateInterval('PT1S')); $notificationTimestamp = $ts->getTimestamp(TS_MW); if ($notificationTimestamp < $this->timestamp) { if ($force != 'force') { return; } else { // This is a little silly… $notificationTimestamp = $this->timestamp; } } } } // If the page is watched by the user (or may be watched), update the timestamp if ($mode === self::DEFERRED) { $job = new ActivityUpdateJob($title, array('type' => 'updateWatchlistNotification', 'userid' => $this->getUserId(), 'notifTime' => $notificationTimestamp, 'curTime' => time())); // Try to run this post-send DeferredUpdates::addCallableUpdate(function () use($job) { $job->run(); }); } else { $dbw = wfGetDB(DB_MASTER); $dbw->update('watchlist', array('wl_notificationtimestamp' => $dbw->timestampOrNull($notificationTimestamp)), $this->dbCond(), __METHOD__); } $this->timestamp = null; }
/** * What to do if the category table conflicts with the number of results * returned? This function says what. Each type is considered independently * of the other types. * * @param int $rescnt The number of items returned by our database query. * @param int $dbcnt The number of items according to the category table. * @param string $type 'subcat', 'article', or 'file' * @return string A message giving the number of items, to output to HTML. */ private function getCountMessage($rescnt, $dbcnt, $type) { // There are three cases: // 1) The category table figure seems sane. It might be wrong, but // we can't do anything about it if we don't recalculate it on ev- // ery category view. // 2) The category table figure isn't sane, like it's smaller than the // number of actual results, *but* the number of results is less // than $this->limit and there's no offset. In this case we still // know the right figure. // 3) We have no idea. // Check if there's a "from" or "until" for anything // This is a little ugly, but we seem to use different names // for the paging types then for the messages. if ($type === 'article') { $pagingType = 'page'; } else { $pagingType = $type; } $fromOrUntil = false; if (isset($this->from[$pagingType]) && $this->from[$pagingType] !== null || isset($this->until[$pagingType]) && $this->until[$pagingType] !== null) { $fromOrUntil = true; } if ($dbcnt == $rescnt || ($rescnt == $this->limit || $fromOrUntil) && $dbcnt > $rescnt) { // Case 1: seems sane. $totalcnt = $dbcnt; } elseif ($rescnt < $this->limit && !$fromOrUntil) { // Case 2: not sane, but salvageable. Use the number of results. // Since there are fewer than 200, we can also take this opportunity // to refresh the incorrect category table entry -- which should be // quick due to the small number of entries. $totalcnt = $rescnt; $category = $this->cat; DeferredUpdates::addCallableUpdate(function () use($category) { $category->refreshCounts(); }); } else { // Case 3: hopeless. Don't give a total count at all. // Messages: category-subcat-count-limited, category-article-count-limited, // category-file-count-limited return $this->msg("category-{$type}-count-limited")->numParams($rescnt)->parseAsBlock(); } // Messages: category-subcat-count, category-article-count, category-file-count return $this->msg("category-{$type}-count")->numParams($rescnt, $totalcnt)->parseAsBlock(); }
/** * @param Title $title * Updates squid cache for a title. Defers till after main commit(). */ public static function purgeSquid(Title $title) { DeferredUpdates::addCallableUpdate(function () use($title) { $title->purgeSquid(); HTMLFileCache::clearFileCache($title); }); }
/** * Publish the log entry. * * @param int $newId Id of the log entry. * @param string $to One of: rcandudp (default), rc, udp */ public function publish($newId, $to = 'rcandudp') { DeferredUpdates::addCallableUpdate(function () use($newId, $to) { $log = new LogPage($this->getType()); if (!$log->isRestricted()) { $rc = $this->getRecentChange($newId); if ($to === 'rc' || $to === 'rcandudp') { // save RC, passing tags so they are applied there $tags = $this->getTags(); if (is_null($tags)) { $tags = []; } $rc->addTags($tags); $rc->save('pleasedontudp'); } if ($to === 'udp' || $to === 'rcandudp') { $rc->notifyRCFeeds(); } // Log the autopatrol if the log entry is patrollable if ($this->getIsPatrollable() && $rc->getAttribute('rc_patrolled') === 1) { PatrolLog::record($rc, true, $this->getPerformer()); } } }, DeferredUpdates::POSTSEND, wfGetDB(DB_MASTER)); }
private function removeInvalidSuggestions($sourceLanguage, array $existingTitles) { DeferredUpdates::addCallableUpdate(function () use($sourceLanguage, $existingTitles) { // Remove the already existing links from cx_suggestion table $manager = new SuggestionListManager(); $manager->removeTitles($sourceLanguage, $existingTitles); }); }
/** * Save the session * * Update both the backend data and the associated WebRequest(s) to * reflect the state of the the SessionBackend. This might include * persisting or unpersisting the session. * * @param bool $closing Whether the session is being closed */ public function save($closing = false) { $anon = $this->user->isAnon(); if (!$anon && $this->provider->getManager()->isUserSessionPrevented($this->user->getName())) { $this->logger->debug('SessionBackend "{session}" not saving, user {user} was ' . 'passed to SessionManager::preventSessionsForUser', ['session' => $this->id, 'user' => $this->user]); return; } // Ensure the user has a token // @codeCoverageIgnoreStart if (!$anon && !$this->user->getToken(false)) { $this->logger->debug('SessionBackend "{session}" creating token for user {user} on save', ['session' => $this->id, 'user' => $this->user]); $this->user->setToken(); if (!wfReadOnly()) { // Promise that the token set here will be valid; save it at end of request $user = $this->user; \DeferredUpdates::addCallableUpdate(function () use($user) { $user->saveSettings(); }); } $this->metaDirty = true; } // @codeCoverageIgnoreEnd if (!$this->metaDirty && !$this->dataDirty && $this->dataHash !== md5(serialize($this->data))) { $this->logger->debug('SessionBackend "{session}" data dirty due to hash mismatch, {expected} !== {got}', ['session' => $this->id, 'expected' => $this->dataHash, 'got' => md5(serialize($this->data))]); $this->dataDirty = true; } if (!$this->metaDirty && !$this->dataDirty && !$this->forcePersist) { return; } $this->logger->debug('SessionBackend "{session}" save: dataDirty={dataDirty} ' . 'metaDirty={metaDirty} forcePersist={forcePersist}', ['session' => $this->id, 'dataDirty' => (int) $this->dataDirty, 'metaDirty' => (int) $this->metaDirty, 'forcePersist' => (int) $this->forcePersist]); // Persist or unpersist to the provider, if necessary if ($this->metaDirty || $this->forcePersist) { if ($this->persist) { foreach ($this->requests as $request) { $request->setSessionId($this->getSessionId()); $this->provider->persistSession($this, $request); } if (!$closing) { $this->checkPHPSession(); } } else { foreach ($this->requests as $request) { if ($request->getSessionId() === $this->id) { $this->provider->unpersistSession($request); } } } } $this->forcePersist = false; if (!$this->metaDirty && !$this->dataDirty) { return; } // Save session data to store, if necessary $metadata = $origMetadata = ['provider' => (string) $this->provider, 'providerMetadata' => $this->providerMetadata, 'userId' => $anon ? 0 : $this->user->getId(), 'userName' => User::isValidUserName($this->user->getName()) ? $this->user->getName() : null, 'userToken' => $anon ? null : $this->user->getToken(), 'remember' => !$anon && $this->remember, 'forceHTTPS' => $this->forceHTTPS, 'expires' => time() + $this->lifetime, 'loggedOut' => $this->loggedOut, 'persisted' => $this->persist]; \Hooks::run('SessionMetadata', [$this, &$metadata, $this->requests]); foreach ($origMetadata as $k => $v) { if ($metadata[$k] !== $v) { throw new \UnexpectedValueException("SessionMetadata hook changed metadata key \"{$k}\""); } } $flags = $this->persist ? 0 : CachedBagOStuff::WRITE_CACHE_ONLY; $flags |= CachedBagOStuff::WRITE_SYNC; // write to all datacenters $this->store->set(wfMemcKey('MWSession', (string) $this->id), ['data' => $this->data, 'metadata' => $metadata], $metadata['expires'], $flags); $this->metaDirty = false; $this->dataDirty = false; $this->dataHash = md5(serialize($this->data)); $this->expires = $metadata['expires']; }
/** * Clear the user's notification timestamp for the given title. * If e-notif e-mails are on, they will receive notification mails on * the next change of the page if it's watched etc. * @note If the user doesn't have 'editmywatchlist', this will do nothing. * @param Title $title Title of the article to look at * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed. */ public function clearNotification(&$title, $oldid = 0) { global $wgUseEnotif, $wgShowUpdatedMarker; // Do nothing if the database is locked to writes if (wfReadOnly()) { return; } // Do nothing if not allowed to edit the watchlist if (!$this->isAllowed('editmywatchlist')) { return; } // If we're working on user's talk page, we should update the talk page message indicator if ($title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName()) { if (!Hooks::run('UserClearNewTalkNotification', array(&$this, $oldid))) { return; } $that = $this; // Try to update the DB post-send and only if needed... DeferredUpdates::addCallableUpdate(function () use($that, $title, $oldid) { if (!$that->getNewtalk()) { return; // no notifications to clear } // Delete the last notifications (they stack up) $that->setNewtalk(false); // If there is a new, unseen, revision, use its timestamp $nextid = $oldid ? $title->getNextRevisionID($oldid, Title::GAID_FOR_UPDATE) : null; if ($nextid) { $that->setNewtalk(true, Revision::newFromId($nextid)); } }); } if (!$wgUseEnotif && !$wgShowUpdatedMarker) { return; } if ($this->isAnon()) { // Nothing else to do... return; } // Only update the timestamp if the page is being watched. // The query to find out if it is watched is cached both in memcached and per-invocation, // and when it does have to be executed, it can be on a slave // If this is the user's newtalk page, we always update the timestamp $force = ''; if ($title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName()) { $force = 'force'; } $this->getWatchedItem($title)->resetNotificationTimestamp($force, $oldid, WatchedItem::DEFERRED); }
public function publish() { $params = $this->extractRequestParams(); $user = $this->getUser(); $targetTitle = ContentTranslation\SiteMapper::getTargetTitle($params['title'], $user->getName()); $title = Title::newFromText($targetTitle); if (!$title) { $this->dieUsageMsg('invalidtitle', $params['title']); } try { $wikitext = $this->convertHtmlToWikitext($title, $params['html']); } catch (MWException $e) { $this->dieUsage($e->getMessage(), 'parsoidserver'); } $saveresult = $this->saveWikitext($title, $wikitext, $params); $editStatus = $saveresult['edit']['result']; if ($editStatus === 'Success') { if (isset($saveresult['edit']['newrevid'])) { // Add the tags post-send, after RC row insertion $revId = intval($saveresult['edit']['newrevid']); DeferredUpdates::addCallableUpdate(function () use($revId, $params) { ChangeTags::addTags('contenttranslation', null, $revId, null, FormatJson::encode(array('from' => $params['from'], 'to' => $params['to']))); }); } $result = array('result' => 'success'); if (isset($saveresult['edit']['newrevid'])) { $result['newrevid'] = intval($saveresult['edit']['newrevid']); } $this->saveTranslationHistory($params); // Notify user about milestones $this->notifyTranslator(); } else { $result = array('result' => 'error', 'edit' => $saveresult['edit']); } $this->getResult()->addValue(null, $this->getModuleName(), $result); }
/** * Makes an entry in the database corresponding to page creation * Note: the title object must be loaded with the new id using resetArticleID() * * @param string $timestamp * @param Title $title * @param bool $minor * @param User $user * @param string $comment * @param bool $bot * @param string $ip * @param int $size * @param int $newId * @param int $patrol * @return RecentChange */ public static function notifyNew($timestamp, &$title, $minor, &$user, $comment, $bot, $ip = '', $size = 0, $newId = 0, $patrol = 0) { $rc = new RecentChange(); $rc->mTitle = $title; $rc->mPerformer = $user; $rc->mAttribs = array('rc_timestamp' => $timestamp, 'rc_namespace' => $title->getNamespace(), 'rc_title' => $title->getDBkey(), 'rc_type' => RC_NEW, 'rc_source' => self::SRC_NEW, 'rc_minor' => $minor ? 1 : 0, 'rc_cur_id' => $title->getArticleID(), 'rc_user' => $user->getId(), 'rc_user_text' => $user->getName(), 'rc_comment' => $comment, 'rc_this_oldid' => $newId, 'rc_last_oldid' => 0, 'rc_bot' => $bot ? 1 : 0, 'rc_ip' => self::checkIPAddress($ip), 'rc_patrolled' => intval($patrol), 'rc_new' => 1, 'rc_old_len' => 0, 'rc_new_len' => $size, 'rc_deleted' => 0, 'rc_logid' => 0, 'rc_log_type' => null, 'rc_log_action' => '', 'rc_params' => ''); $rc->mExtra = array('prefixedDBkey' => $title->getPrefixedDBkey(), 'lastTimestamp' => 0, 'oldSize' => 0, 'newSize' => $size, 'pageStatus' => 'created'); DeferredUpdates::addCallableUpdate(function () use($rc) { $rc->save(); if ($rc->mAttribs['rc_patrolled']) { PatrolLog::record($rc, true, $rc->getPerformer()); } }); return $rc; }
/** * Attempt submission (no UI) * * @param array $result Array to add statuses to, currently with the * possible keys: * - spam (string): Spam string from content if any spam is detected by * matchSpamRegex. * - sectionanchor (string): Section anchor for a section save. * - nullEdit (boolean): Set if doEditContent is OK. True if null edit, * false otherwise. * - redirect (bool): Set if doEditContent is OK. True if resulting * revision is a redirect. * @param bool $bot True if edit is being made under the bot right. * * @return Status Status object, possibly with a message, but always with * one of the AS_* constants in $status->value, * * @todo FIXME: This interface is TERRIBLE, but hard to get rid of due to * various error display idiosyncrasies. There are also lots of cases * where error metadata is set in the object and retrieved later instead * of being returned, e.g. AS_CONTENT_TOO_BIG and * AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some * time. */ function internalAttemptSave(&$result, $bot = false) { global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize; global $wgContentHandlerUseDB; $status = Status::newGood(); if (!Hooks::run('EditPage::attemptSave', array($this))) { wfDebug("Hook 'EditPage::attemptSave' aborted article saving\n"); $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; return $status; } $spam = $wgRequest->getText('wpAntispam'); if ($spam !== '') { wfDebugLog('SimpleAntiSpam', $wgUser->getName() . ' editing "' . $this->mTitle->getPrefixedText() . '" submitted bogus field "' . $spam . '"'); $status->fatal('spamprotectionmatch', false); $status->value = self::AS_SPAM_ERROR; return $status; } try { # Construct Content object $textbox_content = $this->toEditContent($this->textbox1); } catch (MWContentSerializationException $ex) { $status->fatal('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage()); $status->value = self::AS_PARSE_ERROR; return $status; } # Check image redirect if ($this->mTitle->getNamespace() == NS_FILE && $textbox_content->isRedirect() && !$wgUser->isAllowed('upload')) { $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; $status->setResult(false, $code); return $status; } # Check for spam $match = self::matchSummarySpamRegex($this->summary); if ($match === false && $this->section == 'new') { # $wgSpamRegex is enforced on this new heading/summary because, unlike # regular summaries, it is added to the actual wikitext. if ($this->sectiontitle !== '') { # This branch is taken when the API is used with the 'sectiontitle' parameter. $match = self::matchSpamRegex($this->sectiontitle); } else { # This branch is taken when the "Add Topic" user interface is used, or the API # is used with the 'summary' parameter. $match = self::matchSpamRegex($this->summary); } } if ($match === false) { $match = self::matchSpamRegex($this->textbox1); } if ($match !== false) { $result['spam'] = $match; $ip = $wgRequest->getIP(); $pdbk = $this->mTitle->getPrefixedDBkey(); $match = str_replace("\n", '', $match); wfDebugLog('SpamRegex', "{$ip} spam regex hit [[{$pdbk}]]: \"{$match}\""); $status->fatal('spamprotectionmatch', $match); $status->value = self::AS_SPAM_ERROR; return $status; } if (!Hooks::run('EditFilter', array($this, $this->textbox1, $this->section, &$this->hookError, $this->summary))) { # Error messages etc. could be handled within the hook... $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; return $status; } elseif ($this->hookError != '') { # ...or the hook could be expecting us to produce an error $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR_EXPECTED; return $status; } if ($wgUser->isBlockedFrom($this->mTitle, false)) { // Auto-block user's IP if the account was "hard" blocked $wgUser->spreadAnyEditBlock(); # Check block state against master, thus 'false'. $status->setResult(false, self::AS_BLOCKED_PAGE_FOR_USER); return $status; } $this->kblength = (int) (strlen($this->textbox1) / 1024); if ($this->kblength > $wgMaxArticleSize) { // Error will be displayed by showEditForm() $this->tooBig = true; $status->setResult(false, self::AS_CONTENT_TOO_BIG); return $status; } if (!$wgUser->isAllowed('edit')) { if ($wgUser->isAnon()) { $status->setResult(false, self::AS_READ_ONLY_PAGE_ANON); return $status; } else { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE_LOGGED; return $status; } } $changingContentModel = false; if ($this->contentModel !== $this->mTitle->getContentModel()) { if (!$wgContentHandlerUseDB) { $status->fatal('editpage-cannot-use-custom-model'); $status->value = self::AS_CANNOT_USE_CUSTOM_MODEL; return $status; } elseif (!$wgUser->isAllowed('editcontentmodel')) { $status->setResult(false, self::AS_NO_CHANGE_CONTENT_MODEL); return $status; } $changingContentModel = true; $oldContentModel = $this->mTitle->getContentModel(); } if ($this->changeTags) { $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange($this->changeTags, $wgUser); if (!$changeTagsStatus->isOK()) { $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR; return $changeTagsStatus; } } if (wfReadOnly()) { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE; return $status; } if ($wgUser->pingLimiter() || $wgUser->pingLimiter('linkpurge', 0)) { $status->fatal('actionthrottledtext'); $status->value = self::AS_RATE_LIMITED; return $status; } # If the article has been deleted while editing, don't save it without # confirmation if ($this->wasDeletedSinceLastEdit() && !$this->recreate) { $status->setResult(false, self::AS_ARTICLE_WAS_DELETED); return $status; } # Load the page data from the master. If anything changes in the meantime, # we detect it by using page_latest like a token in a 1 try compare-and-swap. $this->mArticle->loadPageData('fromdbmaster'); $new = !$this->mArticle->exists(); if ($new) { // Late check for create permission, just in case *PARANOIA* if (!$this->mTitle->userCan('create', $wgUser)) { $status->fatal('nocreatetext'); $status->value = self::AS_NO_CREATE_PERMISSION; wfDebug(__METHOD__ . ": no create permission\n"); return $status; } // Don't save a new page if it's blank or if it's a MediaWiki: // message with content equivalent to default (allow empty pages // in this case to disable messages, see bug 50124) $defaultMessageText = $this->mTitle->getDefaultMessageText(); if ($this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false) { $defaultText = $defaultMessageText; } else { $defaultText = ''; } if (!$this->allowBlankArticle && $this->textbox1 === $defaultText) { $this->blankArticle = true; $status->fatal('blankarticle'); $status->setResult(false, self::AS_BLANK_ARTICLE); return $status; } if (!$this->runPostMergeFilters($textbox_content, $status, $wgUser)) { return $status; } $content = $textbox_content; $result['sectionanchor'] = ''; if ($this->section == 'new') { if ($this->sectiontitle !== '') { // Insert the section title above the content. $content = $content->addSectionHeader($this->sectiontitle); } elseif ($this->summary !== '') { // Insert the section title above the content. $content = $content->addSectionHeader($this->summary); } $this->summary = $this->newSectionSummary($result['sectionanchor']); } $status->value = self::AS_SUCCESS_NEW_ARTICLE; } else { # not $new # Article exists. Check for edit conflict. $this->mArticle->clear(); # Force reload of dates, etc. $timestamp = $this->mArticle->getTimestamp(); wfDebug("timestamp: {$timestamp}, edittime: {$this->edittime}\n"); if ($timestamp != $this->edittime) { $this->isConflict = true; if ($this->section == 'new') { if ($this->mArticle->getUserText() == $wgUser->getName() && $this->mArticle->getComment() == $this->newSectionSummary()) { // Probably a duplicate submission of a new comment. // This can happen when squid resends a request after // a timeout but the first one actually went through. wfDebug(__METHOD__ . ": duplicate new section submission; trigger edit conflict!\n"); } else { // New comment; suppress conflict. $this->isConflict = false; wfDebug(__METHOD__ . ": conflict suppressed; new section\n"); } } elseif ($this->section == '' && Revision::userWasLastToEdit(DB_MASTER, $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime)) { # Suppress edit conflict with self, except for section edits where merging is required. wfDebug(__METHOD__ . ": Suppressing edit conflict, same user.\n"); $this->isConflict = false; } } // If sectiontitle is set, use it, otherwise use the summary as the section title. if ($this->sectiontitle !== '') { $sectionTitle = $this->sectiontitle; } else { $sectionTitle = $this->summary; } $content = null; if ($this->isConflict) { wfDebug(__METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'" . " (article time '{$timestamp}')\n"); $content = $this->mArticle->replaceSectionContent($this->section, $textbox_content, $sectionTitle, $this->edittime); } else { wfDebug(__METHOD__ . ": getting section '{$this->section}'\n"); $content = $this->mArticle->replaceSectionContent($this->section, $textbox_content, $sectionTitle); } if (is_null($content)) { wfDebug(__METHOD__ . ": activating conflict; section replace failed.\n"); $this->isConflict = true; $content = $textbox_content; // do not try to merge here! } elseif ($this->isConflict) { # Attempt merge if ($this->mergeChangesIntoContent($content)) { // Successful merge! Maybe we should tell the user the good news? $this->isConflict = false; wfDebug(__METHOD__ . ": Suppressing edit conflict, successful merge.\n"); } else { $this->section = ''; $this->textbox1 = ContentHandler::getContentText($content); wfDebug(__METHOD__ . ": Keeping edit conflict, failed merge.\n"); } } if ($this->isConflict) { $status->setResult(false, self::AS_CONFLICT_DETECTED); return $status; } if (!$this->runPostMergeFilters($content, $status, $wgUser)) { return $status; } if ($this->section == 'new') { // Handle the user preference to force summaries here if (!$this->allowBlankSummary && trim($this->summary) == '') { $this->missingSummary = true; $status->fatal('missingsummary'); // or 'missingcommentheader' if $section == 'new'. Blegh $status->value = self::AS_SUMMARY_NEEDED; return $status; } // Do not allow the user to post an empty comment if ($this->textbox1 == '') { $this->missingComment = true; $status->fatal('missingcommenttext'); $status->value = self::AS_TEXTBOX_EMPTY; return $status; } } elseif (!$this->allowBlankSummary && !$content->equals($this->getOriginalContent($wgUser)) && !$content->isRedirect() && md5($this->summary) == $this->autoSumm) { $this->missingSummary = true; $status->fatal('missingsummary'); $status->value = self::AS_SUMMARY_NEEDED; return $status; } # All's well $sectionanchor = ''; if ($this->section == 'new') { $this->summary = $this->newSectionSummary($sectionanchor); } elseif ($this->section != '') { # Try to get a section anchor from the section source, redirect # to edited section if header found. # XXX: Might be better to integrate this into Article::replaceSection # for duplicate heading checking and maybe parsing. $hasmatch = preg_match("/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches); # We can't deal with anchors, includes, html etc in the header for now, # headline would need to be parsed to improve this. if ($hasmatch && strlen($matches[2]) > 0) { $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText($matches[2]); } } $result['sectionanchor'] = $sectionanchor; // Save errors may fall down to the edit form, but we've now // merged the section into full text. Clear the section field // so that later submission of conflict forms won't try to // replace that into a duplicated mess. $this->textbox1 = $this->toEditText($content); $this->section = ''; $status->value = self::AS_SUCCESS_UPDATE; } if (!$this->allowSelfRedirect && $content->isRedirect() && $content->getRedirectTarget()->equals($this->getTitle())) { // If the page already redirects to itself, don't warn. $currentTarget = $this->getCurrentContent()->getRedirectTarget(); if (!$currentTarget || !$currentTarget->equals($this->getTitle())) { $this->selfRedirect = true; $status->fatal('selfredirect'); $status->value = self::AS_SELF_REDIRECT; return $status; } } // Check for length errors again now that the section is merged in $this->kblength = (int) (strlen($this->toEditText($content)) / 1024); if ($this->kblength > $wgMaxArticleSize) { $this->tooBig = true; $status->setResult(false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED); return $status; } $flags = EDIT_AUTOSUMMARY | ($new ? EDIT_NEW : EDIT_UPDATE) | ($this->minoredit && !$this->isNew ? EDIT_MINOR : 0) | ($bot ? EDIT_FORCE_BOT : 0); $doEditStatus = $this->mArticle->doEditContent($content, $this->summary, $flags, false, $wgUser, $content->getDefaultFormat()); if (!$doEditStatus->isOK()) { // Failure from doEdit() // Show the edit conflict page for certain recognized errors from doEdit(), // but don't show it for errors from extension hooks $errors = $doEditStatus->getErrorsArray(); if (in_array($errors[0][0], array('edit-gone-missing', 'edit-conflict', 'edit-already-exists'))) { $this->isConflict = true; // Destroys data doEdit() put in $status->value but who cares $doEditStatus->value = self::AS_END; } return $doEditStatus; } $result['nullEdit'] = $doEditStatus->hasMessage('edit-no-change'); if ($result['nullEdit']) { // We don't know if it was a null edit until now, so increment here $wgUser->pingLimiter('linkpurge'); } $result['redirect'] = $content->isRedirect(); $this->updateWatchlist(); if ($this->changeTags && isset($doEditStatus->value['revision'])) { // If a revision was created, apply any change tags that were requested $addTags = $this->changeTags; $revId = $doEditStatus->value['revision']->getId(); // Defer this both for performance and so that addTags() sees the rc_id // since the recentchange entry addition is deferred first (bug T100248) DeferredUpdates::addCallableUpdate(function () use($addTags, $revId) { ChangeTags::addTags($addTags, null, $revId); }); } // If the content model changed, add a log entry if ($changingContentModel) { $this->addContentModelChangeLogEntry($wgUser, $oldContentModel, $this->contentModel, $this->summary); } return $status; }