Group related functions
authorAlexandre Emsenhuber <ialex@users.mediawiki.org>
Sun, 11 Dec 2011 14:48:45 +0000 (14:48 +0000)
committerAlexandre Emsenhuber <ialex@users.mediawiki.org>
Sun, 11 Dec 2011 14:48:45 +0000 (14:48 +0000)
includes/Title.php

index 2caa5ca..5ee2eab 100644 (file)
@@ -479,6 +479,33 @@ class Title {
                return $wgLegalTitleChars;
        }
 
+       /**
+        * Returns a simple regex that will match on characters and sequences invalid in titles.
+        * 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.
+        *
+        * @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;
+       }
+
        /**
         * Get a string representation of a title suitable for
         * including in a search index
@@ -544,6 +571,22 @@ class Title {
                return Sanitizer::escapeId( $fragment, 'noninitial' );
        }
 
+       /**
+        * Callback for usort() to do title sorts by (namespace, title)
+        *
+        * @param $a Title
+        * @param $b Title
+        *
+        * @return Integer: result of string comparison, or namespace comparison
+        */
+       public static function compare( $a, $b ) {
+               if ( $a->getNamespace() == $b->getNamespace() ) {
+                       return strcmp( $a->getText(), $b->getText() );
+               } else {
+                       return $a->getNamespace() - $b->getNamespace();
+               }
+       }
+
        /**
         * Determine whether the object refers to a page within
         * this project.
@@ -630,6 +673,15 @@ class Title {
                return $this->mDbkeyform;
        }
 
+       /**
+        * Get the DB key with the initial letter case as specified by the user
+        *
+        * @return String DB key
+        */
+       function getUserCaseDBKey() {
+               return $this->mUserCaseDBKey;
+       }
+
        /**
         * Get the namespace index, i.e. one of the NS_xxxx constants.
         *
@@ -676,15 +728,6 @@ class Title {
                return $wgContLang->getNsText( $this->mNamespace );
        }
 
-       /**
-        * Get the DB key with the initial letter case as specified by the user
-        *
-        * @return String DB key
-        */
-       function getUserCaseDBKey() {
-               return $this->mUserCaseDBKey;
-       }
-
        /**
         * Get the namespace text of the subject (rather than talk) page
         *
@@ -715,163 +758,491 @@ class Title {
        }
 
        /**
-        * Get the Title fragment (i.e.\ the bit after the #) in text form
+        * Is this in a namespace that allows actual pages?
         *
-        * @return String Title fragment
-        */
-       public function getFragment() { return $this->mFragment; }
-
-       /**
-        * Get the fragment in URL form, including the "#" character if there is one
-        * @return String Fragment in URL form
+        * @return Bool
+        * @internal note -- uses hardcoded namespace index instead of constants
         */
