Merge "MessageCache: Remove $wgMsgCacheExpiry configuration var"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 27 Aug 2019 18:33:05 +0000 (18:33 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 27 Aug 2019 18:33:05 +0000 (18:33 +0000)
97 files changed:
RELEASE-NOTES-1.34
autoload.php
includes/DefaultSettings.php
includes/ForkController.php
includes/Linker.php
includes/MediaWikiServices.php
includes/OutputPage.php
includes/Rest/EntryPoint.php
includes/ServiceWiring.php
includes/Setup.php
includes/Title.php
includes/actions/InfoAction.php
includes/actions/pagers/HistoryPager.php
includes/api/ApiPageSet.php
includes/api/ApiQueryAllDeletedRevisions.php
includes/api/ApiQueryImageInfo.php
includes/api/i18n/fr.json
includes/api/i18n/sv.json
includes/api/i18n/zh-hans.json
includes/block/BlockManager.php
includes/cache/localisation/LocalisationCache.php
includes/filebackend/FileBackendGroup.php
includes/filebackend/lockmanager/LockManagerGroup.php
includes/filebackend/lockmanager/LockManagerGroupFactory.php [new file with mode: 0644]
includes/gallery/ImageGalleryBase.php
includes/installer/Installer.php
includes/installer/SqliteInstaller.php
includes/language/ConverterRule.php [new file with mode: 0644]
includes/language/LanguageCode.php
includes/language/LanguageNameUtils.php [deleted file]
includes/libs/Xhprof.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/mail/EmailNotification.php
includes/objectcache/SqlBagOStuff.php
includes/pager/IndexPager.php
includes/pager/TablePager.php
includes/parser/CoreParserFunctions.php
includes/parser/PPFrame_DOM.php
includes/parser/PPFrame_Hash.php
includes/password/PasswordPolicyChecks.php
includes/preferences/DefaultPreferencesFactory.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/SearchResultTrait.php [new file with mode: 0644]
includes/search/SqlSearchResult.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialNewSection.php
includes/specials/SpecialRecentChanges.php
includes/specials/pagers/AllMessagesTablePager.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/CategoryPager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/NewFilesPager.php
includes/specials/pagers/ProtectedPagesPager.php
languages/ConverterRule.php [deleted file]
languages/Language.php
languages/data/Names.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
maintenance/rebuildLocalisationCache.php
package-lock.json
package.json
resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
tests/common/TestsAutoLoader.php
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiIntegrationTestCase.php
tests/phpunit/includes/Rest/EntryPointTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiQuerySearchTest.php
tests/phpunit/includes/api/ApiQuerySiteinfoTest.php
tests/phpunit/includes/cache/LocalisationCacheTest.php
tests/phpunit/includes/language/ConverterRuleTest.php [new file with mode: 0644]
tests/phpunit/includes/logging/LogFormatterTest.php
tests/phpunit/includes/password/PasswordPolicyChecksTest.php
tests/phpunit/includes/search/SearchResultTest.php [deleted file]
tests/phpunit/includes/search/SearchResultTraitTest.php [new file with mode: 0644]
tests/phpunit/languages/LanguageTest.php
tests/phpunit/mocks/search/MockSearchResult.php
tests/phpunit/unit/includes/Rest/EntryPointTest.php [deleted file]
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/phpunit/unit/includes/language/LanguageNameUtilsTest.php [deleted file]
tests/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php [deleted file]
tests/selenium/wdio.conf.js

index 7cf1c02..0205829 100644 (file)
@@ -356,8 +356,6 @@ because of Phabricator reports.
 * The UserIsBlockedFrom hook is only called if a block is found first, and
   should only be used to unblock a blocked user.
 * …
-* Language::$dataCache has been removed (without prior deprecation, for
-  practical reasons). Use MediaWikiServices instead to get a LocalisationCache.
 
 === Deprecations in 1.34 ===
 * The MWNamespace class is deprecated. Use NamespaceInfo.
@@ -467,12 +465,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.
-* Language::getLocalisationCache() is deprecated. Use MediaWikiServices.
-* The following Language methods are deprecated: isSupportedLanguage,
-  isValidCode, isValidBuiltInCode, isKnownLanguageTag, fetchLanguageNames,
-  fetchLanguageName, getFileName, getMessagesFileName, getJsonMessagesFileName.
-  Use the new LanguageNameUtils class instead. (Note that fetchLanguageName(s)
-  are called getLanguageName(s) in the new class.)
+* 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 dfdf802..35c9b0a 100644 (file)
@@ -316,7 +316,7 @@ $wgAutoloadLocalClasses = [
        'ConvertExtensionToRegistration' => __DIR__ . '/maintenance/convertExtensionToRegistration.php',
        'ConvertLinks' => __DIR__ . '/maintenance/convertLinks.php',
        'ConvertUserOptions' => __DIR__ . '/maintenance/convertUserOptions.php',
-       'ConverterRule' => __DIR__ . '/languages/ConverterRule.php',
+       'ConverterRule' => __DIR__ . '/includes/language/ConverterRule.php',
        'Cookie' => __DIR__ . '/includes/libs/Cookie.php',
        'CookieJar' => __DIR__ . '/includes/libs/CookieJar.php',
        'CopyFileBackend' => __DIR__ . '/maintenance/copyFileBackend.php',
@@ -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',
@@ -892,7 +893,6 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Languages\\Data\\CrhExceptions' => __DIR__ . '/languages/data/CrhExceptions.php',
        'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
        'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php',
-       'MediaWiki\\Languages\\LanguageNameUtils' => __DIR__ . '/includes/language/LanguageNameUtils.php',
        'MediaWiki\\Logger\\ConsoleLogger' => __DIR__ . '/includes/debug/logger/ConsoleLogger.php',
        'MediaWiki\\Logger\\ConsoleSpi' => __DIR__ . '/includes/debug/logger/ConsoleSpi.php',
        'MediaWiki\\Logger\\LegacyLogger' => __DIR__ . '/includes/debug/logger/LegacyLogger.php',
@@ -1286,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',
@@ -1317,6 +1319,7 @@ $wgAutoloadLocalClasses = [
        'SearchResult' => __DIR__ . '/includes/search/SearchResult.php',
        'SearchResultSet' => __DIR__ . '/includes/search/SearchResultSet.php',
        'SearchResultSetTrait' => __DIR__ . '/includes/search/SearchResultSetTrait.php',
+       'SearchResultTrait' => __DIR__ . '/includes/search/SearchResultTrait.php',
        'SearchSqlite' => __DIR__ . '/includes/search/SearchSqlite.php',
        'SearchSuggestion' => __DIR__ . '/includes/search/SearchSuggestion.php',
        'SearchSuggestionSet' => __DIR__ . '/includes/search/SearchSuggestionSet.php',
index b1d7b3a..35e8ae5 100644 (file)
@@ -2636,8 +2636,6 @@ $wgLocalisationCacheConf = [
        'store' => 'detect',
        'storeClass' => false,
        'storeDirectory' => false,
-       'storeServer' => [],
-       'forceRecache' => false,
        'manualRecache' => false,
 ];
 
@@ -6839,6 +6837,8 @@ $wgRCLinkLimits = [ 50, 100, 250, 500 ];
 /**
  * List of Days options to list in the Special:Recentchanges and
  * Special:Recentchangeslinked pages.
+ *
+ * @see ChangesListSpecialPage::getLinkDays
  */
 $wgRCLinkDays = [ 1, 3, 7, 14, 30 ];
 
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 47be8a2..a79ec3a 100644 (file)
@@ -1322,7 +1322,7 @@ class Linker {
                                        $services->getNamespaceInfo()->getCanonicalName( NS_MEDIA ), '/' );
                                $medians .= '|';
                                $medians .= preg_quote(
-                                       MediaWikiServices::getInstance()->getContentLanguage()->getNsText( NS_MEDIA ),
+                                       $services->getContentLanguage()->getNsText( NS_MEDIA ),
                                        '/'
                                ) . '):';
 
@@ -1359,7 +1359,7 @@ class Linker {
                                        }
                                        if ( $match[1] !== false && $match[1] !== '' ) {
                                                if ( preg_match(
-                                                       MediaWikiServices::getInstance()->getContentLanguage()->linkTrail(),
+                                                       $services->getContentLanguage()->linkTrail(),
                                                        $match[3],
                                                        $submatch
                                                ) ) {
@@ -1375,7 +1375,7 @@ class Linker {
 
                                                Title::newFromText( $linkTarget );
                                                try {
-                                                       $target = MediaWikiServices::getInstance()->getTitleParser()->
+                                                       $target = $services->getTitleParser()->
                                                                parseTitle( $linkTarget );
 
                                                        if ( $target->getText() == '' && !$target->isExternal()
index e926c32..3b80e58 100644 (file)
@@ -14,12 +14,11 @@ use GlobalVarConfig;
 use Hooks;
 use IBufferingStatsdDataFactory;
 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
-use LocalisationCache;
 use MediaWiki\Block\BlockManager;
 use MediaWiki\Block\BlockRestrictionStore;
 use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
 use MediaWiki\Http\HttpRequestFactory;
-use MediaWiki\Languages\LanguageNameUtils;
 use MediaWiki\Page\MovePageFactory;
 use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\Preferences\PreferencesFactory;
@@ -625,14 +624,6 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'InterwikiLookup' );
        }
 
-       /**
-        * @since 1.34
-        * @return LanguageNameUtils
-        */
-       public function getLanguageNameUtils() {
-               return $this->getService( 'LanguageNameUtils' );
-       }
-
        /**
         * @since 1.28
         * @return LinkCache
@@ -660,14 +651,6 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'LinkRendererFactory' );
        }
 
-       /**
-        * @since 1.34
-        * @return LocalisationCache
-        */
-       public function getLocalisationCache() : LocalisationCache {
-               return $this->getService( 'LocalisationCache' );
-       }
-
        /**
         * @since 1.28
         * @return \BagOStuff
@@ -676,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 3f2dcf7..b2ca53a 100644 (file)
@@ -3227,7 +3227,7 @@ class OutputPage extends ContextSource {
 
                $title = $this->getTitle();
                $ns = $title->getNamespace();
-               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               $nsInfo = $services->getNamespaceInfo();
                $canonicalNamespace = $nsInfo->exists( $ns )
                        ? $nsInfo->getCanonicalName( $ns )
                        : $title->getNsText();
index ae01ae4..a4959d1 100644 (file)
@@ -3,6 +3,7 @@
 namespace MediaWiki\Rest;
 
 use ExtensionRegistry;
+use MediaWiki;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
 use RequestContext;
@@ -16,6 +17,8 @@ class EntryPoint {
        private $webResponse;
        /** @var Router */
        private $router;
+       /** @var RequestContext */
+       private $context;
 
        public static function main() {
                // URL safety checks
@@ -24,10 +27,12 @@ class EntryPoint {
                        return;
                }
 
+               $context = RequestContext::getMain();
+
                // Set $wgTitle and the title in RequestContext, as in api.php
                global $wgTitle;
                $wgTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/rest.php' );
-               RequestContext::getMain()->setTitle( $wgTitle );
+               $context->setTitle( $wgTitle );
 
                $services = MediaWikiServices::getInstance();
                $conf = $services->getMainConfig();
@@ -42,7 +47,7 @@ class EntryPoint {
                        'cookiePrefix' => $conf->get( 'CookiePrefix' )
                ] );
 
-               $authorizer = new MWBasicAuthorizer( RequestContext::getMain()->getUser(),
+               $authorizer = new MWBasicAuthorizer( $context->getUser(),
                        $services->getPermissionManager() );
 
                global $IP;
@@ -56,21 +61,24 @@ class EntryPoint {
                );
 
                $entryPoint = new self(
+                       $context,
                        $request,
                        $wgRequest->response(),
                        $router );
                $entryPoint->execute();
        }
 
-       public function __construct( RequestInterface $request, WebResponse $webResponse,
-               Router $router
+       public function __construct( RequestContext $context, RequestInterface $request,
+               WebResponse $webResponse, Router $router
        ) {
+               $this->context = $context;
                $this->request = $request;
                $this->webResponse = $webResponse;
                $this->router = $router;
        }
 
        public function execute() {
+               ob_start();
                $response = $this->router->execute( $this->request );
 
                $this->webResponse->header(
@@ -91,10 +99,13 @@ class EntryPoint {
                }
 
                // Clear all errors that might have been displayed if display_errors=On
-               ob_clean();
+               ob_end_clean();
 
                $stream = $response->getBody();
                $stream->rewind();
+
+               MediaWiki::preOutputCommit( $this->context );
+
                if ( $stream instanceof CopyableStreamInterface ) {
                        $stream->copyToStream( fopen( 'php://output', 'w' ) );
                } else {
@@ -106,5 +117,8 @@ class EntryPoint {
                                echo $buffer;
                        }
                }
+
+               $mw = new MediaWiki;
+               $mw->doPostOutputShutdown( 'fast' );
        }
 }
index 78609e5..1acd038 100644 (file)
@@ -45,10 +45,10 @@ 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;
-use MediaWiki\Languages\LanguageNameUtils;
 use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkRendererFactory;
 use MediaWiki\Logger\LoggerFactory;
@@ -257,13 +257,6 @@ return [
                );
        },
 
-       'LanguageNameUtils' => function ( MediaWikiServices $services ) : LanguageNameUtils {
-               return new LanguageNameUtils( new ServiceOptions(
-                       LanguageNameUtils::$constructorOptions,
-                       $services->getMainConfig()
-               ) );
-       },
-
        'LinkCache' => function ( MediaWikiServices $services ) : LinkCache {
                return new LinkCache(
                        $services->getTitleFormatter(),
@@ -290,56 +283,6 @@ return [
                );
        },
 
-       'LocalisationCache' => function ( MediaWikiServices $services ) : LocalisationCache {
-               $conf = $services->getMainConfig()->get( 'LocalisationCacheConf' );
-
-               $logger = LoggerFactory::getInstance( 'localisation' );
-
-               // Figure out what class to use for the LCStore
-               $storeArg = [];
-               $storeArg['directory'] =
-                       $conf['storeDirectory'] ?? $services->getMainConfig()->get( 'CacheDirectory' );
-
-               if ( !empty( $conf['storeClass'] ) ) {
-                       $storeClass = $conf['storeClass'];
-               } elseif ( $conf['store'] === 'files' || $conf['store'] === 'file' ||
-                       ( $conf['store'] === 'detect' && $storeArg['directory'] )
-               ) {
-                       $storeClass = LCStoreCDB::class;
-               } elseif ( $conf['store'] === 'db' || $conf['store'] === 'detect' ) {
-                       $storeClass = LCStoreDB::class;
-                       $storeArg['server'] = $conf['storeServer'] ?? [];
-               } elseif ( $conf['store'] === 'array' ) {
-                       $storeClass = LCStoreStaticArray::class;
-               } else {
-                       throw new MWException(
-                               'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
-                       );
-               }
-               $logger->debug( "LocalisationCache: using store $storeClass" );
-
-               return new $conf['class'](
-                       new ServiceOptions(
-                               LocalisationCache::$constructorOptions,
-                               // Two of the options are stored in $wgLocalisationCacheConf
-                               $conf,
-                               // In case someone set that config variable and didn't reset all keys, set defaults.
-                               [
-                                       'forceRecache' => false,
-                                       'manualRecache' => false,
-                               ],
-                               // Some other options come from config itself
-                               $services->getMainConfig()
-                       ),
-                       new $storeClass( $storeArg ),
-                       $logger,
-                       [ function () use ( $services ) {
-                               $services->getResourceLoader()->getMessageBlobStore()->clear();
-                       } ],
-                       $services->getLanguageNameUtils()
-               );
-       },
-
        'LocalServerObjectCache' => function ( MediaWikiServices $services ) : BagOStuff {
                $config = $services->getMainConfig();
                $cacheId = \ObjectCache::detectLocalServerCache();
@@ -347,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 201e1a9..2267800 100644 (file)
@@ -96,7 +96,7 @@ if ( !interface_exists( 'Psr\Log\LoggerInterface' ) ) {
 // Install a header callback
 MediaWiki\HeaderCallback::register();
 
-// Set the encoding used by reading HTTP input, writing HTTP output.
+// Set the encoding used by PHP for reading HTTP input, and writing output.
 // This is also the default for mbstring functions.
 mb_internal_encoding( 'UTF-8' );
 
@@ -128,9 +128,6 @@ if ( defined( 'MW_SETUP_CALLBACK' ) ) {
  * Main setup
  */
 
-$fname = 'Setup.php';
-$ps_setup = Profiler::instance()->scopedProfileIn( $fname );
-
 // Load queued extensions
 ExtensionRegistry::getInstance()->loadFromQueue();
 // Don't let any other extensions load
@@ -141,8 +138,6 @@ putenv( "LC_ALL=$wgShellLocale" );
 setlocale( LC_ALL, $wgShellLocale );
 
 // Set various default paths sensibly...
-$ps_default = Profiler::instance()->scopedProfileIn( $fname . '-defaults' );
-
 if ( $wgScript === false ) {
        $wgScript = "$wgScriptPath/index.php";
 }
@@ -368,19 +363,6 @@ foreach ( $wgForeignFileRepos as &$repo ) {
 unset( $repo ); // no global pollution; destroy reference
 
 $rcMaxAgeDays = $wgRCMaxAge / ( 3600 * 24 );
-if ( $wgRCFilterByAge ) {
-       // Trim down $wgRCLinkDays so that it only lists links which are valid
-       // as determined by $wgRCMaxAge.
-       // Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
-       sort( $wgRCLinkDays );
-
-       foreach ( $wgRCLinkDays as $i => $days ) {
-               if ( $days >= $rcMaxAgeDays ) {
-                       array_splice( $wgRCLinkDays, $i + 1 );
-                       break;
-               }
-       }
-}
 // Ensure that default user options are not invalid, since that breaks Special:Preferences
 $wgDefaultUserOptions['rcdays'] = min(
        $wgDefaultUserOptions['rcdays'],
@@ -638,8 +620,6 @@ if ( defined( 'MW_NO_SESSION' ) ) {
        $wgPHPSessionHandling = MW_NO_SESSION === 'warn' ? 'warn' : 'disable';
 }
 
-Profiler::instance()->scopedProfileOut( $ps_default );
-
 // Disable MWDebug for command line mode, this prevents MWDebug from eating up
 // all the memory from logging SQL queries on maintenance scripts
 global $wgCommandLineMode;
@@ -669,8 +649,6 @@ foreach ( [ 'wgArticlePath', 'wgVariantArticlePath' ] as $varName ) {
        }
 }
 
-$ps_default2 = Profiler::instance()->scopedProfileIn( $fname . '-defaults2' );
-
 if ( $wgCanonicalServer === false ) {
        $wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
 }
@@ -740,10 +718,6 @@ if ( $wgSharedDB && $wgSharedTables ) {
        );
 }
 
-Profiler::instance()->scopedProfileOut( $ps_default2 );
-
-$ps_misc = Profiler::instance()->scopedProfileIn( $fname . '-misc' );
-
 // Raise the memory limit if it's too low
 // Note, this makes use of wfDebug, and thus should not be before
 // MWDebug::init() is called.
@@ -823,13 +797,9 @@ wfDebugLog( 'caches',
        ', session: ' . get_class( ObjectCache::getInstance( $wgSessionCacheType ) )
 );
 
-Profiler::instance()->scopedProfileOut( $ps_misc );
-
 // Most of the config is out, some might want to run hooks here.
 Hooks::run( 'SetupAfterCache' );
 
-$ps_globals = Profiler::instance()->scopedProfileIn( $fname . '-globals' );
-
 /**
  * @var Language $wgContLang
  * @deprecated since 1.32, use the ContentLanguage service directly
@@ -939,9 +909,6 @@ $wgParser = new StubObject( 'wgParser', function () {
  */
 $wgTitle = null;
 
-Profiler::instance()->scopedProfileOut( $ps_globals );
-$ps_extensions = Profiler::instance()->scopedProfileIn( $fname . '-extensions' );
-
 // Extension setup functions
 // Entries should be added to this variable during the inclusion
 // of the extension file. This allows the extension to perform
@@ -974,6 +941,3 @@ if ( !$wgCommandLineMode ) {
 }
 
 $wgFullyInitialised = true;
-
-Profiler::instance()->scopedProfileOut( $ps_extensions );
-Profiler::instance()->scopedProfileOut( $ps_setup );
index 75ddea8..eb19076 100644 (file)
@@ -3184,7 +3184,7 @@ class Title implements LinkTarget, IDBAccessObject {
        public static function capitalize( $text, $ns = NS_MAIN ) {
                $services = MediaWikiServices::getInstance();
                if ( $services->getNamespaceInfo()->isCapitalized( $ns ) ) {
-                       return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
+                       return $services->getContentLanguage()->ucfirst( $text );
                } else {
                        return $text;
                }
index 15cee94..7bcfc88 100644 (file)
@@ -280,7 +280,7 @@ class InfoAction extends FormlessAction {
                // Language in which the page content is (supposed to be) written
                $pageLang = $title->getPageLanguage()->getCode();
 
-               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               $permissionManager = $services->getPermissionManager();
 
                $pageLangHtml = $pageLang . ' - ' .
                        Language::fetchLanguageName( $pageLang, $lang->getCode() );
index 14f76bc..fd2fbd0 100644 (file)
@@ -422,7 +422,7 @@ class HistoryPager extends ReverseChronologicalPager {
                                $undoTooltip = $latest
                                        ? [ 'title' => $this->msg( 'tooltip-undo' )->text() ]
                                        : [];
-                               $undolink = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+                               $undolink = $this->getLinkRenderer()->makeKnownLink(
                                        $this->getTitle(),
                                        $this->msg( 'editundo' )->text(),
                                        $undoTooltip,
@@ -502,7 +502,7 @@ class HistoryPager extends ReverseChronologicalPager {
                ) {
                        return $cur;
                } else {
-                       return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+                       return $this->getLinkRenderer()->makeKnownLink(
                                $this->getTitle(),
                                new HtmlArmor( $cur ),
                                [],
@@ -531,7 +531,7 @@ class HistoryPager extends ReverseChronologicalPager {
                        return $last;
                }
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                if ( $next === 'unknown' ) {
                        # Next row probably exists but is unknown, use an oldid=prev link
                        return $linkRenderer->makeKnownLink(
index 1b58865..c604322 100644 (file)
@@ -1253,7 +1253,7 @@ class ApiPageSet extends ApiBase {
 
                        // Need gender information
                        if (
-                               MediaWikiServices::getInstance()->getNamespaceInfo()->
+                               $services->getNamespaceInfo()->
                                        hasGenderDistinction( $titleObj->getNamespace() )
                        ) {
                                $usernames[] = $titleObj->getText();
index d713b3a..7d6d342 100644 (file)
@@ -134,7 +134,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        $this->addJoinConds(
                                [ 'change_tag' => [ 'JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ]
                        );
-                       $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
+                       $changeTagDefStore = $services->getChangeTagDefStore();
                        try {
                                $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) );
                        } catch ( NameTableAccessException $exception ) {
index b97ab3c..5e737c3 100644 (file)
@@ -63,7 +63,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                $this->dieWithError( [ 'apierror-bad-badfilecontexttitle', $p ], 'invalid-title' );
                        }
                } else {
-                       $badFileContextTitle = false;
+                       $badFileContextTitle = null;
                }
 
                $pageIds = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
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 03043e1..83b59c7 100644 (file)
@@ -104,6 +104,7 @@ class BlockManager {
         */
        public function getUserBlock( User $user, $fromReplica ) {
                $isAnon = $user->getId() === 0;
+               $fromMaster = !$fromReplica;
 
                // TODO: If $user is the current user, we should use the current request. Otherwise,
                // we should not look for XFF or cookie blocks.
@@ -127,7 +128,7 @@ class BlockManager {
                // User/IP blocking
                // After this, $blocks is an array of blocks or an empty array
                // TODO: remove dependency on DatabaseBlock
-               $blocks = DatabaseBlock::newListFromTarget( $user, $ip, !$fromReplica );
+               $blocks = DatabaseBlock::newListFromTarget( $user, $ip, $fromMaster );
 
                // Cookie blocking
                $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
@@ -164,7 +165,7 @@ class BlockManager {
                        $xff = array_map( 'trim', explode( ',', $xff ) );
                        $xff = array_diff( $xff, [ $ip ] );
                        // TODO: remove dependency on DatabaseBlock
-                       $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, !$fromReplica );
+                       $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, $fromMaster );
                        $blocks = array_merge( $blocks, $xffblocks );
                }
 
@@ -256,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 fb4675e..ffc7cd0 100644 (file)
 
 use CLDRPluralRuleParser\Evaluator;
 use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-use Psr\Log\LoggerInterface;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class for caching the contents of localisation files, Messages*.php
  * and *.i18n.php.
  *
- * An instance of this class is available using MediaWikiServices.
+ * An instance of this class is available using Language::getLocalisationCache().
  *
  * The values retrieved from here are merged, containing items from extension
  * files, core messages files and the language fallback sequence (e.g. zh-cn ->
@@ -41,8 +40,8 @@ use Psr\Log\LoggerInterface;
 class LocalisationCache {
        const VERSION = 4;
 
-       /** @var ServiceOptions */
-       private $options;
+       /** Configuration associative array */
+       private $conf;
 
        /**
         * True if recaching should only be done on an explicit call to recache().
@@ -51,6 +50,11 @@ class LocalisationCache {
         */
        private $manualRecache = false;
 
+       /**
+        * True to treat all files as expired until they are regenerated by this object.
+        */
+       private $forceRecache = false;
+
        /**
         * The cache data. 3-d array, where the first key is the language code,
         * the second key is the item key e.g. 'messages', and the third key is
@@ -67,16 +71,10 @@ class LocalisationCache {
        private $store;
 
        /**
-        * @var LoggerInterface
+        * @var \Psr\Log\LoggerInterface
         */
        private $logger;
 
-       /** @var callable[] See comment for parameter in constructor */
-       private $clearStoreCallbacks;
-
-       /** @var LanguageNameUtils */
-       private $langNameUtils;
-
        /**
         * A 2-d associative array, code/key, where presence indicates that the item
         * is loaded. Value arbitrary.
@@ -190,52 +188,60 @@ class LocalisationCache {
 
        private $mergeableKeys = null;
 
-       /**
-        * @todo Make this a const when HHVM support is dropped (T192166)
-        *
-        * @var array
-        * @since 1.34
-        */
-       public static $constructorOptions = [
-               // True to treat all files as expired until they are regenerated by this object.
-               'forceRecache',
-               'manualRecache',
-               'ExtensionMessagesFiles',
-               'MessagesDirs',
-       ];
-
        /**
         * For constructor parameters, see the documentation in DefaultSettings.php
         * for $wgLocalisationCacheConf.
         *
-        * Do not construct this directly. Use MediaWikiServices.
-        *
-        * @param ServiceOptions $options
-        * @param LCStore $store What backend to use for storage
-        * @param LoggerInterface $logger
-        * @param callable[] $clearStoreCallbacks To be called whenever the cache is cleared. Can be
-        *   used to clear other caches that depend on this one, such as ResourceLoader's
-        *   MessageBlobStore.
-        * @param LanguageNameUtils $langNameUtils
+        * @param array $conf
         * @throws MWException
         */
-       function __construct(
-               ServiceOptions $options,
-               LCStore $store,
-               LoggerInterface $logger,
-               array $clearStoreCallbacks,
-               LanguageNameUtils $langNameUtils
-       ) {
-               $options->assertRequiredOptions( self::$constructorOptions );
-
-               $this->options = $options;
-               $this->store = $store;
-               $this->logger = $logger;
-               $this->clearStoreCallbacks = $clearStoreCallbacks;
-               $this->langNameUtils = $langNameUtils;
-
-               // Keep this separate from $this->options so it can be mutable
-               $this->manualRecache = $options->get( 'manualRecache' );
+       function __construct( $conf ) {
+               global $wgCacheDirectory;
+
+               $this->conf = $conf;
+               $this->logger = LoggerFactory::getInstance( 'localisation' );
+
+               $directory = !empty( $conf['storeDirectory'] ) ? $conf['storeDirectory'] : $wgCacheDirectory;
+               $storeArg = [];
+               $storeArg['directory'] = $directory;
+
+               if ( !empty( $conf['storeClass'] ) ) {
+                       $storeClass = $conf['storeClass'];
+               } else {
+                       switch ( $conf['store'] ) {
+                               case 'files':
+                               case 'file':
+                                       $storeClass = LCStoreCDB::class;
+                                       break;
+                               case 'db':
+                                       $storeClass = LCStoreDB::class;
+                                       $storeArg['server'] = $conf['storeServer'] ?? [];
+                                       break;
+                               case 'array':
+                                       $storeClass = LCStoreStaticArray::class;
+                                       break;
+                               case 'detect':
+                                       if ( $directory ) {
+                                               $storeClass = LCStoreCDB::class;
+                                       } else {
+                                               $storeClass = LCStoreDB::class;
+                                               $storeArg['server'] = $conf['storeServer'] ?? [];
+                                       }
+                                       break;
+                               default:
+                                       throw new MWException(
+                                               'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
+                                       );
+                       }
+               }
+               $this->logger->debug( static::class . ": using store $storeClass" );
+
+               $this->store = new $storeClass( $storeArg );
+               foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
+                       if ( isset( $conf[$var] ) ) {
+                               $this->$var = $conf[$var];
+                       }
+               }
        }
 
        /**
@@ -400,7 +406,7 @@ class LocalisationCache {
         * @return bool
         */
        public function isExpired( $code ) {
-               if ( $this->options->get( 'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
+               if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
                        $this->logger->debug( __METHOD__ . "($code): forced reload" );
 
                        return true;
@@ -445,7 +451,7 @@ class LocalisationCache {
                $this->initialisedLangs[$code] = true;
 
                # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
-               if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
+               if ( !Language::isValidBuiltInCode( $code ) ) {
                        $this->initShallowFallback( $code, 'en' );
 
                        return;
@@ -453,7 +459,7 @@ class LocalisationCache {
 
                # Recache the data if necessary
                if ( !$this->manualRecache && $this->isExpired( $code ) ) {
-                       if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
+                       if ( Language::isSupportedLanguage( $code ) ) {
                                $this->recache( $code );
                        } elseif ( $code === 'en' ) {
                                throw new MWException( 'MessagesEn.php is missing.' );
@@ -691,7 +697,7 @@ class LocalisationCache {
                global $IP;
 
                // This reads in the PHP i18n file with non-messages l10n data
-               $fileName = $this->langNameUtils->getMessagesFileName( $code );
+               $fileName = Language::getMessagesFileName( $code );
                if ( !file_exists( $fileName ) ) {
                        $data = [];
                } else {
@@ -798,12 +804,14 @@ class LocalisationCache {
        public function getMessagesDirs() {
                global $IP;
 
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+               $messagesDirs = $config->get( 'MessagesDirs' );
                return [
                        'core' => "$IP/languages/i18n",
                        'exif' => "$IP/languages/i18n/exif",
                        'api' => "$IP/includes/api/i18n",
                        'oojs-ui' => "$IP/resources/lib/ooui/i18n",
-               ] + $this->options->get( 'MessagesDirs' );
+               ] + $messagesDirs;
        }
 
        /**
@@ -813,6 +821,8 @@ class LocalisationCache {
         * @throws MWException
         */
        public function recache( $code ) {
+               global $wgExtensionMessagesFiles;
+
                if ( !$code ) {
                        throw new MWException( "Invalid language code requested" );
                }
@@ -864,7 +874,7 @@ class LocalisationCache {
 
                # Load non-JSON localisation data for extensions
                $extensionData = array_fill_keys( $codeSequence, $initialData );
-               foreach ( $this->options->get( 'ExtensionMessagesFiles' ) as $extension => $fileName ) {
+               foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
                        if ( isset( $messageDirs[$extension] ) ) {
                                # This extension has JSON message data; skip the PHP shim
                                continue;
@@ -1028,9 +1038,8 @@ class LocalisationCache {
                # HACK: If using a null (i.e. disabled) storage backend, we
                # can't write to the MessageBlobStore either
                if ( !$this->store instanceof LCStoreNull ) {
-                       foreach ( $this->clearStoreCallbacks as $callback ) {
-                               $callback();
-                       }
+                       $blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
+                       $blobStore->clear();
                }
        }
 
@@ -1091,4 +1100,5 @@ class LocalisationCache {
                $this->store = new LCStoreNull;
                $this->manualRecache = false;
        }
+
 }
index db7141f..8b31f05 100644 (file)
@@ -186,7 +186,7 @@ class FileBackendGroup {
                                'mimeCallback' => [ $this, 'guessMimeInternal' ],
                                'obResetFunc' => 'wfResetOutputBuffers',
                                'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
-                               'tmpFileFactory' => MediaWikiServices::getInstance()->getTempFSFileFactory(),
+                               'tmpFileFactory' => $services->getTempFSFileFactory(),
                                'statusWrapper' => [ Status::class, 'wrap' ],
                                'wanCache' => $services->getMainWANObjectCache(),
                                'srvCache' => ObjectCache::getLocalServerInstance( 'hash' ),
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 06e1271..991ef79 100644 (file)
@@ -80,10 +80,10 @@ abstract class ImageGalleryBase extends ContextSource {
        public $mParser;
 
        /**
-        * @var Title Contextual title, used when images are being screened against
+        * @var Title|null Contextual title, used when images are being screened against
         *   the bad image list
         */
-       protected $contextTitle = false;
+       protected $contextTitle = null;
 
        /** @var array */
        protected $mAttribs = [];
@@ -363,7 +363,7 @@ abstract class ImageGalleryBase extends ContextSource {
        /**
         * Set the contextual title
         *
-        * @param Title $title Contextual title
+        * @param Title|null $title Contextual title
         */
        public function setContextTitle( $title ) {
                $this->contextTitle = $title;
@@ -372,12 +372,10 @@ abstract class ImageGalleryBase extends ContextSource {
        /**
         * Get the contextual title, if applicable
         *
-        * @return Title|bool Title or false
+        * @return Title|null
         */
        public function getContextTitle() {
-               return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title
-                       ? $this->contextTitle
-                       : false;
+               return $this->contextTitle;
        }
 
        /**
index 414222b..de15456 100644 (file)
@@ -412,17 +412,14 @@ abstract class Installer {
                // This will be overridden in the web installer with the user-specified language
                RequestContext::getMain()->setLanguage( 'en' );
 
+               // Disable the i18n cache
+               // TODO: manage LocalisationCache singleton in MediaWikiServices
+               Language::getLocalisationCache()->disableBackend();
+
                // Disable all global services, since we don't have any configuration yet!
                MediaWikiServices::disableStorageBackend();
 
                $mwServices = MediaWikiServices::getInstance();
-
-               // Disable i18n cache
-               $mwServices->getLocalisationCache()->disableBackend();
-
-               // Clear language cache so the old i18n cache doesn't sneak back in
-               Language::clearCaches();
-
                // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
                // SqlBagOStuff will then throw since we just disabled wfGetDB)
                $wgObjectCaches = $mwServices->getMainConfig()->get( 'ObjectCaches' );
index 7c39ded..01bb30e 100644 (file)
@@ -242,27 +242,6 @@ class SqliteInstaller extends DatabaseInstaller {
                $this->setVar( 'wgDBpassword', '' );
                $this->setupSchemaVars();
 
-               # Create the global cache DB
-               try {
-                       $conn = Database::factory(
-                               'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] );
-                       # @todo: don't duplicate objectcache definition, though it's very simple
-                       $sql =
-<<<EOT
-       CREATE TABLE IF NOT EXISTS objectcache (
-               keyname BLOB NOT NULL default '' PRIMARY KEY,
-               value BLOB,
-               exptime TEXT
-       )
-EOT;
-                       $conn->query( $sql );
-                       $conn->query( "CREATE INDEX IF NOT EXISTS exptime ON objectcache (exptime)" );
-                       $conn->query( "PRAGMA journal_mode=WAL" ); // this is permanent
-                       $conn->close();
-               } catch ( DBConnectionError $e ) {
-                       return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() );
-               }
-
                # Create the l10n cache DB
                try {
                        $conn = Database::factory(
diff --git a/includes/language/ConverterRule.php b/includes/language/ConverterRule.php
new file mode 100644 (file)
index 0000000..4a330ad
--- /dev/null
@@ -0,0 +1,498 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Language
+ */
+
+/**
+ * Parser for rules of language conversion, parse rules in -{ }- tag.
+ * @ingroup Language
+ * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
+ */
+class ConverterRule {
+       public $mText; // original text in -{text}-
+       public $mConverter; // LanguageConverter object
+       public $mRuleDisplay = '';
+       public $mRuleTitle = false;
+       public $mRules = ''; // string : the text of the rules
+       public $mRulesAction = 'none';
+       public $mFlags = [];
+       public $mVariantFlags = [];
+       public $mConvTable = [];
+       public $mBidtable = []; // array of the translation in each variant
+       public $mUnidtable = []; // array of the translation in each variant
+
+       /**
+        * @param string $text The text between -{ and }-
+        * @param LanguageConverter $converter
+        */
+       public function __construct( $text, $converter ) {
+               $this->mText = $text;
+               $this->mConverter = $converter;
+       }
+
+       /**
+        * Check if variants array in convert array.
+        *
+        * @param array|string $variants Variant language code
+        * @return string Translated text
+        */
+       public function getTextInBidtable( $variants ) {
+               $variants = (array)$variants;
+               if ( !$variants ) {
+                       return false;
+               }
+               foreach ( $variants as $variant ) {
+                       if ( isset( $this->mBidtable[$variant] ) ) {
+                               return $this->mBidtable[$variant];
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Parse flags with syntax -{FLAG| ... }-
+        * @private
+        */
+       function parseFlags() {
+               $text = $this->mText;
+               $flags = [];
+               $variantFlags = [];
+
+               $sepPos = strpos( $text, '|' );
+               if ( $sepPos !== false ) {
+                       $validFlags = $this->mConverter->mFlags;
+                       $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
+                       foreach ( $f as $ff ) {
+                               $ff = trim( $ff );
+                               if ( isset( $validFlags[$ff] ) ) {
+                                       $flags[$validFlags[$ff]] = true;
+                               }
+                       }
+                       $text = strval( substr( $text, $sepPos + 1 ) );
+               }
+
+               if ( !$flags ) {
+                       $flags['S'] = true;
+               } elseif ( isset( $flags['R'] ) ) {
+                       $flags = [ 'R' => true ];// remove other flags
+               } elseif ( isset( $flags['N'] ) ) {
+                       $flags = [ 'N' => true ];// remove other flags
+               } elseif ( isset( $flags['-'] ) ) {
+                       $flags = [ '-' => true ];// remove other flags
+               } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
+                       $flags['H'] = true;
+               } elseif ( isset( $flags['H'] ) ) {
+                       // replace A flag, and remove other flags except T
+                       $temp = [ '+' => true, 'H' => true ];
+                       if ( isset( $flags['T'] ) ) {
+                               $temp['T'] = true;
+                       }
+                       if ( isset( $flags['D'] ) ) {
+                               $temp['D'] = true;
+                       }
+                       $flags = $temp;
+               } else {
+                       if ( isset( $flags['A'] ) ) {
+                               $flags['+'] = true;
+                               $flags['S'] = true;
+                       }
+                       if ( isset( $flags['D'] ) ) {
+                               unset( $flags['S'] );
+                       }
+                       // try to find flags like "zh-hans", "zh-hant"
+                       // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
+                       $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
+                       if ( $variantFlags ) {
+                               $variantFlags = array_flip( $variantFlags );
+                               $flags = [];
+                       }
+               }
+               $this->mVariantFlags = $variantFlags;
+               $this->mRules = $text;
+               $this->mFlags = $flags;
+       }
+
+       /**
+        * Generate conversion table.
+        * @private
+        */
+       function parseRules() {
+               $rules = $this->mRules;
+               $bidtable = [];
+               $unidtable = [];
+               $variants = $this->mConverter->mVariants;
+               $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
+
+               // Split according to $varsep_pattern, but ignore semicolons from HTML entities
+               $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
+               $choice = preg_split( $varsep_pattern, $rules );
+               $choice = str_replace( "\x01", ';', $choice );
+
+               foreach ( $choice as $c ) {
+                       $v = explode( ':', $c, 2 );
+                       if ( count( $v ) != 2 ) {
+                               // syntax error, skip
+                               continue;
+                       }
+                       $to = trim( $v[1] );
+                       $v = trim( $v[0] );
+                       $u = explode( '=>', $v, 2 );
+                       $vv = $this->mConverter->validateVariant( $v );
+                       // if $to is empty (which is also used as $from in bidtable),
+                       // strtr() could return a wrong result.
+                       if ( count( $u ) == 1 && $to !== '' && $vv ) {
+                               $bidtable[$vv] = $to;
+                       } elseif ( count( $u ) == 2 ) {
+                               $from = trim( $u[0] );
+                               $v = trim( $u[1] );
+                               $vv = $this->mConverter->validateVariant( $v );
+                               // if $from is empty, strtr() could return a wrong result.
+                               if ( array_key_exists( $vv, $unidtable )
+                                       && !is_array( $unidtable[$vv] )
+                                       && $from !== ''
+                                       && $vv ) {
+                                       $unidtable[$vv] = [ $from => $to ];
+                               } elseif ( $from !== '' && $vv ) {
+                                       $unidtable[$vv][$from] = $to;
+                               }
+                       }
+                       // syntax error, pass
+                       if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
+                               $bidtable = [];
+                               $unidtable = [];
+                               break;
+                       }
+               }
+               $this->mBidtable = $bidtable;
+               $this->mUnidtable = $unidtable;
+       }
+
+       /**
+        * @private
+        *
+        * @return string
+        */
+       function getRulesDesc() {
+               $codesep = $this->mConverter->mDescCodeSep;
+               $varsep = $this->mConverter->mDescVarSep;
+               $text = '';
+               foreach ( $this->mBidtable as $k => $v ) {
+                       $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
+               }
+               foreach ( $this->mUnidtable as $k => $a ) {
+                       foreach ( $a as $from => $to ) {
+                               $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
+                                       "$codesep$to$varsep";
+                       }
+               }
+               return $text;
+       }
+
+       /**
+        * Parse rules conversion.
+        * @private
+        *
+        * @param string $variant
+        *
+        * @return string
+        */
+       function getRuleConvertedStr( $variant ) {
+               $bidtable = $this->mBidtable;
+               $unidtable = $this->mUnidtable;
+
+               if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
+                       return $this->mRules;
+               } else {
+                       // display current variant in bidirectional array
+                       $disp = $this->getTextInBidtable( $variant );
+                       // or display current variant in fallbacks
+                       if ( $disp === false ) {
+                               $disp = $this->getTextInBidtable(
+                                       $this->mConverter->getVariantFallbacks( $variant ) );
+                       }
+                       // or display current variant in unidirectional array
+                       if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
+                               $disp = array_values( $unidtable[$variant] )[0];
+                       }
+                       // or display first text under disable manual convert
+                       if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
+                               if ( count( $bidtable ) > 0 ) {
+                                       $disp = array_values( $bidtable )[0];
+                               } else {
+                                       $disp = array_values( array_values( $unidtable )[0] )[0];
+                               }
+                       }
+                       return $disp;
+               }
+       }
+
+       /**
+        * Similar to getRuleConvertedStr(), but this prefers to use original
+        * page title if $variant === $this->mConverter->mMainLanguageCode
+        * and may return false in this case (so this title conversion rule
+        * will be ignored and the original title is shown).
+        *
+        * @since 1.22
+        * @param string $variant The variant code to display page title in
+        * @return string|bool The converted title or false if just page name
+        */
+       function getRuleConvertedTitle( $variant ) {
+               if ( $variant === $this->mConverter->mMainLanguageCode ) {
+                       // If a string targeting exactly this variant is set,
+                       // use it. Otherwise, just return false, so the real
+                       // page name can be shown (and because variant === main,
+                       // there'll be no further automatic conversion).
+                       $disp = $this->getTextInBidtable( $variant );
+                       if ( $disp ) {
+                               return $disp;
+                       }
+                       if ( array_key_exists( $variant, $this->mUnidtable ) ) {
+                               $disp = array_values( $this->mUnidtable[$variant] )[0];
+                       }
+                       // Assigned above or still false.
+                       return $disp;
+               } else {
+                       return $this->getRuleConvertedStr( $variant );
+               }
+       }
+
+       /**
+        * Generate conversion table for all text.
+        * @private
+        */
+       function generateConvTable() {
+               // Special case optimisation
+               if ( !$this->mBidtable && !$this->mUnidtable ) {
+                       $this->mConvTable = [];
+                       return;
+               }
+
+               $bidtable = $this->mBidtable;
+               $unidtable = $this->mUnidtable;
+               $manLevel = $this->mConverter->mManualLevel;
+
+               $vmarked = [];
+               foreach ( $this->mConverter->mVariants as $v ) {
+                       /* for bidirectional array
+                               fill in the missing variants, if any,
+                               with fallbacks */
+                       if ( !isset( $bidtable[$v] ) ) {
+                               $variantFallbacks =
+                                       $this->mConverter->getVariantFallbacks( $v );
+                               $vf = $this->getTextInBidtable( $variantFallbacks );
+                               if ( $vf ) {
+                                       $bidtable[$v] = $vf;
+                               }
+                       }
+
+                       if ( isset( $bidtable[$v] ) ) {
+                               foreach ( $vmarked as $vo ) {
+                                       // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
+                                       // or -{H|zh:WordZh;zh-tw:WordTw}-
+                                       // or -{-|zh:WordZh;zh-tw:WordTw}-
+                                       // to introduce a custom mapping between
+                                       // words WordZh and WordTw in the whole text
+                                       if ( $manLevel[$v] == 'bidirectional' ) {
+                                               $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
+                                       }
+                                       if ( $manLevel[$vo] == 'bidirectional' ) {
+                                               $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
+                                       }
+                               }
+                               $vmarked[] = $v;
+                       }
+                       /* for unidirectional array fill to convert tables */
+                       if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
+                               && isset( $unidtable[$v] )
+                       ) {
+                               if ( isset( $this->mConvTable[$v] ) ) {
+                                       $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
+                               } else {
+                                       $this->mConvTable[$v] = $unidtable[$v];
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Parse rules and flags.
+        * @param string|null $variant Variant language code
+        */
+       public function parse( $variant = null ) {
+               if ( !$variant ) {
+                       $variant = $this->mConverter->getPreferredVariant();
+               }
+
+               $this->parseFlags();
+               $flags = $this->mFlags;
+
+               // convert to specified variant
+               // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
+               if ( $this->mVariantFlags ) {
+                       // check if current variant in flags
+                       if ( isset( $this->mVariantFlags[$variant] ) ) {
+                               // then convert <text to convert> to current language
+                               $this->mRules = $this->mConverter->autoConvert( $this->mRules,
+                                       $variant );
+                       } else {
+                               // if current variant no in flags,
+                               // then we check its fallback variants.
+                               $variantFallbacks =
+                                       $this->mConverter->getVariantFallbacks( $variant );
+                               if ( is_array( $variantFallbacks ) ) {
+                                       foreach ( $variantFallbacks as $variantFallback ) {
+                                               // if current variant's fallback exist in flags
+                                               if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
+                                                       // then convert <text to convert> to fallback language
+                                                       $this->mRules =
+                                                               $this->mConverter->autoConvert( $this->mRules,
+                                                                       $variantFallback );
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+                       $this->mFlags = $flags = [ 'R' => true ];
+               }
+
+               if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
+                       // decode => HTML entities modified by Sanitizer::removeHTMLtags
+                       $this->mRules = str_replace( '=&gt;', '=>', $this->mRules );
+                       $this->parseRules();
+               }
+               $rules = $this->mRules;
+
+               if ( !$this->mBidtable && !$this->mUnidtable ) {
+                       if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
+                               // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
+                               if ( $rules !== '' ) {
+                                       foreach ( $this->mConverter->mVariants as $v ) {
+                                               $this->mBidtable[$v] = $rules;
+                                       }
+                               }
+                       } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
+                               $this->mFlags = $flags = [ 'R' => true ];
+                       }
+               }
+
+               $this->mRuleDisplay = false;
+               foreach ( $flags as $flag => $unused ) {
+                       switch ( $flag ) {
+                               case 'R':
+                                       // if we don't do content convert, still strip the -{}- tags
+                                       $this->mRuleDisplay = $rules;
+                                       break;
+                               case 'N':
+                                       // process N flag: output current variant name
+                                       $ruleVar = trim( $rules );
+                                       $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
+                                       break;
+                               case 'D':
+                                       // process D flag: output rules description
+                                       $this->mRuleDisplay = $this->getRulesDesc();
+                                       break;
+                               case 'H':
+                                       // process H,- flag or T only: output nothing
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case '-':
+                                       $this->mRulesAction = 'remove';
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case '+':
+                                       $this->mRulesAction = 'add';
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case 'S':
+                                       $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
+                                       break;
+                               case 'T':
+                                       $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               default:
+                                       // ignore unknown flags (but see error case below)
+                       }
+               }
+               if ( $this->mRuleDisplay === false ) {
+                       $this->mRuleDisplay = '<span class="error">'
+                               . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
+                               . '</span>';
+               }
+
+               $this->generateConvTable();
+       }
+
+       /**
+        * Checks if there are conversion rules.
+        * @return bool
+        */
+       public function hasRules() {
+               return $this->mRules !== '';
+       }
+
+       /**
+        * Get display text on markup -{...}-
+        * @return string
+        */
+       public function getDisplay() {
+               return $this->mRuleDisplay;
+       }
+
+       /**
+        * Get converted title.
+        * @return string
+        */
+       public function getTitle() {
+               return $this->mRuleTitle;
+       }
+
+       /**
+        * Return how deal with conversion rules.
+        * @return string
+        */
+       public function getRulesAction() {
+               return $this->mRulesAction;
+       }
+
+       /**
+        * Get conversion table. (bidirectional and unidirectional
+        * conversion table)
+        * @return array
+        */
+       public function getConvTable() {
+               return $this->mConvTable;
+       }
+
+       /**
+        * Get conversion rules string.
+        * @return string
+        */
+       public function getRules() {
+               return $this->mRules;
+       }
+
+       /**
+        * Get conversion flags.
+        * @return array
+        */
+       public function getFlags() {
+               return $this->mFlags;
+       }
+}
index 1d2f0b4..7d954d3 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * Methods for dealing with language codes.
+ * @todo Move some of the code-related static methods out of Language into this class
  *
  * @since 1.29
  * @ingroup Language
diff --git a/includes/language/LanguageNameUtils.php b/includes/language/LanguageNameUtils.php
deleted file mode 100644 (file)
index 08d9ab3..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-/**
- * Internationalisation code.
- * See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more information.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * @defgroup Language Language
- */
-
-namespace MediaWiki\Languages;
-
-use HashBagOStuff;
-use Hooks;
-use MediaWiki\Config\ServiceOptions;
-use MediaWikiTitleCodec;
-use MWException;
-use Wikimedia\Assert\Assert;
-
-/**
- * @ingroup Language
- *
- * A service that provides utilities to do with language names and codes.
- *
- * @since 1.34
- */
-class LanguageNameUtils {
-       /**
-        * Return autonyms in getLanguageName(s).
-        */
-       const AUTONYMS = null;
-
-       /**
-        * Return all known languages in getLanguageName(s).
-        */
-       const ALL = 'all';
-
-       /**
-        * Return in getLanguageName(s) only the languages that are defined by MediaWiki.
-        */
-       const DEFINED = 'mw';
-
-       /**
-        * Return in getLanguageName(s) only the languages for which we have at least some localisation.
-        */
-       const SUPPORTED = 'mwfile';
-
-       /** @var ServiceOptions */
-       private $options;
-
-       /**
-        * Cache for language names
-        * @var HashBagOStuff|null
-        */
-       private $languageNameCache;
-
-       /**
-        * Cache for validity of language codes
-        * @var array
-        */
-       private $validCodeCache = [];
-
-       public static $constructorOptions = [
-               'ExtraLanguageNames',
-               'UsePigLatinVariant',
-       ];
-
-       /**
-        * @param ServiceOptions $options
-        */
-       public function __construct( ServiceOptions $options ) {
-               $options->assertRequiredOptions( self::$constructorOptions );
-               $this->options = $options;
-       }
-
-       /**
-        * Checks whether any localisation is available for that language tag in MediaWiki
-        * (MessagesXx.php or xx.json exists).
-        *
-        * @param string $code Language tag (in lower case)
-        * @return bool Whether language is supported
-        */
-       public function isSupportedLanguage( $code ) {
-               if ( !$this->isValidBuiltInCode( $code ) ) {
-                       return false;
-               }
-
-               if ( $code === 'qqq' ) {
-                       // Special code for internal use, not supported even though there is a qqq.json
-                       return false;
-               }
-
-               return is_readable( $this->getMessagesFileName( $code ) ) ||
-                       is_readable( $this->getJsonMessagesFileName( $code ) );
-       }
-
-       /**
-        * Returns true if a language code string is of a valid form, whether or not it exists. This
-        * includes codes which are used solely for customisation via the MediaWiki namespace.
-        *
-        * @param string $code
-        *
-        * @return bool
-        */
-       public function isValidCode( $code ) {
-               Assert::parameterType( 'string', $code, '$code' );
-               if ( !isset( $this->validCodeCache[$code] ) ) {
-                       // People think language codes are HTML-safe, so enforce it.  Ideally we should only
-                       // allow a-zA-Z0-9- but .+ and other chars are often used for {{int:}} hacks.  See bugs
-                       // T39564, T39587, T38938.
-                       $this->validCodeCache[$code] =
-                               // Protect against path traversal
-                               strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) &&
-                               !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
-               }
-               return $this->validCodeCache[$code];
-       }
-
-       /**
-        * Returns true if a language code is of a valid form for the purposes of internal customisation
-        * of MediaWiki, via Messages*.php or *.json.
-        *
-        * @param string $code
-        * @return bool
-        */
-       public function isValidBuiltInCode( $code ) {
-               Assert::parameterType( 'string', $code, '$code' );
-
-               return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
-       }
-
-       /**
-        * Returns true if a language code is an IETF tag known to MediaWiki.
-        *
-        * @param string $tag
-        *
-        * @return bool
-        */
-       public function isKnownLanguageTag( $tag ) {
-               // Quick escape for invalid input to avoid exceptions down the line when code tries to
-               // process tags which are not valid at all.
-               if ( !$this->isValidBuiltInCode( $tag ) ) {
-                       return false;
-               }
-
-               if ( isset( Data\Names::$names[$tag] ) || $this->getLanguageName( $tag, $tag ) !== '' ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * Get an array of language names, indexed by code.
-        * @param null|string $inLanguage Code of language in which to return the names
-        *   Use self::AUTONYMS for autonyms (native names)
-        * @param string $include One of:
-        *   self::ALL all available languages
-        *   self::DEFINED only if the language is defined in MediaWiki or wgExtraLanguageNames
-        *     (default)
-        *   self::SUPPORTED only if the language is in self::DEFINED *and* has a message file
-        * @return array Language code => language name (sorted by key)
-        */
-       public function getLanguageNames( $inLanguage = self::AUTONYMS, $include = self::DEFINED ) {
-               $cacheKey = $inLanguage === self::AUTONYMS ? 'null' : $inLanguage;
-               $cacheKey .= ":$include";
-               if ( !$this->languageNameCache ) {
-                       $this->languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
-               }
-
-               $ret = $this->languageNameCache->get( $cacheKey );
-               if ( !$ret ) {
-                       $ret = $this->getLanguageNamesUncached( $inLanguage, $include );
-                       $this->languageNameCache->set( $cacheKey, $ret );
-               }
-               return $ret;
-       }
-
-       /**
-        * Uncached helper for getLanguageNames
-        * @param null|string $inLanguage As getLanguageNames
-        * @param string $include As getLanguageNames
-        * @return array Language code => language name (sorted by key)
-        */
-       private function getLanguageNamesUncached( $inLanguage, $include ) {
-               // If passed an invalid language code to use, fallback to en
-               if ( $inLanguage !== self::AUTONYMS && !$this->isValidCode( $inLanguage ) ) {
-                       $inLanguage = 'en';
-               }
-
-               $names = [];
-
-               if ( $inLanguage !== self::AUTONYMS ) {
-                       # TODO: also include for self::AUTONYMS, when this code is more efficient
-                       Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
-               }
-
-               $mwNames = $this->options->get( 'ExtraLanguageNames' ) + Data\Names::$names;
-               if ( $this->options->get( 'UsePigLatinVariant' ) ) {
-                       // Pig Latin (for variant development)
-                       $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
-               }
-
-               foreach ( $mwNames as $mwCode => $mwName ) {
-                       # - Prefer own MediaWiki native name when not using the hook
-                       # - For other names just add if not added through the hook
-                       if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
-                               $names[$mwCode] = $mwName;
-                       }
-               }
-
-               if ( $include === self::ALL ) {
-                       ksort( $names );
-                       return $names;
-               }
-
-               $returnMw = [];
-               $coreCodes = array_keys( $mwNames );
-               foreach ( $coreCodes as $coreCode ) {
-                       $returnMw[$coreCode] = $names[$coreCode];
-               }
-
-               if ( $include === self::SUPPORTED ) {
-                       $namesMwFile = [];
-                       # We do this using a foreach over the codes instead of a directory loop so that messages
-                       # files in extensions will work correctly.
-                       foreach ( $returnMw as $code => $value ) {
-                               if ( is_readable( $this->getMessagesFileName( $code ) ) ||
-                                       is_readable( $this->getJsonMessagesFileName( $code ) )
-                               ) {
-                                       $namesMwFile[$code] = $names[$code];
-                               }
-                       }
-
-                       ksort( $namesMwFile );
-                       return $namesMwFile;
-               }
-
-               ksort( $returnMw );
-               # self::DEFINED option; default if it's not one of the other two options
-               # (self::ALL/self::SUPPORTED)
-               return $returnMw;
-       }
-
-       /**
-        * @param string $code The code of the language for which to get the name
-        * @param null|string $inLanguage Code of language in which to return the name (self::AUTONYMS
-        *   for autonyms)
-        * @param string $include See getLanguageNames(), except this defaults to self::ALL instead of
-        *   self::DEFINED
-        * @return string Language name or empty
-        * @since 1.20
-        */
-       public function getLanguageName( $code, $inLanguage = self::AUTONYMS, $include = self::ALL ) {
-               $code = strtolower( $code );
-               $array = $this->getLanguageNames( $inLanguage, $include );
-               return $array[$code] ?? '';
-       }
-
-       /**
-        * Get the name of a file for a certain language code
-        * @param string $prefix Prepend this to the filename
-        * @param string $code Language code
-        * @param string $suffix Append this to the filename
-        * @throws MWException
-        * @return string $prefix . $mangledCode . $suffix
-        */
-       public function getFileName( $prefix, $code, $suffix = '.php' ) {
-               if ( !$this->isValidBuiltInCode( $code ) ) {
-                       throw new MWException( "Invalid language code \"$code\"" );
-               }
-
-               return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
-       }
-
-       /**
-        * @param string $code
-        * @return string
-        */
-       public function getMessagesFileName( $code ) {
-               global $IP;
-               $file = $this->getFileName( "$IP/languages/messages/Messages", $code, '.php' );
-               Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
-               return $file;
-       }
-
-       /**
-        * @param string $code
-        * @return string
-        * @throws MWException
-        */
-       public function getJsonMessagesFileName( $code ) {
-               global $IP;
-
-               if ( !$this->isValidBuiltInCode( $code ) ) {
-                       throw new MWException( "Invalid language code \"$code\"" );
-               }
-
-               return "$IP/languages/i18n/$code.json";
-       }
-}
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 5c172d6..066d4b4 100644 (file)
@@ -970,17 +970,7 @@ class LoadBalancer implements ILoadBalancer {
                $serverIndex = $conn->getLBInfo( 'serverIndex' );
                $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
                if ( $serverIndex === null || $refCount === null ) {
-                       /**
-                        * This can happen in code like:
-                        *   foreach ( $dbs as $db ) {
-                        *     $conn = $lb->getConnection( $lb::DB_REPLICA, [], $db );
-                        *     ...
-                        *     $lb->reuseConnection( $conn );
-                        *   }
-                        * When a connection to the local DB is opened in this way, reuseConnection()
-                        * should be ignored
-                        */
-                       return;
+                       return; // non-foreign connection; no domain-use tracking to update
                } elseif ( $conn instanceof DBConnRef ) {
                        // DBConnRef already handles calling reuseConnection() and only passes the live
                        // Database instance to this method. Any caller passing in a DBConnRef is broken.
@@ -1312,7 +1302,7 @@ class LoadBalancer implements ILoadBalancer {
 
                // Create a live connection object
                try {
-                       $db = Database::factory( $server['type'], $server );
+                       $conn = Database::factory( $server['type'], $server );
                        // Log when many connection are made on requests
                        ++$this->connectionCounter;
                        $currentConnCount = $this->getCurrentConnectionCount();
@@ -1325,28 +1315,28 @@ class LoadBalancer implements ILoadBalancer {
                } catch ( DBConnectionError $e ) {
                        // FIXME: This is probably the ugliest thing I have ever done to
                        // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
-                       $db = $e->db;
+                       $conn = $e->db;
                }
 
-               $db->setLBInfo( $server );
-               $db->setLazyMasterHandle(
-                       $this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
+               $conn->setLBInfo( $server );
+               $conn->setLazyMasterHandle(
+                       $this->getLazyConnectionRef( self::DB_MASTER, [], $conn->getDomainID() )
                );
-               $db->setTableAliases( $this->tableAliases );
-               $db->setIndexAliases( $this->indexAliases );
+               $conn->setTableAliases( $this->tableAliases );
+               $conn->setIndexAliases( $this->indexAliases );
 
                if ( $server['serverIndex'] === $this->getWriterIndex() ) {
                        if ( $this->trxRoundId !== false ) {
-                               $this->applyTransactionRoundFlags( $db );
+                               $this->applyTransactionRoundFlags( $conn );
                        }
                        foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
-                               $db->setTransactionListener( $name, $callback );
+                               $conn->setTransactionListener( $name, $callback );
                        }
                }
 
                $this->lazyLoadReplicationPositions(); // session consistency
 
-               return $db;
+               return $conn;
        }
 
        /**
@@ -2283,9 +2273,9 @@ class LoadBalancer implements ILoadBalancer {
                ) );
 
                // Update the prefix for all local connections...
-               $this->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
-                       if ( !$db->getLBInfo( 'foreign' ) ) {
-                               $db->tablePrefix( $prefix );
+               $this->forEachOpenConnection( function ( IDatabase $conn ) use ( $prefix ) {
+                       if ( !$conn->getLBInfo( 'foreign' ) ) {
+                               $conn->tablePrefix( $prefix );
                        }
                } );
        }
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 db4838f..e634edc 100644 (file)
@@ -31,6 +31,7 @@ use Wikimedia\Rdbms\DBConnectionError;
 use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\LoadBalancer;
 use Wikimedia\ScopedCallback;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 use Wikimedia\WaitConditionLoop;
 
 /**
@@ -190,28 +191,30 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                $type = $info['type'] ?? 'mysql';
                                $host = $info['host'] ?? '[unknown]';
                                $this->logger->debug( __CLASS__ . ": connecting to $host" );
-                               $db = Database::factory( $type, $info );
-                               $db->clearFlag( DBO_TRX ); // auto-commit mode
-                               $this->conns[$shardIndex] = $db;
+                               $conn = Database::factory( $type, $info );
+                               $conn->clearFlag( DBO_TRX ); // auto-commit mode
+                               $this->conns[$shardIndex] = $conn;
                        }
-                       $db = $this->conns[$shardIndex];
+                       $conn = $this->conns[$shardIndex];
                } else {
                        // Use the main LB database
                        $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
                        $index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
-                       if ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' ) {
-                               // Keep a separate connection to avoid contention and deadlocks
-                               $db = $lb->getConnectionRef( $index, [], false, $lb::CONN_TRX_AUTOCOMMIT );
-                       } else {
-                               // However, SQLite has the opposite behavior due to DB-level locking.
-                               // Stock sqlite MediaWiki installs use a separate sqlite cache DB instead.
-                               $db = $lb->getConnectionRef( $index );
+                       // If the RDBMS has row-level locking, use the autocommit connection to avoid
+                       // contention and deadlocks. Do not do this if it only has DB-level locking since
+                       // that would just cause deadlocks.
+                       $attribs = $lb->getServerAttributes( $lb->getWriterIndex() );
+                       $flags = $attribs[Database::ATTR_DB_LEVEL_LOCKING] ? 0 : $lb::CONN_TRX_AUTOCOMMIT;
+                       $conn = $lb->getMaintenanceConnectionRef( $index, [], false, $flags );
+                       // Automatically create the objectcache table for sqlite as needed
+                       if ( $conn->getType() === 'sqlite' ) {
+                               $this->initSqliteDatabase( $conn );
                        }
                }
 
-               $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
+               $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $conn ) );
 
-               return $db;
+               return $conn;
        }
 
        /**
@@ -582,10 +585,10 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @param string $exptime
         * @return bool
         */
-       private function isExpired( $db, $exptime ) {
+       private function isExpired( IDatabase $db, $exptime ) {
                return (
                        $exptime != $this->getMaxDateTime( $db ) &&
-                       wfTimestamp( TS_UNIX, $exptime ) < $this->getCurrentTime()
+                       ConvertibleTimestamp::convert( TS_UNIX, $exptime ) < $this->getCurrentTime()
                );
        }
 
@@ -687,7 +690,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                $serversDoneCount = 0,
                &$keysDeletedCount = 0
        ) {
-               $cutoffUnix = wfTimestamp( TS_UNIX, $timestamp );
+               $cutoffUnix = ConvertibleTimestamp::convert( TS_UNIX, $timestamp );
                $shardIndexes = range( 0, $this->numTableShards - 1 );
                shuffle( $shardIndexes );
 
@@ -709,7 +712,8 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                if ( $res->numRows() ) {
                                        $row = $res->current();
                                        if ( $lag === null ) {
-                                               $lag = max( $cutoffUnix - wfTimestamp( TS_UNIX, $row->exptime ), 1 );
+                                               $rowExpUnix = ConvertibleTimestamp::convert( TS_UNIX, $row->exptime );
+                                               $lag = max( $cutoffUnix - $rowExpUnix, 1 );
                                        }
 
                                        $keys = [];
@@ -731,7 +735,8 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
 
                                if ( is_callable( $progressCallback ) ) {
                                        if ( $lag ) {
-                                               $remainingLag = $cutoffUnix - wfTimestamp( TS_UNIX, $continue );
+                                               $continueUnix = ConvertibleTimestamp::convert( TS_UNIX, $continue );
+                                               $remainingLag = $cutoffUnix - $continueUnix;
                                                $processedLag = max( $lag - $remainingLag, 0 );
                                                $doneRatio = ( $numShardsDone + $processedLag / $lag ) / $this->numTableShards;
                                        } else {
@@ -948,20 +953,47 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        }
 
        /**
-        * Create shard tables. For use from eval.php.
+        * @param IMaintainableDatabase $db
+        * @throws DBError
+        */
+       private function initSqliteDatabase( IMaintainableDatabase $db ) {
+               if ( $db->tableExists( 'objectcache' ) ) {
+                       return;
+               }
+               // Use one table for SQLite; sharding does not seem to have much benefit
+               $db->query( "PRAGMA journal_mode=WAL" ); // this is permanent
+               $db->startAtomic( __METHOD__ ); // atomic DDL
+               try {
+                       $encTable = $db->tableName( 'objectcache' );
+                       $encExptimeIndex = $db->addIdentifierQuotes( $db->tablePrefix() . 'exptime' );
+                       $db->query(
+                               "CREATE TABLE $encTable (\n" .
+                               "       keyname BLOB NOT NULL default '' PRIMARY KEY,\n" .
+                               "       value BLOB,\n" .
+                               "       exptime TEXT\n" .
+                               ")",
+                               __METHOD__
+                       );
+                       $db->query( "CREATE INDEX $encExptimeIndex ON $encTable (exptime)" );
+                       $db->endAtomic( __METHOD__ );
+               } catch ( DBError $e ) {
+                       $db->rollback( __METHOD__ );
+                       throw $e;
+               }
+       }
+
+       /**
+        * Create the shard tables on all databases (e.g. via eval.php/shell.php)
         */
        public function createTables() {
                for ( $shardIndex = 0; $shardIndex < $this->numServerShards; $shardIndex++ ) {
                        $db = $this->getConnection( $shardIndex );
-                       if ( $db->getType() !== 'mysql' ) {
-                               throw new MWException( __METHOD__ . ' is not supported on this DB server' );
-                       }
-
-                       for ( $i = 0; $i < $this->numTableShards; $i++ ) {
-                               $db->query(
-                                       'CREATE TABLE ' . $db->tableName( $this->getTableNameByShard( $i ) ) .
-                                       ' LIKE ' . $db->tableName( 'objectcache' ),
-                                       __METHOD__ );
+                       if ( in_array( $db->getType(), [ 'mysql', 'postgres' ], true ) ) {
+                               for ( $i = 0; $i < $this->numTableShards; $i++ ) {
+                                       $encBaseTable = $db->tableName( 'objectcache' );
+                                       $encShardTable = $db->tableName( $this->getTableNameByShard( $i ) );
+                                       $db->query( "CREATE TABLE $encShardTable LIKE $encBaseTable" );
+                               }
                        }
                }
        }
index 04021cc..472bcdd 100644 (file)
  * @ingroup Pager
  */
 
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Navigation\PrevNextNavigationRenderer;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IResultWrapper;
 
 /**
  * IndexPager is an efficient pager which uses a (roughly unique) index in the
@@ -157,7 +159,10 @@ abstract class IndexPager extends ContextSource implements Pager {
         */
        public $mResult;
 
-       public function __construct( IContextSource $context = null ) {
+       /** @var LinkRenderer */
+       private $linkRenderer;
+
+       public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
                if ( $context ) {
                        $this->setContext( $context );
                }
@@ -209,6 +214,7 @@ abstract class IndexPager extends ContextSource implements Pager {
                                ? $dir[$this->mOrderType]
                                : $dir;
                }
+               $this->linkRenderer = $linkRenderer;
        }
 
        /**
@@ -526,9 +532,9 @@ abstract class IndexPager extends ContextSource implements Pager {
                        $attrs['class'] = "mw-{$type}link";
                }
 
-               return Linker::linkKnown(
+               return $this->getLinkRenderer()->makeKnownLink(
                        $this->getTitle(),
-                       $text,
+                       new HtmlArmor( $text ),
                        $attrs,
                        $query + $this->getDefaultQuery()
                );
@@ -804,4 +810,11 @@ abstract class IndexPager extends ContextSource implements Pager {
 
                return $prevNext->buildPrevNextNavigation( $title, $offset, $limit, $query,  $atend );
        }
+
+       protected function getLinkRenderer() {
+               if ( $this->linkRenderer === null ) {
+                        $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               }
+               return $this->linkRenderer;
+       }
 }
index d94104b..f611699 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Pager
  */
 
+use MediaWiki\Linker\LinkRenderer;
+
 /**
  * Table-based display with a user-selectable sort order
  * @ingroup Pager
@@ -32,7 +34,7 @@ abstract class TablePager extends IndexPager {
        /** @var stdClass */
        protected $mCurrentRow;
 
-       public function __construct( IContextSource $context = null ) {
+       public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
                if ( $context ) {
                        $this->setContext( $context );
                }
@@ -49,7 +51,8 @@ abstract class TablePager extends IndexPager {
                        $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
                } /* Else leave it at whatever the class default is */
 
-               parent::__construct();
+               // Parent constructor needs mSort set, so we call it last
+               parent::__construct( null, $linkRenderer );
        }
 
        /**
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;
index c3af88f..8eecbcc 100644 (file)
@@ -151,8 +151,6 @@ class PasswordPolicyChecks {
                global $wgPopularPasswordFile, $wgSitename;
                $status = Status::newGood();
                if ( $policyVal > 0 ) {
-                       wfDeprecated( __METHOD__, '1.33' );
-
                        $langEn = Language::factory( 'en' );
                        $passwordKey = $langEn->lc( trim( $password ) );
 
index 001c975..00c2903 100644 (file)
@@ -232,7 +232,10 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        } elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) {
                                $info['default'] = $globalDefault;
                        } else {
-                               throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
+                               $globalDefault = json_encode( $globalDefault );
+                               throw new MWException(
+                                       "Default '$globalDefault' is invalid for preference $name of user $user"
+                               );
                        }
                }
 
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 1954e85..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;
-
-       /**
-        * A function returning a set of extension data.
-        * @var Closure|null
-        */
-       protected $extensionData;
+       public function __construct() {
+               if ( self::class === static::class ) {
+                       wfDeprecated( __METHOD__, '1.34' );
+               }
+       }
 
        /**
         * Return a new SearchResult and initializes it with a title.
@@ -65,216 +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;
-       }
-
-       /**
-        * Get the extension data as:
-        * augmentor name => data
-        * @return array[]
-        */
-       public function getExtensionData() {
-               if ( $this->extensionData ) {
-                       return call_user_func( $this->extensionData );
-               } else {
-                       return [];
-               }
-       }
-
-       /**
-        * Set extension data for this result.
-        * The data is:
-        * augmentor name => data
-        * @param Closure|array $extensionData Takes no arguments, returns
-        *  either array of extension data or null.
-        */
-       public function setExtensionData( $extensionData ) {
-               if ( $extensionData instanceof Closure ) {
-                       $this->extensionData = $extensionData;
-               } elseif ( is_array( $extensionData ) ) {
-                       wfDeprecated( __METHOD__ . ' with array argument', '1.32' );
-                       $this->extensionData = function () use ( $extensionData ) {
-                               return $extensionData;
-                       };
-               } else {
-                       $type = is_object( $extensionData )
-                               ? get_class( $extensionData )
-                               : gettype( $extensionData );
-                       throw new \InvalidArgumentException(
-                               __METHOD__ . " must be called with Closure|array, but received $type" );
-               }
-       }
 }
diff --git a/includes/search/SearchResultTrait.php b/includes/search/SearchResultTrait.php
new file mode 100644 (file)
index 0000000..9a0df25
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Trait for SearchResult subclasses to share non-obvious behaviors or methods
+ * that rarely specialized
+ */
+trait SearchResultTrait {
+       /**
+        * A function returning a set of extension data.
+        * @var Closure|null
+        */
+       protected $extensionData;
+
+       /**
+        * Get the extension data as:
+        * augmentor name => data
+        * @return array[]
+        */
+       public function getExtensionData() {
+               if ( $this->extensionData ) {
+                       return call_user_func( $this->extensionData );
+               } else {
+                       return [];
+               }
+       }
+
+       /**
+        * Set extension data for this result.
+        * The data is:
+        * augmentor name => data
+        * @param Closure|array $extensionData Takes no arguments, returns
+        *  either array of extension data or null.
+        */
+       public function setExtensionData( $extensionData ) {
+               if ( $extensionData instanceof Closure ) {
+                       $this->extensionData = $extensionData;
+               } elseif ( is_array( $extensionData ) ) {
+                       wfDeprecated( __METHOD__ . ' with array argument', '1.32' );
+                       $this->extensionData = function () use ( $extensionData ) {
+                               return $extensionData;
+                       };
+               } else {
+                       $type = is_object( $extensionData )
+                               ? get_class( $extensionData )
+                               : gettype( $extensionData );
+                       throw new \InvalidArgumentException(
+                               __METHOD__ . " must be called with Closure|array, but received $type" );
+               }
+       }
+}
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 2fa8fab..3893e92 100644 (file)
@@ -766,6 +766,33 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                }
        }
 
+       /**
+        * @see $wgRCLinkDays in DefaultSettings.php.
+        * @see $wgRCFilterByAge in DefaultSettings.php.
+        * @return int[]
+        */
+       protected function getLinkDays() {
+               $linkDays = $this->getConfig()->get( 'RCLinkDays' );
+               $filterByAge = $this->getConfig()->get( 'RCFilterByAge' );
+               $maxAge = $this->getConfig()->get( 'RCMaxAge' );
+               if ( $filterByAge ) {
+                       // Trim it to only links which are within $wgRCMaxAge.
+                       // Note that we allow one link higher than the max for things like
+                       // "age 56 days" being accessible through the "60 days" link.
+                       sort( $linkDays );
+
+                       $maxAgeDays = $maxAge / ( 3600 * 24 );
+                       foreach ( $linkDays as $i => $days ) {
+                               if ( $days >= $maxAgeDays ) {
+                                       array_splice( $linkDays, $i + 1 );
+                                       break;
+                               }
+                       }
+               }
+
+               return $linkDays;
+       }
+
        /**
         * Include the modules and configuration for the RCFilters app.
         * Conditional on the user having the feature enabled.
@@ -798,7 +825,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                        'maxDays' => (int)$this->getConfig()->get( 'RCMaxAge' ) / ( 24 * 3600 ), // Translate to days
                                        'limitArray' => $this->getConfig()->get( 'RCLinkLimits' ),
                                        'limitDefault' => $this->getDefaultLimit(),
-                                       'daysArray' => $this->getConfig()->get( 'RCLinkDays' ),
+                                       'daysArray' => $this->getLinkDays(),
                                        'daysDefault' => $this->getDefaultDays(),
                                ]
                        );
index c124c14..6c328da 100644 (file)
@@ -24,7 +24,7 @@ class SpecialNewSection extends RedirectSpecialPage {
        public function __construct() {
                parent::__construct( 'NewSection' );
                $this->mAllowedRedirectParams = [ 'preloadtitle', 'nosummary', 'editintro',
-                       'preload', 'preloadparams[]', 'summary' ];
+                       'preload', 'preloadparams', 'summary' ];
        }
 
        /**
@@ -43,6 +43,7 @@ class SpecialNewSection extends RedirectSpecialPage {
        protected function showNoRedirectPage() {
                $this->setHeaders();
                $this->outputHeader();
+               $this->addHelpLink( 'Help:New section' );
                $this->showForm();
        }
 
index 6949c61..30f4655 100644 (file)
@@ -847,7 +847,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                sort( $linkLimits );
                $linkLimits = array_unique( $linkLimits );
 
-               $linkDays = $config->get( 'RCLinkDays' );
+               $linkDays = $this->getLinkDays();
                $linkDays[] = $options['days'];
                sort( $linkDays );
                $linkDays = array_unique( $linkDays );
index 76e2ab7..45d77ce 100644 (file)
@@ -235,7 +235,7 @@ class AllMessagesTablePager extends TablePager {
        }
 
        function formatValue( $field, $value ) {
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                switch ( $field ) {
                        case 'am_title' :
                                $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
@@ -256,8 +256,7 @@ class AllMessagesTablePager extends TablePager {
                                        $title = $linkRenderer->makeKnownLink( $title, $this->getLanguage()->lcfirst( $value ) );
                                } else {
                                        $title = $linkRenderer->makeBrokenLink(
-                                               $title,
-                                               $this->getLanguage()->lcfirst( $value )
+                                               $title, $this->getLanguage()->lcfirst( $value )
                                        );
                                }
                                if ( $this->mCurrentRow->am_talk_exists ) {
index 01aed22..77b7326 100644 (file)
@@ -45,9 +45,9 @@ class BlockListPager extends TablePager {
         * @param array $conds
         */
        public function __construct( $page, $conds ) {
+               parent::__construct( $page->getContext(), $page->getLinkRenderer() );
                $this->conds = $conds;
                $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
-               parent::__construct( $page->getContext() );
        }
 
        function getFieldNames() {
@@ -97,7 +97,7 @@ class BlockListPager extends TablePager {
 
                $formatted = '';
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                switch ( $name ) {
                        case 'ipb_timestamp':
@@ -250,7 +250,7 @@ class BlockListPager extends TablePager {
         */
        private function getRestrictionListHTML( stdClass $row ) {
                $items = [];
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                foreach ( $this->restrictions as $restriction ) {
                        if ( $restriction->getBlockId() !== (int)$row->ipb_id ) {
index 7db90c1..db2ee38 100644 (file)
@@ -25,11 +25,6 @@ use MediaWiki\Linker\LinkRenderer;
  */
 class CategoryPager extends AlphabeticPager {
 
-       /**
-        * @var LinkRenderer
-        */
-       protected $linkRenderer;
-
        /**
         * @param IContextSource $context
         * @param string $from
@@ -37,15 +32,13 @@ class CategoryPager extends AlphabeticPager {
         */
        public function __construct( IContextSource $context, $from, LinkRenderer $linkRenderer
        ) {
-               parent::__construct( $context );
+               parent::__construct( $context, $linkRenderer );
                $from = str_replace( ' ', '_', $from );
                if ( $from !== '' ) {
                        $from = Title::capitalize( $from, NS_CATEGORY );
                        $this->setOffset( $from );
                        $this->setIncludeOffset( true );
                }
-
-               $this->linkRenderer = $linkRenderer;
        }
 
        function getQueryInfo() {
@@ -85,7 +78,7 @@ class CategoryPager extends AlphabeticPager {
        function formatRow( $result ) {
                $title = new TitleValue( NS_CATEGORY, $result->cat_title );
                $text = $title->getText();
-               $link = $this->linkRenderer->makeLink( $title, $text );
+               $link = $this->getLinkRenderer()->makeLink( $title, $text );
 
                $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
                return Html::rawElement( 'li', null, $this->getLanguage()->specialList( $link, $count ) ) . "\n";
index 9ac7df5..1b0c59a 100644 (file)
@@ -621,7 +621,7 @@ class ContribsPager extends RangeChronologicalPager {
                $classes = [];
                $attribs = [];
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                $page = null;
                // Create a title for the revision if possible
index 2f40ace..7dbfae8 100644 (file)
@@ -289,7 +289,7 @@ class DeletedContribsPager extends IndexPager {
        function formatRevisionRow( $row ) {
                $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                $rev = new Revision( [
                        'title' => $page,
index 2d3b6b2..81b7808 100644 (file)
@@ -54,6 +54,7 @@ class ImageListPager extends TablePager {
                $including = false, $showAll = false
        ) {
                $this->setContext( $context );
+
                $this->mIncluding = $including;
                $this->mShowAll = $showAll;
 
@@ -95,7 +96,7 @@ class ImageListPager extends TablePager {
                        $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
                }
 
-               parent::__construct( $context );
+               parent::__construct();
        }
 
        /**
@@ -437,7 +438,7 @@ class ImageListPager extends TablePager {
         */
        function formatValue( $field, $value ) {
                $services = MediaWikiServices::getInstance();
-               $linkRenderer = $services->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                switch ( $field ) {
                        case 'thumb':
                                $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
@@ -478,7 +479,7 @@ class ImageListPager extends TablePager {
 
                                        // Add delete links if allowed
                                        // From https://github.com/Wikia/app/pull/3859
-                                       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+                                       $permissionManager = $services->getPermissionManager();
 
                                        if ( $permissionManager->userCan( 'delete', $this->getUser(), $filePage ) ) {
                                                $deleteMsg = $this->msg( 'listfiles-delete' )->text();
index ed86e54..57db8b3 100644 (file)
@@ -194,7 +194,7 @@ class NewFilesPager extends RangeChronologicalPager {
                $user = User::newFromId( $row->img_user );
 
                $title = Title::makeTitle( NS_FILE, $name );
-               $ul = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
+               $ul = $this->getLinkRenderer()->makeLink(
                        $user->getUserPage(),
                        $user->getName()
                );
index 5583842..747dea2 100644 (file)
@@ -26,11 +26,6 @@ class ProtectedPagesPager extends TablePager {
        public $mConds;
        private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
 
-       /**
-        * @var LinkRenderer
-        */
-       private $linkRenderer;
-
        /**
         * @param SpecialPage $form
         * @param array $conds
@@ -48,6 +43,7 @@ class ProtectedPagesPager extends TablePager {
                $sizetype, $size, $indefonly, $cascadeonly, $noredirect,
                LinkRenderer $linkRenderer
        ) {
+               parent::__construct( $form->getContext(), $linkRenderer );
                $this->mConds = $conds;
                $this->type = $type ?: 'edit';
                $this->level = $level;
@@ -57,8 +53,6 @@ class ProtectedPagesPager extends TablePager {
                $this->indefonly = (bool)$indefonly;
                $this->cascadeonly = (bool)$cascadeonly;
                $this->noredirect = (bool)$noredirect;
-               $this->linkRenderer = $linkRenderer;
-               parent::__construct( $form->getContext() );
        }
 
        function preprocessResults( $result ) {
@@ -119,6 +113,7 @@ class ProtectedPagesPager extends TablePager {
        function formatValue( $field, $value ) {
                /** @var object $row */
                $row = $this->mCurrentRow;
+               $linkRenderer = $this->getLinkRenderer();
 
                switch ( $field ) {
                        case 'log_timestamp':
@@ -148,7 +143,7 @@ class ProtectedPagesPager extends TablePager {
                                                )
                                        );
                                } else {
-                                       $formatted = $this->linkRenderer->makeLink( $title );
+                                       $formatted = $linkRenderer->makeLink( $title );
                                }
                                if ( !is_null( $row->page_len ) ) {
                                        $formatted .= $this->getLanguage()->getDirMark() .
@@ -165,7 +160,7 @@ class ProtectedPagesPager extends TablePager {
                                        $value, /* User preference timezone */true ) );
                                $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
                                if ( $this->getUser()->isAllowed( 'protect' ) && $title ) {
-                                       $changeProtection = $this->linkRenderer->makeKnownLink(
+                                       $changeProtection = $linkRenderer->makeKnownLink(
                                                $title,
                                                $this->msg( 'protect_change' )->text(),
                                                [],
diff --git a/languages/ConverterRule.php b/languages/ConverterRule.php
deleted file mode 100644 (file)
index 4a330ad..0000000
+++ /dev/null
@@ -1,498 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Parser for rules of language conversion, parse rules in -{ }- tag.
- * @ingroup Language
- * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
- */
-class ConverterRule {
-       public $mText; // original text in -{text}-
-       public $mConverter; // LanguageConverter object
-       public $mRuleDisplay = '';
-       public $mRuleTitle = false;
-       public $mRules = ''; // string : the text of the rules
-       public $mRulesAction = 'none';
-       public $mFlags = [];
-       public $mVariantFlags = [];
-       public $mConvTable = [];
-       public $mBidtable = []; // array of the translation in each variant
-       public $mUnidtable = []; // array of the translation in each variant
-
-       /**
-        * @param string $text The text between -{ and }-
-        * @param LanguageConverter $converter
-        */
-       public function __construct( $text, $converter ) {
-               $this->mText = $text;
-               $this->mConverter = $converter;
-       }
-
-       /**
-        * Check if variants array in convert array.
-        *
-        * @param array|string $variants Variant language code
-        * @return string Translated text
-        */
-       public function getTextInBidtable( $variants ) {
-               $variants = (array)$variants;
-               if ( !$variants ) {
-                       return false;
-               }
-               foreach ( $variants as $variant ) {
-                       if ( isset( $this->mBidtable[$variant] ) ) {
-                               return $this->mBidtable[$variant];
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Parse flags with syntax -{FLAG| ... }-
-        * @private
-        */
-       function parseFlags() {
-               $text = $this->mText;
-               $flags = [];
-               $variantFlags = [];
-
-               $sepPos = strpos( $text, '|' );
-               if ( $sepPos !== false ) {
-                       $validFlags = $this->mConverter->mFlags;
-                       $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
-                       foreach ( $f as $ff ) {
-                               $ff = trim( $ff );
-                               if ( isset( $validFlags[$ff] ) ) {
-                                       $flags[$validFlags[$ff]] = true;
-                               }
-                       }
-                       $text = strval( substr( $text, $sepPos + 1 ) );
-               }
-
-               if ( !$flags ) {
-                       $flags['S'] = true;
-               } elseif ( isset( $flags['R'] ) ) {
-                       $flags = [ 'R' => true ];// remove other flags
-               } elseif ( isset( $flags['N'] ) ) {
-                       $flags = [ 'N' => true ];// remove other flags
-               } elseif ( isset( $flags['-'] ) ) {
-                       $flags = [ '-' => true ];// remove other flags
-               } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
-                       $flags['H'] = true;
-               } elseif ( isset( $flags['H'] ) ) {
-                       // replace A flag, and remove other flags except T
-                       $temp = [ '+' => true, 'H' => true ];
-                       if ( isset( $flags['T'] ) ) {
-                               $temp['T'] = true;
-                       }
-                       if ( isset( $flags['D'] ) ) {
-                               $temp['D'] = true;
-                       }
-                       $flags = $temp;
-               } else {
-                       if ( isset( $flags['A'] ) ) {
-                               $flags['+'] = true;
-                               $flags['S'] = true;
-                       }
-                       if ( isset( $flags['D'] ) ) {
-                               unset( $flags['S'] );
-                       }
-                       // try to find flags like "zh-hans", "zh-hant"
-                       // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
-                       $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
-                       if ( $variantFlags ) {
-                               $variantFlags = array_flip( $variantFlags );
-                               $flags = [];
-                       }
-               }
-               $this->mVariantFlags = $variantFlags;
-               $this->mRules = $text;
-               $this->mFlags = $flags;
-       }
-
-       /**
-        * Generate conversion table.
-        * @private
-        */
-       function parseRules() {
-               $rules = $this->mRules;
-               $bidtable = [];
-               $unidtable = [];
-               $variants = $this->mConverter->mVariants;
-               $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
-
-               // Split according to $varsep_pattern, but ignore semicolons from HTML entities
-               $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
-               $choice = preg_split( $varsep_pattern, $rules );
-               $choice = str_replace( "\x01", ';', $choice );
-
-               foreach ( $choice as $c ) {
-                       $v = explode( ':', $c, 2 );
-                       if ( count( $v ) != 2 ) {
-                               // syntax error, skip
-                               continue;
-                       }
-                       $to = trim( $v[1] );
-                       $v = trim( $v[0] );
-                       $u = explode( '=>', $v, 2 );
-                       $vv = $this->mConverter->validateVariant( $v );
-                       // if $to is empty (which is also used as $from in bidtable),
-                       // strtr() could return a wrong result.
-                       if ( count( $u ) == 1 && $to !== '' && $vv ) {
-                               $bidtable[$vv] = $to;
-                       } elseif ( count( $u ) == 2 ) {
-                               $from = trim( $u[0] );
-                               $v = trim( $u[1] );
-                               $vv = $this->mConverter->validateVariant( $v );
-                               // if $from is empty, strtr() could return a wrong result.
-                               if ( array_key_exists( $vv, $unidtable )
-                                       && !is_array( $unidtable[$vv] )
-                                       && $from !== ''
-                                       && $vv ) {
-                                       $unidtable[$vv] = [ $from => $to ];
-                               } elseif ( $from !== '' && $vv ) {
-                                       $unidtable[$vv][$from] = $to;
-                               }
-                       }
-                       // syntax error, pass
-                       if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
-                               $bidtable = [];
-                               $unidtable = [];
-                               break;
-                       }
-               }
-               $this->mBidtable = $bidtable;
-               $this->mUnidtable = $unidtable;
-       }
-
-       /**
-        * @private
-        *
-        * @return string
-        */
-       function getRulesDesc() {
-               $codesep = $this->mConverter->mDescCodeSep;
-               $varsep = $this->mConverter->mDescVarSep;
-               $text = '';
-               foreach ( $this->mBidtable as $k => $v ) {
-                       $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
-               }
-               foreach ( $this->mUnidtable as $k => $a ) {
-                       foreach ( $a as $from => $to ) {
-                               $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
-                                       "$codesep$to$varsep";
-                       }
-               }
-               return $text;
-       }
-
-       /**
-        * Parse rules conversion.
-        * @private
-        *
-        * @param string $variant
-        *
-        * @return string
-        */
-       function getRuleConvertedStr( $variant ) {
-               $bidtable = $this->mBidtable;
-               $unidtable = $this->mUnidtable;
-
-               if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
-                       return $this->mRules;
-               } else {
-                       // display current variant in bidirectional array
-                       $disp = $this->getTextInBidtable( $variant );
-                       // or display current variant in fallbacks
-                       if ( $disp === false ) {
-                               $disp = $this->getTextInBidtable(
-                                       $this->mConverter->getVariantFallbacks( $variant ) );
-                       }
-                       // or display current variant in unidirectional array
-                       if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
-                               $disp = array_values( $unidtable[$variant] )[0];
-                       }
-                       // or display first text under disable manual convert
-                       if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
-                               if ( count( $bidtable ) > 0 ) {
-                                       $disp = array_values( $bidtable )[0];
-                               } else {
-                                       $disp = array_values( array_values( $unidtable )[0] )[0];
-                               }
-                       }
-                       return $disp;
-               }
-       }
-
-       /**
-        * Similar to getRuleConvertedStr(), but this prefers to use original
-        * page title if $variant === $this->mConverter->mMainLanguageCode
-        * and may return false in this case (so this title conversion rule
-        * will be ignored and the original title is shown).
-        *
-        * @since 1.22
-        * @param string $variant The variant code to display page title in
-        * @return string|bool The converted title or false if just page name
-        */
-       function getRuleConvertedTitle( $variant ) {
-               if ( $variant === $this->mConverter->mMainLanguageCode ) {
-                       // If a string targeting exactly this variant is set,
-                       // use it. Otherwise, just return false, so the real
-                       // page name can be shown (and because variant === main,
-                       // there'll be no further automatic conversion).
-                       $disp = $this->getTextInBidtable( $variant );
-                       if ( $disp ) {
-                               return $disp;
-                       }
-                       if ( array_key_exists( $variant, $this->mUnidtable ) ) {
-                               $disp = array_values( $this->mUnidtable[$variant] )[0];
-                       }
-                       // Assigned above or still false.
-                       return $disp;
-               } else {
-                       return $this->getRuleConvertedStr( $variant );
-               }
-       }
-
-       /**
-        * Generate conversion table for all text.
-        * @private
-        */
-       function generateConvTable() {
-               // Special case optimisation
-               if ( !$this->mBidtable && !$this->mUnidtable ) {
-                       $this->mConvTable = [];
-                       return;
-               }
-
-               $bidtable = $this->mBidtable;
-               $unidtable = $this->mUnidtable;
-               $manLevel = $this->mConverter->mManualLevel;
-
-               $vmarked = [];
-               foreach ( $this->mConverter->mVariants as $v ) {
-                       /* for bidirectional array
-                               fill in the missing variants, if any,
-                               with fallbacks */
-                       if ( !isset( $bidtable[$v] ) ) {
-                               $variantFallbacks =
-                                       $this->mConverter->getVariantFallbacks( $v );
-                               $vf = $this->getTextInBidtable( $variantFallbacks );
-                               if ( $vf ) {
-                                       $bidtable[$v] = $vf;
-                               }
-                       }
-
-                       if ( isset( $bidtable[$v] ) ) {
-                               foreach ( $vmarked as $vo ) {
-                                       // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
-                                       // or -{H|zh:WordZh;zh-tw:WordTw}-
-                                       // or -{-|zh:WordZh;zh-tw:WordTw}-
-                                       // to introduce a custom mapping between
-                                       // words WordZh and WordTw in the whole text
-                                       if ( $manLevel[$v] == 'bidirectional' ) {
-                                               $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
-                                       }
-                                       if ( $manLevel[$vo] == 'bidirectional' ) {
-                                               $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
-                                       }
-                               }
-                               $vmarked[] = $v;
-                       }
-                       /* for unidirectional array fill to convert tables */
-                       if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
-                               && isset( $unidtable[$v] )
-                       ) {
-                               if ( isset( $this->mConvTable[$v] ) ) {
-                                       $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
-                               } else {
-                                       $this->mConvTable[$v] = $unidtable[$v];
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Parse rules and flags.
-        * @param string|null $variant Variant language code
-        */
-       public function parse( $variant = null ) {
-               if ( !$variant ) {
-                       $variant = $this->mConverter->getPreferredVariant();
-               }
-
-               $this->parseFlags();
-               $flags = $this->mFlags;
-
-               // convert to specified variant
-               // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
-               if ( $this->mVariantFlags ) {
-                       // check if current variant in flags
-                       if ( isset( $this->mVariantFlags[$variant] ) ) {
-                               // then convert <text to convert> to current language
-                               $this->mRules = $this->mConverter->autoConvert( $this->mRules,
-                                       $variant );
-                       } else {
-                               // if current variant no in flags,
-                               // then we check its fallback variants.
-                               $variantFallbacks =
-                                       $this->mConverter->getVariantFallbacks( $variant );
-                               if ( is_array( $variantFallbacks ) ) {
-                                       foreach ( $variantFallbacks as $variantFallback ) {
-                                               // if current variant's fallback exist in flags
-                                               if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
-                                                       // then convert <text to convert> to fallback language
-                                                       $this->mRules =
-                                                               $this->mConverter->autoConvert( $this->mRules,
-                                                                       $variantFallback );
-                                                       break;
-                                               }
-                                       }
-                               }
-                       }
-                       $this->mFlags = $flags = [ 'R' => true ];
-               }
-
-               if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
-                       // decode => HTML entities modified by Sanitizer::removeHTMLtags
-                       $this->mRules = str_replace( '=&gt;', '=>', $this->mRules );
-                       $this->parseRules();
-               }
-               $rules = $this->mRules;
-
-               if ( !$this->mBidtable && !$this->mUnidtable ) {
-                       if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
-                               // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
-                               if ( $rules !== '' ) {
-                                       foreach ( $this->mConverter->mVariants as $v ) {
-                                               $this->mBidtable[$v] = $rules;
-                                       }
-                               }
-                       } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
-                               $this->mFlags = $flags = [ 'R' => true ];
-                       }
-               }
-
-               $this->mRuleDisplay = false;
-               foreach ( $flags as $flag => $unused ) {
-                       switch ( $flag ) {
-                               case 'R':
-                                       // if we don't do content convert, still strip the -{}- tags
-                                       $this->mRuleDisplay = $rules;
-                                       break;
-                               case 'N':
-                                       // process N flag: output current variant name
-                                       $ruleVar = trim( $rules );
-                                       $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
-                                       break;
-                               case 'D':
-                                       // process D flag: output rules description
-                                       $this->mRuleDisplay = $this->getRulesDesc();
-                                       break;
-                               case 'H':
-                                       // process H,- flag or T only: output nothing
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case '-':
-                                       $this->mRulesAction = 'remove';
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case '+':
-                                       $this->mRulesAction = 'add';
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case 'S':
-                                       $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
-                                       break;
-                               case 'T':
-                                       $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               default:
-                                       // ignore unknown flags (but see error case below)
-                       }
-               }
-               if ( $this->mRuleDisplay === false ) {
-                       $this->mRuleDisplay = '<span class="error">'
-                               . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
-                               . '</span>';
-               }
-
-               $this->generateConvTable();
-       }
-
-       /**
-        * Checks if there are conversion rules.
-        * @return bool
-        */
-       public function hasRules() {
-               return $this->mRules !== '';
-       }
-
-       /**
-        * Get display text on markup -{...}-
-        * @return string
-        */
-       public function getDisplay() {
-               return $this->mRuleDisplay;
-       }
-
-       /**
-        * Get converted title.
-        * @return string
-        */
-       public function getTitle() {
-               return $this->mRuleTitle;
-       }
-
-       /**
-        * Return how deal with conversion rules.
-        * @return string
-        */
-       public function getRulesAction() {
-               return $this->mRulesAction;
-       }
-
-       /**
-        * Get conversion table. (bidirectional and unidirectional
-        * conversion table)
-        * @return array
-        */
-       public function getConvTable() {
-               return $this->mConvTable;
-       }
-
-       /**
-        * Get conversion rules string.
-        * @return string
-        */
-       public function getRules() {
-               return $this->mRules;
-       }
-
-       /**
-        * Get conversion flags.
-        * @return array
-        */
-       public function getFlags() {
-               return $this->mFlags;
-       }
-}
index 872614c..bb256c9 100644 (file)
@@ -27,8 +27,8 @@
  */
 
 use CLDRPluralRuleParser\Evaluator;
-use MediaWiki\Languages\LanguageNameUtils;
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Assert\Assert;
 
 /**
  * Internationalisation code
@@ -38,24 +38,21 @@ class Language {
        /**
         * Return autonyms in fetchLanguageName(s).
         * @since 1.32
-        * @deprecated since 1.34, LanguageNameUtils::AUTONYMS
         */
-       const AS_AUTONYMS = LanguageNameUtils::AUTONYMS;
+       const AS_AUTONYMS = null;
 
        /**
         * Return all known languages in fetchLanguageName(s).
         * @since 1.32
-        * @deprecated since 1.34, use LanguageNameUtils::ALL
         */
-       const ALL = LanguageNameUtils::ALL;
+       const ALL = 'all';
 
        /**
         * Return in fetchLanguageName(s) only the languages for which we have at
         * least some localisation.
         * @since 1.32
-        * @deprecated since 1.34, use LanguageNameUtils::SUPPORTED
         */
-       const SUPPORTED = LanguageNameUtils::SUPPORTED;
+       const SUPPORTED = 'mwfile';
 
        /**
         * @var LanguageConverter
@@ -78,11 +75,10 @@ class Language {
         */
        public $transformData = [];
 
-       /** @var LocalisationCache */
-       private $localisationCache;
-
-       /** @var LanguageNameUtils */
-       private $langNameUtils;
+       /**
+        * @var LocalisationCache
+        */
+       public static $dataCache;
 
        public static $mLangObjCache = [];
 
@@ -98,7 +94,6 @@ class Language {
         */
        const STRICT_FALLBACKS = 1;
 
-       // TODO Make these const once we drop HHVM support (T192166)
        public static $mWeekdayMsgs = [
                'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
                'friday', 'saturday'
@@ -183,6 +178,12 @@ class Language {
         */
        private static $grammarTransformations;
 
+       /**
+        * Cache for language names
+        * @var HashBagOStuff|null
+        */
+       private static $languageNameCache;
+
        /**
         * Unicode directional formatting characters, for embedBidi()
         */
@@ -238,12 +239,11 @@ class Language {
         * @return Language
         */
        protected static function newFromCode( $code, $fallback = false ) {
-               $langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
-               if ( !$langNameUtils->isValidCode( $code ) ) {
+               if ( !self::isValidCode( $code ) ) {
                        throw new MWException( "Invalid language code \"$code\"" );
                }
 
-               if ( !$langNameUtils->isValidBuiltInCode( $code ) ) {
+               if ( !self::isValidBuiltInCode( $code ) ) {
                        // It's not possible to customise this code with class files, so
                        // just return a Language object. This is to support uselang= hacks.
                        $lang = new Language;
@@ -262,7 +262,7 @@ class Language {
                // Keep trying the fallback list until we find an existing class
                $fallbacks = self::getFallbacksFor( $code );
                foreach ( $fallbacks as $fallbackCode ) {
-                       if ( !$langNameUtils->isValidBuiltInCode( $fallbackCode ) ) {
+                       if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
                                throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
                        }
 
@@ -283,30 +283,37 @@ class Language {
         * @since 1.32
         */
        public static function clearCaches() {
-               if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MEDIAWIKI_INSTALL' ) ) {
-                       throw new MWException( __METHOD__ . ' must not be used outside tests/installer' );
-               }
-               if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
-                       MediaWikiServices::getInstance()->resetServiceForTesting( 'LocalisationCache' );
-                       MediaWikiServices::getInstance()->resetServiceForTesting( 'LanguageNameUtils' );
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException( __METHOD__ . ' must not be used outside tests' );
                }
+               self::$dataCache = null;
+               // Reinitialize $dataCache, since it's expected to always be available
+               self::getLocalisationCache();
                self::$mLangObjCache = [];
                self::$fallbackLanguageCache = [];
                self::$grammarTransformations = null;
+               self::$languageNameCache = null;
        }
 
        /**
         * Checks whether any localisation is available for that language tag
         * in MediaWiki (MessagesXx.php exists).
         *
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $code Language tag (in lower case)
         * @return bool Whether language is supported
         * @since 1.21
         */
        public static function isSupportedLanguage( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->isSupportedLanguage( $code );
+               if ( !self::isValidBuiltInCode( $code ) ) {
+                       return false;
+               }
+
+               if ( $code === 'qqq' ) {
+                       return false;
+               }
+
+               return is_readable( self::getMessagesFileName( $code ) ) ||
+                       is_readable( self::getJsonMessagesFileName( $code ) );
        }
 
        /**
@@ -374,55 +381,77 @@ class Language {
         * not it exists. This includes codes which are used solely for
         * customisation via the MediaWiki namespace.
         *
-        * @deprecated since 1.34, use LanguageNameUtils
-        *
         * @param string $code
         *
         * @return bool
         */
        public static function isValidCode( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()->isValidCode( $code );
+               static $cache = [];
+               Assert::parameterType( 'string', $code, '$code' );
+               if ( !isset( $cache[$code] ) ) {
+                       // People think language codes are html safe, so enforce it.
+                       // Ideally we should only allow a-zA-Z0-9-
+                       // but, .+ and other chars are often used for {{int:}} hacks
+                       // see bugs T39564, T39587, T38938
+                       $cache[$code] =
+                               // Protect against path traversal
+                               strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
+                               && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
+               }
+               return $cache[$code];
        }
 
        /**
         * Returns true if a language code is of a valid form for the purposes of
         * internal customisation of MediaWiki, via Messages*.php or *.json.
         *
-        * @deprecated since 1.34, use LanguageNameUtils
-        *
         * @param string $code
         *
         * @since 1.18
         * @return bool
         */
        public static function isValidBuiltInCode( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->isValidBuiltInCode( $code );
+               Assert::parameterType( 'string', $code, '$code' );
+
+               return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
        }
 
        /**
         * Returns true if a language code is an IETF tag known to MediaWiki.
         *
-        * @deprecated since 1.34, use LanguageNameUtils
-        *
         * @param string $tag
         *
         * @since 1.21
         * @return bool
         */
        public static function isKnownLanguageTag( $tag ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->isKnownLanguageTag( $tag );
+               // Quick escape for invalid input to avoid exceptions down the line
+               // when code tries to process tags which are not valid at all.
+               if ( !self::isValidBuiltInCode( $tag ) ) {
+                       return false;
+               }
+
+               if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
+                       || self::fetchLanguageName( $tag, $tag ) !== ''
+               ) {
+                       return true;
+               }
+
+               return false;
        }
 
        /**
         * Get the LocalisationCache instance
         *
-        * @deprecated since 1.34, use MediaWikiServices
         * @return LocalisationCache
         */
        public static function getLocalisationCache() {
-               return MediaWikiServices::getInstance()->getLocalisationCache();
+               if ( is_null( self::$dataCache ) ) {
+                       global $wgLocalisationCacheConf;
+                       $class = $wgLocalisationCacheConf['class'];
+                       self::$dataCache = new $class( $wgLocalisationCacheConf );
+               }
+               return self::$dataCache;
        }
 
        function __construct() {
@@ -433,9 +462,7 @@ class Language {
                } else {
                        $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
                }
-               $services = MediaWikiServices::getInstance();
-               $this->localisationCache = $services->getLocalisationCache();
-               $this->langNameUtils = $services->getLanguageNameUtils();
+               self::getLocalisationCache();
        }
 
        /**
@@ -467,7 +494,7 @@ class Language {
         * @return array
         */
        public function getBookstoreList() {
-               return $this->localisationCache->getItem( $this->mCode, 'bookstoreList' );
+               return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
        }
 
        /**
@@ -484,7 +511,7 @@ class Language {
                                getCanonicalNamespaces();
 
                        $this->namespaceNames = $wgExtraNamespaces +
-                               $this->localisationCache->getItem( $this->mCode, 'namespaceNames' );
+                               self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
                        $this->namespaceNames += $validNamespaces;
 
                        $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
@@ -591,7 +618,7 @@ class Language {
                global $wgExtraGenderNamespaces;
 
                $ns = $wgExtraGenderNamespaces +
-                       (array)$this->localisationCache->getItem( $this->mCode, 'namespaceGenderAliases' );
+                       (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
 
                return $ns[$index][$gender] ?? $this->getNsText( $index );
        }
@@ -613,7 +640,7 @@ class Language {
                        return false;
                } else {
                        // Check what is in i18n files
-                       $aliases = $this->localisationCache->getItem( $this->mCode, 'namespaceGenderAliases' );
+                       $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
                        return count( $aliases ) > 0;
                }
        }
@@ -637,7 +664,7 @@ class Language {
         */
        public function getNamespaceAliases() {
                if ( is_null( $this->namespaceAliases ) ) {
-                       $aliases = $this->localisationCache->getItem( $this->mCode, 'namespaceAliases' );
+                       $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
                        if ( !$aliases ) {
                                $aliases = [];
                        } else {
@@ -651,8 +678,8 @@ class Language {
                        }
 
                        global $wgExtraGenderNamespaces;
-                       $genders = $wgExtraGenderNamespaces + (array)$this->localisationCache
-                               ->getItem( $this->mCode, 'namespaceGenderAliases' );
+                       $genders = $wgExtraGenderNamespaces +
+                               (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
                        foreach ( $genders as $index => $forms ) {
                                foreach ( $forms as $alias ) {
                                        $aliases[$alias] = $index;
@@ -740,7 +767,7 @@ class Language {
                if ( $usemsg && wfMessage( $msg )->exists() ) {
                        return $this->getMessageFromDB( $msg );
                }
-               $name = $this->langNameUtils->getLanguageName( $code );
+               $name = self::fetchLanguageName( $code );
                if ( $name ) {
                        return $name; # if it's defined as a language name, show that
                } else {
@@ -753,21 +780,21 @@ class Language {
         * @return string[]|bool List of date format preference keys, or false if disabled.
         */
        public function getDatePreferences() {
-               return $this->localisationCache->getItem( $this->mCode, 'datePreferences' );
+               return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
        }
 
        /**
         * @return array
         */
        function getDateFormats() {
-               return $this->localisationCache->getItem( $this->mCode, 'dateFormats' );
+               return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
        }
 
        /**
         * @return array|string
         */
        public function getDefaultDateFormat() {
-               $df = $this->localisationCache->getItem( $this->mCode, 'defaultDateFormat' );
+               $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
                if ( $df === 'dmy or mdy' ) {
                        global $wgAmericanDates;
                        return $wgAmericanDates ? 'mdy' : 'dmy';
@@ -780,7 +807,7 @@ class Language {
         * @return array
         */
        public function getDatePreferenceMigrationMap() {
-               return $this->localisationCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
+               return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
        }
 
        /**
@@ -801,8 +828,6 @@ class Language {
 
        /**
         * Get an array of language names, indexed by code.
-        *
-        * @deprecated since 1.34, use LanguageNameUtils::getLanguageNames
         * @param null|string $inLanguage Code of language in which to return the names
         *              Use self::AS_AUTONYMS for autonyms (native names)
         * @param string $include One of:
@@ -813,12 +838,95 @@ class Language {
         * @since 1.20
         */
        public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getLanguageNames( $inLanguage, $include );
+               $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
+               $cacheKey .= ":$include";
+               if ( self::$languageNameCache === null ) {
+                       self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
+               }
+
+               $ret = self::$languageNameCache->get( $cacheKey );
+               if ( !$ret ) {
+                       $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
+                       self::$languageNameCache->set( $cacheKey, $ret );
+               }
+               return $ret;
+       }
+
+       /**
+        * Uncached helper for fetchLanguageNames
+        * @param null|string $inLanguage Code of language in which to return the names
+        *              Use self::AS_AUTONYMS for autonyms (native names)
+        * @param string $include One of:
+        *              self::ALL all available languages
+        *              'mw' only if the language is defined in MediaWiki or wgExtraLanguageNames (default)
+        *              self::SUPPORTED only if the language is in 'mw' *and* has a message file
+        * @return array Language code => language name (sorted by key)
+        */
+       private static function fetchLanguageNamesUncached(
+               $inLanguage = self::AS_AUTONYMS,
+               $include = 'mw'
+       ) {
+               global $wgExtraLanguageNames, $wgUsePigLatinVariant;
+
+               // If passed an invalid language code to use, fallback to en
+               if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
+                       $inLanguage = 'en';
+               }
+
+               $names = [];
+
+               if ( $inLanguage ) {
+                       # TODO: also include when $inLanguage is null, when this code is more efficient
+                       Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
+               }
+
+               $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
+               if ( $wgUsePigLatinVariant ) {
+                       // Pig Latin (for variant development)
+                       $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
+               }
+
+               foreach ( $mwNames as $mwCode => $mwName ) {
+                       # - Prefer own MediaWiki native name when not using the hook
+                       # - For other names just add if not added through the hook
+                       if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
+                               $names[$mwCode] = $mwName;
+                       }
+               }
+
+               if ( $include === self::ALL ) {
+                       ksort( $names );
+                       return $names;
+               }
+
+               $returnMw = [];
+               $coreCodes = array_keys( $mwNames );
+               foreach ( $coreCodes as $coreCode ) {
+                       $returnMw[$coreCode] = $names[$coreCode];
+               }
+
+               if ( $include === self::SUPPORTED ) {
+                       $namesMwFile = [];
+                       # We do this using a foreach over the codes instead of a directory
+                       # loop so that messages files in extensions will work correctly.
+                       foreach ( $returnMw as $code => $value ) {
+                               if ( is_readable( self::getMessagesFileName( $code ) )
+                                       || is_readable( self::getJsonMessagesFileName( $code ) )
+                               ) {
+                                       $namesMwFile[$code] = $names[$code];
+                               }
+                       }
+
+                       ksort( $namesMwFile );
+                       return $namesMwFile;
+               }
+
+               ksort( $returnMw );
+               # 'mw' option; default if it's not one of the other two options (all/mwfile)
+               return $returnMw;
        }
 
        /**
-        * @deprecated since 1.34, use LanguageNameUtils::getLanguageName
         * @param string $code The code of the language for which to get the name
         * @param null|string $inLanguage Code of language in which to return the name
         *   (SELF::AS_AUTONYMS for autonyms)
@@ -831,8 +939,9 @@ class Language {
                $inLanguage = self::AS_AUTONYMS,
                $include = self::ALL
        ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getLanguageName( $code, $inLanguage, $include );
+               $code = strtolower( $code );
+               $array = self::fetchLanguageNames( $inLanguage, $include );
+               return !array_key_exists( $code, $array ) ? '' : $array[$code];
        }
 
        /**
@@ -2165,8 +2274,7 @@ class Language {
                }
 
                if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
-                       $df =
-                               $this->localisationCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
+                       $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
 
                        if ( $type === 'pretty' && $df === null ) {
                                $df = $this->getDateFormatString( 'date', $pref );
@@ -2174,8 +2282,7 @@ class Language {
 
                        if ( !$wasDefault && $df === null ) {
                                $pref = $this->getDefaultDateFormat();
-                               $df = $this->getLocalisationCache()
-                                       ->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
+                               $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
                        }
 
                        $this->dateFormatStrings[$type][$pref] = $df;
@@ -2539,14 +2646,14 @@ class Language {
         * @return string|null
         */
        public function getMessage( $key ) {
-               return $this->localisationCache->getSubitem( $this->mCode, 'messages', $key );
+               return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
        }
 
        /**
         * @return array
         */
        function getAllMessages() {
-               return $this->localisationCache->getItem( $this->mCode, 'messages' );
+               return self::$dataCache->getItem( $this->mCode, 'messages' );
        }
 
        /**
@@ -2788,7 +2895,7 @@ class Language {
         * @return string
         */
        function fallback8bitEncoding() {
-               return $this->localisationCache->getItem( $this->mCode, 'fallback8bitEncoding' );
+               return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
        }
 
        /**
@@ -2978,7 +3085,7 @@ class Language {
         * @return bool
         */
        function isRTL() {
-               return $this->localisationCache->getItem( $this->mCode, 'rtl' );
+               return self::$dataCache->getItem( $this->mCode, 'rtl' );
        }
 
        /**
@@ -3054,7 +3161,7 @@ class Language {
         * @return array
         */
        function capitalizeAllNouns() {
-               return $this->localisationCache->getItem( $this->mCode, 'capitalizeAllNouns' );
+               return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
        }
 
        /**
@@ -3087,7 +3194,7 @@ class Language {
         * @return bool
         */
        function linkPrefixExtension() {
-               return $this->localisationCache->getItem( $this->mCode, 'linkPrefixExtension' );
+               return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
        }
 
        /**
@@ -3095,7 +3202,7 @@ class Language {
         * @return array
         */
        function getMagicWords() {
-               return $this->localisationCache->getItem( $this->mCode, 'magicWords' );
+               return self::$dataCache->getItem( $this->mCode, 'magicWords' );
        }
 
        /**
@@ -3105,7 +3212,7 @@ class Language {
         */
        function getMagic( $mw ) {
                $rawEntry = $this->mMagicExtensions[$mw->mId] ??
-                       $this->localisationCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
+                       self::$dataCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
 
                if ( !is_array( $rawEntry ) ) {
                        wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
@@ -3140,7 +3247,7 @@ class Language {
                if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
                        // Initialise array
                        $this->mExtendedSpecialPageAliases =
-                               $this->localisationCache->getItem( $this->mCode, 'specialPageAliases' );
+                               self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
                }
 
                return $this->mExtendedSpecialPageAliases;
@@ -3305,28 +3412,28 @@ class Language {
         * @return string
         */
        function digitGroupingPattern() {
-               return $this->localisationCache->getItem( $this->mCode, 'digitGroupingPattern' );
+               return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
        }
 
        /**
         * @return array
         */
        function digitTransformTable() {
-               return $this->localisationCache->getItem( $this->mCode, 'digitTransformTable' );
+               return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
        }
 
        /**
         * @return array
         */
        function separatorTransformTable() {
-               return $this->localisationCache->getItem( $this->mCode, 'separatorTransformTable' );
+               return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
        }
 
        /**
         * @return int|null
         */
        function minimumGroupingDigits() {
-               return $this->localisationCache->getItem( $this->mCode, 'minimumGroupingDigits' );
+               return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
        }
 
        /**
@@ -4226,7 +4333,7 @@ class Language {
         * @return string
         */
        public function linkTrail() {
-               return $this->localisationCache->getItem( $this->mCode, 'linkTrail' );
+               return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
        }
 
        /**
@@ -4236,7 +4343,7 @@ class Language {
         * @return string
         */
        public function linkPrefixCharset() {
-               return $this->localisationCache->getItem( $this->mCode, 'linkPrefixCharset' );
+               return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
        }
 
        /**
@@ -4338,8 +4445,6 @@ class Language {
 
        /**
         * Get the name of a file for a certain language code
-        *
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $prefix Prepend this to the filename
         * @param string $code Language code
         * @param string $suffix Append this to the filename
@@ -4347,30 +4452,38 @@ class Language {
         * @return string $prefix . $mangledCode . $suffix
         */
        public static function getFileName( $prefix, $code, $suffix = '.php' ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getFileName( $prefix, $code, $suffix );
+               if ( !self::isValidBuiltInCode( $code ) ) {
+                       throw new MWException( "Invalid language code \"$code\"" );
+               }
+
+               return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
        }
 
        /**
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $code
         * @return string
         */
        public static function getMessagesFileName( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getMessagesFileName( $code );
+               global $IP;
+               $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
+               Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
+               return $file;
        }
 
        /**
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $code
         * @return string
         * @throws MWException
         * @since 1.23
         */
        public static function getJsonMessagesFileName( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getJsonMessagesFileName( $code );
+               global $IP;
+
+               if ( !self::isValidBuiltInCode( $code ) ) {
+                       throw new MWException( "Invalid language code \"$code\"" );
+               }
+
+               return "$IP/languages/i18n/$code.json";
        }
 
        /**
@@ -4820,13 +4933,11 @@ class Language {
         * @return array Associative array with plural form, and plural rule as key-value pairs
         */
        public function getCompiledPluralRules() {
-               $pluralRules =
-                       $this->localisationCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
+               $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
                $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRules ) {
                        foreach ( $fallbacks as $fallbackCode ) {
-                               $pluralRules = $this->localisationCache
-                                       ->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
+                               $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
                                if ( $pluralRules ) {
                                        break;
                                }
@@ -4841,13 +4952,11 @@ class Language {
         * @return array Associative array with plural form number and plural rule as key-value pairs
         */
        public function getPluralRules() {
-               $pluralRules =
-                       $this->localisationCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
+               $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
                $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRules ) {
                        foreach ( $fallbacks as $fallbackCode ) {
-                               $pluralRules = $this->localisationCache
-                                       ->getItem( strtolower( $fallbackCode ), 'pluralRules' );
+                               $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
                                if ( $pluralRules ) {
                                        break;
                                }
@@ -4862,13 +4971,11 @@ class Language {
         * @return array Associative array with plural form number and plural rule type as key-value pairs
         */
        public function getPluralRuleTypes() {
-               $pluralRuleTypes =
-                       $this->localisationCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
+               $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
                $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRuleTypes ) {
                        foreach ( $fallbacks as $fallbackCode ) {
-                               $pluralRuleTypes = $this->localisationCache
-                                       ->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
+                               $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
                                if ( $pluralRuleTypes ) {
                                        break;
                                }
index 1d80f6b..00f35b2 100644 (file)
@@ -39,7 +39,7 @@ namespace MediaWiki\Languages\Data;
  * If you are adding support for such a language, add it also to
  * the relevant section in shared.css.
  *
- * Do not use this class directly. Use LanguageNameUtils::getLanguageNames(), which
+ * Do not use this class directly. Use Language::fetchLanguageNames(), which
  * includes support for the CLDR extension.
  *
  * @ingroup Language
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 a239fa0..4213d5f 100644 (file)
  * @ingroup Maintenance
  */
 
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\MediaWikiServices;
-
 require_once __DIR__ . '/Maintenance.php';
 
 /**
@@ -81,25 +77,13 @@ class RebuildLocalisationCache extends Maintenance {
 
                $conf = $wgLocalisationCacheConf;
                $conf['manualRecache'] = false; // Allow fallbacks to create CDB files
-               $conf['forceRecache'] = $force || !empty( $conf['forceRecache'] );
+               if ( $force ) {
+                       $conf['forceRecache'] = true;
+               }
                if ( $this->hasOption( 'outdir' ) ) {
                        $conf['storeDirectory'] = $this->getOption( 'outdir' );
                }
-               // XXX Copy-pasted from ServiceWiring.php. Do we need a factory for this one caller?
-               $lc = new LocalisationCacheBulkLoad(
-                       new ServiceOptions(
-                               LocalisationCache::$constructorOptions,
-                               $conf,
-                               MediaWikiServices::getInstance()->getMainConfig()
-                       ),
-                       new LCStoreDB( [] ),
-                       LoggerFactory::getInstance( 'localisation' ),
-                       [ function () {
-                               MediaWikiServices::getInstance()->getResourceLoader()
-                                       ->getMessageBlobStore()->clear();
-                       } ],
-                       MediaWikiServices::getInstance()->getLanguageNameUtils()
-               );
+               $lc = new LocalisationCacheBulkLoad( $conf );
 
                $allCodes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
                if ( $this->hasOption( 'lang' ) ) {
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 99b548e..7c8df1a 100644 (file)
@@ -221,9 +221,6 @@ $wgAutoloadClasses += [
        # tests/phpunit/unit/includes
        'BadFileLookupTest' => "$testDir/phpunit/unit/includes/BadFileLookupTest.php",
 
-       # tests/phpunit/unit/includes/language
-       'LanguageNameUtilsTestTrait' => "$testDir/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php",
-
        # tests/phpunit/unit/includes/libs/filebackend/fsfile
        'TempFSFileTestTrait' => "$testDir/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTestTrait.php",
 
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 5261b19..33518ef 100644 (file)
@@ -404,7 +404,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
 
                $wgRequest = new FauxRequest();
                MediaWiki\Session\SessionManager::resetCache();
-               Language::clearCaches();
        }
 
        public function run( PHPUnit_Framework_TestResult $result = null ) {
diff --git a/tests/phpunit/includes/Rest/EntryPointTest.php b/tests/phpunit/includes/Rest/EntryPointTest.php
new file mode 100644 (file)
index 0000000..b599e9d
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use GuzzleHttp\Psr7\Stream;
+use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
+use MediaWiki\Rest\Handler;
+use MediaWiki\Rest\EntryPoint;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+use RequestContext;
+use WebResponse;
+
+/**
+ * @covers \MediaWiki\Rest\EntryPoint
+ * @covers \MediaWiki\Rest\Router
+ */
+class EntryPointTest extends \MediaWikiTestCase {
+       private static $mockHandler;
+
+       private function createRouter() {
+               global $IP;
+
+               return new Router(
+                       [ "$IP/tests/phpunit/unit/includes/Rest/testRoutes.json" ],
+                       [],
+                       '/rest',
+                       new EmptyBagOStuff(),
+                       new ResponseFactory(),
+                       new StaticBasicAuthorizer() );
+       }
+
+       private function createWebResponse() {
+               return $this->getMockBuilder( WebResponse::class )
+                       ->setMethods( [ 'header' ] )
+                       ->getMock();
+       }
+
+       public static function mockHandlerHeader() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $response->setHeader( 'Foo', 'Bar' );
+                               return $response;
+                       }
+               };
+       }
+
+       public function testHeader() {
+               $webResponse = $this->createWebResponse();
+               $webResponse->expects( $this->any() )
+                       ->method( 'header' )
+                       ->withConsecutive(
+                               [ 'HTTP/1.1 200 OK', true, null ],
+                               [ 'Foo: Bar', true, null ]
+                       );
+
+               $entryPoint = new EntryPoint(
+                       RequestContext::getMain(),
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
+                       $webResponse,
+                       $this->createRouter() );
+               $entryPoint->execute();
+               $this->assertTrue( true );
+       }
+
+       public static function mockHandlerBodyRewind() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
+                               $stream->write( 'hello' );
+                               $response->setBody( $stream );
+                               return $response;
+                       }
+               };
+       }
+
+       /**
+        * Make sure EntryPoint rewinds a seekable body stream before reading.
+        */
+       public function testBodyRewind() {
+               $entryPoint = new EntryPoint(
+                       RequestContext::getMain(),
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
+                       $this->createWebResponse(),
+                       $this->createRouter() );
+               ob_start();
+               $entryPoint->execute();
+               $this->assertSame( 'hello', ob_get_clean() );
+       }
+
+}
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 6308b82..282188d 100644 (file)
@@ -160,7 +160,6 @@ class ApiQuerySiteinfoTest extends ApiTestCase {
                        'wgExtraInterlanguageLinkPrefixes' => [ 'self' ],
                        'wgExtraLanguageNames' => [ 'self' => 'Recursion' ],
                ] );
-               $this->resetServices();
 
                MessageCache::singleton()->enable();
 
index 39526fb..42957b6 100644 (file)
@@ -1,9 +1,4 @@
 <?php
-
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-use Psr\Log\NullLogger;
-
 /**
  * @group Database
  * @group Cache
@@ -24,51 +19,8 @@ class LocalisationCacheTest extends MediaWikiTestCase {
         */
        protected function getMockLocalisationCache() {
                global $IP;
-
-               $mockLangNameUtils = $this->createMock( LanguageNameUtils::class );
-               $mockLangNameUtils->method( 'isValidBuiltInCode' )->will( $this->returnCallback(
-                       function ( $code ) {
-                               // Copy-paste, but it's only one line
-                               return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
-                       }
-               ) );
-               $mockLangNameUtils->method( 'isSupportedLanguage' )->will( $this->returnCallback(
-                       function ( $code ) {
-                               return in_array( $code, [
-                                       'ar',
-                                       'arz',
-                                       'ba',
-                                       'de',
-                                       'en',
-                                       'ksh',
-                                       'ru',
-                               ] );
-                       }
-               ) );
-               $mockLangNameUtils->method( 'getMessagesFileName' )->will( $this->returnCallback(
-                       function ( $code ) {
-                               global $IP;
-                               $code = str_replace( '-', '_', ucfirst( $code ) );
-                               return "$IP/languages/messages/Messages$code.php";
-                       }
-               ) );
-               $mockLangNameUtils->expects( $this->never() )->method( $this->anythingBut(
-                       'isValidBuiltInCode', 'isSupportedLanguage', 'getMessagesFileName'
-               ) );
-
-               $lc = $this->getMockBuilder( LocalisationCache::class )
-                       ->setConstructorArgs( [
-                               new ServiceOptions( LocalisationCache::$constructorOptions, [
-                                       'forceRecache' => false,
-                                       'manualRecache' => false,
-                                       'ExtensionMessagesFiles' => [],
-                                       'MessagesDirs' => [],
-                               ] ),
-                               new LCStoreDB( [] ),
-                               new NullLogger,
-                               [],
-                               $mockLangNameUtils
-                       ] )
+               $lc = $this->getMockBuilder( \LocalisationCache::class )
+                       ->setConstructorArgs( [ [ 'store' => 'detect' ] ] )
                        ->setMethods( [ 'getMessagesDirs' ] )
                        ->getMock();
                $lc->expects( $this->any() )->method( 'getMessagesDirs' )
@@ -79,7 +31,7 @@ class LocalisationCacheTest extends MediaWikiTestCase {
                return $lc;
        }
 
-       public function testPluralRulesFallback() {
+       public function testPuralRulesFallback() {
                $cache = $this->getMockLocalisationCache();
 
                $this->assertEquals(
diff --git a/tests/phpunit/includes/language/ConverterRuleTest.php b/tests/phpunit/includes/language/ConverterRuleTest.php
new file mode 100644 (file)
index 0000000..1e06142
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @covers ConverterRule
+ */
+class ConverterRuleTest extends MediaWikiTestCase {
+
+       public function setUp() {
+               parent::setUp();
+               $this->setMwGlobals( 'wgUser', new User );
+       }
+
+       public function testParseEmpty() {
+               $converter = new LanguageConverter( new Language(), 'en' );
+               $rule = new ConverterRule( '', $converter );
+               $rule->parse();
+
+               $this->assertSame( false, $rule->getTitle(), 'title' );
+               $this->assertSame( [], $rule->getConvTable(), 'conversion table' );
+               $this->assertSame( 'none', $rule->getRulesAction(), 'rules action' );
+       }
+
+}
index 839272f..4bb9d5a 100644 (file)
@@ -39,13 +39,13 @@ class LogFormatterTest extends MediaWikiLangTestCase {
                global $wgExtensionMessagesFiles;
                self::$oldExtMsgFiles = $wgExtensionMessagesFiles;
                $wgExtensionMessagesFiles['LogTests'] = __DIR__ . '/LogTests.i18n.php';
-               Language::clearCaches();
+               Language::getLocalisationCache()->recache( 'en' );
        }
 
        public static function tearDownAfterClass() {
                global $wgExtensionMessagesFiles;
                $wgExtensionMessagesFiles = self::$oldExtMsgFiles;
-               Language::clearCaches();
+               Language::getLocalisationCache()->recache( 'en' );
 
                parent::tearDownAfterClass();
        }
index 457030f..7ee1ec9 100644 (file)
@@ -148,7 +148,6 @@ class PasswordPolicyChecksTest extends MediaWikiTestCase {
         */
        public function testCheckPopularPasswordBlacklist( $expected, $password ) {
                global $IP;
-               $this->hideDeprecated( 'PasswordPolicyChecks::checkPopularPasswordBlacklist' );
                $this->setMwGlobals( [
                        'wgSitename' => 'sitename',
                        'wgPopularPasswordFile' => "$IP/includes/password/commonpasswords.cdb"
diff --git a/tests/phpunit/includes/search/SearchResultTest.php b/tests/phpunit/includes/search/SearchResultTest.php
deleted file mode 100644 (file)
index 0e1e24c..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-class SearchResultTest extends MediawikiTestCase {
-       /**
-        * @covers SearchResult::getExtensionData
-        * @covers SearchResult::setExtensionData
-        */
-       public function testExtensionData() {
-               $result = SearchResult::newFromTitle( Title::newMainPage() );
-               $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
-
-               $data = [ 'hello' => 'world' ];
-               $result->setExtensionData( function () use ( &$data ) {
-                       return $data;
-               } );
-               $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
-               $data['this'] = 'that';
-               $this->assertEquals( $data, $result->getExtensionData(), 'refetches from callback' );
-       }
-
-       /**
-        * @covers SearchResult::getExtensionData
-        * @covers SearchResult::setExtensionData
-        */
-       public function testExtensionDataArrayBC() {
-               $result = SearchResult::newFromTitle( Title::newMainPage() );
-               $data = [ 'hello' => 'world' ];
-               $this->hideDeprecated( 'SearchResult::setExtensionData with array argument' );
-               $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
-               $result->setExtensionData( $data );
-               $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
-               $data['this'] = 'that';
-               $this->assertNotEquals( $data, $result->getExtensionData(), 'shouldnt hold any reference' );
-
-               $result->setExtensionData( $data );
-               $this->assertEquals( $data, $result->getExtensionData(), 'can replace extension data' );
-       }
-}
diff --git a/tests/phpunit/includes/search/SearchResultTraitTest.php b/tests/phpunit/includes/search/SearchResultTraitTest.php
new file mode 100644 (file)
index 0000000..6700c56
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+class SearchResultTraitTest extends MediawikiTestCase {
+       /**
+        * @covers SearchResultTrait::getExtensionData
+        * @covers SearchResultTrait::setExtensionData
+        */
+       public function testExtensionData() {
+               $result = new class() {
+                       use SearchResultTrait;
+               };
+               $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
+
+               $data = [ 'hello' => 'world' ];
+               $result->setExtensionData( function () use ( &$data ) {
+                       return $data;
+               } );
+               $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
+               $data['this'] = 'that';
+               $this->assertEquals( $data, $result->getExtensionData(), 'refetches from callback' );
+       }
+
+       /**
+        * @covers SearchResultTrait::getExtensionData
+        * @covers SearchResultTrait::setExtensionData
+        */
+       public function testExtensionDataArrayBC() {
+               $result = new class() {
+                       use SearchResultTrait;
+               };
+               $data = [ 'hello' => 'world' ];
+               $this->hideDeprecated( 'SearchResultTrait::setExtensionData with array argument' );
+               $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
+               $result->setExtensionData( $data );
+               $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
+               $data['this'] = 'that';
+               $this->assertNotEquals( $data, $result->getExtensionData(), 'shouldnt hold any reference' );
+
+               $result->setExtensionData( $data );
+               $this->assertEquals( $data, $result->getExtensionData(), 'can replace extension data' );
+       }
+}
index 6f618a2..2f6fa39 100644 (file)
@@ -3,24 +3,6 @@
 use Wikimedia\TestingAccessWrapper;
 
 class LanguageTest extends LanguageClassesTestCase {
-       use LanguageNameUtilsTestTrait;
-
-       /** @var array Copy of $wgHooks from before we unset LanguageGetTranslatedLanguageNames */
-       private $origHooks;
-
-       public function setUp() {
-               global $wgHooks;
-
-               parent::setUp();
-
-               // Don't allow installed hooks to run, except if a test restores them via origHooks (needed
-               // for testIsKnownLanguageTag_cldr)
-               $this->origHooks = $wgHooks;
-               $newHooks = $wgHooks;
-               unset( $newHooks['LanguageGetTranslatedLanguageNames'] );
-               $this->setMwGlobals( 'wgHooks', $newHooks );
-       }
-
        /**
         * @covers Language::convertDoubleWidth
         * @covers Language::normalizeForSearch
@@ -528,6 +510,84 @@ class LanguageTest extends LanguageClassesTestCase {
                );
        }
 
+       /**
+        * Test Language::isValidBuiltInCode()
+        * @dataProvider provideLanguageCodes
+        * @covers Language::isValidBuiltInCode
+        */
+       public function testBuiltInCodeValidation( $code, $expected, $message = '' ) {
+               $this->assertEquals( $expected,
+                       (bool)Language::isValidBuiltInCode( $code ),
+                       "validating code $code $message"
+               );
+       }
+
+       public static function provideLanguageCodes() {
+               return [
+                       [ 'fr', true, 'Two letters, minor case' ],
+                       [ 'EN', false, 'Two letters, upper case' ],
+                       [ 'tyv', true, 'Three letters' ],
+                       [ 'be-tarask', true, 'With dash' ],
+                       [ 'be-x-old', true, 'With extension (two dashes)' ],
+                       [ 'be_tarask', false, 'Reject underscores' ],
+               ];
+       }
+
+       /**
+        * Test Language::isKnownLanguageTag()
+        * @dataProvider provideKnownLanguageTags
+        * @covers Language::isKnownLanguageTag
+        */
+       public function testKnownLanguageTag( $code, $message = '' ) {
+               $this->assertTrue(
+                       (bool)Language::isKnownLanguageTag( $code ),
+                       "validating code $code - $message"
+               );
+       }
+
+       public static function provideKnownLanguageTags() {
+               return [
+                       [ 'fr', 'simple code' ],
+                       [ 'bat-smg', 'an MW legacy tag' ],
+                       [ 'sgs', 'an internal standard MW name, for which a legacy tag is used externally' ],
+               ];
+       }
+
+       /**
+        * @covers Language::isKnownLanguageTag
+        */
+       public function testKnownCldrLanguageTag() {
+               if ( !class_exists( 'LanguageNames' ) ) {
+                       $this->markTestSkipped( 'The LanguageNames class is not available. '
+                               . 'The CLDR extension is probably not installed.' );
+               }
+
+               $this->assertTrue(
+                       (bool)Language::isKnownLanguageTag( 'pal' ),
+                       'validating code "pal" an ancient language, which probably will '
+                               . 'not appear in Names.php, but appears in CLDR in English'
+               );
+       }
+
+       /**
+        * Negative tests for Language::isKnownLanguageTag()
+        * @dataProvider provideUnKnownLanguageTags
+        * @covers Language::isKnownLanguageTag
+        */
+       public function testUnknownLanguageTag( $code, $message = '' ) {
+               $this->assertFalse(
+                       (bool)Language::isKnownLanguageTag( $code ),
+                       "checking that code $code is invalid - $message"
+               );
+       }
+
+       public static function provideUnknownLanguageTags() {
+               return [
+                       [ 'mw', 'non-existent two-letter code' ],
+                       [ 'foo"<bar', 'very invalid language code' ],
+               ];
+       }
+
        /**
         * Test too short timestamp
         * @expectedException MWException
@@ -1752,6 +1812,12 @@ class LanguageTest extends LanguageClassesTestCase {
        public function testClearCaches() {
                $languageClass = TestingAccessWrapper::newFromClass( Language::class );
 
+               // Populate $dataCache
+               Language::getLocalisationCache()->getItem( 'zh', 'mainpage' );
+               $oldCacheObj = Language::$dataCache;
+               $this->assertNotCount( 0,
+                       TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
+
                // Populate $mLangObjCache
                $lang = Language::factory( 'en' );
                $this->assertNotCount( 0, Language::$mLangObjCache );
@@ -1764,11 +1830,36 @@ class LanguageTest extends LanguageClassesTestCase {
                $lang->getGrammarTransformations();
                $this->assertNotNull( $languageClass->grammarTransformations );
 
+               // Populate $languageNameCache
+               Language::fetchLanguageNames();
+               $this->assertNotNull( $languageClass->languageNameCache );
+
                Language::clearCaches();
 
+               $this->assertNotSame( $oldCacheObj, Language::$dataCache );
+               $this->assertCount( 0,
+                       TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
                $this->assertCount( 0, Language::$mLangObjCache );
                $this->assertCount( 0, $languageClass->fallbackLanguageCache );
                $this->assertNull( $languageClass->grammarTransformations );
+               $this->assertNull( $languageClass->languageNameCache );
+       }
+
+       /**
+        * @dataProvider provideIsSupportedLanguage
+        * @covers Language::isSupportedLanguage
+        */
+       public function testIsSupportedLanguage( $code, $expected, $comment ) {
+               $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment );
+       }
+
+       public static function provideIsSupportedLanguage() {
+               return [
+                       [ 'en', true, 'is supported language' ],
+                       [ 'fi', true, 'is supported language' ],
+                       [ 'bunny', false, 'is not supported language' ],
+                       [ 'FI', false, 'is not supported language, input should be in lower case' ],
+               ];
        }
 
        /**
@@ -1874,82 +1965,4 @@ class LanguageTest extends LanguageClassesTestCase {
                        [ 'èl', 'Ll' , 'Non-ASCII is overridden', [ 'è' => 'L' ] ],
                ];
        }
-
-       // The following methods are for LanguageNameUtilsTestTrait
-
-       private function isSupportedLanguage( $code ) {
-               return Language::isSupportedLanguage( $code );
-       }
-
-       private function isValidCode( $code ) {
-               return Language::isValidCode( $code );
-       }
-
-       private function isValidBuiltInCode( $code ) {
-               return Language::isValidBuiltInCode( $code );
-       }
-
-       private function isKnownLanguageTag( $code ) {
-               return Language::isKnownLanguageTag( $code );
-       }
-
-       /**
-        * Call getLanguageName() and getLanguageNames() using the Language static methods.
-        *
-        * @param array $options To set globals for testing Language
-        * @param string $expected
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       private function assertGetLanguageNames( array $options, $expected, $code, ...$otherArgs ) {
-               if ( $options ) {
-                       foreach ( $options as $key => $val ) {
-                               $this->setMwGlobals( "wg$key", $val );
-                       }
-                       $this->resetServices();
-               }
-               $this->assertSame( $expected,
-                       Language::fetchLanguageNames( ...$otherArgs )[strtolower( $code )] ?? '' );
-               $this->assertSame( $expected, Language::fetchLanguageName( $code, ...$otherArgs ) );
-       }
-
-       private function getLanguageNames( ...$args ) {
-               return Language::fetchLanguageNames( ...$args );
-       }
-
-       private function getLanguageName( ...$args ) {
-               return Language::fetchLanguageName( ...$args );
-       }
-
-       private static function getFileName( ...$args ) {
-               return Language::getFileName( ...$args );
-       }
-
-       private static function getMessagesFileName( $code ) {
-               return Language::getMessagesFileName( $code );
-       }
-
-       private static function getJsonMessagesFileName( $code ) {
-               return Language::getJsonMessagesFileName( $code );
-       }
-
-       /**
-        * @todo This really belongs in the cldr extension's tests.
-        *
-        * @covers MediaWiki\Languages\LanguageNameUtils::isKnownLanguageTag
-        * @covers Language::isKnownLanguageTag
-        */
-       public function testIsKnownLanguageTag_cldr() {
-               if ( !class_exists( 'LanguageNames' ) ) {
-                       $this->markTestSkipped( 'The LanguageNames class is not available. '
-                               . 'The CLDR extension is probably not installed.' );
-               }
-
-               // We need to restore the extension's hook that we removed.
-               $this->setMwGlobals( 'wgHooks', $this->origHooks );
-
-               // "pal" is an ancient language, which probably will not appear in Names.php, but appears in
-               // CLDR in English
-               $this->assertTrue( Language::isKnownLanguageTag( 'pal' ) );
-       }
 }
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/Rest/EntryPointTest.php b/tests/phpunit/unit/includes/Rest/EntryPointTest.php
deleted file mode 100644 (file)
index a74c0cb..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use EmptyBagOStuff;
-use GuzzleHttp\Psr7\Uri;
-use GuzzleHttp\Psr7\Stream;
-use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
-use MediaWiki\Rest\Handler;
-use MediaWiki\Rest\EntryPoint;
-use MediaWiki\Rest\RequestData;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWiki\Rest\Router;
-use WebResponse;
-
-/**
- * @covers \MediaWiki\Rest\EntryPoint
- * @covers \MediaWiki\Rest\Router
- */
-class EntryPointTest extends \MediaWikiUnitTestCase {
-       private static $mockHandler;
-
-       private function createRouter() {
-               return new Router(
-                       [ __DIR__ . '/testRoutes.json' ],
-                       [],
-                       '/rest',
-                       new EmptyBagOStuff(),
-                       new ResponseFactory(),
-                       new StaticBasicAuthorizer() );
-       }
-
-       private function createWebResponse() {
-               return $this->getMockBuilder( WebResponse::class )
-                       ->setMethods( [ 'header' ] )
-                       ->getMock();
-       }
-
-       public static function mockHandlerHeader() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $response->setHeader( 'Foo', 'Bar' );
-                               return $response;
-                       }
-               };
-       }
-
-       public function testHeader() {
-               $webResponse = $this->createWebResponse();
-               $webResponse->expects( $this->any() )
-                       ->method( 'header' )
-                       ->withConsecutive(
-                               [ 'HTTP/1.1 200 OK', true, null ],
-                               [ 'Foo: Bar', true, null ]
-                       );
-
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
-                       $webResponse,
-                       $this->createRouter() );
-               $entryPoint->execute();
-               $this->assertTrue( true );
-       }
-
-       public static function mockHandlerBodyRewind() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
-                               $stream->write( 'hello' );
-                               $response->setBody( $stream );
-                               return $response;
-                       }
-               };
-       }
-
-       /**
-        * Make sure EntryPoint rewinds a seekable body stream before reading.
-        */
-       public function testBodyRewind() {
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
-                       $this->createWebResponse(),
-                       $this->createRouter() );
-               ob_start();
-               $entryPoint->execute();
-               $this->assertSame( 'hello', ob_get_clean() );
-       }
-
-}
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();
+       }
+}
diff --git a/tests/phpunit/unit/includes/language/LanguageNameUtilsTest.php b/tests/phpunit/unit/includes/language/LanguageNameUtilsTest.php
deleted file mode 100644 (file)
index 6fbd4a2..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-
-class LanguageNameUtilsTest extends MediaWikiUnitTestCase {
-       /**
-        * @param array $optionsArray
-        */
-       private static function newObj( array $optionsArray = [] ) : LanguageNameUtils {
-               return new LanguageNameUtils( new ServiceOptions(
-                       LanguageNameUtils::$constructorOptions,
-                       $optionsArray,
-                       [
-                               'ExtraLanguageNames' => [],
-                               'LanguageCode' => 'en',
-                               'UsePigLatinVariant' => false,
-                       ]
-               ) );
-       }
-
-       use LanguageNameUtilsTestTrait;
-
-       private function isSupportedLanguage( $code ) {
-               return $this->newObj()->isSupportedLanguage( $code );
-       }
-
-       private function isValidCode( $code ) {
-               return $this->newObj()->isValidCode( $code );
-       }
-
-       private function isValidBuiltInCode( $code ) {
-               return $this->newObj()->isValidBuiltInCode( $code );
-       }
-
-       private function isKnownLanguageTag( $code ) {
-               return $this->newObj()->isKnownLanguageTag( $code );
-       }
-
-       private function assertGetLanguageNames( array $options, $expected, $code, ...$otherArgs ) {
-               $this->assertSame( $expected, $this->newObj( $options )
-                       ->getLanguageNames( ...$otherArgs )[strtolower( $code )] ?? '' );
-               $this->assertSame( $expected,
-                       $this->newObj( $options )->getLanguageName( $code, ...$otherArgs ) );
-       }
-
-       private function getLanguageNames( ...$args ) {
-               return $this->newObj()->getLanguageNames( ...$args );
-       }
-
-       private function getLanguageName( ...$args ) {
-               return $this->newObj()->getLanguageName( ...$args );
-       }
-
-       private static function getFileName( ...$args ) {
-               return self::newObj()->getFileName( ...$args );
-       }
-
-       private static function getMessagesFileName( $code ) {
-               return self::newObj()->getMessagesFileName( $code );
-       }
-
-       private static function getJsonMessagesFileName( $code ) {
-               return self::newObj()->getJsonMessagesFileName( $code );
-       }
-}
diff --git a/tests/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php b/tests/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php
deleted file mode 100644 (file)
index bd777e9..0000000
+++ /dev/null
@@ -1,555 +0,0 @@
-<?php
-
-use MediaWiki\Languages\LanguageNameUtils;
-
-const AUTONYMS = LanguageNameUtils::AUTONYMS;
-const ALL = LanguageNameUtils::ALL;
-const DEFINED = LanguageNameUtils::DEFINED;
-const SUPPORTED = LanguageNameUtils::SUPPORTED;
-
-/**
- * For code shared between LanguageNameUtilsTest and LanguageTest.
- */
-trait LanguageNameUtilsTestTrait {
-       abstract protected function isSupportedLanguage( $code );
-
-       /**
-        * @dataProvider provideIsSupportedLanguage
-        * @covers MediaWiki\Languages\LanguageNameUtils::__construct
-        * @covers MediaWiki\Languages\LanguageNameUtils::isSupportedLanguage
-        * @covers Language::isSupportedLanguage
-        */
-       public function testIsSupportedLanguage( $code, $expected ) {
-               $this->assertSame( $expected, $this->isSupportedLanguage( $code ) );
-       }
-
-       public static function provideIsSupportedLanguage() {
-               return [
-                       'en' => [ 'en', true ],
-                       'fi' => [ 'fi', true ],
-                       'bunny' => [ 'bunny', false ],
-                       'qqq' => [ 'qqq', false ],
-                       'uppercase is not considered supported' => [ 'FI', false ],
-               ];
-       }
-
-       abstract protected function isValidCode( $code );
-
-       /**
-        * We don't test that the result is cached, because that should only be noticeable if the
-        * configuration changes in between calls, and 1) that should never happen in normal operation,
-        * 2) if you do it you deserve whatever you get, and 3) once the static Language method is
-        * dropped and the invalid title regex is moved to something injected instead of a static call,
-        * the cache will be undetectable.
-        *
-        * @todo Should we test changes to $wgLegalTitleChars here? Does anybody actually change that?
-        * Is it possible to change it usefully without breaking everything?
-        *
-        * @dataProvider provideIsValidCode
-        * @covers MediaWiki\Languages\LanguageNameUtils::isValidCode
-        * @covers Language::isValidCode
-        *
-        * @param string $code
-        * @param bool $expected
-        */
-       public function testIsValidCode( $code, $expected ) {
-               $this->assertSame( $expected, $this->isValidCode( $code ) );
-       }
-
-       public static function provideIsValidCode() {
-               $ret = [
-                       'en' => [ 'en', true ],
-                       'en-GB' => [ 'en-GB', true ],
-                       'Funny chars' => [ "%!$()*,-.;=?@^_`~\x80\xA2\xFF+", true ],
-                       'Percent escape not allowed' => [ 'a%aF', false ],
-                       'Percent with only one following char is okay' => [ '%a', true ],
-                       'Percent with non-hex following chars is okay' => [ '%AG', true ],
-                       'Named char reference "a"' => [ 'a&a', false ],
-                       'Named char reference "A"' => [ 'a&A', false ],
-                       'Named char reference "0"' => [ 'a&0', false ],
-                       'Named char reference non-ASCII' => [ "a&\x92", false ],
-                       'Numeric char reference' => [ "a&#0", false ],
-                       'Hex char reference 0' => [ "a&#x0", false ],
-                       'Hex char reference A' => [ "a&#xA", false ],
-                       'Lone ampersand is valid for title but not lang code' => [ '&', false ],
-                       'Ampersand followed by just # is valid for title but not lang code' => [ '&#', false ],
-                       'Ampersand followed by # and non-x/digit is valid for title but not lang code' =>
-                               [ '&#a', false ],
-               ];
-               $disallowedChars = ":/\\\000&<>'\"";
-               foreach ( str_split( $disallowedChars ) as $char ) {
-                       $ret["Disallowed character $char"] = [ "a{$char}a", false ];
-               }
-               return $ret;
-       }
-
-       abstract protected function isValidBuiltInCode( $code );
-
-       /**
-        * @dataProvider provideIsValidBuiltInCode
-        * @covers MediaWiki\Languages\LanguageNameUtils::isValidBuiltInCode
-        * @covers Language::isValidBuiltInCode
-        *
-        * @param string $code
-        * @param bool $expected
-        */
-       public function testIsValidBuiltInCode( $code, $expected ) {
-               $this->assertSame( $expected, $this->isValidBuiltInCode( $code ) );
-       }
-
-       public static function provideIsValidBuiltInCode() {
-               return [
-                       'Two letters, lowercase' => [ 'fr', true ],
-                       'Two letters, uppercase' => [ 'EN', false ],
-                       'Three letters' => [ 'tyv', true ],
-                       'With dash' => [ 'be-tarask', true ],
-                       'With extension (two dashes)' => [ 'be-x-old', true ],
-                       'Reject underscores' => [ 'be_tarask', false ],
-                       'One letter' => [ 'a', false ],
-                       'Only digits' => [ '00', true ],
-                       'Only dashes' => [ '--', true ],
-                       'Unreasonably long' => [ str_repeat( 'x', 100 ), true ],
-                       'qqq' => [ 'qqq', true ],
-               ];
-       }
-
-       abstract protected function isKnownLanguageTag( $code );
-
-       /**
-        * @dataProvider provideIsKnownLanguageTag
-        * @covers MediaWiki\Languages\LanguageNameUtils::isKnownLanguageTag
-        * @covers Language::isKnownLanguageTag
-        *
-        * @param string $code
-        * @param bool $expected
-        */
-       public function testIsKnownLanguageTag( $code, $expected ) {
-               $this->assertSame( $expected, $this->isKnownLanguageTag( $code ) );
-       }
-
-       public static function provideIsKnownLanguageTag() {
-               $invalidBuiltInCodes = array_filter( static::provideIsValidBuiltInCode(),
-                       function ( $arr ) {
-                               // If isValidBuiltInCode() returns false, we want to also, but if it returns true,
-                               // we could still return false from isKnownLanguageTag(), so skip those.
-                               return !$arr[1];
-                       }
-               );
-               return array_merge( $invalidBuiltInCodes, [
-                       'Simple code' => [ 'fr', true ],
-                       'An MW legacy tag' => [ 'bat-smg', true ],
-                       'An internal standard MW name, for which a legacy tag is used externally' =>
-                               [ 'sgs', true ],
-                       'Non-existent two-letter code' => [ 'mw', false ],
-                       'Very invalid language code' => [ 'foo"<bar', false ],
-               ] );
-       }
-
-       abstract protected function assertGetLanguageNames(
-               array $options, $expected, $code, ...$otherArgs
-       );
-
-       abstract protected function getLanguageNames( ...$args );
-
-       abstract protected function getLanguageName( ...$args );
-
-       /**
-        * @dataProvider provideGetLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames( $expected, $code, ...$otherArgs ) {
-               $this->assertGetLanguageNames( [], $expected, $code, ...$otherArgs );
-       }
-
-       public static function provideGetLanguageNames() {
-               // @todo There are probably lots of interesting tests to add here.
-               return [
-                       'Simple code' => [ 'Deutsch', 'de' ],
-                       'Simple code in a different language (doesn\'t work without hook)' =>
-                               [ 'Deutsch', 'de', 'fr' ],
-                       'Invalid code' => [ '', '&' ],
-                       'Pig Latin not enabled' => [ '', 'en-x-piglatin', AUTONYMS, ALL ],
-                       'qqq doesn\'t have a name' => [ '', 'qqq', AUTONYMS, ALL ],
-                       'An MW legacy tag is recognized' => [ 'žemaitėška', 'bat-smg' ],
-                       // @todo Is the next test's result desired?
-                       'An MW legacy tag is not supported' => [ '', 'bat-smg', AUTONYMS, SUPPORTED ],
-                       'An internal standard name, for which a legacy tag is used externally, is supported' =>
-                               [ 'žemaitėška', 'sgs', AUTONYMS, SUPPORTED ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_withHook
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected Expected return value of getLanguageName()
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames_withHook( $expected, $code, ...$otherArgs ) {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names, $inLanguage ) {
-                               switch ( $inLanguage ) {
-                               case 'de':
-                                       $names = [
-                                               'de' => 'Deutsch',
-                                               'en' => 'Englisch',
-                                               'fr' => 'Französisch',
-                                       ];
-                                       break;
-
-                               case 'en':
-                                       $names = [
-                                               'de' => 'German',
-                                               'en' => 'English',
-                                               'fr' => 'French',
-                                               'sqsqsqsq' => '!!?!',
-                                               'bat-smg' => 'Samogitian',
-                                       ];
-                                       break;
-
-                               case 'fr':
-                                       $names = [
-                                               'de' => 'allemand',
-                                               'en' => 'anglais',
-                                               // Deliberate mistake (no cedilla)
-                                               'fr' => 'francais',
-                                       ];
-                                       break;
-                               }
-                       }
-               );
-
-               // Really we could dispense with assertGetLanguageNames() and just call
-               // testGetLanguageNames() here, but it looks weird to call a test method from another test
-               // method.
-               $this->assertGetLanguageNames( [], $expected, $code, ...$otherArgs );
-       }
-
-       public static function provideGetLanguageNames_withHook() {
-               return [
-                       'Simple code in a different language' => [ 'allemand', 'de', 'fr' ],
-                       'Invalid inLanguage defaults to English' => [ 'German', 'de', '&' ],
-                       'If inLanguage not provided, default to autonym' => [ 'Deutsch', 'de' ],
-                       'Hooks ignored for explicitly-requested autonym' => [ 'français', 'fr', 'fr' ],
-                       'Hooks don\'t make a language supported' => [ '', 'bat-smg', 'en', SUPPORTED ],
-                       'Hooks don\'t make a language defined' => [ '', 'sqsqsqsq', 'en', DEFINED ],
-                       'Hooks do make a language name returned with ALL' => [ '!!?!', 'sqsqsqsq', 'en', ALL ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_ExtraLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected Expected return value of getLanguageName()
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames_ExtraLanguageNames( $expected, $code, ...$otherArgs ) {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names ) {
-                               $names['de'] = 'die deutsche Sprache';
-                       }
-               );
-               $this->assertGetLanguageNames(
-                       [ 'ExtraLanguageNames' => [ 'de' => 'deutsche Sprache', 'sqsqsqsq' => '!!?!' ] ],
-                       $expected, $code, ...$otherArgs
-               );
-       }
-
-       public static function provideGetLanguageNames_ExtraLanguageNames() {
-               return [
-                       'Simple extra language name' => [ '!!?!', 'sqsqsqsq' ],
-                       'Extra language is defined' => [ '!!?!', 'sqsqsqsq', AUTONYMS, DEFINED ],
-                       'Extra language is not supported' => [ '', 'sqsqsqsq', AUTONYMS, SUPPORTED ],
-                       'Extra language overrides default' => [ 'deutsche Sprache', 'de' ],
-                       'Extra language overrides hook for explicitly requested autonym' =>
-                               [ 'deutsche Sprache', 'de', 'de' ],
-                       'Hook overrides extra language for non-autonym' =>
-                               [ 'die deutsche Sprache', 'de', 'fr' ],
-               ];
-       }
-
-       /**
-        * Test that getLanguageNames() defaults to DEFINED, and getLanguageName() defaults to ALL.
-        *
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        */
-       public function testGetLanguageNames_parameterDefault() {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names ) {
-                               $names = [ 'sqsqsqsq' => '!!?!' ];
-                       }
-               );
-
-               // We use 'en' here because the hook is not run if we're requesting autonyms, although in
-               // this case (language that isn't defined by MediaWiki itself) that behavior seems wrong.
-               $this->assertArrayNotHasKey( 'sqsqsqsq', $this->getLanguageNames(), 'en' );
-
-               $this->assertSame( '!!?!', $this->getLanguageName( 'sqsqsqsq', 'en' ) );
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_sorted
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers Language::fetchLanguageNames
-        *
-        * @param mixed ...$args To pass to method
-        */
-       public function testGetLanguageNames_sorted( ...$args ) {
-               $names = $this->getLanguageNames( ...$args );
-               $sortedNames = $names;
-               ksort( $sortedNames );
-               $this->assertSame( $sortedNames, $names );
-       }
-
-       public static function provideGetLanguageNames_sorted() {
-               return [
-                       [],
-                       [ AUTONYMS ],
-                       [ AUTONYMS, 'mw' ],
-                       [ AUTONYMS, ALL ],
-                       [ AUTONYMS, SUPPORTED ],
-                       [ 'he', 'mw' ],
-                       [ 'he', ALL ],
-                       [ 'he', SUPPORTED ],
-               ];
-       }
-
-       /**
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers Language::fetchLanguageNames
-        */
-       public function testGetLanguageNames_hookNotCalledForAutonyms() {
-               $count = 0;
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function () use ( &$count ) {
-                               $count++;
-                       }
-               );
-
-               $this->getLanguageNames();
-               $this->assertSame( 0, $count, 'Hook must not be called for autonyms' );
-
-               // We test elsewhere that the hook works, but the following verifies that our test is
-               // working and $count isn't being incremented above only because we're checking autonyms.
-               $this->getLanguageNames( 'fr' );
-               $this->assertSame( 1, $count, 'Hook must be called for non-autonyms' );
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_pigLatin
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames_pigLatin( $expected, ...$otherArgs ) {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names, $inLanguage ) {
-                               switch ( $inLanguage ) {
-                               case 'fr':
-                                       $names = [ 'en-x-piglatin' => 'latin de cochons' ];
-                                       break;
-
-                               case 'en-x-piglatin':
-                                       // Deliberately lowercase
-                                       $names = [ 'en-x-piglatin' => 'igpay atinlay' ];
-                                       break;
-                               }
-                       }
-               );
-
-               $this->assertGetLanguageNames(
-                       [ 'UsePigLatinVariant' => true ], $expected, 'en-x-piglatin', ...$otherArgs );
-       }
-
-       public static function provideGetLanguageNames_pigLatin() {
-               return [
-                       'Simple test' => [ 'Igpay Atinlay' ],
-                       'Not supported' => [ '', AUTONYMS, SUPPORTED ],
-                       'Foreign language' => [ 'latin de cochons', 'fr' ],
-                       'Hook doesn\'t override explicit autonym' =>
-                               [ 'Igpay Atinlay', 'en-x-piglatin', 'en-x-piglatin' ],
-               ];
-       }
-
-       /**
-        * Just for the sake of completeness, test that ExtraLanguageNames will not override the name
-        * for pig Latin. Nobody actually cares about this and if anything current behavior is probably
-        * wrong, but once we're testing the whole file we may as well be comprehensive.
-        *
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        */
-       public function testGetLanguageNames_pigLatinAndExtraLanguageNames() {
-               $this->assertGetLanguageNames(
-                       [
-                               'UsePigLatinVariant' => true,
-                               'ExtraLanguageNames' => [ 'en-x-piglatin' => 'igpay atinlay' ]
-                       ],
-                       'Igpay Atinlay',
-                       'en-x-piglatin'
-               );
-       }
-
-       abstract protected static function getFileName( ...$args );
-
-       /**
-        * @dataProvider provideGetFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getFileName
-        * @covers Language::getFileName
-        *
-        * @param string $expected
-        * @param mixed ...$args To pass to method
-        */
-       public function testGetFileName( $expected, ...$args ) {
-               $this->assertSame( $expected, $this->getFileName( ...$args ) );
-       }
-
-       public static function provideGetFileName() {
-               return [
-                       'Simple case' => [ 'MessagesXx.php', 'Messages', 'xx' ],
-                       'With extension' => [ 'MessagesXx.ext', 'Messages', 'xx', '.ext' ],
-                       'Replacing dashes' => [ '!__?', '!', '--', '?' ],
-                       'Empty prefix and extension' => [ 'Xx', '', 'xx', '' ],
-                       'Uppercase only first letter' => [ 'Messages_a.php', 'Messages', '-a' ],
-               ];
-       }
-
-       abstract protected function getMessagesFileName( $code );
-
-       /**
-        * @dataProvider provideGetMessagesFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getMessagesFileName
-        * @covers Language::getMessagesFileName
-        *
-        * @param string $code
-        * @param string $expected
-        */
-       public function testGetMessagesFileName( $code, $expected ) {
-               $this->assertSame( $expected, $this->getMessagesFileName( $code ) );
-       }
-
-       public static function provideGetMessagesFileName() {
-               global $IP;
-               return [
-                       'Simple case' => [ 'en', "$IP/languages/messages/MessagesEn.php" ],
-                       'Replacing dashes' => [ '--', "$IP/languages/messages/Messages__.php" ],
-                       'Uppercase only first letter' => [ '-a', "$IP/languages/messages/Messages_a.php" ],
-               ];
-       }
-
-       /**
-        * @covers MediaWiki\Languages\LanguageNameUtils::getMessagesFileName
-        * @covers Language::getMessagesFileName
-        */
-       public function testGetMessagesFileName_withHook() {
-               $called = 0;
-
-               $this->setTemporaryHook( 'Language::getMessagesFileName',
-                       function ( $code, &$file ) use ( &$called ) {
-                               global $IP;
-
-                               $called++;
-
-                               $this->assertSame( 'ab-cd', $code );
-                               $this->assertSame( "$IP/languages/messages/MessagesAb_cd.php", $file );
-                               $file = 'bye-bye';
-                       }
-               );
-
-               $this->assertSame( 'bye-bye', $this->getMessagesFileName( 'ab-cd' ) );
-               $this->assertSame( 1, $called );
-       }
-
-       abstract protected function getJsonMessagesFileName( $code );
-
-       /**
-        * @covers MediaWiki\Languages\LanguageNameUtils::getJsonMessagesFileName
-        * @covers Language::getJsonMessagesFileName
-        */
-       public function testGetJsonMessagesFileName() {
-               global $IP;
-
-               // Not so much to test here, one test seems to be enough
-               $expected = "$IP/languages/i18n/en--123.json";
-               $this->assertSame( $expected, $this->getJsonMessagesFileName( 'en--123' ) );
-       }
-
-       /**
-        * getFileName, getMessagesFileName, and getJsonMessagesFileName all throw if they get an
-        * invalid code. To save boilerplate, test them all in one method.
-        *
-        * @dataProvider provideExceptionFromInvalidCode
-        * @covers MediaWiki\Languages\LanguageNameUtils::getFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getMessagesFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getJsonMessagesFileName
-        * @covers Language::getFileName
-        * @covers Language::getMessagesFileName
-        * @covers Language::getJsonMessagesFileName
-        *
-        * @param callable $callback Will throw when passed $code
-        * @param string $code
-        */
-       public function testExceptionFromInvalidCode( $callback, $code ) {
-               $this->setExpectedException( MWException::class, "Invalid language code \"$code\"" );
-
-               $callback( $code );
-       }
-
-       public static function provideExceptionFromInvalidCode() {
-               $ret = [];
-               foreach ( static::provideIsValidBuiltInCode() as $desc => list( $code, $valid ) ) {
-                       if ( $valid ) {
-                               // Won't get an exception from this one
-                               continue;
-                       }
-
-                       // For getFileName, we define an anonymous function because of the extra first param
-                       $ret["getFileName: $desc"] = [
-                               function ( $code ) {
-                                       return static::getFileName( 'Messages', $code );
-                               },
-                               $code
-                       ];
-
-                       $ret["getMessagesFileName: $desc"] =
-                               [ [ static::class, 'getMessagesFileName' ], $code ];
-
-                       $ret["getJsonMessagesFileName: $desc"] =
-                               [ [ static::class, 'getJsonMessagesFileName' ], $code ];
-               }
-               return $ret;
-       }
-}
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