Merge "Moved RecentChange::purgeExpiredChanges to a job"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 10 Feb 2015 21:32:55 +0000 (21:32 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 10 Feb 2015 21:32:55 +0000 (21:32 +0000)
1  2 
autoload.php
includes/DefaultSettings.php
includes/db/Database.php
includes/page/WikiPage.php

diff --combined autoload.php
@@@ -51,7 -51,6 +51,7 @@@ $wgAutoloadLocalClasses = array
        'ApiLogin' => __DIR__ . '/includes/api/ApiLogin.php',
        'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php',
        'ApiMain' => __DIR__ . '/includes/api/ApiMain.php',
 +      'ApiManageTags' => __DIR__ . '/includes/api/ApiManageTags.php',
        'ApiModuleManager' => __DIR__ . '/includes/api/ApiModuleManager.php',
        'ApiMove' => __DIR__ . '/includes/api/ApiMove.php',
        'ApiOpenSearch' => __DIR__ . '/includes/api/ApiOpenSearch.php',
        'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php',
        'DoubleRedirectJob' => __DIR__ . '/includes/jobqueue/jobs/DoubleRedirectJob.php',
        'DoubleRedirectsPage' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php',
 -      'DoubleReplacer' => __DIR__ . '/includes/utils/StringUtils.php',
 +      'DoubleReplacer' => __DIR__ . '/includes/libs/replacers/DoubleReplacer.php',
        'DummyLinker' => __DIR__ . '/includes/Linker.php',
        'DummyTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
        'Dump7ZipOutput' => __DIR__ . '/includes/Export.php',
        'ErrorPageError' => __DIR__ . '/includes/exception/ErrorPageError.php',
        'Exif' => __DIR__ . '/includes/media/Exif.php',
        'ExifBitmapHandler' => __DIR__ . '/includes/media/ExifBitmap.php',
 -      'ExplodeIterator' => __DIR__ . '/includes/utils/StringUtils.php',
 +      'ExplodeIterator' => __DIR__ . '/includes/libs/ExplodeIterator.php',
        'ExportProgressFilter' => __DIR__ . '/maintenance/backup.inc',
        'ExtensionLanguages' => __DIR__ . '/maintenance/language/languages.inc',
        'ExtensionProcessor' => __DIR__ . '/includes/registration/ExtensionProcessor.php',
        'HashBagOStuff' => __DIR__ . '/includes/objectcache/HashBagOStuff.php',
        'HashConfig' => __DIR__ . '/includes/config/HashConfig.php',
        'HashRing' => __DIR__ . '/includes/libs/HashRing.php',
 -      'HashtableReplacer' => __DIR__ . '/includes/utils/StringUtils.php',
 +      'HashtableReplacer' => __DIR__ . '/includes/libs/replacers/HashtableReplacer.php',
        'HistoryAction' => __DIR__ . '/includes/actions/HistoryAction.php',
        'HistoryBlob' => __DIR__ . '/includes/HistoryBlob.php',
        'HistoryBlobCurStub' => __DIR__ . '/includes/HistoryBlob.php',
        'ImageQueryPage' => __DIR__ . '/includes/specialpage/ImageQueryPage.php',
        'ImportReporter' => __DIR__ . '/includes/specials/SpecialImport.php',
        'ImportSiteScripts' => __DIR__ . '/maintenance/importSiteScripts.php',
 +      'ImportSource' => __DIR__ . '/includes/Import.php',
        'ImportStreamSource' => __DIR__ . '/includes/Import.php',
        'ImportStringSource' => __DIR__ . '/includes/Import.php',
        'ImportTitleFactory' => __DIR__ . '/includes/title/ImportTitleFactory.php',
        'MWHookException' => __DIR__ . '/includes/Hooks.php',
        'MWHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'MWLogger' => __DIR__ . '/includes/debug/logger/Logger.php',
 +      'MWLoggerFactory' => __DIR__ . '/includes/debug/logger/Factory.php',
        'MWLoggerLegacyLogger' => __DIR__ . '/includes/debug/logger/legacy/Logger.php',
        'MWLoggerLegacySpi' => __DIR__ . '/includes/debug/logger/legacy/Spi.php',
        'MWLoggerMonologHandler' => __DIR__ . '/includes/debug/logger/monolog/Handler.php',
        'MediaTransformError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'MediaTransformOutput' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'MediaWiki' => __DIR__ . '/includes/MediaWiki.php',
 -      'MediaWikiBagOStuff' => __DIR__ . '/includes/objectcache/SqlBagOStuff.php',
        'MediaWikiI18N' => __DIR__ . '/includes/skins/MediaWikiI18N.php',
        'MediaWikiPageLinkRenderer' => __DIR__ . '/includes/title/MediaWikiPageLinkRenderer.php',
        'MediaWikiSite' => __DIR__ . '/includes/site/MediaWikiSite.php',
        'MessageBlobStore' => __DIR__ . '/includes/MessageBlobStore.php',
        'MessageCache' => __DIR__ . '/includes/cache/MessageCache.php',
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
 +      'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
        'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php',
        'MimeMagic' => __DIR__ . '/includes/MimeMagic.php',
        'MinifyScript' => __DIR__ . '/maintenance/minify.php',
        'RebuildSitesCache' => __DIR__ . '/maintenance/rebuildSitesCache.php',
        'RebuildTextIndex' => __DIR__ . '/maintenance/rebuildtextindex.php',
        'RecentChange' => __DIR__ . '/includes/changes/RecentChange.php',
+       'RecentChangesUpdateJob' => __DIR__ . '/includes/jobqueue/jobs/RecentChangesUpdateJob.php',
        'RecompressTracked' => __DIR__ . '/maintenance/storage/recompressTracked.php',
        'RedirectSpecialArticle' => __DIR__ . '/includes/specialpage/RedirectSpecialPage.php',
        'RedirectSpecialPage' => __DIR__ . '/includes/specialpage/RedirectSpecialPage.php',
        'RefreshLinks' => __DIR__ . '/maintenance/refreshLinks.php',
        'RefreshLinksJob' => __DIR__ . '/includes/jobqueue/jobs/RefreshLinksJob.php',
        'RefreshLinksJob2' => __DIR__ . '/includes/jobqueue/jobs/RefreshLinksJob2.php',
 -      'RegexlikeReplacer' => __DIR__ . '/includes/utils/StringUtils.php',
 +      'RegexlikeReplacer' => __DIR__ . '/includes/libs/replacers/RegexlikeReplacer.php',
        'RemoveInvalidEmails' => __DIR__ . '/maintenance/removeInvalidEmails.php',
        'RemoveUnusedAccounts' => __DIR__ . '/maintenance/removeUnusedAccounts.php',
        'RenameDbPrefix' => __DIR__ . '/maintenance/renameDbPrefix.php',
        'RenderAction' => __DIR__ . '/includes/actions/RenderAction.php',
 -      'ReplacementArray' => __DIR__ . '/includes/utils/StringUtils.php',
 -      'Replacer' => __DIR__ . '/includes/utils/StringUtils.php',
 +      'ReplacementArray' => __DIR__ . '/includes/libs/ReplacementArray.php',
 +      'Replacer' => __DIR__ . '/includes/libs/replacers/Replacer.php',
        'RepoGroup' => __DIR__ . '/includes/filerepo/RepoGroup.php',
        'RequestContext' => __DIR__ . '/includes/context/RequestContext.php',
        'ResetUserTokens' => __DIR__ . '/maintenance/resetUserTokens.php',
        'StatCounter' => __DIR__ . '/includes/StatCounter.php',
        'StatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
        'Status' => __DIR__ . '/includes/Status.php',
 +      'StatusValue' => __DIR__ . '/includes/libs/StatusValue.php',
        'StorageTypeStats' => __DIR__ . '/maintenance/storage/storageTypeStats.php',
        'StoreFileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'StreamFile' => __DIR__ . '/includes/StreamFile.php',
        'StringPrefixSearch' => __DIR__ . '/includes/PrefixSearch.php',
 -      'StringUtils' => __DIR__ . '/includes/utils/StringUtils.php',
 +      'StringUtils' => __DIR__ . '/includes/libs/StringUtils.php',
        'StripState' => __DIR__ . '/includes/parser/StripState.php',
        'StubObject' => __DIR__ . '/includes/StubObject.php',
        'StubUserLang' => __DIR__ . '/includes/StubObject.php',
        'UserloginTemplate' => __DIR__ . '/includes/templates/Userlogin.php',
        'UserrightsPage' => __DIR__ . '/includes/specials/SpecialUserrights.php',
        'UsersPager' => __DIR__ . '/includes/specials/SpecialListusers.php',
 -      'UtfNormal' => __DIR__ . '/includes/normal/UtfNormal.php',
 +      'UtfNormal' => __DIR__ . '/includes/libs/normal/UtfNormal.php',
        'UzConverter' => __DIR__ . '/languages/classes/LanguageUz.php',
        'VFormHTMLForm' => __DIR__ . '/includes/htmlform/VFormHTMLForm.php',
        'ValidateRegistrationFile' => __DIR__ . '/maintenance/validateRegistrationFile.php',
@@@ -270,16 -270,6 +270,16 @@@ $wgFavicon = '/favicon.ico'
   */
  $wgAppleTouchIcon = false;
  
 +/**
 + * Value for the referrer policy meta tag.
 + * One of 'never', 'default', 'origin', 'always'. Setting it to false just
 + * prevents the meta tag from being output.
 + * See http://www.w3.org/TR/referrer-policy/ for details.
 + *
 + * @since 1.25
 + */
 +$wgReferrerPolicy = false;
 +
  /**
   * The local filesystem path to a temporary directory. This is not required to
   * be web accessible.
@@@ -951,12 -941,12 +951,12 @@@ $wgExiv2Command = '/usr/bin/exiv2'
   * are passed as parameters after $srcPath, $dstPath, $width, $height
   */
  $wgSVGConverters = array(
 -      'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output',
 +      'ImageMagick' => '$path/convert -background "#ffffff00" -thumbnail $widthx$height\! $input PNG:$output',
        'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output',
        'inkscape' => '$path/inkscape -z -w $width -f $input -e $output',
        'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d '
                . '$output $input',
 -      'rsvg' => '$path/rsvg -w $width -h $height $input $output',
 +      'rsvg' => '$path/rsvg-convert -w $width -h $height -o $output $input',
        'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output',
        'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ),
  );
@@@ -2111,17 -2101,17 +2111,17 @@@ $wgLanguageConverterCacheType = CACHE_A
   */
  $wgObjectCaches = array(
        CACHE_NONE => array( 'class' => 'EmptyBagOStuff' ),
 -      CACHE_DB => array( 'class' => 'SqlBagOStuff' ),
 +      CACHE_DB => array( 'class' => 'SqlBagOStuff', 'loggroup' => 'SQLBagOStuff' ),
  
        CACHE_ANYTHING => array( 'factory' => 'ObjectCache::newAnything' ),
        CACHE_ACCEL => array( 'factory' => 'ObjectCache::newAccelerator' ),
 -      CACHE_MEMCACHED => array( 'factory' => 'ObjectCache::newMemcached' ),
 +      CACHE_MEMCACHED => array( 'factory' => 'ObjectCache::newMemcached', 'loggroup' => 'memcached' ),
  
        'apc' => array( 'class' => 'APCBagOStuff' ),
        'xcache' => array( 'class' => 'XCacheBagOStuff' ),
        'wincache' => array( 'class' => 'WinCacheBagOStuff' ),
 -      'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff' ),
 -      'memcached-pecl' => array( 'class' => 'MemcachedPeclBagOStuff' ),
 +      'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff', 'loggroup' => 'memcached' ),
 +      'memcached-pecl' => array( 'class' => 'MemcachedPeclBagOStuff', 'loggroup' => 'memcached' ),
        'hash' => array( 'class' => 'HashBagOStuff' ),
  );
  
