Merge "Remove unneeded else{} in LogEventsList::userCanBitfield()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 4 Dec 2013 14:40:10 +0000 (14:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 4 Dec 2013 14:40:10 +0000 (14:40 +0000)
20 files changed:
RELEASE-NOTES-1.23
img_auth.php
includes/DefaultSettings.php
includes/EditPage.php
includes/Namespace.php
includes/ProtectionForm.php
includes/SkinTemplate.php
includes/filebackend/FileBackendStore.php
includes/filebackend/FileOp.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/UnregisteredLocalFile.php
includes/job/JobQueueRedis.php
includes/logging/LogEntry.php
includes/resourceloader/ResourceLoaderModule.php
languages/classes/LanguageRu.php
languages/messages/MessagesEn.php
maintenance/getConfiguration.php
maintenance/language/messages.inc
resources/mediawiki.language/mediawiki.language.js
tests/phpunit/languages/LanguageRuTest.php

index cf1678e..4697b4d 100644 (file)
@@ -39,6 +39,11 @@ production.
   hide their (unrelated) log entries.
 * Added $wgOpenSearchDefaultLimit defining the default number of entries to show
   on action=opensearch API call.
+* For namespaces with $wgNamespaceProtection (including the MediaWiki
+  namespace), the "protect" tab will be shown only if there are restriction
+  levels available that would restrict editing beyond what
+  $wgNamespaceProtection already applies. The protection form will offer only
+  those protection levels.
 
 === Bug fixes in 1.23 ===
 * (bug 41759) The "updated since last visit" markers (on history pages, recent
index c8759ec..2d2db9a 100644 (file)
@@ -52,7 +52,7 @@ wfImageAuthMain();
 wfLogProfilingData();
 
 function wfImageAuthMain() {
-       global $wgImgAuthPublicTest, $wgRequest;
+       global $wgImgAuthPublicTest, $wgImgAuthUrlPathMap, $wgRequest;
 
        // See if this is a public Wiki (no protections).
        if ( $wgImgAuthPublicTest
@@ -77,14 +77,32 @@ function wfImageAuthMain() {
 
        // Check for bug 28235: QUERY_STRING overriding the correct extension
        $whitelist = array();
-       $dotPos = strrpos( $path, '.' );
-       if ( $dotPos !== false ) {
-               $whitelist[] = substr( $path, $dotPos + 1 );
+       $extension = FileBackend::extensionFromPath( $path );
+       if ( $extension != '' ) {
+               $whitelist[] = $extension;
        }
        if ( !$wgRequest->checkUrlExtension( $whitelist ) ) {
                return;
        }
 
+       // Various extensions may have their own backends that need access.
+       // Check if there is a special backend and storage base path for this file.
+       foreach ( $wgImgAuthUrlPathMap as $prefix => $storageDir ) {
+               $prefix = rtrim( $prefix, '/' ) . '/'; // implicit trailing slash
+               if ( strpos( $path, $prefix ) === 0 ) {
+                       $be = FileBackendGroup::singleton()->backendFromPath( $storageDir );
+                       $filename = $storageDir . substr( $path, strlen( $prefix ) ); // strip prefix
+                       if ( $be->fileExists( array( 'src' => $filename ) ) ) {
+                               wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
+                               $be->streamFile( array( 'src' => $filename ),
+                                       array( 'Cache-Control: private', 'Vary: Cookie' ) );
+                       } else {
+                               wfForbidden( 'img-auth-accessdenied', 'img-auth-nofile', $filename );
+                       }
+                       return;
+               }
+       }
+
        // Get the local file repository
        $repo = RepoGroup::singleton()->getRepo( 'local' );
 
@@ -145,6 +163,7 @@ function wfForbidden( $msg1, $msg2 ) {
        $args = func_get_args();
        array_shift( $args );
        array_shift( $args );
+       $args = ( isset( $args[0] ) && is_array( $args[0] ) ) ? $args[0] : $args;
 
        $msgHdr = wfMessage( $msg1 )->escaped();
        $detailMsgKey = $wgImgAuthDetails ? $msg2 : 'badaccess-group0';
index 3102384..e9e4545 100644 (file)
@@ -361,6 +361,22 @@ $wgImgAuthDetails = false;
  */
 $wgImgAuthPublicTest = true;
 
+/**
+ * Map of relative URL directories to match to internal mwstore:// base storage paths.
+ * For img_auth.php requests, everything after "img_auth.php/" is checked to see
+ * if starts with any of the prefixes defined here. The prefixes should not overlap.
+ * The prefix that matches has a corresponding storage path, which the rest of the URL
+ * is assumed to be relative to. The file at that path (or a 404) is send to the client.
+ *
+ * Example:
+ * $wgImgAuthUrlPathMap['/timeline/'] = 'mwstore://local-fs/timeline-render/';
+ * The above maps ".../img_auth.php/timeline/X" to "mwstore://local-fs/timeline-render/".
+ * The name "local-fs" should correspond by name to an entry in $wgFileBackends.
+ *
+ * @see $wgFileBackends
+ */
+$wgImgAuthUrlPathMap = array();
+
 /**
  * File repository structures
  *
index c0e7ed2..f86cae4 100644 (file)
@@ -2476,7 +2476,9 @@ class EditPage {
                        }
                }
 
-               if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+               if ( $this->mTitle->isProtected( 'edit' ) &&
+                       MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
+               ) {
                        # Is the title semi-protected?
                        if ( $this->mTitle->isSemiProtected() ) {
                                $noticeMsg = 'semiprotectedpagewarning';
@@ -2678,7 +2680,9 @@ HTML
                        $attribs = array( 'style' => 'display:none;' );
                } else {
                        $classes = array(); // Textarea CSS
-                       if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+                       if ( $this->mTitle->isProtected( 'edit' ) &&
+                               MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
+                       ) {
                                # Is the title semi-protected?
                                if ( $this->mTitle->isSemiProtected() ) {
                                        $classes[] = 'mw-textarea-sprotected';
index 5c8e63b..ce585ce 100644 (file)
@@ -433,4 +433,74 @@ class MWNamespace {
                        ? $wgNamespaceContentModels[$index]
                        : null;
        }
+
+       /**
+        * Determine which restriction levels it makes sense to use in a namespace,
+        * optionally filtered by a user's rights.
+        *
+        * @since 1.23
+        * @param int $index Index to check
+        * @param User $user User to check
+        * @return array
+        */
+       public static function getRestrictionLevels( $index, User $user = null ) {
+               global $wgNamespaceProtection, $wgRestrictionLevels;
+
+               if ( !isset( $wgNamespaceProtection[$index] ) ) {
+                       // All levels are valid if there's no namespace restriction.
+                       // But still filter by user, if necessary
+                       $levels = $wgRestrictionLevels;
+                       if ( $user ) {
+                               $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
+                                       $right = $level;
+                                       if ( $right == 'sysop' ) {
+                                               $right = 'editprotected'; // BC
+                                       }
+                                       if ( $right == 'autoconfirmed' ) {
+                                               $right = 'editsemiprotected'; // BC
+                                       }
+                                       return ( $right == '' || $user->isAllowed( $right ) );
+                               } ) );
+                       }
+                       return $levels;
+               }
+
+               // First, get the list of groups that can edit this namespace.
+               $namespaceGroups = array();
+               $combine = 'array_merge';
+               foreach ( (array)$wgNamespaceProtection[$index] as $right ) {
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+                       if ( $right != '' ) {
+                               $namespaceGroups = call_user_func( $combine, $namespaceGroups,
+                                       User::getGroupsWithPermission( $right ) );
+                               $combine = 'array_intersect';
+                       }
+               }
+
+               // Now, keep only those restriction levels where there is at least one
+               // group that can edit the namespace but would be blocked by the
+               // restriction.
+               $usableLevels = array( '' );
+               foreach ( $wgRestrictionLevels as $level ) {
+                       $right = $level;
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+                       if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) &&
+                               array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) )
+                       ) {
+                               $usableLevels[] = $level;
+                       }
+               }
+
+               return $usableLevels;
+       }
 }
