* 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.
* 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.)
=== Other changes in 1.34 ===
* …
'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',
'store' => 'detect',
'storeClass' => false,
'storeDirectory' => false,
- 'storeServer' => [],
- 'forceRecache' => false,
'manualRecache' => false,
];
/**
* List of Days options to list in the Special:Recentchanges and
* Special:Recentchangeslinked pages.
+ *
+ * @see ChangesListSpecialPage::getLinkDays
*/
$wgRCLinkDays = [ 1, 3, 7, 14, 30 ];
$services->getNamespaceInfo()->getCanonicalName( NS_MEDIA ), '/' );
$medians .= '|';
$medians .= preg_quote(
- MediaWikiServices::getInstance()->getContentLanguage()->getNsText( NS_MEDIA ),
+ $services->getContentLanguage()->getNsText( NS_MEDIA ),
'/'
) . '):';
}
if ( $match[1] !== false && $match[1] !== '' ) {
if ( preg_match(
- MediaWikiServices::getInstance()->getContentLanguage()->linkTrail(),
+ $services->getContentLanguage()->linkTrail(),
$match[3],
$submatch
) ) {
Title::newFromText( $linkTarget );
try {
- $target = MediaWikiServices::getInstance()->getTitleParser()->
+ $target = $services->getTitleParser()->
parseTitle( $linkTarget );
if ( $target->getText() == '' && !$target->isExternal()
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\Http\HttpRequestFactory;
-use MediaWiki\Languages\LanguageNameUtils;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Preferences\PreferencesFactory;
return $this->getService( 'InterwikiLookup' );
}
- /**
- * @since 1.34
- * @return LanguageNameUtils
- */
- public function getLanguageNameUtils() {
- return $this->getService( 'LanguageNameUtils' );
- }
-
/**
* @since 1.28
* @return LinkCache
return $this->getService( 'LinkRendererFactory' );
}
- /**
- * @since 1.34
- * @return LocalisationCache
- */
- public function getLocalisationCache() : LocalisationCache {
- return $this->getService( 'LocalisationCache' );
- }
-
/**
* @since 1.28
* @return \BagOStuff
$title = $this->getTitle();
$ns = $title->getNamespace();
- $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+ $nsInfo = $services->getNamespaceInfo();
$canonicalNamespace = $nsInfo->exists( $ns )
? $nsInfo->getCanonicalName( $ns )
: $title->getNsText();
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;
);
},
- 'LanguageNameUtils' => function ( MediaWikiServices $services ) : LanguageNameUtils {
- return new LanguageNameUtils( new ServiceOptions(
- LanguageNameUtils::$constructorOptions,
- $services->getMainConfig()
- ) );
- },
-
'LinkCache' => function ( MediaWikiServices $services ) : LinkCache {
return new LinkCache(
$services->getTitleFormatter(),
);
},
- 'LocalisationCache' => function ( MediaWikiServices $services ) : LocalisationCache {
- $conf = $services->getMainConfig()->get( 'LocalisationCacheConf' );
-
- $logger = LoggerFactory::getInstance( 'localisation' );
-
- $store = LocalisationCache::getStoreFromConf(
- $conf, $services->getMainConfig()->get( 'CacheDirectory' ) );
- $logger->debug( 'LocalisationCache: using store ' . get_class( $store ) );
-
- 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()
- ),
- $store,
- $logger,
- [ function () use ( $services ) {
- $services->getResourceLoader()->getMessageBlobStore()->clear();
- } ],
- $services->getLanguageNameUtils()
- );
- },
-
'LocalServerObjectCache' => function ( MediaWikiServices $services ) : BagOStuff {
$config = $services->getMainConfig();
$cacheId = \ObjectCache::detectLocalServerCache();
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'],
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;
}
// 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() );
$undoTooltip = $latest
? [ 'title' => $this->msg( 'tooltip-undo' )->text() ]
: [];
- $undolink = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+ $undolink = $this->getLinkRenderer()->makeKnownLink(
$this->getTitle(),
$this->msg( 'editundo' )->text(),
$undoTooltip,
) {
return $cur;
} else {
- return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+ return $this->getLinkRenderer()->makeKnownLink(
$this->getTitle(),
new HtmlArmor( $cur ),
[],
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(
// Need gender information
if (
- MediaWikiServices::getInstance()->getNamespaceInfo()->
+ $services->getNamespaceInfo()->
hasGenderDistinction( $titleObj->getNamespace() )
) {
$usernames[] = $titleObj->getText();
$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 ) {
*/
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.
// 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 );
$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 );
}
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 ->
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().
*/
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
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.
private $mergeableKeys = null;
/**
- * Return a suitable LCStore as specified by the given configuration.
+ * For constructor parameters, see the documentation in DefaultSettings.php
+ * for $wgLocalisationCacheConf.
*
- * @param array $conf In the format of $wgLocalisationCacheConf
- * @param string|false|null $fallbackCacheDir In case 'storeDirectory' isn't specified
- * @return LCStore
+ * @param array $conf
+ * @throws MWException
*/
- public static function getStoreFromConf( array $conf, $fallbackCacheDir ) : LCStore {
+ function __construct( $conf ) {
+ global $wgCacheDirectory;
+
+ $this->conf = $conf;
+ $this->logger = LoggerFactory::getInstance( 'localisation' );
+
+ $directory = !empty( $conf['storeDirectory'] ) ? $conf['storeDirectory'] : $wgCacheDirectory;
$storeArg = [];
- $storeArg['directory'] =
- $conf['storeDirectory'] ?: $fallbackCacheDir;
+ $storeArg['directory'] = $directory;
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.'
- );
+ 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" );
- return new $storeClass( $storeArg );
- }
-
- /**
- * @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
- * @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' );
+ $this->store = new $storeClass( $storeArg );
+ foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
+ if ( isset( $conf[$var] ) ) {
+ $this->$var = $conf[$var];
+ }
+ }
}
/**
* @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;
$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;
# 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.' );
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 {
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;
}
/**
* @throws MWException
*/
public function recache( $code ) {
+ global $wgExtensionMessagesFiles;
+
if ( !$code ) {
throw new MWException( "Invalid language code requested" );
}
# 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;
# 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();
}
}
$this->store = new LCStoreNull;
$this->manualRecache = false;
}
+
}
'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' ),
// 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' );
$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(
/**
* 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
+++ /dev/null
-<?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";
- }
-}
$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.
// 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();
} 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;
}
/**
) );
// 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 );
}
} );
}
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\ScopedCallback;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
use Wikimedia\WaitConditionLoop;
/**
$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;
}
/**
* @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()
);
}
$serversDoneCount = 0,
&$keysDeletedCount = 0
) {
- $cutoffUnix = wfTimestamp( TS_UNIX, $timestamp );
+ $cutoffUnix = ConvertibleTimestamp::convert( TS_UNIX, $timestamp );
$shardIndexes = range( 0, $this->numTableShards - 1 );
shuffle( $shardIndexes );
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 = [];
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 {
}
/**
- * 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" );
+ }
}
}
}
* @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
*/
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 );
}
? $dir[$this->mOrderType]
: $dir;
}
+ $this->linkRenderer = $linkRenderer;
}
/**
$attrs['class'] = "mw-{$type}link";
}
- return Linker::linkKnown(
+ return $this->getLinkRenderer()->makeKnownLink(
$this->getTitle(),
- $text,
+ new HtmlArmor( $text ),
$attrs,
$query + $this->getDefaultQuery()
);
return $prevNext->buildPrevNextNavigation( $title, $offset, $limit, $query, $atend );
}
+
+ protected function getLinkRenderer() {
+ if ( $this->linkRenderer === null ) {
+ $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+ }
+ return $this->linkRenderer;
+ }
}
* @ingroup Pager
*/
+use MediaWiki\Linker\LinkRenderer;
+
/**
* Table-based display with a user-selectable sort order
* @ingroup Pager
/** @var stdClass */
protected $mCurrentRow;
- public function __construct( IContextSource $context = null ) {
- if ( $context ) {
- $this->setContext( $context );
- }
+ public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
+ parent::__construct( $context, $linkRenderer );
$this->mSort = $this->getRequest()->getText( 'sort' );
if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
} elseif ( $this->getRequest()->getBool( 'desc' ) ) {
$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
} /* Else leave it at whatever the class default is */
-
- parent::__construct();
}
/**
}
}
+ /**
+ * @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.
'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(),
]
);
sort( $linkLimits );
$linkLimits = array_unique( $linkLimits );
- $linkDays = $config->get( 'RCLinkDays' );
+ $linkDays = $this->getLinkDays();
$linkDays[] = $options['days'];
sort( $linkDays );
$linkDays = array_unique( $linkDays );
}
function formatValue( $field, $value ) {
- $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+ $linkRenderer = $this->getLinkRenderer();
switch ( $field ) {
case 'am_title' :
$title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
$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 ) {
* @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() {
$formatted = '';
- $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+ $linkRenderer = $this->getLinkRenderer();
switch ( $name ) {
case 'ipb_timestamp':
*/
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 ) {
*/
class CategoryPager extends AlphabeticPager {
- /**
- * @var LinkRenderer
- */
- protected $linkRenderer;
-
/**
* @param IContextSource $context
* @param string $from
*/
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() {
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";
$classes = [];
$attribs = [];
- $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+ $linkRenderer = $this->getLinkRenderer();
$page = null;
// Create a title for the revision if possible
function formatRevisionRow( $row ) {
$page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
- $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+ $linkRenderer = $this->getLinkRenderer();
$rev = new Revision( [
'title' => $page,
public function __construct( IContextSource $context, $userName = null, $search = '',
$including = false, $showAll = false
) {
- $this->setContext( $context );
+ parent::__construct( $context );
+
$this->mIncluding = $including;
$this->mShowAll = $showAll;
} else {
$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
}
-
- parent::__construct( $context );
}
/**
*/
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 ) ];
// 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();
$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()
);
public $mConds;
private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
- /**
- * @var LinkRenderer
- */
- private $linkRenderer;
-
/**
* @param SpecialPage $form
* @param array $conds
$sizetype, $size, $indefonly, $cascadeonly, $noredirect,
LinkRenderer $linkRenderer
) {
+ parent::__construct( $form->getContext(), $linkRenderer );
$this->mConds = $conds;
$this->type = $type ?: 'edit';
$this->level = $level;
$this->indefonly = (bool)$indefonly;
$this->cascadeonly = (bool)$cascadeonly;
$this->noredirect = (bool)$noredirect;
- $this->linkRenderer = $linkRenderer;
- parent::__construct( $form->getContext() );
}
function preprocessResults( $result ) {
function formatValue( $field, $value ) {
/** @var object $row */
$row = $this->mCurrentRow;
+ $linkRenderer = $this->getLinkRenderer();
switch ( $field ) {
case 'log_timestamp':
)
);
} else {
- $formatted = $this->linkRenderer->makeLink( $title );
+ $formatted = $linkRenderer->makeLink( $title );
}
if ( !is_null( $row->page_len ) ) {
$formatted .= $this->getLanguage()->getDirMark() .
$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(),
[],
* another page
*/
public function __construct( IContextSource $context = null, $par = null, $including = null ) {
- if ( $context ) {
- $this->setContext( $context );
- }
-
$request = $this->getRequest();
$par = $par ?? '';
$parms = explode( '/', $par );
}
}
- parent::__construct();
+ parent::__construct( $context );
}
/**
*/
use CLDRPluralRuleParser\Evaluator;
-use MediaWiki\Languages\LanguageNameUtils;
use MediaWiki\MediaWikiServices;
+use Wikimedia\Assert\Assert;
/**
* Internationalisation code
/**
* 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
*/
public $transformData = [];
- /** @var LocalisationCache */
- private $localisationCache;
-
- /** @var LanguageNameUtils */
- private $langNameUtils;
+ /**
+ * @var LocalisationCache
+ */
+ public static $dataCache;
public static $mLangObjCache = [];
*/
const STRICT_FALLBACKS = 1;
- // TODO Make these const once we drop HHVM support (T192166)
public static $mWeekdayMsgs = [
'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday'
*/
private static $grammarTransformations;
+ /**
+ * Cache for language names
+ * @var HashBagOStuff|null
+ */
+ private static $languageNameCache;
+
/**
* Unicode directional formatting characters, for embedBidi()
*/
* @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;
// 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'" );
}
* @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 ) );
}
/**
* 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() {
} else {
$this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
}
- $services = MediaWikiServices::getInstance();
- $this->localisationCache = $services->getLocalisationCache();
- $this->langNameUtils = $services->getLanguageNameUtils();
+ self::getLocalisationCache();
}
/**
* @return array
*/
public function getBookstoreList() {
- return $this->localisationCache->getItem( $this->mCode, 'bookstoreList' );
+ return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
}
/**
getCanonicalNamespaces();
$this->namespaceNames = $wgExtraNamespaces +
- $this->localisationCache->getItem( $this->mCode, 'namespaceNames' );
+ self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
$this->namespaceNames += $validNamespaces;
$this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
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 );
}
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;
}
}
*/
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 {
}
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;
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 {
* @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';
* @return array
*/
public function getDatePreferenceMigrationMap() {
- return $this->localisationCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
+ return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
}
/**
/**
* 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:
* @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)
$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];
}
/**
}
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 );
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;
* @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' );
}
/**
* @return string
*/
function fallback8bitEncoding() {
- return $this->localisationCache->getItem( $this->mCode, 'fallback8bitEncoding' );
+ return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
}
/**
* @return bool
*/
function isRTL() {
- return $this->localisationCache->getItem( $this->mCode, 'rtl' );
+ return self::$dataCache->getItem( $this->mCode, 'rtl' );
}
/**
* @return array
*/
function capitalizeAllNouns() {
- return $this->localisationCache->getItem( $this->mCode, 'capitalizeAllNouns' );
+ return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
}
/**
* @return bool
*/
function linkPrefixExtension() {
- return $this->localisationCache->getItem( $this->mCode, 'linkPrefixExtension' );
+ return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
}
/**
* @return array
*/
function getMagicWords() {
- return $this->localisationCache->getItem( $this->mCode, 'magicWords' );
+ return self::$dataCache->getItem( $this->mCode, 'magicWords' );
}
/**
*/
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\"" );
if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
// Initialise array
$this->mExtendedSpecialPageAliases =
- $this->localisationCache->getItem( $this->mCode, 'specialPageAliases' );
+ self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
}
return $this->mExtendedSpecialPageAliases;
* @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' );
}
/**
* @return string
*/
public function linkTrail() {
- return $this->localisationCache->getItem( $this->mCode, 'linkTrail' );
+ return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
}
/**
* @return string
*/
public function linkPrefixCharset() {
- return $this->localisationCache->getItem( $this->mCode, 'linkPrefixCharset' );
+ return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
}
/**
/**
* 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
* @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";
}
/**
* @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;
}
* @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;
}
* @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;
}
* 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
* @ingroup Maintenance
*/
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\MediaWikiServices;
-
require_once __DIR__ . '/Maintenance.php';
/**
}
public function execute() {
- global $wgLocalisationCacheConf, $wgCacheDirectory;
+ global $wgLocalisationCacheConf;
$force = $this->hasOption( 'force' );
$threads = $this->getOption( 'threads', 1 );
$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()
- ),
- LocalisationCache::getStoreFromConf( $conf, $wgCacheDirectory ),
- 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' ) ) {
# 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",
$wgRequest = new FauxRequest();
MediaWiki\Session\SessionManager::resetCache();
- Language::clearCaches();
}
public function run( PHPUnit_Framework_TestResult $result = null ) {
'wgExtraInterlanguageLinkPrefixes' => [ 'self' ],
'wgExtraLanguageNames' => [ 'self' => 'Recursion' ],
] );
- $this->resetServices();
MessageCache::singleton()->enable();
<?php
-
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-use Psr\Log\NullLogger;
-
/**
* @group Database
* @group Cache
*/
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' )
return $lc;
}
- public function testPluralRulesFallback() {
+ public function testPuralRulesFallback() {
$cache = $this->getMockLocalisationCache();
$this->assertEquals(
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();
}
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
);
}
+ /**
+ * 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
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 );
$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' ],
+ ];
}
/**
[ 'è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' ) );
- }
}
+++ /dev/null
-<?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 );
- }
-}
+++ /dev/null
-<?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�", false ],
- 'Hex char reference 0' => [ "a�", false ],
- 'Hex char reference A' => [ "a
", 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;
- }
-}