Merge "Segregate right to edit sitewide CSS/JS"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 26 Jul 2018 22:26:11 +0000 (22:26 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 26 Jul 2018 22:26:11 +0000 (22:26 +0000)
1  2 
RELEASE-NOTES-1.32
includes/DefaultSettings.php
includes/Title.php
includes/installer/Installer.php
languages/i18n/en.json
languages/i18n/qqq.json

diff --combined RELEASE-NOTES-1.32
@@@ -29,6 -29,14 +29,14 @@@ production
  * The archive table's ar_rev_id field is now unique.
  * Special:BotPasswords now requires reauthentication.
  * (T194414) The default watchlist view time has been increased from 3 to 7 days.
+ * The right to edit sitewide Javascript (e.g. MediaWiki:Common.js), CSS or JSON
+   was separated from 'editinterface' and is available under
+   'editsitejs'/'editsitecss'/'editsitejson'. Having 'editinterface' is still
+   necessary to edit such pages.
+ * A new user group, 'interface-admin', is added for controlling access to
+   sitewide CSS/JS (and editing other users' CSS/JS). No other group has
+   'editsitecss', 'editusercss', 'editsitejs' or 'edituserjs' by default.
+ * A new grant group, 'editsiteconfig', is added for granting the above rights.
  
  === New features in 1.32 ===
  * (T112474) Generalized the ResourceLoader mechanism for overriding modules
@@@ -65,8 -73,6 +73,8 @@@
  * Updated wikimedia/scoped-callback from 1.0.0 to 2.0.0.
  ** ScopedCallback objects can no longer be serialized.
  * Updated wikimedia/wrappedstring from 2.3.0 to 3.0.1.
 +* Updated mediawiki/mediawiki-codesniffer from v20.0.0 to v21.0.0.
 +* Updated composer/spdx-licenses from 1.3.0 to 1.4.0.
  
  ==== New external libraries ====
  * Added wikimedia/xmp-reader 0.5.1
  * (T198935) User list preferences such as `email-blacklist` and similar
    extension preferences are no longer represented as arrays when returned by
    action=query&meta=userinfo&uiprop=options.
 +* 'missingparam' errors will now use the prefixed parameter name in the code
 +  and error text, e.g. "noxxfoo" and "The 'xxfoo' parameter must be set" rather
 +  than "nofoo" and "The 'foo' parameter must be set".
  
  === Action API internal changes in 1.32 ===
  * Added 'ApiParseMakeOutputPage' hook.
@@@ -197,6 -200,9 +205,6 @@@ because of Phabricator reports
    CapsuleMultiselectWidget. The following methods may no longer be used:
    * setItemsFromData: Use setValue instead
    * getItemsData: Use getItems instead and get the data property
 -* LanguageCode::bcp47() now always returns a valid BCP 47 code.  This means
 -  that some MediaWiki-specific language codes, such as `simple`, are mapped
 -  into valid BCP 47 codes (eg `en-simple`).
  
  === Deprecations in 1.32 ===
  * Use of a StartProfiler.php file is deprecated in favour of placing
    'log-show-hide-[type]' format. Instead use 'logeventslist-[type]-log'.
  * Global functions  wfArrayFilter() and wfArrayFilterByKey() are deprecated.
    use array_filter() directly.
 +* The $wgShowSQLErrors global is deprecated and nonfunctional.
 +  Set $wgShowExceptionDetails and/or $wgShowHostnames instead.
 +* The $wgShowDBErrorBacktrace global is deprecated and nonfunctional.
 +  Set $wgShowExceptionDetails instead.
 +* Public access to the DifferenceEngine properties mOldid, mNewid, mOldPage,
 +  mNewPage, mOldContent, mNewContent, mRevisionsLoaded, mTextLoaded and
 +  mCacheHit is deprecated. Use getOldid() / getNewid() for the first two,
 +  do your own lookup for page/content. mNewRev / mOldRev remains public.
 +* The $wgExternalDiffEngine value 'wikidiff2' is deprecated. To use wikidiff2
 +  just enable the PHP extension, and it will be autodetected.
  
  === Other changes in 1.32 ===
  * (T198811) The following tables have had their UNIQUE indexes turned into
@@@ -4548,6 -4548,12 +4548,12 @@@ $wgPasswordPolicy = 
                        'PasswordCannotMatchUsername' => true,
                        'PasswordCannotBePopular' => 25,
                ],
+               'interface-admin' => [
+                       'MinimalPasswordLength' => 8,
+                       'MinimumPasswordLengthToLogin' => 1,
+                       'PasswordCannotMatchUsername' => true,
+                       'PasswordCannotBePopular' => 25,
+               ],
                'bot' => [
                        'MinimalPasswordLength' => 8,
                        'MinimumPasswordLengthToLogin' => 1,
@@@ -5217,9 -5223,8 +5223,8 @@@ $wgGroupPermissions['sysop']['deletedhi
  $wgGroupPermissions['sysop']['deletedtext'] = true;
  $wgGroupPermissions['sysop']['undelete'] = true;
  $wgGroupPermissions['sysop']['editinterface'] = true;
- $wgGroupPermissions['sysop']['editusercss'] = true;
+ $wgGroupPermissions['sysop']['editsitejson'] = true;
  $wgGroupPermissions['sysop']['edituserjson'] = true;
- $wgGroupPermissions['sysop']['edituserjs'] = true;
  $wgGroupPermissions['sysop']['import'] = true;
  $wgGroupPermissions['sysop']['importupload'] = true;
  $wgGroupPermissions['sysop']['move'] = true;
@@@ -5252,6 -5257,14 +5257,14 @@@ $wgGroupPermissions['sysop']['mergehist
  $wgGroupPermissions['sysop']['managechangetags'] = true;
  $wgGroupPermissions['sysop']['deletechangetags'] = true;
  
+ $wgGroupPermissions['interface-admin']['editinterface'] = true;
+ $wgGroupPermissions['interface-admin']['editsitecss'] = true;
+ $wgGroupPermissions['interface-admin']['editsitejson'] = true;
+ $wgGroupPermissions['interface-admin']['editsitejs'] = true;
+ $wgGroupPermissions['interface-admin']['editusercss'] = true;
+ $wgGroupPermissions['interface-admin']['edituserjson'] = true;
+ $wgGroupPermissions['interface-admin']['edituserjs'] = true;
  // Permission to change users' group assignments
  $wgGroupPermissions['bureaucrat']['userrights'] = true;
  $wgGroupPermissions['bureaucrat']['noratelimit'] = true;
@@@ -5858,9 -5871,14 +5871,14 @@@ $wgGrantPermissions['editmyoptions']['e
  
  $wgGrantPermissions['editinterface'] = $wgGrantPermissions['editpage'];
  $wgGrantPermissions['editinterface']['editinterface'] = true;
- $wgGrantPermissions['editinterface']['editusercss'] = true;
  $wgGrantPermissions['editinterface']['edituserjson'] = true;
- $wgGrantPermissions['editinterface']['edituserjs'] = true;
+ $wgGrantPermissions['editinterface']['editsitejson'] = true;
+ $wgGrantPermissions['editsiteconfig'] = $wgGrantPermissions['editinterface'];
+ $wgGrantPermissions['editsiteconfig']['editusercss'] = true;
+ $wgGrantPermissions['editsiteconfig']['edituserjs'] = true;
+ $wgGrantPermissions['editsiteconfig']['editsitecss'] = true;
+ $wgGrantPermissions['editsiteconfig']['editsitejs'] = true;
  
  $wgGrantPermissions['createeditmovepage'] = $wgGrantPermissions['editpage'];
  $wgGrantPermissions['createeditmovepage']['createpage'] = true;
@@@ -5939,6 -5957,7 +5957,7 @@@ $wgGrantPermissionGroups = 
        'editmyoptions'       => 'customization',
  
        'editinterface'       => 'administration',
+       'editsiteconfig'      => 'administration',
        'rollback'            => 'administration',
        'blockusers'          => 'administration',
        'delete'              => 'administration',
@@@ -6296,17 -6315,14 +6315,17 @@@ $wgSpecialVersionShowHooks = false
   * Whether to show "we're sorry, but there has been a database error" pages.
   * Displaying errors aids in debugging, but may display information useful
   * to an attacker.
 + *
 + * @deprecated and nonfunctional since 1.32: set $wgShowExceptionDetails and/or
 + * $wgShowHostnames instead.
   */
  $wgShowSQLErrors = false;
  
  /**
 - * If set to true, uncaught exceptions will print a complete stack trace
 - * to output. This should only be used for debugging, as it may reveal
 - * private information in function parameters due to PHP's backtrace
 - * formatting.
 + * If set to true, uncaught exceptions will print the exception message and a
 + * complete stack trace to output. This should only be used for debugging, as it
 + * may reveal private information in function parameters due to PHP's backtrace
 + * formatting.  If set to false, only the exception's class will be shown.
   */
  $wgShowExceptionDetails = false;
  
   * reported in the normal manner. $wgShowExceptionDetails applies in other cases,
   * including those in which an uncaught exception is thrown from within the
   * exception handler.
 + *
 + * @deprecated and nonfunctional since 1.32: set $wgShowExceptionDetails instead.
   */
  $wgShowDBErrorBacktrace = false;
  
@@@ -8674,14 -8688,9 +8693,14 @@@ $wgSiteTypes = 
  $wgPagePropsHaveSortkey = true;
  
  /**
 - * Port where you have HTTPS running
 - * Supports HTTPS on non-standard ports
 - * @see T67184
 + * For installations where the canonical server is HTTP but HTTPS is optionally
 + * supported, you can specify a non-standard HTTPS port here. $wgServer should
 + * be a protocol-relative URL.
 + *
 + * If HTTPS is always used, just specify the port number in $wgServer.
 + *
 + * @see https://phabricator.wikimedia.org/T67184
 + *
   * @since 1.24
   */
  $wgHttpsPort = 443;
diff --combined includes/Title.php
@@@ -306,10 -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();
         */
        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()
                );
        }
  
         */
        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()
                );
        }
  
                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?
         *
                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' ) {
+                       // 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' ) ) {
+                               $errors[] = [ 'sitecssprotected', $action ];
+                       } elseif ( $this->isSiteJsonConfigPage() && !$user->isAllowed( 'editsitejson' ) ) {
+                               $errors[] = [ 'sitejsonprotected', $action ];
+                       } elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
+                               $errors[] = [ 'sitejsprotected', $action ];
+                       }
+               }
+               return $errors;
+       }
        /**
         * Check CSS/JSON/JS sub-page permissions
         *
                                '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',
                                'checkQuickPermissions',
                                'checkPermissionHooks',
                                'checkSpecialsAndNSPermissions',
+                               'checkSiteConfigPermissions',
                                'checkUserConfigPermissions',
                                'checkPageRestrictions',
                                'checkCascadingSourcesRestrictions',
@@@ -1606,7 -1606,7 +1606,7 @@@ abstract class Installer 
        }
  
        /**
-        * Create the first user account, grant it sysop and bureaucrat rights
+        * Create the first user account, grant it sysop, bureaucrat and interface-admin rights
         *
         * @return Status
         */
  
                        $user->addGroup( 'sysop' );
                        $user->addGroup( 'bureaucrat' );
