Merge "Add Special:Mute as a shortcut for muting notifications"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 25 Jun 2019 17:15:39 +0000 (17:15 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 25 Jun 2019 17:15:39 +0000 (17:15 +0000)
1  2 
RELEASE-NOTES-1.34
autoload.php
includes/DefaultSettings.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialEmailUser.php
languages/i18n/en.json
languages/i18n/qqq.json

diff --combined RELEASE-NOTES-1.34
@@@ -33,6 -33,9 +33,9 @@@ For notes on 1.33.x and older releases
    the code as the request identificator. Otherwise, the sent header will be
    ignored and the request ID will either be taken from Apache's mod_unique
    module or will be generated by Mediawiki itself (depending on the set-up).
+ * $wgEnableSpecialMute (T218265) - This configuration controls whether
+   Special:Mute is available and whether to include a link to it on emails
+   originating from Special:Email.
  
  ==== Changed configuration ====
  * $wgUseCdn, $wgCdnServers, $wgCdnServersNoPurge, and $wgCdnMaxAge – These four
@@@ -57,7 -60,8 +60,8 @@@
    wikidiff2.moved_paragraph_detection_cutoff.
  
  === New user-facing features in 1.34 ===
- * …
+ * Special:Mute has been added as a quick way for users to block unwanted emails
+   from other users originating from Special:EmailUser.
  
  === New developer features in 1.34 ===
  * Language::formatTimePeriod now supports the new 'avoidhours' option to output
  * Updated cssjanus/cssjanus from 1.2.1 to 1.3.0.
  * Updated wikimedia/at-ease from 1.2.0 to 2.0.0.
  * Updated wikimedia/remex-html from 2.0.1 to 2.0.3.
 +* Updated monolog/monolog from 1.22.1 to 1.24.0 (dev-only).
 +* Updated wikimedia/object-factory from 1.0.0 to 2.0.0.
 +* Updated wikimedia/timestamp from 2.2.0 to 3.0.0.
 +* Updated wikimedia/xmp-reader from 0.6.2 to 0.6.3.
  * …
  
  ==== Removed external libraries ====
@@@ -156,10 -156,9 +160,10 @@@ because of Phabricator reports
  * User::makeGroupLinkWiki(), deprecated in 1.29, has been removed. Use
    UserGroupMembership::getLink() instead.
  * SavepointPostgres, deprecated in 1.31, has been removed.
 -* Output::sectionEditLinksEnabled(), ParserOutput::getEditSectionTokens,
 -  ::getTOCEnabled, ::setEditSectionTokens, ::setTOCEnabled, deprecated in 1.31,
 -  have been removed.
 +* OutputPage::enableSectionEditLinks(), OutputPage::sectionEditLinksEnabled(),
 +  ParserOptions::getEditSection(), ParserOptions::setEditSection(), and
 +  ParserOutput::getEditSectionTokens, ::getTOCEnabled, ::setEditSectionTokens,
 +  and ::setTOCEnabled, deprecated in 1.31, have been removed.
  * EditPage::safeUnicodeInput() and ::safeUnicodeOutput(), deprecated in 1.30,
    have been removed.
  * Four methods in OutputPage, deprecated in 1.32, have been removed. You should
  * jquery.ui.effect-bounce, jquery.ui.effect-explode, jquery.ui.effect-fold
    jquery.ui.effect-pulsate, jquery.ui.effect-slide, jquery.ui.effect-transfer,
    which are no longer used, have now been removed.
 +* SpecialEmailUser::validateTarget(), ::getTarget() without a sender/user
 +  specified, deprecated in 1.30, have been removed.
 +* BufferingStatsdDataFactory::getBuffer(), deprecated in 1.30, has been removed.
 +* The constant DB_SLAVE, deprecated in 1.28, has been removed. Use DB_REPLICA.
 +* Replacer, DoubleReplacer, HashtableReplacer and RegexlikeReplacer
 +  (deprecated in 1.32) have been removed. Closures should be used instead.
 +* OutputPage::addWikiText(), ::addWikiTextWithTitle(), ::addWikiTextTitleTidy(),
 +  ::addWikiTextTidy(), ::addWikiTextTitle(), deprecated in 1.32, have been
 +  removed.
 +* The $wgUseKeyHeader configuration option and the OutputPage::getKeyHeader()
 +  method, deprecated in 1.32, have been removed.
 +* WebInstallerOutput::addWikiText(), deprecated in 1.32, has been removed.
 +* Parser::fetchFile(), deprecated in 1.32, has been removed. Use the method
 +  Parser::fetchFileAndTitle() instead.
 +* The global function wfBCP47, deprecated in 1.31, has been removed.
 +* wfCountDown() function, deprecated in 1.31, has been removed. Use
 +  \Maintenance::countDown() method instead.
 +* OutputPage::wrapWikiMsg() no longer accepts an options parameter. This was
 +  deprecated since 1.20.
 +* Skin::outputPage() no longer accepts a context. This was deprecated in 1.20.
 +* Linker::link() no longer accepts a string for the query array, as was
 +  deprecated in 1.20.
 +* PrefixSearch::titleSearch(), deprecated in 1.23, has been removed. Use the
 +  SearchEngine::defaultPrefixSearch or ::completionSearch() methods instead.
 +* The UserRights hook, deprecated in 1.26, has been removed. Instead, use the
 +  UserGroupsChanged hook.
 +* Skin::getDefaultInstance(), deprecated in 1.27, has been removed. Get the
 +  instance from MediaWikiServices instead.
 +* The UserLoadFromSession hook, deprecated in 1.27, has been removed.
 +* The wfResetSessionID global function, deprecated in 1.27, has been removed.
 +  Use MediaWiki\Session\SessionManager instead.
 +* The wfGetLBFactory global function, deprecated in 1.27, has been removed.
 +  Use MediaWikiServices::getInstance()->getDBLoadBalancerFactory().
  * …
  
  === Deprecations in 1.34 ===
  * DatabaseBlock::setCookie, DatabaseBlock::getCookieValue,
    DatabaseBlock::getIdFromCookieValue and AbstractBlock::shouldTrackWithCookie
    are moved to internal helper methods for BlockManager::trackBlockWithCookie.
 +* ResourceLoaderContext::getConfig and ResourceLoaderContext::getLogger have
 +  been deprecated. Inside ResourceLoaderModule subclasses, use the local methods
 +  instead. Elsewhere, use the methods from the ResourceLoader class.
 +* The Preprocessor_DOM implementation has been deprecated.  It will be
 +  removed in a future release.  Use the Preprocessor_Hash implementation
 +  instead.
 +* Sanitizer::attributeWhitelist() and Sanitizer::setupAttributeWhitelist()
 +  have been deprecated; they will be made private in the future.
  
  === Other changes in 1.34 ===
  * …
diff --combined autoload.php
@@@ -416,6 -416,7 +416,6 @@@ $wgAutoloadLocalClasses = 
        'DnsSrvDiscoverer' => __DIR__ . '/includes/libs/DnsSrvDiscoverer.php',
        'DoubleRedirectJob' => __DIR__ . '/includes/jobqueue/jobs/DoubleRedirectJob.php',
        'DoubleRedirectsPage' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php',
 -      'DoubleReplacer' => __DIR__ . '/includes/libs/replacers/DoubleReplacer.php',
        'DummyLinker' => __DIR__ . '/includes/DummyLinker.php',
        'DummySearchIndexFieldDefinition' => __DIR__ . '/includes/search/DummySearchIndexFieldDefinition.php',
        'DummyTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
        'HashConfig' => __DIR__ . '/includes/config/HashConfig.php',
        'HashRing' => __DIR__ . '/includes/libs/HashRing.php',
        'HashSiteStore' => __DIR__ . '/includes/site/HashSiteStore.php',
 -      'HashtableReplacer' => __DIR__ . '/includes/libs/replacers/HashtableReplacer.php',
        'HistoryAction' => __DIR__ . '/includes/actions/HistoryAction.php',
        'HistoryBlob' => __DIR__ . '/includes/historyblob/HistoryBlob.php',
        'HistoryBlobCurStub' => __DIR__ . '/includes/historyblob/HistoryBlobCurStub.php',
        'LanguageAz' => __DIR__ . '/languages/classes/LanguageAz.php',
        'LanguageBe_tarask' => __DIR__ . '/languages/classes/LanguageBe_tarask.php',
        'LanguageBs' => __DIR__ . '/languages/classes/LanguageBs.php',
 -      'LanguageCode' => __DIR__ . '/languages/LanguageCode.php',
 +      'LanguageCode' => __DIR__ . '/includes/language/LanguageCode.php',
        'LanguageConverter' => __DIR__ . '/languages/LanguageConverter.php',
        'LanguageCrh' => __DIR__ . '/languages/classes/LanguageCrh.php',
        'LanguageCu' => __DIR__ . '/languages/classes/LanguageCu.php',
        'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php',
        'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php',
        'MergeableUpdate' => __DIR__ . '/includes/deferred/MergeableUpdate.php',
 -      'Message' => __DIR__ . '/includes/Message.php',
 +      'Message' => __DIR__ . '/includes/language/Message.php',
        'MessageBlobStore' => __DIR__ . '/includes/resourceloader/MessageBlobStore.php',
        'MessageCache' => __DIR__ . '/includes/cache/MessageCache.php',
        'MessageCacheUpdate' => __DIR__ . '/includes/deferred/MessageCacheUpdate.php',
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
 -      'MessageLocalizer' => __DIR__ . '/languages/MessageLocalizer.php',
 +      'MessageLocalizer' => __DIR__ . '/includes/language/MessageLocalizer.php',
        'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
        'MigrateActors' => __DIR__ . '/maintenance/includes/MigrateActors.php',
        'MigrateArchiveText' => __DIR__ . '/maintenance/migrateArchiveText.php',
        'RefreshImageMetadata' => __DIR__ . '/maintenance/refreshImageMetadata.php',
        'RefreshLinks' => __DIR__ . '/maintenance/refreshLinks.php',
        'RefreshLinksJob' => __DIR__ . '/includes/jobqueue/jobs/RefreshLinksJob.php',
 -      'RegexlikeReplacer' => __DIR__ . '/includes/libs/replacers/RegexlikeReplacer.php',
        'RemexStripTagHandler' => __DIR__ . '/includes/parser/RemexStripTagHandler.php',
        'RemoveInvalidEmails' => __DIR__ . '/maintenance/removeInvalidEmails.php',
        'RemoveUnusedAccounts' => __DIR__ . '/maintenance/removeUnusedAccounts.php',
        'RenameDbPrefix' => __DIR__ . '/maintenance/renameDbPrefix.php',
        'RenderAction' => __DIR__ . '/includes/actions/RenderAction.php',
        'ReplacementArray' => __DIR__ . '/includes/libs/ReplacementArray.php',
 -      'Replacer' => __DIR__ . '/includes/libs/replacers/Replacer.php',
        'ReplicatedBagOStuff' => __DIR__ . '/includes/libs/objectcache/ReplicatedBagOStuff.php',
        'RepoGroup' => __DIR__ . '/includes/filerepo/RepoGroup.php',
        'RequestContext' => __DIR__ . '/includes/context/RequestContext.php',
        'ResetUserTokens' => __DIR__ . '/maintenance/resetUserTokens.php',
        'ResourceFileCache' => __DIR__ . '/includes/cache/ResourceFileCache.php',
        'ResourceLoader' => __DIR__ . '/includes/resourceloader/ResourceLoader.php',
 +      'ResourceLoaderCircularDependencyError' => __DIR__ . '/includes/resourceloader/ResourceLoaderCircularDependencyError.php',
        'ResourceLoaderClientHtml' => __DIR__ . '/includes/resourceloader/ResourceLoaderClientHtml.php',
        'ResourceLoaderContext' => __DIR__ . '/includes/resourceloader/ResourceLoaderContext.php',
        'ResourceLoaderFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderFileModule.php',
        'SearchUpdate' => __DIR__ . '/includes/deferred/SearchUpdate.php',
        'SectionProfileCallback' => __DIR__ . '/includes/profiler/SectionProfileCallback.php',
        'SectionProfiler' => __DIR__ . '/includes/profiler/SectionProfiler.php',
 +      'SerializedValueContainer' => __DIR__ . '/includes/libs/objectcache/serialized/SerializedValueContainer.php',
        'SevenZipStream' => __DIR__ . '/maintenance/includes/SevenZipStream.php',
        'ShiConverter' => __DIR__ . '/languages/classes/LanguageShi.php',
        'ShortPagesPage' => __DIR__ . '/includes/specials/SpecialShortpages.php',
        'SpecialLockdb' => __DIR__ . '/includes/specials/SpecialLockdb.php',
        'SpecialLog' => __DIR__ . '/includes/specials/SpecialLog.php',
        'SpecialMergeHistory' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
+       'SpecialMute' => __DIR__ . '/includes/specials/SpecialMute.php',
        'SpecialMyLanguage' => __DIR__ . '/includes/specials/SpecialMyLanguage.php',
        'SpecialMycontributions' => __DIR__ . '/includes/specials/redirects/SpecialMycontributions.php',
        'SpecialMypage' => __DIR__ . '/includes/specials/redirects/SpecialMypage.php',
@@@ -193,13 -193,6 +193,13 @@@ $wgScript = false
   */
  $wgLoadScript = false;
  
 +/**
 + * The URL path to the REST API
 + * Defaults to "{$wgScriptPath}/rest.php"
 + * @since 1.34
 + */
 +$wgRestPath = false;
 +
  /**
   * The URL path of the skins directory.
   * Defaults to "{$wgResourceBasePath}/skins".
@@@ -1689,6 -1682,16 +1689,16 @@@ $wgEnableEmail = true
   */
  $wgEnableUserEmail = true;
  
+ /**
+  * Set to true to enable the Special Mute page. This allows users
+  * to mute unwanted communications from other users, and is linked
+  * to from emails originating from Special:Email.
+  *
+  * @since 1.34
+  * @deprecated 1.34
+  */
+ $wgEnableSpecialMute = false;
  /**
   * Set to true to enable user-to-user e-mail blacklist.
   *
@@@ -2737,6 -2740,14 +2747,6 @@@ $wgUseCdn = false
   */
  $wgUseESI = false;
  
 -/**
 - * Send the Key HTTP header for better caching.
 - * See https://datatracker.ietf.org/doc/draft-ietf-httpbis-key/ for details.
 - * @since 1.27
 - * @deprecated in 1.32, the IETF spec expired without becoming a standard.
 - */
 -$wgUseKeyHeader = false;
 -
  /**
   * Add X-Forwarded-Proto to the Vary and Key headers for API requests and
   * RSS/Atom feeds. Use this if you have an SSL termination setup
@@@ -4142,10 -4153,8 +4152,10 @@@ $wgInvalidRedirectTargets = [ 'Filepath
   *                    temporary storage. Preprocessor_DOM generally uses less memory;
   *                    the speed of the two is roughly the same.
   *
 - *                    If this parameter is not given, it uses Preprocessor_DOM if the
 - *                    DOM module is available, otherwise it uses Preprocessor_Hash.
 + *                    If this parameter is not given, it uses Preprocessor_Hash.
 + *
 + * The Preprocessor_DOM class is deprecated, and will be removed in a future
 + * release.
   *
   * The entire associative array will be passed through to the constructor as
   * the first parameter. Note that only Setup.php can use this variable --
@@@ -5418,20 -5427,20 +5428,20 @@@ $wgAutoConfirmCount = 0
   *
   * The basic syntax for `$wgAutopromote` is:
   *
 - *     $wgAutopromote = array(
 + *     $wgAutopromote = [
   *         'groupname' => cond,
   *         'group2' => cond2,
 - *     );
 + *     ];
   *
   * A `cond` may be:
   *  - a single condition without arguments:
   *      Note that Autopromote wraps a single non-array value into an array
   *      e.g. `APCOND_EMAILCONFIRMED` OR
 - *           array( `APCOND_EMAILCONFIRMED` )
 + *           [ `APCOND_EMAILCONFIRMED` ]
   *  - a single condition with arguments:
 - *      e.g. `array( APCOND_EDITCOUNT, 100 )`
 + *      e.g. `[ APCOND_EDITCOUNT, 100 ]`
   *  - a set of conditions:
 - *      e.g. `array( 'operand', cond1, cond2, ... )`
 + *      e.g. `[ 'operand', cond1, cond2, ... ]`
   *
   * When constructing a set of conditions, the following conditions are available:
   *  - `&` (**AND**):
   *      promote if user matches **ONLY ONE OF THE CONDITIONS**
   *  - `!` (**NOT**):
   *      promote if user matces **NO** condition
 - *  - array( APCOND_EMAILCONFIRMED ):
 + *  - [ APCOND_EMAILCONFIRMED ]:
   *      true if user has a confirmed e-mail
 - *  - array( APCOND_EDITCOUNT, number of edits ):
 + *  - [ APCOND_EDITCOUNT, number of edits ]:
   *      true if user has the at least the number of edits as the passed parameter
 - *  - array( APCOND_AGE, seconds since registration ):
 + *  - [ APCOND_AGE, seconds since registration ]:
   *      true if the length of time since the user created his/her account
   *      is at least the same length of time as the passed parameter
 - *  - array( APCOND_AGE_FROM_EDIT, seconds since first edit ):
 + *  - [ APCOND_AGE_FROM_EDIT, seconds since first edit ]:
   *      true if the length of time since the user made his/her first edit
   *      is at least the same length of time as the passed parameter
 - *  - array( APCOND_INGROUPS, group1, group2, ... ):
 + *  - [ APCOND_INGROUPS, group1, group2, ... ]:
   *      true if the user is a member of each of the passed groups
 - *  - array( APCOND_ISIP, ip ):
 + *  - [ APCOND_ISIP, ip ]:
   *      true if the user has the passed IP address
 - *  - array( APCOND_IPINRANGE, range ):
 + *  - [ APCOND_IPINRANGE, range ]:
   *      true if the user has an IP address in the range of the passed parameter
 - *  - array( APCOND_BLOCKED ):
 + *  - [ APCOND_BLOCKED ]:
   *      true if the user is blocked
 - *  - array( APCOND_ISBOT ):
 + *  - [ APCOND_ISBOT ]:
   *      true if the user is a bot
   *  - similar constructs can be defined by extensions
   *
@@@ -6414,7 -6423,7 +6424,7 @@@ $wgDeprecationReleaseLimit = false
   *
   * @code
   *   $wgProfiler['class'] = 'ProfilerXhprof';
 - *   $wgProfiler['output'] = array( 'ProfilerOutputDb' );
 + *   $wgProfiler['output'] = [ 'ProfilerOutputDb' ];
   *   $wgProfiler['sampling'] = 50; // one every 50 requests
   * @endcode
   *
@@@ -8087,10 -8096,10 +8097,10 @@@ $wgExemptFromUserRobotsControl = null
  /** @} */ # End robot policy }
  
  /************************************************************************//**
 - * @name   AJAX and API
 + * @name   AJAX, Action API and REST API
   * Note: The AJAX entry point which this section refers to is gradually being
 - * replaced by the API entry point, api.php. They are essentially equivalent.
 - * Both of them are used for dynamic client-side features, via XHR.
 + * replaced by the Action API entry point, api.php. They are essentially
 + * equivalent. Both of them are used for dynamic client-side features, via XHR.
   * @{
   */
  
@@@ -232,6 -232,7 +232,7 @@@ class SpecialPageFactory 
                'EmailAuthentication',
                'EnableEmail',
                'EnableJavaScriptTest',
+               'EnableSpecialMute',
                'PageLanguageUseDB',
                'SpecialPages',
        ];
                                $this->list['JavaScriptTest'] = \SpecialJavaScriptTest::class;
                        }
  