index f10317a..d6cc8a3 100644 (file)
@@ -83,8 +83,8 @@ class ProtectionForm {
         */
        function loadData() {
                global $wgRequest, $wgUser;
-               global $wgRestrictionLevels;
 
+               $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(), $wgUser );
                $this->mCascade = $this->mTitle->areRestrictionsCascading();
 
                $this->mReason = $wgRequest->getText( 'mwProtect-reason' );
@@ -132,21 +132,7 @@ class ProtectionForm {
                        }
 
                        $val = $wgRequest->getVal( "mwProtect-level-$action" );
-                       if ( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
-                               // Prevent users from setting levels that they cannot later unset
-                               if ( $val == 'sysop' ) {
-                                       // Special case, rewrite sysop to editprotected
-                                       if ( !$wgUser->isAllowed( 'editprotected' ) ) {
-                                               continue;
-                                       }
-                               } elseif ( $val == 'autoconfirmed' ) {
-                                       // Special case, rewrite autoconfirmed to editsemiprotected
-                                       if ( !$wgUser->isAllowed( 'editsemiprotected' ) ) {
-                                               continue;
-                                       }
-                               } elseif ( !$wgUser->isAllowed( $val ) ) {
-                                       continue;
-                               }
+                       if ( isset( $val ) && in_array( $val, $levels ) ) {
                                $this->mRestrictions[$action] = $val;
                        }
                }
@@ -189,7 +175,7 @@ class ProtectionForm {
        function execute() {
                global $wgRequest, $wgOut;
 
-               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+               if ( MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) === array( '' ) ) {
                        throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
                }
 
@@ -556,28 +542,13 @@ class ProtectionForm {
         * @return String: HTML fragment
         */
        function buildSelector( $action, $selected ) {
-               global $wgRestrictionLevels, $wgUser;
-
-               $levels = array();
-               foreach ( $wgRestrictionLevels as $key ) {
-                       //don't let them choose levels above their own (aka so they can still unprotect and edit the page). but only when the form isn't disabled
-                       if ( $key == 'sysop' ) {
-                               //special case, rewrite sysop to editprotected
-                               if ( !$wgUser->isAllowed( 'editprotected' ) && !$this->disabled ) {
-                                       continue;
-                               }
-                       } elseif ( $key == 'autoconfirmed' ) {
-                               //special case, rewrite autoconfirmed to editsemiprotected
-                               if ( !$wgUser->isAllowed( 'editsemiprotected' ) && !$this->disabled ) {
-                                       continue;
-                               }
-                       } else {
-                               if ( !$wgUser->isAllowed( $key ) && !$this->disabled ) {
-                                       continue;
-                               }
-                       }
-                       $levels[] = $key;
-               }
+               global $wgUser;
+
+               // If the form is disabled, display all relevant levels. Otherwise,
+               // just show the ones this user can use.
+               $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(),
+                       $this->disabled ? null : $wgUser
+               );
 
                $id = 'mwProtect-level-' . $action;
                $attribs = array(
index dde3f37..da4c376 100644 (file)
@@ -1039,7 +1039,9 @@ class SkinTemplate extends Skin {
                                        }
                                }
 
-                               if ( $title->getNamespace() !== NS_MEDIAWIKI && $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() ) {
+                               if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() &&
+                                       MWNamespace::getRestrictionLevels( $title->getNamespace(), $user ) !== array( '' )
+                               ) {
                                        $mode = $title->isProtected() ? 'unprotect' : 'protect';
                                        $content_navigation['actions'][$mode] = array(
                                                'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
index 2fe51c0..2f3e0e2 100644 (file)
@@ -70,7 +70,7 @@ abstract class FileBackendStore extends FileBackend {
                        ? $config['mimeCallback']
                        : function ( $storagePath, $content, $fsPath ) {
                                // @TODO: handle the case of extension-less files using the contents
-                               return StreamFile::contentTypeFromPath( $storagePath ) ? : 'unknown/unknown';
+                               return StreamFile::contentTypeFromPath( $storagePath ) ?: 'unknown/unknown';
                        };
                $this->memCache = new EmptyBagOStuff(); // disabled by default
                $this->cheapCache = new ProcessCacheLRU( self::CACHE_CHEAP_SIZE );
index 538d9b4..82959d8 100644 (file)
@@ -645,7 +645,7 @@ class CopyFileOp extends FileOp {
                        $status = Status::newGood(); // nothing to do
                } elseif ( $this->params['src'] === $this->params['dst'] ) {
                        // Just update the destination file headers
-                       $headers = $this->getParam( 'headers' ) ? : array();
+                       $headers = $this->getParam( 'headers' ) ?: array();
                        $status = $this->backend->describeInternal( $this->setFlags( array(
                                'src' => $this->params['dst'], 'headers' => $headers
                        ) ) );
index c6ea79f..191642f 100644 (file)
@@ -46,44 +46,81 @@ define( 'MW_FILE_VERSION', 9 );
 class LocalFile extends File {
        const CACHE_FIELD_MAX_LEN = 1000;
 
-       /**#@+
-        * @private
-        */
-       var
-               $fileExists,       # does the file exist on disk? (loadFromXxx)
-               $historyLine,      # Number of line to return by nextHistoryLine() (constructor)
-               $historyRes,       # result of the query for the file's history (nextHistoryLine)
-               $width,            # \
-               $height,           #  |
-               $bits,             #   --- returned by getimagesize (loadFromXxx)
-               $attr,             # /
-               $media_type,       # MEDIATYPE_xxx (bitmap, drawing, audio...)
-               $mime,             # MIME type, determined by MimeMagic::guessMimeType
-               $major_mime,       # Major mime type
-               $minor_mime,       # Minor mime type
-               $size,             # Size in bytes (loadFromXxx)
-               $metadata,         # Handler-specific metadata
-               $timestamp,        # Upload timestamp
-               $sha1,             # SHA-1 base 36 content hash
-               $user, $user_text, # User, who uploaded the file
-               $description,      # Description of current revision of the file
-               $dataLoaded,       # Whether or not core data has been loaded from the database (loadFromXxx)
-               $extraDataLoaded,  # Whether or not lazy-loaded data has been loaded from the database
-               $upgraded,         # Whether the row was upgraded on load
-               $locked,           # True if the image row is locked
-               $lockedOwnTrx,     # True if the image row is locked with a lock initiated transaction
-               $missing,          # True if file is not present in file system. Not to be cached in memcached
-               $deleted;          # Bitfield akin to rev_deleted
-
-       /**#@-*/
-
-       /**
-        * @var LocalRepo
-        */
-       var $repo;
+       /** @var bool Does the file exist on disk? (loadFromXxx) */
+       protected $fileExists;
 
+       /** @var int image width */
+       protected $width;
+
+       /** @var int image height */
+       protected $height;
+
+       /** @var int Returned by getimagesize (loadFromXxx) */
+       protected $bits;
+
+       /** @var string MEDIATYPE_xxx (bitmap, drawing, audio...) */
+       protected $media_type;
+
+       /** @var string MIME type, determined by MimeMagic::guessMimeType */
+       protected $mime;
+
+       /** @var int Size in bytes (loadFromXxx) */
+       protected $size;
+
+       /** @var string Handler-specific metadata */
+       protected $metadata;
+
+       /** @var string SHA-1 base 36 content hash */
+       protected $sha1;
+
+       /** @var bool Whether or not core data has been loaded from the database (loadFromXxx) */
+       protected $dataLoaded;
+
+       /** @var bool Whether or not lazy-loaded data has been loaded from the database */
+       protected $extraDataLoaded;
+
+       /** @var int Bitfield akin to rev_deleted */
+       protected $deleted;
+
+       /** @var string */
        protected $repoClass = 'LocalRepo';
 
+       /** @var int Number of line to return by nextHistoryLine() (constructor) */
+       private $historyLine;
+
+       /** @var int Result of the query for the file's history (nextHistoryLine) */
+       private $historyRes;
+
+       /** @var string Major mime type */
+       private $major_mime;
+
+       /** @var string Minor mime type */
+       private $minor_mime;
+
+       /** @var string Upload timestamp */
+       private $timestamp;
+
+       /** @var int User ID of uploader */
+       private $user;
+
+       /** @var string User name of uploader */
+       private $user_text;
+
+       /** @var string Description of current revision of the file */
+       private $description;
+
+       /** @var bool Whether the row was upgraded on load */
+       private $upgraded;
+
+       /** @var bool True if the image row is locked */
+       private $locked;
+
+       /** @var bool True if the image row is locked with a lock initiated transaction */
+       private $lockedOwnTrx;
+
+       /** @var bool True if file is not present in file system. Not to be cached in memcached */
+       private $missing;
+
        const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
 
        /**
@@ -1852,13 +1889,26 @@ class LocalFile extends File {
  */
 class LocalFileDeleteBatch {
 
-       /**
-        * @var LocalFile
-        */
-       var $file;
+       /** @var LocalFile */
+       private $file;
+
+       /** @var string */
+       private $reason;
 
-       var $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
-       var $status;
+       /** @var array */
+       private $srcRels = array();
+
+       /** @var array */
+       private $archiveUrls = array();
+
+       /** @var array Items to be processed in the deletion batch */
+       private $deletionBatch;
+
+       /** @var bool Wether to suppress all suppressable fields when deleting */
+       private $suppress;
+
+       /** @var FileRepoStatus */
+       private $status;
 
        /**
         * @param $file File
@@ -2197,12 +2247,20 @@ class LocalFileDeleteBatch {
  * @ingroup FileAbstraction
  */
 class LocalFileRestoreBatch {
-       /**
-        * @var LocalFile
-        */
-       var $file;
+       /** @var LocalFile */
+       private $file;
+
+       /** @var array List of file IDs to restore */
+       private $cleanupBatch;
+
+       /** @var array List of file IDs to restore */
+       private $ids;
 
-       var $cleanupBatch, $ids, $all, $unsuppress = false;
+       /** @var bool Add all revisions of the file  */
+       private $all;
+
+       /** @var bool Wether to remove all settings for suppressed fields  */
+       private $unsuppress = false;
 
        /**
         * @param $file File
@@ -2563,23 +2621,28 @@ class LocalFileRestoreBatch {
  * @ingroup FileAbstraction
  */
 class LocalFileMoveBatch {
+       /** @var LocalFile */
+       protected $file;
 
-       /**
-        * @var LocalFile
-        */
-       var $file;
+       /** @var Title */
+       protected $target;
 
-       /**
-        * @var Title
-        */
-       var $target;
+       /** @var   */
+       protected $cur;
+
+       /** @var   */
+       protected $olds;
+
+       /** @var   */
+       protected $oldCount;
 
-       var $cur, $olds, $oldCount, $archive;
+       /** @var   */
+       protected $archive;
 
        /**
         * @var DatabaseBase
         */
-       var $db;
+       protected $db;
 
        /**
         * @param File $file
index e7fa46e..6d0786d 100644 (file)
  *
  * Read-only.
  *
- * TODO: Currently it doesn't really work in the repository role, there are
+ * @todo Currently it doesn't really work in the repository role, there are
  * lots of functions missing. It is used by the WebStore extension in the
  * standalone role.
  *
  * @ingroup FileAbstraction
  */
 class UnregisteredLocalFile extends File {
-       var $title, $path, $mime, $dims, $metadata;
+       /** @var Title  */
+       protected $title;
 
-       /**
-        * @var MediaHandler
-        */
-       var $handler;
+       /** @var string */
+       protected $path;
+
+       /** @var bool|string */
+       protected $mime;
+
+       /** @var array Dimension data */
+       protected $dims;
+
+       /** @var bool|string Handler-specific metadata which will be saved in the img_metadata field */
+       protected $metadata;
+
+       /** @var MediaHandler */
+       public $handler;
 
        /**
         * @param string $path Storage path
index 9d2a673..9b9fe2d 100644 (file)
@@ -776,7 +776,7 @@ LUA;
                        'title' => $job->getTitle()->getDBkey(),
                        'params' => $job->getParams(),
                        // Some jobs cannot run until a "release timestamp"
-                       'rtimestamp' => $job->getReleaseTimestamp() ? : 0,
+                       'rtimestamp' => $job->getReleaseTimestamp() ?: 0,
                        // Additional job metadata
                        'uuid' => UIDGenerator::newRawUUIDv4( UIDGenerator::QUICK_RAND ),
                        'sha1' => $job->ignoreDuplicates()
index e15943f..40846a2 100644 (file)
@@ -450,7 +450,7 @@ class ManualLogEntry extends LogEntryBase {
        public function insert( IDatabase $dbw = null ) {
                global $wgContLang;
 
-               $dbw = $dbw ? : wfGetDB( DB_MASTER );
+               $dbw = $dbw ?: wfGetDB( DB_MASTER );
                $id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
 
                if ( $this->timestamp === null ) {
index db84dcf..429bcec 100644 (file)
@@ -408,7 +408,7 @@ abstract class ResourceLoaderModule {
                }
 
                $cache = wfGetCache( CACHE_ANYTHING );
-               $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName() );
+               $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName(), $hash );
 
                $data = $cache->get( $key );
                if ( is_array( $data ) && $data['hash'] === $hash ) {
index 11b42cf..243a876 100644 (file)
@@ -122,6 +122,10 @@ class LanguageRu extends Language {
         * @return string
         */
        function convertPlural( $count, $forms ) {
+               $forms = $this->handleExplicitPluralForms( $count, $forms );
+               if ( is_string( $forms ) ) {
+                       return $forms;
+               }
                if ( !count( $forms ) ) {
                        return '';
                }
index e6ecc78..1bd59b2 100644 (file)
@@ -815,7 +815,6 @@ future releases. Also note that since each list value is wrapped in a unique
 'qbedit'         => 'Edit',
 'qbpageoptions'  => 'This page',
 'qbmyoptions'    => 'My pages',
-'qbspecialpages' => 'Special pages',
 'faq'            => 'FAQ',
 'faqpage'        => 'Project:FAQ',
 'sitetitle'      => '{{SITENAME}}', # do not translate or duplicate this message to other languages
@@ -1051,9 +1050,6 @@ It may contain one or more characters that cannot be used in titles.',
 'perfcachedts'                  => 'The following data is cached, and was last updated $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.',
 'querypage-no-updates'          => 'Updates for this page are currently disabled.
 Data here will not presently be refreshed.',
-'wrong_wfQuery_params'          => 'Incorrect parameters to wfQuery()<br />
-Function: $1<br />
-Query: $2',
 'viewsource'                    => 'View source',
 'viewsource-title'              => 'View source for $1',
 'actionthrottled'               => 'Action throttled',
@@ -4537,10 +4533,6 @@ $8', # only translate this message to other languages if you have to change it
 'exif-urgency-high'   => 'High ($1)',
 'exif-urgency-other'  => 'User-defined priority ($1)',
 
-# External editor support
-'edit-externally'      => 'Edit this file using an external application',
-'edit-externally-help' => '(See the [https://www.mediawiki.org/wiki/Manual:External_editors setup instructions] for more information)',
-
 # 'all' in various places, this might be different for inflected languages
 'watchlistall2' => 'all',
 'namespacesall' => 'all',
index 5a5eb58..52cb209 100644 (file)
@@ -87,7 +87,7 @@ class GetConfiguration extends Maintenance {
        public function finalSetup() {
                parent::finalSetup();
 
-               $this->regex = $this->getOption( 'regex' ) ? : $this->getOption( 'iregex' );
+               $this->regex = $this->getOption( 'regex' ) ?: $this->getOption( 'iregex' );
                if ( $this->regex ) {
                        $this->regex = '/' . $this->regex . '/';
                        if ( $this->hasOption( 'iregex' ) ) {
index f0160cc..947dfe9 100644 (file)
@@ -188,7 +188,6 @@ $wgMessageStructure = array(
                'qbedit',
                'qbpageoptions',
                'qbmyoptions',
-               'qbspecialpages',
                'faq',
                'faqpage',
                'sitetitle',
@@ -406,7 +405,6 @@ $wgMessageStructure = array(
                'perfcached',
                'perfcachedts',
                'querypage-no-updates',
-               'wrong_wfQuery_params',
                'viewsource',
                'viewsource-title',
                'actionthrottled',
@@ -3453,10 +3451,6 @@ $wgMessageStructure = array(
                'exif-urgency-high',
                'exif-urgency-other',
        ),
-       'edit-externally' => array(
-               'edit-externally',
-               'edit-externally-help',
-       ),
        'all' => array(
                'watchlistall2',
                'namespacesall',
@@ -4191,7 +4185,6 @@ Variants for Chinese language",
        'exif-maxaperturevalue' => '',
        'exif-iimcategory' => '',
        'exif-urgency' => '',
-       'edit-externally' => 'External editor support',
        'all' => "'all' in various places, this might be different for inflected languages",
        'confirmemail' => 'Email address confirmation',
        'scarytransclusion' => 'Scary transclusion',
index 631d13d..3927f00 100644 (file)
@@ -73,6 +73,10 @@ var language = {
                        return form;
                } );
 
+               if ( forms.length === 0 ) {
+                       return '';
+               }
+
                pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' );
                if ( !pluralRules ) {
                        // default fallback.
index e938be7..56f8490 100644 (file)
@@ -17,6 +17,17 @@ class LanguageRuTest extends LanguageClassesTestCase {
                $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) );
        }
 
+       /**
+        * Test explicit plural forms - n=FormN forms
+        * @covers Language::convertPlural
+        */
+       public function testExplicitPlural() {
+               $forms = array( 'one', 'few', 'many', 'other', '12=dozen' );
+               $this->assertEquals( 'dozen', $this->getLang()->convertPlural( 12, $forms ) );
+               $forms = array( 'one', 'few', 'many', '100=hundred', 'other', '12=dozen' );
+               $this->assertEquals( 'hundred', $this->getLang()->convertPlural( 100, $forms ) );
+       }
+
        /**
         * @dataProvider providePlural
         * @covers Language::getPluralRuleType