+                       $user->addGroup( 'interface-admin' );
                        if ( $this->getVar( '_AdminEmail' ) ) {
                                $user->setEmail( $this->getVar( '_AdminEmail' ) );
                        }
                $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
                // Debug-friendly
                $GLOBALS['wgShowExceptionDetails'] = true;
 +              $GLOBALS['wgShowHostnames'] = true;
                // Don't break forms
                $GLOBALS['wgExternalLinkTarget'] = '_blank';
  
 -              // Extended debugging
 -              $GLOBALS['wgShowSQLErrors'] = true;
 -              $GLOBALS['wgShowDBErrorBacktrace'] = true;
 -
                // Allow multiple ob_flush() calls
                $GLOBALS['wgDisableOutputCompression'] = true;
  
diff --combined languages/i18n/en.json
        "customcssprotected": "You do not have permission to edit this CSS page because it contains another user's personal settings.",
        "customjsonprotected": "You do not have permission to edit this JSON page because it contains another user's personal settings.",
        "customjsprotected": "You do not have permission to edit this JavaScript page because it contains another user's personal settings.",
+       "sitecssprotected": "You do not have permission to edit this CSS page because it may affect all visitors",
+       "sitejsonprotected": "You do not have permission to edit this JSON page because it may affect all visitors",
+       "sitejsprotected": "You do not have permission to edit this JavaScript page because it may affect all visitors",
        "mycustomcssprotected": "You do not have permission to edit this CSS page.",
        "mycustomjsonprotected": "You do not have permission to edit this JSON page.",
        "mycustomjsprotected": "You do not have permission to edit this JavaScript page.",
        "group-autoconfirmed": "Autoconfirmed users",
        "group-bot": "Bots",
        "group-sysop": "Administrators",