+                       if ( $this->options->get( 'EnableSpecialMute' ) ) {
+                               $this->list['Mute'] = \SpecialMute::class;
+                       }
                        if ( $this->options->get( 'PageLanguageUseDB' ) ) {
                                $this->list['PageLanguage'] = \SpecialPageLanguage::class;
                        }
                        if ( $this->options->get( 'ContentHandlerUseDB' ) ) {
                                $this->list['ChangeContentModel'] = \SpecialChangeContentModel::class;
                        }
         * subpage.
         *
         * @param string $alias
 -       * @return array Array( String, String|null ), or array( null, null ) if the page is invalid
 +       * @return array [ String, String|null ], or [ null, null ] if the page is invalid
         */
        public function resolveAlias( $alias ) {
                $bits = explode( '/', $alias, 2 );
@@@ -166,10 -166,14 +166,10 @@@ class SpecialEmailUser extends Unlisted
         * Validate target User
         *
         * @param string $target Target user name
 -       * @param User|null $sender User sending the email
 +       * @param User $sender User sending the email
         * @return User|string User object on success or a string on error
         */
 -      public static function getTarget( $target, User $sender = null ) {
 -              if ( $sender === null ) {
 -                      wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' );
 -              }
 -
 +      public static function getTarget( $target, User $sender ) {
                if ( $target == '' ) {
                        wfDebug( "Target is empty.\n" );
  
         * Validate target User
         *
         * @param User $target Target user
 -       * @param User|null $sender User sending the email
 +       * @param User $sender User sending the email
         * @return string Error message or empty string if valid.
         * @since 1.30
         */
 -      public static function validateTarget( $target, User $sender = null ) {
 -              if ( $sender === null ) {
 -                      wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' );
 -              }
 -
 +      public static function validateTarget( $target, User $sender ) {
                if ( !$target instanceof User || !$target->getId() ) {
                        wfDebug( "Target is invalid user.\n" );
  
                        return 'nowikiemail';
                }
  
 -              if ( $sender !== null && !$target->getOption( 'email-allow-new-users' ) &&
 -                      $sender->isNewbie()
 -              ) {
 +              if ( !$target->getOption( 'email-allow-new-users' ) && $sender->isNewbie() ) {
                        wfDebug( "User does not allow user emails from new users.\n" );
  
                        return 'nowikiemail';
                }
  
 -              if ( $sender !== null ) {
 -                      $blacklist = $target->getOption( 'email-blacklist', '' );
 -                      if ( $blacklist ) {
 -                              $blacklist = MultiUsernameFilter::splitIds( $blacklist );
 -                              $lookup = CentralIdLookup::factory();
 -                              $senderId = $lookup->centralIdFromLocalUser( $sender );
 -                              if ( $senderId !== 0 && in_array( $senderId, $blacklist ) ) {
 -                                      wfDebug( "User does not allow user emails from this user.\n" );
 -
 -                                      return 'nowikiemail';
 -                              }
 +              $blacklist = $target->getOption( 'email-blacklist', '' );
 +              if ( $blacklist ) {
 +                      $blacklist = MultiUsernameFilter::splitIds( $blacklist );
 +                      $lookup = CentralIdLookup::factory();
 +                      $senderId = $lookup->centralIdFromLocalUser( $sender );
 +                      if ( $senderId !== 0 && in_array( $senderId, $blacklist ) ) {
 +                              wfDebug( "User does not allow user emails from this user.\n" );
 +
 +                              return 'nowikiemail';
                        }
                }
  
                $text .= $context->msg( 'emailuserfooter',
                        $from->name, $to->name )->inContentLanguage()->text();
  
+               if ( $config->get( 'EnableSpecialMute' ) ) {
+                       $specialMutePage = SpecialPage::getTitleFor( 'Mute', $context->getUser()->getName() );
+                       $text .= "\n" . $context->msg(
+                               'specialmute-email-footer',
+                               $specialMutePage->getCanonicalURL(),
+                               $context->getUser()->getName()
+                       );
+               }
                // Check and increment the rate limits
                if ( $context->getUser()->pingLimiter( 'emailuser' ) ) {
                        throw new ThrottledError();
diff --combined languages/i18n/en.json
        "history": "Page history",
        "history_short": "History",
        "history_small": "history",
 -      "updatedmarker": "updated since my last visit",
 +      "updatedmarker": "updated since your last visit",
        "printableversion": "Printable version",
        "permalink": "Permanent link",
        "print": "Print",
        "autoblockedtext": "Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou may contact $1 or one of the other [[{{MediaWiki:Grouppage-sysop}}|administrators]] to discuss the block.\n\nNote that you may not use the \"{{int:emailuser}}\" feature unless you have a valid email address registered in your [[Special:Preferences|user preferences]] and you have not been blocked from using it.\n\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
        "systemblockedtext": "Your username or IP address has been automatically blocked by MediaWiki.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYour current IP address is $3.\nPlease include all above details in any queries you make.",
        "blockednoreason": "no reason given",
 +      "blockedtext-composite": "<strong>Your username or IP address has been blocked.</strong>\n\nThe reason given is:\n\n:<em>$2</em>.\n\n* Start of block: $8\n* Expiration of longest block: $6\n\nYour current IP address is $3.\nPlease include all above details in any queries you make.",
 +      "blockedtext-composite-reason": "There are multiple blocks against your account and/or IP address",
        "whitelistedittext": "Please $1 to edit pages.",
        "confirmedittext": "You must confirm your email address before editing pages.\nPlease set and validate your email address through your [[Special:Preferences|user preferences]].",
        "nosuchsectiontitle": "Cannot find section",
        "restrictionsfield-help": "One IP address or CIDR range per line. To enable everything, use:<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "Error: $1",
        "edit-error-long": "Errors:\n\n$1",
+       "specialmute": "Mute",
+       "specialmute-success": "Your mute preferences have been successfully updated. See all muted users in [[Special:Preferences]].",
+       "specialmute-submit": "Confirm",
+       "specialmute-label-mute-email": "Mute emails from this user",
+       "specialmute-header": "Please select your mute preferences for [[User:$1]].",
+       "specialmute-error-invalid-user": "The username requested could not be found.",
+       "specialmute-error-email-blacklist-disabled": "Muting users from sending you emails is not enabled.",
+       "specialmute-error-email-preferences": "You must confirm your email address before you can mute a user. You may do so from [[Special:Preferences]].",
+       "specialmute-email-footer": "[$1 Manage email preferences for $2.]",
+       "specialmute-login-required": "Please log in to change your mute preferences.",
        "revid": "revision $1",
        "pageid": "page ID $1",
        "interfaceadmin-info": "$1\n\nPermissions for editing of sitewide CSS/JS/JSON files were recently separated from the <code>editinterface</code> right. If you do not understand why you are getting this error, see [[mw:MediaWiki_1.32/interface-admin]].",
diff --combined languages/i18n/qqq.json
        "autoblockedtext": "Text displayed to automatically blocked users.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block (in case of autoblocks: {{msg-mw|autoblocker}})\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link). Use it for GENDER.\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext|notext=1}}\n* {{msg-mw|Systemblockedtext|notext=1}}",
        "systemblockedtext": "Text displayed to requests blocked by MediaWiki configuration.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - (Unused) A dummy user attributed as the blocker, possibly as a link to a user page.\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the dummy blocking user's username (plain text, without the link).\n* $5 - A short string indicating the type of system block.\n* $6 - the expiry of the block\n* $7 - the intended target of the block\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext|notext=1}}\n* {{msg-mw|Autoblockedtext|notext=1}}",
        "blockednoreason": "Substituted with <code>$2</code> in the following message if the reason is not given:\n* {{msg-mw|cantcreateaccount-text}}.\n{{Identical|No reason given}}",
 +      "blockedtext-composite": "Text displayed to requests blocked by more than one block.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - (Unused) A dummy user attributed as the blocker, possibly as a link to a user page.\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the dummy blocking user's username (plain text, without the link).\n* $5 - (Unused) placeholder for the block ID.\n* $6 - the expiry of the block with the longest duration\n* $7 - (Unused) the intended target of the block\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Systemblockedtext|notext=1}}",
 +      "blockedtext-composite-reason": "Reason given to blocked users who are affected by more than one block.\n\nSee also:\n* {{msg-mw|blockedtext-composite}}",
        "whitelistedittext": "Used as error message. Parameters:\n* $1 - a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description\n* $2 - an URL to the same\n\nSee also:\n* {{msg-mw|Nocreatetext}}\n* {{msg-mw|Uploadnologintext}}\n* {{msg-mw|Loginreqpagetext}}",
        "confirmedittext": "Used as error message.",
        "nosuchsectiontitle": "Used as error message when the user has attempted to edit a nonexistent section.",
        "restrictionsfield-help": "Placeholder text displayed in restriction fields (e.g. on Special:BotPassword).",
        "edit-error-short": "Error message. Parameters:\n* $1 - the error details\nSee also:\n* {{msg-mw|edit-error-long}}\n{{Identical|Error}}",
        "edit-error-long": "Error message. Parameters:\n* $1 - the error details\nSee also:\n* {{msg-mw|edit-error-short}}\n{{Identical|Error}}",
+       "specialmute": "The name of the special page [[Special:Mute]].",
+       "specialmute-success": "The content of [[Special:Mute]] with a successful message indicating that your mute preferences have been updated after the form has been submitted.",
+       "specialmute-submit": "Submit button on [[Special:Mute]] form.",
+       "specialmute-label-mute-email": "Label for the checkbox that mutes/unmutes emails from the specified user.",
+       "specialmute-header": "Used as header text on [[Special:Mute]]. Shown before the form with the muting options.\n* $1 - User selected for muting",
+       "specialmute-error-invalid-user": "Error displayed when the username cannot be found.",
+       "specialmute-error-email-blacklist-disabled": "Error displayed when email blacklist is not enabled.",
+       "specialmute-error-email-preferences": "Error displayed when the user has not confirmed their email address.",
+       "specialmute-email-footer": "Email footer linking to [[Special:Mute]] preselecting the sender to manage muting options.\n* $1 - Url linking to [[Special:Mute]].\n* $2 - The user sending the email.",
+       "specialmute-login-required": "Error displayed when a user tries to access [[Special:Mute]] before logging in.",
        "revid": "Used to format a revision ID number in text. Parameters:\n* $1 - Revision ID number.\n{{Identical|Revision}}",
        "pageid": "Used to format a page ID number in text. Parameters:\n* $1 - Page ID number.",
        "interfaceadmin-info": "Part of the error message shown when someone with the <code>editinterface</code> right but without the appropriate <code>editsite*</code> right tries to edit a sitewide CSS/JSON/JS page.",