Merge "Drop outdated "documentation reviewed" tags"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 18 Mar 2015 16:56:58 +0000 (16:56 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 18 Mar 2015 16:56:58 +0000 (16:56 +0000)
1  2 
includes/MediaWiki.php
includes/Title.php
includes/cache/BacklinkCache.php
includes/page/Article.php
includes/page/WikiPage.php

diff --combined includes/MediaWiki.php
@@@ -22,8 -22,6 +22,6 @@@
  
  /**
   * The MediaWiki class is the helper class for the index.php entry point.
-  *
-  * @internal documentation reviewed 15 Mar 2010
   */
  class MediaWiki {
        /**
@@@ -59,7 -57,7 +57,7 @@@
                $request = $this->context->getRequest();
                $curid = $request->getInt( 'curid' );
                $title = $request->getVal( 'title' );
 -              $action = $request->getVal( 'action', 'view' );
 +              $action = $request->getVal( 'action' );
  
                if ( $request->getCheck( 'search' ) ) {
                        // Compatibility with old search URLs which didn't use Special:Search
        private function performRequest() {
                global $wgTitle;
  
 -              wfProfileIn( __METHOD__ );
 -
                $request = $this->context->getRequest();
                $requestTitle = $title = $this->context->getTitle();
                $output = $this->context->getOutput();
                        || $title->isSpecial( 'Badtitle' )
                ) {
                        $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
 -                      wfProfileOut( __METHOD__ );
                        throw new BadTitleError();
                }
  
                        $this->context->setTitle( $badTitle );
                        $wgTitle = $badTitle;
  
 -                      wfProfileOut( __METHOD__ );
                        throw new PermissionsError( 'read', $permErrors );
                }
  
                                $output->redirect( $url, 301 );
                        } else {
                                $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
 -                              wfProfileOut( __METHOD__ );
                                throw new BadTitleError();
                        }
                // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
                        } elseif ( is_string( $article ) ) {
                                $output->redirect( $article );
                        } else {
 -                              wfProfileOut( __METHOD__ );
                                throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
                                        . " returned neither an object nor a URL" );
                        }
                        $user->addAutopromoteOnceGroups( 'onView' );
                }
  
 -              wfProfileOut( __METHOD__ );
        }
  
        /**
         * @return mixed An Article, or a string to redirect to another URL
         */
        private function initializeArticle() {
 -              wfProfileIn( __METHOD__ );
  
                $title = $this->context->getTitle();
                if ( $this->context->canUseWikiPage() ) {
                // NS_MEDIAWIKI has no redirects.
                // It is also used for CSS/JS, so performance matters here...
                if ( $title->getNamespace() == NS_MEDIAWIKI ) {
 -                      wfProfileOut( __METHOD__ );
                        return $article;
                }
  
                                if ( is_string( $target ) ) {
                                        if ( !$this->config->get( 'DisableHardRedirects' ) ) {
                                                // we'll need to redirect
 -                                              wfProfileOut( __METHOD__ );
                                                return $target;
                                        }
                                }
                        }
                }
  
 -              wfProfileOut( __METHOD__ );
                return $article;
        }
  
         * @param Title $requestTitle The original title, before any redirects were applied
         */
        private function performAction( Page $page, Title $requestTitle ) {
 -              wfProfileIn( __METHOD__ );
  
                $request = $this->context->getRequest();
                $output = $this->context->getOutput();
                if ( !Hooks::run( 'MediaWikiPerformAction',
                                array( $output, $page, $title, $user, $request, $this ) )
                ) {
 -                      wfProfileOut( __METHOD__ );
                        return;
                }
  
                if ( $action instanceof Action ) {
                        # Let Squid cache things if we can purge them.
                        if ( $this->config->get( 'UseSquid' ) &&
 -                              in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() )
 +                              in_array(
 +                                      // Use PROTO_INTERNAL because that's what getSquidURLs() uses
 +                                      wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ),
 +                                      $requestTitle->getSquidURLs()
 +                              )
                        ) {
                                $output->setSquidMaxage( $this->config->get( 'SquidMaxage' ) );
                        }
  
                        $action->show();
 -                      wfProfileOut( __METHOD__ );
                        return;
                }
  
                        $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
                }
  
 -              wfProfileOut( __METHOD__ );
        }
  
        /**
         * @return bool
         */
        private function checkMaxLag() {
 -              wfProfileIn( __METHOD__ );
                $maxLag = $this->context->getRequest()->getVal( 'maxlag' );
                if ( !is_null( $maxLag ) ) {
                        list( $host, $lag ) = wfGetLB()->getMaxLag();
                                        echo "Waiting for a database server: $lag seconds lagged\n";
                                }
  
 -                              wfProfileOut( __METHOD__ );
 -
                                exit;
                        }
                }
 -              wfProfileOut( __METHOD__ );
                return true;
        }
  
        private function main() {
                global $wgTitle;
  
 -              wfProfileIn( __METHOD__ );
 -
                $request = $this->context->getRequest();
  
                // Send Ajax requests to the Ajax dispatcher.
 -              if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action', 'view' ) == 'ajax' ) {
 -
 +              if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action' ) === 'ajax' ) {
                        // Set a dummy title, because $wgTitle == null might break things
                        $title = Title::makeTitle( NS_MAIN, 'AJAX' );
                        $this->context->setTitle( $title );
  
                        $dispatcher = new AjaxDispatcher( $this->config );
                        $dispatcher->performAction( $this->context->getUser() );
 -                      wfProfileOut( __METHOD__ );
                        return;
                }
  
                $action = $this->getAction();
                $wgTitle = $title;
  
 +              $trxProfiler = Profiler::instance()->getTransactionProfiler();
 +
 +              // Aside from rollback, master queries should not happen on GET requests.
 +              // Periodic or "in passing" updates on GET should use the job queue.
 +              if ( !$request->wasPosted()
 +                      && in_array( $action, array( 'view', 'edit', 'history' ) )
 +              ) {
 +                      $trxProfiler->setExpectation( 'masterConns', 0, __METHOD__ );
 +                      $trxProfiler->setExpectation( 'writes', 0, __METHOD__ );
 +              } else {
 +                      $trxProfiler->setExpectation( 'maxAffected', 500, __METHOD__ );
 +              }
 +
                // If the user has forceHTTPS set to true, or if the user
                // is in a group requiring HTTPS, or if they have the HTTPS
                // preference set, redirect them to HTTPS.
                                $output->addVaryHeader( 'X-Forwarded-Proto' );
                                $output->redirect( $redirUrl );
                                $output->output();
 -                              wfProfileOut( __METHOD__ );
                                return;
                        }
                }
  
                if ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) {
 -                      wfProfileIn( 'main-try-filecache' );
                        if ( HTMLFileCache::useFileCache( $this->context ) ) {
                                // Try low-level file cache hit
                                $cache = new HTMLFileCache( $title, $action );
                                        $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
                                        // Tell OutputPage that output is taken care of
                                        $this->context->getOutput()->disable();
 -                                      wfProfileOut( 'main-try-filecache' );
 -                                      wfProfileOut( __METHOD__ );
                                        return;
                                }
                        }
 -                      wfProfileOut( 'main-try-filecache' );
                }
  
                // Actually do the work of the request and build up any output
                // Output everything!
                $this->context->getOutput()->output();
  
 -              wfProfileOut( __METHOD__ );
        }
  
        /**
         * Ends this task peacefully
         */
        public function restInPeace() {
 +              // Ignore things like master queries/connections on GET requests
 +              // as long as they are in deferred updates (which catch errors).
 +              Profiler::instance()->getTransactionProfiler()->resetExpectations();
 +
                // Do any deferred jobs
                DeferredUpdates::doUpdates( 'commit' );
  
                        return; // recursion guard
                }
  
 -              $section = new ProfileSection( __METHOD__ );
 -
                if ( $jobRunRate < 1 ) {
                        $max = mt_getrandmax();
                        if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
                        $n = intval( $jobRunRate );
                }
  
 +              $runJobsLogger = MWLoggerFactory::getInstance( 'runJobs' );
 +
                if ( !$this->config->get( 'RunJobsAsync' ) ) {
                        // Fall back to running the job here while the user waits
 -                      $runner = new JobRunner();
 +                      $runner = new JobRunner( $runJobsLogger );
                        $runner->run( array( 'maxJobs'  => $n ) );
                        return;
                }
                );
                wfRestoreWarnings();
                if ( !$sock ) {
 -                      wfDebugLog( 'runJobs', "Failed to start cron API (socket error $errno): $errstr\n" );
 +                      $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" );
                        // Fall back to running the job here while the user waits
 -                      $runner = new JobRunner();
 +                      $runner = new JobRunner( $runJobsLogger );
                        $runner->run( array( 'maxJobs'  => $n ) );
                        return;
                }
                $url = wfAppendQuery( wfScript( 'index' ), $query );
                $req = "POST $url HTTP/1.1\r\nHost: {$info['host']}\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n";
  
 -              wfDebugLog( 'runJobs', "Running $n job(s) via '$url'\n" );
 +              $runJobsLogger->info( "Running $n job(s) via '$url'" );
                // Send a cron API request to be performed in the background.
                // Give up if this takes too long to send (which should be rare).
                stream_set_timeout( $sock, 1 );
                $bytes = fwrite( $sock, $req );
                if ( $bytes !== strlen( $req ) ) {
 -                      wfDebugLog( 'runJobs', "Failed to start cron API (socket write error)\n" );
 +                      $runJobsLogger->error( "Failed to start cron API (socket write error)" );
                } else {
                        // Do not wait for the response (the script should handle client aborts).
                        // Make sure that we don't close before that script reaches ignore_user_abort().
                        $status = fgets( $sock );
                        if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) {
 -                              wfDebugLog( 'runJobs', "Failed to start cron API: received '$status'\n" );
 +                              $runJobsLogger->error( "Failed to start cron API: received '$status'" );
                        }
                }
                fclose( $sock );
