Merge "Add parameter to API modules to apply change tags to log entries"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 13 Jan 2017 17:42:04 +0000 (17:42 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 13 Jan 2017 17:42:04 +0000 (17:42 +0000)
1  2 
includes/Title.php
includes/api/ApiBlock.php
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/specials/SpecialBlock.php

diff --combined includes/Title.php
@@@ -1681,9 -1681,8 +1681,9 @@@ class Title implements LinkTarget 
  
                # Finally, add the fragment.
                $url .= $this->getFragmentForURL();
 -
 -              Hooks::run( 'GetFullURL', [ &$this, &$url, $query ] );
 +              // Avoid PHP 7.1 warning from passing $this by reference
 +              $titleRef = $this;
 +              Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
                return $url;
        }
  
                        $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
                        if ( $query == '' ) {
                                $url = str_replace( '$1', $dbkey, $wgArticlePath );
 -                              Hooks::run( 'GetLocalURL::Article', [ &$this, &$url ] );
 +                              // Avoid PHP 7.1 warning from passing $this by reference
 +                              $titleRef = $this;
 +                              Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
                        } else {
                                global $wgVariantArticlePath, $wgActionPaths, $wgContLang;
                                $url = false;
                                        $url = "{$wgScript}?title={$dbkey}&{$query}";
                                }
                        }
 -
 -                      Hooks::run( 'GetLocalURL::Internal', [ &$this, &$url, $query ] );
 +                      // Avoid PHP 7.1 warning from passing $this by reference
 +                      $titleRef = $this;
 +                      Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
  
                        // @todo FIXME: This causes breakage in various places when we
                        // actually expected a local URL and end up with dupe prefixes.
                                $url = $wgServer . $url;
                        }
                }
 -              Hooks::run( 'GetLocalURL', [ &$this, &$url, $query ] );
 +              // Avoid PHP 7.1 warning from passing $this by reference
 +              $titleRef = $this;
 +              Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
                return $url;
        }
  
                $query = self::fixUrlQueryArgs( $query, $query2 );
                $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
                $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
 -              Hooks::run( 'GetInternalURL', [ &$this, &$url, $query ] );
 +              // Avoid PHP 7.1 warning from passing $this by reference
 +              $titleRef = $this;
 +              Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
                return $url;
        }
  
        public function getCanonicalURL( $query = '', $query2 = false ) {
                $query = self::fixUrlQueryArgs( $query, $query2 );
                $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
 -              Hooks::run( 'GetCanonicalURL', [ &$this, &$url, $query ] );
 +              // Avoid PHP 7.1 warning from passing $this by reference
 +              $titleRef = $this;
 +              Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
                return $url;
        }
  
        private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
                // Use getUserPermissionsErrors instead
                $result = '';
 -              if ( !Hooks::run( 'userCan', [ &$this, &$user, $action, &$result ] ) ) {
 +              // Avoid PHP 7.1 warning from passing $this by reference
 +              $titleRef = $this;
 +              if ( !Hooks::run( 'userCan', [ &$titleRef, &$user, $action, &$result ] ) ) {
                        return $result ? [] : [ [ 'badaccess-group0' ] ];
                }
                // Check getUserPermissionsErrors hook
 -              if ( !Hooks::run( 'getUserPermissionsErrors', [ &$this, &$user, $action, &$result ] ) ) {
 +              // Avoid PHP 7.1 warning from passing $this by reference
 +              $titleRef = $this;
 +              if ( !Hooks::run( 'getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ] ) ) {
                        $errors = $this->resultToError( $errors, $result );
                }
                // Check getUserPermissionsErrorsExpensive hook
                if (
                        $rigor !== 'quick'
                        && !( $short && count( $errors ) > 0 )
 -                      && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$this, &$user, $action, &$result ] )
 +                      && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ] )
                ) {
                        $errors = $this->resultToError( $errors, $result );
                }
                );
        }
  
 -      /**
 -       * Move this page without authentication
 -       *
 -       * @deprecated since 1.25 use MovePage class instead
 -       * @param Title $nt The new page Title
 -       * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
 -       */
 -      public function moveNoAuth( &$nt ) {
 -              wfDeprecated( __METHOD__, '1.25' );
 -              return $this->moveTo( $nt, false );
 -      }
 -
        /**
         * Check whether a given move operation would be valid.
         * Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
         * @param string $reason The reason for the move
         * @param bool $createRedirect Whether to create a redirect from the old title to the new title.
         *  Ignored if the user doesn't have the suppressredirect right.
+        * @param array $changeTags Applied to the entry in the move log and redirect page revision
         * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
         */
