Merge "Title: Title::getSubpage should not lose the interwiki prefix"
[lhc/web/wiklou.git] / includes / Title.php
index f69f1a4..28bec0b 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 use MediaWiki\Permissions\PermissionManager;
+use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\Linker\LinkTarget;
@@ -851,7 +852,10 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Returns true if the title is valid, false if it is invalid.
         *
-        * Valid titles can be round-tripped via makeTitleSafe() and newFromText().
+        * Valid titles can be round-tripped via makeTitle() and newFromText().
+        * Their DB key can be used in the database, though it may not have the correct
+        * capitalization.
+        *
         * Invalid titles may get returned from makeTitle(), and it may be useful to
         * allow them to exist, e.g. in order to process log entries about pages in
         * namespaces that belong to extensions that are no longer installed.
@@ -870,10 +874,23 @@ class Title implements LinkTarget, IDBAccessObject {
 
                try {
                        $services->getTitleParser()->parseTitle( $this->mDbkeyform, $this->mNamespace );
-                       return true;
                } catch ( MalformedTitleException $ex ) {
                        return false;
                }
+
+               try {
+                       // Title value applies basic syntax checks. Should perhaps be moved elsewhere.
+                       new TitleValue(
+                               $this->mNamespace,
+                               $this->mDbkeyform,
+                               $this->mFragment,
+                               $this->mInterwiki
+                       );
+               } catch ( InvalidArgumentException $ex ) {
+                       return false;
+               }
+
+               return true;
        }
 
        /**
@@ -1128,14 +1145,16 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Can this title have a corresponding talk page?
         *
-        * @see NamespaceInfo::hasTalkNamespace
+        * False for relative section links (with getText() === ''),
+        * interwiki links (with getInterwiki() !== ''), and pages in NS_SPECIAL.
+        *
+        * @see NamespaceInfo::canHaveTalkPage
         * @since 1.30
         *
         * @return bool True if this title either is a talk page or can have a talk page associated.
         */
        public function canHaveTalkPage() {
-               return MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       hasTalkNamespace( $this->mNamespace );
+               return MediaWikiServices::getInstance()->getNamespaceInfo()->canHaveTalkPage( $this );
        }
 
        /**
@@ -1150,11 +1169,15 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Can this title be added to a user's watchlist?
         *
+        * False for relative section links (with getText() === ''),
+        * interwiki links (with getInterwiki() !== ''), and pages in NS_SPECIAL.
+        *
         * @return bool
         */
        public function isWatchable() {
-               return !$this->isExternal() && MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       isWatchable( $this->mNamespace );
+               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               return $this->getText() !== '' && !$this->isExternal() &&
+                       $nsInfo->isWatchable( $this->mNamespace );
        }
 
        /**
@@ -1515,8 +1538,11 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get a Title object associated with the talk page of this article
         *
-        * @deprecated since 1.34, use NamespaceInfo::getTalkPage
+        * @deprecated since 1.34, use getTalkPageIfDefined() or NamespaceInfo::getTalkPage()
+        *             with NamespaceInfo::canHaveTalkPage().
         * @return Title The object for the talk page
+        * @throws MWException if $target doesn't have talk pages, e.g. because it's in NS_SPECIAL
+        *         or because it's a relative link, or an interwiki link.
         */
        public function getTalkPage() {
                return self::castFromLinkTarget(
@@ -1728,6 +1754,9 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get the root page name text without a namespace, i.e. the leftmost part before any slashes
         *
+        * @note the return value may contain trailing whitespace and is thus
+        * not safe for use with makeTitle or TitleValue.
+        *
         * @par Example:
         * @code
         * Title::newFromText('User:Foo/Bar/Baz')->getRootText();
@@ -1761,12 +1790,20 @@ class Title implements LinkTarget, IDBAccessObject {
         * @since 1.20
         */
        public function getRootTitle() {
-               return self::makeTitle( $this->mNamespace, $this->getRootText() );
+               $title = self::makeTitleSafe( $this->mNamespace, $this->getRootText() );
+               Assert::postcondition(
+                       $title !== null,
+                       'makeTitleSafe() should always return a Title for the text returned by getRootText().'
+               );
+               return $title;
        }
 
        /**
         * Get the base page name without a namespace, i.e. the part before the subpage name
         *
+        * @note the return value may contain trailing whitespace and is thus
+        * not safe for use with makeTitle or TitleValue.
+        *
         * @par Example:
         * @code
         * Title::newFromText('User:Foo/Bar/Baz')->getBaseText();
@@ -1794,7 +1831,7 @@ class Title implements LinkTarget, IDBAccessObject {
        }
 
        /**
-        * Get the base page name title, i.e. the part before the subpage name
+        * Get the base page name title, i.e. the part before the subpage name.
         *
         * @par Example:
         * @code
@@ -1806,7 +1843,12 @@ class Title implements LinkTarget, IDBAccessObject {
         * @since 1.20
         */
        public function getBaseTitle() {
-               return self::makeTitle( $this->mNamespace, $this->getBaseText() );
+               $title = self::makeTitleSafe( $this->mNamespace, $this->getBaseText() );
+               Assert::postcondition(
+                       $title !== null,
+                       'makeTitleSafe() should always return a Title for the text returned by getBaseText().'
+               );
+               return $title;
        }
 
        /**
@@ -1845,7 +1887,12 @@ class Title implements LinkTarget, IDBAccessObject {
         * @since 1.20
         */
        public function getSubpage( $text ) {
-               return self::makeTitleSafe( $this->mNamespace, $this->getText() . '/' . $text );
+               return self::makeTitleSafe(
+                       $this->mNamespace,
+                       $this->getText() . '/' . $text,
+                       '',
+                       $this->mInterwiki
+               );
        }
 
        /**
@@ -2252,34 +2299,6 @@ class Title implements LinkTarget, IDBAccessObject {
                        ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors );
        }
 
-       /**
-        * Add the resulting error code to the errors array
-        *
-        * @param array $errors List of current errors
-        * @param array|string|MessageSpecifier|false $result Result of errors
-        *
-        * @return array List of errors
-        */
-       private function resultToError( $errors, $result ) {
-               if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
-                       // A single array representing an error
-                       $errors[] = $result;
-               } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
-                       // A nested array representing multiple errors
-                       $errors = array_merge( $errors, $result );
-               } elseif ( $result !== '' && is_string( $result ) ) {
-                       // A string representing a message-id
-                       $errors[] = [ $result ];
-               } elseif ( $result instanceof MessageSpecifier ) {
-                       // A message specifier representing an error
-                       $errors[] = [ $result ];
-               } elseif ( $result === false ) {
-                       // a generic "We don't want them to do that"
-                       $errors[] = [ 'badaccess-group0' ];
-               }
-               return $errors;
-       }
-
        /**
         * Get a filtered list of all restriction types supported by this wiki.
         * @param bool $exists True to get all restriction types that apply to
@@ -2907,7 +2926,7 @@ class Title implements LinkTarget, IDBAccessObject {
                        $this->mHasSubpages = false;
                        $subpages = $this->getSubpages( 1 );
                        if ( $subpages instanceof TitleArray ) {
-                               $this->mHasSubpages = (bool)$subpages->count();
+                               $this->mHasSubpages = (bool)$subpages->current();
                        }
                }
 
@@ -4248,7 +4267,7 @@ class Title implements LinkTarget, IDBAccessObject {
         * Get the timestamp when this page was updated since the user last saw it.
         *
         * @param User|null $user
-        * @return string|null
+        * @return string|bool|null String timestamp, false if not watched, null if nothing is unseen
         */
        public function getNotificationTimestamp( $user = null ) {
                global $wgUser;