diff --combined includes/Title.php
@@@ -29,8 -29,6 +29,6 @@@
   *       however, it does so inefficiently.
   * @note Consider using a TitleValue object instead. TitleValue is more lightweight
   *       and does not rely on global state or the database.
-  *
-  * @internal documentation reviewed 15 Mar 2010
   */
  class Title {
        /** @var MapCacheLRU */
         * Note that this doesn't pick up many things that could be wrong with titles, but that
         * replacing this regex with something valid will make many titles valid.
         *
 -       * @todo move this into MediaWikiTitleCodec
 +       * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead
         *
         * @return string Regex string
         */
        static function getTitleInvalidRegex() {
 -              static $rxTc = false;
 -              if ( !$rxTc ) {
 -                      # Matching titles will be held as illegal.
 -                      $rxTc = '/' .
 -                              # Any character not allowed is forbidden...
 -                              '[^' . self::legalChars() . ']' .
 -                              # URL percent encoding sequences interfere with the ability
 -                              # to round-trip titles -- you can't link to them consistently.
 -                              '|%[0-9A-Fa-f]{2}' .
 -                              # XML/HTML character references produce similar issues.
 -                              '|&[A-Za-z0-9\x80-\xff]+;' .
 -                              '|&#[0-9]+;' .
 -                              '|&#x[0-9A-Fa-f]+;' .
 -                              '/S';
 -              }
 -
 -              return $rxTc;
 +              wfDeprecated( __METHOD__, '1.25' );
 +              return MediaWikiTitleCodec::getTitleInvalidRegex();
        }
  
        /**
         * @return string Content model id
         */
        public function getContentModel( $flags = 0 ) {
 -              # Calling getArticleID() loads the field from cache as needed
                if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
                        $linkCache = LinkCache::singleton();
 +                      $linkCache->addLinkObj( $this ); # in case we already had an article ID
                        $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
                }
  
                return Title::makeTitle( $subjectNS, $this->getDBkey() );
        }
  
 +      /**
 +       * Get the other title for this page, if this is a subject page
 +       * get the talk page, if it is a subject page get the talk page
 +       *
 +       * @since 1.25
 +       * @throws MWException
 +       * @return Title
 +       */
 +      public function getOtherPage() {
 +              if ( $this->isSpecialPage() ) {
 +                      throw new MWException( 'Special pages cannot have other pages' );
 +              }
 +              if ( $this->isTalkPage() ) {
 +                      return $this->getSubjectPage();
 +              } else {
 +                      return $this->getTalkPage();
 +              }
 +      }
 +
        /**
         * Get the default namespace index, for when there is no namespace
         *
         * @return string The URL
         */
        public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
 -              wfProfileIn( __METHOD__ );
                if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
                        $ret = $this->getFullURL( $query, $query2, $proto );
                } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
                } else {
                        $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
                }
 -              wfProfileOut( __METHOD__ );
                return $ret;
        }
  
         * @param string $action Action that permission needs to be checked for
         * @param User $user User to check (since 1.19); $wgUser will be used if not
         *   provided.
 -       * @param bool $doExpensiveQueries Set this to false to avoid doing
 -       *   unnecessary queries.
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @return bool
         */
 -      public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
 +      public function userCan( $action, $user = null, $rigor = 'secure' ) {
                if ( !$user instanceof User ) {
                        global $wgUser;
                        $user = $wgUser;
                }
  
 -              return !count( $this->getUserPermissionsErrorsInternal(
 -                      $action, $user, $doExpensiveQueries, true ) );
 +              return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
        }
  
        /**
         *
         * @param string $action Action that permission needs to be checked for
         * @param User $user User to check
 -       * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary
 -       *   queries by skipping checks for cascading protections and user blocks.
 +       * @param string $rigor One of (quick,full,secure)
 +       *   - quick  : does cheap permission checks from slaves (usable for GUI creation)
 +       *   - full   : does cheap and expensive checks possibly from a slave
 +       *   - secure : does cheap and expensive checks, using the master as needed
 +       * @param bool $short Set this to true to stop after the first permission error.
         * @param array $ignoreErrors Array of Strings Set this to a list of message keys
         *   whose corresponding errors may be ignored.
         * @return array Array of arguments to wfMessage to explain permissions problems.
         */
 -      public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true,
 -              $ignoreErrors = array()
 +      public function getUserPermissionsErrors(
 +              $action, $user, $rigor = 'secure', $ignoreErrors = array()
        ) {
 -              $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
 +              $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
  
                // Remove the errors being ignored.
                foreach ( $errors as $index => $error ) {
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkQuickPermissions( $action, $user, $errors,
 -              $doExpensiveQueries, $short
 -      ) {
 +      private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
                if ( !Hooks::run( 'TitleQuickPermissions',
 -                      array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) )
 +                      array( $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ) )
                ) {
                        return $errors;
                }
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
 +      private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
                // Use getUserPermissionsErrors instead
                $result = '';
                if ( !Hooks::run( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
                }
                // Check getUserPermissionsErrorsExpensive hook
                if (
 -                      $doExpensiveQueries
 +                      $rigor !== 'quick'
                        && !( $short && count( $errors ) > 0 )
                        && !Hooks::run( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
                ) {
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkSpecialsAndNSPermissions( $action, $user, $errors,
 -              $doExpensiveQueries, $short
 -      ) {
 +      private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
                # Only 'createaccount' can be performed on special pages,
                # which don't actually exist in the DB.
                if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
 +      private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
                # Protect css/js subpages of user pages
                # XXX: this might be better using restrictions
                # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
 +      private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
                foreach ( $this->getRestrictions( $action ) as $right ) {
                        // Backwards compatibility, rewrite sysop -> editprotected
                        if ( $right == 'sysop' ) {
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkCascadingSourcesRestrictions( $action, $user, $errors,
 -              $doExpensiveQueries, $short
 -      ) {
 -              if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
 +      private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
 +              if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
                        # We /could/ use the protection level on the source page, but it's
                        # fairly ugly as we have to establish a precedence hierarchy for pages
                        # included by multiple cascade-protected pages. So just restrict
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkActionPermissions( $action, $user, $errors,
 -              $doExpensiveQueries, $short
 -      ) {
 +      private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
                global $wgDeleteRevisionsLimit, $wgLang;
  
                if ( $action == 'protect' ) {
 -                      if ( count( $this->getUserPermissionsErrorsInternal( 'edit',
 -                              $user, $doExpensiveQueries, true ) )
 -                      ) {
 +                      if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
                                // If they can't edit, they shouldn't protect.
                                $errors[] = array( 'protect-cantedit' );
                        }
                                $errors[] = array( 'immobile-target-page' );
                        }
                } elseif ( $action == 'delete' ) {
 -                      $tempErrors = $this->checkPageRestrictions( 'edit',
 -                              $user, array(), $doExpensiveQueries, true );
 +                      $tempErrors = $this->checkPageRestrictions( 'edit', $user, array(), $rigor, true );
                        if ( !$tempErrors ) {
                                $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
 -                                      $user, $tempErrors, $doExpensiveQueries, true );
 +                                      $user, $tempErrors, $rigor, true );
                        }
                        if ( $tempErrors ) {
                                // If protection keeps them from editing, they shouldn't be able to delete.
                                $errors[] = array( 'deleteprotected' );
                        }
 -                      if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
 +                      if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
                                && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
                        ) {
                                $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
 +      private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
                // Account creation blocks handled at userlogin.
                // Unblocking handled in SpecialUnblock
 -              if ( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
 +              if ( $rigor === 'quick' || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
                        return $errors;
                }
  
                        $errors[] = array( 'confirmedittext' );
                }
  
 -              if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
 +              $useSlave = ( $rigor !== 'secure' );
 +              if ( ( $action == 'edit' || $action == 'create' )
 +                      && !$user->isBlockedFrom( $this, $useSlave )
 +              ) {
                        // Don't block the user from editing their own talk page unless they've been
                        // explicitly blocked from that too.
 -              } elseif ( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
 +              } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
                        // @todo FIXME: Pass the relevant context into this function.
                        $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
                }
         * @param string $action The action to check
         * @param User $user User to check
         * @param array $errors List of current errors
 -       * @param bool $doExpensiveQueries Whether or not to perform expensive queries
 +       * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @param bool $short Short circuit on first error
         *
         * @return array List of errors
         */
 -      private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
 +      private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
                global $wgWhitelistRead, $wgWhitelistReadRegexp;
  
                $whitelisted = false;
         *
         * @param string $action Action that permission needs to be checked for
         * @param User $user User to check
 -       * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries.
 +       * @param string $rigor One of (quick,full,secure)
 +       *   - quick  : does cheap permission checks from slaves (usable for GUI creation)
 +       *   - full   : does cheap and expensive checks possibly from a slave
 +       *   - secure : does cheap and expensive checks, using the master as needed
         * @param bool $short Set this to true to stop after the first permission error.
         * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
         */
 -      protected function getUserPermissionsErrorsInternal( $action, $user,
 -              $doExpensiveQueries = true, $short = false
 +      protected function getUserPermissionsErrorsInternal(
 +              $action, $user, $rigor = 'secure', $short = false
        ) {
 -              wfProfileIn( __METHOD__ );
 +              if ( $rigor === true ) {
 +                      $rigor = 'secure'; // b/c
 +              } elseif ( $rigor === false ) {
 +                      $rigor = 'quick'; // b/c
 +              } elseif ( !in_array( $rigor, array( 'quick', 'full', 'secure' ) ) ) {
 +                      throw new Exception( "Invalid rigor parameter '$rigor'." );
 +              }
  
                # Read has special handling
                if ( $action == 'read' ) {
                while ( count( $checks ) > 0 &&
                                !( $short && count( $errors ) > 0 ) ) {
                        $method = array_shift( $checks );
 -                      $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
 +                      $errors = $this->$method( $action, $user, $errors, $rigor, $short );
                }
  
 -              wfProfileOut( __METHOD__ );
                return $errors;
        }
  
                        return array( $this->mHasCascadingRestrictions, $pagerestrictions );
                }
  
 -              wfProfileIn( __METHOD__ );
 -
                $dbr = wfGetDB( DB_SLAVE );
  
                if ( $this->getNamespace() == NS_FILE ) {
  
                $sources = $getPages ? array() : false;
                $now = wfTimestampNow();
 -              $purgeExpired = false;
  
                foreach ( $res as $row ) {
                        $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
                                } else {
                                        $sources = true;
                                }
 -                      } else {
 -                              // Trigger lazy purge of expired restrictions from the db
 -                              $purgeExpired = true;
                        }
                }
 -              if ( $purgeExpired ) {
 -                      Title::purgeExpiredRestrictions();
 -              }
  
                if ( $getPages ) {
                        $this->mCascadeSources = $sources;
                        $this->mHasCascadingRestrictions = $sources;
                }
  
 -              wfProfileOut( __METHOD__ );
                return array( $sources, $pagerestrictions );
        }
  
                if ( count( $rows ) ) {
                        # Current system - load second to make them override.
                        $now = wfTimestampNow();
 -                      $purgeExpired = false;
  
                        # Cycle through all the restrictions.
                        foreach ( $rows as $row ) {
                                        $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
  
                                        $this->mCascadeRestriction |= $row->pr_cascade;
 -                              } else {
 -                                      // Trigger a lazy purge of expired restrictions
 -                                      $purgeExpired = true;
                                }
                        }
 -
 -                      if ( $purgeExpired ) {
 -                              Title::purgeExpiredRestrictions();
 -                      }
                }
  
                $this->mRestrictionsLoaded = true;
                                                $this->mRestrictionsExpiry['create'] = $expiry;
                                                $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
                                        } else { // Get rid of the old restrictions
 -                                              Title::purgeExpiredRestrictions();
                                                $this->mTitleProtection = false;
                                        }
                                } else {
                if ( !is_null( $this->mRedirect ) ) {
                        return $this->mRedirect;
                }
 -              # Calling getArticleID() loads the field from cache as needed
                if ( !$this->getArticleID( $flags ) ) {
                        $this->mRedirect = false;
                        return $this->mRedirect;
                }
  
                $linkCache = LinkCache::singleton();
 +              $linkCache->addLinkObj( $this ); # in case we already had an article ID
                $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
                if ( $cached === null ) {
                        # Trust LinkCache's state over our own
                if ( $this->mLength != -1 ) {
                        return $this->mLength;
                }
 -              # Calling getArticleID() loads the field from cache as needed
                if ( !$this->getArticleID( $flags ) ) {
                        $this->mLength = 0;
                        return $this->mLength;
                }
                $linkCache = LinkCache::singleton();
 +              $linkCache->addLinkObj( $this ); # in case we already had an article ID
                $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
                if ( $cached === null ) {
                        # Trust LinkCache's state over our own, as for isRedirect()
                if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
                        return intval( $this->mLatestID );
                }
 -              # Calling getArticleID() loads the field from cache as needed
                if ( !$this->getArticleID( $flags ) ) {
                        $this->mLatestID = 0;
                        return $this->mLatestID;
                }
                $linkCache = LinkCache::singleton();
 -              $linkCache->addLinkObj( $this );
 +              $linkCache->addLinkObj( $this ); # in case we already had an article ID
                $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
                if ( $cached === null ) {
                        # Trust LinkCache's state over our own, as for isRedirect()
         *
         * @deprecated since 1.25, use MovePage's methods instead
         * @param Title $nt The new title
 -       * @param bool $auth Ignored
 +       * @param bool $auth Whether to check user permissions (uses $wgUser)
         * @param string $reason Is the log summary of the move, used for spam checking
         * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
         */
                }
  
                $mp = new MovePage( $this, $nt );
 -              $errors = wfMergeErrorArrays(
 -                      $mp->isValidMove()->getErrorsArray(),
 -                      $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
 -              );
 +              $errors = $mp->isValidMove()->getErrorsArray();
 +              if ( $auth ) {
 +                      $errors = wfMergeErrorArrays(
 +                              $errors,
 +                              $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
 +                      );
 +              }
  
                return $errors ? : true;
        }
                $errors = array();
  
                $destFile = wfLocalFile( $nt );
 -              if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
 +              $destFile->load( File::READ_LATEST );
 +              if ( !$wgUser->isAllowed( 'reupload-shared' )
 +                      && !$destFile->exists() && wfFindFile( $nt )
 +              ) {
                        $errors[] = array( 'file-exists-sharedrepo' );
                }
  
                # Is it an existing file?
                if ( $nt->getNamespace() == NS_FILE ) {
                        $file = wfLocalFile( $nt );
 +                      $file->load( File::READ_LATEST );
                        if ( $file->exists() ) {
                                wfDebug( __METHOD__ . ": file exists\n" );
                                return false;
                if ( $this->mIsBigDeletion === null ) {
                        $dbr = wfGetDB( DB_SLAVE );
  
 -                      $innerQuery = $dbr->selectSQLText(
 +                      $revCount = $dbr->selectRowCount(
                                'revision',
                                '1',
                                array( 'rev_page' => $this->getArticleID() ),
                                array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
                        );
  
 -                      $revCount = $dbr->query(
 -                              'SELECT COUNT(*) FROM (' . $innerQuery . ') AS innerQuery',
 -                              __METHOD__
 -                      );
 -                      $revCount = $revCount->fetchRow();
 -                      $revCount = $revCount['COUNT(*)'];
 -
                        $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
                }
  
                        'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
                );
                if ( $max !== null ) {
 -                      $res = $dbr->select( 'revision', '1',
 +                      return $dbr->selectRowCount( 'revision', '1',
                                $conds,
                                __METHOD__,
                                array( 'LIMIT' => $max + 1 ) // extra to detect truncation
                        );
 -                      return $res->numRows();
                } else {
                        return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
                }
                }
                // No DB query needed if $old and $new are the same or successive revisions:
                if ( $old->getId() === $new->getId() ) {
 -                      return ( $old_cmp === '>' && $new_cmp === '<' ) ? array() : array( $old->getRawUserText() );
 +                      return ( $old_cmp === '>' && $new_cmp === '<' ) ?
 +                              array() :
 +                              array( $old->getUserText( Revision::RAW ) );
                } elseif ( $old->getId() === $new->getParentId() ) {
                        if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
 -                              $authors[] = $old->getRawUserText();
 -                              if ( $old->getRawUserText() != $new->getRawUserText() ) {
 -                                      $authors[] = $new->getRawUserText();
 +                              $authors[] = $old->getUserText( Revision::RAW );
 +                              if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
 +                                      $authors[] = $new->getUserText( Revision::RAW );
                                }
                        } elseif ( $old_cmp === '>=' ) {
 -                              $authors[] = $old->getRawUserText();
 +                              $authors[] = $old->getUserText( Revision::RAW );
                        } elseif ( $new_cmp === '<=' ) {
 -                              $authors[] = $new->getRawUserText();
 +                              $authors[] = $new->getUserText( Revision::RAW );
                        }
                        return $authors;
                }
         */
        public function getPageLanguage() {
                global $wgLang, $wgLanguageCode;
 -              wfProfileIn( __METHOD__ );
                if ( $this->isSpecialPage() ) {
                        // special pages are in the user language
 -                      wfProfileOut( __METHOD__ );
                        return $wgLang;
                }
  
                // Checking if DB language is set
                if ( $this->mDbPageLanguage ) {
 -                      wfProfileOut( __METHOD__ );
                        return wfGetLangObj( $this->mDbPageLanguage );
                }
  
                        $langObj = wfGetLangObj( $this->mPageLanguage[0] );
                }
  
 -              wfProfileOut( __METHOD__ );
                return $langObj;
        }
  
        public function getEditNotices( $oldid = 0 ) {
                $notices = array();
  
 -              # Optional notices on a per-namespace and per-page basis
 +              // Optional notice for the entire namespace
                $editnotice_ns = 'editnotice-' . $this->getNamespace();
 -              $editnotice_ns_message = wfMessage( $editnotice_ns );
 -              if ( $editnotice_ns_message->exists() ) {
 -                      $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
 +              $msg = wfMessage( $editnotice_ns );
 +              if ( $msg->exists() ) {
 +                      $html = $msg->parseAsBlock();
 +                      // Edit notices may have complex logic, but output nothing (T91715)
 +                      if ( trim( $html ) !== '' ) {
 +                              $notices[$editnotice_ns] = Html::rawElement(
 +                                      'div',
 +                                      array( 'class' => array(
 +                                              'mw-editnotice',
 +                                              'mw-editnotice-namespace',
 +                                              Sanitizer::escapeClass( "mw-$editnotice_ns" )
 +                                      ) ),
 +                                      $html
 +                              );
 +                      }
                }
 +
                if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
 +                      // Optional notice for page itself and any parent page
                        $parts = explode( '/', $this->getDBkey() );
                        $editnotice_base = $editnotice_ns;
                        while ( count( $parts ) > 0 ) {
                                $editnotice_base .= '-' . array_shift( $parts );
 -                              $editnotice_base_msg = wfMessage( $editnotice_base );
 -                              if ( $editnotice_base_msg->exists() ) {
 -                                      $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
 +                              $msg = wfMessage( $editnotice_base );
 +                              if ( $msg->exists() ) {
 +                                      $html = $msg->parseAsBlock();
 +                                      if ( trim( $html ) !== '' ) {
 +                                              $notices[$editnotice_base] = Html::rawElement(
 +                                                      'div',
 +                                                      array( 'class' => array(
 +                                                              'mw-editnotice',
 +                                                              'mw-editnotice-base',
 +                                                              Sanitizer::escapeClass( "mw-$editnotice_base" )
 +                                                      ) ),
 +                                                      $html
 +                                              );
 +                                      }
                                }
                        }
                } else {
 -                      # Even if there are no subpages in namespace, we still don't want / in MW ns.
 +                      // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
                        $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
 -                      $editnoticeMsg = wfMessage( $editnoticeText );
 -                      if ( $editnoticeMsg->exists() ) {
 -                              $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
 +                      $msg = wfMessage( $editnoticeText );
 +                      if ( $msg->exists() ) {
 +                              $html = $msg->parseAsBlock();
 +                              if ( trim( $html ) !== '' ) {
 +                                      $notices[$editnoticeText] = Html::rawElement(
 +                                              'div',
 +                                              array( 'class' => array(
 +                                                      'mw-editnotice',
 +                                                      'mw-editnotice-page',
 +                                                      Sanitizer::escapeClass( "mw-$editnoticeText" )
 +                                              ) ),
 +                                              $html
 +                                      );
 +                              }
                        }
                }
  
@@@ -38,8 -38,6 +38,6 @@@
   * of memory.
   *
   * Introduced by r47317
-  *
-  * @internal documentation reviewed on 18 Mar 2011 by hashar
   */
  class BacklinkCache {
        /** @var ProcessCacheLRU */
         * @return ResultWrapper
         */
        protected function queryLinks( $table, $startId, $endId, $max, $select = 'all' ) {
 -              wfProfileIn( __METHOD__ );
  
                $fromField = $this->getPrefix( $table ) . '_from';
  
                        }
                }
  
 -              wfProfileOut( __METHOD__ );
 -
                return $res;
        }
  
  
                return array( 'numRows' => $numRows, 'batches' => $batches );
        }
 +
 +      /**
 +       * Get a Title iterator for cascade-protected template/file use backlinks
 +       *
 +       * @return TitleArray
 +       * @since 1.25
 +       */
 +      public function getCascadeProtectedLinks() {
 +              $dbr = $this->getDB();
 +
 +              // @todo: use UNION without breaking tests that use temp tables
 +              $resSets = array();
 +              $resSets[] = $dbr->select(
 +                      array( 'templatelinks', 'page_restrictions', 'page' ),
 +                      array( 'page_namespace', 'page_title', 'page_id' ),
 +                      array(
 +                              'tl_namespace' => $this->title->getNamespace(),
 +                              'tl_title' => $this->title->getDBkey(),
 +                              'tl_from = pr_page',
 +                              'pr_cascade' => 1,
 +                              'page_id = tl_from'
 +                      ),
 +                      __METHOD__,
 +                      array( 'DISTINCT' )
 +              );
 +              if ( $this->title->getNamespace() == NS_FILE ) {
 +                      $resSets[] = $dbr->select(
 +                              array( 'imagelinks', 'page_restrictions', 'page' ),
 +                              array( 'page_namespace', 'page_title', 'page_id' ),
 +                              array(
 +                                      'il_to' => $this->title->getDBkey(),
 +                                      'il_from = pr_page',
 +                                      'pr_cascade' => 1,
 +                                      'page_id = il_from'
 +                              ),
 +                              __METHOD__,
 +                              array( 'DISTINCT' )
 +                      );
 +              }
 +
 +              // Combine and de-duplicate the results
 +              $mergedRes = array();
 +              foreach ( $resSets as $res ) {
 +                      foreach ( $res as $row ) {
 +                              $mergedRes[$row->page_id] = $row;
 +                      }
 +              }
 +
 +              return TitleArray::newFromResult(
 +                      new FakeResultWrapper( array_values( $mergedRes ) ) );
 +      }
  }
@@@ -30,8 -30,6 +30,6 @@@
   * See design.txt for an overview.
   * Note: edit user interface and cache support functions have been
   * moved to separate EditPage and HTMLFileCache classes.
-  *
-  * @internal documentation reviewed 15 Mar 2010
   */
  class Article implements Page {
        /** @var IContextSource The context this Article is executed in */
         * @since 1.21
         */
        protected function getContentObject() {
 -              wfProfileIn( __METHOD__ );
  
                if ( $this->mPage->getID() === 0 ) {
                        # If this is a MediaWiki:x message, then load the messages
                        $content = $this->mContentObject;
                }
  
 -              wfProfileOut( __METHOD__ );
                return $content;
        }
  
                        return $this->mContent;
                }
  
 -              wfProfileIn( __METHOD__ );
 -
                $content = $this->fetchContentObject();
  
                if ( !$content ) {
 -                      wfProfileOut( __METHOD__ );
                        return false;
                }
  
                $this->mContent = ContentHandler::getContentText( $content );
                ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
  
 -              wfProfileOut( __METHOD__ );
 -
                return $this->mContent;
        }
  
                        return $this->mContentObject;
                }
  
 -              wfProfileIn( __METHOD__ );
 -
                $this->mContentLoaded = true;
                $this->mContent = null;
  
                                $this->mRevision = Revision::newFromId( $oldid );
                                if ( !$this->mRevision ) {
                                        wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
 -                                      wfProfileOut( __METHOD__ );
                                        return false;
                                }
                        }
                        if ( !$this->mPage->getLatest() ) {
                                wfDebug( __METHOD__ . " failed to find page data for title " .
                                        $this->getTitle()->getPrefixedText() . "\n" );
 -                              wfProfileOut( __METHOD__ );
                                return false;
                        }
  
                        if ( !$this->mRevision ) {
                                wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " .
                                        $this->mPage->getLatest() . "\n" );
 -                              wfProfileOut( __METHOD__ );
                                return false;
                        }
                }
                // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
                // We should instead work with the Revision object when we need it...
                // Loads if user is allowed
 -              $this->mContentObject = $this->mRevision->getContent(
 +              $content = $this->mRevision->getContent(
                        Revision::FOR_THIS_USER,
                        $this->getContext()->getUser()
                );
 +
 +              if ( !$content ) {
 +                      wfDebug( __METHOD__ . " failed to retrieve content of revision " .
 +                              $this->mRevision->getId() . "\n" );
 +                      return false;
 +              }
 +
 +              $this->mContentObject = $content;
                $this->mRevIdFetched = $this->mRevision->getId();
  
                Hooks::run( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
  
 -              wfProfileOut( __METHOD__ );
 -
                return $this->mContentObject;
        }
  
        public function view() {
                global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects;
  
 -              wfProfileIn( __METHOD__ );
 -
                # Get variables from query string
                # As side effect this will load the revision and update the title
                # in a revision ID is passed in the request, so this should remain
                $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
                if ( count( $permErrors ) ) {
                        wfDebug( __METHOD__ . ": denied on secondary read check\n" );
 -                      wfProfileOut( __METHOD__ );
                        throw new PermissionsError( 'read', $permErrors );
                }
  
                if ( $this->mRedirectUrl ) {
                        $outputPage->redirect( $this->mRedirectUrl );
                        wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
 -                      wfProfileOut( __METHOD__ );
  
                        return;
                }
                if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
                        wfDebug( __METHOD__ . ": showing diff page\n" );
                        $this->showDiffPage();
 -                      wfProfileOut( __METHOD__ );
  
                        return;
                }
                        # Is it client cached?
                        if ( $outputPage->checkLastModified( $timestamp ) ) {
                                wfDebug( __METHOD__ . ": done 304\n" );
 -                              wfProfileOut( __METHOD__ );
  
                                return;
                        # Try file cache
                                # tell wgOut that output is taken care of
                                $outputPage->disable();
                                $this->mPage->doViewUpdates( $user, $oldid );
 -                              wfProfileOut( __METHOD__ );
  
                                return;
                        }
                $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
                wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
                if ( $user->getStubThreshold() ) {
 -                      wfIncrStats( 'pcache_miss_stub' );
 +                      $this->getContext()->getStats()->increment( 'pcache_miss_stub' );
                }
  
                $this->showRedirectedFromHeader();
                                                wfDebug( __METHOD__ . ": showing missing article\n" );
                                                $this->showMissingArticle();
                                                $this->mPage->doViewUpdates( $user );
 -                                              wfProfileOut( __METHOD__ );
                                                return;
                                        }
  
  
                                                if ( !$this->showDeletedRevisionHeader() ) {
                                                        wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
 -                                                      wfProfileOut( __METHOD__ );
                                                        return;
                                                }
                                        }
                                                        $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
                                                }
                                                # Connection or timeout error
 -                                              wfProfileOut( __METHOD__ );
                                                return;
                                        }
  
                                        $this->mParserOutput = $poolArticleView->getParserOutput();
                                        $outputPage->addParserOutput( $this->mParserOutput );
                                        if ( $content->getRedirectTarget() ) {
 -                                              $outputPage->addSubtitle(
 -                                                      "<span id=\"redirectsub\">" . wfMessage( 'redirectpagesub' )->parse() . "</span>"
 -                                              );
 +                                              $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
 +                                                      $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
                                        }
  
                                        # Don't cache a dirty ParserOutput object
                }
  
                # Get the ParserOutput actually *displayed* here.
 -              # Note that $this->mParserOutput is the *current* version output.
 +              # Note that $this->mParserOutput is the *current*/oldid version output.
                $pOutput = ( $outputDone instanceof ParserOutput )
                        ? $outputDone // object fetched by hook
                        : $this->mParserOutput;
  
                $outputPage->addModules( 'mediawiki.action.view.postEdit' );
  
 -              wfProfileOut( __METHOD__ );
        }
  
        /**
         * Show a diff page according to current request variables. For use within
         * Article::view() only, other callers should use the DifferenceEngine class.
         *
 -       * @todo Make protected
         */
 -      public function showDiffPage() {
 +      protected function showDiffPage() {
                $request = $this->getContext()->getRequest();
                $user = $this->getContext()->getUser();
                $diff = $request->getVal( 'diff' );
  
                if ( $showCacheHint ) {
                        $dir = $this->getContext()->getLanguage()->getDir();
 -                      $lang = $this->getContext()->getLanguage()->getCode();
 +                      $lang = $this->getContext()->getLanguage()->getHtmlCode();
  
                        $outputPage->wrapWikiMsg(
                                "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
         */
        public function showRedirectedFromHeader() {
                global $wgRedirectSources;
 -              $outputPage = $this->getContext()->getOutput();
  
 -              $request = $this->getContext()->getRequest();
 +              $context = $this->getContext();
 +              $outputPage = $context->getOutput();
 +              $request = $context->getRequest();
                $rdfrom = $request->getVal( 'rdfrom' );
  
                // Construct a URL for the current page view, but with the target title
                                );
  
                                $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
 -                                      wfMessage( 'redirectedfrom' )->rawParams( $redir )->parse()
 +                                      $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
                                . "</span>" );
  
                                // Add the script to update the displayed URL and
                        if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
                                $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
                                $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
 -                                      wfMessage( 'redirectedfrom' )->rawParams( $redir )->parse()
 +                                      $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
                                . "</span>" );
  
                                // Add the script to update the displayed URL
                        return false;
                }
  
 -              wfProfileIn( __METHOD__ );
 -
                // New page patrol: Get the timestamp of the oldest revison which
                // the revision table holds for the given page. Then we look
                // whether it's within the RC lifespan and if it is, we try
  
                // Check for cached results
                if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
 -                      wfProfileOut( __METHOD__ );
                        return false;
                }
  
                ) {
                        // The current revision is already older than what could be in the RC table
                        // 6h tolerance because the RC might not be cleaned out regularly
 -                      wfProfileOut( __METHOD__ );
                        return false;
                }
  
                        // Don't cache in case we can patrol as this could change
                        $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
  
 -                      wfProfileOut( __METHOD__ );
                        return false;
                }
  
 -              if ( $rc->getPerformer()->getName() == $user->getName() ) {
 +              if ( $rc->getPerformer()->equals( $user ) ) {
                        // Don't show a patrol link for own creations. If the user could
                        // patrol them, they already would be patrolled
 -                      wfProfileOut( __METHOD__ );
                        return false;
                }
  
                        '</div>'
                );
  
 -              wfProfileOut( __METHOD__ );
                return true;
        }
  
                        return;
                }
  
 -              $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1;
 +              $context = $this->getContext();
 +              $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
  
                # Cascade unhide param in links for easy deletion browsing
                $extraParams = array();
                $timestamp = $revision->getTimestamp();
  
                $current = ( $oldid == $this->mPage->getLatest() );
 -              $language = $this->getContext()->getLanguage();
 -              $user = $this->getContext()->getUser();
 +              $language = $context->getLanguage();
 +              $user = $context->getUser();
  
                $td = $language->userTimeAndDate( $timestamp, $user );
                $tddate = $language->userDate( $timestamp, $user );
                # Show user links if allowed to see them. If hidden, then show them only if requested...
                $userlinks = Linker::revUserTools( $revision, !$unhide );
  
 -              $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
 +              $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
                        ? 'revision-info-current'
                        : 'revision-info';
  
 -              $outputPage = $this->getContext()->getOutput();
 +              $outputPage = $context->getOutput();
                $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" .
 -                      wfMessage( $infomsg, $td )
 +                      $context->msg( $infomsg, $td )
                                ->rawParams( $userlinks )
                                ->params( $revision->getID(), $tddate, $tdtime, $revision->getUserText() )
                                ->rawParams( Linker::revComment( $revision, true, true ) )
                );
  
                $lnk = $current
 -                      ? wfMessage( 'currentrevisionlink' )->escaped()
 +                      ? $context->msg( 'currentrevisionlink' )->escaped()
                        : Linker::linkKnown(
                                $this->getTitle(),
 -                              wfMessage( 'currentrevisionlink' )->escaped(),
 +                              $context->msg( 'currentrevisionlink' )->escaped(),
                                array(),
                                $extraParams
                        );
                $curdiff = $current
 -                      ? wfMessage( 'diff' )->escaped()
 +                      ? $context->msg( 'diff' )->escaped()
                        : Linker::linkKnown(
                                $this->getTitle(),
 -                              wfMessage( 'diff' )->escaped(),
 +                              $context->msg( 'diff' )->escaped(),
                                array(),
                                array(
                                        'diff' => 'cur',
                $prevlink = $prev
                        ? Linker::linkKnown(
                                $this->getTitle(),
 -                              wfMessage( 'previousrevision' )->escaped(),
 +                              $context->msg( 'previousrevision' )->escaped(),
                                array(),
                                array(
                                        'direction' => 'prev',
                                        'oldid' => $oldid
                                ) + $extraParams
                        )
 -                      : wfMessage( 'previousrevision' )->escaped();
 +                      : $context->msg( 'previousrevision' )->escaped();
                $prevdiff = $prev
                        ? Linker::linkKnown(
                                $this->getTitle(),
 -                              wfMessage( 'diff' )->escaped(),
 +                              $context->msg( 'diff' )->escaped(),
                                array(),
                                array(
                                        'diff' => 'prev',
                                        'oldid' => $oldid
                                ) + $extraParams
                        )
 -                      : wfMessage( 'diff' )->escaped();
 +                      : $context->msg( 'diff' )->escaped();
                $nextlink = $current
 -                      ? wfMessage( 'nextrevision' )->escaped()
 +                      ? $context->msg( 'nextrevision' )->escaped()
                        : Linker::linkKnown(
                                $this->getTitle(),
 -                              wfMessage( 'nextrevision' )->escaped(),
 +                              $context->msg( 'nextrevision' )->escaped(),
                                array(),
                                array(
                                        'direction' => 'next',
                                ) + $extraParams
                        );
                $nextdiff = $current
 -                      ? wfMessage( 'diff' )->escaped()
 +                      ? $context->msg( 'diff' )->escaped()
                        : Linker::linkKnown(
                                $this->getTitle(),
 -                              wfMessage( 'diff' )->escaped(),
 +                              $context->msg( 'diff' )->escaped(),
                                array(),
                                array(
                                        'diff' => 'next',
                }
  
                $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
 -                      wfMessage( 'revision-nav' )->rawParams(
 +                      $context->msg( 'revision-nav' )->rawParams(
                                $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
                        )->escaped() . "</div>" );
        }
                $lang = $this->getTitle()->getPageLanguage();
                $out = $this->getContext()->getOutput();
                if ( $appendSubtitle ) {
 -                      $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
 +                      $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
                }
                $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
                return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
                                ( $forceKnown ? array( 'known', 'noclasses' ) : array() )
                        ) . '</li>';
                }
 +              $html .= '</ul>';
  
 -              $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->text();
 +              $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
  
                return '<div class="redirectMsg">' .
                        '<p>' . $redirectToText . '</p>' .
                if ( !$reason ) {
                        try {
                                $reason = $this->generateReason( $hasHistory );
 -                      } catch ( MWException $e ) {
 +                      } catch ( Exception $e ) {
                                # if a page is horribly broken, we still want to be able to
                                # delete it. So be lenient about errors here.
                                wfDebug( "Error while building auto delete summary: $e" );
                                $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
                                $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
                                        $context->msg( 'history' )->escaped(),
 -                                      array( 'rel' => 'archives' ),
 +                                      array(),
                                        array( 'action' => 'history' ) ) .
                                '</strong>'
                        );
