Merge "Title: Simplify use of MWNamespace::getCanonicalName()"
[lhc/web/wiklou.git] / includes / Title.php
index 1be9863..b771477 100644 (file)
@@ -625,20 +625,6 @@ class Title implements LinkTarget {
                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.
-        *
-        * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead
-        *
-        * @return string Regex string
-        */
-       static function getTitleInvalidRegex() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return MediaWikiTitleCodec::getTitleInvalidRegex();
-       }
-
        /**
         * Utility method for converting a character sequence from bytes to Unicode.
         *
@@ -1041,12 +1027,11 @@ class Title implements LinkTarget {
         */
        public function getNsText() {
                if ( $this->isExternal() ) {
-                       // This probably shouldn't even happen,
-                       // but for interwiki transclusion it sometimes does.
-                       // Use the canonical namespaces if possible to try to
-                       // resolve a foreign namespace.
-                       if ( MWNamespace::exists( $this->mNamespace ) ) {
-                               return MWNamespace::getCanonicalName( $this->mNamespace );
+                       // This probably shouldn't even happen, except for interwiki transclusion.
+                       // If possible, use the canonical name for the foreign namespace.
+                       $nsText = MWNamespace::getCanonicalName( $this->mNamespace );
+                       if ( $nsText !== false ) {
+                               return $nsText;
                        }
                }
 
@@ -1292,71 +1277,153 @@ class Title implements LinkTarget {
        }
 
        /**
-        * Could this page contain custom CSS or JavaScript for the global UI.
-        * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS
-        * or CONTENT_MODEL_JAVASCRIPT.
+        * Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the
+        * global UI. This is generally true for pages in the MediaWiki namespace having
+        * CONTENT_MODEL_CSS, CONTENT_MODEL_JSON, or CONTENT_MODEL_JAVASCRIPT.
         *
-        * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage()
+        * This method does *not* return true for per-user JS/JSON/CSS. Use isUserConfigPage()
         * for that!
         *
-        * Note that this method should not return true for pages that contain and
-        * show "inactive" CSS or JS.
+        * Note that this method should not return true for pages that contain and show
+        * "inactive" CSS, JSON, or JS.
         *
         * @return bool
-        * @todo FIXME: Rename to isSiteConfigPage() and remove deprecated hook
+        * @since 1.31
+        */
+       public function isSiteConfigPage() {
+               return (
+                       NS_MEDIAWIKI == $this->mNamespace
+                       && (
+                               $this->hasContentModel( CONTENT_MODEL_CSS )
+                               || $this->hasContentModel( CONTENT_MODEL_JSON )
+                               || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
+                       )
+               );
+       }
+
+       /**
+        * @return bool
+        * @deprecated Since 1.31; use ::isSiteConfigPage() instead (which also checks for JSON pages)
         */
        public function isCssOrJsPage() {
-               $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
-                       && ( $this->hasContentModel( CONTENT_MODEL_CSS )
-                               || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
+               wfDeprecated( __METHOD__, '1.31' );
+               return ( NS_MEDIAWIKI == $this->mNamespace
+                               && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+                                       || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
+       }
 
-               return $isCssOrJsPage;
+       /**
+        * Is this a "config" (.css, .json, or .js) sub-page of a user page?
+        *
+        * @return bool
+        * @since 1.31
+        */
+       public function isUserConfigPage() {
+               return (
+                       NS_USER == $this->mNamespace
+                       && $this->isSubpage()
+                       && (
+                               $this->hasContentModel( CONTENT_MODEL_CSS )
+                               || $this->hasContentModel( CONTENT_MODEL_JSON )
+                               || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
+                       )
+               );
        }
 
        /**
-        * Is this a .css or .js subpage of a user page?
         * @return bool
-        * @todo FIXME: Rename to isUserConfigPage()
+        * @deprecated Since 1.31; use ::isUserConfigPage() instead (which also checks for JSON pages)
         */
        public function isCssJsSubpage() {
+               wfDeprecated( __METHOD__, '1.31' );
                return ( NS_USER == $this->mNamespace && $this->isSubpage()
                                && ( $this->hasContentModel( CONTENT_MODEL_CSS )
                                        || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
        }
 
        /**
-        * Trim down a .css or .js subpage title to get the corresponding skin name
+        * Trim down a .css, .json, or .js subpage title to get the corresponding skin name
         *
-        * @return string Containing skin name from .css or .js subpage title
+        * @return string Containing skin name from .css, .json, or .js subpage title
+        * @since 1.31
         */
-       public function getSkinFromCssJsSubpage() {
+       public function getSkinFromConfigSubpage() {
                $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 $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
                }
                return substr( $subpage, 0, $lastdot );
        }
 
        /**
-        * Is this a .css subpage of a user page?
+        * @deprecated Since 1.31; use ::getSkinFromConfigSubpage() instead
+        * @return string Containing skin name from .css, .json, or .js subpage title
+        */
+       public function getSkinFromCssJsSubpage() {
+               wfDeprecated( __METHOD__, '1.31' );
+               return $this->getSkinFromConfigSubpage();
+       }
+
+       /**
+        * Is this a CSS "config" sub-page of a user page?
         *
         * @return bool
+        * @since 1.31
+        */
+       public function isUserCssConfigPage() {
+               return (
+                       NS_USER == $this->mNamespace
+                       && $this->isSubpage()
+                       && $this->hasContentModel( CONTENT_MODEL_CSS )
+               );
+       }
+
+       /**
+        * @deprecated Since 1.31; use ::isUserCssConfigPage()
+        * @return bool
         */
        public function isCssSubpage() {
-               return ( NS_USER == $this->mNamespace && $this->isSubpage()
-                       && $this->hasContentModel( CONTENT_MODEL_CSS ) );
+               wfDeprecated( __METHOD__, '1.31' );
+               return $this->isUserCssConfigPage();
        }
 
        /**
-        * Is this a .js subpage of a user page?
+        * Is this a JSON "config" sub-page of a user page?
         *
         * @return bool
+        * @since 1.31
+        */
+       public function isUserJsonConfigPage() {
+               return (
+                       NS_USER == $this->mNamespace
+                       && $this->isSubpage()
+                       && $this->hasContentModel( CONTENT_MODEL_JSON )
+               );
+       }
+
+       /**
+        * Is this a JS "config" sub-page of a user page?
+        *
+        * @return bool
+        * @since 1.31
+        */
+       public function isUserJsConfigPage() {
+               return (
+                       NS_USER == $this->mNamespace
+                       && $this->isSubpage()
+                       && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
+               );
+       }
+
+       /**
+        * @deprecated Since 1.31; use ::isUserJsConfigPage()
+        * @return bool
         */
        public function isJsSubpage() {
-               return ( NS_USER == $this->mNamespace && $this->isSubpage()
-                       && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
+               wfDeprecated( __METHOD__, '1.31' );
+               return $this->isUserJsConfigPage();
        }
 
        /**
@@ -2250,7 +2317,7 @@ class Title implements LinkTarget {
        }
 
        /**
-        * Check CSS/JS sub-page permissions
+        * Check CSS/JSON/JS sub-page permissions
         *
         * @param string $action The action to check
         * @param User $user User to check
@@ -2260,20 +2327,43 @@ class Title implements LinkTarget {
         *
         * @return array List of errors
         */
-       private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
-               # Protect css/js subpages of user pages
+       private function checkUserConfigPermissions( $action, $user, $errors, $rigor, $short ) {
+               # Protect css/json/js subpages of user pages
                # XXX: this might be better using restrictions
+
                if ( $action != 'patrol' ) {
                        if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
-                               if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
+                               if (
+                                       $this->isUserCssConfigPage()
+                                       && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
+                               ) {
                                        $errors[] = [ 'mycustomcssprotected', $action ];
-                               } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
+                               } elseif (
+                                       $this->isUserJsonConfigPage()
+                                       && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
+                               ) {
+                                       $errors[] = [ 'mycustomjsonprotected', $action ];
+                               } elseif (
+                                       $this->isUserJsConfigPage()
+                                       && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
+                               ) {
                                        $errors[] = [ 'mycustomjsprotected', $action ];
                                }
                        } else {
-                               if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
+                               if (
+                                       $this->isUserCssConfigPage()
+                                       && !$user->isAllowed( 'editusercss' )
+                               ) {
                                        $errors[] = [ 'customcssprotected', $action ];
-                               } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
+                               } elseif (
+                                       $this->isUserJsonConfigPage()
+                                       && !$user->isAllowed( 'edituserjson' )
+                               ) {
+                                       $errors[] = [ 'customjsonprotected', $action ];
+                               } elseif (
+                                       $this->isUserJsConfigPage()
+                                       && !$user->isAllowed( 'edituserjs' )
+                               ) {
                                        $errors[] = [ 'customjsprotected', $action ];
                                }
                        }
@@ -2330,7 +2420,7 @@ class Title implements LinkTarget {
         * @return array List of errors
         */
        private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
-               if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
+               if ( $rigor !== 'quick' && !$this->isUserConfigPage() ) {
                        # 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
@@ -2611,7 +2701,7 @@ class Title implements LinkTarget {
                                'checkReadPermissions',
                                'checkUserBlock', // for wgBlockDisablesLogin
                        ];
-               # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
+               # Don't call checkSpecialsAndNSPermissions or checkUserConfigPermissions
                # here as it will lead to duplicate error messages. This is okay to do
                # since anywhere that checks for create will also check for edit, and
                # those checks are called for edit.
@@ -2629,7 +2719,7 @@ class Title implements LinkTarget {
                                'checkQuickPermissions',
                                'checkPermissionHooks',
                                'checkSpecialsAndNSPermissions',
-                               'checkCSSandJSPermissions',
+                               'checkUserConfigPermissions',
                                'checkPageRestrictions',
                                'checkCascadingSourcesRestrictions',
                                'checkActionPermissions',
@@ -3743,9 +3833,11 @@ class Title implements LinkTarget {
                }
 
                // If we are looking at a css/js user subpage, purge the action=raw.
-               if ( $this->isJsSubpage() ) {
+               if ( $this->isUserJsConfigPage() ) {
                        $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
-               } elseif ( $this->isCssSubpage() ) {
+               } elseif ( $this->isUserJsonConfigPage() ) {
+                       $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
+               } elseif ( $this->isUserCssConfigPage() ) {
                        $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
                }
 
@@ -4389,17 +4481,18 @@ class Title implements LinkTarget {
                        return $authors;
                }
                $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
+               $revQuery = Revision::getQueryInfo();
+               $authors = $dbr->selectFieldValues(
+                       $revQuery['tables'],
+                       $revQuery['fields']['rev_user_text'],
                        [
                                'rev_page' => $this->getArticleID(),
                                "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
                                "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
                        ], __METHOD__,
-                       [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
+                       [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
+                       $revQuery['joins']
                );
-               foreach ( $res as $row ) {
-                       $authors[] = $row->rev_user_text;
-               }
                return $authors;
        }
 
@@ -4701,14 +4794,12 @@ class Title implements LinkTarget {
         */
        public function getNamespaceKey( $prepend = 'nstab-' ) {
                global $wgContLang;
-               // Gets the subject namespace if this title
-               $namespace = MWNamespace::getSubject( $this->getNamespace() );
-               // Checks if canonical namespace name exists for namespace
-               if ( MWNamespace::exists( $this->getNamespace() ) ) {
-                       // Uses canonical namespace name
-                       $namespaceKey = MWNamespace::getCanonicalName( $namespace );
-               } else {
-                       // Uses text of namespace
+               // Gets the subject namespace of this title
+               $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
+               // Prefer canonical namespace name for HTML IDs
+               $namespaceKey = MWNamespace::getCanonicalName( $subjectNS );
+               if ( $namespaceKey === false ) {
+                       // Fallback to localised text
                        $namespaceKey = $this->getSubjectNsText();
                }
                // Makes namespace key lowercase