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');
 }
Example #2
0
 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');
 }
Example #3
0
 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());
     }
 }
Example #4
0
 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);
             }
         }
     });
 }
Example #11
0
 /**
  * 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;
 }
Example #12
0
 /**
  * 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);
             }
         }
     }
 }
Example #14
0
 /**
  * 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);
 }
Example #16
0
 /**
  * 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;
 }
Example #18
0
 /**
  * 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;
 }
Example #21
0
 /**
  * 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);
     });
 }
Example #24
0
 /**
  * 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);
     });
 }
Example #26
0
 /**
  * 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'];
 }
Example #27
0
 /**
  * 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;
 }