Merge "Restore some previous constructor calls"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 27 Aug 2019 17:04:14 +0000 (17:04 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 27 Aug 2019 17:04:14 +0000 (17:04 +0000)
42 files changed:
RELEASE-NOTES-1.34
autoload.php
includes/ForkController.php
includes/MediaWikiServices.php
includes/ServiceWiring.php
includes/api/i18n/fr.json
includes/api/i18n/sv.json
includes/api/i18n/zh-hans.json
includes/block/BlockManager.php
includes/filebackend/lockmanager/LockManagerGroup.php
includes/filebackend/lockmanager/LockManagerGroupFactory.php [new file with mode: 0644]
includes/libs/Xhprof.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/mail/EmailNotification.php
includes/parser/CoreParserFunctions.php
includes/parser/PPFrame_DOM.php
includes/parser/PPFrame_Hash.php
includes/search/RevisionSearchResult.php [new file with mode: 0644]
includes/search/RevisionSearchResultTrait.php [new file with mode: 0644]
includes/search/SearchResult.php
includes/search/SqlSearchResult.php
languages/i18n/da.json
languages/i18n/el.json
languages/i18n/exif/zh-hans.json
languages/i18n/fr.json
languages/i18n/id.json
languages/i18n/it.json
languages/i18n/min.json
languages/i18n/nl.json
languages/i18n/nys.json
languages/i18n/pl.json
languages/i18n/sd.json
languages/i18n/zh-hans.json
package-lock.json
package.json
resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
tests/parser/ParserTestRunner.php
tests/phpunit/includes/api/ApiQuerySearchTest.php
tests/phpunit/mocks/search/MockSearchResult.php
tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupTest.php [new file with mode: 0644]
tests/selenium/wdio.conf.js

index 620a6f5..73a1f7b 100644 (file)
@@ -463,6 +463,10 @@ because of Phabricator reports.
 * Constructing MovePage directly is deprecated. Use MovePageFactory.
 * TempFSFile::factory() has been deprecated. Use TempFSFileFactory instead.
 * wfIsBadImage() is deprecated. Use the BadFileLookup service instead.
+* Building a new SearchResult is hard-deprecated, always call
+  SearchResult::newFromTitle(). This class is being refactored into an abstract
+  class. If you extend this class please be sure to override all its methods
+  or extend RevisionSearchResult.
 
 === Other changes in 1.34 ===
 * …
index 95297fb..35c9b0a 100644 (file)
@@ -882,6 +882,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php',
        'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
        'MediaWiki\\FileBackend\\FSFile\\TempFSFileFactory' => __DIR__ . '/includes/libs/filebackend/fsfile/TempFSFileFactory.php',
+       'MediaWiki\\FileBackend\\LockManager\\LockManagerGroupFactory' => __DIR__ . '/includes/filebackend/lockmanager/LockManagerGroupFactory.php',
        'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
        'MediaWiki\\Http\\HttpRequestFactory' => __DIR__ . '/includes/http/HttpRequestFactory.php',
        'MediaWiki\\Installer\\InstallException' => __DIR__ . '/includes/installer/InstallException.php',
@@ -1285,6 +1286,8 @@ $wgAutoloadLocalClasses = [
        'RevisionItemBase' => __DIR__ . '/includes/revisionlist/RevisionItemBase.php',
        'RevisionList' => __DIR__ . '/includes/revisionlist/RevisionList.php',
        'RevisionListBase' => __DIR__ . '/includes/revisionlist/RevisionListBase.php',
+       'RevisionSearchResult' => __DIR__ . '/includes/search/RevisionSearchResult.php',
+       'RevisionSearchResultTrait' => __DIR__ . '/includes/search/RevisionSearchResultTrait.php',
        'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php',
        'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php',
        'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php',
index 85f3a7d..af06a88 100644 (file)
@@ -154,7 +154,6 @@ class ForkController {
                // Don't share DB, storage, or memcached connections
                MediaWikiServices::resetChildProcessServices();
                FileBackendGroup::destroySingleton();
-               LockManagerGroup::destroySingletons();
                JobQueueGroup::destroySingletons();
                ObjectCache::clear();
                RedisConnectionPool::destroySingletons();
index 6013aaf..3b80e58 100644 (file)
@@ -17,6 +17,7 @@ use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
 use MediaWiki\Block\BlockManager;
 use MediaWiki\Block\BlockRestrictionStore;
 use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
 use MediaWiki\Http\HttpRequestFactory;
 use MediaWiki\Page\MovePageFactory;
 use MediaWiki\Permissions\PermissionManager;
@@ -658,6 +659,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'LocalServerObjectCache' );
        }
 
+       /**
+        * @since 1.34
+        * @return LockManagerGroupFactory
+        */
+       public function getLockManagerGroupFactory() : LockManagerGroupFactory {
+               return $this->getService( 'LockManagerGroupFactory' );
+       }
+
        /**
         * @since 1.32
         * @return MagicWordFactory
index b307264..cc68cb6 100644 (file)
@@ -45,6 +45,7 @@ use MediaWiki\Block\BlockRestrictionStore;
 use MediaWiki\Config\ConfigRepository;
 use MediaWiki\Config\ServiceOptions;
 use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
 use MediaWiki\Http\HttpRequestFactory;
 use MediaWiki\Interwiki\ClassicInterwikiLookup;
 use MediaWiki\Interwiki\InterwikiLookup;
@@ -289,6 +290,14 @@ return [
                return \ObjectCache::newFromParams( $config->get( 'ObjectCaches' )[$cacheId] );
        },
 
+       'LockManagerGroupFactory' => function ( MediaWikiServices $services ) : LockManagerGroupFactory {
+               return new LockManagerGroupFactory(
+                       WikiMap::getCurrentWikiDbDomain()->getId(),
+                       $services->getMainConfig()->get( 'LockManagers' ),
+                       $services->getDBLoadBalancerFactory()
+               );
+       },
+
        'MagicWordFactory' => function ( MediaWikiServices $services ) : MagicWordFactory {
                return new MagicWordFactory( $services->getContentLanguage() );
        },
index 591bf31..b72d519 100644 (file)
        "apiwarn-deprecation-missingparam": "Comme <var>$1</var> n’a pas été spécifié, un format ancien a été utilisé pour la sortie. Ce format est obsolète, et dans le futur, le nouveau format sera toujours utilisé.",
        "apiwarn-deprecation-parameter": "Le paramètre <var>$1</var> est désuet.",
        "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MédiaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
+       "apiwarn-deprecation-post-without-content-type": "Une requête POST a été faite sans entête <code>Content-Type</code>. Cela ne fonctionne pas de façon fiable.",
        "apiwarn-deprecation-purge-get": "L’utilisation de <kbd>action=purge</kbd> via un GET est désuète. Utiliser POST à la place.",
        "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est désuet. Veuillez utiliser <kbd>$2</kbd> à la place.",
        "apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
index a0fa693..ea84a53 100644 (file)
        "apierror-unknownformat": "Okänt format \"$1\".",
        "apiwarn-compare-no-next": "Sidversion $2 är den senaste sidversionen av $1, det finns ingen sidversion för <kbd>torelative=next</kbd> att jämföra med.",
        "apiwarn-compare-no-prev": "Sidversionen $2 är den tidigaste sidversion för $1, det finns ingen sidversion för <kbd>torelative=prev</kbd> att jämföra med.",
+       "apiwarn-deprecation-post-without-content-type": "En POST-begäran gjordes utan en <code>Content-Type</code> i sidhuvudet. Det fungerar inte ordentligt.",
        "api-feed-error-title": "Fel ($1)"
 }
index 2a61360..7e08e77 100644 (file)
@@ -30,7 +30,8 @@
                        "科劳",
                        "SolidBlock",
                        "神樂坂秀吉",
-                       "94rain"
+                       "94rain",
+                       "予弦"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>MediaWiki API是一个成熟稳定的,不断受到支持和改进的界面。尽管我们尽力避免,但偶尔也需要作出重大更新;请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。</p>",
index 948ed93..83b59c7 100644 (file)
@@ -257,7 +257,7 @@ class BlockManager {
                        $block = DatabaseBlock::newFromID( $blockCookieId );
                        if (
                                $block instanceof DatabaseBlock &&
-                               $this->shouldApplyCookieBlock( $block, $user->isAnon() )
+                               $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
                        ) {
                                return $block;
                        }
index 957af3e..7da3753 100644 (file)
@@ -22,6 +22,7 @@
  */
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Logger\LoggerFactory;
+use Wikimedia\Rdbms\LBFactory;
 
 /**
  * Class to handle file lock manager registration
@@ -30,62 +31,27 @@ use MediaWiki\Logger\LoggerFactory;
  * @since 1.19
  */
 class LockManagerGroup {
-       /** @var LockManagerGroup[] (domain => LockManagerGroup) */
-       protected static $instances = [];
+       /** @var string domain (usually wiki ID) */
+       protected $domain;
 
-       protected $domain; // string; domain (usually wiki ID)
+       /** @var LBFactory */
+       protected $lbFactory;
 
        /** @var array Array of (name => ('class' => ..., 'config' => ..., 'instance' => ...)) */
        protected $managers = [];
 
        /**
+        * Do not call this directly. Use LockManagerGroupFactory.
+        *
         * @param string $domain Domain (usually wiki ID)
+        * @param array[] $lockManagerConfigs In format of $wgLockManagers
+        * @param LBFactory $lbFactory
         */
-       protected function __construct( $domain ) {
+       public function __construct( $domain, array $lockManagerConfigs, LBFactory $lbFactory ) {
                $this->domain = $domain;
-       }
-
-       /**
-        * @param bool|string $domain Domain (usually wiki ID). Default: false.
-        * @return LockManagerGroup
-        */
-       public static function singleton( $domain = false ) {
-               if ( $domain === false ) {
-                       $domain = WikiMap::getCurrentWikiDbDomain()->getId();
-               }
+               $this->lbFactory = $lbFactory;
 
-               if ( !isset( self::$instances[$domain] ) ) {
-                       self::$instances[$domain] = new self( $domain );
-                       self::$instances[$domain]->initFromGlobals();
-               }
-
-               return self::$instances[$domain];
-       }
-
-       /**
-        * Destroy the singleton instances
-        */
-       public static function destroySingletons() {
-               self::$instances = [];
-       }
-
-       /**
-        * Register lock managers from the global variables
-        */
-       protected function initFromGlobals() {
-               global $wgLockManagers;
-
-               $this->register( $wgLockManagers );
-       }
-
-       /**
-        * Register an array of file lock manager configurations
-        *
-        * @param array $configs
-        * @throws Exception
-        */
-       protected function register( array $configs ) {
-               foreach ( $configs as $config ) {
+               foreach ( $lockManagerConfigs as $config ) {
                        $config['domain'] = $this->domain;
                        if ( !isset( $config['name'] ) ) {
                                throw new Exception( "Cannot register a lock manager with no name." );
@@ -104,6 +70,26 @@ class LockManagerGroup {
                }
        }
 
+       /**
+        * @deprecated since 1.34, use LockManagerGroupFactory
+        *
+        * @param bool|string $domain Domain (usually wiki ID). Default: false.
+        * @return LockManagerGroup
+        */
+       public static function singleton( $domain = false ) {
+               return MediaWikiServices::getInstance()->getLockManagerGroupFactory()
+                       ->getLockManagerGroup( $domain );
+       }
+
+       /**
+        * Destroy the singleton instances
+        *
+        * @deprecated since 1.34, use resetServiceForTesting() on LockManagerGroupFactory
+        */
+       public static function destroySingletons() {
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
+       }
+
        /**
         * Get the lock manager object with a given name
         *
@@ -120,8 +106,7 @@ class LockManagerGroup {
                        $class = $this->managers[$name]['class'];
                        $config = $this->managers[$name]['config'];
                        if ( $class === DBLockManager::class ) {
-                               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-                               $lb = $lbFactory->getMainLB( $config['domain'] );
+                               $lb = $this->lbFactory->getMainLB( $config['domain'] );
                                $config['dbServers']['localDBMaster'] = $lb->getLazyConnectionRef(
                                        DB_MASTER,
                                        [],
@@ -132,6 +117,11 @@ class LockManagerGroup {
                        }
                        $config['logger'] = LoggerFactory::getInstance( 'LockManager' );
 
+                       // XXX Looks like phan is right, we are trying to instantiate an abstract class and it
+                       // throws. Did this ever work? Presumably we need to detect the right subclass? Or
+                       // should we just get rid of this? It looks like it never worked since it was first
+                       // introduced by 0cf832a3394 in 2016, so if no one's complained until now, clearly it
+                       // can't be very useful?
                        // @phan-suppress-next-line PhanTypeInstantiateAbstract
                        $this->managers[$name]['instance'] = new $class( $config );
                }
@@ -159,6 +149,8 @@ class LockManagerGroup {
         * Get the default lock manager configured for the site.
         * Returns NullLockManager if no lock manager could be found.
         *
+        * XXX This looks unused, should we just get rid of it?
+        *
         * @return LockManager
         */
        public function getDefault() {
@@ -172,6 +164,8 @@ class LockManagerGroup {
         * or at least some other effective configured lock manager.
         * Throws an exception if no lock manager could be found.
         *
+        * XXX This looks unused, should we just get rid of it?
+        *
         * @return LockManager
         * @throws Exception
         */
diff --git a/includes/filebackend/lockmanager/LockManagerGroupFactory.php b/includes/filebackend/lockmanager/LockManagerGroupFactory.php
new file mode 100644 (file)
index 0000000..54bbc3d
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+namespace MediaWiki\FileBackend\LockManager;
+
+use LockManagerGroup;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * Service to construct LockManagerGroups.
+ */
+class LockManagerGroupFactory {
+       /** @var string */
+       private $defaultDomain;
+
+       /** @var array */
+       private $lockManagerConfigs;
+
+       /** @var LBFactory */
+       private $lbFactory;
+
+       /** @var LockManagerGroup[] (domain => LockManagerGroup) */
+       private $instances = [];
+
+       /**
+        * Do not call directly, use MediaWikiServices.
+        *
+        * @param string $defaultDomain
+        * @param array $lockManagerConfigs In format of $wgLockManagers
+        * @param LBFactory $lbFactory
+        */
+       public function __construct( $defaultDomain, array $lockManagerConfigs, LBFactory $lbFactory ) {
+               $this->defaultDomain = $defaultDomain;
+               $this->lockManagerConfigs = $lockManagerConfigs;
+               $this->lbFactory = $lbFactory;
+       }
+
+       /**
+        * @param string|bool $domain Domain (usually wiki ID). false for the default (normally the
+        *   current wiki's domain).
+        * @return LockManagerGroup
+        */
+       public function getLockManagerGroup( $domain = false ) : LockManagerGroup {
+               if ( $domain === false ) {
+                       $domain = $this->defaultDomain;
+               }
+
+               if ( !isset( $this->instances[$domain] ) ) {
+                       $this->instances[$domain] =
+                               new LockManagerGroup( $domain, $this->lockManagerConfigs, $this->lbFactory );
+               }
+
+               return $this->instances[$domain];
+       }
+}
index a1ddfd0..575fbe7 100644 (file)
@@ -53,6 +53,12 @@ class Xhprof {
                if ( self::isEnabled() ) {
                        throw new Exception( 'Profiling is already enabled.' );
                }
+
+               $args = [ $flags ];
+               if ( $options ) {
+                       $args[] = $options;
+               }
+
                self::$enabled = true;
                self::callAny(
                        [
@@ -60,7 +66,7 @@ class Xhprof {
                                'tideways_enable',
                                'tideways_xhprof_enable'
                        ],
-                       [ $flags, $options ]
+                       $args
                );
        }
 
index b1521dc..2977291 100644 (file)
@@ -57,9 +57,6 @@ class DatabaseSqlite extends Database {
        /** @var array List of shared database already attached to this connection */
        private $alreadyAttached = [];
 
-       /** @var bool Whether full text is enabled */
-       private static $fulltextEnabled = null;
-
        /** @var string[] See https://www.sqlite.org/lang_transaction.html */
        private static $VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
 
@@ -291,8 +288,9 @@ class DatabaseSqlite extends Database {
        }
 
        /**
-        * Attaches external database to our connection, see https://sqlite.org/lang_attach.html
-        * for details.
+        * Attaches external database to the connection handle
+        *
+        * @see https://sqlite.org/lang_attach.html
         *
         * @param string $name Database name to be used in queries like
         *   SELECT foo FROM dbname.table
index 7361032..cba68ef 100644 (file)
@@ -173,13 +173,23 @@ class EmailNotification {
         * @param string $pageStatus
         * @throws MWException
         */
-       public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit,
-               $oldid, $watchers, $pageStatus = 'changed' ) {
+       public function actuallyNotifyOnPageChange(
+               $editor,
+               $title,
+               $timestamp,
+               $summary,
+               $minorEdit,
+               $oldid,
+               $watchers,
+               $pageStatus = 'changed'
+       ) {
                # we use $wgPasswordSender as sender's address
                global $wgUsersNotifiedOnAllChanges;
                global $wgEnotifWatchlist, $wgBlockDisablesLogin;
                global $wgEnotifMinorEdits, $wgEnotifUserTalk;
 
+               $messageCache = MediaWikiServices::getInstance()->getMessageCache();
+
                # The following code is only run, if several conditions are met:
                # 1. EmailNotification for pages (other than user_talk pages) must be enabled
                # 2. minor edits (changes) are only regarded if the global flag indicates so
@@ -210,7 +220,7 @@ class EmailNotification {
                                && $this->canSendUserTalkEmail( $editor, $title, $minorEdit )
                        ) {
                                $targetUser = User::newFromName( $title->getText() );
-                               $this->compose( $targetUser, self::USER_TALK );
+                               $this->compose( $targetUser, self::USER_TALK, $messageCache );
                                $userTalkId = $targetUser->getId();
                        }
 
@@ -229,7 +239,7 @@ class EmailNotification {
                                                && !( $wgBlockDisablesLogin && $watchingUser->getBlock() )
                                                && Hooks::run( 'SendWatchlistEmailNotification', [ $watchingUser, $title, $this ] )
                                        ) {
-                                               $this->compose( $watchingUser, self::WATCHLIST );
+                                               $this->compose( $watchingUser, self::WATCHLIST, $messageCache );
                                        }
                                }
                        }
@@ -241,7 +251,7 @@ class EmailNotification {
                                continue;
                        }
                        $user = User::newFromName( $name );
-                       $this->compose( $user, self::ALL_CHANGES );
+                       $this->compose( $user, self::ALL_CHANGES, $messageCache );
                }
 
                $this->sendMails();
@@ -288,8 +298,9 @@ class EmailNotification {
 
        /**
         * Generate the generic "this page has been changed" e-mail text.
+        * @param MessageCache $messageCache
         */
-       private function composeCommonMailtext() {
+       private function composeCommonMailtext( MessageCache $messageCache ) {
                global $wgPasswordSender, $wgNoReplyAddress;
                global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
                global $wgEnotifImpersonal, $wgEnotifUseRealName;
@@ -374,7 +385,7 @@ class EmailNotification {
 
                $body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
                $body = strtr( $body, $keys );
-               $body = MessageCache::singleton()->transform( $body, false, null, $this->title );
+               $body = $messageCache->transform( $body, false, null, $this->title );
                $this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
 
                # Reveal the page editor's address as REPLY-TO address only if
@@ -406,12 +417,13 @@ class EmailNotification {
         * Call sendMails() to send any mails that were queued.
         * @param User $user
         * @param string $source
+        * @param MessageCache $messageCache
         */
-       private function compose( $user, $source ) {
+       private function compose( $user, $source, MessageCache $messageCache ) {
                global $wgEnotifImpersonal;
 
                if ( !$this->composed_common ) {
-                       $this->composeCommonMailtext();
+                       $this->composeCommonMailtext( $messageCache );
                }
 
                if ( $wgEnotifImpersonal ) {
index a7916c5..e1f4f38 100644 (file)
@@ -434,7 +434,7 @@ class CoreParserFunctions {
                if ( !$wgRestrictDisplayTitle ||
                        ( $title instanceof Title
                        && !$title->hasFragment()
-                       && $title->equals( $parser->mTitle ) )
+                       && $title->equals( $parser->getTitle() ) )
                ) {
                        $old = $parser->mOutput->getProperty( 'displaytitle' );
                        if ( $old === false || $arg !== 'displaytitle_noreplace' ) {
@@ -845,10 +845,7 @@ class CoreParserFunctions {
         * @return string
         */
        public static function protectionlevel( $parser, $type = '', $title = '' ) {
-               $titleObject = Title::newFromText( $title );
-               if ( !( $titleObject instanceof Title ) ) {
-                       $titleObject = $parser->mTitle;
-               }
+               $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
                if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
                        $restrictions = $titleObject->getRestrictions( strtolower( $type ) );
                        # Title::getRestrictions returns an array, its possible it may have
@@ -871,10 +868,7 @@ class CoreParserFunctions {
         * @return string
         */
        public static function protectionexpiry( $parser, $type = '', $title = '' ) {
-               $titleObject = Title::newFromText( $title );
-               if ( !( $titleObject instanceof Title ) ) {
-                       $titleObject = $parser->mTitle;
-               }
+               $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
                if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
                        $expiry = $titleObject->getRestrictionExpiry( strtolower( $type ) );
                        // getRestrictionExpiry() returns false on invalid type; trying to
@@ -1377,10 +1371,7 @@ class CoreParserFunctions {
         * @since 1.23
         */
        public static function cascadingsources( $parser, $title = '' ) {
-               $titleObject = Title::newFromText( $title );
-               if ( !( $titleObject instanceof Title ) ) {
-                       $titleObject = $parser->mTitle;
-               }
+               $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
                if ( $titleObject->areCascadeProtectionSourcesLoaded()
                        || $parser->incrementExpensiveFunctionCount()
                ) {
index 452bab1..e3c12eb 100644 (file)
@@ -70,7 +70,7 @@ class PPFrame_DOM implements PPFrame {
        public function __construct( $preprocessor ) {
                $this->preprocessor = $preprocessor;
                $this->parser = $preprocessor->parser;
-               $this->title = $this->parser->mTitle;
+               $this->title = $this->parser->getTitle();
                $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
                $this->loopCheckHash = [];
                $this->depth = 0;
index 845ec73..f38cb06 100644 (file)
@@ -69,7 +69,7 @@ class PPFrame_Hash implements PPFrame {
        public function __construct( $preprocessor ) {
                $this->preprocessor = $preprocessor;
                $this->parser = $preprocessor->parser;
-               $this->title = $this->parser->mTitle;
+               $this->title = $this->parser->getTitle();
                $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
                $this->loopCheckHash = [];
                $this->depth = 0;
diff --git a/includes/search/RevisionSearchResult.php b/includes/search/RevisionSearchResult.php
new file mode 100644 (file)
index 0000000..f94ea2a
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * SearchResult class based on the Revision information.
+ * This class is suited for search engines that do not store a specialized version of the searched
+ * content.
+ */
+class RevisionSearchResult extends SearchResult {
+       use RevisionSearchResultTrait;
+
+       /**
+        * @param Title|null $title
+        */
+       public function __construct( $title ) {
+               $this->mTitle = $title;
+               $this->initFromTitle( $title );
+       }
+}
diff --git a/includes/search/RevisionSearchResultTrait.php b/includes/search/RevisionSearchResultTrait.php
new file mode 100644 (file)
index 0000000..24370c3
--- /dev/null
@@ -0,0 +1,200 @@
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Transitional trait used to share the methods between SearchResult and RevisionSearchResult.
+ * All the content of this trait can be moved to RevisionSearchResult once SearchResult is finally
+ * refactored into an abstract class.
+ * NOTE: This trait MUST NOT be used by something else than SearchResult and RevisionSearchResult.
+ * It will be removed without deprecation period once SearchResult
+ */
+trait RevisionSearchResultTrait {
+       /**
+        * @var Revision
+        */
+       protected $mRevision = null;
+
+       /**
+        * @var File
+        */
+       protected $mImage = null;
+
+       /**
+        * @var Title
+        */
+       protected $mTitle;
+
+       /**
+        * @var string
+        */
+       protected $mText;
+
+       /**
+        * Initialize from a Title and if possible initializes a corresponding
+        * Revision and File.
+        *
+        * @param Title $title
+        */
+       protected function initFromTitle( $title ) {
+               $this->mTitle = $title;
+               $services = MediaWikiServices::getInstance();
+               if ( !is_null( $this->mTitle ) ) {
+                       $id = false;
+                       Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
+                       $this->mRevision = Revision::newFromTitle(
+                               $this->mTitle, $id, Revision::READ_NORMAL );
+                       if ( $this->mTitle->getNamespace() === NS_FILE ) {
+                               $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
+                       }
+               }
+       }
+
+       /**
+        * Check if this is result points to an invalid title
+        *
+        * @return bool
+        */
+       public function isBrokenTitle() {
+               return is_null( $this->mTitle );
+       }
+
+       /**
+        * Check if target page is missing, happens when index is out of date
+        *
+        * @return bool
+        */
+       public function isMissingRevision() {
+               return !$this->mRevision && !$this->mImage;
+       }
+
+       /**
+        * @return Title
+        */
+       public function getTitle() {
+               return $this->mTitle;
+       }
+
+       /**
+        * Get the file for this page, if one exists
+        * @return File|null
+        */
+       public function getFile() {
+               return $this->mImage;
+       }
+
+       /**
+        * Lazy initialization of article text from DB
+        */
+       protected function initText() {
+               if ( !isset( $this->mText ) ) {
+                       if ( $this->mRevision != null ) {
+                               $content = $this->mRevision->getContent();
+                               $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
+                       } else { // TODO: can we fetch raw wikitext for commons images?
+                               $this->mText = '';
+                       }
+               }
+       }
+
+       /**
+        * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
+        * @return string Highlighted text snippet, null (and not '') if not supported
+        */
+       public function getTextSnippet( $terms = [] ) {
+               return '';
+       }
+
+       /**
+        * @return string Highlighted title, '' if not supported
+        */
+       public function getTitleSnippet() {
+               return '';
+       }
+
+       /**
+        * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
+        */
+       public function getRedirectSnippet() {
+               return '';
+       }
+
+       /**
+        * @return Title|null Title object for the redirect to this page, null if none or not supported
+        */
+       public function getRedirectTitle() {
+               return null;
+       }
+
+       /**
+        * @return string Highlighted relevant section name, null if none or not supported
+        */
+       public function getSectionSnippet() {
+               return '';
+       }
+
+       /**
+        * @return Title|null Title object (pagename+fragment) for the section,
+        *  null if none or not supported
+        */
+       public function getSectionTitle() {
+               return null;
+       }
+
+       /**
+        * @return string Highlighted relevant category name or '' if none or not supported
+        */
+       public function getCategorySnippet() {
+               return '';
+       }
+
+       /**
+        * @return string Timestamp
+        */
+       public function getTimestamp() {
+               if ( $this->mRevision ) {
+                       return $this->mRevision->getTimestamp();
+               } elseif ( $this->mImage ) {
+                       return $this->mImage->getTimestamp();
+               }
+               return '';
+       }
+
+       /**
+        * @return int Number of words
+        */
+       public function getWordCount() {
+               $this->initText();
+               return str_word_count( $this->mText );
+       }
+
+       /**
+        * @return int Size in bytes
+        */
+       public function getByteSize() {
+               $this->initText();
+               return strlen( $this->mText );
+       }
+
+       /**
+        * @return string Interwiki prefix of the title (return iw even if title is broken)
+        */
+       public function getInterwikiPrefix() {
+               return '';
+       }
+
+       /**
+        * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
+        */
+       public function getInterwikiNamespaceText() {
+               return '';
+       }
+
+       /**
+        * Did this match file contents (eg: PDF/DJVU)?
+        * @return bool
+        */
+       public function isFileMatch() {
+               return false;
+       }
+}
index 4f91ccb..3d32de7 100644 (file)
  * @ingroup Search
  */
 
-use MediaWiki\MediaWikiServices;
-
 /**
- * @todo FIXME: This class is horribly factored. It would probably be better to
- * have a useful base class to which you pass some standard information, then
- * let the fancy self-highlighters extend that.
+ * NOTE: this class is being refactored into an abstract base class.
+ * If you extend this class directly, please implement all the methods declared
+ * in RevisionSearchResultTrait or extend RevisionSearchResult.
+ *
+ * Once the hard-deprecation period is over (1.36?):
+ * - all methods declared in RevisionSearchResultTrait should be declared
+ *   as abstract in this class
+ * - RevisionSearchResultTrait body should be moved to RevisionSearchResult and then removed without
+ *   deprecation
+ * - caveat: all classes extending this one may potentially break if they did not properly implement
+ *   all the methods.
  * @ingroup Search
  */
 class SearchResult {
        use SearchResultTrait;
+       use RevisionSearchResultTrait;
 
-       /**
-        * @var Revision
-        */
-       protected $mRevision = null;
-
-       /**
-        * @var File
-        */
-       protected $mImage = null;
-
-       /**
-        * @var Title
-        */
-       protected $mTitle;
-
-       /**
-        * @var string
-        */
-       protected $mText;
+       public function __construct() {
+               if ( self::class === static::class ) {
+                       wfDeprecated( __METHOD__, '1.34' );
+               }
+       }
 
        /**
         * Return a new SearchResult and initializes it with a title.
@@ -60,180 +53,10 @@ class SearchResult {
         * @return SearchResult
         */
        public static function newFromTitle( $title, ISearchResultSet $parentSet = null ) {
-               $result = new static();
-               $result->initFromTitle( $title );
+               $result = new RevisionSearchResult( $title );
                if ( $parentSet ) {
                        $parentSet->augmentResult( $result );
                }
                return $result;
        }
-
-       /**
-        * Initialize from a Title and if possible initializes a corresponding
-        * Revision and File.
-        *
-        * @param Title $title
-        */
-       protected function initFromTitle( $title ) {
-               $this->mTitle = $title;
-               $services = MediaWikiServices::getInstance();
-               if ( !is_null( $this->mTitle ) ) {
-                       $id = false;
-                       Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
-                       $this->mRevision = Revision::newFromTitle(
-                               $this->mTitle, $id, Revision::READ_NORMAL );
-                       if ( $this->mTitle->getNamespace() === NS_FILE ) {
-                               $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
-                       }
-               }
-       }
-
-       /**
-        * Check if this is result points to an invalid title
-        *
-        * @return bool
-        */
-       public function isBrokenTitle() {
-               return is_null( $this->mTitle );
-       }
-
-       /**
-        * Check if target page is missing, happens when index is out of date
-        *
-        * @return bool
-        */
-       public function isMissingRevision() {
-               return !$this->mRevision && !$this->mImage;
-       }
-
-       /**
-        * @return Title
-        */
-       public function getTitle() {
-               return $this->mTitle;
-       }
-
-       /**
-        * Get the file for this page, if one exists
-        * @return File|null
-        */
-       public function getFile() {
-               return $this->mImage;
-       }
-
-       /**
-        * Lazy initialization of article text from DB
-        */
-       protected function initText() {
-               if ( !isset( $this->mText ) ) {
-                       if ( $this->mRevision != null ) {
-                               $content = $this->mRevision->getContent();
-                               $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
-                       } else { // TODO: can we fetch raw wikitext for commons images?
-                               $this->mText = '';
-                       }
-               }
-       }
-
-       /**
-        * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
-        * @return string Highlighted text snippet, null (and not '') if not supported
-        */
-       public function getTextSnippet( $terms = [] ) {
-               return '';
-       }
-
-       /**
-        * @return string Highlighted title, '' if not supported
-        */
-       public function getTitleSnippet() {
-               return '';
-       }
-
-       /**
-        * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
-        */
-       public function getRedirectSnippet() {
-               return '';
-       }
-
-       /**
-        * @return Title|null Title object for the redirect to this page, null if none or not supported
-        */
-       public function getRedirectTitle() {
-               return null;
-       }
-
-       /**
-        * @return string Highlighted relevant section name, null if none or not supported
-        */
-       public function getSectionSnippet() {
-               return '';
-       }
-
-       /**
-        * @return Title|null Title object (pagename+fragment) for the section,
-        *  null if none or not supported
-        */
-       public function getSectionTitle() {
-               return null;
-       }
-
-       /**
-        * @return string Highlighted relevant category name or '' if none or not supported
-        */
-       public function getCategorySnippet() {
-               return '';
-       }
-
-       /**
-        * @return string Timestamp
-        */
-       public function getTimestamp() {
-               if ( $this->mRevision ) {
-                       return $this->mRevision->getTimestamp();
-               } elseif ( $this->mImage ) {
-                       return $this->mImage->getTimestamp();
-               }
-               return '';
-       }
-
-       /**
-        * @return int Number of words
-        */
-       public function getWordCount() {
-               $this->initText();
-               return str_word_count( $this->mText );
-       }
-
-       /**
-        * @return int Size in bytes
-        */
-       public function getByteSize() {
-               $this->initText();
-               return strlen( $this->mText );
-       }
-
-       /**
-        * @return string Interwiki prefix of the title (return iw even if title is broken)
-        */
-       public function getInterwikiPrefix() {
-               return '';
-       }
-
-       /**
-        * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
-        */
-       public function getInterwikiNamespaceText() {
-               return '';
-       }
-
-       /**
-        * Did this match file contents (eg: PDF/DJVU)?
-        * @return bool
-        */
-       public function isFileMatch() {
-               return false;
-       }
-
 }
index 9804e44..f470dbb 100644 (file)
@@ -22,7 +22,7 @@
  * @ingroup Search
  */
 
-class SqlSearchResult extends SearchResult {
+class SqlSearchResult extends RevisionSearchResult {
        /** @var string[] */
        private $terms;
 
@@ -32,7 +32,7 @@ class SqlSearchResult extends SearchResult {
         * @param string[] $terms list of parsed terms
         */
        public function __construct( Title $title, array $terms ) {
-               $this->initFromTitle( $title );
+               parent::__construct( $title );
                $this->terms = $terms;
        }
 
index 10d1709..b1fae68 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Vis ændringer på sider der linker til",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Sider som linker til</strong> den valgte side",
        "rcfilters-target-page-placeholder": "Indtast et sidenavn (eller en kategori)",
+       "rcfilters-allcontents-label": "Alt indhold",
        "rcnotefrom": "Nedenfor er op til '''$1''' {{PLURAL:$5|ændring|ændringer}} siden '''$2''' vist.",
        "rclistfromreset": "Nulstil datovalg",
        "rclistfrom": "Vis nye ændringer startende fra den $3 kl. $2",
index 8a99dd9..00e0413 100644 (file)
@@ -59,7 +59,8 @@
                        "Fitoschido",
                        "KATRINE1993",
                        "Vlad5250",
-                       "Sarri.greek"
+                       "Sarri.greek",
+                       "Kostajh"
                ]
        },
        "tog-underline": "Υπογράμμιση συνδέσμων:",
        "history": "Ιστορικό σελίδας",
        "history_short": "Ιστορικό",
        "history_small": "ιστορικό",
-       "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή μου",
+       "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή σας",
        "printableversion": "Έκδοση εκτύπωσης",
        "permalink": "Σταθερός σύνδεσμος",
        "print": "Εκτύπωση",
index 2a07679..50d1db2 100644 (file)
@@ -9,7 +9,8 @@
                        "PhiLiP",
                        "Qiyue2001",
                        "Xiaomingyan",
-                       "神樂坂秀吉"
+                       "神樂坂秀吉",
+                       "予弦"
                ]
        },
        "exif-imagewidth": "宽度",
index 5cae831..1782812 100644 (file)
        "tag-filter": "Filtrer les [[Special:Tags|balises]] :",
        "tag-filter-submit": "Filtrer",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Balise|Balises}}]] : $2",
-       "tag-mw-contentmodelchange": "modification du modèle de contenu",
+       "tag-mw-contentmodelchange": "Modification du modèle de contenu",
        "tag-mw-contentmodelchange-description": "Modifications qui [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel changent le modèle de contenu] d'une page",
        "tag-mw-new-redirect": "Nouvelle redirection",
        "tag-mw-new-redirect-description": "Modifications qui créent une nouvelle redirection ou transforment une page en redirection",
        "tag-mw-changed-redirect-target-description": "Modifications qui changent la cible d’une redirection",
        "tag-mw-blank": "Blanchiment",
        "tag-mw-blank-description": "Modifications qui suppriment le contenu des pages",
-       "tag-mw-replace": "Remplacé",
+       "tag-mw-replace": "Contenu remplacé",
        "tag-mw-replace-description": "Modifications qui enlèvent plus de 90% du contenu des pages",
        "tag-mw-rollback": "Révocation",
        "tag-mw-rollback-description": "Modifications qui annulent des modifications existantes en utilisant le lien de révocation (''rollback'')",
index d2691d7..9baf870 100644 (file)
        "timezoneregion-indian": "Samudera Hindia",
        "timezoneregion-pacific": "Samudera Pasifik",
        "allowemail": "Izinkan pengguna lain mengirim surel kepada saya",
-       "email-allow-new-users-label": "Izinkan email dari pengguna baru",
+       "email-allow-new-users-label": "Izinkan surel dari pengguna baru",
        "email-blacklist-label": "Cegah para pengguna ini mengirim saya surel:",
        "prefs-searchoptions": "Cari",
        "prefs-namespaces": "Ruang nama",
index 7feae60..bbd2dfa 100644 (file)
        "last": "prec",
        "page_first": "prima",
        "page_last": "ultima",
-       "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione corrente, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
+       "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione attuale, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
        "history-fieldset-title": "Filtra versioni",
        "history-show-deleted": "Solo versioni cancellate",
        "histfirst": "prima",
index e1b8d44..152fecc 100644 (file)
        "prefs-personal": "Profil pangguno",
        "prefs-rc": "Parubahan baru",
        "prefs-watchlist": "Daftar pantau",
+       "prefs-editwatchlist": "Suntiang daftar pantauan",
+       "prefs-editwatchlist-label": "Suntiang entri daftar pantauan Sanak:",
        "prefs-watchlist-days": "Jumlah hari dalam daftar pantau:",
        "prefs-watchlist-days-max": "Maksimum $1 {{PLURAL:$1|hari}}",
        "prefs-watchlist-edits": "Jumlah suntiangan nan ditunjuakan pado daftar pantau:",
        "timezoneregion-indian": "Samudera Hindia",
        "timezoneregion-pacific": "Samudera Pasifik",
        "allowemail": "Izinkan pangguno lain mangirim surel",
+       "email-allow-new-users-label": "Izinkan surel dari pangguno baru",
+       "email-blacklist-label": "Panggono ko indak dapek kirim surel ka Ambo:",
        "prefs-searchoptions": "Cari",
        "prefs-namespaces": "Ruangnamo",
        "default": "baku",
        "prefs-files": "Berkas",
-       "prefs-custom-css": "CSS paribadi",
-       "prefs-custom-js": "JS paribadi",
+       "prefs-custom-css": "CSS surang",
+       "prefs-custom-js": "JS surang",
        "prefs-common-config": "CSS/JS untuak kasado kulik:",
        "prefs-reset-intro": "Angku dapek manggunokan laman ko untuak mangambalikan pangaturan ka setelan baku situs ko.\nPangambalian pangaturan indak dapek dibatalan.",
        "prefs-emailconfirm-label": "Surel konfirmasi:",
        "prefs-advancedwatchlist": "Piliahan lanjuik",
        "prefs-displayrc": "Piliahan tampilan",
        "prefs-displaywatchlist": "Piliahan tampilan",
+       "prefs-changesrc": "Parubahan ditampilkan",
        "prefs-diffs": "Pabedoan",
        "userrights": "Manajemen hak pangguno",
        "userrights-lookup-user": "Mangatua kalompok pangguno",
index a5ab1a3..7e90572 100644 (file)
@@ -97,7 +97,8 @@
                        "KlaasZ4usV",
                        "Elroy",
                        "PiefPafPier",
-                       "Ecthelion3"
+                       "Ecthelion3",
+                       "RadioAzureus"
                ]
        },
        "tog-underline": "Verwijzingen onderstrepen:",
        "rcfilters-filter-showlinkedto-label": "Toon wijzigingen op pagina's gekoppeld naar",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Pagina's gekoppeld naar</strong> de geselecteerde pagina",
        "rcfilters-target-page-placeholder": "Voer een paginanaam (of categorie) in",
-       "rcfilters-allcontents-label": "Alle inhoud",
-       "rcfilters-alldiscussions-label": "Al het overleg",
+       "rcfilters-allcontents-label": "De volledige inhoud",
+       "rcfilters-alldiscussions-label": "Alle discussies",
        "rcnotefrom": "Wijzigingen sinds <strong>$3 om $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|wijziging|wijzigingen}}).",
        "rclistfromreset": "Datum selectie opnieuw instellen",
        "rclistfrom": "Wijzigingen bekijken vanaf $3 $2",
        "immobile-target-namespace-iw": "Een interwikikoppeling is geen geldige bestemming voor het hernoemen van een pagina.",
        "immobile-source-page": "Deze pagina kan niet hernoemd worden.",
        "immobile-target-page": "Het is niet mogelijk te hernoemen naar die paginanaam.",
-       "movepage-invalid-target-title": "De opgevraagde naam is ongeldig.",
+       "movepage-invalid-target-title": "De gevraagde naam is ongeldig.",
        "bad-target-model": "De gewenste bestemming gebruikt een ander inhoudsmodel. Het is niet mogelijk om te zetten van $1 naar $2.",
        "imagenocrossnamespace": "Een mediabestand kan niet naar een andere naamruimte verplaatst worden",
        "nonfile-cannot-move-to-file": "Het is niet mogelijk te hernoemen van en naar de bestandsnaamruimte",
index f199643..6e22ad4 100644 (file)
        "showdiff": "Yong-a wallak",
        "anoneditwarning": "<strong>Warning:</strong> You are not logged in. Noonook IP-karl-up will be publicly djinang il noonook wallak. Noonook-il <strong>[$1 log in]</strong> or <strong>[$2 create an gudak]</strong>, noonook wallak will be attributed to noonook niall-kwel-le, along with other benefits.",
        "blockedtitle": "Niall be nap-nap",
-       "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"email this user\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
+       "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"{{int:emailuser}}\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
        "loginreqlink": "yaarlkoorl",
        "newarticletext": "Noonook ngwaliny beda bibol uart-yogow yeye.\nWallak bibol qadgin mar waangkin ngardal (djinang [$1 mar yira bibol] ngatta katitjiny)\nWarra bainya noonook nidja, click noonook bowser's <strong>woort koorl</strong>button",
-       "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify him/her.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
+       "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify balang.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
        "noarticletext": "There is currently no text in this page.\nYou can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],\nor [{{fullurl:{{FULLPAGENAME}}|action=edit}} create this page]</span>.",
        "noarticletext-nopermission": "Nidja yeye uart text il nidja bibol.\nNoonook [[Special:Search/{{PAGENAME}}|genuniny-ung nidja bibol katta wir-iny]] bura wam bibol, ka <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} genuniny boonadairn]</span>, noonook uart kaya ijow walbirniny nidja bibol.",
        "userpage-userdoesnotexist-view": "Niall gaduk $1 be uart yeye-quadga",
        "recentchangeslinked-feed": "Noyyang wallak",
        "recentchangeslinked-toolbox": "Noyyang wallak",
        "recentchangeslinked-title": "Wallak noyyanging $1",
-       "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol (or il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
+       "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol ({{ns:category}} il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
        "recentchangeslinked-page": "Bibol kwel-le:",
        "recentchangeslinked-to": "Yong-a wallak bibol beda nitja bibol",
        "upload": "Yirra file",
index 8a1f1fe..3d39fd7 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Pokaż zmiany na stronach linkujących do",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Strony linkujące do</strong> zaznaczonej strony",
        "rcfilters-target-page-placeholder": "Wprowadź nazwę strony (lub kategorii)",
-       "rcfilters-alldiscussions-label": "Wszystkie dyskusje",
+       "rcfilters-allcontents-label": "Wszystkie (treść)",
+       "rcfilters-alldiscussions-label": "Wszystkie (dyskusje)",
        "rcnotefrom": "Poniżej {{PLURAL:$5|pokazano zmianę|pokazano zmiany}} {{PLURAL:$5|wykonaną|wykonane}} po <strong>$3, $4</strong> (nie więcej niż '''$1''' pozycji).",
        "rclistfromreset": "Zresetuj wybór daty",
        "rclistfrom": "Pokaż nowe zmiany od $3 $2",
        "ipb-disableusertalk": "Edytowanie przez tego użytkownika swojej strony dyskusji",
        "ipb-change-block": "Zmień ustawienia blokady",
        "ipb-confirm": "Potwierdzam blokadę",
-       "ipb-sitewide": "Całkowita",
-       "ipb-partial": "Częściowa",
+       "ipb-sitewide": "Całkowicie",
+       "ipb-partial": "Częściowo",
        "ipb-sitewide-help": "Wszystkie strony na wiki i wszystkie akcje inne edycyjne.",
        "ipb-partial-help": "Konkretne strony lub przestrzenie nazw.",
        "ipb-pages-label": "Strony",
index fc6d25f..e68fde6 100644 (file)
        "revdelete-hide-image": "فائيل جو مواد لڪايو",
        "revdelete-hide-name": "هدف ۽ نيمپيما لڪايو",
        "revdelete-hide-comment": "سنوار جو تتُ",
-       "revdelete-hide-user": "اÙ\8aÚ\8aÙ\8aٽر Ø¬Ù\88 Ù\88اپرائÙ\8aÙ\86دÚ\99\86اÙ\86Ø¡Ù\8f/آءÙ\90Ù¾Ù\90ي پتو",
+       "revdelete-hide-user": "سÙ\86Ù\88ارÙ\8aÙ\86دÚ\99 Ø¬Ù\88 Ù\88اپرائÙ\8aÙ\86دÚ\99\86اÙ\86Ø¡Ù\8f/آئÙ\90Ù¾ي پتو",
        "revdelete-hide-restricted": "منتظمن توڙي ٻين کان مليل اعداد دٻايو",
        "revdelete-radio-same": "(نہ بدلايو)",
        "revdelete-radio-set": "لڪل",
        "rcfilters-filter-editsbyother-label": "ٻين پاران تبديليون",
        "rcfilters-filtergroup-user-experience-level": "واپرائيندڙن جي رجسٽريشن ۽ تجربو",
        "rcfilters-filter-user-experience-level-registered-label": "رجسٽر ٿيل",
-       "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 Ø§Ù\8aÚ\8aÙ\8aٽر.",
+       "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 Ø³Ù\86Ù\88ارÙ\8aÙ\86دÚ\99.",
        "rcfilters-filter-user-experience-level-unregistered-label": "اڻرجسٽر ٿيل",
        "rcfilters-filter-user-experience-level-unregistered-description": "سنواريندڙ جيڪي داخل ٿيل ناھن.",
        "rcfilters-filter-user-experience-level-newcomer-label": "نوان ايندڙ",
        "imagelinks": "فائيل جو استعمال",
        "linkstoimage": "ھن فائيل کي {{PLURAL:$1|ھيٺيون صفحو استعمال ڪري ٿو|$1 ھيٺيان صفحا استعمال ڪن ٿا}}:",
        "nolinkstoimage": "ڪي بہ صفحا ناھن جيڪي ھن فائيل کي استعمال ڪندا ھجن.",
+       "linkstoimage-redirect": "$1 (فائيل چورڻو) $2",
        "sharedupload": "هيءَ فائيل $1 کان آهي ۽ ان کي ٻيون رٿائون به استعمال ڪري سگھن ٿيون.",
        "sharedupload-desc-here": "ھي فائيل $1 مان آھي ۽ ٻين رٿائن پاران پڻ استعمال ٿي سگھي ٿو. تشريح انجي [[$2 جو تشريحي صفحو]] ھيٺان ڏنل آھي.",
        "filepage-nofile": "ھن نالي سان ڪوبہ  فائيل وجود نٿو رکي.",
        "protectlogpage": "تحفظ لاگ",
        "protectedarticle": "محفوظ ٿيل \"[[$1]]\"",
        "modifiedarticleprotection": "\"[[$1]]\" جي تحفظ جي سطح تبديل ڪئي",
+       "unprotectedarticle": "\"[[$1]]\" تان تحفظ ھٽايو ويو",
        "movedarticleprotection": "\"[[$2]]\" جو حفاظت درجو \"[[$1]]\" جي طرف منتقل ڪيو",
+       "unprotectedarticle-comment": "\"[[$1]]\" تان {{GENDER:$2|تحفظ ھٽايو}}",
        "prot_1movedto2": "[[$1]] کي چوري [[$2]] تي رکيو ويو",
        "protect-legend": "تحفظڻ جي پڪ ڪريو",
        "protectcomment": "سبب:",
        "htmlform-cloner-delete": "هٽايو",
        "htmlform-title-not-exists": "$1 وجود نٿو رکي.",
        "logentry-delete-delete": "$1 {{GENDER:$2|ڊاٿو}} صفحو $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|بحاليو}} صفحو $3 ($4)",
        "logentry-delete-revision": "$1 $3: $4 صفحي تي {{PLURAL:$5|ھڪ مسودي|$5 مسودن}} جي ظاھريت {{GENDER:$2|تبديل ڪئي}}",
        "revdelete-content-hid": "مواد لڪيل",
        "revdelete-uname-hid": "واپرائيندڙ-نانءُ لڪل",
+       "revdelete-unrestricted": "منتظمن تان پابنديون ھٽايون ويون",
        "logentry-block-block": "$1، {{GENDER:$4|$3}} تي $5 وقت جي خاتمي تائين {{GENDER:$2|بندش هئي آهي}} $6",
        "logentry-move-move": "$1 {{GENDER:$2|چوريو}} صفحو $3 ڏانهن $4",
        "logentry-move-move-noredirect": "$1 $3 صفحي کي $4 ڏانھن {{GENDER:$2|چوريو}} سواءِ ڪو ريڊائريڪٽ ڇڏيندي",
        "logentry-patrol-patrol-auto": "$1 پاڻمرادو صفحي $3 جي $4 مسودي تي گشت ڪيل طور {{GENDER:$2|نشان لڳايو}}",
        "logentry-newusers-create": "واپرائيندڙ کاتو $1 {{GENDER:$2|سرجيو ويو}}",
        "logentry-newusers-autocreate": "واپرائيندڙ کاتو $1 پاڻمرادو {{GENDER:$2|کوليو ويو}}",
+       "logentry-protect-unprotect": "$1 $3 تان تحفظ {{GENDER:$2|ھٽايو}}",
        "logentry-protect-protect": "$1 {{GENDER:$2|محفوظ ڪيو}} $3 $4",
        "logentry-upload-upload": "$1 {{GENDER:$2|چاڙهيو}} $3",
        "logentry-upload-overwrite": "$1 $3 جو ھڪ نئون ورزن {{GENDER:$2|چاڙھيو}}",
        "mw-widgets-usersmultiselect-placeholder": "وڌيڪ شامل ڪيو...",
        "date-range-from": "تاريخ کان:",
        "date-range-to": "تاريخ تائين:",
+       "randomrootpage": "بلاترتيب پاڙ صفحو",
        "log-action-filter-all": "سڀ"
 }
index 361f463..9214243 100644 (file)
                        "Suchichi02",
                        "神樂坂秀吉",
                        "WQL",
-                       "Looong"
+                       "Looong",
+                       "予弦"
                ]
        },
        "tog-underline": "链接下划线:",
index 044b9be..1def4bd 100644 (file)
           "requires": {
             "lodash": "^4.17.11"
           }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
         }
       }
     },
         }
       }
     },
-    "humanize-duration": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.15.3.tgz",
-      "integrity": "sha512-BMz6w8p3NVa6QP9wDtqUkXfwgBqDaZ5z/np0EYdoWrLqL849Onp6JWMXMhbHtuvO9jUThLN5H1ThRQ8dUWnYkA==",
-      "dev": true
-    },
     "iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
       }
     },
     "lodash": {
-      "version": "4.17.11",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-      "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
       "dev": true
     },
     "lodash.get": {
       }
     },
     "mixin-deep": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
-      "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
       "dev": true,
       "requires": {
         "for-in": "^1.0.2",
       "dev": true
     },
     "set-value": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
-      "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
       "dev": true,
       "requires": {
         "extend-shallow": "^2.0.1",
       }
     },
     "union-value": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
-      "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
       "dev": true,
       "requires": {
         "arr-union": "^3.1.0",
         "get-value": "^2.0.6",
         "is-extendable": "^0.1.1",
-        "set-value": "^0.4.3"
-      },
-      "dependencies": {
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        },
-        "set-value": {
-          "version": "0.4.3",
-          "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
-          "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
-          "dev": true,
-          "requires": {
-            "extend-shallow": "^2.0.1",
-            "is-extendable": "^0.1.1",
-            "is-plain-object": "^2.0.1",
-            "to-object-path": "^0.3.0"
-          }
-        }
+        "set-value": "^2.0.1"
       }
     },
     "uniq": {
         "sauce-connect-launcher": "~1.2.3"
       }
     },
-    "wdio-spec-reporter": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.5.tgz",
-      "integrity": "sha512-MqvgTow8hFwhFT47q67JwyJyeynKodGRQCxF7ijKPGfsaG1NLssbXYc0JhiL7SiAyxnQxII0UxzTCd3I6sEdkg==",
-      "dev": true,
-      "requires": {
-        "babel-runtime": "~6.26.0",
-        "chalk": "^2.3.0",
-        "humanize-duration": "~3.15.0"
-      }
-    },
     "wdio-sync": {
       "version": "0.7.3",
       "resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.3.tgz",
index f16b605..2b88303 100644 (file)
     "postcss-less": "2.0.0",
     "qunit": "2.9.1",
     "stylelint-config-wikimedia": "0.6.0",
+    "wdio-dot-reporter": "0.0.10",
     "wdio-junit-reporter": "0.4.4",
     "wdio-mediawiki": "file:tests/selenium/wdio-mediawiki",
     "wdio-mocha-framework": "0.6.4",
     "wdio-sauce-service": "0.4.14",
-    "wdio-spec-reporter": "0.1.5",
     "webdriverio": "4.14.4"
   }
 }
index 22bac08..b129303 100644 (file)
                        )
                );
 
-               if ( this.cache ) {
-                       this.cache.set( pageData );
-               }
-
                // Offer the exact text as a suggestion if the page exists
                if ( this.addQueryInput && pageExists && !pageExistsExact ) {
                        titles.unshift( this.getQueryValue() );
+                       // Ensure correct page metadata gets used
+                       pageData[ this.getQueryValue() ] = pageData[ titleObj.getPrefixedText() ];
+               }
+
+               if ( this.cache ) {
+                       this.cache.set( pageData );
                }
 
                for ( i = 0, len = titles.length; i < len; i++ ) {
index 36c3fe2..c8b8ef9 100644 (file)
@@ -313,7 +313,7 @@ class ParserTestRunner {
                        'class' => NullLockManager::class,
                ] ];
                $reset = function () {
-                       LockManagerGroup::destroySingletons();
+                       MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
                };
                $setup[] = $reset;
                $teardown[] = $reset;
index 93c5345..cf835ce 100644 (file)
@@ -119,7 +119,7 @@ class ApiQuerySearchTest extends ApiTestCase {
         */
        private function mockResultClosure( $title, $setters = [] ) {
                return function () use ( $title, $setters ){
-                       $result = MockSearchResult::newFromTitle( Title::newFromText( $title ) );
+                       $result = new MockSearchResult( Title::newFromText( $title ) );
 
                        foreach ( $setters as $method => $param ) {
                                $result->$method( $param );
index e92eb56..418832e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 
-class MockSearchResult extends SearchResult {
+class MockSearchResult extends RevisionSearchResult {
        private $isMissingRevision = false;
        private $isBrokenTitle = false;
 
diff --git a/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php b/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php
new file mode 100644 (file)
index 0000000..38fcf29
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * @covers MediaWiki\FileBackend\LockManager\LockManagerGroupFactory
+ * @todo Should we somehow test that the LockManagerGroup objects are as we expect? How do we do
+ *   that without getting into testing LockManagerGroup itself?
+ */
+class LockManagerGroupFactoryTest extends MediaWikiUnitTestCase {
+       public function testGetLockManagerGroup() {
+               $mockLbFactory = $this->createMock( LBFactory::class );
+               $mockLbFactory->expects( $this->never() )->method( $this->anything() );
+
+               $factory = new LockManagerGroupFactory( 'defaultDomain', [], $mockLbFactory );
+               $lbmUnspecified = $factory->getLockManagerGroup();
+               $lbmFalse = $factory->getLockManagerGroup( false );
+               $lbmDefault = $factory->getLockManagerGroup( 'defaultDomain' );
+               $lbmOther = $factory->getLockManagerGroup( 'otherDomain' );
+
+               $this->assertSame( $lbmUnspecified, $lbmFalse );
+               $this->assertSame( $lbmFalse, $lbmDefault );
+               $this->assertSame( $lbmDefault, $lbmUnspecified );
+               $this->assertNotEquals( $lbmUnspecified, $lbmOther );
+               $this->assertNotEquals( $lbmFalse, $lbmOther );
+               $this->assertNotEquals( $lbmDefault, $lbmOther );
+
+               $this->assertSame( $lbmUnspecified, $factory->getLockManagerGroup() );
+               $this->assertSame( $lbmFalse, $factory->getLockManagerGroup( false ) );
+               $this->assertSame( $lbmDefault, $factory->getLockManagerGroup( 'defaultDomain' ) );
+               $this->assertSame( $lbmOther, $factory->getLockManagerGroup( 'otherDomain' ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupTest.php b/tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupTest.php
new file mode 100644 (file)
index 0000000..79baac9
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Since this is a unit test, we don't test the singleton() or destroySingletons() methods. We also
+ * can't test get() with a valid argument, because that winds up calling static methods of
+ * ObjectCache and LoggerFactory that aren't yet compatible with proper unit tests. Those will be
+ * tested in the integration test for now.
+ *
+ * @covers LockManagerGroup
+ */
+class LockManagerGroupTest extends MediaWikiUnitTestCase {
+       private function getMockLBFactory() {
+               $mock = $this->createMock( LBFactory::class );
+               $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
+               return $mock;
+       }
+
+       public function testConstructorNoConfigs() {
+               new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+               $this->assertTrue( true, 'No exception thrown' );
+       }
+
+       public function testConstructorConfigWithNoName() {
+               $this->setExpectedException( Exception::class,
+                       'Cannot register a lock manager with no name.' );
+
+               new LockManagerGroup( 'domain',
+                       [ [ 'name' => 'a', 'class' => 'b' ], [ 'class' => 'c' ] ], $this->getMockLBFactory() );
+       }
+
+       public function testConstructorConfigWithNoClass() {
+               $this->setExpectedException( Exception::class,
+                       'Cannot register lock manager `c` with no class.' );
+
+               new LockManagerGroup( 'domain',
+                       [ [ 'name' => 'a', 'class' => 'b' ], [ 'name' => 'c' ] ], $this->getMockLBFactory() );
+       }
+
+       public function testGetUndefined() {
+               $this->setExpectedException( Exception::class,
+                       'No lock manager defined with the name `c`.' );
+
+               $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+                       $this->getMockLBFactory() );
+               $lmg->get( 'c' );
+       }
+
+       public function testConfigUndefined() {
+               $this->setExpectedException( Exception::class,
+                       'No lock manager defined with the name `c`.' );
+
+               $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+                       $this->getMockLBFactory() );
+               $lmg->config( 'c' );
+       }
+
+       public function testConfig() {
+               $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b', 'foo' => 'c' ] ],
+                       $this->getMockLBFactory() );
+               $this->assertSame(
+                       [ 'class' => 'b', 'name' => 'a', 'foo' => 'c', 'domain' => 'domain' ],
+                       $lmg->config( 'a' )
+               );
+       }
+
+       public function testGetDefaultNull() {
+               $lmg = new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+               $expected = new NullLockManager( [] );
+               $actual = $lmg->getDefault();
+               // Have to get rid of the $sessions for equality check to work
+               TestingAccessWrapper::newFromObject( $actual )->session = null;
+               TestingAccessWrapper::newFromObject( $expected )->session = null;
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function testGetAnyException() {
+               // XXX Isn't the name 'getAny' misleading if we don't get whatever's available?
+               $this->setExpectedException( Exception::class,
+                       'No lock manager defined with the name `fsLockManager`.' );
+
+               $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+                       $this->getMockLBFactory() );
+               $lmg->getAny();
+       }
+}
index 662224c..6512e7d 100644 (file)
@@ -121,7 +121,7 @@ exports.config = {
 
        // Test reporter for stdout.
        // See also: http://webdriver.io/guide/testrunner/reporters.html
-       reporters: [ 'spec', 'junit' ],
+       reporters: [ 'dot', 'junit' ],
        reporterOptions: {
                junit: {
                        outputDir: logPath