-       public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
+       public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
+               array $changeTags = [] ) {
                global $wgUser;
                $err = $this->isValidMoveOperation( $nt, $auth, $reason );
                if ( is_array( $err ) ) {
                }
  
                $mp = new MovePage( $this, $nt );
-               $status = $mp->move( $wgUser, $reason, $createRedirect );
+               $status = $mp->move( $wgUser, $reason, $createRedirect, $changeTags );
                if ( $status->isOK() ) {
                        return true;
                } else {
         * @param string $reason The reason for the move
         * @param bool $createRedirect Whether to create redirects from the old subpages to
         *     the new ones Ignored if the user doesn't have the 'suppressredirect' right
+        * @param array $changeTags Applied to the entry in the move log and redirect page revision
         * @return array Array with old page titles as keys, and strings (new page titles) or
         *     getUserPermissionsErrors()-like arrays (errors) as values, or a
         *     getUserPermissionsErrors()-like error array with numeric indices if
         *     no pages were moved
         */
-       public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
+       public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
+               array $changeTags = [] ) {
                global $wgMaximumMovedPages;
                // Check permissions
                if ( !$this->userCan( 'move-subpages' ) ) {
                        # be longer than 255 characters.
                        $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
  
-                       $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
+                       $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags );
                        if ( $success === true ) {
                                $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
                        } else {
@@@ -39,6 -39,8 +39,6 @@@ class ApiBlock extends ApiBase 
         * of success. If it fails, the result will specify the nature of the error.
         */
        public function execute() {
 -              global $wgContLang;
 -
                $this->checkUserRightsAny( 'block' );
  
                $user = $this->getUser();
                        }
                }
  
+               if ( $params['tags'] ) {
+                       $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
+                       if ( !$ableToTag->isOK() ) {
+                               $this->dieStatus( $ableToTag );
+                       }
+               }
                if ( $params['hidename'] && !$user->isAllowed( 'hideuser' ) ) {
                        $this->dieWithError( 'apierror-canthide' );
                }
                        'Reblock' => $params['reblock'],
                        'Watch' => $params['watchuser'],
                        'Confirm' => true,
+                       'Tags' => $params['tags'],
                ];
  
                $retval = SpecialBlock::processForm( $data, $this->getContext() );
  
                $block = Block::newFromTarget( $target, null, true );
                if ( $block instanceof Block ) {
 -                      $res['expiry'] = $wgContLang->formatExpiry( $block->mExpiry, TS_ISO_8601, 'infinite' );
 +                      $res['expiry'] = ApiResult::formatExpiry( $block->mExpiry, 'infinite' );
                        $res['id'] = $block->getId();
                } else {
                        # should be unreachable
                        'allowusertalk' => false,
                        'reblock' => false,
                        'watchuser' => false,
+                       'tags' => [
+                               ApiBase::PARAM_TYPE => 'tags',
+                               ApiBase::PARAM_ISMULTI => true,
+                       ],
                ];
        }
  
@@@ -37,6 -37,7 +37,7 @@@
        "apihelp-block-param-allowusertalk": "Allow the user to edit their own talk page (depends on <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
        "apihelp-block-param-reblock": "If the user is already blocked, overwrite the existing block.",
        "apihelp-block-param-watchuser": "Watch the user's or IP address's user and talk pages.",
+       "apihelp-block-param-tags": "Change tags to apply to the entry in the block log.",
        "apihelp-block-example-ip-simple": "Block IP address <kbd>192.0.2.5</kbd> for three days with reason <kbd>First strike</kbd>.",
        "apihelp-block-example-user-complex": "Block user <kbd>Vandal</kbd> indefinitely with reason <kbd>Vandalism</kbd>, and prevent new account creation and email sending.",
  
  
        "apihelp-imagerotate-description": "Rotate one or more images.",
        "apihelp-imagerotate-param-rotation": "Degrees to rotate image clockwise.",
 +      "apihelp-imagerotate-param-tags": "Tags to apply to the entry in the upload log.",
        "apihelp-imagerotate-example-simple": "Rotate <kbd>File:Example.png</kbd> by <kbd>90</kbd> degrees.",
        "apihelp-imagerotate-example-generator": "Rotate all images in <kbd>Category:Flip</kbd> by <kbd>180</kbd> degrees.",
  
        "apihelp-import-param-templates": "For interwiki imports: import all included templates as well.",
        "apihelp-import-param-namespace": "Import to this namespace. Cannot be used together with <var>$1rootpage</var>.",
        "apihelp-import-param-rootpage": "Import as subpage of this page. Cannot be used together with <var>$1namespace</var>.",
+       "apihelp-import-param-tags": "Change tags to apply to the entry in the import log and to the null revision on the imported pages.",
        "apihelp-import-example-import": "Import [[meta:Help:ParserFunctions]] to namespace 100 with full history.",
  
        "apihelp-linkaccount-description": "Link an account from a third-party provider to the current user.",
        "apihelp-managetags-param-tag": "Tag to create, delete, activate or deactivate. For tag creation, the tag must not exist. For tag deletion, the tag must exist. For tag activation, the tag must exist and not be in use by an extension. For tag deactivation, the tag must be currently active and manually defined.",
        "apihelp-managetags-param-reason": "An optional reason for creating, deleting, activating or deactivating the tag.",
        "apihelp-managetags-param-ignorewarnings": "Whether to ignore any warnings that are issued during the operation.",