+       "group-interface-admin": "Interface administrators",
        "group-bureaucrat": "Bureaucrats",
        "group-suppress": "Suppressors",
        "group-all": "(all)",
        "group-autoconfirmed-member": "{{GENDER:$1|autoconfirmed user}}",
        "group-bot-member": "{{GENDER:$1|bot}}",
        "group-sysop-member": "{{GENDER:$1|administrator}}",
+       "group-interface-admin-member": "{{GENDER:$1|interface administrator}}",
        "group-bureaucrat-member": "{{GENDER:$1|bureaucrat}}",
        "group-suppress-member": "{{GENDER:$1|suppressor}}",
        "grouppage-user": "{{ns:project}}:Users",
        "grouppage-autoconfirmed": "{{ns:project}}:Autoconfirmed users",
        "grouppage-bot": "{{ns:project}}:Bots",
        "grouppage-sysop": "{{ns:project}}:Administrators",
+       "grouppage-interface-admin": "{{ns:project}}:Interface administrators",
        "grouppage-bureaucrat": "{{ns:project}}:Bureaucrats",
        "grouppage-suppress": "{{ns:project}}:Suppress",
        "right-read": "Read pages",
        "right-editusercss": "Edit other users' CSS files",
        "right-edituserjson": "Edit other users' JSON files",
        "right-edituserjs": "Edit other users' JavaScript files",
