Merge "User.php: Update 'setEmailWithConfirmation' for notification email"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 10 Apr 2016 05:30:11 +0000 (05:30 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 10 Apr 2016 05:30:11 +0000 (05:30 +0000)
1  2 
includes/user/User.php
languages/i18n/en.json
languages/i18n/qqq.json

diff --combined includes/user/User.php
   */
  
  use MediaWiki\Session\SessionManager;
 +use MediaWiki\Session\Token;
  
  /**
   * String Some punctuation to prevent editing from broken text-mangling proxies.
 - * @deprecated since 1.27, use \\MediaWiki\\Session\\Token::SUFFIX
 + * @deprecated since 1.27, use \MediaWiki\Session\Token::SUFFIX
   * @ingroup Constants
   */
 -define( 'EDIT_TOKEN_SUFFIX', MediaWiki\Session\Token::SUFFIX );
 +define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
  
  /**
   * The User object encapsulates all of the user-specific settings (user_id,
@@@ -54,7 -53,7 +54,7 @@@ class User implements IDBAccessObject 
        /**
         * Global constant made accessible as class constants so that autoloader
         * magic can be used.
 -       * @deprecated since 1.27, use \\MediaWiki\\Session\\Token::SUFFIX
 +       * @deprecated since 1.27, use \MediaWiki\Session\Token::SUFFIX
         */
        const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX;
  
  
        /** Cache variables */
        // @{
 +      /** @var int */
        public $mId;
        /** @var string */
        public $mName;
                        || strlen( $name ) > $wgMaxNameChars
                        || $name != $wgContLang->ucfirst( $name )
                ) {
 -                      wfDebugLog( 'username', __METHOD__ .
 -                              ": '$name' invalid due to empty, IP, slash, length, or lowercase" );
                        return false;
                }
  
                if ( is_null( $parsed )
                        || $parsed->getNamespace()
                        || strcmp( $name, $parsed->getPrefixedText() ) ) {
 -                      wfDebugLog( 'username', __METHOD__ .
 -                              ": '$name' invalid due to ambiguous prefixes" );
                        return false;
                }
  
                        '\x{e000}-\x{f8ff}' . # private use
                        ']/u';
                if ( preg_match( $unicodeBlacklist, $name ) ) {
 -                      wfDebugLog( 'username', __METHOD__ .
 -                              ": '$name' invalid due to blacklisted characters" );
                        return false;
                }
  
                        // Don't load if this was initialized from an ID
                        $this->load();
                }
 -              return $this->mId;
 +
 +              return (int)$this->mId;
        }
  
        /**
                // Get the "last viewed rev" timestamp from the oldest message notification
                $timestamp = $dbr->selectField( 'user_newtalk',
                        'MIN(user_last_timestamp)',
 -                      $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getID() ],
 +                      $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
                        __METHOD__ );
                $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
                return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
                        return Status::newGood( true );
                }
  
+               $type = $oldaddr != '' ? 'changed' : 'set';
+               $notificationResult = null;
+               if ( $wgEmailAuthentication ) {
+                       // Send the user an email notifying the user of the change in registered
+                       // email address on their previous email address
+                       if ( $type == 'changed' ) {
+                               $change = $str != '' ? 'changed' : 'removed';
+                               $notificationResult = $this->sendMail(
+                                       wfMessage( 'notificationemail_subject_' . $change )->text(),
+                                       wfMessage( 'notificationemail_body_' . $change,
+                                               $this->getRequest()->getIP(),
+                                               $this->getName(),
+                                               $str )->text()
+                               );
+                       }
+               }
                $this->setEmail( $str );
  
                if ( $str !== '' && $wgEmailAuthentication ) {
                        // Send a confirmation request to the new address if needed
-                       $type = $oldaddr != '' ? 'changed' : 'set';
                        $result = $this->sendConfirmationMail( $type );
+                       if ( $notificationResult !== null ) {
+                               $result->merge( $notificationResult );
+                       }
                        if ( $result->isGood() ) {
-                               // Say to the caller that a confirmation mail has been sent
+                               // Say to the caller that a confirmation and notification mail has been sent
                                $result->value = 'eauth';
                        }
                } else {
                if ( $this->getId() ) {
                        $dbw->insert( 'user_groups',
                                [
 -                                      'ug_user' => $this->getID(),
 +                                      'ug_user' => $this->getId(),
                                        'ug_group' => $group,
                                ],
                                __METHOD__,
                $dbw = wfGetDB( DB_MASTER );
                $dbw->delete( 'user_groups',
                        [
 -                              'ug_user' => $this->getID(),
 +                              'ug_user' => $this->getId(),
                                'ug_group' => $group,
                        ], __METHOD__
                );
                // Remember that the user was in this group
                $dbw->insert( 'user_former_groups',
                        [
 -                              'ufg_user' => $this->getID(),
 +                              'ufg_user' => $this->getId(),
                                'ufg_group' => $group,
                        ],
                        __METHOD__,
         * @return bool
         */
        public function isLoggedIn() {
 -              return $this->getID() != 0;
 +              return $this->getId() != 0;
        }
  
        /**
         */
        public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
                if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
 -                      WatchedItemStore::getDefaultInstance()->addWatchBatch( [
 -                              [ $this, $title->getSubjectPage() ],
 -                              [ $this, $title->getTalkPage() ],
 -                      ]
 +                      WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
 +                              $this,
 +                              [ $title->getSubjectPage(), $title->getTalkPage() ]
                        );
                }
                $this->invalidateCache();
                if ( $this->isLoggedIn() && $this->isBlocked() ) {
                        return $this->spreadBlock();
                }
 +
                return false;
        }
  
         * @since 1.27
         * @param string|array $salt Array of Strings Optional function-specific data for hashing
         * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
 -       * @return MediaWiki\\Session\\Token The new edit token
 +       * @return MediaWiki\Session\Token The new edit token
         */
        public function getEditTokenObject( $salt = '', $request = null ) {
                if ( $this->isAnon() ) {
  
        /**
         * Get the embedded timestamp from a token.
 -       * @deprecated since 1.27, use \\MediaWiki\\Session\\Token::getTimestamp instead.
 +       * @deprecated since 1.27, use \MediaWiki\Session\Token::getTimestamp instead.
         * @param string $val Input token
         * @return int|null
         */
         * @return bool Whether the token matches
         */
        public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
 -              $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . self::EDIT_TOKEN_SUFFIX;
 +              $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
                return $this->matchEditToken( $val, $salt, $request, $maxage );
        }
  
diff --combined languages/i18n/en.json
        "noemail": "There is no email address recorded for user \"$1\".",
        "noemailcreate": "You need to provide a valid email address.",
        "passwordsent": "A new password has been sent to the email address registered for \"$1\".\nPlease log in again after you receive it.",
 -      "blocked-mailpassword": "Your IP address is blocked from editing, and so is not allowed to use the password recovery function to prevent abuse.",
 +      "blocked-mailpassword": "Your IP address is blocked from editing. To prevent abuse, it is not allowed to use password recovery from this IP address.",
        "eauthentsent": "A confirmation email has been sent to the specified email address.\nBefore any other email is sent to the account, you will have to follow the instructions in the email, to confirm that the account is actually yours.",
        "throttled-mailpassword": "A password reset email has already been sent, within the last {{PLURAL:$1|hour|$1 hours}}.\nTo prevent abuse, only one password reset email will be sent per {{PLURAL:$1|hour|$1 hours}}.",
        "signupstart": "",
        "botpasswords-insert-failed": "Failed to add bot name \"$1\". Was it already added?",
        "botpasswords-update-failed": "Failed to update bot name \"$1\". Was it deleted?",
        "botpasswords-created-title": "Bot password created",
 -      "botpasswords-created-body": "The bot password \"$1\" was created.",
 +      "botpasswords-created-body": "The bot password for bot name \"$1\" of user \"$2\" was created.",
        "botpasswords-updated-title": "Bot password updated",
 -      "botpasswords-updated-body": "The bot password \"$1\" was updated.",
 +      "botpasswords-updated-body": "The bot password for bot name \"$1\" of user \"$2\" was updated.",
        "botpasswords-deleted-title": "Bot password deleted",
 -      "botpasswords-deleted-body": "The bot password \"$1\" was deleted.",
 +      "botpasswords-deleted-body": "The bot password for bot name \"$1\" of user \"$2\" was deleted.",
        "botpasswords-newpassword": "The new password to log in with <strong>$1</strong> is <strong>$2</strong>. <em>Please record this for future reference.</em>",
        "botpasswords-no-provider": "BotPasswordsSessionProvider is not available.",
        "botpasswords-restriction-failed": "Bot password restrictions prevent this login.",
        "recentchangeslinked-page": "Page name:",
        "recentchangeslinked-to": "Show changes to pages linked to the given page instead",
        "recentchanges-page-added-to-category": "[[:$1]] added to category",
 -      "recentchanges-page-added-to-category-bundled": "[[:$1]] and {{PLURAL:$2|one page|$2 pages}} added to category",
 +      "recentchanges-page-added-to-category-bundled": "[[:$1]] and [[Special:WhatLinksHere/$1|{{PLURAL:$2|one page|$2 pages}}]] added to category",
        "recentchanges-page-removed-from-category": "[[:$1]] removed from category",
 -      "recentchanges-page-removed-from-category-bundled": "[[:$1]] and {{PLURAL:$2|one page|$2 pages}} removed from category",
 +      "recentchanges-page-removed-from-category-bundled": "[[:$1]] and [[Special:WhatLinksHere/$1|{{PLURAL:$2|one page|$2 pages}}]] removed from category",
        "autochange-username": "MediaWiki automatic change",
        "upload": "Upload file",
        "uploadbtn": "Upload file",
        "uploadstash-summary": "This page provides access to files that are uploaded or in the process of uploading, but are not yet published to the wiki. These files are not visible to anyone but the user who uploaded them.",
        "uploadstash-clear": "Clear stashed files",
        "uploadstash-nofiles": "You have no stashed files.",
 -      "uploadstash-badtoken": "Performing that action failed. Perhaps because your editing credentials expired. Please try again.",
 +      "uploadstash-badtoken": "Performing that action failed, perhaps because your editing credentials expired. Please try again.",
        "uploadstash-errclear": "Clearing the files failed.",
        "uploadstash-refresh": "Refresh the list of files",
 +      "uploadstash-thumbnail": "view thumbnail",
        "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.",
        "changecontentmodel-title-label": "Page title",
        "changecontentmodel-model-label": "New content model",
        "changecontentmodel-reason-label": "Reason:",
 +      "changecontentmodel-submit": "Change",
        "changecontentmodel-success-title": "The content model was changed",
        "changecontentmodel-success-text": "The content type of [[:$1]] has been changed.",
        "changecontentmodel-cannot-convert": "The content on [[:$1]] cannot be converted to a type of $2.",
        "unblock-summary": "",
        "blockip": "Block {{GENDER:$1|user}}",
        "blockip-legend": "Block user",
 -      "blockiptext": "Use the form below to block write access from a specific IP address or username.\nThis should be done only to prevent vandalism, and in accordance with [[{{MediaWiki:Policy-url}}|policy]].\nFill in a specific reason below (for example, citing particular pages that were vandalized).\nYou can block IP ranges using the [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] syntax; the largest allowed range is /$1 for IPv4 and /$2 for IPv6.",
 +      "blockiptext": "Use the form below to block write access from a specific IP address or username.\nThis should be done only to prevent vandalism, and in accordance with [[{{MediaWiki:Policy-url}}|policy]].\nFill in a specific reason below (for example, citing particular pages that were vandalized).\nYou can block IP address ranges using the [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] syntax; the largest allowed range is /$1 for IPv4 and /$2 for IPv6.",
        "ipaddressorusername": "IP address or username:",
        "ipbexpiry": "Expiration:",
        "ipbreason": "Reason:",
        "ipb-unblock": "Unblock a username or IP address",
        "ipb-blocklist": "View existing blocks",
        "ipb-blocklist-contribs": "Contributions for {{GENDER:$1|$1}}",
 +      "ipb-blocklist-duration-left": "$1 left",
        "unblockip": "Unblock user",
        "unblockiptext": "Use the form below to restore write access to a previously blocked IP address or username.",
        "ipusubmit": "Remove this block",
        "unblock-hideuser": "You cannot unblock this user, as their username has been hidden.",
        "ipb_cant_unblock": "Error: Block ID $1 not found. It may have been unblocked already.",
        "ipb_blocked_as_range": "Error: The IP address $1 is not blocked directly and cannot be unblocked.\nIt is, however, blocked as part of the range $2, which can be unblocked.",
 -      "ip_range_invalid": "Invalid IP range.",
 +      "ip_range_invalid": "Invalid IP address range.",
        "ip_range_toolarge": "Range blocks larger than /$1 are not allowed.",
        "proxyblocker": "Proxy blocker",
        "proxyblockreason": "Your IP address has been blocked because it is an open proxy.\nPlease contact your Internet service provider or technical support of your organization and inform them of this serious security problem.",
        "import-logentry-interwiki": "transwikied $1",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|revision|revisions}} imported from $2",
        "javascripttest": "JavaScript testing",
 -      "javascripttest-backlink": "< $1",
 -      "javascripttest-title": "$1",
 -      "javascripttest-pagetext-noframework": "This page is reserved for running JavaScript tests.",
 -      "javascripttest-pagetext-unknownframework": "Unknown testing framework \"$1\".",
        "javascripttest-pagetext-unknownaction": "Unknown action \"$1\".",
 -      "javascripttest-pagetext-frameworks": "Please choose one of the following testing frameworks: $1",
 -      "javascripttest-pagetext-skins": "Choose a skin to run the tests with:",
 -      "javascripttest-qunit-name": "QUnit",
        "javascripttest-qunit-intro": "See [$1 testing documentation] on mediawiki.org.",
        "accesskey-pt-userpage": ".",
        "accesskey-pt-anonuserpage": ".",
        "confirmemail_body_set": "Someone, probably you, from IP address $1,\nhas set the email address of the account \"$2\" to this address on {{SITENAME}}.\n\nTo confirm that this account really does belong to you and activate\nemail features on {{SITENAME}}, open this link in your browser:\n\n$3\n\nIf the account does *not* belong to you, follow this link\nto cancel the email address confirmation:\n\n$5\n\nThis confirmation code will expire at $4.",
        "confirmemail_invalidated": "Email address confirmation canceled",
        "invalidateemail": "Cancel email confirmation",
