Merge "Drop outdated "documentation reviewed" tags"
[lhc/web/wiklou.git] / includes / Title.php
index ff85197..921538b 100644 (file)
@@ -612,28 +612,13 @@ class Title {
         * 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();
        }
 
        /**
@@ -955,9 +940,9 @@ class Title {
         * @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' );
                }
 
@@ -1332,6 +1317,25 @@ class Title {
                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
         *
@@ -1776,7 +1780,6 @@ class Title {
         * @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() ) {
@@ -1784,7 +1787,6 @@ class Title {
                } else {
                        $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
                }
-               wfProfileOut( __METHOD__ );
                return $ret;
        }
 
@@ -1884,18 +1886,16 @@ class Title {
         * @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 ) );
        }
 
        /**
@@ -1905,16 +1905,19 @@ class Title {
         *
         * @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 ) {
@@ -1934,16 +1937,14 @@ class Title {
         * @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;
                }
@@ -2034,12 +2035,12 @@ class Title {
         * @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 ) ) ) {
@@ -2051,7 +2052,7 @@ class Title {
                }
                // Check getUserPermissionsErrorsExpensive hook
                if (
-                       $doExpensiveQueries
+                       $rigor !== 'quick'
                        && !( $short && count( $errors ) > 0 )
                        && !Hooks::run( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
                ) {
@@ -2067,14 +2068,12 @@ class Title {
         * @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' ) {
@@ -2098,12 +2097,12 @@ class Title {
         * @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
@@ -2134,12 +2133,12 @@ class Title {
         * @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' ) {
@@ -2168,15 +2167,13 @@ class Title {
         * @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
@@ -2217,20 +2214,16 @@ class Title {
         * @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' );
                        }
@@ -2263,17 +2256,16 @@ class Title {
                                $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 ) );
@@ -2288,15 +2280,15 @@ class Title {
         * @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;
                }
 
@@ -2306,10 +2298,13 @@ class Title {
                        $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() );
                }
@@ -2323,12 +2318,12 @@ class Title {
         * @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;
@@ -2431,14 +2426,23 @@ class Title {
         *
         * @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' ) {
@@ -2476,10 +2480,9 @@ class Title {
                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;
        }
 
@@ -2715,8 +2718,6 @@ class Title {
                        return array( $this->mHasCascadingRestrictions, $pagerestrictions );
                }
 
-               wfProfileIn( __METHOD__ );
-
                $dbr = wfGetDB( DB_SLAVE );
 
                if ( $this->getNamespace() == NS_FILE ) {
@@ -2749,7 +2750,6 @@ class Title {
 
                $sources = $getPages ? array() : false;
                $now = wfTimestampNow();
-               $purgeExpired = false;
 
                foreach ( $res as $row ) {
                        $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
@@ -2775,14 +2775,8 @@ class Title {
                                } else {
                                        $sources = true;
                                }
-                       } else {
-                               // Trigger lazy purge of expired restrictions from the db
-                               $purgeExpired = true;
                        }
                }
-               if ( $purgeExpired ) {
-                       Title::purgeExpiredRestrictions();
-               }
 
                if ( $getPages ) {
                        $this->mCascadeSources = $sources;
@@ -2791,7 +2785,6 @@ class Title {
                        $this->mHasCascadingRestrictions = $sources;
                }
 
-               wfProfileOut( __METHOD__ );
                return array( $sources, $pagerestrictions );
        }
 
@@ -2934,7 +2927,6 @@ class Title {
                if ( count( $rows ) ) {
                        # Current system - load second to make them override.
                        $now = wfTimestampNow();
-                       $purgeExpired = false;
 
                        # Cycle through all the restrictions.
                        foreach ( $rows as $row ) {
@@ -2954,15 +2946,8 @@ class Title {
                                        $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;
@@ -3000,7 +2985,6 @@ class Title {
                                                $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 {
@@ -3186,13 +3170,13 @@ class Title {
                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
@@ -3221,12 +3205,12 @@ class Title {
                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()
@@ -3249,13 +3233,12 @@ class Title {
                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()
@@ -3609,7 +3592,7 @@ class Title {
         *
         * @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
         */
@@ -3623,10 +3606,13 @@ class Title {
                }
 
                $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;
        }
@@ -3643,7 +3629,10 @@ class Title {
                $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' );
                }
 
@@ -3818,6 +3807,7 @@ class Title {
                # 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;
@@ -4040,7 +4030,7 @@ class Title {
                if ( $this->mIsBigDeletion === null ) {
                        $dbr = wfGetDB( DB_SLAVE );
 
-                       $innerQuery = $dbr->selectSQLText(
+                       $revCount = $dbr->selectRowCount(
                                'revision',
                                '1',
                                array( 'rev_page' => $this->getArticleID() ),
@@ -4048,13 +4038,6 @@ class Title {
                                array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
                        );
 
-                       $revCount = $dbr->query(
-                               'SELECT COUNT(*) FROM (' . $innerQuery . ') AS innerQuery',
-                               __METHOD__
-                       );
-                       $revCount = $revCount->fetchRow();
-                       $revCount = $revCount['COUNT(*)'];
-
                        $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
                }
 
@@ -4106,12 +4089,11 @@ class Title {
                        '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__ );
                }
@@ -4162,17 +4144,19 @@ class Title {
                }
                // 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;
                }
@@ -4624,16 +4608,13 @@ class Title {
         */
        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 );
                }
 
@@ -4651,7 +4632,6 @@ class Title {
                        $langObj = wfGetLangObj( $this->mPageLanguage[0] );
                }
 
-               wfProfileOut( __METHOD__ );
                return $langObj;
        }
 
@@ -4698,28 +4678,64 @@ class Title {
        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
+                                       );
+                               }
                        }
                }