+       "right-editsitecss": "Edit sitewide CSS",
+       "right-editsitejson": "Edit sitewide JSON",
+       "right-editsitejs": "Edit sitewide JavaScript",
        "right-editmyusercss": "Edit your own user CSS files",
        "right-editmyuserjson": "Edit your own user JSON files",
        "right-editmyuserjs": "Edit your own user JavaScript files",
        "grant-createaccount": "Create accounts",
        "grant-createeditmovepage": "Create, edit, and move pages",
        "grant-delete": "Delete pages, revisions, and log entries",
-       "grant-editinterface": "Edit the MediaWiki namespace and user CSS/JSON/JavaScript",
+       "grant-editinterface": "Edit the MediaWiki namespace and sitewide/user JSON",
        "grant-editmycssjs": "Edit your user CSS/JSON/JavaScript",
        "grant-editmyoptions": "Edit your user preferences",
        "grant-editmywatchlist": "Edit your watchlist",
+       "grant-editsiteconfig": "Edit sitewide and user CSS/JS",
        "grant-editpage": "Edit existing pages",
        "grant-editprotected": "Edit protected pages",
        "grant-highvolume": "High-volume editing",
        "uploadstash-zero-length": "File is zero length.",
        "invalid-chunk-offset": "Invalid chunk offset",
        "img-auth-accessdenied": "Access denied",
 -      "img-auth-nopathinfo": "Missing PATH_INFO.\nYour server is not set up to pass this information.\nIt may be CGI-based and cannot support img_auth.\nSee https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
 +      "img-auth-nopathinfo": "Missing path information.\nYour server must be set up to pass the REQUEST_URI and/or PATH_INFO variables.\nIf it is, try enabling $wgUsePathInfo.\nSee https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "Requested path is not in the configured upload directory.",
        "img-auth-badtitle": "Unable to construct a valid title from \"$1\".",
        "img-auth-nologinnWL": "You are not logged in and \"$1\" is not in the whitelist.",
        "http-timed-out": "HTTP request timed out.",
        "http-curl-error": "Error fetching URL: $1",
        "http-bad-status": "There was a problem during the HTTP request: $1 $2",
 +      "http-internal-error": "HTTP internal error.",
        "upload-curl-error6": "Could not reach URL",
        "upload-curl-error6-text": "The URL provided could not be reached.\nPlease double-check that the URL is correct and the site is up.",
        "upload-curl-error28": "Upload timeout",
        "passwordpolicies-policy-passwordcannotmatchusername": "Password cannot be the same as username",
        "passwordpolicies-policy-passwordcannotmatchblacklist": "Password cannot match specifically blacklisted passwords",
        "passwordpolicies-policy-maximalpasswordlength": "Password must be less than $1 {{PLURAL:$1|character|characters}} long",
 -      "passwordpolicies-policy-passwordcannotbepopular": "Password cannot be {{PLURAL:$1|the popular password|in the list of $1 popular passwords}}"
 +      "passwordpolicies-policy-passwordcannotbepopular": "Password cannot be {{PLURAL:$1|the popular password|in the list of $1 popular passwords}}",
 +      "easydeflate-invaliddeflate": "Content provided is not properly deflated"
  }