+       "notificationemail_subject_changed": "{{SITENAME}} registered email address has been changed",
+       "notificationemail_subject_removed": "{{SITENAME}} registered email address has been removed",
+       "notificationemail_body_changed": "Someone, probably you, from IP address $1,\nhas changed the email address of the account \"$2\" to \"$3\" on {{SITENAME}}.\n\nIf this was not you, contact a site administrator immediately.",
+       "notificationemail_body_removed": "Someone, probably you, from IP address $1,\nhas removed the email address of the account \"$2\" on {{SITENAME}}.\n\nIf this was not you, contact a site administrator immediately.",
        "scarytranscludedisabled": "[Interwiki transcluding is disabled]",
        "scarytranscludefailed": "[Template fetch failed for $1]",
        "scarytranscludefailed-httpstatus": "[Template fetch failed for $1: HTTP $2]",
        "version-libraries-description": "Description",
        "version-libraries-authors": "Authors",
        "redirect": "Redirect by file, user, page, revision, or log ID",
 -      "redirect-legend": "Redirect to a file or page",
        "redirect-text": "",
        "redirect-summary": "This special page redirects to a file (given the filename), a page (given a revision ID or page ID), a user page (given a numeric user ID), or a log entry (given the log ID). Usage: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], or [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Go",
        "redirect-not-exists": "Value not found",
        "fileduplicatesearch": "Search for duplicate files",
        "fileduplicatesearch-summary": "Search for duplicate files based on hash values.",
 -      "fileduplicatesearch-legend": "Search for a duplicate",
        "fileduplicatesearch-filename": "Filename:",
        "fileduplicatesearch-submit": "Search",
        "fileduplicatesearch-info": "$1 Ã— $2 pixel<br />File size: $3<br />MIME type: $4",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|protected}} $3 $4 [cascading]",
        "logentry-protect-modify": "$1 {{GENDER:$2|changed}} protection level for $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|changed}} protection level for $3 $4 [cascading]",
 -      "logentry-rights-rights": "$1 {{GENDER:$2|changed}} group membership for $3 from $4 to $5",
 +      "logentry-rights-rights": "$1 {{GENDER:$2|changed}} group membership for {{GENDER:$3|$3}} from $4 to $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|changed}} group membership for $3",
        "logentry-rights-autopromote": "$1 was automatically {{GENDER:$2|promoted}} from $4 to $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|uploaded}} $3",
        "api-error-unknownerror": "Unknown error: \"$1\".",
        "api-error-uploaddisabled": "Uploading is disabled on this wiki.",
        "api-error-verification-error": "This file might be corrupt, or have the wrong extension.",
 +      "api-error-was-deleted": "A file of this name has been previously uploaded and subsequently deleted.",
        "duration-seconds": "$1 {{PLURAL:$1|second|seconds}}",
        "duration-minutes": "$1 {{PLURAL:$1|minute|minutes}}",
        "duration-hours": "$1 {{PLURAL:$1|hour|hours}}",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Symbols",
        "special-characters-group-greek": "Greek",
 +      "special-characters-group-greekextended": "Greek extended",
        "special-characters-group-cyrillic": "Cyrillic",
        "special-characters-group-arabic": "Arabic",
        "special-characters-group-arabicextended": "Arabic extended",
        "sessionprovider-generic": "$1 sessions",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "cookie-based sessions",
        "sessionprovider-nocookies": "Cookies may be disabled. Ensure you have cookies enabled and start again.",
 -      "randomrootpage": "Random root page"
 +      "randomrootpage": "Random root page",
 +      "log-action-filter-block": "Type of block:",
 +      "log-action-filter-contentmodel": "Type of contentmodel modification:",
 +      "log-action-filter-delete": "Type of deletion:",
 +      "log-action-filter-import": "Type of import:",
 +      "log-action-filter-managetags": "Type of tag management action:",
 +      "log-action-filter-move": "Type of move:",
 +      "log-action-filter-newusers": "Type of account creation:",
 +      "log-action-filter-patrol": "Type of patrol:",
 +      "log-action-filter-protect": "Type of protection:",
 +      "log-action-filter-rights": "Type of right change",
 +      "log-action-filter-suppress": "Type of suppression",
 +      "log-action-filter-upload": "Type of upload:",
 +      "log-action-filter-all": "All",
 +      "log-action-filter-block-block": "Block",
 +      "log-action-filter-block-reblock": "Block modification",
 +      "log-action-filter-block-unblock": "Unblock",
 +      "log-action-filter-contentmodel-change": "Change of Contentmodel",
 +      "log-action-filter-contentmodel-new": "Creation of page with non-standard Contentmodel",
 +      "log-action-filter-delete-delete": "Page deletion",
 +      "log-action-filter-delete-restore": "Page undeletion",
 +      "log-action-filter-delete-event": "Log deletion",
 +      "log-action-filter-delete-revision": "Revision deletion",
 +      "log-action-filter-import-interwiki": "Transwiki import",
 +      "log-action-filter-import-upload": "Import by XML upload",
 +      "log-action-filter-managetags-create": "Tag creation",
 +      "log-action-filter-managetags-delete": "Tag deletion",
 +      "log-action-filter-managetags-activate": "Tag activation",
 +      "log-action-filter-managetags-deactivate": "Tag deactivation",
 +      "log-action-filter-move-move": "Move without overwriting of redirects",
 +      "log-action-filter-move-move_redir": "Move with overwriting of redirects",
 +      "log-action-filter-newusers-create": "Creation by anonymous user",
 +      "log-action-filter-newusers-create2": "Creation by registered user",
 +      "log-action-filter-newusers-autocreate": "Automatic creation",
 +      "log-action-filter-newusers-byemail": "Creation with password sent by e-mail",
 +      "log-action-filter-patrol-patrol": "Manual patrol",
 +      "log-action-filter-patrol-autopatrol": "Automatic patrol",
 +      "log-action-filter-protect-protect": "Protection",
 +      "log-action-filter-protect-modify": "Protection modification",
 +      "log-action-filter-protect-unprotect": "Unprotection",
 +      "log-action-filter-protect-move_prot": "Moved protection",
 +      "log-action-filter-rights-rights": "Manual change",
 +      "log-action-filter-rights-autopromote": "Automatic change",
 +      "log-action-filter-suppress-event": "Log suppression",
 +      "log-action-filter-suppress-revision": "Revision suppression",
 +      "log-action-filter-suppress-delete": "Page suppression",
 +      "log-action-filter-suppress-block": "User supppression by block",
 +      "log-action-filter-suppress-reblock": "User suppression by reblock",
 +      "log-action-filter-upload-upload": "New upload",
 +      "log-action-filter-upload-overwrite": "Reupload"
  }