@@@ -3154,7 -3144,6 +3154,7 @@@ $wgExperimentalHtmlIds = false
   * for the icon, the following keys are used:
   * - src: An absolute url to the image to use for the icon, this is recommended
   *        but not required, however some skins will ignore icons without an image
 + * - srcset: optional additional-resolution images; see HTML5 specs
   * - url: The url to use in the a element around the text or icon, if not set an a element will
   *        not be outputted
   * - alt: This is the text form of the icon, it will be displayed without an image in
@@@ -3171,9 -3160,7 +3171,9 @@@ $wgFooterIcons = array
        ),
        "poweredby" => array(
                "mediawiki" => array(
 -                      "src" => null, // Defaults to "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
 +                      "src" => null, // Defaults to point at
 +                                     // "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
 +                                     // plus srcset for 1.5x, 2x resolution variants.
                        "url" => "//www.mediawiki.org/",
                        "alt" => "Powered by MediaWiki",
                )
@@@ -3858,12 -3845,20 +3858,12 @@@ $wgNamespacesWithSubpages = array
   * A message with the suffix '-desc' should be added as a description message
   * to have extra information on Special:TrackingCategories.
   *
 + * @deprecated since 1.25 Extensions should now register tracking categories using
 + *                        the new extension registration system.
 + *
   * @since 1.23
   */
 -$wgTrackingCategories = array(
 -      'index-category',
 -      'noindex-category',
 -      'duplicate-args-category',
 -      'expensive-parserfunction-category',
 -      'post-expand-template-argument-category',
 -      'post-expand-template-inclusion-category',
 -      'hidden-category-category',
 -      'broken-file-category',
 -      'node-count-exceeded-category',
 -      'expansion-depth-exceeded-category',
 -);
 +$wgTrackingCategories = array();
  
  /**
   * Array of namespaces which can be deemed to contain valid "content", as far
@@@ -4622,7 -4617,6 +4622,7 @@@ $wgGroupPermissions['sysop']['suppressr
  #$wgGroupPermissions['sysop']['pagelang'] = true;
  #$wgGroupPermissions['sysop']['upload_by_url'] = true;
  $wgGroupPermissions['sysop']['mergehistory'] = true;
 +$wgGroupPermissions['sysop']['managechangetags'] = true;
  
  // Permission to change users' group assignments
  $wgGroupPermissions['bureaucrat']['userrights'] = true;
@@@ -5285,16 -5279,16 +5285,16 @@@ $wgDebugDumpSqlLength = 500
  $wgDebugLogGroups = array();
  
  /**
 - * Default service provider for creating MWLogger instances.
 + * Default service provider for creating Psr\Log\LoggerInterface instances.
   *
   * The value should be an array suitable for use with
   * ObjectFactory::getObjectFromSpec(). The created object is expected to
   * implement the MWLoggerSpi interface. See ObjectFactory for additional
   * details.
   *
 - * Alternately the MWLogger::registerProvider method can be called to inject
 - * an MWLoggerSpi instance into MWLogger and bypass the use of this
 - * configuration variable entirely.
 + * Alternately the MWLoggerFactory::registerProvider method can be called to
 + * inject an MWLoggerSpi instance into MWLoggerFactory and bypass the use of
 + * this configuration variable entirely.
   *
   * @since 1.25
   * @var array $wgMWLoggerDefaultSpi
@@@ -6322,7 -6316,7 +6322,7 @@@ $wgAutoloadAttemptLowercase = true
   *     'version' => '1.9.0',
   *     'url' => 'http://example.org/example-extension/',
   *     'descriptionmsg' => 'exampleextension-desc',
 - *     'license-name' => 'GPL-2.0',
 + *     'license-name' => 'GPL-2.0+',
   * );
   * @endcode
   *
   *    localizable message (omit in favour of 'descriptionmsg').
   *
   * - license-name: Short name of the license (used as label for the link), such
 - *   as "GPL-2.0" or "MIT" (https://spdx.org/licenses/ for a list of identifiers).
 + *   as "GPL-2.0+" or "MIT" (https://spdx.org/licenses/ for a list of identifiers).
   */
  $wgExtensionCredits = array();
  
@@@ -6417,6 -6411,7 +6417,7 @@@ $wgJobClasses = array
        'AssembleUploadChunks' => 'AssembleUploadChunksJob',
        'PublishStashedFile' => 'PublishStashedFileJob',
        'ThumbnailRender' => 'ThumbnailRenderJob',
+       'recentChangesUpdate' => 'RecentChangesUpdateJob',
        'null' => 'NullJob'
  );
  