diff --combined languages/i18n/qqq.json
        "customcssprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
        "customjsonprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
        "customjsprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
+       "sitecssprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
+       "sitejsonprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
+       "sitejsprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
        "mycustomcssprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
        "mycustomjsonprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
        "mycustomjsprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform",
        "group-autoconfirmed": "{{doc-group|autoconfirmed}}\nOn Wikimedia sites autoconfirmed users are users which are older than 4 days. After those 4 days, they have more rights.",
        "group-bot": "{{doc-group|bot}}\n{{Identical|Bot}}",
        "group-sysop": "{{doc-group|sysop}}\n{{Identical|Administrator}}",
+       "group-interface-admin": "{{doc-group|interface-admin}}",
        "group-bureaucrat": "{{doc-group|bureaucrat}}",
        "group-suppress": "{{doc-group|suppress}}\nThis is an optional (disabled by default) user group, meant for the suppression feature in [[mw:Flow|Flow]]. It is not to be confused with the Oversighters group, which also has access to the [[mw:RevisionDelete|RevisionDelete]] feature, to change the visibility of revisions through [[Special:RevisionDelete]].\n\n{{Identical|Suppress}}",
        "group-all": "The name of the user group that contains all users, including anonymous users\n\n{{Identical|All}}",
        "group-autoconfirmed-member": "{{doc-group|autoconfirmed|member}}",
        "group-bot-member": "{{doc-group|bot|member}}",
        "group-sysop-member": "{{doc-group|sysop|member}}\n{{Identical|Administrator}}",
+       "group-interface-admin-member": "{{doc-group|interface-admin|member}}",
        "group-bureaucrat-member": "{{doc-group|bureaucrat|member}}",
        "group-suppress-member": "{{doc-group|suppress|member}}\nThis is a member of the optional (disabled by default) user group, meant for the [[mw:RevisionDelete|RevisionDelete]] feature, to change the visibility of revisions through [[Special:RevisionDelete]].\n\n{{Identical|Suppress}}",
        "grouppage-user": "{{doc-group|user|page}}\n{{Identical|User}}",
        "grouppage-autoconfirmed": "{{doc-group|autoconfirmed|page}}",
        "grouppage-bot": "{{doc-group|bot|page}}\n{{Identical|Bot}}",
        "grouppage-sysop": "{{doc-group|sysop|page}}",
+       "grouppage-interface-admin": "{{doc-group|interface-admin|page}}",
        "grouppage-bureaucrat": "{{doc-group|bureaucrat|page}}",
        "grouppage-suppress": "{{doc-group|suppress|page}}\n{{Identical|Suppress}}",
        "right-read": "{{doc-right|read}}\nBasic right to read any page.",
        "right-editusercss": "{{doc-right|editusercss}}\nSee also:\n* {{msg-mw|Right-editmyusercss}}",
        "right-edituserjson": "{{doc-right|edituserjson}}\nSee also:\n* {{msg-mw|Right-editmyuserjson}}",
        "right-edituserjs": "{{doc-right|edituserjs}}\nSee also:\n* {{msg-mw|Right-editmyuserjs}}",
