Merge "[MCR] Allow extensions to manipulate service instances"
[lhc/web/wiklou.git] / includes / Title.php
index f6e5912..cc7d722 100644 (file)
@@ -37,7 +37,7 @@ use MediaWiki\MediaWikiServices;
  *       and does not rely on global state or the database.
  */
 class Title implements LinkTarget {
-       /** @var HashBagOStuff */
+       /** @var MapCacheLRU */
        static private $titleCache = null;
 
        /**
@@ -306,6 +306,10 @@ class Title implements LinkTarget {
        public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
                if ( is_object( $text ) ) {
                        throw new MWException( '$text must be a string, given an object' );
+               } elseif ( $text === null ) {
+                       // Legacy code relies on MalformedTitleException being thrown in this case
+                       // (happens when URL with no title in it is parsed). TODO fix
+                       throw new MalformedTitleException( 'title-invalid-empty' );
                }
 
                $titleCache = self::getTitleCache();
@@ -371,11 +375,11 @@ class Title implements LinkTarget {
        }
 
        /**
-        * @return HashBagOStuff
+        * @return MapCacheLRU
         */
        private static function getTitleCache() {
                if ( self::$titleCache == null ) {
-                       self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
+                       self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
                }
                return self::$titleCache;
        }
@@ -1289,12 +1293,9 @@ class Title implements LinkTarget {
         */
        public function isSiteConfigPage() {
                return (
-                       NS_MEDIAWIKI == $this->mNamespace
-                       && (
-                               $this->hasContentModel( CONTENT_MODEL_CSS )
-                               || $this->hasContentModel( CONTENT_MODEL_JSON )
-                               || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
-                       )
+                       $this->isSiteCssConfigPage()
+                       || $this->isSiteJsonConfigPage()
+                       || $this->isSiteJsConfigPage()
                );
        }
 
@@ -1317,13 +1318,9 @@ class Title implements LinkTarget {
         */
        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 )
-                       )
+                       $this->isUserCssConfigPage()
+                       || $this->isUserJsonConfigPage()
+                       || $this->isUserJsConfigPage()
                );
        }
 
@@ -1423,6 +1420,60 @@ class Title implements LinkTarget {
                return $this->isUserJsConfigPage();
        }
 