diff --combined languages/i18n/qqq.json
        "botpasswords-insert-failed": "Error message when saving a new bot password failed. It's likely that the failure was because the user resubmitted the form after a previous successful save. Parameters:\n* $1 - Bot name",
        "botpasswords-update-failed": "Error message when saving changes to an existing bot password failed. It's likely that the failure was because the user deleted the bot password in another browser window. Parameters:\n* $1 - Bot name",
        "botpasswords-created-title": "Title of the success page when a new bot password is created.",
 -      "botpasswords-created-body": "Success message when a new bot password is created. Parameters:\n* $1 - Bot name",
 +      "botpasswords-created-body": "Success message when a new bot password is created. Parameters:\n* $1 - Bot name\n* $2 - User name",
        "botpasswords-updated-title": "Title of the success page when a bot password is updated.",
 -      "botpasswords-updated-body": "Success message when a bot password is updated. Parameters:\n* $1 - Bot name",
 +      "botpasswords-updated-body": "Success message when a bot password is updated. Parameters:\n* $1 - Bot name\n* $2 - User name",
        "botpasswords-deleted-title": "Title of the success page when a bot password is deleted.",
 -      "botpasswords-deleted-body": "Success message when a bot password is deleted. Parameters:\n* $1 - Bot name",
 +      "botpasswords-deleted-body": "Success message when a bot password is deleted. Parameters:\n* $1 - Bot name\n* $2 - User name",
        "botpasswords-newpassword": "Success message to display the new password when a bot password is created or updated. Parameters:\n* $1 - User name to be used for login.\n* $2 - Password to be used for login.",
        "botpasswords-no-provider": "Error message when login is attempted but the BotPasswordsSessionProvider is not included in <code>$wgSessionProviders</code>.",
        "botpasswords-restriction-failed": "Error message when login is rejected because the configured restrictions were not satisfied.",
        "changeemail-submit": "Submit button on [[Special:ChangeEmail]]",
        "changeemail-throttled": "Error message shown at [[Special:ChangeEmail]] after the user has tried to login with incorrect password too many times.\n\nThe user has to wait a certain time before trying to log in again.\n\nParameters:\n* $1 - the time to wait before the next login attempt. Automatically formatted using the following duration messages:\n** {{msg-mw|Duration-millennia}}\n** {{msg-mw|Duration-centuries}}\n** {{msg-mw|Duration-decades}}\n** {{msg-mw|Duration-years}}\n** {{msg-mw|Duration-weeks}}\n** {{msg-mw|Duration-days}}\n** {{msg-mw|Duration-hours}}\n** {{msg-mw|Duration-minutes}}\n** {{msg-mw|Duration-seconds}}\n\nThis is a protection against robots trying to find the password by trying lots of them.\nThe number of attempts and waiting time are configured via [[mw:Manual:$wgPasswordAttemptThrottle|$wgPasswordAttemptThrottle]].\nThis message is used in html.\n\nSee also:\n* {{msg-mw|Changepassword-throttled}}",
        "changeemail-nochange": "Error message shown on [[Special:ChangeEmail]] if the old email address was entered in the new email address field.",