-       public function getFragmentForURL() {
-               if ( $this->mFragment == '' ) {
-                       return '';
-               } else {
-                       return '#' . Title::escapeFragmentForURL( $this->mFragment );
-               }
+       public function canExist() {
+               return $this->mNamespace >= 0 && $this->mNamespace != NS_MEDIA;
        }
 
        /**
-        * Get the default namespace index, for when there is no namespace
+        * Can this title be added to a user's watchlist?
         *
-        * @return Int Default namespace index
+        * @return Bool TRUE or FALSE
         */
-       public function getDefaultNamespace() {
-               return $this->mDefaultNamespace;
+       public function isWatchable() {
+               return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
        }
 
        /**
-        * Get title for search index
+        * Returns true if this is a special page.
         *
-        * @return String a stripped-down title string ready for the
-        *  search index
+        * @return boolean
         */
-       public function getIndexTitle() {
-               return Title::indexTitle( $this->mNamespace, $this->mTextform );
+       public function isSpecialPage() {
+               return $this->getNamespace() == NS_SPECIAL;
        }
 
        /**
-        * Get the prefixed database key form
+        * Returns true if this title resolves to the named special page
         *
-        * @return String the prefixed title, with underscores and
-        *  any interwiki and namespace prefixes
+        * @param $name String The special page name
+        * @return boolean
         */
-       public function getPrefixedDBkey() {
-               $s = $this->prefix( $this->mDbkeyform );
-               $s = str_replace( ' ', '_', $s );
-               return $s;
+       public function isSpecial( $name ) {
+               if ( $this->isSpecialPage() ) {
+                       list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
+                       if ( $name == $thisName ) {
+                               return true;
+                       }
+               }
+               return false;
        }
 
        /**
-        * Get the prefixed title with spaces.
-        * This is the form usually used for display
+        * If the Title refers to a special page alias which is not the local default, resolve
+        * the alias, and localise the name as necessary.  Otherwise, return $this
         *
-        * @return String the prefixed title, with spaces
+        * @return Title
         */
-       public function getPrefixedText() {
-               // @todo FIXME: Bad usage of empty() ?
-               if ( empty( $this->mPrefixedText ) ) {
-                       $s = $this->prefix( $this->mTextform );
-                       $s = str_replace( '_', ' ', $s );
-                       $this->mPrefixedText = $s;
+       public function fixSpecialName() {
+               if ( $this->isSpecialPage() ) {
+                       list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
+                       if ( $canonicalName ) {
+                               $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
+                               if ( $localName != $this->mDbkeyform ) {
+                                       return Title::makeTitle( NS_SPECIAL, $localName );
+                               }
+                       }
                }
-               return $this->mPrefixedText;
+               return $this;
        }
 
        /**
-       /**
-        * Get the prefixed title with spaces, plus any fragment
-        * (part beginning with '#')
-        *
-        * @return String the prefixed title, with spaces and the fragment, including '#'
+        * Returns true if the title is inside the specified namespace.
+        * 
+        * Please make use of this instead of comparing to getNamespace()
+        * This function is much more resistant to changes we may make
+        * to namespaces than code that makes direct comparisons.
+        * @param $ns int The namespace
+        * @return bool
+        * @since 1.19
         */
-       public function getFullText() {
-               $text = $this->getPrefixedText();
-               if ( $this->mFragment != '' ) {
-                       $text .= '#' . $this->mFragment;
-               }
-               return $text;
+       public function inNamespace( $ns ) {
+               return MWNamespace::equals( $this->getNamespace(), $ns );
        }
 
        /**
-        * Get the base page name, i.e. the leftmost part before any slashes
+        * Returns true if the title is inside one of the specified namespaces.
         *
-        * @return String Base name
+        * @param ...$namespaces The namespaces to check for
+        * @return bool
+        * @since 1.19
         */
-       public function getBaseText() {
-               if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
-                       return $this->getText();
+       public function inNamespaces( /* ... */ ) {
+               $namespaces = func_get_args();
+               if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
+                       $namespaces = $namespaces[0];
                }
 
-               $parts = explode( '/', $this->getText() );
-               # Don't discard the real title if there's no subpage involved
-               if ( count( $parts ) > 1 ) {
-                       unset( $parts[count( $parts ) - 1] );
+               foreach ( $namespaces as $ns ) {
+                       if ( $this->inNamespace( $ns ) ) {
+                               return true;
+                       }
                }
-               return implode( '/', $parts );
+
+               return false;
        }
 
        /**
-        * Get the lowest-level subpage name, i.e. the rightmost part after any slashes
+        * Returns true if the title has the same subject namespace as the
+        * namespace specified.
+        * For example this method will take NS_USER and return true if namespace
+        * is either NS_USER or NS_USER_TALK since both of them have NS_USER
+        * as their subject namespace.
         *
-        * @return String Subpage name
+        * This is MUCH simpler than individually testing for equivilance
+        * against both NS_USER and NS_USER_TALK, and is also forward compatible.
+        * @since 1.19
         */
-       public function getSubpageText() {
-               if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
-                       return( $this->mTextform );
-               }
-               $parts = explode( '/', $this->mTextform );
-               return( $parts[count( $parts ) - 1] );
+       public function hasSubjectNamespace( $ns ) {
+               return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
        }
 
        /**
-        * Get the HTML-escaped displayable text form.
-        * Used for the title field in <a> tags.
+        * Is this Title in a namespace which contains content?
+        * In other words, is this a content page, for the purposes of calculating
+        * statistics, etc?
         *
-        * @return String the text, including any prefixes
+        * @return Boolean
         */
-       public function getEscapedText() {
-               return htmlspecialchars( $this->getPrefixedText() );
+       public function isContentPage() {
+               return MWNamespace::isContent( $this->getNamespace() );
        }
 
        /**
-        * Get a URL-encoded form of the subpage text
+        * Would anybody with sufficient privileges be able to move this page?
+        * Some pages just aren't movable.
         *
-        * @return String URL-encoded subpage name
+        * @return Bool TRUE or FALSE
         */
-       public function getSubpageUrlForm() {
-               $text = $this->getSubpageText();
-               $text = wfUrlencode( str_replace( ' ', '_', $text ) );
-               return( $text );
+       public function isMovable() {
+               if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
+                       // Interwiki title or immovable namespace. Hooks don't get to override here
+                       return false;
+               }
+
+               $result = true;
+               wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
+               return $result;
        }
 
        /**
-        * Get a URL-encoded title (not an actual URL) including interwiki
+        * Is this the mainpage?
+        * @note Title::newFromText seams to be sufficiently optimized by the title
+        * cache that we don't need to over-optimize by doing direct comparisons and
+        * acidentally creating new bugs where $title->equals( Title::newFromText() )
+        * ends up reporting something differently than $title->isMainPage();
         *
-        * @return String the URL-encoded form
+        * @since 1.18
+        * @return Bool
         */
-       public function getPrefixedURL() {
-               $s = $this->prefix( $this->mDbkeyform );
-               $s = wfUrlencode( str_replace( ' ', '_', $s ) );
-               return $s;
+       public function isMainPage() {
+               return $this->equals( Title::newMainPage() );
        }
 
        /**
-        * Get a real URL referring to this title, with interwiki link and
-        * fragment
+        * Is this a subpage?
         *
-        * @param $query \twotypes{\string,\array} an optional query string, not used for interwiki
-        *   links. Can be specified as an associative array as well, e.g.,
-        *   array( 'action' => 'edit' ) (keys and values will be URL-escaped).
-        * @param $variant String language variant of url (for sr, zh..)
-        * @return String the URL
+        * @return Bool
         */
-       public function getFullURL( $query = '', $variant = false ) {
-               # Hand off all the decisions on urls to getLocalURL
-               $url = $this->getLocalURL( $query, $variant );
-
+       public function isSubpage() {
+               return MWNamespace::hasSubpages( $this->mNamespace )
+                       ? strpos( $this->getText(), '/' ) !== false
+                       : false;
+       }
+
+       /**
+        * Is this a conversion table for the LanguageConverter?
+        *
+        * @return Bool
+        */
+       public function isConversionTable() {
+               return $this->getNamespace() == NS_MEDIAWIKI &&
+                       strpos( $this->getText(), 'Conversiontable' ) !== false;
+       }
+
+       /**
+        * Does that page contain wikitext, or it is JS, CSS or whatever?
+        *
+        * @return Bool
+        */
+       public function isWikitextPage() {
+               $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
+               wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
+               return $retval;
+       }
+
+       /**
+        * Could this page contain custom CSS or JavaScript, based
+        * on the title?
+        *
+        * @return Bool
+        */
+       public function isCssOrJsPage() {
+               $retval = $this->mNamespace == NS_MEDIAWIKI
+                       && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
+               wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
+               return $retval;
+       }
+
+       /**
+        * Is this a .css or .js subpage of a user page?
+        * @return Bool
+        */
+       public function isCssJsSubpage() {
+               return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
+       }
+
+       /**
+        * Is this a *valid* .css or .js subpage of a user page?
+        *
+        * @return Bool
+        * @deprecated since 1.17
+        */
+       public function isValidCssJsSubpage() {
+               return $this->isCssJsSubpage();
+       }
+
+       /**
+        * Trim down a .css or .js subpage title to get the corresponding skin name
+        *
+        * @return string containing skin name from .css or .js subpage title
+        */
+       public function getSkinFromCssJsSubpage() {
+               $subpage = explode( '/', $this->mTextform );
+               $subpage = $subpage[ count( $subpage ) - 1 ];
+               $lastdot = strrpos( $subpage, '.' );
+               if ( $lastdot === false )
+                       return $subpage; # Never happens: only called for names ending in '.css' or '.js'
+               return substr( $subpage, 0, $lastdot );
+       }
+
+       /**
+        * Is this a .css subpage of a user page?
+        *
+        * @return Bool
+        */
+       public function isCssSubpage() {
+               return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
+       }
+
+       /**
+        * Is this a .js subpage of a user page?
+        *
+        * @return Bool
+        */
+       public function isJsSubpage() {
+               return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+       }
+
+       /**
+        * Is this a talk page of some sort?
+        *
+        * @return Bool
+        */
+       public function isTalkPage() {
+               return MWNamespace::isTalk( $this->getNamespace() );
+       }
+
+       /**
+        * Get a Title object associated with the talk page of this article
+        *
+        * @return Title the object for the talk page
+        */
+       public function getTalkPage() {
+               return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
+       }
+
+       /**
+        * Get a title object associated with the subject page of this
+        * talk page
+        *
+        * @return Title the object for the subject page
+        */
+       public function getSubjectPage() {
+               // Is this the same title?
+               $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
+               if ( $this->getNamespace() == $subjectNS ) {
+                       return $this;
+               }
+               return Title::makeTitle( $subjectNS, $this->getDBkey() );
+       }
+
+       /**
+        * Get the default namespace index, for when there is no namespace
+        *
+        * @return Int Default namespace index
+        */
+       public function getDefaultNamespace() {
+               return $this->mDefaultNamespace;
+       }
+
+       /**
+        * Get title for search index
+        *
+        * @return String a stripped-down title string ready for the
+        *  search index
+        */
+       public function getIndexTitle() {
+               return Title::indexTitle( $this->mNamespace, $this->mTextform );
+       }
+
+       /**
+        * Get the Title fragment (i.e.\ the bit after the #) in text form
+        *
+        * @return String Title fragment
+        */
+       public function getFragment() {
+               return $this->mFragment;
+       }
+
+       /**
+        * Get the fragment in URL form, including the "#" character if there is one
+        * @return String Fragment in URL form
+        */
+       public function getFragmentForURL() {
+               if ( $this->mFragment == '' ) {
+                       return '';
+               } else {
+                       return '#' . Title::escapeFragmentForURL( $this->mFragment );
+               }
+       }
+
+       /**
+        * Set the fragment for this title. Removes the first character from the
+        * specified fragment before setting, so it assumes you're passing it with
+        * an initial "#".
+        *
+        * Deprecated for public use, use Title::makeTitle() with fragment parameter.
+        * Still in active use privately.
+        *
+        * @param $fragment String text
+        */
+       public function setFragment( $fragment ) {
+               $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
+       }
+
+       /**
+        * Prefix some arbitrary text with the namespace or interwiki prefix
+        * of this object
+        *
+        * @param $name String the text
+        * @return String the prefixed text
+        * @private
+        */
+       private function prefix( $name ) {
+               $p = '';
+               if ( $this->mInterwiki != '' ) {
+                       $p = $this->mInterwiki . ':';
+               }
+
+               if ( 0 != $this->mNamespace ) {
+                       $p .= $this->getNsText() . ':';
+               }
+               return $p . $name;
+       }
+
+       /**
+        * Get the prefixed database key form
+        *
+        * @return String the prefixed title, with underscores and
+        *  any interwiki and namespace prefixes
+        */
+       public function getPrefixedDBkey() {
+               $s = $this->prefix( $this->mDbkeyform );
+               $s = str_replace( ' ', '_', $s );
+               return $s;
+       }
+
+       /**
+        * Get the prefixed title with spaces.
+        * This is the form usually used for display
+        *
+        * @return String the prefixed title, with spaces
+        */
+       public function getPrefixedText() {
+               // @todo FIXME: Bad usage of empty() ?
+               if ( empty( $this->mPrefixedText ) ) {
+                       $s = $this->prefix( $this->mTextform );
+                       $s = str_replace( '_', ' ', $s );
+                       $this->mPrefixedText = $s;
+               }
+               return $this->mPrefixedText;
+       }
+
+       /**
+        * Return a string representation of this title
+        *
+        * @return String representation of this title
+        */
+       public function __toString() {
+               return $this->getPrefixedText();
+       }
+
+       /**
+        * Get the prefixed title with spaces, plus any fragment
+        * (part beginning with '#')
+        *
+        * @return String the prefixed title, with spaces and the fragment, including '#'
+        */
+       public function getFullText() {
+               $text = $this->getPrefixedText();
+               if ( $this->mFragment != '' ) {
+                       $text .= '#' . $this->mFragment;
+               }
+               return $text;
+       }
+
+       /**
+        * Get the base page name, i.e. the leftmost part before any slashes
+        *
+        * @return String Base name
+        */
+       public function getBaseText() {
+               if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+                       return $this->getText();
+               }
+
+               $parts = explode( '/', $this->getText() );
+               # Don't discard the real title if there's no subpage involved
+               if ( count( $parts ) > 1 ) {
+                       unset( $parts[count( $parts ) - 1] );
+               }
+               return implode( '/', $parts );
+       }
+
+       /**
+        * Get the lowest-level subpage name, i.e. the rightmost part after any slashes
+        *
+        * @return String Subpage name
+        */
+       public function getSubpageText() {
+               if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+                       return( $this->mTextform );
+               }
+               $parts = explode( '/', $this->mTextform );
+               return( $parts[count( $parts ) - 1] );
+       }
+
+       /**
+        * Get the HTML-escaped displayable text form.
+        * Used for the title field in <a> tags.
+        *
+        * @return String the text, including any prefixes
+        */
+       public function getEscapedText() {
+               return htmlspecialchars( $this->getPrefixedText() );
+       }
+
+       /**
+        * Get a URL-encoded form of the subpage text
+        *
+        * @return String URL-encoded subpage name
+        */
+       public function getSubpageUrlForm() {
+               $text = $this->getSubpageText();
+               $text = wfUrlencode( str_replace( ' ', '_', $text ) );
+               return( $text );
+       }
+
+       /**
+        * Get a URL-encoded title (not an actual URL) including interwiki
+        *
+        * @return String the URL-encoded form
+        */
+       public function getPrefixedURL() {
+               $s = $this->prefix( $this->mDbkeyform );
+               $s = wfUrlencode( str_replace( ' ', '_', $s ) );
+               return $s;
+       }
+
+       /**
+        * Get a real URL referring to this title, with interwiki link and
+        * fragment
+        *
+        * @param $query \twotypes{\string,\array} an optional query string, not used for interwiki
+        *   links. Can be specified as an associative array as well, e.g.,
+        *   array( 'action' => 'edit' ) (keys and values will be URL-escaped).
+        * @param $variant String language variant of url (for sr, zh..)
+        * @return String the URL
+        */
+       public function getFullURL( $query = '', $variant = false ) {
+               # Hand off all the decisions on urls to getLocalURL
+               $url = $this->getLocalURL( $query, $variant );
+
                # Expand the url to make it a full url. Note that getLocalURL has the
                # potential to output full urls for a variety of reasons, so we use
                # wfExpandUrl instead of simply prepending $wgServer
@@ -1082,101 +1453,6 @@ class Title {
                return $s;
        }
 
-       /**
-        * Is this page "semi-protected" - the *only* protection is autoconfirm?
-        *
-        * @param $action String Action to check (default: edit)
-        * @return Bool
-        */
-       public function isSemiProtected( $action = 'edit' ) {
-               if ( $this->exists() ) {
-                       $restrictions = $this->getRestrictions( $action );
-                       if ( count( $restrictions ) > 0 ) {
-                               foreach ( $restrictions as $restriction ) {
-                                       if ( strtolower( $restriction ) != 'autoconfirmed' ) {
-                                               return false;
-                                       }
-                               }
-                       } else {
-                               # Not protected
-                               return false;
-                       }
-                       return true;
-               } else {
-                       # If it doesn't exist, it can't be protected
-                       return false;
-               }
-       }
-
-       /**
-        * Does the title correspond to a protected article?
-        *
-        * @param $action String the action the page is protected from,
-        * by default checks all actions.
-        * @return Bool
-        */
-       public function isProtected( $action = '' ) {
-               global $wgRestrictionLevels;
-
-               $restrictionTypes = $this->getRestrictionTypes();
-
-               # Special pages have inherent protection
-               if( $this->isSpecialPage() ) {
-                       return true;
-               }
-
-               # Check regular protection levels
-               foreach ( $restrictionTypes as $type ) {
-                       if ( $action == $type || $action == '' ) {
-                               $r = $this->getRestrictions( $type );
-                               foreach ( $wgRestrictionLevels as $level ) {
-                                       if ( in_array( $level, $r ) && $level != '' ) {
-                                               return true;
-                                       }
-                               }
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Determines if $user is unable to edit this page because it has been protected
-        * by $wgNamespaceProtection.
-        *
-        * @param $user User object to check permissions
-        * @return Bool
-        */
-       public function isNamespaceProtected( User $user ) {
-               global $wgNamespaceProtection;
-
-               if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
-                       foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
-                               if ( $right != '' && !$user->isAllowed( $right ) ) {
-                                       return true;
-                               }
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Is this a conversion table for the LanguageConverter?
-        *
-        * @return Bool
-        */
-       public function isConversionTable() {
-               if(
-                       $this->getNamespace() == NS_MEDIAWIKI &&
-                       strpos( $this->getText(), 'Conversiontable' ) !== false
-               )
-               {
-                       return true;
-               }
-
-               return false;
-       }
-
        /**
         * Is $wgUser watching this page?
         *
@@ -1784,15 +2060,88 @@ class Title {
                        );
                }
 
-               $errors = array();
-               while( count( $checks ) > 0 &&
-                               !( $short && count( $errors ) > 0 ) ) {
-                       $method = array_shift( $checks );
-                       $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
+               $errors = array();
+               while( count( $checks ) > 0 &&
+                               !( $short && count( $errors ) > 0 ) ) {
+                       $method = array_shift( $checks );
+                       $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $errors;
+       }
+
+       /**
+        * Protect css subpages of user pages: can $wgUser edit
+        * this page?
+        *
+        * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
+        * @return Bool
+        */
+       public function userCanEditCssSubpage() {
+               global $wgUser;
+               wfDeprecated( __METHOD__ );
+               return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
+                       || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+       }
+
+       /**
+        * Protect js subpages of user pages: can $wgUser edit
+        * this page?
+        *
+        * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
+        * @return Bool
+        */
+       public function userCanEditJsSubpage() {
+               global $wgUser;
+               wfDeprecated( __METHOD__ );
+               return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
+                          || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+       }
+
+       /**
+        * Get a filtered list of all restriction types supported by this wiki.
+        * @param bool $exists True to get all restriction types that apply to
+        * titles that do exist, False for all restriction types that apply to
+        * titles that do not exist
+        * @return array
+        */
+       public static function getFilteredRestrictionTypes( $exists = true ) {
+               global $wgRestrictionTypes;
+               $types = $wgRestrictionTypes;
+               if ( $exists ) {
+                       # Remove the create restriction for existing titles
+                       $types = array_diff( $types, array( 'create' ) );
+               } else {
+                       # Only the create and upload restrictions apply to non-existing titles
+                       $types = array_intersect( $types, array( 'create', 'upload' ) );
+               }
+               return $types;
+       }
+
+       /**
+        * Returns restriction types for the current Title
+        *
+        * @return array applicable restriction types
+        */
+       public function getRestrictionTypes() {
+               if ( $this->isSpecialPage() ) {
+                       return array();
+               }
+
+               $types = self::getFilteredRestrictionTypes( $this->exists() );
+
+               if ( $this->getNamespace() != NS_FILE ) {
+                       # Remove the upload restriction for non-file titles
+                       $types = array_diff( $types, array( 'upload' ) );
                }
 
-               wfProfileOut( __METHOD__ );
-               return $errors;
+               wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
+
+               wfDebug( __METHOD__ . ': applicable restrictions to [[' .
+                       $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
+
+               return $types;
        }
 
        /**
@@ -1882,285 +2231,104 @@ class Title {
                        if ( $create_perm ) {
                                $params = array( "[create=$create_perm] $expiry_description", '' );
                                $log->addEntry( ( isset( $this->mRestrictions['create'] ) && $this->mRestrictions['create'] ) ? 'modify' : 'protect', $this, trim( $reason ), $params );
-                       } else {
-                               $log->addEntry( 'unprotect', $this, $reason );
-                       }
-               }
-
-               return true;
-       }
-
-       /**
-        * Remove any title protection due to page existing
-        */
-       public function deleteTitleProtection() {
-               $dbw = wfGetDB( DB_MASTER );
-
-               $dbw->delete(
-                       'protected_titles',
-                       array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
-                       __METHOD__
-               );
-               $this->mTitleProtection = false;
-       }
-
-       /**
-        * Would anybody with sufficient privileges be able to move this page?
-        * Some pages just aren't movable.
-        *
-        * @return Bool TRUE or FALSE
-        */
-       public function isMovable() {
-               if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
-                       // Interwiki title or immovable namespace. Hooks don't get to override here
-                       return false;
-               }
-
-               $result = true;
-               wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
-               return $result;
-       }
-
-       /**
-        * Is this the mainpage?
-        * @note Title::newFromText seams to be sufficiently optimized by the title
-        * cache that we don't need to over-optimize by doing direct comparisons and
-        * acidentally creating new bugs where $title->equals( Title::newFromText() )
-        * ends up reporting something differently than $title->isMainPage();
-        *
-        * @since 1.18
-        * @return Bool
-        */
-       public function isMainPage() {
-               return $this->equals( Title::newMainPage() );
-       }
-
-       /**
-        * Is this a talk page of some sort?
-        *
-        * @return Bool
-        */
-       public function isTalkPage() {
-               return MWNamespace::isTalk( $this->getNamespace() );
-       }
-
-       /**
-        * Is this a subpage?
-        *
-        * @return Bool
-        */
-       public function isSubpage() {
-               return MWNamespace::hasSubpages( $this->mNamespace )
-                       ? strpos( $this->getText(), '/' ) !== false
-                       : false;
-       }
-
-       /**
-        * Returns true if the title is inside the specified namespace.
-        * 
-        * Please make use of this instead of comparing to getNamespace()
-        * This function is much more resistant to changes we may make
-        * to namespaces than code that makes direct comparisons.
-        * @param $ns int The namespace
-        * @return bool
-        * @since 1.19
-        */
-       public function inNamespace( $ns ) {
-               return MWNamespace::equals( $this->getNamespace(), $ns );
-       }
-
-       /**
-        * Returns true if the title is inside one of the specified namespaces.
-        *
-        * @param ...$namespaces The namespaces to check for
-        * @return bool
-        * @since 1.19
-        */
-       public function inNamespaces( /* ... */ ) {
-               $namespaces = func_get_args();
-               if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
-                       $namespaces = $namespaces[0];
-               }
-
-               foreach ( $namespaces as $ns ) {
-                       if ( $this->inNamespace( $ns ) ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Returns true if the title has the same subject namespace as the
-        * namespace specified.
-        * For example this method will take NS_USER and return true if namespace
-        * is either NS_USER or NS_USER_TALK since both of them have NS_USER
-        * as their subject namespace.
-        *
-        * This is MUCH simpler than individually testing for equivilance
-        * against both NS_USER and NS_USER_TALK, and is also forward compatible.
-        * @since 1.19
-        */
-       public function hasSubjectNamespace( $ns ) {
-               return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
-       }
-
-       /**
-        * Does this have subpages?  (Warning, usually requires an extra DB query.)
-        *
-        * @return Bool
-        */
-       public function hasSubpages() {
-               if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
-                       # Duh
-                       return false;
-               }
-
-               # We dynamically add a member variable for the purpose of this method
-               # alone to cache the result.  There's no point in having it hanging
-               # around uninitialized in every Title object; therefore we only add it
-               # if needed and don't declare it statically.
-               if ( isset( $this->mHasSubpages ) ) {
-                       return $this->mHasSubpages;
-               }
-
-               $subpages = $this->getSubpages( 1 );
-               if ( $subpages instanceof TitleArray ) {
-                       return $this->mHasSubpages = (bool)$subpages->count();
-               }
-               return $this->mHasSubpages = false;
-       }
-
-       /**
-        * Get all subpages of this page.
-        *
-        * @param $limit Int maximum number of subpages to fetch; -1 for no limit
-        * @return mixed TitleArray, or empty array if this page's namespace
-        *  doesn't allow subpages
-        */
-       public function getSubpages( $limit = -1 ) {
-               if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
-                       return array();
-               }
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $conds['page_namespace'] = $this->getNamespace();
-               $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
-               $options = array();
-               if ( $limit > -1 ) {
-                       $options['LIMIT'] = $limit;
-               }
-               return $this->mSubpages = TitleArray::newFromResult(
-                       $dbr->select( 'page',
-                               array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
-                               $conds,
-                               __METHOD__,
-                               $options
-                       )
-               );
-       }
-
-       /**
-        * Does that page contain wikitext, or it is JS, CSS or whatever?
-        *
-        * @return Bool
-        */
-       public function isWikitextPage() {
-               $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
-               wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
-               return $retval;
-       }
-
-       /**
-        * Could this page contain custom CSS or JavaScript, based
-        * on the title?
-        *
-        * @return Bool
-        */
-       public function isCssOrJsPage() {
-               $retval = $this->mNamespace == NS_MEDIAWIKI
-                       && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
-               wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
-               return $retval;
-       }
-
-       /**
-        * Is this a .css or .js subpage of a user page?
-        * @return Bool
-        */
-       public function isCssJsSubpage() {
-               return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
-       }
-
-       /**
-        * Is this a *valid* .css or .js subpage of a user page?
-        *
-        * @return Bool
-        * @deprecated since 1.17
-        */
-       public function isValidCssJsSubpage() {
-               return $this->isCssJsSubpage();
-       }
-
-       /**
-        * Trim down a .css or .js subpage title to get the corresponding skin name
-        *
-        * @return string containing skin name from .css or .js subpage title
-        */
-       public function getSkinFromCssJsSubpage() {
-               $subpage = explode( '/', $this->mTextform );
-               $subpage = $subpage[ count( $subpage ) - 1 ];
-               $lastdot = strrpos( $subpage, '.' );
-               if ( $lastdot === false )
-                       return $subpage; # Never happens: only called for names ending in '.css' or '.js'
-               return substr( $subpage, 0, $lastdot );
+                       } else {
+                               $log->addEntry( 'unprotect', $this, $reason );
+                       }
+               }
+
+               return true;
        }
 
        /**
-        * Is this a .css subpage of a user page?
-        *
-        * @return Bool
+        * Remove any title protection due to page existing
         */
-       public function isCssSubpage() {
-               return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
+       public function deleteTitleProtection() {
+               $dbw = wfGetDB( DB_MASTER );
+
+               $dbw->delete(
+                       'protected_titles',
+                       array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
+                       __METHOD__
+               );
+               $this->mTitleProtection = false;
        }
 
        /**
-        * Is this a .js subpage of a user page?
+        * Is this page "semi-protected" - the *only* protection is autoconfirm?
         *
+        * @param $action String Action to check (default: edit)
         * @return Bool
         */
-       public function isJsSubpage() {
-               return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+       public function isSemiProtected( $action = 'edit' ) {
+               if ( $this->exists() ) {
+                       $restrictions = $this->getRestrictions( $action );
+                       if ( count( $restrictions ) > 0 ) {
+                               foreach ( $restrictions as $restriction ) {
+                                       if ( strtolower( $restriction ) != 'autoconfirmed' ) {
+                                               return false;
+                                       }
+                               }
+                       } else {
+                               # Not protected
+                               return false;
+                       }
+                       return true;
+               } else {
+                       # If it doesn't exist, it can't be protected
+                       return false;
+               }
        }
 
        /**
-        * Protect css subpages of user pages: can $wgUser edit
-        * this page?
+        * Does the title correspond to a protected article?
         *
-        * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
+        * @param $action String the action the page is protected from,
+        * by default checks all actions.
         * @return Bool
         */
-       public function userCanEditCssSubpage() {
-               global $wgUser;
-               wfDeprecated( __METHOD__ );
-               return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
-                       || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+       public function isProtected( $action = '' ) {
+               global $wgRestrictionLevels;
+
+               $restrictionTypes = $this->getRestrictionTypes();
+
+               # Special pages have inherent protection
+               if( $this->isSpecialPage() ) {
+                       return true;
+               }
+
+               # Check regular protection levels
+               foreach ( $restrictionTypes as $type ) {
+                       if ( $action == $type || $action == '' ) {
+                               $r = $this->getRestrictions( $type );
+                               foreach ( $wgRestrictionLevels as $level ) {
+                                       if ( in_array( $level, $r ) && $level != '' ) {
+                                               return true;
+                                       }
+                               }
+                       }
+               }
+
+               return false;
        }
 
        /**
-        * Protect js subpages of user pages: can $wgUser edit
-        * this page?
+        * Determines if $user is unable to edit this page because it has been protected
+        * by $wgNamespaceProtection.
         *
-        * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
+        * @param $user User object to check permissions
         * @return Bool
         */
-       public function userCanEditJsSubpage() {
-               global $wgUser;
-               wfDeprecated( __METHOD__ );
-               return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
-                          || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+       public function isNamespaceProtected( User $user ) {
+               global $wgNamespaceProtection;
+
+               if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
+                       foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
+                               if ( $right != '' && !$user->isAllowed( $right ) ) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
        }
 
        /**
@@ -2271,6 +2439,34 @@ class Title {
                return array( $sources, $pagerestrictions );
        }
 
+       /**
+        * Accessor/initialisation for mRestrictions
+        *
+        * @param $action String action that permission needs to be checked for
+        * @return Array of Strings the array of groups allowed to edit this article
+        */
+       public function getRestrictions( $action ) {
+               if ( !$this->mRestrictionsLoaded ) {
+                       $this->loadRestrictions();
+               }
+               return isset( $this->mRestrictions[$action] )
+                               ? $this->mRestrictions[$action]
+                               : array();
+       }
+
+       /**
+        * Get the expiry time for the restriction against a given action
+        *
+        * @return String|Bool 14-char timestamp, or 'infinity' if the page is protected forever
+        *      or not protected at all, or false if the action is not recognised.
+        */
+       public function getRestrictionExpiry( $action ) {
+               if ( !$this->mRestrictionsLoaded ) {
+                       $this->loadRestrictions();
+               }
+               return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
+       }
+
        /**
         * Returns cascading restrictions for the current article
         *
@@ -2445,31 +2641,58 @@ class Title {
        }
 
        /**
-        * Accessor/initialisation for mRestrictions
+        * Does this have subpages?  (Warning, usually requires an extra DB query.)
         *
-        * @param $action String action that permission needs to be checked for
-        * @return Array of Strings the array of groups allowed to edit this article
+        * @return Bool
         */
-       public function getRestrictions( $action ) {
-               if ( !$this->mRestrictionsLoaded ) {
-                       $this->loadRestrictions();
+       public function hasSubpages() {
+               if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+                       # Duh
+                       return false;
                }
-               return isset( $this->mRestrictions[$action] )
-                               ? $this->mRestrictions[$action]
-                               : array();
+
+               # We dynamically add a member variable for the purpose of this method
+               # alone to cache the result.  There's no point in having it hanging
+               # around uninitialized in every Title object; therefore we only add it
+               # if needed and don't declare it statically.
+               if ( isset( $this->mHasSubpages ) ) {
+                       return $this->mHasSubpages;
+               }
+
+               $subpages = $this->getSubpages( 1 );
+               if ( $subpages instanceof TitleArray ) {
+                       return $this->mHasSubpages = (bool)$subpages->count();
+               }
+               return $this->mHasSubpages = false;
        }
 
        /**
-        * Get the expiry time for the restriction against a given action
+        * Get all subpages of this page.
         *
-        * @return String|Bool 14-char timestamp, or 'infinity' if the page is protected forever
-        *      or not protected at all, or false if the action is not recognised.
+        * @param $limit Int maximum number of subpages to fetch; -1 for no limit
+        * @return mixed TitleArray, or empty array if this page's namespace
+        *  doesn't allow subpages
         */
-       public function getRestrictionExpiry( $action ) {
-               if ( !$this->mRestrictionsLoaded ) {
-                       $this->loadRestrictions();
+       public function getSubpages( $limit = -1 ) {
+               if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
+                       return array();
                }
-               return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $conds['page_namespace'] = $this->getNamespace();
+               $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
+               $options = array();
+               if ( $limit > -1 ) {
+                       $options['LIMIT'] = $limit;
+               }
+               return $this->mSubpages = TitleArray::newFromResult(
+                       $dbr->select( 'page',
+                               array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
+                               $conds,
+                               __METHOD__,
+                               $options
+                       )
+               );
        }
 
        /**
@@ -2611,117 +2834,50 @@ class Title {
        }
 
        /**
-        * What is the page_latest field for this page?
-        *
-        * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
-        * @return Int or 0 if the page doesn't exist
-        */
-       public function getLatestRevID( $flags = 0 ) {
-               if ( $this->mLatestID !== false ) {
-                       return intval( $this->mLatestID );
-               }
-               # Calling getArticleID() loads the field from cache as needed
-               if ( !$this->getArticleID( $flags ) ) {
-                       return $this->mLatestID = 0;
-               }
-               $linkCache = LinkCache::singleton();
-               $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
-
-               return $this->mLatestID;
-       }
-
-       /**
-        * This clears some fields in this object, and clears any associated
-        * keys in the "bad links" section of the link cache.
-        *
-        * - This is called from Article::doEdit() and Article::insertOn() to allow
-        * loading of the new page_id. It's also called from
-        * Article::doDeleteArticle()
-        *
-        * @param $newid Int the new Article ID
-        */
-       public function resetArticleID( $newid ) {
-               $linkCache = LinkCache::singleton();
-               $linkCache->clearLink( $this );
-
-               if ( $newid === false ) {
-                       $this->mArticleID = -1;
-               } else {
-                       $this->mArticleID = intval( $newid );
-               }
-               $this->mRestrictionsLoaded = false;
-               $this->mRestrictions = array();
-               $this->mRedirect = null;
-               $this->mLength = -1;
-               $this->mLatestID = false;
-               $this->mCounter = -1;
-       }
-
-       /**
-        * Updates page_touched for this page; called from LinksUpdate.php
-        *
-        * @return Bool true if the update succeded
-        */
-       public function invalidateCache() {
-               if ( wfReadOnly() ) {
-                       return false;
-               }
-               $dbw = wfGetDB( DB_MASTER );
-               $success = $dbw->update(
-                       'page',
-                       array( 'page_touched' => $dbw->timestamp() ),
-                       $this->pageCond(),
-                       __METHOD__
-               );
-               HTMLFileCache::clearFileCache( $this );
-               return $success;
-       }
-
-       /**
-        * Prefix some arbitrary text with the namespace or interwiki prefix
-        * of this object
+        * What is the page_latest field for this page?
         *
-        * @param $name String the text
-        * @return String the prefixed text
-        * @private
+        * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @return Int or 0 if the page doesn't exist
         */
-       private function prefix( $name ) {
-               $p = '';
-               if ( $this->mInterwiki != '' ) {
-                       $p = $this->mInterwiki . ':';
+       public function getLatestRevID( $flags = 0 ) {
+               if ( $this->mLatestID !== false ) {
+                       return intval( $this->mLatestID );
                }
-
-               if ( 0 != $this->mNamespace ) {
-                       $p .= $this->getNsText() . ':';
+               # Calling getArticleID() loads the field from cache as needed
+               if ( !$this->getArticleID( $flags ) ) {
+                       return $this->mLatestID = 0;
                }
-               return $p . $name;
+               $linkCache = LinkCache::singleton();
+               $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
+
+               return $this->mLatestID;
        }
 
        /**
-        * Returns a simple regex that will match on characters and sequences invalid in titles.
-        * 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.
+        * This clears some fields in this object, and clears any associated
+        * keys in the "bad links" section of the link cache.
         *
-        * @return String regex string
+        * - This is called from Article::doEdit() and Article::insertOn() to allow
+        * loading of the new page_id. It's also called from
+        * Article::doDeleteArticle()
+        *
+        * @param $newid Int the new Article ID
         */
-       static function getTitleInvalidRegex() {
-               static $rxTc = false;
-               if ( !$rxTc ) {
-                       # Matching titles will be held as illegal.
-                       $rxTc = '/' .
-                               # Any character not allowed is forbidden...
-                               '[^' . Title::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';
-               }
+       public function resetArticleID( $newid ) {
+               $linkCache = LinkCache::singleton();
+               $linkCache->clearLink( $this );
 
-               return $rxTc;
+               if ( $newid === false ) {
+                       $this->mArticleID = -1;
+               } else {
+                       $this->mArticleID = intval( $newid );
+               }
+               $this->mRestrictionsLoaded = false;
+               $this->mRestrictions = array();
+               $this->mRedirect = null;
+               $this->mLength = -1;
+               $this->mLatestID = false;
+               $this->mCounter = -1;
        }
 
        /**
@@ -2938,44 +3094,6 @@ class Title {
                return true;
        }
 
-       /**
-        * Set the fragment for this title. Removes the first character from the
-        * specified fragment before setting, so it assumes you're passing it with
-        * an initial "#".
-        *
-        * Deprecated for public use, use Title::makeTitle() with fragment parameter.
-        * Still in active use privately.
-        *
-        * @param $fragment String text
-        */
-       public function setFragment( $fragment ) {
-               $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
-       }
-
-       /**
-        * Get a Title object associated with the talk page of this article
-        *
-        * @return Title the object for the talk page
-        */
-       public function getTalkPage() {
-               return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
-       }
-
-       /**
-        * Get a title object associated with the subject page of this
-        * talk page
-        *
-        * @return Title the object for the subject page
-        */
-       public function getSubjectPage() {
-               // Is this the same title?
-               $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
-               if ( $this->getNamespace() == $subjectNS ) {
-                       return $this;
-               }
-               return Title::makeTitle( $subjectNS, $this->getDBkey() );
-       }
-
        /**
         * Get an array of Title objects linking to this Title
         * Also stores the IDs in the link cache.
@@ -3713,15 +3831,6 @@ class Title {
                return true;
        }
 
-       /**
-        * Can this title be added to a user's watchlist?
-        *
-        * @return Bool TRUE or FALSE
-        */
-       public function isWatchable() {
-               return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
-       }
-
        /**
         * Get categories to which this Title belongs and return an array of
         * categories' names.
@@ -3967,31 +4076,6 @@ class Title {
                        && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
        }
 
-       /**
-        * Callback for usort() to do title sorts by (namespace, title)
-        *
-        * @param $a Title
-        * @param $b Title
-        *
-        * @return Integer: result of string comparison, or namespace comparison
-        */
-       public static function compare( $a, $b ) {
-               if ( $a->getNamespace() == $b->getNamespace() ) {
-                       return strcmp( $a->getText(), $b->getText() );
-               } else {
-                       return $a->getNamespace() - $b->getNamespace();
-               }
-       }
-
-       /**
-        * Return a string representation of this title
-        *
-        * @return String representation of this title
-        */
-       public function __toString() {
-               return $this->getPrefixedText();
-       }
-
        /**
         * Check if page exists.  For historical reasons, this function simply
         * checks for the existence of the title in the page table, and will
@@ -4103,13 +4187,23 @@ class Title {
        }
 
        /**
-        * Is this in a namespace that allows actual pages?
+        * Updates page_touched for this page; called from LinksUpdate.php
         *
-        * @return Bool
-        * @internal note -- uses hardcoded namespace index instead of constants
+        * @return Bool true if the update succeded
         */
-       public function canExist() {
-               return $this->mNamespace >= 0 && $this->mNamespace != NS_MEDIA;
+       public function invalidateCache() {
+               if ( wfReadOnly() ) {
+                       return false;
+               }
+               $dbw = wfGetDB( DB_MASTER );
+               $success = $dbw->update(
+                       'page',
+                       array( 'page_touched' => $dbw->timestamp() ),
+                       $this->pageCond(),
+                       __METHOD__
+               );
+               HTMLFileCache::clearFileCache( $this );
+               return $success;
        }
 
        /**
@@ -4207,61 +4301,6 @@ class Title {
                return $prepend . $namespaceKey;
        }
 
-       /**
-        * Returns true if this is a special page.
-        *
-        * @return boolean
-        */
-       public function isSpecialPage() {
-               return $this->getNamespace() == NS_SPECIAL;
-       }
-
-       /**
-        * Returns true if this title resolves to the named special page
-        *
-        * @param $name String The special page name
-        * @return boolean
-        */
-       public function isSpecial( $name ) {
-               if ( $this->isSpecialPage() ) {
-                       list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
-                       if ( $name == $thisName ) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * If the Title refers to a special page alias which is not the local default, resolve
-        * the alias, and localise the name as necessary.  Otherwise, return $this
-        *
-        * @return Title
-        */
-       public function fixSpecialName() {
-               if ( $this->isSpecialPage() ) {
-                       list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
-                       if ( $canonicalName ) {
-                               $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
-                               if ( $localName != $this->mDbkeyform ) {
-                                       return Title::makeTitle( NS_SPECIAL, $localName );
-                               }
-                       }
-               }
-               return $this;
-       }
-
-       /**
-        * Is this Title in a namespace which contains content?
-        * In other words, is this a content page, for the purposes of calculating
-        * statistics, etc?
-        *
-        * @return Boolean
-        */
-       public function isContentPage() {
-               return MWNamespace::isContent( $this->getNamespace() );
-       }
-
        /**
         * Get all extant redirects to this Title
         *
@@ -4344,50 +4383,6 @@ class Title {
 
        }
 
-       /**
-        * Returns restriction types for the current Title
-        *
-        * @return array applicable restriction types
-        */
-       public function getRestrictionTypes() {
-               if ( $this->isSpecialPage() ) {
-                       return array();
-               }
-
-               $types = self::getFilteredRestrictionTypes( $this->exists() );
-
-               if ( $this->getNamespace() != NS_FILE ) {
-                       # Remove the upload restriction for non-file titles
-                       $types = array_diff( $types, array( 'upload' ) );
-               }
-
-               wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
-
-               wfDebug( __METHOD__ . ': applicable restrictions to [[' .
-                       $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
-
-               return $types;
-       }
-       /**
-        * Get a filtered list of all restriction types supported by this wiki.
-        * @param bool $exists True to get all restriction types that apply to
-        * titles that do exist, False for all restriction types that apply to
-        * titles that do not exist
-        * @return array
-        */
-       public static function getFilteredRestrictionTypes( $exists = true ) {
-               global $wgRestrictionTypes;
-               $types = $wgRestrictionTypes;
-               if ( $exists ) {
-                       # Remove the create restriction for existing titles
-                       $types = array_diff( $types, array( 'create' ) );
-               } else {
-                       # Only the create and upload restrictions apply to non-existing titles
-                       $types = array_intersect( $types, array( 'create', 'upload' ) );
-               }
-               return $types;
-       }
-
        /**
         * Returns the raw sort key to be used for categories, with the specified
         * prefix.  This will be fed to Collation::getSortKey() to get a