+       /**
+        * Is this a sitewide CSS "config" page?
+        *
+        * @return bool
+        * @since 1.32
+        */
+       public function isSiteCssConfigPage() {
+               return (
+                       NS_MEDIAWIKI == $this->mNamespace
+                       && (
+                               $this->hasContentModel( CONTENT_MODEL_CSS )
+                               // paranoia - a MediaWiki: namespace page with mismatching extension and content
+                               // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
+                               || substr( $this->getDBkey(), -4 ) === '.css'
+                       )
+               );
+       }
+
+       /**
+        * Is this a sitewide JSON "config" page?
+        *
+        * @return bool
+        * @since 1.32
+        */
+       public function isSiteJsonConfigPage() {
+               return (
+                       NS_MEDIAWIKI == $this->mNamespace
+                       && (
+                               $this->hasContentModel( CONTENT_MODEL_JSON )
+                               // paranoia - a MediaWiki: namespace page with mismatching extension and content
+                               // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
+                               || substr( $this->getDBkey(), -5 ) === '.json'
+                       )
+               );
+       }
+
+       /**
+        * Is this a sitewide JS "config" page?
+        *
+        * @return bool
+        * @since 1.31
+        */
+       public function isSiteJsConfigPage() {
+               return (
+                       NS_MEDIAWIKI == $this->mNamespace
+                       && (
+                               $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
+                               // paranoia - a MediaWiki: namespace page with mismatching extension and content
+                               // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
+                               || substr( $this->getDBkey(), -3 ) === '.js'
+                       )
+               );
+       }
+
        /**
         * Is this a talk page of some sort?
         *
@@ -2086,7 +2137,7 @@ class Title implements LinkTarget {
         * May provide false positives, but should never provide a false negative.
         *
         * @param string $action Action that permission needs to be checked for
-        * @param User $user User to check (since 1.19); $wgUser will be used if not provided.
+        * @param User|null $user User to check (since 1.19); $wgUser will be used if not provided.
         * @return bool
         */
        public function quickUserCan( $action, $user = null ) {
@@ -2097,7 +2148,7 @@ class Title implements LinkTarget {
         * Can $user perform $action on this page?
         *
         * @param string $action Action that permission needs to be checked for
-        * @param User $user User to check (since 1.19); $wgUser will be used if not
+        * @param User|null $user User to check (since 1.19); $wgUser will be used if not
         *   provided.
         * @param string $rigor Same format as Title::getUserPermissionsErrors()
         * @return bool
@@ -2313,6 +2364,44 @@ class Title implements LinkTarget {
                return $errors;
        }
 
+       /**
+        * Check sitewide CSS/JSON/JS permissions
+        *
+        * @param string $action The action to check
+        * @param User $user User to check
+        * @param array $errors List of current errors
+        * @param string $rigor Same format as Title::getUserPermissionsErrors()
+        * @param bool $short Short circuit on first error
+        *
+        * @return array List of errors
+        */
+       private function checkSiteConfigPermissions( $action, $user, $errors, $rigor, $short ) {
+               if ( $action != 'patrol' ) {
+                       $error = null;
+                       // Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the
+                       // editinterface right. That's implemented as a restriction so no check needed here.
+                       if ( $this->isSiteCssConfigPage() && !$user->isAllowed( 'editsitecss' ) ) {
+                               $error = [ 'sitecssprotected', $action ];
+                       } elseif ( $this->isSiteJsonConfigPage() && !$user->isAllowed( 'editsitejson' ) ) {
+                               $error = [ 'sitejsonprotected', $action ];
+                       } elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
+                               $error = [ 'sitejsprotected', $action ];
+                       }
+
+                       if ( $error ) {
+                               if ( $user->isAllowed( 'editinterface' ) ) {
+                                       // Most users / site admins will probably find out about the new, more restrictive
+                                       // permissions by failing to edit something. Give them more info.
+                                       // TODO remove this a few release cycles after 1.32
+                                       $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
+                               }
+                               $errors[] = $error;
+                       }
+               }
+
+               return $errors;
+       }
+
        /**
         * Check CSS/JSON/JS sub-page permissions
         *
@@ -2701,10 +2790,10 @@ class Title implements LinkTarget {
                                'checkReadPermissions',
                                'checkUserBlock', // for wgBlockDisablesLogin
                        ];
-               # 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.
+               # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
+               # 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.
                } elseif ( $action == 'create' ) {
                        $checks = [
                                'checkQuickPermissions',
@@ -2719,6 +2808,7 @@ class Title implements LinkTarget {
                                'checkQuickPermissions',
                                'checkPermissionHooks',
                                'checkSpecialsAndNSPermissions',
+                               'checkSiteConfigPermissions',
                                'checkUserConfigPermissions',
                                'checkPageRestrictions',
                                'checkCascadingSourcesRestrictions',
@@ -3139,7 +3229,7 @@ class Title implements LinkTarget {
         * Public for usage by LiquidThreads.
         *
         * @param array $rows Array of db result objects
-        * @param string $oldFashionedRestrictions Comma-separated set of permission keys
+        * @param string|null $oldFashionedRestrictions Comma-separated set of permission keys
         * indicating who can move or edit the page from the page table, (pre 1.10) rows.
         * Edit and move sections are separated by a colon
         * Example: "edit=autoconfirmed,sysop:move=sysop"
@@ -3211,7 +3301,7 @@ class Title implements LinkTarget {
        /**
         * Load restrictions from the page_restrictions table
         *
-        * @param string $oldFashionedRestrictions Comma-separated set of permission keys
+        * @param string|null $oldFashionedRestrictions Comma-separated set of permission keys
         * indicating who can move or edit the page from the page table, (pre 1.10) rows.
         * Edit and move sections are separated by a colon
         * Example: "edit=autoconfirmed,sysop:move=sysop"
@@ -4681,7 +4771,7 @@ class Title implements LinkTarget {
        /**
         * Updates page_touched for this page; called from LinksUpdate.php
         *
-        * @param string $purgeTime [optional] TS_MW timestamp
+        * @param string|null $purgeTime [optional] TS_MW timestamp
         * @return bool True if the update succeeded
         */
        public function invalidateCache( $purgeTime = null ) {
@@ -4752,7 +4842,7 @@ class Title implements LinkTarget {
        /**
         * Get the timestamp when this page was updated since the user last saw it.
         *
-        * @param User $user
+        * @param User|null $user
         * @return string|null
         */
        public function getNotificationTimestamp( $user = null ) {