+       "notificationemail_subject_changed": "Subject of the email sent on the previously registered email address notifying them about the change in the registered email address.",
+       "notificationemail_subject_removed": "Subject of the email sent on the previously registered email address notifying them about the removal of the registered email address.",
+       "notificationemail_body_changed": "Body of the email sent on the previously registered email address notifying them about the change in the registered email address.",
+       "notificationemail_body_removed": "Body of the email sent on the previously registered email address notifying them about the removal of the registered email address.",
        "resettokens": "{{doc-special|ResetTokens}}\nIn this case \"token\" may be translated as \"key\", or similar.\n{{Identical|Reset token}}",
        "resettokens-summary": "{{ignored}}",
        "resettokens-text": "Text on [[Special:ResetTokens]].",
        "undo-summary-username-hidden": "Edit summary for an undo action where the username of the old revision is hidden.\n\nParameters:\n* $1 - the revision ID being undone\nSee also:\n* {{msg-mw|Undo-summary}}",
        "cantcreateaccounttitle": "Used as title of the error message {{msg-mw|Cantcreateaccount-text}}.",
        "cantcreateaccount-text": "Used as error message, with the title {{msg-mw|Cantcreateaccounttitle}}.\n* $1 - target IP address\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\nSee also:\n* {{msg-mw|Cantcreateaccount-range-text}}",
 -      "cantcreateaccount-range-text": "Used as more detailed version of the {{msg-mw|Cantcreateaccount-text}} error message, with the title {{msg-mw|Cantcreateaccounttitle}}.\n* $1 - target IP range\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\n* $4 - current user's IP address",
 +      "cantcreateaccount-range-text": "Used as more detailed version of the {{msg-mw|Cantcreateaccount-text}} error message, with the title {{msg-mw|Cantcreateaccounttitle}}.\n* $1 - target IP address range\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\n* $4 - current user's IP address",
        "createaccount-hook-aborted": "Placeholder message to return with API errors on account create; passes through the message from a hook {{notranslate}}",
        "viewpagelogs": "Link displayed in history of pages",
        "nohistory": "Message shown when there are no history to list. See [{{canonicalurl:x|action=history}} example history].\n----\nAlso used as title of error message when the feed is empty. See [{{canonicalurl:x|action=history&feed=atom}} example feed].\n\nSee the error message:\n* {{msg-mw|history-feed-empty}}",
        "grant-group-page-interaction": "{{Related|grant-group}}",
        "grant-group-file-interaction": "{{Related|grant-group}}",
        "grant-group-watchlist-interaction": "{{Related|grant-group}}",
 -      "grant-group-email": "{{Related|grant-group}}\n{{Identical|E-mail}}",
 -      "grant-group-high-volume": "{{Related|grant-group}}",
 -      "grant-group-customization": "{{Related|grant-group}}",
 -      "grant-group-administration": "{{Related|grant-group}}",
 -      "grant-group-other": "{{Related|grant-group}}",
 +      "grant-group-email": "{{Related|Grant-group}}\n{{Identical|E-mail}}",
 +      "grant-group-high-volume": "{{Related|Grant-group}}",
 +      "grant-group-customization": "{{Related|Grant-group}}",
 +      "grant-group-administration": "{{Related|Grant-group}}",
 +      "grant-group-other": "{{Related|Grant-group}}",
        "grant-blockusers": "Name for grant \"blockusers\".\n{{Related|grant}}",
        "grant-createaccount": "Name for grant \"createaccount\".\n{{Related|grant}}",
        "grant-createeditmovepage": "Name for grant \"createeditmovepage\".\n{{Related|grant}}",
        "uploadstash-badtoken": "Used as error message in [[Special:UploadStash]].",
        "uploadstash-errclear": "Used as error message in [[Special:UploadStash]].",
        "uploadstash-refresh": "Used as link text in [[Special:UploadStash]].",
 +      "uploadstash-thumbnail": "Used as link text in [[Special:UploadStash]].",
        "invalid-chunk-offset": "Error that can happen if chunks get uploaded out of order.\nAs a result of this error, clients can continue from an offset provided or restart the upload.\nUsed on [[Special:UploadWizard]].",
        "img-auth-accessdenied": "[[mw:Manual:Image Authorization|Manual:Image Authorization]]: Access Denied\n{{Identical|Access denied}}",
        "img-auth-nopathinfo": "[[mw:Manual:Image Authorization|Manual:Image Authorization]]: Missing PATH_INFO - see english description\n{{Doc-important|This is plain text. Do not use any wiki syntax.}}",
        "changecontentmodel-title-label": "Label for the input field where the target page title should be entered\n{{Identical|Page title}}",
        "changecontentmodel-model-label": "Label of the dropdown listing available content model types the user can change a page to",
        "changecontentmodel-reason-label": "{{Identical|Reason}}",
 +      "changecontentmodel-submit": "Label of the form \"submit\" button for [[Special:ChangeContentModel]]\n{{Identical|Change}}",
        "changecontentmodel-success-title": "Title of the success page of the change content model special page",
        "changecontentmodel-success-text": "Message telling user that their change has been successfully done.\n* $1 - Target page title",
        "changecontentmodel-cannot-convert": "Error message shown if the content model cannot be changed to the specified type. $1 is the page title, $2 is the localized content model name.",
        "ipb-unblock": "Used as page title in [[Special:Block]], if the target user is not specified.\n\nSee also:\n* {{msg-mw|Ipb-unblock-addr}}",
        "ipb-blocklist": "Used as link text in [[Special:Block]].\n\nThe link points to Specil:BlockList.",
        "ipb-blocklist-contribs": "Used in [[Special:Block]].\n* $1 - target username",
 +      "ipb-blocklist-duration-left": "Used on [[Special:BlockList]] to show the remaining time (years, months, days, hours, minutes) until the block expires.\n$1 - The duration left",
        "unblockip": "Used as legend for the form in [[Special:Unblock]].",
        "unblockiptext": "Used in the {{msg-mw|Unblockip}} form on [[Special:Unblock]].",
        "ipusubmit": "Used as button text on [{{canonicalurl:Special:BlockList|action=unblock}} Special:BlockList?action=unblock]. To see the message:\n* Go to [[Special:BlockList]]\n* Click \"unblock\" for any block (but you can only see \"unblock\" if you have administrator rights)\n* It is now the button below the form",
        "ipb_expiry_temp": "Warning message displayed on [[Special:BlockIP]] if the option \"hide username\" is selected but the expiry time is not infinite.",
        "ipb_hide_invalid": "Used as error message in [[Special:Block]].\n* $1 - Number of edits (Value of [[mw:Manual:$wgHideUserContribLimit]])",
        "ipb_already_blocked": "{{Identical|$1 is already blocked}}",
 -      "ipb-needreblock": "Used in [[Special:Block]].\n* $1 - target username",
 +      "ipb-needreblock": "Used in [[Special:Block]].\n* $1 - target username, can be used for GENDER support",
        "ipb-otherblocks-header": "[[File:Special.Block with other blocks from GlobalBlocking and TorBlocks.png|thumb|Example]]\nUsed on [[Special:Block]] as header for other blocks, i.e. from GlobalBlocking or TorBlocks\n\nParameters:\n* $1 - number of blocks\nSee also:\n* {{msg-mw|Ipblocklist-otherblocks}}",
        "unblock-hideuser": "{{doc-singularthey}}",
        "ipb_cant_unblock": "Used as error message in [[Special:Unblock]]. Parameters:\n* $1 - block ID",
 -      "ipb_blocked_as_range": "Used when unblock of a single IP fails. Parameters:\n* $1 - IP address\n* $2 - IP range",
 +      "ipb_blocked_as_range": "Used when unblock of a single IP fails. Parameters:\n* $1 - IP address\n* $2 - IP address range",
        "ip_range_invalid": "Used as error message in [[Special:Block]].\n\nSee also:\n* {{msg-mw|Range block disabled}}\n* {{msg-mw|Ip range invalid}}\n* {{msg-mw|Ip range toolarge}}",
        "ip_range_toolarge": "Used as error message in [[Special:Block]]. Parameters:\n* $1 - a number from 0 to 32 for IPv4 (from 0 to 128 for IPv6); a part of CIDR (Classless Inter-Domain Routing) notation.\nSee also:\n* {{msg-mw|Range block disabled}}\n* {{msg-mw|Ip range invalid}}\n* {{msg-mw|Ip range toolarge}}",
        "proxyblocker": "Used in [[Special:BlockMe]].\n\nSee also:\n* {{msg-mw|proxyblocker-disabled}}\n* {{msg-mw|proxyblockreason}}\n* {{msg-mw|proxyblocksuccess}}",
        "import-logentry-interwiki": "{{ignored}}This is a ''logentry'' message only used on IRC. Parameters:\n* $1 - page title",
        "import-logentry-interwiki-detail": "Used as success message and log entry. Parameters:\n* $1 - number of succeeded revisions\n* $2 - interwiki name\nSee also:\n* {{msg-mw|Import-logentry-upload-detail}}",
        "javascripttest": "Title of the special page [[Special:JavaScriptTest]].\n\nSee also:\n* {{msg-mw|Javascripttest|title}}\n* {{msg-mw|Javascripttest-pagetext-noframework|summary}}\n* {{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
 -      "javascripttest-backlink": "{{optional}}\nUsed as subtitle in [[Special:JavaScriptTest]]. Parameters:\n* $1 - page title",
 -      "javascripttest-title": "{{Ignore}}",
 -      "javascripttest-pagetext-noframework": "Used as summary when no framework specified.\n\nSee also:\n* {{msg-mw|Javascripttest|title}}\n* {{msg-mw|Javascripttest-pagetext-noframework|summary}}\n* {{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
 -      "javascripttest-pagetext-unknownframework": "Error message when given framework ID is not found. Parameters:\n* $1 - the ID of the framework\nSee also:\n* {{msg-mw|Javascripttest|title}}\n* {{msg-mw|Javascripttest-pagetext-noframework|summary}}\n* {{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
        "javascripttest-pagetext-unknownaction": "Error message when url specifies an unknown action. Parameters:\n* $1 - the action specified in the url.",
 -      "javascripttest-pagetext-frameworks": "Parameters:\n* $1 - frameworks list which contain a link text {{msg-mw|Javascripttest-qunit-name}}",
 -      "javascripttest-pagetext-skins": "Used as label in [[Special:JavaScriptTest]].",
 -      "javascripttest-qunit-name": "{{Ignore}}",
        "javascripttest-qunit-intro": "Used as summary. Parameters:\n* $1 - the configured URL to the documentation\nSee also:\n* {{msg-mw|Javascripttest-qunit-heading}}",
        "accesskey-pt-userpage": "{{doc-accesskey}}\nSee also:\n<!--* username-->\n* {{msg-mw|Accesskey-pt-userpage}}\n* {{msg-mw|Tooltip-pt-userpage}}",
        "accesskey-pt-anonuserpage": "{{doc-accesskey}}",
        "version-libraries-description": "Column header for the library's description\n{{Identical|Description}}",
        "version-libraries-authors": "Column header for the library's authors\n{{Identical|Author}}",
        "redirect": "{{doc-special|Redirect}}\nThis means \"Redirect by file '''name''', user '''ID''', page '''ID''', revision '''ID''', or log '''ID'''\".",
 -      "redirect-legend": "Legend of fieldset around input box in [[Special:Redirect]]",
        "redirect-text": "Inside fieldset for [[Special:Redirect]]",
        "redirect-summary": "Shown at top of [[Special:Redirect]]",
        "redirect-submit": "Button label in [[Special:Redirect]].\n{{Identical|Go}}",
        "redirect-not-exists": "Used as error message in [[Special:Redirect]]",
        "fileduplicatesearch": "Name of special page [[Special:FileDuplicateSearch]].",
        "fileduplicatesearch-summary": "Summary of [[Special:FileDuplicateSearch]]",
 -      "fileduplicatesearch-legend": "Legend of the fieldset around the input form of [[Special:FileDuplicateSearch]]",
        "fileduplicatesearch-filename": "Input form of [[Special:FileDuplicateSearch]]:\n\n{{Identical|Filename}}",
        "fileduplicatesearch-submit": "Button label on [[Special:FileDuplicateSearch]].\n\n{{Identical|Search}}",
        "fileduplicatesearch-info": "Information beneath the thumbnail on the right side shown after a successful search via [[Special:FileDuplicateSearch]].\n\nParameters:\n* $1 - width of the file\n* $2 - height of the file\n* $3 - File size\n* $4 - MIME type",
        "logentry-protect-protect-cascade": "{{Logentry|[[Special:Log/protect]]}}\n\n* $4 - protect expiry (formatted with {{msg-mw|protect-summary-desc}}, multiple possible)\nFor word \"cascading\" see {{msg-mw|protect-summary-cascade}}",
        "logentry-protect-modify": "{{Logentry|[[Special:Log/protect]]}}\n\n* $4 - protect expiry (formatted with {{msg-mw|protect-summary-desc}}, multiple possible)",
        "logentry-protect-modify-cascade": "{{Logentry|[[Special:Log/protect]]}}\n\n* $4 - protect expiry (formatted with {{msg-mw|protect-summary-desc}}, multiple possible)\nFor word \"cascading\" see {{msg-mw|protect-summary-cascade}}",
 -      "logentry-rights-rights": "* $1 - username\n* $2 - (see below)\n* $3 - username\n* $4 - list of user groups or {{msg-mw|Rightsnone}}\n* $5 - list of user groups or {{msg-mw|Rightsnone}}\n----\n{{Logentry|[[Special:Log/rights]]}}",
 +      "logentry-rights-rights": "* $1 - username\n* $2 - (see below)\n* $3 - username, also used for GENDER support\n* $4 - list of user groups or {{msg-mw|Rightsnone}}\n* $5 - list of user groups or {{msg-mw|Rightsnone}}\n----\n{{Logentry|[[Special:Log/rights]]}}",
        "logentry-rights-rights-legacy": "* $1 - username\n* $2 - (see below)\n* $3 - username\n----\n{{Logentry|[[Special:Log/rights]]}}",
        "logentry-rights-autopromote": "* $1 - username\n* $2 - (see below)\n* $3 - (see below)\n* $4 - comma separated list of old user groups or {{msg-mw|Rightsnone}}\n* $5 - comma separated list of new user groups\n----\n{{Logentry|[[Special:Log/rights]]}}",
        "logentry-upload-upload": "{{Logentry|[[Special:Log/upload]]}}",
        "api-error-unknownerror": "API error message that can be used for client side localisation of API errors.\n\nParameters:\n* $1 - an unknown error message\n{{Identical|Unknown error}}",
        "api-error-uploaddisabled": "API error message that can be used for client side localisation of API errors.",
        "api-error-verification-error": "The word \"extension\" refers to the part behind the last dot in a file name, that by convention gives a hint about the kind of data format which a files contents are in.",
 +      "api-error-was-deleted": "API error message that can be used for client side localisation of API errors.",
        "duration-seconds": "Used as duration. Parameters:\n* $1 - number of seconds\n{{Related|Duration}}\n{{Identical|Second}}",
        "duration-minutes": "Used as duration. Parameters:\n* $1 - number of minutes\n{{Related|Duration}}\n{{Identical|Minute}}",
        "duration-hours": "Used as duration. Parameters:\n* $1 - number of hours\n{{Related|Duration}}",
        "special-characters-group-ipa": "IPA means a script: \"international phonetic alphabet\" here, and not \"international phonetic association\", the organization behind it.",
        "special-characters-group-symbols": "The section name for symbols\n\n{{Identical|Symbol}}",
        "special-characters-group-greek": "This is the name of a script, or alphabet, not a language.",
 +      "special-characters-group-greekextended": "The name of the Greek Extended character set.",
        "special-characters-group-cyrillic": "This is the name of a script, or a group of alphabets, used mainly in Eastern Europe and North and Central Asia.\n{{related|Special-characters-group}}",
        "special-characters-group-arabic": "This is the name of a script, or alphabet, not a language.\n{{related|Special-characters-group}}",
        "special-characters-group-arabicextended": "This is a description of the additional group of Arabic script characters for languages such as a Persian, Urdu, Pashto and others. This message is supposed to be similar to {{msg-mw|special-characters-group-latinextended}}.\n{{related|Special-characters-group}}",
        "sessionprovider-generic": "Used to create a generic session type description when one isn't provided via the proper message. Should be phrased to make sense when added to a message such as {{msg-mw|cannotloginnow-text}}.\n\nParameters:\n* $1 - PHP classname.",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "Description of the sessions provided by the CookieSessionProvider class, which use HTTP cookies. Should be phrased to make sense when added to a message such as {{msg-mw|cannotloginnow-text}}.",
        "sessionprovider-nocookies": "Used to inform the user that sessions may be missing due to lack of cookies.",
 -      "randomrootpage": "{{doc-special|RandomRootPage}}"
 +      "randomrootpage": "{{doc-special|RandomRootPage}}",
 +      "log-action-filter-block": "Which type of action to filter for in this log",
 +      "log-action-filter-contentmodel": "Which type of action to filter for in this log",
 +      "log-action-filter-delete": "Which type of action to filter for in this log",
 +      "log-action-filter-import": "Which type of action to filter for in this log",
 +      "log-action-filter-managetags": "Which type of action to filter for in this log",
 +      "log-action-filter-move": "Which type of action to filter for in this log",
 +      "log-action-filter-newusers": "Which type of action to filter for in this log",
 +      "log-action-filter-patrol": "Which type of action to filter for in this log",
 +      "log-action-filter-protect": "Which type of action to filter for in this log",
 +      "log-action-filter-rights": "Which type of action to filter for in this log",
 +      "log-action-filter-suppress": "Which type of action to filter for in this log",
 +      "log-action-filter-upload": "Which type of action to filter for in this log",
 +      "log-action-filter-all": "All types of action are allowed\n{{Identical|All}}",
 +      "log-action-filter-block-block": "Action to filter for in this log\n{{Identical|Block}}",
 +      "log-action-filter-block-reblock": "Action to filter for in this log",
 +      "log-action-filter-block-unblock": "Action to filter for in this log\n{{Identical|Unblock}}",
 +      "log-action-filter-contentmodel-change": "Action to filter for in this log",
 +      "log-action-filter-contentmodel-new": "Action to filter for in this log",
 +      "log-action-filter-delete-delete": "Action to filter for in this log",
 +      "log-action-filter-delete-restore": "Action to filter for in this log",
 +      "log-action-filter-delete-event": "Action to filter for in this log",
 +      "log-action-filter-delete-revision": "Action to filter for in this log",
 +      "log-action-filter-import-interwiki": "Action to filter for in this log",
 +      "log-action-filter-import-upload": "Action to filter for in this log",
 +      "log-action-filter-managetags-create": "Action to filter for in this log",
 +      "log-action-filter-managetags-delete": "Action to filter for in this log",
 +      "log-action-filter-managetags-activate": "Action to filter for in this log",
 +      "log-action-filter-managetags-deactivate": "Action to filter for in this log",
 +      "log-action-filter-move-move": "Action to filter for in this log",
 +      "log-action-filter-move-move_redir": "Action to filter for in this log",
 +      "log-action-filter-newusers-create": "Action to filter for in this log",
 +      "log-action-filter-newusers-create2": "Action to filter for in this log",
 +      "log-action-filter-newusers-autocreate": "Action to filter for in this log",
 +      "log-action-filter-newusers-byemail": "Action to filter for in this log",
 +      "log-action-filter-patrol-patrol": "Action to filter for in this log",
 +      "log-action-filter-patrol-autopatrol": "Action to filter for in this log",
 +      "log-action-filter-protect-protect": "Action to filter for in this log",
 +      "log-action-filter-protect-modify": "Action to filter for in this log",
 +      "log-action-filter-protect-unprotect": "Action to filter for in this log",
 +      "log-action-filter-protect-move_prot": "Action to filter for in this log",
 +      "log-action-filter-rights-rights": "Action to filter for in this log",
 +      "log-action-filter-rights-autopromote": "Action to filter for in this log",
 +      "log-action-filter-suppress-event": "Action to filter for in this log",
 +      "log-action-filter-suppress-revision": "Action to filter for in this log",
 +      "log-action-filter-suppress-delete": "Action to filter for in this log",
 +      "log-action-filter-suppress-block": "Action to filter for in this log",
 +      "log-action-filter-suppress-reblock": "Action to filter for in this log",
 +      "log-action-filter-upload-upload": "Action to filter for in this log",
 +      "log-action-filter-upload-overwrite": "Action to filter for in this log"
  }