Merge "Use LogFormatter to format rights log."
authorNikerabbit <niklas.laxstrom@gmail.com>
Thu, 1 Nov 2012 18:51:09 +0000 (18:51 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 1 Nov 2012 18:51:09 +0000 (18:51 +0000)
1  2 
includes/AutoLoader.php
includes/DefaultSettings.php
includes/User.php
includes/specials/SpecialUserrights.php
languages/messages/MessagesEn.php
maintenance/language/messages.inc

diff --combined includes/AutoLoader.php
@@@ -435,6 -435,7 +435,6 @@@ $wgAutoloadLocalClasses = array
        'GenderCache' => 'includes/cache/GenderCache.php',
        'GlobalDependency' => 'includes/cache/CacheDependency.php',
        'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
 -      'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php',
        'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
        'LinkBatch' => 'includes/cache/LinkBatch.php',
        'LinkCache' => 'includes/cache/LinkCache.php',
        'TitleDependency' => 'includes/cache/CacheDependency.php',
        'TitleListDependency' => 'includes/cache/CacheDependency.php',
  
 -      # includes/conf
 -      'Conf' => 'includes/conf/Conf.php',
 -      'DatabaseConf' => 'includes/conf/DatabaseConf.php',
 -      'DefaultSettings' => 'includes/conf/DefaultSettings.php',
 -
        # includes/context
        'ContextSource' => 'includes/context/ContextSource.php',
        'DerivativeContext' => 'includes/context/DerivativeContext.php',
        'WebInstallerPage' => 'includes/installer/WebInstallerPage.php',
  
        # includes/job
 -      'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php',
 -      'EmaillingJob' => 'includes/job/EmaillingJob.php',
 -      'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php',
        'Job' => 'includes/job/Job.php',
        'JobQueue' => 'includes/job/JobQueue.php',
        'JobQueueDB' => 'includes/job/JobQueueDB.php',
        'JobQueueGroup' => 'includes/job/JobQueueGroup.php',
 -      'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php',
 -      'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
 -      'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
 +
 +      # includes/job/jobs
 +      'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php',
 +      'EmaillingJob' => 'includes/job/jobs/EmaillingJob.php',
 +      'EnotifNotifyJob' => 'includes/job/jobs/EnotifNotifyJob.php',
 +      'HTMLCacheUpdateJob' => 'includes/job/jobs/HTMLCacheUpdateJob.php',
 +      'NullJob' => 'includes/job/jobs/NullJob.php',
 +      'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php',
 +      'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php',
 +      'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php',
  
        # includes/json
        'FormatJson' => 'includes/json/FormatJson.php',
        'PatrolLog' => 'includes/logging/PatrolLog.php',
        'PatrolLogFormatter' => 'includes/logging/LogFormatter.php',
        'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php',
+       'RightsLogFormatter' => 'includes/logging/LogFormatter.php',
  
        # includes/media
        'BitmapHandler' => 'includes/media/Bitmap.php',
        'TestRecorder' => 'tests/testHelpers.inc',
  
        # tests/phpunit
 -      'DummyContentHandlerForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
 -      'DummyContentForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
 -      'JavascriptContentTest' => 'tests/phpunit/includes/JavascriptContentTest.php',
        'RevisionStorageTest' => 'tests/phpunit/includes/RevisionStorageTest.php',
 -      'TextContentTest' => 'tests/phpunit/includes/TextContentTest.php',
        'WikiPageTest' => 'tests/phpunit/includes/WikiPageTest.php',
  
 +      # tests/phpunit/content
 +      'DummyContentHandlerForTesting' => 'tests/phpunit/includes/content/ContentHandlerTest.php',
 +      'DummyContentForTesting' => 'tests/phpunit/includes/content/ContentHandlerTest.php',
 +      'JavascriptContentTest' => 'tests/phpunit/includes/content/JavascriptContentTest.php',
 +      'TextContentTest' => 'tests/phpunit/includes/content/TextContentTest.php',
 +
        # tests/phpunit/includes
        'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php',
  
@@@ -752,14 -752,10 +752,14 @@@ $wgMediaHandlers = array
   * @since 1.21
   */
  $wgContentHandlers = array(
 -      CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
 -      CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
 -      CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
 -      CONTENT_MODEL_TEXT => 'TextContentHandler', // plain text, for use by extensions etc
 +      // the usual case
 +      CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler',
 +      // dumb version, no syntax highlighting
 +      CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler',
 +      // dumb version, no syntax highlighting
 +      CONTENT_MODEL_CSS => 'CssContentHandler',
 +      // plain text, for use by extensions etc
 +      CONTENT_MODEL_TEXT => 'TextContentHandler',
  );
  
  /**
@@@ -1853,10 -1849,9 +1853,10 @@@ $wgUseLocalMessageCache = false
  $wgLocalMessageCacheSerialized = true;
  
  /**
 - * Instead of caching everything, keep track which messages are requested and
 - * load only most used messages. This only makes sense if there is lots of
 - * interface messages customised in the wiki (like hundreds in many languages).
 + * Instead of caching everything, only cache those messages which have
 + * been customised in the site content language. This means that
 + * MediaWiki:Foo/ja is ignored if MediaWiki:Foo doesn't exist.
 + * This option is probably only useful for translatewiki.net.
   */
  $wgAdaptiveMessageCache = false;
  
@@@ -2592,12 -2587,12 +2592,12 @@@ $wgSiteNotice = ''
  /**
   * A subtitle to add to the tagline, for skins that have it/
   */
 -$wgExtraSubtitle      = '';
 +$wgExtraSubtitle = '';
  
  /**
   * If this is set, a "donate" link will appear in the sidebar. Set it to a URL.
   */
 -$wgSiteSupportPage    = '';
 +$wgSiteSupportPage = '';
  
  /**
   * Validate the overall output using tidy and refuse
@@@ -5414,15 -5409,14 +5414,15 @@@ $wgHooks = array()
   * can add to this to provide custom jobs
   */
  $wgJobClasses = array(
 -      'refreshLinks' => 'RefreshLinksJob',
 -      'refreshLinks2' => 'RefreshLinksJob2',
 -      'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
 +      'refreshLinks'      => 'RefreshLinksJob',
 +      'refreshLinks2'     => 'RefreshLinksJob2',
 +      'htmlCacheUpdate'   => 'HTMLCacheUpdateJob',
        'html_cache_update' => 'HTMLCacheUpdateJob', // backwards-compatible
 -      'sendMail' => 'EmaillingJob',
 -      'enotifNotify' => 'EnotifNotifyJob',
 +      'sendMail'          => 'EmaillingJob',
 +      'enotifNotify'      => 'EnotifNotifyJob',
        'fixDoubleRedirect' => 'DoubleRedirectJob',
 -      'uploadFromUrl' => 'UploadFromUrlJob',
 +      'uploadFromUrl'     => 'UploadFromUrlJob',
 +      'null'              => 'NullJob'
  );
  
  /**
@@@ -5442,7 -5436,7 +5442,7 @@@ $wgJobTypesExcludedFromDefaultQueue = a
   * These settings should be global to all wikis.
   */
  $wgJobTypeConf = array(
 -      'default' => array( 'class' => 'JobQueueDB' ),
 +      'default' => array( 'class' => 'JobQueueDB', 'order' => 'random' ),
  );
  
  /**
@@@ -5647,8 -5641,6 +5647,6 @@@ $wgLogActions = array
        'protect/modify'     => 'modifiedarticleprotection',
        'protect/unprotect'  => 'unprotectedarticle',
        'protect/move_prot'  => 'movedarticleprotection',
-       'rights/rights'      => 'rightslogentry',
-       'rights/autopromote' => 'rightslogentry-autopromote',
        'upload/upload'      => 'uploadedimage',
        'upload/overwrite'   => 'overwroteimage',
        'upload/revert'      => 'uploadedimage',
   * @see LogFormatter
   */
  $wgLogActionsHandlers = array(
-       'move/move'         => 'MoveLogFormatter',
-       'move/move_redir'  => 'MoveLogFormatter',
-       'delete/delete'     => 'DeleteLogFormatter',
-       'delete/restore'    => 'DeleteLogFormatter',
-       'delete/revision'   => 'DeleteLogFormatter',
-       'delete/event'      => 'DeleteLogFormatter',
-       'suppress/revision' => 'DeleteLogFormatter',
-       'suppress/event'    => 'DeleteLogFormatter',
-       'suppress/delete'   => 'DeleteLogFormatter',
-       'patrol/patrol'     => 'PatrolLogFormatter',
+       'move/move'          => 'MoveLogFormatter',
+       'move/move_redir'    => 'MoveLogFormatter',
+       'delete/delete'      => 'DeleteLogFormatter',
+       'delete/restore'     => 'DeleteLogFormatter',
+       'delete/revision'    => 'DeleteLogFormatter',
+       'delete/event'       => 'DeleteLogFormatter',
+       'suppress/revision'  => 'DeleteLogFormatter',
+       'suppress/event'     => 'DeleteLogFormatter',
+       'suppress/delete'    => 'DeleteLogFormatter',
+       'patrol/patrol'      => 'PatrolLogFormatter',
+       'rights/rights'      => 'RightsLogFormatter',
+       'rights/autopromote' => 'RightsLogFormatter',
  );
  
  /**
diff --combined includes/User.php
@@@ -455,12 -455,11 +455,12 @@@ class User 
         * will be loaded once more from the database when accessing them.
         *
         * @param $row Array A row from the user table
 +       * @param $data Array Further data to load into the object (see User::loadFromRow for valid keys)
         * @return User
         */
 -      public static function newFromRow( $row ) {
 +      public static function newFromRow( $row, $data = null ) {
                $user = new User;
 -              $user->loadFromRow( $row );
 +              $user->loadFromRow( $row, $data );
                return $user;
        }
  
         * Initialize this object from a row from the user table.
         *
         * @param $row Array Row from the user table to load.
 +       * @param $data Array Further user data to load into the object
 +       *
 +       *      user_groups             Array with groups out of the user_groups table
 +       *      user_properties         Array with properties out of the user_properties table
         */
 -      public function loadFromRow( $row ) {
 +      public function loadFromRow( $row, $data = null ) {
                $all = true;
  
                $this->mGroups = null; // deferred
                if ( $all ) {
                        $this->mLoadedItems = true;
                }
 +
 +              if ( is_array( $data ) ) {
 +                      if ( is_array( $data['user_groups'] ) ) {
 +                              $this->mGroups = $data['user_groups'];
 +                      }
 +                      if ( is_array( $data['user_properties'] ) ) {
 +                              $this->loadOptions( $data['user_properties'] );
 +                      }
 +              }
        }
  
        /**
                                }
                                $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
  
-                               $log = new LogPage( 'rights', $wgAutopromoteOnceLogInRC /* in RC? */ );
-                               $log->addEntry( 'autopromote',
-                                       $this->getUserPage(),
-                                       '', // no comment
-                                       // These group names are "list to texted"-ed in class LogPage.
-                                       array( implode( ', ', $oldGroups ), implode( ', ', $newGroups ) )
-                               );
+                               $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
+                               $logEntry->setPerformer( $this );
+                               $logEntry->setTarget( $this->getUserPage() );
+                               $logEntry->setParameters( array(
+                                       '4::oldgroups' => $oldGroups,
+                                       '5::newgroups' => $newGroups,
+                               ) );
+                               $logid = $logEntry->insert();
+                               if ( $wgAutopromoteOnceLogInRC ) {
+                                       $logEntry->publish( $logid );
+                               }
                        }
                }
                return $toPromote;
        public static function getDefaultOptions() {
                global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
  
 +              static $defOpt = null;
 +              if ( !defined( 'MW_PHPUNIT_TEST' ) && $defOpt !== null ) {
 +                      // Disabling this for the unit tests, as they rely on being able to change $wgContLang
 +                      // mid-request and see that change reflected in the return value of this function.
 +                      // Which is insane and would never happen during normal MW operation
 +                      return $defOpt;
 +              }
 +
                $defOpt = $wgDefaultUserOptions;
                # default language setting
                $defOpt['variant'] = $wgContLang->getCode();
                }
                $defOpt['skin'] = $wgDefaultSkin;
  
 -              // FIXME: Ideally we'd cache the results of this function so the hook is only run once,
 -              // but that breaks the parser tests because they rely on being able to change $wgContLang
 -              // mid-request and see that change reflected in the return value of this function.
 -              // Which is insane and would never happen during normal MW operation, but is also not
 -              // likely to get fixed unless and until we context-ify everything.
 -              // See also https://www.mediawiki.org/wiki/Special:Code/MediaWiki/101488#c25275
                wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
  
                return $defOpt;
                global $wgHiddenPrefs;
                $this->loadOptions();
  
 -              if ( is_null( $this->mOptions ) ) {
 -                      if($defaultOverride != '') {
 -                              return $defaultOverride;
 -                      }
 -                      $this->mOptions = User::getDefaultOptions();
 -              }
 -
                # We want 'disabled' preferences to always behave as the default value for
                # users, even if they have set the option explicitly in their settings (ie they
                # set it, and then it was disabled removing their ability to change it).  But
         * @param $val mixed New value to set
         */
        public function setOption( $oname, $val ) {
 -              $this->load();
                $this->loadOptions();
  
                // Explicitly NULL values should refer to defaults
                if( is_null( $val ) ) {
 -                      $defaultOption = self::getDefaultOption( $oname );
 -                      if( !is_null( $defaultOption ) ) {
 -                              $val = $defaultOption;
 -                      }
 +                      $val = self::getDefaultOption( $oname );
                }
  
                $this->mOptions[$oname] = $val;
        }
  
        /**
 -       * @todo document
 +       * Load the user options either from cache, the database or an array
 +       *
 +       * @param $data Rows for the current user out of the user_properties table
         */
 -      protected function loadOptions() {
 +      protected function loadOptions( $data = null ) {
                global $wgContLang;
  
                $this->load();
                                $this->mOptions[$key] = $value;
                        }
                } else {
 -                      wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
 -                      // Load from database
 -                      $dbr = wfGetDB( DB_SLAVE );
 -
 -                      $res = $dbr->select(
 -                              'user_properties',
 -                              array( 'up_property', 'up_value' ),
 -                              array( 'up_user' => $this->getId() ),
 -                              __METHOD__
 -                      );
 +                      if( !is_array( $data ) ) {
 +                              wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
 +                              // Load from database
 +                              $dbr = wfGetDB( DB_SLAVE );
  
 -                      $this->mOptionOverrides = array();
 -                      foreach ( $res as $row ) {
 -                              $this->mOptionOverrides[$row->up_property] = $row->up_value;
 -                              $this->mOptions[$row->up_property] = $row->up_value;
 +                              $res = $dbr->select(
 +                                      'user_properties',
 +                                      array( 'up_property', 'up_value' ),
 +                                      array( 'up_user' => $this->getId() ),
 +                                      __METHOD__
 +                              );
 +
 +                              $this->mOptionOverrides = array();
 +                              $data = array();
 +                              foreach ( $res as $row ) {
 +                                      $data[$row->up_property] = $row->up_value;
 +                              }
 +                      }
 +                      foreach ( $data as $property => $value ) {
 +                              $this->mOptionOverrides[$property] = $value;
 +                              $this->mOptions[$property] = $value;
                        }
                }
  
@@@ -245,16 -245,16 +245,16 @@@ class UserrightsPage extends SpecialPag
         * Add a rights log entry for an action.
         */
        function addLogEntry( $user, $oldGroups, $newGroups, $reason ) {
-               $log = new LogPage( 'rights' );
-               $log->addEntry( 'rights',
-                       $user->getUserPage(),
-                       $reason,
-                       array(
-                               $this->makeGroupNameListForLog( $oldGroups ),
-                               $this->makeGroupNameListForLog( $newGroups )
-                       )
-               );
+               $logEntry = new ManualLogEntry( 'rights', 'rights' );
+               $logEntry->setPerformer( $this->getUser() );
+               $logEntry->setTarget( $user->getUserPage() );
+               $logEntry->setComment( $reason );
+               $logEntry->setParameters( array(
+                       '4::oldgroups' => $oldGroups,
+                       '5::newgroups' => $newGroups,
+               ) );
+               $logid = $logEntry->insert();
+               $logEntry->publish( $logid );
        }
  
        /**
                }
        }
  
+       /**
+        * Make a list of group names to be stored as parameter for log entries
+        *
+        * @deprecated in 1.21; use LogFormatter instead.
+        * @param $ids array
+        * @return string
+        */
        function makeGroupNameListForLog( $ids ) {
+               wfDeprecated( __METHOD__, '1.21' );
                if( empty( $ids ) ) {
                        return '';
                } else {
         */
        protected function showEditUserGroupsForm( $user, $groups ) {
                $list = array();
 +              $membersList = array();
                foreach( $groups as $group ) {
                        $list[] = self::buildGroupLink( $group );
 +                      $membersList[] = self::buildGroupMemberLink( $group );
                }
  
 -              $autolist = array();
 +              $autoList = array();
 +              $autoMembersList = array();
                if ( $user instanceof User ) {
                        foreach( Autopromote::getAutopromoteGroups( $user ) as $group ) {
 -                              $autolist[] = self::buildGroupLink( $group );
 +                              $autoList[] = self::buildGroupLink( $group );
 +                              $autoMembersList[] = self::buildGroupMemberLink( $group );
                        }
                }
  
 +              $language = $this->getLanguage();
 +              $displayedList = $this->msg( 'userrights-groupsmember-type',
 +                      $language->listToText( $list ),
 +                      $language->listToText( $membersList )
 +              )->plain();
 +              $displayedAutolist = $this->msg( 'userrights-groupsmember-type',
 +                      $language->listToText( $autoList ),
 +                      $language->listToText( $autoMembersList )
 +              )->plain();
 +
                $grouplist = '';
                $count = count( $list );
 -              if( $count > 0 ) {
 +              if ( $count > 0 ) {
                        $grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
 -                      $grouplist = '<p>' . $grouplist  . ' ' . $this->getLanguage()->listToText( $list ) . "</p>\n";
 +                      $grouplist = '<p>' . $grouplist  . ' ' . $displayedList . "</p>\n";
                }
 -              $count = count( $autolist );
 -              if( $count > 0 ) {
 +              $count = count( $autoList );
 +              if ( $count > 0 ) {
                        $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )->parse();
 -                      $grouplist .= '<p>' . $autogrouplistintro  . ' ' . $this->getLanguage()->listToText( $autolist ) . "</p>\n";
 +                      $grouplist .= '<p>' . $autogrouplistintro  . ' ' . $displayedAutolist . "</p>\n";
                }
  
                $userToolLinks = Linker::userToolLinks(
         * @return string
         */
        private static function buildGroupLink( $group ) {
 -              static $cache = array();
 -              if( !isset( $cache[$group] ) )
 -                      $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupName( $group ) ) );
 -              return $cache[$group];
 +              return User::makeGroupLinkHtml( $group, User::getGroupName( $group ) );
 +      }
 +
 +      /**
 +       * Format a link to a group member description page
 +       *
 +       * @param $group string
 +       * @return string
 +       */
 +      private static function buildGroupMemberLink( $group ) {
 +              return User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) );
        }
  
        /**
                }
  
                # Build the HTML table
 -              $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) .
 +              $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) .
                        "<tr>\n";
                foreach( $columns as $name => $column ) {
                        if( $column === array() )
@@@ -1392,7 -1392,7 +1392,7 @@@ Custom .css and .js pages use a lowerca
  'note'                             => "'''Note:'''",
  'previewnote'                      => "'''Remember that this is only a preview.'''
  Your changes have not yet been saved!",
 -'continue-editing'                 => 'Continue editing',
 +'continue-editing'                 => 'Go to editing area',
  'previewconflict'                  => 'This preview reflects the text in the upper text editing area as it will appear if you choose to save.',
  'session_fail_preview'             => "'''Sorry! We could not process your edit due to a loss of session data.'''
  Please try again.
@@@ -1947,7 -1947,6 +1947,7 @@@ Your e-mail address is not revealed whe
  'saveusergroups'                 => 'Save user groups',
  'userrights-groupsmember'        => 'Member of:',
  'userrights-groupsmember-auto'   => 'Implicit member of:',
 +'userrights-groupsmember-type'   => '$1', # only translate this message to other languages if you have to change it
  'userrights-groups-help'         => 'You may alter the groups this user is in:
  * A checked box means the user is in that group.
  * An unchecked box means the user is not in that group.
  'right-passwordreset'         => 'View password reset e-mails',
  
  # User rights log
- 'rightslog'                  => 'User rights log',
- 'rightslogtext'              => 'This is a log of changes to user rights.',
- 'rightslogentry'             => 'changed group membership for $1 from $2 to $3',
- 'rightslogentry-autopromote' => 'was automatically promoted from $2 to $3',
- 'rightsnone'                 => '(none)',
+ 'rightslog'                     => 'User rights log',
+ 'rightslogtext'                 => 'This is a log of changes to user rights.',
+ 'rightslogentry'                => 'changed group membership for $1 from $2 to $3',
+ 'rightslogentry-autopromote'    => 'was automatically promoted from $2 to $3',
+ 'logentry-rights-rights'        => '$1 changed group membership for $3 from $4 to $5',
+ 'logentry-rights-rights-legacy' => '$1 changed group membership for $3',
+ 'logentry-rights-autopromote'   => '$1 was automatically promoted from $4 to $5',
+ 'rightsnone'                    => '(none)',
  
  # Associated actions - in the sentence "You do not have permission to X"
  'action-read'                 => 'read this page',
@@@ -2807,8 -2809,7 +2810,8 @@@ There may be [[{{MediaWiki:Listgrouprig
  'emailuser-title-notarget' => 'E-mail user',
  'emailuser-summary'        => '', # do not translate or duplicate this message to other languages
  'emailpage'                => 'E-mail user',
 -'emailpagetext'            => 'You can use the form below to send an e-mail message to this user.
 +// Dummy GENDER to prevent warnings at translatewiki
 +'emailpagetext'            => 'You can use the form below to send an e-mail message to this {{GENDER:$1|user}}.
  The e-mail address you entered in [[Special:Preferences|your user preferences]] will appear as the "From" address of the e-mail, so the recipient will be able to reply directly to you.',
  'usermailererror'          => 'Mail object returned error:',
  'defemailsubject'          => '{{SITENAME}} e-mail from user "$1"',
@@@ -3686,7 -3687,7 +3689,7 @@@ You can view its source'
  'standard.css'            => '/* CSS placed here will affect users of the Standard skin */', # only translate this message to other languages if you have to change it
  'nostalgia.css'           => '/* CSS placed here will affect users of the Nostalgia skin */', # only translate this message to other languages if you have to change it
  'cologneblue.css'         => '/* CSS placed here will affect users of the Cologne Blue skin */', # only translate this message to other languages if you have to change it
 -'monobook.css'            => '/* CSS placed here will affect users of the Monobook skin */', # only translate this message to other languages if you have to change it
 +'monobook.css'            => '/* CSS placed here will affect users of the MonoBook skin */', # only translate this message to other languages if you have to change it
  'myskin.css'              => '/* CSS placed here will affect users of the MySkin skin */', # only translate this message to other languages if you have to change it
  'chick.css'               => '/* CSS placed here will affect users of the Chick skin */', # only translate this message to other languages if you have to change it
  'simple.css'              => '/* CSS placed here will affect users of the Simple skin */', # only translate this message to other languages if you have to change it
@@@ -3744,7 -3745,7 +3747,7 @@@ This is probably caused by a link to a 
  # Info page
  'pageinfo-header'              => '-', # do not translate or duplicate this message to other languages
  'pageinfo-title'               => 'Information for "$1"',
 -'pageinfo-not-current'         => 'Information may only be displayed for the current revision.',
 +'pageinfo-not-current'         => 'Sorry, it\'s impossible to provide this information for old revisions.',
  'pageinfo-header-basic'        => 'Basic information',
  'pageinfo-header-edits'        => 'Edit history',
  'pageinfo-header-restrictions' => 'Page protection',
  'pageinfo-default-sort'        => 'Default sort key',
  'pageinfo-length'              => 'Page length (in bytes)',
  'pageinfo-article-id'          => 'Page ID',
 +'pageinfo-language'            => 'Page content language',
  'pageinfo-robot-policy'        => 'Search engine status',
  'pageinfo-robot-index'         => 'Indexable',
  'pageinfo-robot-noindex'       => 'Not indexable',
@@@ -4715,7 -4715,6 +4718,7 @@@ You can also [[Special:EditWatchlist|us
  'version-license'                       => 'License',
  'version-poweredby-credits'             => "This wiki is powered by '''[//www.mediawiki.org/ MediaWiki]''', copyright © 2001-$1 $2.",
  'version-poweredby-others'              => 'others',
 +'version-credits-summary'               => 'We would like to recognize the following persons for their contribution to [[Special:Version|MediaWiki]].',
  'version-license-info'                  => 'MediaWiki is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
  
  MediaWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
@@@ -1087,7 -1087,6 +1087,7 @@@ $wgMessageStructure = array
                'saveusergroups',
                'userrights-groupsmember',
                'userrights-groupsmember-auto',
 +              'userrights-groupsmember-type',
                'userrights-groups-help',
                'userrights-reason',
                'userrights-no-interwiki',
                'rightslogtext',
                'rightslogentry',
                'rightslogentry-autopromote',
+               'logentry-rights-rights',
+               'logentry-rights-rights-legacy',
+               'logentry-rights-autopromote',
                'rightsnone',
        ),
        'action' => array(
                'pageinfo-default-sort',
                'pageinfo-length',
                'pageinfo-article-id',
 +              'pageinfo-language',
                'pageinfo-robot-policy',
                'pageinfo-robot-index',
                'pageinfo-robot-noindex',
                'version-license',
                'version-poweredby-credits',
                'version-poweredby-others',
 +              'version-credits-summary',
                'version-license-info',
                'version-software',
                'version-software-product',