@@@ -6566,7 -6561,6 +6567,7 @@@ $wgLogTypes = array
        'patrol',
        'merge',
        'suppress',
 +      'managetags',
  );
  
  /**
@@@ -6695,10 -6689,6 +6696,10 @@@ $wgLogActionsHandlers = array
        'upload/overwrite' => 'LogFormatter',
        'upload/revert' => 'LogFormatter',
        'merge/merge' => 'MergeLogFormatter',
 +      'managetags/create' => 'LogFormatter',
 +      'managetags/delete' => 'LogFormatter',
 +      'managetags/activate' => 'LogFormatter',
 +      'managetags/deactivate' => 'LogFormatter',
  );
  
  /**
diff --combined includes/db/Database.php
@@@ -46,6 -46,9 +46,6 @@@ abstract class DatabaseBase implements 
        /** Maximum time to wait before retry */
        const DEADLOCK_DELAY_MAX = 1500000;
  
 -      /** How many row changes in a write query trigger a log entry */
 -      const LOG_WRITE_THRESHOLD = 300;
 -
        protected $mLastQuery = '';
        protected $mDoneWrites = false;
        protected $mPHPError = false;
  
                # Log the query time and feed it into the DB trx profiler
                if ( $queryProf != '' ) {
 +                      $that = $this;
                        $queryStartTime = microtime( true );
                        $queryProfile = new ScopedCallback(
 -                              function () use ( $queryStartTime, $queryProf, $isMaster ) {
 -                                      $trxProfiler = Profiler::instance()->getTransactionProfiler();
 -                                      $trxProfiler->recordQueryCompletion( $queryProf, $queryStartTime, $isMaster );
 +                              function () use ( $that, $queryStartTime, $queryProf, $isMaster ) {
 +                                      $n = $that->affectedRows();
 +                                      $trxProf = Profiler::instance()->getTransactionProfiler();
 +                                      $trxProf->recordQueryCompletion( $queryProf, $queryStartTime, $isMaster, $n );
                                }
                        );
                }
  
                if ( false === $ret ) {
                        $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
 -              } else {
 -                      $n = $this->affectedRows();
 -                      if ( $isWriteQuery && $n > self::LOG_WRITE_THRESHOLD && PHP_SAPI !== 'cli' ) {
 -                              wfDebugLog( 'DBPerformance',
 -                                      "Query affected $n rows:\n" .
 -                                      DatabaseBase::generalizeSQL( $sql ) . "\n" . wfBacktrace( true ) );
 -                      }
                }
  
                $res = $this->resultObject( $ret );
         *
         * @return bool|mixed The value from the field, or false on failure.
         */
-       public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
-               $options = array()
+       public function selectField(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = array()
        ) {
+               if ( $var === '*' ) { // sanity
+                       throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+               }
                if ( !is_array( $options ) ) {
                        $options = array( $options );
                }
                $options['LIMIT'] = 1;
  
                $res = $this->select( $table, $var, $cond, $fname, $options );
                if ( $res === false || !$this->numRows( $res ) ) {
                        return false;
                }
                }
        }
  