@@@ -31,8 -31,6 +31,6 @@@ interface Page 
   *
   * Some fields are public only for backwards-compatibility. Use accessors.
   * In the past, this class was part of Article.php and everything was public.
-  *
-  * @internal documentation reviewed 15 Mar 2010
   */
  class WikiPage implements Page, IDBAccessObject {
        // Constants for $mDataLoadedFrom and related
         * @return Revision|null
         */
        public function getOldestRevision() {
 -              wfProfileIn( __METHOD__ );
  
                // Try using the slave database first, then try the master
                $continue = 2;
                        }
                }
  
 -              wfProfileOut( __METHOD__ );
                return $row ? Revision::newFromRow( $row ) : null;
        }
  
         * @return array Array of authors, duplicates not removed
         */
        public function getLastNAuthors( $num, $revLatest = 0 ) {
 -              wfProfileIn( __METHOD__ );
                // First try the slave
                // If that doesn't have the latest revision, try the master
                $continue = 2;
                        );
  
                        if ( !$res ) {
 -                              wfProfileOut( __METHOD__ );
                                return array();
                        }
  
                        $authors[] = $row->rev_user_text;
                }
  
 -              wfProfileOut( __METHOD__ );
                return $authors;
        }
  
         * @return ParserOutput|bool ParserOutput or false if the revision was not found
         */
        public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
 -              wfProfileIn( __METHOD__ );
  
                $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
                wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
                if ( $useParserCache ) {
                        $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
                        if ( $parserOutput !== false ) {
 -                              wfProfileOut( __METHOD__ );
                                return $parserOutput;
                        }
                }
                $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
                $pool->execute();
  
 -              wfProfileOut( __METHOD__ );
 -
                return $pool->getParserOutput();
        }
  
         * @return int The newly created page_id key, or false if the title already existed
         */
        public function insertOn( $dbw ) {
 -              wfProfileIn( __METHOD__ );
  
                $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
                $dbw->insert( 'page', array(
                        $this->mId = $newid;
                        $this->mTitle->resetArticleID( $newid );
                }
 -              wfProfileOut( __METHOD__ );
  
                return $affected ? $newid : false;
        }
        ) {
                global $wgContentHandlerUseDB;
  
 -              wfProfileIn( __METHOD__ );
 -
                $content = $revision->getContent();
                $len = $content ? $content->getSize() : 0;
                $rt = $content ? $content->getUltimateRedirectTarget() : null;
                                                                                                        $this->mLatest, $revision->getContentModel() );
                }
  
 -              wfProfileOut( __METHOD__ );
                return $result;
        }
  
                        return true;
                }
  
 -              wfProfileIn( __METHOD__ );
                if ( $isRedirect ) {
                        $this->insertRedirectEntry( $redirectTitle );
                } else {
                if ( $this->getTitle()->getNamespace() == NS_FILE ) {
                        RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
                }
 -              wfProfileOut( __METHOD__ );
  
                return ( $dbw->affectedRows() != 0 );
        }
         * @return bool
         */
        public function updateIfNewerOn( $dbw, $revision ) {
 -              wfProfileIn( __METHOD__ );
  
                $row = $dbw->selectRow(
                        array( 'revision', 'page' ),
  
                if ( $row ) {
                        if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
 -                              wfProfileOut( __METHOD__ );
                                return false;
                        }
                        $prev = $row->rev_id;
  
                $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
  
 -              wfProfileOut( __METHOD__ );
                return $ret;
        }
  
         */
        public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
                $edittime = null ) {
 -              wfProfileIn( __METHOD__ );
  
                $baseRevId = null;
                if ( $edittime && $sectionId !== 'new' ) {
                        }
                }
  
 -              wfProfileOut( __METHOD__ );
                return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
        }
  
        public function replaceSectionAtRev( $sectionId, Content $sectionContent,
                $sectionTitle = '', $baseRevId = null
        ) {
 -              wfProfileIn( __METHOD__ );
  
                if ( strval( $sectionId ) === '' ) {
                        // Whole-page edit; let the whole text through
                        $newContent = $sectionContent;
                } else {
                        if ( !$this->supportsSections() ) {
 -                              wfProfileOut( __METHOD__ );
                                throw new MWException( "sections not supported for content model " .
                                        $this->getContentHandler()->getModelID() );
                        }
                                if ( !$rev ) {
                                        wfDebug( __METHOD__ . " asked for bogus section (page: " .
                                                $this->getId() . "; section: $sectionId)\n" );
 -                                      wfProfileOut( __METHOD__ );
                                        return null;
                                }
  
  
                        if ( !$oldContent ) {
                                wfDebug( __METHOD__ . ": no page text\n" );
 -                              wfProfileOut( __METHOD__ );
                                return null;
                        }
  
                        $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
                }
  
 -              wfProfileOut( __METHOD__ );
                return $newContent;
        }
  
         * error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
         *
 -       * @param bool|int $baseRevId The revision ID this edit was based off, if any
 +       * @param bool|int $baseRevId The revision ID this edit was based off, if any.
 +       *   This is not the parent revision ID, rather the revision ID for older
 +       *   content used as the source for a rollback, for example.
         * @param User $user The user doing the edit
         *
         * @throws MWException
         * error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
         *
 -       * @param bool|int $baseRevId The revision ID this edit was based off, if any
 +       * @param bool|int $baseRevId The revision ID this edit was based off, if any.
 +       *   This is not the parent revision ID, rather the revision ID for older
 +       *   content used as the source for a rollback, for example.
         * @param User $user The user doing the edit
         * @param string $serialFormat Format for storing the content in the
         *   database.
                        throw new MWException( 'Something is trying to edit an article with an empty title' );
                }
  
 -              wfProfileIn( __METHOD__ );
 -
                if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
 -                      wfProfileOut( __METHOD__ );
                        return Status::newFatal( 'content-not-allowed-here',
                                ContentHandler::getLocalizedName( $content->getModel() ),
                                $this->getTitle()->getPrefixedText() );
                                $status->fatal( 'edit-hook-aborted' );
                        }
  
 -                      wfProfileOut( __METHOD__ );
                        return $status;
                }
  
                                wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
                                $status->fatal( 'edit-gone-missing' );
  
 -                              wfProfileOut( __METHOD__ );
                                return $status;
                        } elseif ( !$old_content ) {
                                // Sanity check for bug 37225
 -                              wfProfileOut( __METHOD__ );
                                throw new MWException( "Could not find text for current revision {$oldid}." );
                        }
  
                                $dbw->begin( __METHOD__ );
                                try {
  
 -                                      $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
 +                                      $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
                                        $status->merge( $prepStatus );
  
                                        if ( !$status->isOK() ) {
                                                $dbw->rollback( __METHOD__ );
  
 -                                              wfProfileOut( __METHOD__ );
                                                return $status;
                                        }
                                        $revisionId = $revision->insertOn( $dbw );
  
                                                $dbw->rollback( __METHOD__ );
  
 -                                              wfProfileOut( __METHOD__ );
                                                return $status;
                                        }
  
                                                }
                                        }
                                        $user->incEditCount();
 -                              } catch ( MWException $e ) {
 +                              } catch ( Exception $e ) {
                                        $dbw->rollback( __METHOD__ );
                                        // Question: Would it perhaps be better if this method turned all
                                        // exceptions into $status's?
                        $dbw->begin( __METHOD__ );
                        try {
  
 -                              $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
 +                              $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
                                $status->merge( $prepStatus );
  
                                if ( !$status->isOK() ) {
                                        $dbw->rollback( __METHOD__ );
  
 -                                      wfProfileOut( __METHOD__ );
                                        return $status;
                                }
  
                                        $dbw->rollback( __METHOD__ );
                                        $status->fatal( 'edit-already-exists' );
  
 -                                      wfProfileOut( __METHOD__ );
                                        return $status;
                                }
  
                                }
                                $user->incEditCount();
  
 -                      } catch ( MWException $e ) {
 +                      } catch ( Exception $e ) {
                                $dbw->rollback( __METHOD__ );
                                throw $e;
                        }
                        $user->addAutopromoteOnceGroups( 'onEdit' );
                } );
  
 -              wfProfileOut( __METHOD__ );
                return $status;
        }
  
         * 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;
 +              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???
                }
  
                // The edit may have already been prepared via api.php?action=stashedit
 -              $cachedEdit = $useCache
 +              $cachedEdit = $useCache && $wgAjaxEditStash
                        ? ApiStashEdit::checkCache( $this->getTitle(), $content, $user )
                        : false;
  
                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;
         * - changed: boolean, whether the revision changed the content (default true)
         * - created: boolean, whether the revision created the page (default false)
         * - moved: boolean, whether the page was moved (default false)
 -       * - oldcountable: boolean or null (default null):
 +       * - oldcountable: boolean, null, or string 'no-change' (default null):
         *   - boolean: whether the page was counted as an article before that
         *     revision, only used in changed is true and created is false
 -       *   - null: don't change the article count
 +       *   - null: if created is false, don't update the article count; if created
 +       *     is true, do update the article count
 +       *   - 'no-change': don't update the article count, ever
         */
        public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
                global $wgEnableParserCache;
  
 -              wfProfileIn( __METHOD__ );
 -
                $options += array(
                        'changed' => true,
                        'created' => false,
                // 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() ) {
 -                      wfProfileOut( __METHOD__ );
                        return;
                }
  
                $title = $this->mTitle->getPrefixedDBkey();
                $shortTitle = $this->mTitle->getDBkey();
  
 -              if ( !$options['changed'] && !$options['moved'] ) {
 +              if ( $options['oldcountable'] === 'no-change' ||
 +                      ( !$options['changed'] && !$options['moved'] )
 +              ) {
                        $good = 0;
                } elseif ( $options['created'] ) {
                        $good = (int)$this->isCountable( $editInfo );
                        self::onArticleEdit( $this->mTitle );
                }
  
 -              wfProfileOut( __METHOD__ );
        }
  
        /**
        public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
                $serialFormat = null
        ) {
 -              wfProfileIn( __METHOD__ );
  
                $serialized = $content->serialize( $serialFormat );
  
  
                Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
  
 -              wfProfileOut( __METHOD__ );
        }
  
        /**
  
                // 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(),
                }
  
                // Generate the edit summary if necessary
 -              $target = Revision::newFromId( $s->rev_id );
 +              $target = Revision::newFromId( $s->rev_id, Revision::READ_LATEST );
                if ( empty( $summary ) ) {
                        if ( $from == '' ) { // no public user name
                                $summary = wfMessage( 'revertpage-nouser' );
         *
         * @param Title $title
         */
 -      public static function onArticleCreate( $title ) {
 +      public static function onArticleCreate( Title $title ) {
                // Update existence markers on article/talk tabs...
 -              if ( $title->isTalkPage() ) {
 -                      $other = $title->getSubjectPage();
 -              } else {
 -                      $other = $title->getTalkPage();
 -              }
 +              $other = $title->getOtherPage();
  
                $other->invalidateCache();
                $other->purgeSquid();
         *
         * @param Title $title
         */
 -      public static function onArticleDelete( $title ) {
 +      public static function onArticleDelete( Title $title ) {
                // Update existence markers on article/talk tabs...
 -              if ( $title->isTalkPage() ) {
 -                      $other = $title->getSubjectPage();
 -              } else {
 -                      $other = $title->getTalkPage();
 -              }
 +              $other = $title->getOtherPage();
  
                $other->invalidateCache();
                $other->purgeSquid();
         * Purge caches on page update etc
         *
         * @param Title $title
 -       * @todo Verify that $title is always a Title object (and never false or
 -       *   null), add Title hint to parameter $title.
         */
 -      public static function onArticleEdit( $title ) {
 +      public static function onArticleEdit( Title $title ) {
                // Invalidate caches of articles which include this page
                DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
  
        }
  
        /**
 -       * Updates cascading protections
 +       * Opportunistically enqueue link update jobs given fresh parser output if useful
         *
 -       * @param ParserOutput $parserOutput ParserOutput object for the current version
 +       * @param ParserOutput $parserOutput Current version page output
 +       * @return bool Whether a job was pushed
 +       * @since 1.25
         */
 -      public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
 -              if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
 -                      return;
 -              }
 -
 -              // templatelinks or imagelinks tables may have become out of sync,
 -              // especially if using variable-based transclusions.
 -              // For paranoia, check if things have changed and if
 -              // so apply updates to the database. This will ensure
 -              // that cascaded protections apply as soon as the changes
 -              // are visible.
 -
 -              // Get templates from templatelinks and images from imagelinks
 -              $id = $this->getId();
 -
 -              $dbLinks = array();
 -
 -              $dbr = wfGetDB( DB_SLAVE );
 -              $res = $dbr->select( array( 'templatelinks' ),
 -                      array( 'tl_namespace', 'tl_title' ),
 -                      array( 'tl_from' => $id ),
 -                      __METHOD__
 -              );
 -
 -              foreach ( $res as $row ) {
 -                      $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
 +      public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
 +              if ( wfReadOnly() ) {
 +                      return false;
                }
  
 -              $dbr = wfGetDB( DB_SLAVE );
 -              $res = $dbr->select( array( 'imagelinks' ),
 -                      array( 'il_to' ),
 -                      array( 'il_from' => $id ),
 -                      __METHOD__
 -              );
 -
 -              foreach ( $res as $row ) {
 -                      $dbLinks[NS_FILE . ":{$row->il_to}"] = true;
 +              if ( $this->mTitle->areRestrictionsCascading() ) {
 +                      // If the page is cascade protecting, the links should really be up-to-date
 +                      $params = array( 'prioritize' => true );
 +              } elseif ( $parserOutput->hasDynamicContent() ) {
 +                      // Assume the output contains time/random based magic words
 +                      $params = array();
 +              } else {
 +                      // If the inclusions are deterministic, the edit-triggered link jobs are enough
 +                      return false;
                }
  
 -              // Get templates and images from parser output.
 -              $poLinks = array();
 -              foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
 -                      foreach ( $templates as $dbk => $id ) {
 -                              $poLinks["$ns:$dbk"] = true;
 -                      }
 -              }
 -              foreach ( $parserOutput->getImages() as $dbk => $id ) {
 -                      $poLinks[NS_FILE . ":$dbk"] = true;
 +              // Check if the last link refresh was before page_touched
 +              if ( $this->getLinksTimestamp() < $this->getTouched() ) {
 +                      JobQueueGroup::singleton()->push( EnqueueJob::newFromLocalJobs(
 +                              new JobSpecification( 'refreshLinks', $params, array(), $this->mTitle )
 +                      ) );
 +                      return true;
                }
  
 -              // Get the diff
 -              $links_diff = array_diff_key( $poLinks, $dbLinks );
 -
 -              if ( count( $links_diff ) > 0 ) {
 -                      // Whee, link updates time.
 -                      // Note: we are only interested in links here. We don't need to get
 -                      // other DataUpdate items from the parser output.
 -                      $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
 -                      $u->doUpdate();
 -              }
 +              return false;
        }
  
        /**