+       "apihelp-managetags-param-tags": "Change tags to apply to the entry in the tag management log.",
        "apihelp-managetags-example-create": "Create a tag named <kbd>spam</kbd> with the reason <kbd>For use in edit patrolling</kbd>",
        "apihelp-managetags-example-delete": "Delete the <kbd>vandlaism</kbd> tag with the reason <kbd>Misspelt</kbd>",
        "apihelp-managetags-example-activate": "Activate a tag named <kbd>spam</kbd> with the reason <kbd>For use in edit patrolling</kbd>",
        "apihelp-move-param-unwatch": "Remove the page and the redirect from the current user's watchlist.",
        "apihelp-move-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
        "apihelp-move-param-ignorewarnings": "Ignore any warnings.",
+       "apihelp-move-param-tags": "Change tags to apply to the entry in the move log and to the null revision on the destination page.",
        "apihelp-move-example-move": "Move <kbd>Badtitle</kbd> to <kbd>Goodtitle</kbd> without leaving a redirect.",
  
        "apihelp-opensearch-description": "Search the wiki using the OpenSearch protocol.",
        "apihelp-query+imageinfo-param-extmetadatamultilang": "If translations for extmetadata property are available, fetch all of them.",
        "apihelp-query+imageinfo-param-extmetadatafilter": "If specified and non-empty, only these keys will be returned for $1prop=extmetadata.",
        "apihelp-query+imageinfo-param-urlparam": "A handler specific parameter string. For example, PDFs might use <kbd>page15-100px</kbd>. <var>$1urlwidth</var> must be used and be consistent with <var>$1urlparam</var>.",
 -      "apihelp-query+imageinfo-param-badfilecontexttitle": "If <kbd>$2prop=badfile</kbd> is set, this is the page title used when evaluting the [[MediaWiki:Bad image list]]",
 +      "apihelp-query+imageinfo-param-badfilecontexttitle": "If <kbd>$2prop=badfile</kbd> is set, this is the page title used when evaluating the [[MediaWiki:Bad image list]]",
        "apihelp-query+imageinfo-param-localonly": "Look only for files in the local repository.",
        "apihelp-query+imageinfo-example-simple": "Fetch information about the current version of [[:File:Albert Einstein Head.jpg]].",
        "apihelp-query+imageinfo-example-dated": "Fetch information about versions of [[:File:Test.jpg]] from 2008 and later.",
        "apihelp-query+usercontribs-param-limit": "The maximum number of contributions to return.",
        "apihelp-query+usercontribs-param-start": "The start timestamp to return from.",
        "apihelp-query+usercontribs-param-end": "The end timestamp to return to.",
 -      "apihelp-query+usercontribs-param-user": "The users to retrieve contributions for.",
 -      "apihelp-query+usercontribs-param-userprefix": "Retrieve contributions for all users whose names begin with this value. Overrides $1user.",
 +      "apihelp-query+usercontribs-param-user": "The users to retrieve contributions for. Cannot be used with <var>$1userids</var> or <var>$1userprefix</var>.",
 +      "apihelp-query+usercontribs-param-userprefix": "Retrieve contributions for all users whose names begin with this value. Cannot be used with <var>$1user</var> or <var>$1userids</var>.",
 +      "apihelp-query+usercontribs-param-userids": "The user IDs to retrieve contributions for. Cannot be used with <var>$1user</var> or <var>$1userprefix</var>.",
        "apihelp-query+usercontribs-param-namespace": "Only list contributions in these namespaces.",
        "apihelp-query+usercontribs-param-prop": "Include additional pieces of information:",
        "apihelp-query+usercontribs-paramvalue-prop-ids": "Adds the page ID and revision ID.",
        "apihelp-revisiondelete-param-show": "What to unhide for each revision.",
        "apihelp-revisiondelete-param-suppress": "Whether to suppress data from administrators as well as others.",
        "apihelp-revisiondelete-param-reason": "Reason for the deletion or undeletion.",
 +      "apihelp-revisiondelete-param-tags": "Tags to apply to the entry in the deletion log.",
        "apihelp-revisiondelete-example-revision": "Hide content for revision <kbd>12345</kbd> on the page <kbd>Main Page</kbd>.",
        "apihelp-revisiondelete-example-log": "Hide all data on log entry <kbd>67890</kbd> with the reason <kbd>BLP violation</kbd>.",
  
        "apihelp-setnotificationtimestamp-example-pagetimestamp": "Set the notification timestamp for <kbd>Main page</kbd> so all edits since 1 January 2012 are unviewed.",
        "apihelp-setnotificationtimestamp-example-allpages": "Reset the notification status for pages in the <kbd>{{ns:user}}</kbd> namespace.",
  
 +      "apihelp-setpagelanguage-description": "Change the language of a page.",
 +      "apihelp-setpagelanguage-description-disabled": "Changing the language of a page is not allowed on this wiki.\n\nEnable <var>[[mw:Manual:$wgPageLanguageUseDB|$wgPageLanguageUseDB]]</var> to use this action.",
 +      "apihelp-setpagelanguage-param-title": "Title of the page whose language you wish to change. Cannot be used together with <var>$1pageid</var>.",
 +      "apihelp-setpagelanguage-param-pageid": "Page ID of the page whose language you wish to change. Cannot be used together with <var>$1title</var>.",
 +      "apihelp-setpagelanguage-param-lang": "Language code of the language to change the page to. Use <kbd>default</kbd> to reset the page to the wiki's default content language.",
 +      "apihelp-setpagelanguage-param-reason": "Reason for the change.",
 +      "apihelp-setpagelanguage-param-tags": "Change tags to apply to the log entry resulting from this action.",
 +      "apihelp-setpagelanguage-example-language": "Change the language of <kbd>Main Page</kbd> to Basque.",
 +      "apihelp-setpagelanguage-example-default": "Change the language of the page with ID 123 to the wiki's default content language.",
 +
        "apihelp-stashedit-description": "Prepare an edit in shared cache.\n\nThis is intended to be used via AJAX from the edit form to improve the performance of the page save.",
        "apihelp-stashedit-param-title": "Title of the page being edited.",
        "apihelp-stashedit-param-section": "Section number. <kbd>0</kbd> for the top section, <kbd>new</kbd> for a new section.",
        "apihelp-tag-param-add": "Tags to add. Only manually defined tags can be added.",
        "apihelp-tag-param-remove": "Tags to remove. Only tags that are either manually defined or completely undefined can be removed.",
        "apihelp-tag-param-reason": "Reason for the change.",
 +      "apihelp-tag-param-tags": "Tags to apply to the log entry that will be created as a result of this action.",
        "apihelp-tag-example-rev": "Add the <kbd>vandalism</kbd> tag to revision ID 123 without specifying a reason",
        "apihelp-tag-example-log": "Remove the <kbd>spam</kbd> tag from log entry ID 123 with the reason <kbd>Wrongly applied</kbd>",
  
        "apihelp-userrights-param-add": "Add the user to these groups.",
        "apihelp-userrights-param-remove": "Remove the user from these groups.",
        "apihelp-userrights-param-reason": "Reason for the change.",
 +      "apihelp-userrights-param-tags": "Change tags to apply to the entry in the user rights log.",
        "apihelp-userrights-example-user": "Add user <kbd>FooBot</kbd> to group <kbd>bot</kbd>, and remove from groups <kbd>sysop</kbd> and <kbd>bureaucrat</kbd>.",
        "apihelp-userrights-example-userid": "Add the user with ID <kbd>123</kbd> to group <kbd>bot</kbd>, and remove from groups <kbd>sysop</kbd> and <kbd>bureaucrat</kbd>.",
  
        "apierror-autoblocked": "Your IP address has been blocked automatically, because it was used by a blocked user.",
        "apierror-badconfig-resulttoosmall": "The value of <code>$wgAPIMaxResultSize</code> on this wiki is too small to hold basic result information.",
        "apierror-badcontinue": "Invalid continue param. You should pass the original value returned by the previous query.",
 -      "apierror-baddiff": "The diff cannot be retrieved, one or both revisions do not exist or you do not have permission to view them.",
 +      "apierror-baddiff": "The diff cannot be retrieved. One or both revisions do not exist or you do not have permission to view them.",
        "apierror-baddiffto": "<var>$1diffto</var> must be set to a non-negative number, <kbd>prev</kbd>, <kbd>next</kbd> or <kbd>cur</kbd>.",
        "apierror-badformat-generic": "The requested format $1 is not supported for content model $2.",
        "apierror-badformat": "The requested format $1 is not supported for content model $2 used by $3.",
        "apierror-invalidtitle": "Bad title \"$1\".",
        "apierror-invalidurlparam": "Invalid value for <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
        "apierror-invaliduser": "Invalid username \"$1\".",
 +      "apierror-invaliduserid": "User ID <var>$1</var> is not valid.",
        "apierror-maxlag-generic": "Waiting for a database server: $1 {{PLURAL:$1|second|seconds}} lagged.",
        "apierror-maxlag": "Waiting for $2: $1 {{PLURAL:$1|second|seconds}} lagged.",
        "apierror-mimesearchdisabled": "MIME search is disabled in Miser Mode.",
        "apierror-opensearch-json-warnings": "Warnings cannot be represented in OpenSearch JSON format.",
        "apierror-pagecannotexist": "Namespace doesn't allow actual pages.",
        "apierror-pagedeleted": "The page has been deleted since you fetched its timestamp.",
 +      "apierror-pagelang-disabled": "Changing the language of a page is not allowed on this wiki.",
        "apierror-paramempty": "The parameter <var>$1</var> may not be empty.",
        "apierror-parsetree-notwikitext": "<kbd>prop=parsetree</kbd> is only supported for wikitext content.",
        "apierror-parsetree-notwikitext-title": "<kbd>prop=parsetree</kbd> is only supported for wikitext content. $1 uses content model $2.",