+       /**
+        * A SELECT wrapper which returns a list of single field values from result rows.
+        *
+        * Usually throws a DBQueryError on failure. If errors are explicitly
+        * ignored, returns false on failure.
+        *
+        * If no result rows are returned from the query, false is returned.
+        *
+        * @param string|array $table Table name. See DatabaseBase::select() for details.
+        * @param string $var The field name to select. This must be a valid SQL
+        *   fragment: do not use unvalidated user input.
+        * @param string|array $cond The condition array. See DatabaseBase::select() for details.
+        * @param string $fname The function name of the caller.
+        * @param string|array $options The query options. See DatabaseBase::select() for details.
+        *
+        * @return bool|array The values from the field, or false on failure
+        * @since 1.25
+        */
+       public function selectFieldValues(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = array()
+       ) {
+               if ( $var === '*' ) { // sanity
+                       throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+               }
+               if ( !is_array( $options ) ) {
+                       $options = array( $options );
+               }
+               $res = $this->select( $table, $var, $cond, $fname, $options );
+               if ( $res === false ) {
+                       return false;
+               }
+               $values = array();
+               foreach ( $res as $row ) {
+                       $values[] = $row->$var;
+               }
+               return $values;
+       }
        /**
         * Returns an optional USE INDEX clause to go after the table, and a
         * string to go at the end of the query.
  
                if ( $res ) {
                        $row = $this->fetchRow( $res );
 -                      $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
 +                      $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
                }
  
                return $rows;
  
                if ( $res ) {
                        $row = $this->fetchRow( $res );
 -                      $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
 +                      $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
                }
  
                return $rows;
@@@ -2029,8 -2029,7 +2029,8 @@@ class WikiPage implements Page, IDBAcce
         * Returns a stdClass with source, pst and output members
         *
         * @param Content $content
 -       * @param int|null $revid
 +       * @param Revision|int|null $revision Revision object. For backwards compatibility, a
 +       *        revision ID is also accepted, but this is deprecated.
         * @param User|null $user
         * @param string|null $serialFormat
         * @param bool $useCache Check shared prepared edit cache
         * @since 1.21
         */
        public function prepareContentForEdit(
 -              Content $content, $revid = null, User $user = null, $serialFormat = null, $useCache = true
 +              Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true
        ) {
                global $wgContLang, $wgUser, $wgAjaxEditStash;
  
 +              if ( is_object( $revision ) ) {
 +                      $revid = $revision->getId();
 +              } else {
 +                      $revid = $revision;
 +                      // This code path is deprecated, and nothing is known to
 +                      // use it, so performance here shouldn't be a worry.
 +                      if ( $revid !== null ) {
 +                              $revision = Revision::newFromId( $revid, Revision::READ_LATEST );
 +                      } else {
 +                              $revision = null;
 +                      }
 +              }
 +
                $user = is_null( $user ) ? $wgUser : $user;
                //XXX: check $user->getId() here???
  
                if ( $cachedEdit ) {
                        $edit->output = $cachedEdit->output;
                } else {
 +                      if ( $revision ) {
 +                              // We get here if vary-revision is set. This means that this page references
 +                              // itself (such as via self-transclusion). In this case, we need to make sure
 +                              // that any such self-references refer to the newly-saved revision, and not
 +                              // to the previous one, which could otherwise happen due to slave lag.
 +                              $oldCallback = $edit->popts->setCurrentRevisionCallback(
 +                                      function ( $title, $parser = false ) use ( $revision, &$oldCallback ) {
 +                                              if ( $title->equals( $revision->getTitle() ) ) {
 +                                                      return $revision;
 +                                              } else {
 +                                                      return call_user_func(
 +                                                              $oldCallback,
 +                                                              $title,
 +                                                              $parser
 +                                                      );
 +                                              }
 +                                      }
 +                              );
 +                      }
                        $edit->output = $edit->pstContent
                                ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
                                : null;
                // already pre-save transformed once.
                if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
                        wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
 -                      $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
 +                      $editInfo = $this->prepareContentForEdit( $content, $revision, $user );
                } else {
                        wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
                        $editInfo = $this->mPreparedEdit;
                Hooks::run( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
  
                if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
-                       if ( 0 == mt_rand( 0, 99 ) ) {
-                               // Flush old entries from the `recentchanges` table; we do this on
-                               // random requests so as to avoid an increase in writes for no good reason
-                               RecentChange::purgeExpiredChanges();
-                       }
+                       // Flush old entries from the `recentchanges` table
+                       JobQueueGroup::singleton()->push( RecentChangesUpdateJob::newPurgeJob() );
                }
  
                if ( !$this->exists() ) {
  
                // Get the last edit not by this guy...
                // Note: these may not be public values
 -              $user = intval( $current->getRawUser() );
 -              $user_text = $dbw->addQuotes( $current->getRawUserText() );
 +              $user = intval( $current->getUser( Revision::RAW ) );
 +              $user_text = $dbw->addQuotes( $current->getUserText( Revision::RAW ) );
                $s = $dbw->selectRow( 'revision',
                        array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
                        array( 'rev_page' => $current->getPage(),