Merge "Surround edit notices with appropriate classes"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 11 Jan 2015 13:25:49 +0000 (13:25 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 11 Jan 2015 13:25:49 +0000 (13:25 +0000)
1  2 
includes/Title.php

diff --combined includes/Title.php
@@@ -158,9 -158,6 +158,9 @@@ class Title 
  
        /** @var TitleValue A corresponding TitleValue object */
        private $mTitleValue = null;
 +
 +      /** @var bool Would deleting this page be a big deletion? */
 +      private $mIsBigDeletion = null;
        // @}
  
        /**
                }
  
                $t = new Title();
 -              $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
 +              $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
                if ( $t->secureAndSplit() ) {
                        return $t;
                } else {
         * 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();
        }
  
        /**
         * @param string $title The DB key form the title
         * @param string $fragment The link fragment (after the "#")
         * @param string $interwiki The interwiki prefix
 +       * @param bool $canoncialNamespace If true, use the canonical name for
 +       *   $ns instead of the localized version.
         * @return string The prefixed form of the title
         */
 -      public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
 +      public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
 +              $canoncialNamespace = false
 +      ) {
                global $wgContLang;
  
 -              $namespace = $wgContLang->getNsText( $ns );
 +              if ( $canoncialNamespace ) {
 +                      $namespace = MWNamespace::getCanonicalName( $ns );
 +              } else {
 +                      $namespace = $wgContLang->getNsText( $ns );
 +              }
                $name = $namespace == '' ? $title : "$namespace:$title";
                if ( strval( $interwiki ) != '' ) {
                        $name = "$interwiki:$name";
  
        /**
         * Determine whether the object refers to a page within
 -       * this project.
 +       * this project (either this wiki or a wiki with a local
 +       * interwiki, see https://www.mediawiki.org/wiki/Manual:Interwiki_table#iw_local )
         *
         * @return bool True if this is an in-project interwiki link or a wikilink, false otherwise
         */
         * Get the page's content model id, see the CONTENT_MODEL_XXX constants.
         *
         * @throws MWException
 +       * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
         * @return string Content model id
         */
 -      public function getContentModel() {
 -              if ( !$this->mContentModel ) {
 +      public function getContentModel( $flags = 0 ) {
 +              # Calling getArticleID() loads the field from cache as needed
 +              if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
                        $linkCache = LinkCache::singleton();
                        $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
                }
         * Is this in a namespace that allows actual pages?
         *
         * @return bool
 -       * @internal note -- uses hardcoded namespace index instead of constants
         */
        public function canExist() {
                return $this->mNamespace >= NS_MAIN;
                }
  
                $result = true;
 -              wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
 +              Hooks::run( 'TitleIsMovable', array( $this, &$result ) );
                return $result;
        }
  
  
                # @note This hook is also called in ContentHandler::getDefaultModel.
                #   It's called here again to make sure hook functions can force this
 -              #   method to return true even outside the mediawiki namespace.
 +              #   method to return true even outside the MediaWiki namespace.
  
 -              wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
 +              Hooks::run( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ), '1.25' );
  
                return $isCssOrJsPage;
        }
                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
         *
                # Finally, add the fragment.
                $url .= $this->getFragmentForURL();
  
 -              wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
 +              Hooks::run( 'GetFullURL', array( &$this, &$url, $query ) );
                return $url;
        }
  
                        $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
                        if ( $query == '' ) {
                                $url = str_replace( '$1', $dbkey, $wgArticlePath );
 -                              wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
 +                              Hooks::run( 'GetLocalURL::Article', array( &$this, &$url ) );
                        } else {
                                global $wgVariantArticlePath, $wgActionPaths, $wgContLang;
                                $url = false;
                                }
                        }
  
 -                      wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
 +                      Hooks::run( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
  
                        // @todo FIXME: This causes breakage in various places when we
                        // actually expected a local URL and end up with dupe prefixes.
                                $url = $wgServer . $url;
                        }
                }
 -              wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
 +              Hooks::run( 'GetLocalURL', array( &$this, &$url, $query ) );
                return $url;
        }
  
         * @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;
        }
  
                $query = self::fixUrlQueryArgs( $query, $query2 );
                $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
                $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
 -              wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
 +              Hooks::run( 'GetInternalURL', array( &$this, &$url, $query ) );
                return $url;
        }
  
        public function getCanonicalURL( $query = '', $query2 = false ) {
                $query = self::fixUrlQueryArgs( $query, $query2 );
                $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
 -              wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
 +              Hooks::run( 'GetCanonicalURL', array( &$this, &$url, $query ) );
                return $url;
        }
  
        private function checkQuickPermissions( $action, $user, $errors,
                $doExpensiveQueries, $short
        ) {
 -              if ( !wfRunHooks( 'TitleQuickPermissions',
 +              if ( !Hooks::run( 'TitleQuickPermissions',
                        array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) )
                ) {
                        return $errors;
        private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
                // Use getUserPermissionsErrors instead
                $result = '';
 -              if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
 +              if ( !Hooks::run( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
                        return $result ? array() : array( array( 'badaccess-group0' ) );
                }
                // Check getUserPermissionsErrors hook
 -              if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
 +              if ( !Hooks::run( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
                        $errors = $this->resultToError( $errors, $result );
                }
                // Check getUserPermissionsErrorsExpensive hook
                if (
                        $doExpensiveQueries
                        && !( $short && count( $errors ) > 0 )
 -                      && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
 +                      && !Hooks::run( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
                ) {
                        $errors = $this->resultToError( $errors, $result );
                }
                        $ns = $this->mNamespace == NS_MAIN ?
                                wfMessage( 'nstab-main' )->text() : $this->getNsText();
                        $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
 -                              array( 'protectedinterface' ) : array( 'namespaceprotected', $ns );
 +                              array( 'protectedinterface', $action ) : array( 'namespaceprotected', $ns, $action );
                }
  
                return $errors;
                if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
                        if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
                                if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
 -                                      $errors[] = array( 'mycustomcssprotected' );
 +                                      $errors[] = array( 'mycustomcssprotected', $action );
                                } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
 -                                      $errors[] = array( 'mycustomjsprotected' );
 +                                      $errors[] = array( 'mycustomjsprotected', $action );
                                }
                        } else {
                                if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
 -                                      $errors[] = array( 'customcssprotected' );
 +                                      $errors[] = array( 'customcssprotected', $action );
                                } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
 -                                      $errors[] = array( 'customjsprotected' );
 +                                      $errors[] = array( 'customjsprotected', $action );
                                }
                        }
                }
                                continue;
                        }
                        if ( !$user->isAllowed( $right ) ) {
 -                              $errors[] = array( 'protectedpagetext', $right );
 +                              $errors[] = array( 'protectedpagetext', $right, $action );
                        } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
 -                              $errors[] = array( 'protectedpagetext', 'protect' );
 +                              $errors[] = array( 'protectedpagetext', 'protect', $action );
                        }
                }
  
                                                foreach ( $cascadingSources as $page ) {
                                                        $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
                                                }
 -                                              $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
 +                                              $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages, $action );
                                        }
                                }
                        }
                } elseif ( $action == 'create' ) {
                        $title_protection = $this->getTitleProtection();
                        if ( $title_protection ) {
 -                              if ( $title_protection['pt_create_perm'] == 'sysop' ) {
 -                                      $title_protection['pt_create_perm'] = 'editprotected'; // B/C
 -                              }
 -                              if ( $title_protection['pt_create_perm'] == 'autoconfirmed' ) {
 -                                      $title_protection['pt_create_perm'] = 'editsemiprotected'; // B/C
 -                              }
 -                              if ( $title_protection['pt_create_perm'] == ''
 -                                      || !$user->isAllowed( $title_protection['pt_create_perm'] )
 +                              if ( $title_protection['permission'] == ''
 +                                      || !$user->isAllowed( $title_protection['permission'] )
                                ) {
                                        $errors[] = array(
                                                'titleprotected',
 -                                              User::whoIs( $title_protection['pt_user'] ),
 -                                              $title_protection['pt_reason']
 +                                              User::whoIs( $title_protection['user'] ),
 +                                              $title_protection['reason']
                                        );
                                }
                        }
                                $errors[] = array( 'immobile-target-page' );
                        }
                } elseif ( $action == 'delete' ) {
 -                      if ( count( $this->getUserPermissionsErrorsInternal( 'edit',
 -                              $user, $doExpensiveQueries, true ) )
 -                      ) {
 -                              // If they can't edit, they shouldn't delete.
 -                              $errors[] = array( 'delete-cantedit' );
 +                      $tempErrors = $this->checkPageRestrictions( 'edit',
 +                              $user, array(), $doExpensiveQueries, true );
 +                      if ( !$tempErrors ) {
 +                              $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
 +                                      $user, $tempErrors, $doExpensiveQueries, true );
 +                      }
 +                      if ( $tempErrors ) {
 +                              // If protection keeps them from editing, they shouldn't be able to delete.
 +                              $errors[] = array( 'deleteprotected' );
                        }
                        if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
                                && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
  
                if ( !$whitelisted ) {
                        # If the title is not whitelisted, give extensions a chance to do so...
 -                      wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
 +                      Hooks::run( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
                        if ( !$whitelisted ) {
                                $errors[] = $this->missingPermissionError( $action, $short );
                        }
        protected function getUserPermissionsErrorsInternal( $action, $user,
                $doExpensiveQueries = true, $short = false
        ) {
 -              wfProfileIn( __METHOD__ );
  
                # Read has special handling
                if ( $action == 'read' ) {
                                'checkPermissionHooks',
                                'checkReadPermissions',
                        );
 +              # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
 +              # here as it will lead to duplicate error messages. This is okay to do
 +              # since anywhere that checks for create will also check for edit, and
 +              # those checks are called for edit.
 +              } elseif ( $action == 'create' ) {
 +                      $checks = array(
 +                              'checkQuickPermissions',
 +                              'checkPermissionHooks',
 +                              'checkPageRestrictions',
 +                              'checkCascadingSourcesRestrictions',
 +                              'checkActionPermissions',
 +                              'checkUserBlock'
 +                      );
                } else {
                        $checks = array(
                                'checkQuickPermissions',
                        $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
                }
  
 -              wfProfileOut( __METHOD__ );
                return $errors;
        }
  
                        $types = array_diff( $types, array( 'upload' ) );
                }
  
 -              wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
 +              Hooks::run( 'TitleGetRestrictionTypes', array( $this, &$types ) );
  
                wfDebug( __METHOD__ . ': applicable restrictions to [[' .
                        $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
         * @return array|bool An associative array representing any existent title
         *   protection, or false if there's none.
         */
 -      private function getTitleProtection() {
 +      public function getTitleProtection() {
                // Can't protect pages in special namespaces
                if ( $this->getNamespace() < 0 ) {
                        return false;
                        $dbr = wfGetDB( DB_SLAVE );
                        $res = $dbr->select(
                                'protected_titles',
 -                              array( 'pt_user', 'pt_reason', 'pt_expiry', 'pt_create_perm' ),
 +                              array(
 +                                      'user' => 'pt_user',
 +                                      'reason' => 'pt_reason',
 +                                      'expiry' => 'pt_expiry',
 +                                      'permission' => 'pt_create_perm'
 +                              ),
                                array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
                                __METHOD__
                        );
  
                        // fetchRow returns false if there are no rows.
 -                      $this->mTitleProtection = $dbr->fetchRow( $res );
 +                      $row = $dbr->fetchRow( $res );
 +                      if ( $row ) {
 +                              if ( $row['permission'] == 'sysop' ) {
 +                                      $row['permission'] = 'editprotected'; // B/C
 +                              }
 +                              if ( $row['permission'] == 'autoconfirmed' ) {
 +                                      $row['permission'] = 'editsemiprotected'; // B/C
 +                              }
 +                      }
 +                      $this->mTitleProtection = $row;
                }
                return $this->mTitleProtection;
        }
                        return array( $this->mHasCascadingRestrictions, $pagerestrictions );
                }
  
 -              wfProfileIn( __METHOD__ );
 -
                $dbr = wfGetDB( DB_SLAVE );
  
                if ( $this->getNamespace() == NS_FILE ) {
                        $this->mHasCascadingRestrictions = $sources;
                }
  
 -              wfProfileOut( __METHOD__ );
                return array( $sources, $pagerestrictions );
        }
  
         * Accessor/initialisation for mRestrictions
         *
         * @param string $action Action that permission needs to be checked for
 -       * @return array Restriction levels needed to take the action. All levels
 -       *     are required.
 +       * @return array Restriction levels needed to take the action. All levels are
 +       *     required. Note that restriction levels are normally user rights, but 'sysop'
 +       *     and 'autoconfirmed' are also allowed for backwards compatibility. These should
 +       *     be mapped to 'editprotected' and 'editsemiprotected' respectively.
         */
        public function getRestrictions( $action ) {
                if ( !$this->mRestrictionsLoaded ) {
  
                                if ( $title_protection ) {
                                        $now = wfTimestampNow();
 -                                      $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
 +                                      $expiry = $wgContLang->formatExpiry( $title_protection['expiry'], TS_MW );
  
                                        if ( !$expiry || $expiry > $now ) {
                                                // Apply the restrictions
                                                $this->mRestrictionsExpiry['create'] = $expiry;
 -                                              $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
 +                                              $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
                                        } else { // Get rid of the old restrictions
                                                Title::purgeExpiredRestrictions();
                                                $this->mTitleProtection = false;
                $this->mEstimateRevisions = null;
                $this->mPageLanguage = false;
                $this->mDbPageLanguage = null;
 +              $this->mIsBigDeletion = null;
        }
  
        /**
                        // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
                        //        the parsing code with Title, while avoiding massive refactoring.
                        // @todo: get rid of secureAndSplit, refactor parsing code.
 -                      $parser = self::getTitleParser();
 -                      $parts = $parser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
 +                      $titleParser = self::getTitleParser();
 +                      $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
                } catch ( MalformedTitleException $ex ) {
                        return false;
                }
                        $urls[] = $this->getInternalUrl( 'action=raw&ctype=text/css' );
                }
  
 -              wfRunHooks( 'TitleSquidURLs', array( $this, &$urls ) );
 +              Hooks::run( 'TitleSquidURLs', array( $this, &$urls ) );
                return $urls;
        }
  
        /**
         * Move this page without authentication
         *
 +       * @deprecated since 1.25 use MovePage class instead
         * @param Title $nt The new page Title
         * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
         */
        public function moveNoAuth( &$nt ) {
 +              wfDeprecated( __METHOD__, '1.25' );
                return $this->moveTo( $nt, false );
        }
  
         * Check whether a given move operation would be valid.
         * Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
         *
 +       * @deprecated since 1.25, use MovePage's methods instead
         * @param Title $nt The new title
 -       * @param bool $auth Indicates whether $wgUser's permissions
 -       *  should be checked
 +       * @param bool $auth Ignored
         * @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
         */
        public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
 -              global $wgUser, $wgContentHandlerUseDB;
 +              global $wgUser;
  
 -              $errors = array();
 -              if ( !$nt ) {
 +              if ( !( $nt instanceof Title ) ) {
                        // Normally we'd add this to $errors, but we'll get
                        // lots of syntax errors if $nt is not an object
                        return array( array( 'badtitletext' ) );
                }
 -              if ( $this->equals( $nt ) ) {
 -                      $errors[] = array( 'selfmove' );
 -              }
 -              if ( !$this->isMovable() ) {
 -                      $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
 -              }
 -              if ( $nt->isExternal() ) {
 -                      $errors[] = array( 'immobile-target-namespace-iw' );
 -              }
 -              if ( !$nt->isMovable() ) {
 -                      $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
 -              }
 -
 -              $oldid = $this->getArticleID();
 -              $newid = $nt->getArticleID();
 -
 -              if ( strlen( $nt->getDBkey() ) < 1 ) {
 -                      $errors[] = array( 'articleexists' );
 -              }
 -              if (
 -                      ( $this->getDBkey() == '' ) ||
 -                      ( !$oldid ) ||
 -                      ( $nt->getDBkey() == '' )
 -              ) {
 -                      $errors[] = array( 'badarticleerror' );
 -              }
 -
 -              // Content model checks
 -              if ( !$wgContentHandlerUseDB &&
 -                              $this->getContentModel() !== $nt->getContentModel() ) {
 -                      // can't move a page if that would change the page's content model
 -                      $errors[] = array(
 -                              'bad-target-model',
 -                              ContentHandler::getLocalizedName( $this->getContentModel() ),
 -                              ContentHandler::getLocalizedName( $nt->getContentModel() )
 -                      );
 -              }
 -
 -              // Image-specific checks
 -              if ( $this->getNamespace() == NS_FILE ) {
 -                      $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
 -              }
 -
 -              if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
 -                      $errors[] = array( 'nonfile-cannot-move-to-file' );
 -              }
 -
 -              if ( $auth ) {
 -                      $errors = wfMergeErrorArrays( $errors,
 -                              $this->getUserPermissionsErrors( 'move', $wgUser ),
 -                              $this->getUserPermissionsErrors( 'edit', $wgUser ),
 -                              $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
 -                              $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
 -              }
  
 -              $match = EditPage::matchSummarySpamRegex( $reason );
 -              if ( $match !== false ) {
 -                      // This is kind of lame, won't display nice
 -                      $errors[] = array( 'spamprotectiontext' );
 -              }
 -
 -              $err = null;
 -              if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
 -                      $errors[] = array( 'hookaborted', $err );
 -              }
 -
 -              # The move is allowed only if (1) the target doesn't exist, or
 -              # (2) the target is a redirect to the source, and has no history
 -              # (so we can undo bad moves right after they're done).
 +              $mp = new MovePage( $this, $nt );
 +              $errors = wfMergeErrorArrays(
 +                      $mp->isValidMove()->getErrorsArray(),
 +                      $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
 +              );
  
 -              if ( 0 != $newid ) { # Target exists; check for validity
 -                      if ( !$this->isValidMoveTarget( $nt ) ) {
 -                              $errors[] = array( 'articleexists' );
 -                      }
 -              } else {
 -                      $tp = $nt->getTitleProtection();
 -                      $right = $tp['pt_create_perm'];
 -                      if ( $right == 'sysop' ) {
 -                              $right = 'editprotected'; // B/C
 -                      }
 -                      if ( $right == 'autoconfirmed' ) {
 -                              $right = 'editsemiprotected'; // B/C
 -                      }
 -                      if ( $tp and !$wgUser->isAllowed( $right ) ) {
 -                              $errors[] = array( 'cantmove-titleprotected' );
 -                      }
 -              }
 -              if ( empty( $errors ) ) {
 -                      return true;
 -              }
 -              return $errors;
 +              return $errors ? : true;
        }
  
        /**
         * Check if the requested move target is a valid file move target
 +       * @todo move this to MovePage
         * @param Title $nt Target title
         * @return array List of errors
         */
  
                $errors = array();
  
 -              // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
 -
 -              $file = wfLocalFile( $this );
 -              if ( $file->exists() ) {
 -                      if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
 -                              $errors[] = array( 'imageinvalidfilename' );
 -                      }
 -                      if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
 -                              $errors[] = array( 'imagetypemismatch' );
 -                      }
 -              }
 -
 -              if ( $nt->getNamespace() != NS_FILE ) {
 -                      $errors[] = array( 'imagenocrossnamespace' );
 -                      // From here we want to do checks on a file object, so if we can't
 -                      // create one, we must return.
 -                      return $errors;
 -              }
 -
 -              // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
 -
                $destFile = wfLocalFile( $nt );
                if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
                        $errors[] = array( 'file-exists-sharedrepo' );
        /**
         * Move a title to a new location
         *
 +       * @deprecated since 1.25, use the MovePage class instead
         * @param Title $nt The new title
         * @param bool $auth Indicates whether $wgUser's permissions
         *  should be checked
                        $createRedirect = true;
                }
  
 -              wfRunHooks( 'TitleMove', array( $this, $nt, $wgUser ) );
 -
 -              // If it is a file, move it first.
 -              // It is done before all other moving stuff is done because it's hard to revert.
 -              $dbw = wfGetDB( DB_MASTER );
 -              if ( $this->getNamespace() == NS_FILE ) {
 -                      $file = wfLocalFile( $this );
 -                      if ( $file->exists() ) {
 -                              $status = $file->move( $nt );
 -                              if ( !$status->isOk() ) {
 -                                      return $status->getErrorsArray();
 -                              }
 -                      }
 -                      // Clear RepoGroup process cache
 -                      RepoGroup::singleton()->clearCache( $this );
 -                      RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
 -              }
 -
 -              $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
 -              $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
 -              $protected = $this->isProtected();
 -
 -              // Do the actual move
 -              $this->moveToInternal( $nt, $reason, $createRedirect );
 -
 -              // Refresh the sortkey for this row.  Be careful to avoid resetting
 -              // cl_timestamp, which may disturb time-based lists on some sites.
 -              $prefixes = $dbw->select(
 -                      'categorylinks',
 -                      array( 'cl_sortkey_prefix', 'cl_to' ),
 -                      array( 'cl_from' => $pageid ),
 -                      __METHOD__
 -              );
 -              foreach ( $prefixes as $prefixRow ) {
 -                      $prefix = $prefixRow->cl_sortkey_prefix;
 -                      $catTo = $prefixRow->cl_to;
 -                      $dbw->update( 'categorylinks',
 -                              array(
 -                                      'cl_sortkey' => Collation::singleton()->getSortKey(
 -                                              $nt->getCategorySortkey( $prefix ) ),
 -                                      'cl_timestamp=cl_timestamp' ),
 -                              array(
 -                                      'cl_from' => $pageid,
 -                                      'cl_to' => $catTo ),
 -                              __METHOD__
 -                      );
 -              }
 -
 -              $redirid = $this->getArticleID();
 -
 -              if ( $protected ) {
 -                      # Protect the redirect title as the title used to be...
 -                      $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
 -                              array(
 -                                      'pr_page' => $redirid,
 -                                      'pr_type' => 'pr_type',
 -                                      'pr_level' => 'pr_level',
 -                                      'pr_cascade' => 'pr_cascade',
 -                                      'pr_user' => 'pr_user',
 -                                      'pr_expiry' => 'pr_expiry'
 -                              ),
 -                              array( 'pr_page' => $pageid ),
 -                              __METHOD__,
 -                              array( 'IGNORE' )
 -                      );
 -                      # Update the protection log
 -                      $log = new LogPage( 'protect' );
 -                      $comment = wfMessage(
 -                              'prot_1movedto2',
 -                              $this->getPrefixedText(),
 -                              $nt->getPrefixedText()
 -                      )->inContentLanguage()->text();
 -                      if ( $reason ) {
 -                              $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
 -                      }
 -                      // @todo FIXME: $params?
 -                      $logId = $log->addEntry(
 -                              'move_prot',
 -                              $nt,
 -                              $comment,
 -                              array( $this->getPrefixedText() ),
 -                              $wgUser
 -                      );
 -
 -                      // reread inserted pr_ids for log relation
 -                      $insertedPrIds = $dbw->select(
 -                              'page_restrictions',
 -                              'pr_id',
 -                              array( 'pr_page' => $redirid ),
 -                              __METHOD__
 -                      );
 -                      $logRelationsValues = array();
 -                      foreach ( $insertedPrIds as $prid ) {
 -                              $logRelationsValues[] = $prid->pr_id;
 -                      }
 -                      $log->addRelations( 'pr_id', $logRelationsValues, $logId );
 -              }
 -
 -              // Update *_from_namespace fields as needed
 -              if ( $this->getNamespace() != $nt->getNamespace() ) {
 -                      $dbw->update( 'pagelinks',
 -                              array( 'pl_from_namespace' => $nt->getNamespace() ),
 -                              array( 'pl_from' => $pageid ),
 -                              __METHOD__
 -                      );
 -                      $dbw->update( 'templatelinks',
 -                              array( 'tl_from_namespace' => $nt->getNamespace() ),
 -                              array( 'tl_from' => $pageid ),
 -                              __METHOD__
 -                      );
 -                      $dbw->update( 'imagelinks',
 -                              array( 'il_from_namespace' => $nt->getNamespace() ),
 -                              array( 'il_from' => $pageid ),
 -                              __METHOD__
 -                      );
 -              }
 -
 -              # Update watchlists
 -              $oldtitle = $this->getDBkey();
 -              $newtitle = $nt->getDBkey();
 -              $oldsnamespace = MWNamespace::getSubject( $this->getNamespace() );
 -              $newsnamespace = MWNamespace::getSubject( $nt->getNamespace() );
 -              if ( $oldsnamespace != $newsnamespace || $oldtitle != $newtitle ) {
 -                      WatchedItem::duplicateEntries( $this, $nt );
 -              }
 -
 -              $dbw->commit( __METHOD__ );
 -
 -              wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid, $reason ) );
 -              return true;
 -      }
 -
 -      /**
 -       * Move page to a title which is either a redirect to the
 -       * source page or nonexistent
 -       *
 -       * @param Title $nt The page to move to, which should be a redirect or nonexistent
 -       * @param string $reason The reason for the move
 -       * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
 -       *   if the user has the suppressredirect right
 -       * @throws MWException
 -       */
 -      private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
 -              global $wgUser, $wgContLang;
 -
 -              if ( $nt->exists() ) {
 -                      $moveOverRedirect = true;
 -                      $logType = 'move_redir';
 -              } else {
 -                      $moveOverRedirect = false;
 -                      $logType = 'move';
 -              }
 -
 -              if ( $createRedirect ) {
 -                      if ( $this->getNamespace() == NS_CATEGORY
 -                              && !wfMessage( 'category-move-redirect-override' )->inContentLanguage()->isDisabled()
 -                      ) {
 -                              $redirectContent = new WikitextContent(
 -                                      wfMessage( 'category-move-redirect-override' )
 -                                              ->params( $nt->getPrefixedText() )->inContentLanguage()->plain() );
 -                      } else {
 -                              $contentHandler = ContentHandler::getForTitle( $this );
 -                              $redirectContent = $contentHandler->makeRedirectContent( $nt,
 -                                      wfMessage( 'move-redirect-text' )->inContentLanguage()->plain() );
 -                      }
 -
 -                      // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
 +              $mp = new MovePage( $this, $nt );
 +              $status = $mp->move( $wgUser, $reason, $createRedirect );
 +              if ( $status->isOK() ) {
 +                      return true;
                } else {
 -                      $redirectContent = null;
 -              }
 -
 -              $logEntry = new ManualLogEntry( 'move', $logType );
 -              $logEntry->setPerformer( $wgUser );
 -              $logEntry->setTarget( $this );
 -              $logEntry->setComment( $reason );
 -              $logEntry->setParameters( array(
 -                      '4::target' => $nt->getPrefixedText(),
 -                      '5::noredir' => $redirectContent ? '0': '1',
 -              ) );
 -
 -              $formatter = LogFormatter::newFromEntry( $logEntry );
 -              $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
 -              $comment = $formatter->getPlainActionText();
 -              if ( $reason ) {
 -                      $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
 -              }
 -              # Truncate for whole multibyte characters.
 -              $comment = $wgContLang->truncate( $comment, 255 );
 -
 -              $oldid = $this->getArticleID();
 -
 -              $dbw = wfGetDB( DB_MASTER );
 -
 -              $newpage = WikiPage::factory( $nt );
 -
 -              if ( $moveOverRedirect ) {
 -                      $newid = $nt->getArticleID();
 -                      $newcontent = $newpage->getContent();
 -
 -                      # Delete the old redirect. We don't save it to history since
 -                      # by definition if we've got here it's rather uninteresting.
 -                      # We have to remove it so that the next step doesn't trigger
 -                      # a conflict on the unique namespace+title index...
 -                      $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
 -
 -                      $newpage->doDeleteUpdates( $newid, $newcontent );
 -              }
 -
 -              # Save a null revision in the page's history notifying of the move
 -              $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $wgUser );
 -              if ( !is_object( $nullRevision ) ) {
 -                      throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
 +                      return $status->getErrorsArray();
                }
 -
 -              $nullRevision->insertOn( $dbw );
 -
 -              # Change the name of the target page:
 -              $dbw->update( 'page',
 -                      /* SET */ array(
 -                              'page_namespace' => $nt->getNamespace(),
 -                              'page_title' => $nt->getDBkey(),
 -                      ),
 -                      /* WHERE */ array( 'page_id' => $oldid ),
 -                      __METHOD__
 -              );
 -
 -              // clean up the old title before reset article id - bug 45348
 -              if ( !$redirectContent ) {
 -                      WikiPage::onArticleDelete( $this );
 -              }
 -
 -              $this->resetArticleID( 0 ); // 0 == non existing
 -              $nt->resetArticleID( $oldid );
 -              $newpage->loadPageData( WikiPage::READ_LOCKING ); // bug 46397
 -
 -              $newpage->updateRevisionOn( $dbw, $nullRevision );
 -
 -              wfRunHooks( 'NewRevisionFromEditComplete',
 -                      array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
 -
 -              $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
 -
 -              if ( !$moveOverRedirect ) {
 -                      WikiPage::onArticleCreate( $nt );
 -              }
 -
 -              # Recreate the redirect, this time in the other direction.
 -              if ( $redirectContent ) {
 -                      $redirectArticle = WikiPage::factory( $this );
 -                      $redirectArticle->loadFromRow( false, WikiPage::READ_LOCKING ); // bug 46397
 -                      $newid = $redirectArticle->insertOn( $dbw );
 -                      if ( $newid ) { // sanity
 -                              $this->resetArticleID( $newid );
 -                              $redirectRevision = new Revision( array(
 -                                      'title' => $this, // for determining the default content model
 -                                      'page' => $newid,
 -                                      'user_text' => $wgUser->getName(),
 -                                      'user' => $wgUser->getId(),
 -                                      'comment' => $comment,
 -                                      'content' => $redirectContent ) );
 -                              $redirectRevision->insertOn( $dbw );
 -                              $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
 -
 -                              wfRunHooks( 'NewRevisionFromEditComplete',
 -                                      array( $redirectArticle, $redirectRevision, false, $wgUser ) );
 -
 -                              $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
 -                      }
 -              }
 -
 -              # Log the move
 -              $logid = $logEntry->insert();
 -              $logEntry->publish( $logid );
        }
  
        /**
         * Checks if $this can be moved to a given Title
         * - Selects for update, so don't call it unless you mean business
         *
 +       * @deprecated since 1.25, use MovePage's methods instead
         * @param Title $nt The new title to check
         * @return bool
         */
                        return false;
                }
  
 -              $revCount = $this->estimateRevisionCount();
 -              return $revCount > $wgDeleteRevisionsLimit;
 +              if ( $this->mIsBigDeletion === null ) {
 +                      $dbr = wfGetDB( DB_SLAVE );
 +
 +                      $revCount = $dbr->selectRowCount(
 +                              'revision',
 +                              '1',
 +                              array( 'rev_page' => $this->getArticleID() ),
 +                              __METHOD__,
 +                              array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
 +                      );
 +
 +                      $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
 +              }
 +
 +              return $this->mIsBigDeletion;
        }
  
        /**
 -       * Get the  approximate revision count of this page.
 +       * Get the approximate revision count of this page.
         *
         * @return int
         */
         * @return bool
         */
        public function exists() {
 -              return $this->getArticleID() != 0;
 +              $exists = $this->getArticleID() != 0;
 +              Hooks::run( 'TitleExists', array( $this, &$exists ) );
 +              return $exists;
        }
  
        /**
                 * @param Title $title
                 * @param bool|null $isKnown
                 */
 -              wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
 +              Hooks::run( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
  
                if ( !is_null( $isKnown ) ) {
                        return $isKnown;
                // on the Title object passed in, and should probably
                // tell the users to run updateCollations.php --force
                // in order to re-sort existing category relations.
 -              wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
 +              Hooks::run( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
                if ( $prefix !== '' ) {
                        # Separate with a line feed, so the unprefixed part is only used as
                        # a tiebreaker when two pages have the exact same prefix.
         */
        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;
        }
  
                $editnotice_ns = 'editnotice-' . $this->getNamespace();
                $editnotice_ns_message = wfMessage( $editnotice_ns );
                if ( $editnotice_ns_message->exists() ) {
-                       $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
+                       $notices[$editnotice_ns] = '<div class="mw-editnotice mw-editnotice-namespace ' .
+                               Sanitizer::escapeClass( "mw-$editnotice_ns" ) . '">' .
+                               $editnotice_ns_message->parseAsBlock() . '</div>';
                }
                if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
                        $parts = explode( '/', $this->getDBkey() );
                                $editnotice_base .= '-' . array_shift( $parts );
                                $editnotice_base_msg = wfMessage( $editnotice_base );
                                if ( $editnotice_base_msg->exists() ) {
-                                       $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
+                                       $notices[$editnotice_base] = '<div class="mw-editnotice mw-editnotice-base ' .
+                                               Sanitizer::escapeClass( "mw-$editnotice_base" ) . '">' .
+                                               $editnotice_base_msg->parseAsBlock() . '</div>';
                                }
                        }
                } else {
                        $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
                        $editnoticeMsg = wfMessage( $editnoticeText );
                        if ( $editnoticeMsg->exists() ) {
-                               $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
+                               $notices[$editnoticeText] = '<div class="mw-editnotice mw-editnotice-page ' .
+                                       Sanitizer::escapeClass( "mw-$editnoticeText" ) . '">' .
+                                       $editnoticeMsg->parseAsBlock() . '</div>';
                        }
                }
  
 -              wfRunHooks( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
 +              Hooks::run( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
                return $notices;
        }
  }