@@@ -46,6 -46,7 +46,7 @@@
        "apihelp-block-param-allowusertalk": "{{doc-apihelp-param|block|allowusertalk}}\n* See also {{msg-mw|ipb-disableusertalk}}",
        "apihelp-block-param-reblock": "{{doc-apihelp-param|block|reblock}}",
        "apihelp-block-param-watchuser": "{{doc-apihelp-param|block|watchuser}}",
+       "apihelp-block-param-tags": "{{doc-apihelp-param|block|tags}}",
        "apihelp-block-example-ip-simple": "{{doc-apihelp-example|block}}",
        "apihelp-block-example-user-complex": "{{doc-apihelp-example|block}}",
        "apihelp-changeauthenticationdata-description": "{{doc-apihelp-description|changeauthenticationdata}}",
        "apihelp-help-example-query": "{{doc-apihelp-example|help}}",
        "apihelp-imagerotate-description": "{{doc-apihelp-description|imagerotate}}",
        "apihelp-imagerotate-param-rotation": "{{doc-apihelp-param|imagerotate|rotation}}",
 +      "apihelp-imagerotate-param-tags": "{{doc-apihelp-param|imagerotate|tags}}",
        "apihelp-imagerotate-example-simple": "{{doc-apihelp-example|imagerotate}}",
        "apihelp-imagerotate-example-generator": "{{doc-apihelp-example|imagerotate}}",
        "apihelp-import-description": "{{doc-apihelp-description|import}}",
        "apihelp-import-param-templates": "{{doc-apihelp-param|import|templates}}",
        "apihelp-import-param-namespace": "{{doc-apihelp-param|import|namespace}}",
        "apihelp-import-param-rootpage": "{{doc-apihelp-param|import|rootpage}}",
