Merge "Update Namespaces, SpecialPageAliases and MagicWords for South Azerbaijani...
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 17 Jul 2015 19:11:01 +0000 (19:11 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 17 Jul 2015 19:11:01 +0000 (19:11 +0000)
32 files changed:
autoload.php
includes/DefaultSettings.php
includes/Message.php
includes/OutputPage.php
includes/Title.php
includes/User.php
includes/WatchedItem.php
includes/actions/InfoAction.php
includes/api/ApiMessage.php
includes/api/ApiStashEdit.php
includes/db/LoadMonitorMySQL.php
includes/filebackend/FileBackend.php
includes/filerepo/file/File.php
includes/htmlform/HTMLSelectNamespace.php
includes/installer/MysqlInstaller.php
includes/installer/PostgresInstaller.php
includes/installer/i18n/en.json
includes/installer/i18n/qqq.json
includes/media/SVG.php
includes/media/XMP.php
includes/widget/AUTHORS.txt
includes/widget/NamespaceInputWidget.php [new file with mode: 0644]
includes/widget/TitleInputWidget.php
languages/messages/MessagesPnb.php
resources/Resources.php
resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css [new file with mode: 0644]
resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js [new file with mode: 0644]
resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js
resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js
tests/phpunit/includes/MessageTest.php
tests/phpunit/includes/api/ApiMessageTest.php
thumb.php

index 2c9dd8f..fd75c8a 100644 (file)
@@ -754,6 +754,7 @@ $wgAutoloadLocalClasses = array(
        'MediaWiki\\Logger\\Monolog\\WikiProcessor' => __DIR__ . '/includes/debug/logger/monolog/WikiProcessor.php',
        'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
        'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
+       'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php',
        'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
        'MemCachedClientforWiki' => __DIR__ . '/includes/objectcache/MemcachedClient.php',
        'MemcLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MemcLockManager.php',
index 6050ba7..70ae468 100644 (file)
@@ -1623,15 +1623,6 @@ $wgEnotifMaxRecips = 500;
  */
 $wgEnotifUseJobQ = false;
 
-/**
- * Use the job queue for user activity updates like updating "last visited"
- * fields for email notifications of page changes. This should only be enabled
- * if the jobs have a dedicated runner to avoid update lag.
- *
- * @since 1.26
- */
-$wgActivityUpdatesUseJobQueue = false;
-
 /**
  * Use real name instead of username in e-mail "from" field.
  */
index 329d97a..ee41db0 100644 (file)
  *
  * @since 1.17
  */
-class Message implements MessageSpecifier {
+class Message implements MessageSpecifier, Serializable {
 
        /**
         * In which language to get this message. True, which is the default,
@@ -252,6 +252,41 @@ class Message implements MessageSpecifier {
                $this->language = $language ?: $wgLang;
        }
 
+       /**
+        * @see Serializable::serialize()
+        * @since 1.26
+        * @return string
+        */
+       public function serialize() {
+               return serialize( array(
+                       'interface' => $this->interface,
+                       'language' => $this->language->getCode(),
+                       'key' => $this->key,
+                       'keysToTry' => $this->keysToTry,
+                       'parameters' => $this->parameters,
+                       'format' => $this->format,
+                       'useDatabase' => $this->useDatabase,
+                       'title' => $this->title,
+               ) );
+       }
+
+       /**
+        * @see Serializable::unserialize()
+        * @since 1.26
+        * @param string $serialized
+        */
+       public function unserialize( $serialized ) {
+               $data = unserialize( $serialized );
+               $this->interface = $data['interface'];
+               $this->key = $data['key'];
+               $this->keysToTry = $data['keysToTry'];
+               $this->parameters = $data['parameters'];
+               $this->format = $data['format'];
+               $this->useDatabase = $data['useDatabase'];
+               $this->language = Language::factory( $data['language'] );
+               $this->title = $data['title'];
+       }
+
        /**
         * @since 1.24
         *
index 30ee19c..e832b82 100644 (file)
@@ -3959,6 +3959,7 @@ class OutputPage extends ContextSource {
                        'oojs-ui.styles.icons',
                        'oojs-ui.styles.indicators',
                        'oojs-ui.styles.textures',
+                       'mediawiki.widgets.styles',
                ) );
        }
 }
index 1ab88a6..fac45d0 100644 (file)
@@ -4759,4 +4759,26 @@ class Title {
                Hooks::run( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
                return $notices;
        }
+
+       /**
+        * @return array
+        */
+       public function __sleep() {
+               return array(
+                       'mNamespace',
+                       'mDbkeyform',
+                       'mFragment',
+                       'mInterwiki',
+                       'mLocalInterwiki',
+                       'mUserCaseDBKey',
+                       'mDefaultNamespace',
+               );
+       }
+
+       public function __wakeup() {
+               $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
+               $this->mUrlform = wfUrlencode( $this->mDbkeyform );
+               $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
+       }
+
 }
index 06ae856..bd0d30b 100644 (file)
@@ -3721,15 +3721,13 @@ class User implements IDBAccessObject {
                        : wfGetDB( DB_SLAVE );
 
                $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
-                       ? array( 'FOR UPDATE' )
+                       ? array( 'LOCK IN SHARE MODE' )
                        : array();
 
-               $id = $db->selectField( 'user', 'user_id', array( 'user_name' => $s ), __METHOD__, $options );
-               if ( $id === false ) {
-                       $id = 0;
-               }
+               $id = $db->selectField( 'user',
+                       'user_id', array( 'user_name' => $s ), __METHOD__, $options );
 
-               return $id;
+               return (int)$id;
        }
 
        /**
index 73b0b93..adee126 100644 (file)
@@ -224,8 +224,6 @@ class WatchedItem {
        public function resetNotificationTimestamp(
                $force = '', $oldid = 0, $mode = self::IMMEDIATE
        ) {
-               global $wgActivityUpdatesUseJobQueue;
-
                // Only loggedin user can have a watchlist
                if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
                        return;
@@ -275,20 +273,20 @@ class WatchedItem {
                }
 
                // If the page is watched by the user (or may be watched), update the timestamp
-               if ( $mode === self::DEFERRED && $wgActivityUpdatesUseJobQueue ) {
-                       JobQueueGroup::singleton()->push(
-                               EnqueueJob::newFromLocalJobs( new JobSpecification(
-                                       'activityUpdateJob',
-                                       array(
-                                               'type'      => 'updateWatchlistNotification',
-                                               'userid'    => $this->getUserId(),
-                                               'notifTime' => $notificationTimestamp,
-                                               'curTime'   => time()
-                                       ),
-                                       array( 'removeDuplicates' => true ),
-                                       $title
-                               ) )
+               if ( $mode === self::DEFERRED ) {
+                       $job = new ActivityUpdateJob(
+                               $title,
+                               array(
+                                       'type'      => 'updateWatchlistNotification',
+                                       'userid'    => $this->getUserId(),
+                                       'notifTime' => $notificationTimestamp,
+                                       'curTime'   => time()
+                               )
                        );
+                       // Try to run this post-send
+                       DeferredUpdates::addCallableUpdate( function() use ( $job ) {
+                               $job->run();
+                       } );
                } else {
                        $dbw = wfGetDB( DB_MASTER );
                        $dbw->update( 'watchlist',
index 0c1a344..5491f81 100644 (file)
@@ -675,11 +675,11 @@ class InfoAction extends FormlessAction {
                $id = $title->getArticleID();
                $config = $this->context->getConfig();
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbrWatchlist = wfGetDB( DB_SLAVE, 'watchlist' );
                $result = array();
 
                // Number of page watchers
-               $watchers = (int)$dbr->selectField(
+               $watchers = (int)$dbrWatchlist->selectField(
                        'watchlist',
                        'COUNT(*)',
                        array(
@@ -694,15 +694,15 @@ class InfoAction extends FormlessAction {
                        // Threshold: last visited about 26 weeks before latest edit
                        $updated = wfTimestamp( TS_UNIX, $this->page->getTimestamp() );
                        $age = $config->get( 'WatchersMaxAge' );
-                       $threshold = $dbr->timestamp( $updated - $age );
+                       $threshold = $dbrWatchlist->timestamp( $updated - $age );
                        // Number of page watchers who also visited a "recent" edit
-                       $visitingWatchers = (int)$dbr->selectField(
+                       $visitingWatchers = (int)$dbrWatchlist->selectField(
                                'watchlist',
                                'COUNT(*)',
                                array(
                                        'wl_namespace' => $title->getNamespace(),
                                        'wl_title' => $title->getDBkey(),
-                                       'wl_notificationtimestamp >= ' . $dbr->addQuotes( $threshold ) .
+                                       'wl_notificationtimestamp >= ' . $dbrWatchlist->addQuotes( $threshold ) .
                                        ' OR wl_notificationtimestamp IS NULL'
                                ),
                                __METHOD__
@@ -710,6 +710,7 @@ class InfoAction extends FormlessAction {
                        $result['visitingWatchers'] = $visitingWatchers;
                }
 
+               $dbr = wfGetDB( DB_SLAVE );
                // Total number of edits
                $edits = (int)$dbr->selectField(
                        'revision',
index 6717c39..db826a6 100644 (file)
@@ -132,6 +132,21 @@ class ApiMessage extends Message implements IApiMessage {
        public function setApiData( array $data ) {
                $this->apiData = $data;
        }
+
+       public function serialize() {
+               return serialize( array(
+                       'parent' => parent::serialize(),
+                       'apiCode' => $this->apiCode,
+                       'apiData' => $this->apiData,
+               ) );
+       }
+
+       public function unserialize( $serialized ) {
+               $data = unserialize( $serialized );
+               parent::unserialize( $data['parent'] );
+               $this->apiCode = $data['apiCode'];
+               $this->apiData = $data['apiData'];
+       }
 }
 
 /**
@@ -188,4 +203,19 @@ class ApiRawMessage extends RawMessage implements IApiMessage {
        public function setApiData( array $data ) {
                $this->apiData = $data;
        }
+
+       public function serialize() {
+               return serialize( array(
+                       'parent' => parent::serialize(),
+                       'apiCode' => $this->apiCode,
+                       'apiData' => $this->apiData,
+               ) );
+       }
+
+       public function unserialize( $serialized ) {
+               $data = unserialize( $serialized );
+               parent::unserialize( $data['parent'] );
+               $this->apiCode = $data['apiCode'];
+               $this->apiData = $data['apiData'];
+       }
 }
index 177b546..a66b356 100644 (file)
@@ -112,6 +112,7 @@ class ApiStashEdit extends ApiBase {
                if ( $user->pingLimiter( 'stashedit' ) ) {
                        $status = 'ratelimited';
                } elseif ( $wgMemc->lock( $key, 0, 30 ) ) {
+                       /** @noinspection PhpUnusedLocalVariableInspection */
                        $unlocker = new ScopedCallback( function() use ( $key ) {
                                global $wgMemc;
                                $wgMemc->unlock( $key );
index 7c4a35c..c80697b 100644 (file)
@@ -79,12 +79,13 @@ class LoadMonitorMySQL implements LoadMonitor {
                if ( $this->mainCache->lock( $key, 0, 10 ) ) {
                        # Let this process alone update the cache value
                        $cache = $this->mainCache;
+                       /** @noinspection PhpUnusedLocalVariableInspection */
                        $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
                                $cache->unlock( $key );
                        } );
                } elseif ( $staleValue ) {
                        # Could not acquire lock but an old cache exists, so use it
-                       return $value['lagTimes'];
+                       return $staleValue['lagTimes'];
                }
 
                $lagTimes = array();
index b87e26d..fa1c462 100644 (file)
@@ -380,6 +380,7 @@ abstract class FileBackend {
                                $op['headers']['Content-Disposition'] = $op['disposition'];
                        }
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
                return $this->doOperationsInternal( $ops, $opts );
        }
@@ -612,6 +613,7 @@ abstract class FileBackend {
                                $op['headers']['Content-Disposition'] = $op['disposition'];
                        }
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
                return $this->doQuickOperationsInternal( $ops );
        }
@@ -756,6 +758,7 @@ abstract class FileBackend {
                if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
                        return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
                return $this->doPrepare( $params );
        }
@@ -785,6 +788,7 @@ abstract class FileBackend {
                if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
                        return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
                return $this->doSecure( $params );
        }
@@ -816,6 +820,7 @@ abstract class FileBackend {
                if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
                        return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
                return $this->doPublish( $params );
        }
@@ -840,6 +845,7 @@ abstract class FileBackend {
                if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
                        return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
                return $this->doClean( $params );
        }
index 76ed27b..7ea658d 100644 (file)
@@ -421,7 +421,10 @@ abstract class File implements IDBAccessObject {
        public function getLocalRefPath() {
                $this->assertRepoDefined();
                if ( !isset( $this->fsFile ) ) {
+                       $starttime = microtime( true );
                        $this->fsFile = $this->repo->getLocalReference( $this->getPath() );
+                       RequestContext::getMain()->getStats()->timing( 'media.thumbnail.generate.fetchoriginal', microtime( true ) - $starttime );
+
                        if ( !$this->fsFile ) {
                                $this->fsFile = false; // null => false; cache negative hits
                        }
@@ -1094,6 +1097,8 @@ abstract class File implements IDBAccessObject {
        public function generateAndSaveThumb( $tmpFile, $transformParams, $flags ) {
                global $wgUseSquid, $wgIgnoreImageErrors;
 
+               $stats = RequestContext::getMain()->getStats();
+
                $handler = $this->getHandler();
 
                $normalisedParams = $transformParams;
@@ -1109,10 +1114,14 @@ abstract class File implements IDBAccessObject {
                        $this->generateBucketsIfNeeded( $normalisedParams, $flags );
                }
 
+               $starttime = microtime( true );
+
                // Actually render the thumbnail...
                $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
                $tmpFile->bind( $thumb ); // keep alive with $thumb
 
+               $stats->timing( 'media.thumbnail.generate.transform', microtime( true ) - $starttime );
+
                if ( !$thumb ) { // bad params?
                        $thumb = false;
                } elseif ( $thumb->isError() ) { // transform error
@@ -1123,6 +1132,9 @@ abstract class File implements IDBAccessObject {
                        }
                } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
                        // Copy the thumbnail from the file system into storage...
+
+                       $starttime = microtime( true );
+
                        $disposition = $this->getThumbDisposition( $thumbName );
                        $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
                        if ( $status->isOK() ) {
@@ -1130,6 +1142,9 @@ abstract class File implements IDBAccessObject {
                        } else {
                                $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
                        }
+
+                       $stats->timing( 'media.thumbnail.generate.store', microtime( true ) - $starttime );
+
                        // Give extensions a chance to do something with this thumbnail...
                        Hooks::run( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
                }
@@ -1139,10 +1154,16 @@ abstract class File implements IDBAccessObject {
                // "thumbs" which have the main image URL though (bug 13776)
                if ( $wgUseSquid ) {
                        if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
+                               $starttime = microtime( true );
+
                                SquidUpdate::purge( array( $thumbUrl ) );
+
+                               $stats->timing( 'media.thumbnail.generate.purge', microtime( true ) - $starttime );
                        }
                }
 
+
+
                return $thumb;
        }
 
@@ -1167,6 +1188,8 @@ abstract class File implements IDBAccessObject {
                        return false;
                }
 
+               $starttime = microtime( true );
+
                $params['physicalWidth'] = $bucket;
                $params['width'] = $bucket;
 
@@ -1182,6 +1205,8 @@ abstract class File implements IDBAccessObject {
 
                $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
 
+               $buckettime = microtime( true ) - $starttime;
+
                if ( !$thumb || $thumb->isError() ) {
                        return false;
                }
@@ -1191,6 +1216,8 @@ abstract class File implements IDBAccessObject {
                // this object exists
                $tmpFile->bind( $this );
 
+               RequestContext::getMain()->getStats()->timing( 'media.thumbnail.generate.bucket', $buckettime );
+
                return true;
        }
 
index dfab6cf..d6d564e 100644 (file)
@@ -22,21 +22,14 @@ class HTMLSelectNamespace extends HTMLFormField {
        }
 
        public function getInputOOUI( $value ) {
-               $namespaceOptions = Html::namespaceSelectorOptions( array( 'all' => $this->mAllValue ) );
-
-               $options = array();
-               foreach( $namespaceOptions as $id => $name ) {
-                       $options[] = array(
-                               'data' => (string)$id,
-                               'label' => $name,
-                       );
-               };
-
-               return new OOUI\DropdownInputWidget( array(
-                       'options' => $options,
-                       'value' => $value,
-                       'name' => $this->mName,
+               return new MediaWiki\Widget\NamespaceInputWidget( array(
+                       'valueNamespace' => $value,
+                       'nameNamespace' => $this->mName,
                        'id' => $this->mID,
+                       'includeAllValue' => $this->mAllValue,
+                       // Disable additional checkboxes
+                       'nameInvert' => null,
+                       'nameAssociated' => null,
                ) );
        }
 }
index 3af08d6..e5c1a1d 100644 (file)
@@ -93,9 +93,7 @@ class MysqlInstaller extends DatabaseInstaller {
 
        public function submitConnectForm() {
                // Get variables from the request.
-               $newValues = $this->setVarsFromRequest( array(
-                       'wgDBserver', 'wgDBname', 'wgDBprefix', '_InstallUser', '_InstallPassword'
-               ) );
+               $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBname', 'wgDBprefix' ) );
 
                // Validate them.
                $status = Status::newGood();
@@ -110,12 +108,6 @@ class MysqlInstaller extends DatabaseInstaller {
                if ( !preg_match( '/^[a-z0-9_-]*$/i', $newValues['wgDBprefix'] ) ) {
                        $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
                }
-               if ( !strlen( $newValues['_InstallUser'] ) ) {
-                       $status->fatal( 'config-db-username-empty' );
-               }
-               if ( !strlen( $newValues['_InstallPassword'] ) ) {
-                       $status->fatal( 'config-db-password-empty', $newValues['_InstallUser'] );
-               }
                if ( !$status->isOK() ) {
                        return $status;
                }
index b18fe94..cb40f88 100644 (file)
@@ -83,10 +83,7 @@ class PostgresInstaller extends DatabaseInstaller {
 
        function submitConnectForm() {
                // Get variables from the request
-               $newValues = $this->setVarsFromRequest( array(
-                       'wgDBserver', 'wgDBport', 'wgDBname', 'wgDBmwschema',
-                       '_InstallUser', '_InstallPassword'
-               ) );
+               $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBport', 'wgDBname', 'wgDBmwschema' ) );
 
                // Validate them
                $status = Status::newGood();
@@ -98,12 +95,6 @@ class PostgresInstaller extends DatabaseInstaller {
                if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
                        $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
                }
-               if ( !strlen( $newValues['_InstallUser'] ) ) {
-                       $status->fatal( 'config-db-username-empty' );
-               }
-               if ( !strlen( $newValues['_InstallPassword'] ) ) {
-                       $status->fatal( 'config-db-password-empty', $newValues['_InstallUser'] );
-               }
 
                // Submit user box
                if ( $status->isOK() ) {
index c19e3ee..1eb8e03 100644 (file)
@@ -97,8 +97,6 @@
        "config-db-install-account": "User account for installation",
        "config-db-username": "Database username:",
        "config-db-password": "Database password:",
-       "config-db-password-empty": "Please enter a password for the new database user: $1.\nWhile it may be possible to create users with no passwords, it is not secure.",
-       "config-db-username-empty": "You must enter a value for \"{{int:config-db-username}}\".",
        "config-db-install-username": "Enter the username that will be used to connect to the database during the installation process.\nThis is not the username of the MediaWiki account; this is the username for your database.",
        "config-db-install-password": "Enter the password that will be used to connect to the database during the installation process.\nThis is not the password for the MediaWiki account; this is the password for your database.",
        "config-db-install-help": "Enter the username and password that will be used to connect to the database during the installation process.",
index fba5165..89f2dd9 100644 (file)
        "config-db-name-oracle": "Field label in the MediaWiki installer where an Oracle database schema can be specified.",
        "config-db-account-oracle-warn": "A \"[[:wikipedia:Front and back ends|backend]]\" is a system or component that ordinary users don't interact with directly and don't need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are \"system\" or \"service\", or (depending on context and language) even leave it untranslated.",
        "config-db-install-account": "Legend in the MediaWiki installer for the section where database username and password have to be provided.",
-       "config-db-username": "Used as label.\n\nAlso used in {{msg-mw|Config-db-username-empty}}.",
+       "config-db-username": "Used as label.",
        "config-db-password": "Field label in the MediaWiki installer where database password has to be provided.",
-       "config-db-password-empty": "Used as error message. Parameters:\n* $1 - database username",
-       "config-db-username-empty": "Used as error message. Shown when the database username is not entered by the user.\n\nRefers to {{msg-mw|Config-db-username}}.",
        "config-db-install-username": "Help box text in the MediaWiki installer clarifying the requirement for database username.",
        "config-db-install-password": "Help box text in the MediaWiki installer clarifying the requirement for database password.",
        "config-db-install-help": "Help text in MediaWiki installer.",
index 4377f75..1118598 100644 (file)
@@ -205,6 +205,7 @@ class SvgHandler extends ImageHandler {
                $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 );
                $lnPath = "$tmpDir/" . basename( $srcPath );
                $ok = mkdir( $tmpDir, 0771 ) && symlink( $srcPath, $lnPath );
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) {
                        MediaWiki\suppressWarnings();
                        unlink( $lnPath );
index a838355..12550a5 100644 (file)
@@ -525,6 +525,7 @@ class XMPReader implements LoggerAwareInterface {
                );
 
                $oldDisable = libxml_disable_entity_loader( true );
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $reset = new ScopedCallback(
                        'libxml_disable_entity_loader',
                        array( $oldDisable )
index 10064b2..a0d7703 100644 (file)
@@ -3,6 +3,7 @@ Authors (alphabetically)
 Alex Monk <krenair@wikimedia.org>
 Bartosz Dziewoński <bdziewonski@wikimedia.org>
 Ed Sanders <esanders@wikimedia.org>
+Florian Schmidt <florian.schmidt.welzow@t-online.de>
 James D. Forrester <jforrester@wikimedia.org>
 Roan Kattouw <roan@wikimedia.org>
 Sucheta Ghoshal <sghoshal@wikimedia.org>
diff --git a/includes/widget/NamespaceInputWidget.php b/includes/widget/NamespaceInputWidget.php
new file mode 100644 (file)
index 0000000..69427c5
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * MediaWiki Widgets – NamespaceInputWidget class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+namespace MediaWiki\Widget;
+
+/**
+ * Namespace input widget. Displays a dropdown box with the choice of available namespaces, plus two
+ * checkboxes to include associated namespace or to invert selection.
+ */
+class NamespaceInputWidget extends \OOUI\Widget {
+
+       protected $namespace = null;
+       protected $associated = null;
+       protected $invert = null;
+       protected $allValue = null;
+
+       /**
+        * @param array $config Configuration options
+        * @param string $config['nameNamespace'] HTML input name for the namespace dropdown box (default:
+        *     'namespace')
+        * @param string $config['nameInvert'] HTML input name for the "invert selection" checkbox. If
+        *     null, the checkbox will not be generated. (default: 'invert')
+        * @param string $config['nameAssociated'] HTML input name for the "include associated namespace"
+        *     checkbox. If null, the checkbox will not be generated. (default: 'associated')
+        * @param string $config['includeAllValue'] If specified, add a "all namespaces" option to the
+        *     namespace dropdown, and use this as the input value for it
+        * @param int|string $config['valueNamespace'] Input value of the namespace dropdown box. May be a
+        *     string only if 'includeAllValue' is set.
+        * @param boolean $config['valueInvert'] Input value of the "invert selection" checkbox (default:
+        *     false)
+        * @param boolean $config['valueAssociated'] Input value of the "include associated namespace"
+        *     checkbox (default: false)
+        * @param string $config['labelInvert'] Text of label to use for "invert selection" checkbox
+        * @param string $config['labelAssociated'] Text of label to use for "include associated
+        *     namespace" checkbox
+        */
+       public function __construct( array $config = array() ) {
+               // Configuration initialization
+
+               $config = array_merge(
+                       array(
+                               'nameNamespace' => 'namespace',
+                               'nameInvert' => 'invert',
+                               'nameAssociated' => 'associated',
+                               // Choose first available: either main namespace or the "all namespaces" option
+                               'valueNamespace' => null,
+                               'valueInvert' => false,
+                               'valueAssociated' => false,
+                       ),
+                       $config
+               );
+
+               // Parent constructor
+               parent::__construct( $config );
+
+               // Properties
+               $this->allValue = isset( $config['includeAllValue'] ) ? $config['includeAllValue'] : null;
+               $this->namespace = new \OOUI\DropdownInputWidget( array(
+                       'name' => $config['nameNamespace'],
+                       'value' => $config['valueNamespace'],
+                       'options' => $this->getNamespaceDropdownOptions( $config ),
+               ) );
+               if ( $config['nameAssociated'] !== null ) {
+                       // FIXME Should use a LabelWidget? But they don't work like HTML <label>s yet
+                       $this->associated = new \OOUI\FieldLayout(
+                               new \OOUI\CheckboxInputWidget( array(
+                                       'name' => $config['nameAssociated'],
+                                       'selected' => $config['valueAssociated'],
+                                       'value' => '1',
+                               ) ),
+                               array(
+                                       'align' => 'inline',
+                                       'label' => $config['labelAssociated'],
+                               )
+                       );
+               }
+               if ( $config['nameInvert'] !== null ) {
+                       $this->invert = new \OOUI\FieldLayout(
+                               new \OOUI\CheckboxInputWidget( array(
+                                       'name' => $config['nameInvert'],
+                                       'selected' => $config['valueInvert'],
+                                       'value' => '1',
+                               ) ),
+                               array(
+                                       'align' => 'inline',
+                                       'label' => $config['labelInvert'],
+                               )
+                       );
+               }
+
+               // Initialization
+               $this
+                       ->addClasses( array( 'mw-widget-namespaceInputWidget' ) )
+                       ->appendContent( $this->namespace, $this->associated, $this->invert );
+       }
+
+       protected function getNamespaceDropdownOptions( array $config ) {
+               $namespaceOptionsParams = isset( $config['includeAllValue'] ) ?
+                       array( 'all' => $config['includeAllValue'] ) : array();
+               $namespaceOptions = \Html::namespaceSelectorOptions( $namespaceOptionsParams );
+
+               $options = array();
+               foreach( $namespaceOptions as $id => $name ) {
+                       $options[] = array(
+                               'data' => (string)$id,
+                               'label' => $name,
+                       );
+               }
+
+               return $options;
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.widgets.NamespaceInputWidget';
+       }
+
+       public function getConfig( &$config ) {
+               $config['namespace'] = $this->namespace;
+               $config['associated'] = $this->associated;
+               $config['invert'] = $this->invert;
+               $config['allValue'] = $this->allValue;
+               return parent::getConfig( $config );
+       }
+}
index e160a23..37c9d9c 100644 (file)
@@ -20,7 +20,7 @@ class TitleInputWidget extends TextInputWidget {
        /**
         * @param array $config Configuration options
         * @param int|null $config['namespace'] Namespace to prepend to queries
-        * @param bool|null $config['relative'] If a namespace is set, return a title relative to it (default; true)
+        * @param bool|null $config['relative'] If a namespace is set, return a title relative to it (default: true)
         */
        public function __construct( array $config = array() ) {
                // Parent constructor
index e7f45f1..689fc02 100644 (file)
@@ -19,3 +19,21 @@ $fallback8bitEncoding = 'windows-1256';
 
 $rtl = true;
 
+$namespaceNames = array(
+       NS_MEDIA            => 'میڈیا',
+       NS_SPECIAL          => 'خاص',
+       NS_TALK             => 'گل_بات',
+       NS_USER             => 'ورتنوالا',
+       NS_USER_TALK        => 'ورتن_گل_بات',
+       NS_PROJECT_TALK     => 'ویونت_گل_بات',
+       NS_FILE             => 'فائل',
+       NS_FILE_TALK        => 'فائل_گل_بات',
+       NS_MEDIAWIKI        => 'میڈیا_وکی',
+       NS_MEDIAWIKI_TALK   => 'میڈیاوکی_گل_بات',
+       NS_TEMPLATE         => 'سانچہ',
+       NS_TEMPLATE_TALK    => 'سانچہ_گل_بات',
+       NS_HELP             => 'ہتھونڈائی',
+       NS_HELP_TALK        => 'ہتھونڈائی_گل_بات',
+       NS_CATEGORY         => 'گٹھ',
+       NS_CATEGORY_TALK    => 'گٹھ_گل_بات',
+);
index 3217503..0c37ae8 100644 (file)
@@ -1746,13 +1746,17 @@ return array(
        'mediawiki.widgets' => array(
                'scripts' => array(
                        'resources/src/mediawiki.widgets/mw.widgets.js',
+                       'resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js',
                ),
                'skinStyles' => array(
-                       'default' => 'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.css',
+                       'default' => array(
+                               'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.css',
+                       ),
                ),
                'dependencies' => array(
+                       'mediawiki.widgets.styles',
                        'jquery.autoEllipsis',
                        'mediawiki.Title',
                        'mediawiki.api',
@@ -1764,6 +1768,15 @@ return array(
                ),
                'targets' => array( 'desktop', 'mobile' ),
        ),
+       'mediawiki.widgets.styles' => array(
+               'skinStyles' => array(
+                       'default' => array(
+                               'resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css',
+                       ),
+               ),
+               'position' => 'top',
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
 
        /* es5-shim */
        'es5-shim' => array(
diff --git a/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css b/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css
new file mode 100644 (file)
index 0000000..3ce92ef
--- /dev/null
@@ -0,0 +1,26 @@
+/*!
+ * MediaWiki Widgets - base NamespaceInputWidget styles.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+.mw-widget-namespaceInputWidget .oo-ui-dropdownInputWidget,
+.mw-widget-namespaceInputWidget .oo-ui-fieldLayout {
+       display: inline-block;
+       margin-right: 1em;
+}
+
+/* FIXME FieldLayout is not supposed to be used the way we use it here */
+.mw-widget-namespaceInputWidget .oo-ui-fieldLayout {
+       vertical-align: middle;
+       margin-bottom: 0;
+}
+
+.mw-widget-namespaceInputWidget .oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+       padding-left: 0.5em;
+}
+
+.mw-widget-namespaceInputWidget .oo-ui-dropdownInputWidget {
+       width: 20em;
+}
diff --git a/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js b/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js
new file mode 100644 (file)
index 0000000..0558c32
--- /dev/null
@@ -0,0 +1,68 @@
+/*!
+ * MediaWiki Widgets - NamespaceInputWidget class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * Creates a mw.widgets.NamespaceInputWidget object.
+        *
+        * This is not a complete implementation and is not intended for public usage. It only exists
+        * because of HTMLForm shenanigans.
+        *
+        * @class
+        * @private
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {OO.ui.DropdownInputWidget} namespace Widget to include
+        * @cfg {OO.ui.CheckboxInputWidget|null} invert Widget to include
+        * @cfg {OO.ui.CheckboxInputWidget|null} associated Widget to include
+        * @cfg {string|null} allValue Value for "all namespaces" option, if any
+        */
+       mw.widgets.NamespaceInputWidget = function MwWidgetsNamespaceInputWidget( config ) {
+               // Parent constructor
+               OO.ui.Widget.call( this, config );
+
+               // Properties
+               this.namespace = config.namespace;
+               this.invert = config.invert;
+               this.associated = config.associated;
+               this.allValue = config.allValue;
+
+               // Events
+               config.namespace.connect( this, { change: 'updateCheckboxesState' } );
+
+               // Initialization
+               this.$element
+                       .addClass( 'mw-widget-namespaceInputWidget' )
+                       .append(
+                               config.namespace.$element,
+                               config.invert ? config.invert.$element : '',
+                               config.associated ? config.associated.$element : ''
+                       );
+               this.updateCheckboxesState();
+       };
+
+       /* Inheritance */
+
+       OO.inheritClass( mw.widgets.NamespaceInputWidget, OO.ui.Widget );
+
+       /* Methods */
+
+       /**
+        * Update the disabled state of checkboxes when the value of namespace dropdown changes.
+        */
+       mw.widgets.NamespaceInputWidget.prototype.updateCheckboxesState = function () {
+               if ( this.invert ) {
+                       this.invert.getField().setDisabled( this.namespace.getValue() === this.allValue );
+               }
+               if ( this.associated ) {
+                       this.associated.getField().setDisabled( this.namespace.getValue() === this.allValue );
+               }
+       };
+
+}( jQuery, mediaWiki ) );
index ca8c400..e715361 100644 (file)
                        data: this.namespace !== null && this.relative
                                ? mwTitle.getRelativeText( this.namespace )
                                : title,
+                       title: mwTitle,
                        imageUrl: this.showImages ? data.imageUrl : null,
                        description: this.showDescriptions ? data.description : null,
                        missing: data.missing,
index 07b81e4..6623aa7 100644 (file)
@@ -14,7 +14,8 @@
         *
         * @constructor
         * @param {Object} [config] Configuration options
-        * @cfg {string} [data] Page title
+        * @cfg {string} [data] Label to display
+        * @cfg {mw.Title} [title] Page title object
         * @cfg {string} [imageUrl] Thumbnail image URL with URL encoding
         * @cfg {string} [description] Page description
         * @cfg {boolean} [missing] Page doesn't exist
@@ -23,7 +24,7 @@
         * @cfg {string} [query] Matching query string
         */
        mw.widgets.TitleOptionWidget = function MwWidgetsTitleOptionWidget( config ) {
-               var icon, title = config.data;
+               var icon;
 
                if ( config.missing ) {
                        icon = 'page-not-found';
@@ -38,8 +39,8 @@
                // Config initialization
                config = $.extend( {
                        icon: icon,
-                       label: title,
-                       href: mw.util.getUrl( title ),
+                       label: config.data,
+                       href: config.title.getUrl(),
                        autoFitLabel: false
                }, config );
 
index 99ec2e4..cf08dbe 100644 (file)
@@ -548,4 +548,26 @@ class MessageTest extends MediaWikiLangTestCase {
        public function testInLanguageThrows() {
                wfMessage( 'foo' )->inLanguage( 123 );
        }
+
+       /**
+        * @covers Message::serialize
+        * @covers Message::unserialize
+        */
+       public function testSerialization() {
+               $msg = new Message( 'parentheses' );
+               $msg->rawParams( '<a>foo</a>' );
+               $msg->title( Title::newFromText( 'Testing' ) );
+               $this->assertEquals( '(<a>foo</a>)', $msg->parse(), 'Sanity check' );
+               $msg = unserialize( serialize( $msg ) );
+               $this->assertEquals( '(<a>foo</a>)', $msg->parse() );
+               $title = TestingAccessWrapper::newFromObject( $msg )->title;
+               $this->assertInstanceOf( 'Title', $title );
+               $this->assertEquals( 'Testing', $title->getFullText() );
+
+               $msg = new Message( 'mainpage' );
+               $msg->inLanguage( 'de' );
+               $this->assertEquals( 'Hauptseite', $msg->plain(), 'Sanity check' );
+               $msg = unserialize( serialize( $msg ) );
+               $this->assertEquals( 'Hauptseite', $msg->plain() );
+       }
 }
index 6c3ce60..08a984e 100644 (file)
@@ -14,9 +14,13 @@ class ApiMessageTest extends MediaWikiTestCase {
 
                $msg = TestingAccessWrapper::newFromObject( $msg );
                $msg2 = TestingAccessWrapper::newFromObject( $msg2 );
-               foreach ( array( 'interface', 'useDatabase', 'title' ) as $key ) {
-                       $this->assertSame( $msg->$key, $msg2->$key, $key );
-               }
+               $this->assertSame( $msg->interface, $msg2->interface, 'interface' );
+               $this->assertSame( $msg->useDatabase, $msg2->useDatabase, 'useDatabase' );
+               $this->assertSame(
+                       $msg->title ? $msg->title->getFullText() : null,
+                       $msg2->title ? $msg2->title->getFullText() : null,
+                       'title'
+               );
        }
 
        /**
@@ -30,6 +34,11 @@ class ApiMessageTest extends MediaWikiTestCase {
                $this->assertEquals( 'code', $msg2->getApiCode() );
                $this->assertEquals( array( 'data' ), $msg2->getApiData() );
 
+               $msg2 = unserialize( serialize( $msg2 ) );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+
                $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) );
                $msg2 = new ApiMessage( array( array( 'foo', 'bar' ), 'baz' ), 'code', array( 'data' ) );
                $this->compareMessages( $msg, $msg2 );
@@ -63,6 +72,11 @@ class ApiMessageTest extends MediaWikiTestCase {
                $this->assertEquals( 'code', $msg2->getApiCode() );
                $this->assertEquals( array( 'data' ), $msg2->getApiData() );
 
+               $msg2 = unserialize( serialize( $msg2 ) );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+
                $msg = new RawMessage( 'foo', array( 'baz' ) );
                $msg2 = new ApiRawMessage( array( 'foo', 'baz' ), 'code', array( 'data' ) );
                $this->compareMessages( $msg, $msg2 );
index 0d0a15e..5c4eea7 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -298,8 +298,6 @@ function wfStreamThumb( array $params ) {
                $headers[] = 'Vary: ' . implode( ', ', $varyHeader );
        }
 
-       $stats = RequestContext::getMain()->getStats();
-
        // Stream the file if it exists already...
        $thumbPath = $img->getThumbPath( $thumbName );
        if ( $img->getRepo()->fileExists( $thumbPath ) ) {
@@ -310,7 +308,7 @@ function wfStreamThumb( array $params ) {
                if ( !$success ) {
                        wfThumbError( 500, 'Could not stream the file' );
                } else {
-                       $stats->timing( 'media.thumbnail.stream', $streamtime );
+                       RequestContext::getMain()->getStats()->timing( 'media.thumbnail.stream', $streamtime );
                }
                return;
        }
@@ -324,10 +322,8 @@ function wfStreamThumb( array $params ) {
                return;
        }
 
-       // Actually generate a new thumbnail
-       $starttime = microtime( true );
        list( $thumb, $errorMsg ) = wfGenerateThumbnail( $img, $params, $thumbName, $thumbPath );
-       $generatetime = microtime( true ) - $starttime;
+
        /** @var MediaTransformOutput|bool $thumb */
 
        // Check for thumbnail generation errors...
@@ -348,8 +344,6 @@ function wfStreamThumb( array $params ) {
        if ( $errorMsg !== false ) {
                wfThumbError( $errorCode, $errorMsg );
        } else {
-               $stats->timing( 'media.thumbnail.generate', $generatetime );
-
                // Stream the file if there were no errors
                $success = $thumb->streamFile( $headers );
                if ( !$success ) {