+       "right-editsitecss": "{{doc-right|editsitecss}}",
+       "right-editsitejson": "{{doc-right|editsitejson}}",
+       "right-editsitejs": "{{doc-right|editsitejs}}",
        "right-editmyusercss": "{{doc-right|editmyusercss}}\nSee also:\n* {{msg-mw|Right-editusercss}}",
        "right-editmyuserjson": "{{doc-right|editmyuserjson}}\nSee also:\n* {{msg-mw|Right-edituserjson}}",
        "right-editmyuserjs": "{{doc-right|editmyuserjs}}\nSee also:\n* {{msg-mw|Right-edituserjs}}",
        "right-applychangetags": "{{doc-right|applychangetags}}",
        "right-changetags": "{{doc-right|changetags}}",
        "right-deletechangetags": "{{doc-right|deletechangetags}}",
-       "grant-generic": "Used if the grant name is not defined. Parameters:\n* $1 - grant name\n\nDefined grants (grant name refers: blockusers, createeditmovepage, ...):\n* {{msg-mw|grant-checkuser}}\n* {{msg-mw|grant-blockusers}}\n* {{msg-mw|grant-createaccount}}\n* {{msg-mw|grant-createeditmovepage}}\n* {{msg-mw|grant-delete}}\n* {{msg-mw|grant-editinterface}}\n* {{msg-mw|grant-editmycssjs}}\n* {{msg-mw|grant-editmyoptions}}\n* {{msg-mw|grant-editmywatchlist}}\n* {{msg-mw|grant-editpage}}\n* {{msg-mw|grant-editprotected}}\n* {{msg-mw|grant-highvolume}}\n* {{msg-mw|grant-oversight}}\n* {{msg-mw|grant-patrol}}\n* {{msg-mw|grant-privateinfo}}\n* {{msg-mw|grant-protect}}\n* {{msg-mw|grant-rollback}}\n* {{msg-mw|grant-sendemail}}\n* {{msg-mw|grant-uploadeditmovefile}}\n* {{msg-mw|grant-uploadfile}}\n* {{msg-mw|grant-basic}}\n* {{msg-mw|grant-viewdeleted}}\n* {{msg-mw|grant-viewmywatchlist}}",
+       "grant-generic": "Used if the grant name is not defined. Parameters:\n* $1 - grant name\n\nDefined grants (grant name refers: blockusers, createeditmovepage, ...):\n* {{msg-mw|grant-checkuser}}\n* {{msg-mw|grant-blockusers}}\n* {{msg-mw|grant-createaccount}}\n* {{msg-mw|grant-createeditmovepage}}\n* {{msg-mw|grant-delete}}\n* {{msg-mw|grant-editinterface}}\n* {{msg-mw|grant-editmycssjs}}\n* {{msg-mw|grant-editmyoptions}}\n* {{msg-mw|grant-editmywatchlist}}\n* {{msg-mw|grant-editsiteconfig}}\n* {{msg-mw|grant-editpage}}\n* {{msg-mw|grant-editprotected}}\n* {{msg-mw|grant-highvolume}}\n* {{msg-mw|grant-oversight}}\n* {{msg-mw|grant-patrol}}\n* {{msg-mw|grant-privateinfo}}\n* {{msg-mw|grant-protect}}\n* {{msg-mw|grant-rollback}}\n* {{msg-mw|grant-sendemail}}\n* {{msg-mw|grant-uploadeditmovefile}}\n* {{msg-mw|grant-uploadfile}}\n* {{msg-mw|grant-basic}}\n* {{msg-mw|grant-viewdeleted}}\n* {{msg-mw|grant-viewmywatchlist}}",
        "grant-group-page-interaction": "{{Related|Grant-group}}",
        "grant-group-file-interaction": "{{Related|Grant-group}}",
        "grant-group-watchlist-interaction": "{{Related|Grant-group}}",
        "grant-createaccount": "Name for grant \"createaccount\".\n{{Related|Grant}}\n{{Identical|Create account}}",
        "grant-createeditmovepage": "Name for grant \"createeditmovepage\".\n{{Related|Grant}}",
        "grant-delete": "Name for grant \"delete\".\n{{Related|Grant}}",