+       "apihelp-import-param-tags": "{{doc-apihelp-param|import|tags}}",
        "apihelp-import-example-import": "{{doc-apihelp-example|import}}",
        "apihelp-linkaccount-description": "{{doc-apihelp-description|linkaccount}}",
        "apihelp-linkaccount-example-link": "{{doc-apihelp-example|linkaccount}}",
        "apihelp-managetags-param-tag": "{{doc-apihelp-param|managetags|tag}}",
        "apihelp-managetags-param-reason": "{{doc-apihelp-param|managetags|reason}}",
        "apihelp-managetags-param-ignorewarnings": "{{doc-apihelp-param|managetags|ignorewarnings}}",
+       "apihelp-managetags-param-tags": "{{doc-apihelp-param|managetags|tags}}",
        "apihelp-managetags-example-create": "{{doc-apihelp-example|managetags}}",
        "apihelp-managetags-example-delete": "{{doc-apihelp-example|managetags|info={{doc-important|The text \"vandlaism\" in this message is intentionally misspelled; the example being documented by this message is the deletion of a misspelled tag.}}}}",
        "apihelp-managetags-example-activate": "{{doc-apihelp-example|managetags}}",
        "apihelp-move-param-unwatch": "{{doc-apihelp-param|move|unwatch}}",
        "apihelp-move-param-watchlist": "{{doc-apihelp-param|move|watchlist}}",
        "apihelp-move-param-ignorewarnings": "{{doc-apihelp-param|move|ignorewarnings}}",
+       "apihelp-move-param-tags": "{{doc-apihelp-param|move|tags}}",
        "apihelp-move-example-move": "{{doc-apihelp-example|move}}",
        "apihelp-opensearch-description": "{{doc-apihelp-description|opensearch}}",
        "apihelp-opensearch-param-search": "{{doc-apihelp-param|opensearch|search}}",
        "apihelp-query+allfileusages-param-prefix": "{{doc-apihelp-param|query+allfileusages|prefix}}",
        "apihelp-query+allfileusages-param-unique": "{{doc-apihelp-param|query+allfileusages|unique}}",
        "apihelp-query+allfileusages-param-prop": "{{doc-apihelp-param|query+allfileusages|prop|paramvalues=1}}",
 -      "apihelp-query+allfileusages-paramvalue-prop-ids": "{{doc-apihelp-param|query+allfileusages|prop|ids}}",
 -      "apihelp-query+allfileusages-paramvalue-prop-title": "{{doc-apihelp-param|query+allfileusages|prop|title}}",
 +      "apihelp-query+allfileusages-paramvalue-prop-ids": "{{doc-apihelp-paramvalue|query+allfileusages|prop|ids}}",
 +      "apihelp-query+allfileusages-paramvalue-prop-title": "{{doc-apihelp-paramvalue|query+allfileusages|prop|title}}",
        "apihelp-query+allfileusages-param-limit": "{{doc-apihelp-param|query+allfileusages|limit}}",
        "apihelp-query+allfileusages-param-dir": "{{doc-apihelp-param|query+allfileusages|dir}}",
        "apihelp-query+allfileusages-example-B": "{{doc-apihelp-example|query+allfileusages}}",
        "apihelp-query+allredirects-param-prefix": "{{doc-apihelp-param|query+allredirects|prefix}}",
        "apihelp-query+allredirects-param-unique": "{{doc-apihelp-param|query+allredirects|unique}}",
        "apihelp-query+allredirects-param-prop": "{{doc-apihelp-param|query+allredirects|prop|paramvalues=1}}",
 -      "apihelp-query+allredirects-paramvalue-prop-ids": "{{doc-apihelp-param|query+allredirects|prop|ids}}",
 -      "apihelp-query+allredirects-paramvalue-prop-title": "{{doc-apihelp-param|query+allredirects|prop|title}}",
 -      "apihelp-query+allredirects-paramvalue-prop-fragment": "{{doc-apihelp-param|query+allredirects|prop|fragment}}",
 -      "apihelp-query+allredirects-paramvalue-prop-interwiki": "{{doc-apihelp-param|query+allredirects|prop|interwiki}}",
 +      "apihelp-query+allredirects-paramvalue-prop-ids": "{{doc-apihelp-paramvalue|query+allredirects|prop|ids}}",
 +      "apihelp-query+allredirects-paramvalue-prop-title": "{{doc-apihelp-paramvalue|query+allredirects|prop|title}}",
 +      "apihelp-query+allredirects-paramvalue-prop-fragment": "{{doc-apihelp-paramvalue|query+allredirects|prop|fragment}}",
 +      "apihelp-query+allredirects-paramvalue-prop-interwiki": "{{doc-apihelp-paramvalue|query+allredirects|prop|interwiki}}",
        "apihelp-query+allredirects-param-namespace": "{{doc-apihelp-param|query+allredirects|namespace}}",
        "apihelp-query+allredirects-param-limit": "{{doc-apihelp-param|query+allredirects|limit}}",
        "apihelp-query+allredirects-param-dir": "{{doc-apihelp-param|query+allredirects|dir}}",
        "apihelp-query+categorymembers-param-title": "{{doc-apihelp-param|query+categorymembers|title}}",
        "apihelp-query+categorymembers-param-pageid": "{{doc-apihelp-param|query+categorymembers|pageid}}",
        "apihelp-query+categorymembers-param-prop": "{{doc-apihelp-param|query+categorymembers|prop|paramvalues=1}}",
 -      "apihelp-query+categorymembers-paramvalue-prop-ids": "{{doc-apihelp-param|query+categorymembers|prop|ids}}",
 -      "apihelp-query+categorymembers-paramvalue-prop-title": "{{doc-apihelp-param|query+categorymembers|prop|title}}",
 -      "apihelp-query+categorymembers-paramvalue-prop-sortkey": "{{doc-apihelp-param|query+categorymembers|prop|sortkey}}",
 -      "apihelp-query+categorymembers-paramvalue-prop-sortkeyprefix": "{{doc-apihelp-param|query+categorymembers|prop|sortkeyprefix}}",
 -      "apihelp-query+categorymembers-paramvalue-prop-type": "{{doc-apihelp-param|query+categorymembers|prop|type}}",
 -      "apihelp-query+categorymembers-paramvalue-prop-timestamp": "{{doc-apihelp-param|query+categorymembers|prop|timestamp}}",
 +      "apihelp-query+categorymembers-paramvalue-prop-ids": "{{doc-apihelp-paramvalue|query+categorymembers|prop|ids}}",
 +      "apihelp-query+categorymembers-paramvalue-prop-title": "{{doc-apihelp-paramvalue|query+categorymembers|prop|title}}",
 +      "apihelp-query+categorymembers-paramvalue-prop-sortkey": "{{doc-apihelp-paramvalue|query+categorymembers|prop|sortkey}}",
 +      "apihelp-query+categorymembers-paramvalue-prop-sortkeyprefix": "{{doc-apihelp-paramvalue|query+categorymembers|prop|sortkeyprefix}}",
 +      "apihelp-query+categorymembers-paramvalue-prop-type": "{{doc-apihelp-paramvalue|query+categorymembers|prop|type}}",
 +      "apihelp-query+categorymembers-paramvalue-prop-timestamp": "{{doc-apihelp-paramvalue|query+categorymembers|prop|timestamp}}",
        "apihelp-query+categorymembers-param-namespace": "{{doc-apihelp-param|query+categorymembers|namespace}}",
        "apihelp-query+categorymembers-param-type": "{{doc-apihelp-param|query+categorymembers|type}}",
        "apihelp-query+categorymembers-param-limit": "{{doc-apihelp-param|query+categorymembers|limit}}",
        "apihelp-query+langbacklinks-param-title": "{{doc-apihelp-param|query+langbacklinks|title}}",
        "apihelp-query+langbacklinks-param-limit": "{{doc-apihelp-param|query+langbacklinks|limit}}",
        "apihelp-query+langbacklinks-param-prop": "{{doc-apihelp-param|query+langbacklinks|prop|paramvalues=1}}",
 -      "apihelp-query+langbacklinks-paramvalue-prop-lllang": "{{doc-apihelp-param|query+langbacklinks|prop|lllang}}",
 -      "apihelp-query+langbacklinks-paramvalue-prop-lltitle": "{{doc-apihelp-param|query+langbacklinks|prop|lltitle}}",
 +      "apihelp-query+langbacklinks-paramvalue-prop-lllang": "{{doc-apihelp-paramvalue|query+langbacklinks|prop|lllang}}",
 +      "apihelp-query+langbacklinks-paramvalue-prop-lltitle": "{{doc-apihelp-paramvalue|query+langbacklinks|prop|lltitle}}",
        "apihelp-query+langbacklinks-param-dir": "{{doc-apihelp-param|query+langbacklinks|dir}}",
        "apihelp-query+langbacklinks-example-simple": "{{doc-apihelp-example|query+langbacklinks}}",
        "apihelp-query+langbacklinks-example-generator": "{{doc-apihelp-example|query+langbacklinks}}",
        "apihelp-query+pageswithprop-description": "{{doc-apihelp-description|query+pageswithprop}}",
        "apihelp-query+pageswithprop-param-propname": "{{doc-apihelp-param|query+pageswithprop|propname}}",
        "apihelp-query+pageswithprop-param-prop": "{{doc-apihelp-param|query+pageswithprop|prop|paramvalues=1}}",
 -      "apihelp-query+pageswithprop-paramvalue-prop-ids": "{{doc-apihelp-param|query+pageswithprop|prop|ids}}",
 -      "apihelp-query+pageswithprop-paramvalue-prop-title": "{{doc-apihelp-param|query+pageswithprop|prop|title}}",
 -      "apihelp-query+pageswithprop-paramvalue-prop-value": "{{doc-apihelp-param|query+pageswithprop|prop|value}}",
 +      "apihelp-query+pageswithprop-paramvalue-prop-ids": "{{doc-apihelp-paramvalue|query+pageswithprop|prop|ids}}",
 +      "apihelp-query+pageswithprop-paramvalue-prop-title": "{{doc-apihelp-paramvalue|query+pageswithprop|prop|title}}",
 +      "apihelp-query+pageswithprop-paramvalue-prop-value": "{{doc-apihelp-paramvalue|query+pageswithprop|prop|value}}",
        "apihelp-query+pageswithprop-param-limit": "{{doc-apihelp-param|query+pageswithprop|limit}}",
        "apihelp-query+pageswithprop-param-dir": "{{doc-apihelp-param|query+pageswithprop|dir}}",
        "apihelp-query+pageswithprop-example-simple": "{{doc-apihelp-example|query+pageswithprop}}",
        "apihelp-query+usercontribs-param-end": "{{doc-apihelp-param|query+usercontribs|end}}",
        "apihelp-query+usercontribs-param-user": "{{doc-apihelp-param|query+usercontribs|user}}",
        "apihelp-query+usercontribs-param-userprefix": "{{doc-apihelp-param|query+usercontribs|userprefix}}",
 +      "apihelp-query+usercontribs-param-userids": "{{doc-apihelp-param|query+usercontribs|userids}}",
        "apihelp-query+usercontribs-param-namespace": "{{doc-apihelp-param|query+usercontribs|namespace}}",
        "apihelp-query+usercontribs-param-prop": "{{doc-apihelp-param|query+usercontribs|prop|paramvalues=1}}",
        "apihelp-query+usercontribs-paramvalue-prop-ids": "{{doc-apihelp-paramvalue|query+usercontribs|prop|ids}}",
        "apihelp-revisiondelete-param-show": "{{doc-apihelp-param|revisiondelete|show}}",
        "apihelp-revisiondelete-param-suppress": "{{doc-apihelp-param|revisiondelete|suppress}}",
        "apihelp-revisiondelete-param-reason": "{{doc-apihelp-param|revisiondelete|reason}}",
 +      "apihelp-revisiondelete-param-tags": "{{doc-apihelp-param|revisiondelete|tags}}",
        "apihelp-revisiondelete-example-revision": "{{doc-apihelp-example|revisiondelete}}",
        "apihelp-revisiondelete-example-log": "{{doc-apihelp-example|revisiondelete}}",
        "apihelp-rollback-description": "{{doc-apihelp-description|rollback}}",
        "apihelp-setnotificationtimestamp-example-page": "{{doc-apihelp-example|setnotificationtimestamp}}",
        "apihelp-setnotificationtimestamp-example-pagetimestamp": "{{doc-apihelp-example|setnotificationtimestamp}}",
        "apihelp-setnotificationtimestamp-example-allpages": "{{doc-apihelp-example|setnotificationtimestamp}}",
 +      "apihelp-setpagelanguage-description": "{{doc-apihelp-description|setpagelanguage}}",
 +      "apihelp-setpagelanguage-description-disabled": "{{doc-apihelp-description|setpagelanguage|info=This message is used when changing the language of a page is not allowed on the wiki because <var>[[mw:Manual:$wgPageLanguageUseDB|$wgPageLanguageUseDB]]</var> is not enabled.|seealso={{msg-mw|apihelp-setpagelanguage-description}}}}",
 +      "apihelp-setpagelanguage-param-title": "{{doc-apihelp-param|setpagelanguage|title}}",
 +      "apihelp-setpagelanguage-param-pageid": "{{doc-apihelp-param|setpagelanguage|pageid}}",
 +      "apihelp-setpagelanguage-param-lang": "{{doc-apihelp-param|setpagelanguage|lang}}",
 +      "apihelp-setpagelanguage-param-reason": "{{doc-apihelp-param|setpagelanguage|reason}}",
 +      "apihelp-setpagelanguage-param-tags": "{{doc-apihelp-param|setpagelanguage|tags}}",
 +      "apihelp-setpagelanguage-example-language": "{{doc-apihelp-example|setpagelanguage}}",
 +      "apihelp-setpagelanguage-example-default": "{{doc-apihelp-example|setpagelanguage}}",
        "apihelp-stashedit-description": "{{doc-apihelp-description|stashedit}}",
        "apihelp-stashedit-param-title": "{{doc-apihelp-param|stashedit|title}}",
        "apihelp-stashedit-param-section": "{{doc-apihelp-param|stashedit|section}}",
        "apihelp-tag-param-add": "{{doc-apihelp-param|tag|add}}",
        "apihelp-tag-param-remove": "{{doc-apihelp-param|tag|remove}}",
        "apihelp-tag-param-reason": "{{doc-apihelp-param|tag|reason}}",
 +      "apihelp-tag-param-tags": "{{doc-apihelp-param|tag|tags}}",
        "apihelp-tag-example-rev": "{{doc-apihelp-example|tag}}",
        "apihelp-tag-example-log": "{{doc-apihelp-example|tag}}",
        "apihelp-tokens-description": "{{doc-apihelp-description|tokens}}",
        "apihelp-userrights-param-add": "{{doc-apihelp-param|userrights|add}}",
        "apihelp-userrights-param-remove": "{{doc-apihelp-param|userrights|remove}}",
        "apihelp-userrights-param-reason": "{{doc-apihelp-param|userrights|reason}}",
 +      "apihelp-userrights-param-tags": "{{doc-apihelp-param|userrights|tags}}",
        "apihelp-userrights-example-user": "{{doc-apihelp-example|userrights}}",
        "apihelp-userrights-example-userid": "{{doc-apihelp-example|userrights}}",
        "apihelp-validatepassword-description": "{{doc-apihelp-description|validatepassword}}",
        "apihelp-jsonfm-description": "{{doc-apihelp-description|jsonfm|seealso=* {{msg-mw|apihelp-json-description}}}}",
        "apihelp-none-description": "{{doc-apihelp-description|none}}",
        "apihelp-php-description": "{{doc-apihelp-description|php|seealso=* {{msg-mw|apihelp-phpfm-description}}}}",
 -      "apihelp-php-param-formatversion": "{{doc-apihelp-param|json|formatversion}}",
 +      "apihelp-php-param-formatversion": "{{doc-apihelp-param|php|formatversion}}",
        "apihelp-phpfm-description": "{{doc-apihelp-description|phpfm|seealso=* {{msg-mw|apihelp-php-description}}}}",
        "apihelp-rawfm-description": "{{doc-apihelp-description|rawfm|seealso=* {{msg-mw|apihelp-raw-description}}}}",
        "apihelp-xml-description": "{{doc-apihelp-description|xml|seealso=* {{msg-mw|apihelp-xmlfm-description}}}}",
        "apierror-invalidtitle": "{{doc-apierror}}\n\nParameters:\n* $1 - Title that is invalid",
        "apierror-invalidurlparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".\n* $2 - Key\n* $3 - Value.",
        "apierror-invaliduser": "{{doc-apierror}}\n\nParameters:\n* $1 - User name that is invalid.",
 +      "apierror-invaliduserid": "{{doc-apierror}}",
        "apierror-maxlag-generic": "{{doc-apierror}}\n\nParameters:\n* $1 - Database is lag in seconds.",
        "apierror-maxlag": "{{doc-apierror}}\n\nParameters:\n* $1 - Database lag in seconds.\n* $2 - Database server that is lagged.",
        "apierror-mimesearchdisabled": "{{doc-apierror}}",
        "apierror-opensearch-json-warnings": "{{doc-apierror}}",
        "apierror-pagecannotexist": "{{doc-apierror}}",
        "apierror-pagedeleted": "{{doc-apierror}}",
 +      "apierror-pagelang-disabled": "{{doc-apierror}}",
        "apierror-paramempty": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
        "apierror-parsetree-notwikitext": "{{doc-apierror}}",
        "apierror-parsetree-notwikitext-title": "{{doc-apierror}}\n\nParameters:\n* $1 - Page title.\n* $2 - Content model.",
@@@ -127,15 -127,7 +127,15 @@@ class SpecialBlock extends FormSpecialP
         */
        protected function getFormFields() {
                global $wgBlockAllowsUTEdit;
 -
 +              if ( !wfMessage( 'ipbreason-dropdown' )->inContentLanguage()->isDisabled() ) {
 +                      $reasonsList = Xml::getArrayFromWikiTextList(
 +                              wfMessage( 'ipbreason-dropdown' )->inContentLanguage()->text()
 +                      );
 +                      $this->getOutput()->addModules( 'mediawiki.reasonSuggest' );
 +                      $this->getOutput()->addJsConfigVars( [
 +                              'reasons' => $reasonsList
 +                      ] );
 +              }
                $user = $this->getUser();
  
                $suggestedDurations = self::getSuggestedDurations();
                $blockIds = array_merge( [ $status['id'] ], $status['autoIds'] );
                $logEntry->setRelations( [ 'ipb_id' => $blockIds ] );
                $logId = $logEntry->insert();
+               if ( count( $data['Tags'] ) ) {
+                       $logEntry->setTags( $data['Tags'] );
+               }
                $logEntry->publish( $logId );
  
-               # Report to the user
                return true;
        }