-       "grant-editinterface": "Name for grant \"editinterface\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|Grant}}",
+       "grant-editinterface": "Name for grant \"editinterface\".\n{{Related|Grant}}",
        "grant-editmycssjs": "Name for grant \"editmycssjs\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|Grant}}",
        "grant-editmyoptions": "Name for grant \"editmyoptions\".\n{{Related|Grant}}",
        "grant-editmywatchlist": "Name for grant \"editmywatchlist\".\n{{Related|Grant}}\n{{Identical|Edit your watchlist}}",
+       "grant-editsiteconfig": "Name for grant \"editsiteconfig\".\n{{Related|Grant}}",
        "grant-editpage": "Name for grant \"editpage\".\n{{Related|Grant}}",
        "grant-editprotected": "Name for grant \"editprotected\".\n{{Related|Grant}}",
        "grant-highvolume": "Name for grant \"highvolume\".\n{{Related|Grant}}",
        "http-timed-out": "Used as error message when executing HTTP request.\n\nSee also:\n* {{msg-mw|Http-request-error}}\n* {{msg-mw|Http-read-error}}\n* {{msg-mw|Http-host-unreachable|6}}",
        "http-curl-error": "Used as curl error message when the error is other than known messages.\n* $1 - error code; not URL\nKnown messages are:\n* {{msg-mw|http-host-unreachable}}\n* {{msg-mw|http-timed-out}}",
        "http-bad-status": "Parameters:\n* $1 - an HTTP error code (e.g. 404)\n* $2 - the HTTP error message (e.g. File Not Found)",
 +      "http-internal-error": "Used as generic error message when executing HTTP request and no more specific message is available.",
        "upload-curl-error6": "See also:\n* {{msg-mw|Upload-curl-error6|title}}\n* {{msg-mw|Upload-curl-error6-text|body}}",
        "upload-curl-error6-text": "See also:\n* {{msg-mw|Upload-curl-error6|title}}\n* {{msg-mw|Upload-curl-error6-text|body}}",
        "upload-curl-error28": "See also:\n* {{msg-mw|Upload-curl-error28|title}}\n* {{msg-mw|Upload-curl-error28-text|body}}",
        "variantname-gan-hans": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
        "variantname-gan-hant": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
        "variantname-gan": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
 -      "variantname-sr-ec": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-ec</code> is not a conforming BCP 47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-cyrl</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
 -      "variantname-sr-el": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-el</code> is not a conforming BCP 47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-latn</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
 +      "variantname-sr-ec": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-ec</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-cyrl</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
 +      "variantname-sr-el": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-el</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-latn</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
        "variantname-sr": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
        "variantname-kk-kz": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
        "variantname-kk-tr": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
        "passwordpolicies-policy-passwordcannotmatchusername": "Password policy that enforces that the password of the account cannot be the same as the username",
        "passwordpolicies-policy-passwordcannotmatchblacklist": "Password policy that enforces that passwords are not on a list of blacklisted passwords (often previously used during MediaWiki automated testing)",
        "passwordpolicies-policy-maximalpasswordlength": "Password policy that enforces a maximum number of characters a password must be. $1 - maximum number of characters that a password can be",
 -      "passwordpolicies-policy-passwordcannotbepopular": "Password policy that enforces that a password is not in a list of $1 number of \"popular\" passwords. $1 - number of popular passwords the password will be checked against"
 +      "passwordpolicies-policy-passwordcannotbepopular": "Password policy that enforces that a password is not in a list of $1 number of \"popular\" passwords. $1 - number of popular passwords the password will be checked against",
 +      "easydeflate-invaliddeflate": "Error message if the content passed to easydeflate was not deflated (compressed) properly"
  }