* 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.)
+* Building a new SearchResult is hard-deprecated, always call
+ SearchResult::newFromTitle(). This class is being refactored into an abstract
+ class. If you extend this class please be sure to override all its methods
+ or extend RevisionSearchResult.
=== Other changes in 1.34 ===
* …
'ConvertExtensionToRegistration' => __DIR__ . '/maintenance/convertExtensionToRegistration.php',
'ConvertLinks' => __DIR__ . '/maintenance/convertLinks.php',
'ConvertUserOptions' => __DIR__ . '/maintenance/convertUserOptions.php',
- 'ConverterRule' => __DIR__ . '/languages/ConverterRule.php',
+ 'ConverterRule' => __DIR__ . '/includes/language/ConverterRule.php',
'Cookie' => __DIR__ . '/includes/libs/Cookie.php',
'CookieJar' => __DIR__ . '/includes/libs/CookieJar.php',
'CopyFileBackend' => __DIR__ . '/maintenance/copyFileBackend.php',
'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php',
'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
'MediaWiki\\FileBackend\\FSFile\\TempFSFileFactory' => __DIR__ . '/includes/libs/filebackend/fsfile/TempFSFileFactory.php',
+ 'MediaWiki\\FileBackend\\LockManager\\LockManagerGroupFactory' => __DIR__ . '/includes/filebackend/lockmanager/LockManagerGroupFactory.php',
'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
'MediaWiki\\Http\\HttpRequestFactory' => __DIR__ . '/includes/http/HttpRequestFactory.php',
'MediaWiki\\Installer\\InstallException' => __DIR__ . '/includes/installer/InstallException.php',
'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',
'RevisionItemBase' => __DIR__ . '/includes/revisionlist/RevisionItemBase.php',
'RevisionList' => __DIR__ . '/includes/revisionlist/RevisionList.php',
'RevisionListBase' => __DIR__ . '/includes/revisionlist/RevisionListBase.php',
+ 'RevisionSearchResult' => __DIR__ . '/includes/search/RevisionSearchResult.php',
+ 'RevisionSearchResultTrait' => __DIR__ . '/includes/search/RevisionSearchResultTrait.php',
'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php',
'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php',
'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php',
'SearchResult' => __DIR__ . '/includes/search/SearchResult.php',
'SearchResultSet' => __DIR__ . '/includes/search/SearchResultSet.php',
'SearchResultSetTrait' => __DIR__ . '/includes/search/SearchResultSetTrait.php',
+ 'SearchResultTrait' => __DIR__ . '/includes/search/SearchResultTrait.php',
'SearchSqlite' => __DIR__ . '/includes/search/SearchSqlite.php',
'SearchSuggestion' => __DIR__ . '/includes/search/SearchSuggestion.php',
'SearchSuggestionSet' => __DIR__ . '/includes/search/SearchSuggestionSet.php',
'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 ];
// Don't share DB, storage, or memcached connections
MediaWikiServices::resetChildProcessServices();
FileBackendGroup::destroySingleton();
- LockManagerGroup::destroySingletons();
JobQueueGroup::destroySingletons();
ObjectCache::clear();
RedisConnectionPool::destroySingletons();
$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\FileBackend\LockManager\LockManagerGroupFactory;
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
return $this->getService( 'LocalServerObjectCache' );
}
+ /**
+ * @since 1.34
+ * @return LockManagerGroupFactory
+ */
+ public function getLockManagerGroupFactory() : LockManagerGroupFactory {
+ return $this->getService( 'LockManagerGroupFactory' );
+ }
+
/**
* @since 1.32
* @return MagicWordFactory
$title = $this->getTitle();
$ns = $title->getNamespace();
- $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+ $nsInfo = $services->getNamespaceInfo();
$canonicalNamespace = $nsInfo->exists( $ns )
? $nsInfo->getCanonicalName( $ns )
: $title->getNsText();
namespace MediaWiki\Rest;
use ExtensionRegistry;
+use MediaWiki;
use MediaWiki\MediaWikiServices;
use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
use RequestContext;
private $webResponse;
/** @var Router */
private $router;
+ /** @var RequestContext */
+ private $context;
public static function main() {
// URL safety checks
return;
}
+ $context = RequestContext::getMain();
+
// Set $wgTitle and the title in RequestContext, as in api.php
global $wgTitle;
$wgTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/rest.php' );
- RequestContext::getMain()->setTitle( $wgTitle );
+ $context->setTitle( $wgTitle );
$services = MediaWikiServices::getInstance();
$conf = $services->getMainConfig();
'cookiePrefix' => $conf->get( 'CookiePrefix' )
] );
- $authorizer = new MWBasicAuthorizer( RequestContext::getMain()->getUser(),
+ $authorizer = new MWBasicAuthorizer( $context->getUser(),
$services->getPermissionManager() );
global $IP;
);
$entryPoint = new self(
+ $context,
$request,
$wgRequest->response(),
$router );
$entryPoint->execute();
}
- public function __construct( RequestInterface $request, WebResponse $webResponse,
- Router $router
+ public function __construct( RequestContext $context, RequestInterface $request,
+ WebResponse $webResponse, Router $router
) {
+ $this->context = $context;
$this->request = $request;
$this->webResponse = $webResponse;
$this->router = $router;
}
public function execute() {
+ ob_start();
$response = $this->router->execute( $this->request );
$this->webResponse->header(
}
// Clear all errors that might have been displayed if display_errors=On
- ob_clean();
+ ob_end_clean();
$stream = $response->getBody();
$stream->rewind();
+
+ MediaWiki::preOutputCommit( $this->context );
+
if ( $stream instanceof CopyableStreamInterface ) {
$stream->copyToStream( fopen( 'php://output', 'w' ) );
} else {
echo $buffer;
}
}
+
+ $mw = new MediaWiki;
+ $mw->doPostOutputShutdown( 'fast' );
}
}
use MediaWiki\Config\ConfigRepository;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Interwiki\ClassicInterwikiLookup;
use MediaWiki\Interwiki\InterwikiLookup;
-use MediaWiki\Languages\LanguageNameUtils;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\Linker\LinkRendererFactory;
use MediaWiki\Logger\LoggerFactory;
);
},
- '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' );
-
- // Figure out what class to use for the LCStore
- $storeArg = [];
- $storeArg['directory'] =
- $conf['storeDirectory'] ?? $services->getMainConfig()->get( 'CacheDirectory' );
-
- if ( !empty( $conf['storeClass'] ) ) {
- $storeClass = $conf['storeClass'];
- } elseif ( $conf['store'] === 'files' || $conf['store'] === 'file' ||
- ( $conf['store'] === 'detect' && $storeArg['directory'] )
- ) {
- $storeClass = LCStoreCDB::class;
- } elseif ( $conf['store'] === 'db' || $conf['store'] === 'detect' ) {
- $storeClass = LCStoreDB::class;
- $storeArg['server'] = $conf['storeServer'] ?? [];
- } elseif ( $conf['store'] === 'array' ) {
- $storeClass = LCStoreStaticArray::class;
- } else {
- throw new MWException(
- 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
- );
- }
- $logger->debug( "LocalisationCache: using store $storeClass" );
-
- return new $conf['class'](
- new ServiceOptions(
- LocalisationCache::$constructorOptions,
- // Two of the options are stored in $wgLocalisationCacheConf
- $conf,
- // In case someone set that config variable and didn't reset all keys, set defaults.
- [
- 'forceRecache' => false,
- 'manualRecache' => false,
- ],
- // Some other options come from config itself
- $services->getMainConfig()
- ),
- new $storeClass( $storeArg ),
- $logger,
- [ function () use ( $services ) {
- $services->getResourceLoader()->getMessageBlobStore()->clear();
- } ],
- $services->getLanguageNameUtils()
- );
- },
-
'LocalServerObjectCache' => function ( MediaWikiServices $services ) : BagOStuff {
$config = $services->getMainConfig();
$cacheId = \ObjectCache::detectLocalServerCache();
return \ObjectCache::newFromParams( $config->get( 'ObjectCaches' )[$cacheId] );
},
+ 'LockManagerGroupFactory' => function ( MediaWikiServices $services ) : LockManagerGroupFactory {
+ return new LockManagerGroupFactory(
+ WikiMap::getCurrentWikiDbDomain()->getId(),
+ $services->getMainConfig()->get( 'LockManagers' ),
+ $services->getDBLoadBalancerFactory()
+ );
+ },
+
'MagicWordFactory' => function ( MediaWikiServices $services ) : MagicWordFactory {
return new MagicWordFactory( $services->getContentLanguage() );
},
// Install a header callback
MediaWiki\HeaderCallback::register();
-// Set the encoding used by reading HTTP input, writing HTTP output.
+// Set the encoding used by PHP for reading HTTP input, and writing output.
// This is also the default for mbstring functions.
mb_internal_encoding( 'UTF-8' );
* Main setup
*/
-$fname = 'Setup.php';
-$ps_setup = Profiler::instance()->scopedProfileIn( $fname );
-
// Load queued extensions
ExtensionRegistry::getInstance()->loadFromQueue();
// Don't let any other extensions load
setlocale( LC_ALL, $wgShellLocale );
// Set various default paths sensibly...
-$ps_default = Profiler::instance()->scopedProfileIn( $fname . '-defaults' );
-
if ( $wgScript === false ) {
$wgScript = "$wgScriptPath/index.php";
}
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'],
$wgPHPSessionHandling = MW_NO_SESSION === 'warn' ? 'warn' : 'disable';
}
-Profiler::instance()->scopedProfileOut( $ps_default );
-
// Disable MWDebug for command line mode, this prevents MWDebug from eating up
// all the memory from logging SQL queries on maintenance scripts
global $wgCommandLineMode;
}
}
-$ps_default2 = Profiler::instance()->scopedProfileIn( $fname . '-defaults2' );
-
if ( $wgCanonicalServer === false ) {
$wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
}
);
}
-Profiler::instance()->scopedProfileOut( $ps_default2 );
-
-$ps_misc = Profiler::instance()->scopedProfileIn( $fname . '-misc' );
-
// Raise the memory limit if it's too low
// Note, this makes use of wfDebug, and thus should not be before
// MWDebug::init() is called.
', session: ' . get_class( ObjectCache::getInstance( $wgSessionCacheType ) )
);
-Profiler::instance()->scopedProfileOut( $ps_misc );
-
// Most of the config is out, some might want to run hooks here.
Hooks::run( 'SetupAfterCache' );
-$ps_globals = Profiler::instance()->scopedProfileIn( $fname . '-globals' );
-
/**
* @var Language $wgContLang
* @deprecated since 1.32, use the ContentLanguage service directly
*/
$wgTitle = null;
-Profiler::instance()->scopedProfileOut( $ps_globals );
-$ps_extensions = Profiler::instance()->scopedProfileIn( $fname . '-extensions' );
-
// Extension setup functions
// Entries should be added to this variable during the inclusion
// of the extension file. This allows the extension to perform
}
$wgFullyInitialised = true;
-
-Profiler::instance()->scopedProfileOut( $ps_extensions );
-Profiler::instance()->scopedProfileOut( $ps_setup );
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 ) {
$this->dieWithError( [ 'apierror-bad-badfilecontexttitle', $p ], 'invalid-title' );
}
} else {
- $badFileContextTitle = false;
+ $badFileContextTitle = null;
}
$pageIds = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
"apiwarn-deprecation-missingparam": "Comme <var>$1</var> n’a pas été spécifié, un format ancien a été utilisé pour la sortie. Ce format est obsolète, et dans le futur, le nouveau format sera toujours utilisé.",
"apiwarn-deprecation-parameter": "Le paramètre <var>$1</var> est désuet.",
"apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MédiaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
+ "apiwarn-deprecation-post-without-content-type": "Une requête POST a été faite sans entête <code>Content-Type</code>. Cela ne fonctionne pas de façon fiable.",
"apiwarn-deprecation-purge-get": "L’utilisation de <kbd>action=purge</kbd> via un GET est désuète. Utiliser POST à la place.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est désuet. Veuillez utiliser <kbd>$2</kbd> à la place.",
"apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
"apierror-unknownformat": "Okänt format \"$1\".",
"apiwarn-compare-no-next": "Sidversion $2 är den senaste sidversionen av $1, det finns ingen sidversion för <kbd>torelative=next</kbd> att jämföra med.",
"apiwarn-compare-no-prev": "Sidversionen $2 är den tidigaste sidversion för $1, det finns ingen sidversion för <kbd>torelative=prev</kbd> att jämföra med.",
+ "apiwarn-deprecation-post-without-content-type": "En POST-begäran gjordes utan en <code>Content-Type</code> i sidhuvudet. Det fungerar inte ordentligt.",
"api-feed-error-title": "Fel ($1)"
}
"科劳",
"SolidBlock",
"神樂坂秀吉",
- "94rain"
+ "94rain",
+ "予弦"
]
},
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>MediaWiki API是一个成熟稳定的,不断受到支持和改进的界面。尽管我们尽力避免,但偶尔也需要作出重大更新;请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。</p>",
*/
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 );
}
$block = DatabaseBlock::newFromID( $blockCookieId );
if (
$block instanceof DatabaseBlock &&
- $this->shouldApplyCookieBlock( $block, $user->isAnon() )
+ $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
) {
return $block;
}
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;
- /**
- * @todo Make this a const when HHVM support is dropped (T192166)
- *
- * @var array
- * @since 1.34
- */
- public static $constructorOptions = [
- // True to treat all files as expired until they are regenerated by this object.
- 'forceRecache',
- 'manualRecache',
- 'ExtensionMessagesFiles',
- 'MessagesDirs',
- ];
-
/**
* For constructor parameters, see the documentation in DefaultSettings.php
* for $wgLocalisationCacheConf.
*
- * Do not construct this directly. Use MediaWikiServices.
- *
- * @param ServiceOptions $options
- * @param LCStore $store What backend to use for storage
- * @param LoggerInterface $logger
- * @param callable[] $clearStoreCallbacks To be called whenever the cache is cleared. Can be
- * used to clear other caches that depend on this one, such as ResourceLoader's
- * MessageBlobStore.
- * @param LanguageNameUtils $langNameUtils
+ * @param array $conf
* @throws MWException
*/
- function __construct(
- ServiceOptions $options,
- LCStore $store,
- LoggerInterface $logger,
- array $clearStoreCallbacks,
- LanguageNameUtils $langNameUtils
- ) {
- $options->assertRequiredOptions( self::$constructorOptions );
-
- $this->options = $options;
- $this->store = $store;
- $this->logger = $logger;
- $this->clearStoreCallbacks = $clearStoreCallbacks;
- $this->langNameUtils = $langNameUtils;
-
- // Keep this separate from $this->options so it can be mutable
- $this->manualRecache = $options->get( 'manualRecache' );
+ function __construct( $conf ) {
+ global $wgCacheDirectory;
+
+ $this->conf = $conf;
+ $this->logger = LoggerFactory::getInstance( 'localisation' );
+
+ $directory = !empty( $conf['storeDirectory'] ) ? $conf['storeDirectory'] : $wgCacheDirectory;
+ $storeArg = [];
+ $storeArg['directory'] = $directory;
+
+ if ( !empty( $conf['storeClass'] ) ) {
+ $storeClass = $conf['storeClass'];
+ } else {
+ switch ( $conf['store'] ) {
+ case 'files':
+ case 'file':
+ $storeClass = LCStoreCDB::class;
+ break;
+ case 'db':
+ $storeClass = LCStoreDB::class;
+ $storeArg['server'] = $conf['storeServer'] ?? [];
+ break;
+ case 'array':
+ $storeClass = LCStoreStaticArray::class;
+ break;
+ case 'detect':
+ if ( $directory ) {
+ $storeClass = LCStoreCDB::class;
+ } else {
+ $storeClass = LCStoreDB::class;
+ $storeArg['server'] = $conf['storeServer'] ?? [];
+ }
+ break;
+ default:
+ throw new MWException(
+ 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
+ );
+ }
+ }
+ $this->logger->debug( static::class . ": using store $storeClass" );
+
+ $this->store = new $storeClass( $storeArg );
+ foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
+ if ( isset( $conf[$var] ) ) {
+ $this->$var = $conf[$var];
+ }
+ }
}
/**
* @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' ),
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Logger\LoggerFactory;
+use Wikimedia\Rdbms\LBFactory;
/**
* Class to handle file lock manager registration
* @since 1.19
*/
class LockManagerGroup {
- /** @var LockManagerGroup[] (domain => LockManagerGroup) */
- protected static $instances = [];
+ /** @var string domain (usually wiki ID) */
+ protected $domain;
- protected $domain; // string; domain (usually wiki ID)
+ /** @var LBFactory */
+ protected $lbFactory;
/** @var array Array of (name => ('class' => ..., 'config' => ..., 'instance' => ...)) */
protected $managers = [];
/**
+ * Do not call this directly. Use LockManagerGroupFactory.
+ *
* @param string $domain Domain (usually wiki ID)
+ * @param array[] $lockManagerConfigs In format of $wgLockManagers
+ * @param LBFactory $lbFactory
*/
- protected function __construct( $domain ) {
+ public function __construct( $domain, array $lockManagerConfigs, LBFactory $lbFactory ) {
$this->domain = $domain;
- }
-
- /**
- * @param bool|string $domain Domain (usually wiki ID). Default: false.
- * @return LockManagerGroup
- */
- public static function singleton( $domain = false ) {
- if ( $domain === false ) {
- $domain = WikiMap::getCurrentWikiDbDomain()->getId();
- }
+ $this->lbFactory = $lbFactory;
- if ( !isset( self::$instances[$domain] ) ) {
- self::$instances[$domain] = new self( $domain );
- self::$instances[$domain]->initFromGlobals();
- }
-
- return self::$instances[$domain];
- }
-
- /**
- * Destroy the singleton instances
- */
- public static function destroySingletons() {
- self::$instances = [];
- }
-
- /**
- * Register lock managers from the global variables
- */
- protected function initFromGlobals() {
- global $wgLockManagers;
-
- $this->register( $wgLockManagers );
- }
-
- /**
- * Register an array of file lock manager configurations
- *
- * @param array $configs
- * @throws Exception
- */
- protected function register( array $configs ) {
- foreach ( $configs as $config ) {
+ foreach ( $lockManagerConfigs as $config ) {
$config['domain'] = $this->domain;
if ( !isset( $config['name'] ) ) {
throw new Exception( "Cannot register a lock manager with no name." );
}
}
+ /**
+ * @deprecated since 1.34, use LockManagerGroupFactory
+ *
+ * @param bool|string $domain Domain (usually wiki ID). Default: false.
+ * @return LockManagerGroup
+ */
+ public static function singleton( $domain = false ) {
+ return MediaWikiServices::getInstance()->getLockManagerGroupFactory()
+ ->getLockManagerGroup( $domain );
+ }
+
+ /**
+ * Destroy the singleton instances
+ *
+ * @deprecated since 1.34, use resetServiceForTesting() on LockManagerGroupFactory
+ */
+ public static function destroySingletons() {
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
+ }
+
/**
* Get the lock manager object with a given name
*
$class = $this->managers[$name]['class'];
$config = $this->managers[$name]['config'];
if ( $class === DBLockManager::class ) {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $lb = $lbFactory->getMainLB( $config['domain'] );
+ $lb = $this->lbFactory->getMainLB( $config['domain'] );
$config['dbServers']['localDBMaster'] = $lb->getLazyConnectionRef(
DB_MASTER,
[],
}
$config['logger'] = LoggerFactory::getInstance( 'LockManager' );
+ // XXX Looks like phan is right, we are trying to instantiate an abstract class and it
+ // throws. Did this ever work? Presumably we need to detect the right subclass? Or
+ // should we just get rid of this? It looks like it never worked since it was first
+ // introduced by 0cf832a3394 in 2016, so if no one's complained until now, clearly it
+ // can't be very useful?
// @phan-suppress-next-line PhanTypeInstantiateAbstract
$this->managers[$name]['instance'] = new $class( $config );
}
* Get the default lock manager configured for the site.
* Returns NullLockManager if no lock manager could be found.
*
+ * XXX This looks unused, should we just get rid of it?
+ *
* @return LockManager
*/
public function getDefault() {
* or at least some other effective configured lock manager.
* Throws an exception if no lock manager could be found.
*
+ * XXX This looks unused, should we just get rid of it?
+ *
* @return LockManager
* @throws Exception
*/
--- /dev/null
+<?php
+
+namespace MediaWiki\FileBackend\LockManager;
+
+use LockManagerGroup;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * Service to construct LockManagerGroups.
+ */
+class LockManagerGroupFactory {
+ /** @var string */
+ private $defaultDomain;
+
+ /** @var array */
+ private $lockManagerConfigs;
+
+ /** @var LBFactory */
+ private $lbFactory;
+
+ /** @var LockManagerGroup[] (domain => LockManagerGroup) */
+ private $instances = [];
+
+ /**
+ * Do not call directly, use MediaWikiServices.
+ *
+ * @param string $defaultDomain
+ * @param array $lockManagerConfigs In format of $wgLockManagers
+ * @param LBFactory $lbFactory
+ */
+ public function __construct( $defaultDomain, array $lockManagerConfigs, LBFactory $lbFactory ) {
+ $this->defaultDomain = $defaultDomain;
+ $this->lockManagerConfigs = $lockManagerConfigs;
+ $this->lbFactory = $lbFactory;
+ }
+
+ /**
+ * @param string|bool $domain Domain (usually wiki ID). false for the default (normally the
+ * current wiki's domain).
+ * @return LockManagerGroup
+ */
+ public function getLockManagerGroup( $domain = false ) : LockManagerGroup {
+ if ( $domain === false ) {
+ $domain = $this->defaultDomain;
+ }
+
+ if ( !isset( $this->instances[$domain] ) ) {
+ $this->instances[$domain] =
+ new LockManagerGroup( $domain, $this->lockManagerConfigs, $this->lbFactory );
+ }
+
+ return $this->instances[$domain];
+ }
+}
public $mParser;
/**
- * @var Title Contextual title, used when images are being screened against
+ * @var Title|null Contextual title, used when images are being screened against
* the bad image list
*/
- protected $contextTitle = false;
+ protected $contextTitle = null;
/** @var array */
protected $mAttribs = [];
/**
* Set the contextual title
*
- * @param Title $title Contextual title
+ * @param Title|null $title Contextual title
*/
public function setContextTitle( $title ) {
$this->contextTitle = $title;
/**
* Get the contextual title, if applicable
*
- * @return Title|bool Title or false
+ * @return Title|null
*/
public function getContextTitle() {
- return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title
- ? $this->contextTitle
- : false;
+ return $this->contextTitle;
}
/**
// 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(
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Language
+ */
+
+/**
+ * Parser for rules of language conversion, parse rules in -{ }- tag.
+ * @ingroup Language
+ * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
+ */
+class ConverterRule {
+ public $mText; // original text in -{text}-
+ public $mConverter; // LanguageConverter object
+ public $mRuleDisplay = '';
+ public $mRuleTitle = false;
+ public $mRules = ''; // string : the text of the rules
+ public $mRulesAction = 'none';
+ public $mFlags = [];
+ public $mVariantFlags = [];
+ public $mConvTable = [];
+ public $mBidtable = []; // array of the translation in each variant
+ public $mUnidtable = []; // array of the translation in each variant
+
+ /**
+ * @param string $text The text between -{ and }-
+ * @param LanguageConverter $converter
+ */
+ public function __construct( $text, $converter ) {
+ $this->mText = $text;
+ $this->mConverter = $converter;
+ }
+
+ /**
+ * Check if variants array in convert array.
+ *
+ * @param array|string $variants Variant language code
+ * @return string Translated text
+ */
+ public function getTextInBidtable( $variants ) {
+ $variants = (array)$variants;
+ if ( !$variants ) {
+ return false;
+ }
+ foreach ( $variants as $variant ) {
+ if ( isset( $this->mBidtable[$variant] ) ) {
+ return $this->mBidtable[$variant];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Parse flags with syntax -{FLAG| ... }-
+ * @private
+ */
+ function parseFlags() {
+ $text = $this->mText;
+ $flags = [];
+ $variantFlags = [];
+
+ $sepPos = strpos( $text, '|' );
+ if ( $sepPos !== false ) {
+ $validFlags = $this->mConverter->mFlags;
+ $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
+ foreach ( $f as $ff ) {
+ $ff = trim( $ff );
+ if ( isset( $validFlags[$ff] ) ) {
+ $flags[$validFlags[$ff]] = true;
+ }
+ }
+ $text = strval( substr( $text, $sepPos + 1 ) );
+ }
+
+ if ( !$flags ) {
+ $flags['S'] = true;
+ } elseif ( isset( $flags['R'] ) ) {
+ $flags = [ 'R' => true ];// remove other flags
+ } elseif ( isset( $flags['N'] ) ) {
+ $flags = [ 'N' => true ];// remove other flags
+ } elseif ( isset( $flags['-'] ) ) {
+ $flags = [ '-' => true ];// remove other flags
+ } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
+ $flags['H'] = true;
+ } elseif ( isset( $flags['H'] ) ) {
+ // replace A flag, and remove other flags except T
+ $temp = [ '+' => true, 'H' => true ];
+ if ( isset( $flags['T'] ) ) {
+ $temp['T'] = true;
+ }
+ if ( isset( $flags['D'] ) ) {
+ $temp['D'] = true;
+ }
+ $flags = $temp;
+ } else {
+ if ( isset( $flags['A'] ) ) {
+ $flags['+'] = true;
+ $flags['S'] = true;
+ }
+ if ( isset( $flags['D'] ) ) {
+ unset( $flags['S'] );
+ }
+ // try to find flags like "zh-hans", "zh-hant"
+ // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
+ $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
+ if ( $variantFlags ) {
+ $variantFlags = array_flip( $variantFlags );
+ $flags = [];
+ }
+ }
+ $this->mVariantFlags = $variantFlags;
+ $this->mRules = $text;
+ $this->mFlags = $flags;
+ }
+
+ /**
+ * Generate conversion table.
+ * @private
+ */
+ function parseRules() {
+ $rules = $this->mRules;
+ $bidtable = [];
+ $unidtable = [];
+ $variants = $this->mConverter->mVariants;
+ $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
+
+ // Split according to $varsep_pattern, but ignore semicolons from HTML entities
+ $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
+ $choice = preg_split( $varsep_pattern, $rules );
+ $choice = str_replace( "\x01", ';', $choice );
+
+ foreach ( $choice as $c ) {
+ $v = explode( ':', $c, 2 );
+ if ( count( $v ) != 2 ) {
+ // syntax error, skip
+ continue;
+ }
+ $to = trim( $v[1] );
+ $v = trim( $v[0] );
+ $u = explode( '=>', $v, 2 );
+ $vv = $this->mConverter->validateVariant( $v );
+ // if $to is empty (which is also used as $from in bidtable),
+ // strtr() could return a wrong result.
+ if ( count( $u ) == 1 && $to !== '' && $vv ) {
+ $bidtable[$vv] = $to;
+ } elseif ( count( $u ) == 2 ) {
+ $from = trim( $u[0] );
+ $v = trim( $u[1] );
+ $vv = $this->mConverter->validateVariant( $v );
+ // if $from is empty, strtr() could return a wrong result.
+ if ( array_key_exists( $vv, $unidtable )
+ && !is_array( $unidtable[$vv] )
+ && $from !== ''
+ && $vv ) {
+ $unidtable[$vv] = [ $from => $to ];
+ } elseif ( $from !== '' && $vv ) {
+ $unidtable[$vv][$from] = $to;
+ }
+ }
+ // syntax error, pass
+ if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
+ $bidtable = [];
+ $unidtable = [];
+ break;
+ }
+ }
+ $this->mBidtable = $bidtable;
+ $this->mUnidtable = $unidtable;
+ }
+
+ /**
+ * @private
+ *
+ * @return string
+ */
+ function getRulesDesc() {
+ $codesep = $this->mConverter->mDescCodeSep;
+ $varsep = $this->mConverter->mDescVarSep;
+ $text = '';
+ foreach ( $this->mBidtable as $k => $v ) {
+ $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
+ }
+ foreach ( $this->mUnidtable as $k => $a ) {
+ foreach ( $a as $from => $to ) {
+ $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
+ "$codesep$to$varsep";
+ }
+ }
+ return $text;
+ }
+
+ /**
+ * Parse rules conversion.
+ * @private
+ *
+ * @param string $variant
+ *
+ * @return string
+ */
+ function getRuleConvertedStr( $variant ) {
+ $bidtable = $this->mBidtable;
+ $unidtable = $this->mUnidtable;
+
+ if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
+ return $this->mRules;
+ } else {
+ // display current variant in bidirectional array
+ $disp = $this->getTextInBidtable( $variant );
+ // or display current variant in fallbacks
+ if ( $disp === false ) {
+ $disp = $this->getTextInBidtable(
+ $this->mConverter->getVariantFallbacks( $variant ) );
+ }
+ // or display current variant in unidirectional array
+ if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
+ $disp = array_values( $unidtable[$variant] )[0];
+ }
+ // or display first text under disable manual convert
+ if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
+ if ( count( $bidtable ) > 0 ) {
+ $disp = array_values( $bidtable )[0];
+ } else {
+ $disp = array_values( array_values( $unidtable )[0] )[0];
+ }
+ }
+ return $disp;
+ }
+ }
+
+ /**
+ * Similar to getRuleConvertedStr(), but this prefers to use original
+ * page title if $variant === $this->mConverter->mMainLanguageCode
+ * and may return false in this case (so this title conversion rule
+ * will be ignored and the original title is shown).
+ *
+ * @since 1.22
+ * @param string $variant The variant code to display page title in
+ * @return string|bool The converted title or false if just page name
+ */
+ function getRuleConvertedTitle( $variant ) {
+ if ( $variant === $this->mConverter->mMainLanguageCode ) {
+ // If a string targeting exactly this variant is set,
+ // use it. Otherwise, just return false, so the real
+ // page name can be shown (and because variant === main,
+ // there'll be no further automatic conversion).
+ $disp = $this->getTextInBidtable( $variant );
+ if ( $disp ) {
+ return $disp;
+ }
+ if ( array_key_exists( $variant, $this->mUnidtable ) ) {
+ $disp = array_values( $this->mUnidtable[$variant] )[0];
+ }
+ // Assigned above or still false.
+ return $disp;
+ } else {
+ return $this->getRuleConvertedStr( $variant );
+ }
+ }
+
+ /**
+ * Generate conversion table for all text.
+ * @private
+ */
+ function generateConvTable() {
+ // Special case optimisation
+ if ( !$this->mBidtable && !$this->mUnidtable ) {
+ $this->mConvTable = [];
+ return;
+ }
+
+ $bidtable = $this->mBidtable;
+ $unidtable = $this->mUnidtable;
+ $manLevel = $this->mConverter->mManualLevel;
+
+ $vmarked = [];
+ foreach ( $this->mConverter->mVariants as $v ) {
+ /* for bidirectional array
+ fill in the missing variants, if any,
+ with fallbacks */
+ if ( !isset( $bidtable[$v] ) ) {
+ $variantFallbacks =
+ $this->mConverter->getVariantFallbacks( $v );
+ $vf = $this->getTextInBidtable( $variantFallbacks );
+ if ( $vf ) {
+ $bidtable[$v] = $vf;
+ }
+ }
+
+ if ( isset( $bidtable[$v] ) ) {
+ foreach ( $vmarked as $vo ) {
+ // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
+ // or -{H|zh:WordZh;zh-tw:WordTw}-
+ // or -{-|zh:WordZh;zh-tw:WordTw}-
+ // to introduce a custom mapping between
+ // words WordZh and WordTw in the whole text
+ if ( $manLevel[$v] == 'bidirectional' ) {
+ $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
+ }
+ if ( $manLevel[$vo] == 'bidirectional' ) {
+ $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
+ }
+ }
+ $vmarked[] = $v;
+ }
+ /* for unidirectional array fill to convert tables */
+ if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
+ && isset( $unidtable[$v] )
+ ) {
+ if ( isset( $this->mConvTable[$v] ) ) {
+ $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
+ } else {
+ $this->mConvTable[$v] = $unidtable[$v];
+ }
+ }
+ }
+ }
+
+ /**
+ * Parse rules and flags.
+ * @param string|null $variant Variant language code
+ */
+ public function parse( $variant = null ) {
+ if ( !$variant ) {
+ $variant = $this->mConverter->getPreferredVariant();
+ }
+
+ $this->parseFlags();
+ $flags = $this->mFlags;
+
+ // convert to specified variant
+ // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
+ if ( $this->mVariantFlags ) {
+ // check if current variant in flags
+ if ( isset( $this->mVariantFlags[$variant] ) ) {
+ // then convert <text to convert> to current language
+ $this->mRules = $this->mConverter->autoConvert( $this->mRules,
+ $variant );
+ } else {
+ // if current variant no in flags,
+ // then we check its fallback variants.
+ $variantFallbacks =
+ $this->mConverter->getVariantFallbacks( $variant );
+ if ( is_array( $variantFallbacks ) ) {
+ foreach ( $variantFallbacks as $variantFallback ) {
+ // if current variant's fallback exist in flags
+ if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
+ // then convert <text to convert> to fallback language
+ $this->mRules =
+ $this->mConverter->autoConvert( $this->mRules,
+ $variantFallback );
+ break;
+ }
+ }
+ }
+ }
+ $this->mFlags = $flags = [ 'R' => true ];
+ }
+
+ if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
+ // decode => HTML entities modified by Sanitizer::removeHTMLtags
+ $this->mRules = str_replace( '=>', '=>', $this->mRules );
+ $this->parseRules();
+ }
+ $rules = $this->mRules;
+
+ if ( !$this->mBidtable && !$this->mUnidtable ) {
+ if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
+ // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
+ if ( $rules !== '' ) {
+ foreach ( $this->mConverter->mVariants as $v ) {
+ $this->mBidtable[$v] = $rules;
+ }
+ }
+ } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
+ $this->mFlags = $flags = [ 'R' => true ];
+ }
+ }
+
+ $this->mRuleDisplay = false;
+ foreach ( $flags as $flag => $unused ) {
+ switch ( $flag ) {
+ case 'R':
+ // if we don't do content convert, still strip the -{}- tags
+ $this->mRuleDisplay = $rules;
+ break;
+ case 'N':
+ // process N flag: output current variant name
+ $ruleVar = trim( $rules );
+ $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
+ break;
+ case 'D':
+ // process D flag: output rules description
+ $this->mRuleDisplay = $this->getRulesDesc();
+ break;
+ case 'H':
+ // process H,- flag or T only: output nothing
+ $this->mRuleDisplay = '';
+ break;
+ case '-':
+ $this->mRulesAction = 'remove';
+ $this->mRuleDisplay = '';
+ break;
+ case '+':
+ $this->mRulesAction = 'add';
+ $this->mRuleDisplay = '';
+ break;
+ case 'S':
+ $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
+ break;
+ case 'T':
+ $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
+ $this->mRuleDisplay = '';
+ break;
+ default:
+ // ignore unknown flags (but see error case below)
+ }
+ }
+ if ( $this->mRuleDisplay === false ) {
+ $this->mRuleDisplay = '<span class="error">'
+ . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
+ . '</span>';
+ }
+
+ $this->generateConvTable();
+ }
+
+ /**
+ * Checks if there are conversion rules.
+ * @return bool
+ */
+ public function hasRules() {
+ return $this->mRules !== '';
+ }
+
+ /**
+ * Get display text on markup -{...}-
+ * @return string
+ */
+ public function getDisplay() {
+ return $this->mRuleDisplay;
+ }
+
+ /**
+ * Get converted title.
+ * @return string
+ */
+ public function getTitle() {
+ return $this->mRuleTitle;
+ }
+
+ /**
+ * Return how deal with conversion rules.
+ * @return string
+ */
+ public function getRulesAction() {
+ return $this->mRulesAction;
+ }
+
+ /**
+ * Get conversion table. (bidirectional and unidirectional
+ * conversion table)
+ * @return array
+ */
+ public function getConvTable() {
+ return $this->mConvTable;
+ }
+
+ /**
+ * Get conversion rules string.
+ * @return string
+ */
+ public function getRules() {
+ return $this->mRules;
+ }
+
+ /**
+ * Get conversion flags.
+ * @return array
+ */
+ public function getFlags() {
+ return $this->mFlags;
+ }
+}
/**
* 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";
- }
-}
if ( self::isEnabled() ) {
throw new Exception( 'Profiling is already enabled.' );
}
+
+ $args = [ $flags ];
+ if ( $options ) {
+ $args[] = $options;
+ }
+
self::$enabled = true;
self::callAny(
[
'tideways_enable',
'tideways_xhprof_enable'
],
- [ $flags, $options ]
+ $args
);
}
/** @var array List of shared database already attached to this connection */
private $alreadyAttached = [];
- /** @var bool Whether full text is enabled */
- private static $fulltextEnabled = null;
-
/** @var string[] See https://www.sqlite.org/lang_transaction.html */
private static $VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
}
/**
- * Attaches external database to our connection, see https://sqlite.org/lang_attach.html
- * for details.
+ * Attaches external database to the connection handle
+ *
+ * @see https://sqlite.org/lang_attach.html
*
* @param string $name Database name to be used in queries like
* SELECT foo FROM dbname.table
$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 );
}
} );
}
* @param string $pageStatus
* @throws MWException
*/
- public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit,
- $oldid, $watchers, $pageStatus = 'changed' ) {
+ public function actuallyNotifyOnPageChange(
+ $editor,
+ $title,
+ $timestamp,
+ $summary,
+ $minorEdit,
+ $oldid,
+ $watchers,
+ $pageStatus = 'changed'
+ ) {
# we use $wgPasswordSender as sender's address
global $wgUsersNotifiedOnAllChanges;
global $wgEnotifWatchlist, $wgBlockDisablesLogin;
global $wgEnotifMinorEdits, $wgEnotifUserTalk;
+ $messageCache = MediaWikiServices::getInstance()->getMessageCache();
+
# The following code is only run, if several conditions are met:
# 1. EmailNotification for pages (other than user_talk pages) must be enabled
# 2. minor edits (changes) are only regarded if the global flag indicates so
&& $this->canSendUserTalkEmail( $editor, $title, $minorEdit )
) {
$targetUser = User::newFromName( $title->getText() );
- $this->compose( $targetUser, self::USER_TALK );
+ $this->compose( $targetUser, self::USER_TALK, $messageCache );
$userTalkId = $targetUser->getId();
}
&& !( $wgBlockDisablesLogin && $watchingUser->getBlock() )
&& Hooks::run( 'SendWatchlistEmailNotification', [ $watchingUser, $title, $this ] )
) {
- $this->compose( $watchingUser, self::WATCHLIST );
+ $this->compose( $watchingUser, self::WATCHLIST, $messageCache );
}
}
}
continue;
}
$user = User::newFromName( $name );
- $this->compose( $user, self::ALL_CHANGES );
+ $this->compose( $user, self::ALL_CHANGES, $messageCache );
}
$this->sendMails();
/**
* Generate the generic "this page has been changed" e-mail text.
+ * @param MessageCache $messageCache
*/
- private function composeCommonMailtext() {
+ private function composeCommonMailtext( MessageCache $messageCache ) {
global $wgPasswordSender, $wgNoReplyAddress;
global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
global $wgEnotifImpersonal, $wgEnotifUseRealName;
$body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
$body = strtr( $body, $keys );
- $body = MessageCache::singleton()->transform( $body, false, null, $this->title );
+ $body = $messageCache->transform( $body, false, null, $this->title );
$this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
# Reveal the page editor's address as REPLY-TO address only if
* Call sendMails() to send any mails that were queued.
* @param User $user
* @param string $source
+ * @param MessageCache $messageCache
*/
- private function compose( $user, $source ) {
+ private function compose( $user, $source, MessageCache $messageCache ) {
global $wgEnotifImpersonal;
if ( !$this->composed_common ) {
- $this->composeCommonMailtext();
+ $this->composeCommonMailtext( $messageCache );
}
if ( $wgEnotifImpersonal ) {
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 ) {
+ public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
if ( $context ) {
$this->setContext( $context );
}
$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
} /* Else leave it at whatever the class default is */
- parent::__construct();
+ // Parent constructor needs mSort set, so we call it last
+ parent::__construct( null, $linkRenderer );
}
/**
if ( !$wgRestrictDisplayTitle ||
( $title instanceof Title
&& !$title->hasFragment()
- && $title->equals( $parser->mTitle ) )
+ && $title->equals( $parser->getTitle() ) )
) {
$old = $parser->mOutput->getProperty( 'displaytitle' );
if ( $old === false || $arg !== 'displaytitle_noreplace' ) {
* @return string
*/
public static function protectionlevel( $parser, $type = '', $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
$restrictions = $titleObject->getRestrictions( strtolower( $type ) );
# Title::getRestrictions returns an array, its possible it may have
* @return string
*/
public static function protectionexpiry( $parser, $type = '', $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
$expiry = $titleObject->getRestrictionExpiry( strtolower( $type ) );
// getRestrictionExpiry() returns false on invalid type; trying to
* @since 1.23
*/
public static function cascadingsources( $parser, $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areCascadeProtectionSourcesLoaded()
|| $parser->incrementExpensiveFunctionCount()
) {
public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
- $this->title = $this->parser->mTitle;
+ $this->title = $this->parser->getTitle();
$this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
$this->loopCheckHash = [];
$this->depth = 0;
public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
- $this->title = $this->parser->mTitle;
+ $this->title = $this->parser->getTitle();
$this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
$this->loopCheckHash = [];
$this->depth = 0;
global $wgPopularPasswordFile, $wgSitename;
$status = Status::newGood();
if ( $policyVal > 0 ) {
- wfDeprecated( __METHOD__, '1.33' );
-
$langEn = Language::factory( 'en' );
$passwordKey = $langEn->lc( trim( $password ) );
} elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) {
$info['default'] = $globalDefault;
} else {
- throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
+ $globalDefault = json_encode( $globalDefault );
+ throw new MWException(
+ "Default '$globalDefault' is invalid for preference $name of user $user"
+ );
}
}
--- /dev/null
+<?php
+
+/**
+ * SearchResult class based on the Revision information.
+ * This class is suited for search engines that do not store a specialized version of the searched
+ * content.
+ */
+class RevisionSearchResult extends SearchResult {
+ use RevisionSearchResultTrait;
+
+ /**
+ * @param Title|null $title
+ */
+ public function __construct( $title ) {
+ $this->mTitle = $title;
+ $this->initFromTitle( $title );
+ }
+}
--- /dev/null
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Transitional trait used to share the methods between SearchResult and RevisionSearchResult.
+ * All the content of this trait can be moved to RevisionSearchResult once SearchResult is finally
+ * refactored into an abstract class.
+ * NOTE: This trait MUST NOT be used by something else than SearchResult and RevisionSearchResult.
+ * It will be removed without deprecation period once SearchResult
+ */
+trait RevisionSearchResultTrait {
+ /**
+ * @var Revision
+ */
+ protected $mRevision = null;
+
+ /**
+ * @var File
+ */
+ protected $mImage = null;
+
+ /**
+ * @var Title
+ */
+ protected $mTitle;
+
+ /**
+ * @var string
+ */
+ protected $mText;
+
+ /**
+ * Initialize from a Title and if possible initializes a corresponding
+ * Revision and File.
+ *
+ * @param Title $title
+ */
+ protected function initFromTitle( $title ) {
+ $this->mTitle = $title;
+ $services = MediaWikiServices::getInstance();
+ if ( !is_null( $this->mTitle ) ) {
+ $id = false;
+ Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
+ $this->mRevision = Revision::newFromTitle(
+ $this->mTitle, $id, Revision::READ_NORMAL );
+ if ( $this->mTitle->getNamespace() === NS_FILE ) {
+ $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
+ }
+ }
+ }
+
+ /**
+ * Check if this is result points to an invalid title
+ *
+ * @return bool
+ */
+ public function isBrokenTitle() {
+ return is_null( $this->mTitle );
+ }
+
+ /**
+ * Check if target page is missing, happens when index is out of date
+ *
+ * @return bool
+ */
+ public function isMissingRevision() {
+ return !$this->mRevision && !$this->mImage;
+ }
+
+ /**
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * Get the file for this page, if one exists
+ * @return File|null
+ */
+ public function getFile() {
+ return $this->mImage;
+ }
+
+ /**
+ * Lazy initialization of article text from DB
+ */
+ protected function initText() {
+ if ( !isset( $this->mText ) ) {
+ if ( $this->mRevision != null ) {
+ $content = $this->mRevision->getContent();
+ $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
+ } else { // TODO: can we fetch raw wikitext for commons images?
+ $this->mText = '';
+ }
+ }
+ }
+
+ /**
+ * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
+ * @return string Highlighted text snippet, null (and not '') if not supported
+ */
+ public function getTextSnippet( $terms = [] ) {
+ return '';
+ }
+
+ /**
+ * @return string Highlighted title, '' if not supported
+ */
+ public function getTitleSnippet() {
+ return '';
+ }
+
+ /**
+ * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
+ */
+ public function getRedirectSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title|null Title object for the redirect to this page, null if none or not supported
+ */
+ public function getRedirectTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Highlighted relevant section name, null if none or not supported
+ */
+ public function getSectionSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title|null Title object (pagename+fragment) for the section,
+ * null if none or not supported
+ */
+ public function getSectionTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Highlighted relevant category name or '' if none or not supported
+ */
+ public function getCategorySnippet() {
+ return '';
+ }
+
+ /**
+ * @return string Timestamp
+ */
+ public function getTimestamp() {
+ if ( $this->mRevision ) {
+ return $this->mRevision->getTimestamp();
+ } elseif ( $this->mImage ) {
+ return $this->mImage->getTimestamp();
+ }
+ return '';
+ }
+
+ /**
+ * @return int Number of words
+ */
+ public function getWordCount() {
+ $this->initText();
+ return str_word_count( $this->mText );
+ }
+
+ /**
+ * @return int Size in bytes
+ */
+ public function getByteSize() {
+ $this->initText();
+ return strlen( $this->mText );
+ }
+
+ /**
+ * @return string Interwiki prefix of the title (return iw even if title is broken)
+ */
+ public function getInterwikiPrefix() {
+ return '';
+ }
+
+ /**
+ * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
+ */
+ public function getInterwikiNamespaceText() {
+ return '';
+ }
+
+ /**
+ * Did this match file contents (eg: PDF/DJVU)?
+ * @return bool
+ */
+ public function isFileMatch() {
+ return false;
+ }
+}
* @ingroup Search
*/
-use MediaWiki\MediaWikiServices;
-
/**
- * @todo FIXME: This class is horribly factored. It would probably be better to
- * have a useful base class to which you pass some standard information, then
- * let the fancy self-highlighters extend that.
+ * NOTE: this class is being refactored into an abstract base class.
+ * If you extend this class directly, please implement all the methods declared
+ * in RevisionSearchResultTrait or extend RevisionSearchResult.
+ *
+ * Once the hard-deprecation period is over (1.36?):
+ * - all methods declared in RevisionSearchResultTrait should be declared
+ * as abstract in this class
+ * - RevisionSearchResultTrait body should be moved to RevisionSearchResult and then removed without
+ * deprecation
+ * - caveat: all classes extending this one may potentially break if they did not properly implement
+ * all the methods.
* @ingroup Search
*/
class SearchResult {
+ use SearchResultTrait;
+ use RevisionSearchResultTrait;
- /**
- * @var Revision
- */
- protected $mRevision = null;
-
- /**
- * @var File
- */
- protected $mImage = null;
-
- /**
- * @var Title
- */
- protected $mTitle;
-
- /**
- * @var string
- */
- protected $mText;
-
- /**
- * A function returning a set of extension data.
- * @var Closure|null
- */
- protected $extensionData;
+ public function __construct() {
+ if ( self::class === static::class ) {
+ wfDeprecated( __METHOD__, '1.34' );
+ }
+ }
/**
* Return a new SearchResult and initializes it with a title.
* @return SearchResult
*/
public static function newFromTitle( $title, ISearchResultSet $parentSet = null ) {
- $result = new static();
- $result->initFromTitle( $title );
+ $result = new RevisionSearchResult( $title );
if ( $parentSet ) {
$parentSet->augmentResult( $result );
}
return $result;
}
-
- /**
- * Initialize from a Title and if possible initializes a corresponding
- * Revision and File.
- *
- * @param Title $title
- */
- protected function initFromTitle( $title ) {
- $this->mTitle = $title;
- $services = MediaWikiServices::getInstance();
- if ( !is_null( $this->mTitle ) ) {
- $id = false;
- Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
- $this->mRevision = Revision::newFromTitle(
- $this->mTitle, $id, Revision::READ_NORMAL );
- if ( $this->mTitle->getNamespace() === NS_FILE ) {
- $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
- }
- }
- }
-
- /**
- * Check if this is result points to an invalid title
- *
- * @return bool
- */
- public function isBrokenTitle() {
- return is_null( $this->mTitle );
- }
-
- /**
- * Check if target page is missing, happens when index is out of date
- *
- * @return bool
- */
- public function isMissingRevision() {
- return !$this->mRevision && !$this->mImage;
- }
-
- /**
- * @return Title
- */
- public function getTitle() {
- return $this->mTitle;
- }
-
- /**
- * Get the file for this page, if one exists
- * @return File|null
- */
- public function getFile() {
- return $this->mImage;
- }
-
- /**
- * Lazy initialization of article text from DB
- */
- protected function initText() {
- if ( !isset( $this->mText ) ) {
- if ( $this->mRevision != null ) {
- $content = $this->mRevision->getContent();
- $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
- } else { // TODO: can we fetch raw wikitext for commons images?
- $this->mText = '';
- }
- }
- }
-
- /**
- * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
- * @return string Highlighted text snippet, null (and not '') if not supported
- */
- public function getTextSnippet( $terms = [] ) {
- return '';
- }
-
- /**
- * @return string Highlighted title, '' if not supported
- */
- public function getTitleSnippet() {
- return '';
- }
-
- /**
- * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
- */
- public function getRedirectSnippet() {
- return '';
- }
-
- /**
- * @return Title|null Title object for the redirect to this page, null if none or not supported
- */
- public function getRedirectTitle() {
- return null;
- }
-
- /**
- * @return string Highlighted relevant section name, null if none or not supported
- */
- public function getSectionSnippet() {
- return '';
- }
-
- /**
- * @return Title|null Title object (pagename+fragment) for the section,
- * null if none or not supported
- */
- public function getSectionTitle() {
- return null;
- }
-
- /**
- * @return string Highlighted relevant category name or '' if none or not supported
- */
- public function getCategorySnippet() {
- return '';
- }
-
- /**
- * @return string Timestamp
- */
- public function getTimestamp() {
- if ( $this->mRevision ) {
- return $this->mRevision->getTimestamp();
- } elseif ( $this->mImage ) {
- return $this->mImage->getTimestamp();
- }
- return '';
- }
-
- /**
- * @return int Number of words
- */
- public function getWordCount() {
- $this->initText();
- return str_word_count( $this->mText );
- }
-
- /**
- * @return int Size in bytes
- */
- public function getByteSize() {
- $this->initText();
- return strlen( $this->mText );
- }
-
- /**
- * @return string Interwiki prefix of the title (return iw even if title is broken)
- */
- public function getInterwikiPrefix() {
- return '';
- }
-
- /**
- * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
- */
- public function getInterwikiNamespaceText() {
- return '';
- }
-
- /**
- * Did this match file contents (eg: PDF/DJVU)?
- * @return bool
- */
- public function isFileMatch() {
- return false;
- }
-
- /**
- * Get the extension data as:
- * augmentor name => data
- * @return array[]
- */
- public function getExtensionData() {
- if ( $this->extensionData ) {
- return call_user_func( $this->extensionData );
- } else {
- return [];
- }
- }
-
- /**
- * Set extension data for this result.
- * The data is:
- * augmentor name => data
- * @param Closure|array $extensionData Takes no arguments, returns
- * either array of extension data or null.
- */
- public function setExtensionData( $extensionData ) {
- if ( $extensionData instanceof Closure ) {
- $this->extensionData = $extensionData;
- } elseif ( is_array( $extensionData ) ) {
- wfDeprecated( __METHOD__ . ' with array argument', '1.32' );
- $this->extensionData = function () use ( $extensionData ) {
- return $extensionData;
- };
- } else {
- $type = is_object( $extensionData )
- ? get_class( $extensionData )
- : gettype( $extensionData );
- throw new \InvalidArgumentException(
- __METHOD__ . " must be called with Closure|array, but received $type" );
- }
- }
}
--- /dev/null
+<?php
+
+/**
+ * Trait for SearchResult subclasses to share non-obvious behaviors or methods
+ * that rarely specialized
+ */
+trait SearchResultTrait {
+ /**
+ * A function returning a set of extension data.
+ * @var Closure|null
+ */
+ protected $extensionData;
+
+ /**
+ * Get the extension data as:
+ * augmentor name => data
+ * @return array[]
+ */
+ public function getExtensionData() {
+ if ( $this->extensionData ) {
+ return call_user_func( $this->extensionData );
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * Set extension data for this result.
+ * The data is:
+ * augmentor name => data
+ * @param Closure|array $extensionData Takes no arguments, returns
+ * either array of extension data or null.
+ */
+ public function setExtensionData( $extensionData ) {
+ if ( $extensionData instanceof Closure ) {
+ $this->extensionData = $extensionData;
+ } elseif ( is_array( $extensionData ) ) {
+ wfDeprecated( __METHOD__ . ' with array argument', '1.32' );
+ $this->extensionData = function () use ( $extensionData ) {
+ return $extensionData;
+ };
+ } else {
+ $type = is_object( $extensionData )
+ ? get_class( $extensionData )
+ : gettype( $extensionData );
+ throw new \InvalidArgumentException(
+ __METHOD__ . " must be called with Closure|array, but received $type" );
+ }
+ }
+}
* @ingroup Search
*/
-class SqlSearchResult extends SearchResult {
+class SqlSearchResult extends RevisionSearchResult {
/** @var string[] */
private $terms;
* @param string[] $terms list of parsed terms
*/
public function __construct( Title $title, array $terms ) {
- $this->initFromTitle( $title );
+ parent::__construct( $title );
$this->terms = $terms;
}
}
}
+ /**
+ * @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(),
]
);
public function __construct() {
parent::__construct( 'NewSection' );
$this->mAllowedRedirectParams = [ 'preloadtitle', 'nosummary', 'editintro',
- 'preload', 'preloadparams[]', 'summary' ];
+ 'preload', 'preloadparams', 'summary' ];
}
/**
protected function showNoRedirectPage() {
$this->setHeaders();
$this->outputHeader();
+ $this->addHelpLink( 'Help:New section' );
$this->showForm();
}
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,
$including = false, $showAll = false
) {
$this->setContext( $context );
+
$this->mIncluding = $including;
$this->mShowAll = $showAll;
$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
}
- parent::__construct( $context );
+ parent::__construct();
}
/**
*/
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(),
[],
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Parser for rules of language conversion, parse rules in -{ }- tag.
- * @ingroup Language
- * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
- */
-class ConverterRule {
- public $mText; // original text in -{text}-
- public $mConverter; // LanguageConverter object
- public $mRuleDisplay = '';
- public $mRuleTitle = false;
- public $mRules = ''; // string : the text of the rules
- public $mRulesAction = 'none';
- public $mFlags = [];
- public $mVariantFlags = [];
- public $mConvTable = [];
- public $mBidtable = []; // array of the translation in each variant
- public $mUnidtable = []; // array of the translation in each variant
-
- /**
- * @param string $text The text between -{ and }-
- * @param LanguageConverter $converter
- */
- public function __construct( $text, $converter ) {
- $this->mText = $text;
- $this->mConverter = $converter;
- }
-
- /**
- * Check if variants array in convert array.
- *
- * @param array|string $variants Variant language code
- * @return string Translated text
- */
- public function getTextInBidtable( $variants ) {
- $variants = (array)$variants;
- if ( !$variants ) {
- return false;
- }
- foreach ( $variants as $variant ) {
- if ( isset( $this->mBidtable[$variant] ) ) {
- return $this->mBidtable[$variant];
- }
- }
- return false;
- }
-
- /**
- * Parse flags with syntax -{FLAG| ... }-
- * @private
- */
- function parseFlags() {
- $text = $this->mText;
- $flags = [];
- $variantFlags = [];
-
- $sepPos = strpos( $text, '|' );
- if ( $sepPos !== false ) {
- $validFlags = $this->mConverter->mFlags;
- $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
- foreach ( $f as $ff ) {
- $ff = trim( $ff );
- if ( isset( $validFlags[$ff] ) ) {
- $flags[$validFlags[$ff]] = true;
- }
- }
- $text = strval( substr( $text, $sepPos + 1 ) );
- }
-
- if ( !$flags ) {
- $flags['S'] = true;
- } elseif ( isset( $flags['R'] ) ) {
- $flags = [ 'R' => true ];// remove other flags
- } elseif ( isset( $flags['N'] ) ) {
- $flags = [ 'N' => true ];// remove other flags
- } elseif ( isset( $flags['-'] ) ) {
- $flags = [ '-' => true ];// remove other flags
- } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
- $flags['H'] = true;
- } elseif ( isset( $flags['H'] ) ) {
- // replace A flag, and remove other flags except T
- $temp = [ '+' => true, 'H' => true ];
- if ( isset( $flags['T'] ) ) {
- $temp['T'] = true;
- }
- if ( isset( $flags['D'] ) ) {
- $temp['D'] = true;
- }
- $flags = $temp;
- } else {
- if ( isset( $flags['A'] ) ) {
- $flags['+'] = true;
- $flags['S'] = true;
- }
- if ( isset( $flags['D'] ) ) {
- unset( $flags['S'] );
- }
- // try to find flags like "zh-hans", "zh-hant"
- // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
- $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
- if ( $variantFlags ) {
- $variantFlags = array_flip( $variantFlags );
- $flags = [];
- }
- }
- $this->mVariantFlags = $variantFlags;
- $this->mRules = $text;
- $this->mFlags = $flags;
- }
-
- /**
- * Generate conversion table.
- * @private
- */
- function parseRules() {
- $rules = $this->mRules;
- $bidtable = [];
- $unidtable = [];
- $variants = $this->mConverter->mVariants;
- $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
-
- // Split according to $varsep_pattern, but ignore semicolons from HTML entities
- $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
- $choice = preg_split( $varsep_pattern, $rules );
- $choice = str_replace( "\x01", ';', $choice );
-
- foreach ( $choice as $c ) {
- $v = explode( ':', $c, 2 );
- if ( count( $v ) != 2 ) {
- // syntax error, skip
- continue;
- }
- $to = trim( $v[1] );
- $v = trim( $v[0] );
- $u = explode( '=>', $v, 2 );
- $vv = $this->mConverter->validateVariant( $v );
- // if $to is empty (which is also used as $from in bidtable),
- // strtr() could return a wrong result.
- if ( count( $u ) == 1 && $to !== '' && $vv ) {
- $bidtable[$vv] = $to;
- } elseif ( count( $u ) == 2 ) {
- $from = trim( $u[0] );
- $v = trim( $u[1] );
- $vv = $this->mConverter->validateVariant( $v );
- // if $from is empty, strtr() could return a wrong result.
- if ( array_key_exists( $vv, $unidtable )
- && !is_array( $unidtable[$vv] )
- && $from !== ''
- && $vv ) {
- $unidtable[$vv] = [ $from => $to ];
- } elseif ( $from !== '' && $vv ) {
- $unidtable[$vv][$from] = $to;
- }
- }
- // syntax error, pass
- if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
- $bidtable = [];
- $unidtable = [];
- break;
- }
- }
- $this->mBidtable = $bidtable;
- $this->mUnidtable = $unidtable;
- }
-
- /**
- * @private
- *
- * @return string
- */
- function getRulesDesc() {
- $codesep = $this->mConverter->mDescCodeSep;
- $varsep = $this->mConverter->mDescVarSep;
- $text = '';
- foreach ( $this->mBidtable as $k => $v ) {
- $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
- }
- foreach ( $this->mUnidtable as $k => $a ) {
- foreach ( $a as $from => $to ) {
- $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
- "$codesep$to$varsep";
- }
- }
- return $text;
- }
-
- /**
- * Parse rules conversion.
- * @private
- *
- * @param string $variant
- *
- * @return string
- */
- function getRuleConvertedStr( $variant ) {
- $bidtable = $this->mBidtable;
- $unidtable = $this->mUnidtable;
-
- if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
- return $this->mRules;
- } else {
- // display current variant in bidirectional array
- $disp = $this->getTextInBidtable( $variant );
- // or display current variant in fallbacks
- if ( $disp === false ) {
- $disp = $this->getTextInBidtable(
- $this->mConverter->getVariantFallbacks( $variant ) );
- }
- // or display current variant in unidirectional array
- if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
- $disp = array_values( $unidtable[$variant] )[0];
- }
- // or display first text under disable manual convert
- if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
- if ( count( $bidtable ) > 0 ) {
- $disp = array_values( $bidtable )[0];
- } else {
- $disp = array_values( array_values( $unidtable )[0] )[0];
- }
- }
- return $disp;
- }
- }
-
- /**
- * Similar to getRuleConvertedStr(), but this prefers to use original
- * page title if $variant === $this->mConverter->mMainLanguageCode
- * and may return false in this case (so this title conversion rule
- * will be ignored and the original title is shown).
- *
- * @since 1.22
- * @param string $variant The variant code to display page title in
- * @return string|bool The converted title or false if just page name
- */
- function getRuleConvertedTitle( $variant ) {
- if ( $variant === $this->mConverter->mMainLanguageCode ) {
- // If a string targeting exactly this variant is set,
- // use it. Otherwise, just return false, so the real
- // page name can be shown (and because variant === main,
- // there'll be no further automatic conversion).
- $disp = $this->getTextInBidtable( $variant );
- if ( $disp ) {
- return $disp;
- }
- if ( array_key_exists( $variant, $this->mUnidtable ) ) {
- $disp = array_values( $this->mUnidtable[$variant] )[0];
- }
- // Assigned above or still false.
- return $disp;
- } else {
- return $this->getRuleConvertedStr( $variant );
- }
- }
-
- /**
- * Generate conversion table for all text.
- * @private
- */
- function generateConvTable() {
- // Special case optimisation
- if ( !$this->mBidtable && !$this->mUnidtable ) {
- $this->mConvTable = [];
- return;
- }
-
- $bidtable = $this->mBidtable;
- $unidtable = $this->mUnidtable;
- $manLevel = $this->mConverter->mManualLevel;
-
- $vmarked = [];
- foreach ( $this->mConverter->mVariants as $v ) {
- /* for bidirectional array
- fill in the missing variants, if any,
- with fallbacks */
- if ( !isset( $bidtable[$v] ) ) {
- $variantFallbacks =
- $this->mConverter->getVariantFallbacks( $v );
- $vf = $this->getTextInBidtable( $variantFallbacks );
- if ( $vf ) {
- $bidtable[$v] = $vf;
- }
- }
-
- if ( isset( $bidtable[$v] ) ) {
- foreach ( $vmarked as $vo ) {
- // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
- // or -{H|zh:WordZh;zh-tw:WordTw}-
- // or -{-|zh:WordZh;zh-tw:WordTw}-
- // to introduce a custom mapping between
- // words WordZh and WordTw in the whole text
- if ( $manLevel[$v] == 'bidirectional' ) {
- $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
- }
- if ( $manLevel[$vo] == 'bidirectional' ) {
- $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
- }
- }
- $vmarked[] = $v;
- }
- /* for unidirectional array fill to convert tables */
- if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
- && isset( $unidtable[$v] )
- ) {
- if ( isset( $this->mConvTable[$v] ) ) {
- $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
- } else {
- $this->mConvTable[$v] = $unidtable[$v];
- }
- }
- }
- }
-
- /**
- * Parse rules and flags.
- * @param string|null $variant Variant language code
- */
- public function parse( $variant = null ) {
- if ( !$variant ) {
- $variant = $this->mConverter->getPreferredVariant();
- }
-
- $this->parseFlags();
- $flags = $this->mFlags;
-
- // convert to specified variant
- // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
- if ( $this->mVariantFlags ) {
- // check if current variant in flags
- if ( isset( $this->mVariantFlags[$variant] ) ) {
- // then convert <text to convert> to current language
- $this->mRules = $this->mConverter->autoConvert( $this->mRules,
- $variant );
- } else {
- // if current variant no in flags,
- // then we check its fallback variants.
- $variantFallbacks =
- $this->mConverter->getVariantFallbacks( $variant );
- if ( is_array( $variantFallbacks ) ) {
- foreach ( $variantFallbacks as $variantFallback ) {
- // if current variant's fallback exist in flags
- if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
- // then convert <text to convert> to fallback language
- $this->mRules =
- $this->mConverter->autoConvert( $this->mRules,
- $variantFallback );
- break;
- }
- }
- }
- }
- $this->mFlags = $flags = [ 'R' => true ];
- }
-
- if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
- // decode => HTML entities modified by Sanitizer::removeHTMLtags
- $this->mRules = str_replace( '=>', '=>', $this->mRules );
- $this->parseRules();
- }
- $rules = $this->mRules;
-
- if ( !$this->mBidtable && !$this->mUnidtable ) {
- if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
- // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
- if ( $rules !== '' ) {
- foreach ( $this->mConverter->mVariants as $v ) {
- $this->mBidtable[$v] = $rules;
- }
- }
- } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
- $this->mFlags = $flags = [ 'R' => true ];
- }
- }
-
- $this->mRuleDisplay = false;
- foreach ( $flags as $flag => $unused ) {
- switch ( $flag ) {
- case 'R':
- // if we don't do content convert, still strip the -{}- tags
- $this->mRuleDisplay = $rules;
- break;
- case 'N':
- // process N flag: output current variant name
- $ruleVar = trim( $rules );
- $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
- break;
- case 'D':
- // process D flag: output rules description
- $this->mRuleDisplay = $this->getRulesDesc();
- break;
- case 'H':
- // process H,- flag or T only: output nothing
- $this->mRuleDisplay = '';
- break;
- case '-':
- $this->mRulesAction = 'remove';
- $this->mRuleDisplay = '';
- break;
- case '+':
- $this->mRulesAction = 'add';
- $this->mRuleDisplay = '';
- break;
- case 'S':
- $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
- break;
- case 'T':
- $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
- $this->mRuleDisplay = '';
- break;
- default:
- // ignore unknown flags (but see error case below)
- }
- }
- if ( $this->mRuleDisplay === false ) {
- $this->mRuleDisplay = '<span class="error">'
- . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
- . '</span>';
- }
-
- $this->generateConvTable();
- }
-
- /**
- * Checks if there are conversion rules.
- * @return bool
- */
- public function hasRules() {
- return $this->mRules !== '';
- }
-
- /**
- * Get display text on markup -{...}-
- * @return string
- */
- public function getDisplay() {
- return $this->mRuleDisplay;
- }
-
- /**
- * Get converted title.
- * @return string
- */
- public function getTitle() {
- return $this->mRuleTitle;
- }
-
- /**
- * Return how deal with conversion rules.
- * @return string
- */
- public function getRulesAction() {
- return $this->mRulesAction;
- }
-
- /**
- * Get conversion table. (bidirectional and unidirectional
- * conversion table)
- * @return array
- */
- public function getConvTable() {
- return $this->mConvTable;
- }
-
- /**
- * Get conversion rules string.
- * @return string
- */
- public function getRules() {
- return $this->mRules;
- }
-
- /**
- * Get conversion flags.
- * @return array
- */
- public function getFlags() {
- return $this->mFlags;
- }
-}
*/
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
"rcfilters-filter-showlinkedto-label": "Vis ændringer på sider der linker til",
"rcfilters-filter-showlinkedto-option-label": "<strong>Sider som linker til</strong> den valgte side",
"rcfilters-target-page-placeholder": "Indtast et sidenavn (eller en kategori)",
+ "rcfilters-allcontents-label": "Alt indhold",
"rcnotefrom": "Nedenfor er op til '''$1''' {{PLURAL:$5|ændring|ændringer}} siden '''$2''' vist.",
"rclistfromreset": "Nulstil datovalg",
"rclistfrom": "Vis nye ændringer startende fra den $3 kl. $2",
"Fitoschido",
"KATRINE1993",
"Vlad5250",
- "Sarri.greek"
+ "Sarri.greek",
+ "Kostajh"
]
},
"tog-underline": "Υπογράμμιση συνδέσμων:",
"history": "Ιστορικό σελίδας",
"history_short": "Ιστορικό",
"history_small": "ιστορικό",
- "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή μου",
+ "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή σας",
"printableversion": "Έκδοση εκτύπωσης",
"permalink": "Σταθερός σύνδεσμος",
"print": "Εκτύπωση",
"PhiLiP",
"Qiyue2001",
"Xiaomingyan",
- "神樂坂秀吉"
+ "神樂坂秀吉",
+ "予弦"
]
},
"exif-imagewidth": "宽度",
"tag-filter": "Filtrer les [[Special:Tags|balises]] :",
"tag-filter-submit": "Filtrer",
"tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Balise|Balises}}]] : $2",
- "tag-mw-contentmodelchange": "modification du modèle de contenu",
+ "tag-mw-contentmodelchange": "Modification du modèle de contenu",
"tag-mw-contentmodelchange-description": "Modifications qui [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel changent le modèle de contenu] d'une page",
"tag-mw-new-redirect": "Nouvelle redirection",
"tag-mw-new-redirect-description": "Modifications qui créent une nouvelle redirection ou transforment une page en redirection",
"tag-mw-changed-redirect-target-description": "Modifications qui changent la cible d’une redirection",
"tag-mw-blank": "Blanchiment",
"tag-mw-blank-description": "Modifications qui suppriment le contenu des pages",
- "tag-mw-replace": "Remplacé",
+ "tag-mw-replace": "Contenu remplacé",
"tag-mw-replace-description": "Modifications qui enlèvent plus de 90% du contenu des pages",
"tag-mw-rollback": "Révocation",
"tag-mw-rollback-description": "Modifications qui annulent des modifications existantes en utilisant le lien de révocation (''rollback'')",
"timezoneregion-indian": "Samudera Hindia",
"timezoneregion-pacific": "Samudera Pasifik",
"allowemail": "Izinkan pengguna lain mengirim surel kepada saya",
- "email-allow-new-users-label": "Izinkan email dari pengguna baru",
+ "email-allow-new-users-label": "Izinkan surel dari pengguna baru",
"email-blacklist-label": "Cegah para pengguna ini mengirim saya surel:",
"prefs-searchoptions": "Cari",
"prefs-namespaces": "Ruang nama",
"last": "prec",
"page_first": "prima",
"page_last": "ultima",
- "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione corrente, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
+ "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione attuale, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
"history-fieldset-title": "Filtra versioni",
"history-show-deleted": "Solo versioni cancellate",
"histfirst": "prima",
"prefs-personal": "Profil pangguno",
"prefs-rc": "Parubahan baru",
"prefs-watchlist": "Daftar pantau",
+ "prefs-editwatchlist": "Suntiang daftar pantauan",
+ "prefs-editwatchlist-label": "Suntiang entri daftar pantauan Sanak:",
"prefs-watchlist-days": "Jumlah hari dalam daftar pantau:",
"prefs-watchlist-days-max": "Maksimum $1 {{PLURAL:$1|hari}}",
"prefs-watchlist-edits": "Jumlah suntiangan nan ditunjuakan pado daftar pantau:",
"timezoneregion-indian": "Samudera Hindia",
"timezoneregion-pacific": "Samudera Pasifik",
"allowemail": "Izinkan pangguno lain mangirim surel",
+ "email-allow-new-users-label": "Izinkan surel dari pangguno baru",
+ "email-blacklist-label": "Panggono ko indak dapek kirim surel ka Ambo:",
"prefs-searchoptions": "Cari",
"prefs-namespaces": "Ruangnamo",
"default": "baku",
"prefs-files": "Berkas",
- "prefs-custom-css": "CSS paribadi",
- "prefs-custom-js": "JS paribadi",
+ "prefs-custom-css": "CSS surang",
+ "prefs-custom-js": "JS surang",
"prefs-common-config": "CSS/JS untuak kasado kulik:",
"prefs-reset-intro": "Angku dapek manggunokan laman ko untuak mangambalikan pangaturan ka setelan baku situs ko.\nPangambalian pangaturan indak dapek dibatalan.",
"prefs-emailconfirm-label": "Surel konfirmasi:",
"prefs-advancedwatchlist": "Piliahan lanjuik",
"prefs-displayrc": "Piliahan tampilan",
"prefs-displaywatchlist": "Piliahan tampilan",
+ "prefs-changesrc": "Parubahan ditampilkan",
"prefs-diffs": "Pabedoan",
"userrights": "Manajemen hak pangguno",
"userrights-lookup-user": "Mangatua kalompok pangguno",
"KlaasZ4usV",
"Elroy",
"PiefPafPier",
- "Ecthelion3"
+ "Ecthelion3",
+ "RadioAzureus"
]
},
"tog-underline": "Verwijzingen onderstrepen:",
"rcfilters-filter-showlinkedto-label": "Toon wijzigingen op pagina's gekoppeld naar",
"rcfilters-filter-showlinkedto-option-label": "<strong>Pagina's gekoppeld naar</strong> de geselecteerde pagina",
"rcfilters-target-page-placeholder": "Voer een paginanaam (of categorie) in",
- "rcfilters-allcontents-label": "Alle inhoud",
- "rcfilters-alldiscussions-label": "Al het overleg",
+ "rcfilters-allcontents-label": "De volledige inhoud",
+ "rcfilters-alldiscussions-label": "Alle discussies",
"rcnotefrom": "Wijzigingen sinds <strong>$3 om $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|wijziging|wijzigingen}}).",
"rclistfromreset": "Datum selectie opnieuw instellen",
"rclistfrom": "Wijzigingen bekijken vanaf $3 $2",
"immobile-target-namespace-iw": "Een interwikikoppeling is geen geldige bestemming voor het hernoemen van een pagina.",
"immobile-source-page": "Deze pagina kan niet hernoemd worden.",
"immobile-target-page": "Het is niet mogelijk te hernoemen naar die paginanaam.",
- "movepage-invalid-target-title": "De opgevraagde naam is ongeldig.",
+ "movepage-invalid-target-title": "De gevraagde naam is ongeldig.",
"bad-target-model": "De gewenste bestemming gebruikt een ander inhoudsmodel. Het is niet mogelijk om te zetten van $1 naar $2.",
"imagenocrossnamespace": "Een mediabestand kan niet naar een andere naamruimte verplaatst worden",
"nonfile-cannot-move-to-file": "Het is niet mogelijk te hernoemen van en naar de bestandsnaamruimte",
"showdiff": "Yong-a wallak",
"anoneditwarning": "<strong>Warning:</strong> You are not logged in. Noonook IP-karl-up will be publicly djinang il noonook wallak. Noonook-il <strong>[$1 log in]</strong> or <strong>[$2 create an gudak]</strong>, noonook wallak will be attributed to noonook niall-kwel-le, along with other benefits.",
"blockedtitle": "Niall be nap-nap",
- "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"email this user\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
+ "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"{{int:emailuser}}\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
"loginreqlink": "yaarlkoorl",
"newarticletext": "Noonook ngwaliny beda bibol uart-yogow yeye.\nWallak bibol qadgin mar waangkin ngardal (djinang [$1 mar yira bibol] ngatta katitjiny)\nWarra bainya noonook nidja, click noonook bowser's <strong>woort koorl</strong>button",
- "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify him/her.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
+ "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify balang.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
"noarticletext": "There is currently no text in this page.\nYou can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],\nor [{{fullurl:{{FULLPAGENAME}}|action=edit}} create this page]</span>.",
"noarticletext-nopermission": "Nidja yeye uart text il nidja bibol.\nNoonook [[Special:Search/{{PAGENAME}}|genuniny-ung nidja bibol katta wir-iny]] bura wam bibol, ka <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} genuniny boonadairn]</span>, noonook uart kaya ijow walbirniny nidja bibol.",
"userpage-userdoesnotexist-view": "Niall gaduk $1 be uart yeye-quadga",
"recentchangeslinked-feed": "Noyyang wallak",
"recentchangeslinked-toolbox": "Noyyang wallak",
"recentchangeslinked-title": "Wallak noyyanging $1",
- "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol (or il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
+ "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol ({{ns:category}} il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
"recentchangeslinked-page": "Bibol kwel-le:",
"recentchangeslinked-to": "Yong-a wallak bibol beda nitja bibol",
"upload": "Yirra file",
"rcfilters-filter-showlinkedto-label": "Pokaż zmiany na stronach linkujących do",
"rcfilters-filter-showlinkedto-option-label": "<strong>Strony linkujące do</strong> zaznaczonej strony",
"rcfilters-target-page-placeholder": "Wprowadź nazwę strony (lub kategorii)",
- "rcfilters-alldiscussions-label": "Wszystkie dyskusje",
+ "rcfilters-allcontents-label": "Wszystkie (treść)",
+ "rcfilters-alldiscussions-label": "Wszystkie (dyskusje)",
"rcnotefrom": "Poniżej {{PLURAL:$5|pokazano zmianę|pokazano zmiany}} {{PLURAL:$5|wykonaną|wykonane}} po <strong>$3, $4</strong> (nie więcej niż '''$1''' pozycji).",
"rclistfromreset": "Zresetuj wybór daty",
"rclistfrom": "Pokaż nowe zmiany od $3 $2",
"ipb-disableusertalk": "Edytowanie przez tego użytkownika swojej strony dyskusji",
"ipb-change-block": "Zmień ustawienia blokady",
"ipb-confirm": "Potwierdzam blokadę",
- "ipb-sitewide": "Całkowita",
- "ipb-partial": "Częściowa",
+ "ipb-sitewide": "Całkowicie",
+ "ipb-partial": "Częściowo",
"ipb-sitewide-help": "Wszystkie strony na wiki i wszystkie akcje inne edycyjne.",
"ipb-partial-help": "Konkretne strony lub przestrzenie nazw.",
"ipb-pages-label": "Strony",
"revdelete-hide-image": "فائيل جو مواد لڪايو",
"revdelete-hide-name": "هدف ۽ نيمپيما لڪايو",
"revdelete-hide-comment": "سنوار جو تتُ",
- "revdelete-hide-user": "اÙ\8aÚ\8aÙ\8aٽر جÙ\88 Ù\88اپرائÙ\8aÙ\86دÚ\99-Ù\86اÙ\86Ø¡Ù\8f/آءÙ\90Ù¾Ù\90ي پتو",
+ "revdelete-hide-user": "سÙ\86Ù\88ارÙ\8aÙ\86دÚ\99 جÙ\88 Ù\88اپرائÙ\8aÙ\86دÚ\99-Ù\86اÙ\86Ø¡Ù\8f/آئÙ\90Ù¾ي پتو",
"revdelete-hide-restricted": "منتظمن توڙي ٻين کان مليل اعداد دٻايو",
"revdelete-radio-same": "(نہ بدلايو)",
"revdelete-radio-set": "لڪل",
"rcfilters-filter-editsbyother-label": "ٻين پاران تبديليون",
"rcfilters-filtergroup-user-experience-level": "واپرائيندڙن جي رجسٽريشن ۽ تجربو",
"rcfilters-filter-user-experience-level-registered-label": "رجسٽر ٿيل",
- "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 اÙ\8aÚ\8aÙ\8aٽر.",
+ "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 سÙ\86Ù\88ارÙ\8aÙ\86دÚ\99.",
"rcfilters-filter-user-experience-level-unregistered-label": "اڻرجسٽر ٿيل",
"rcfilters-filter-user-experience-level-unregistered-description": "سنواريندڙ جيڪي داخل ٿيل ناھن.",
"rcfilters-filter-user-experience-level-newcomer-label": "نوان ايندڙ",
"imagelinks": "فائيل جو استعمال",
"linkstoimage": "ھن فائيل کي {{PLURAL:$1|ھيٺيون صفحو استعمال ڪري ٿو|$1 ھيٺيان صفحا استعمال ڪن ٿا}}:",
"nolinkstoimage": "ڪي بہ صفحا ناھن جيڪي ھن فائيل کي استعمال ڪندا ھجن.",
+ "linkstoimage-redirect": "$1 (فائيل چورڻو) $2",
"sharedupload": "هيءَ فائيل $1 کان آهي ۽ ان کي ٻيون رٿائون به استعمال ڪري سگھن ٿيون.",
"sharedupload-desc-here": "ھي فائيل $1 مان آھي ۽ ٻين رٿائن پاران پڻ استعمال ٿي سگھي ٿو. تشريح انجي [[$2 جو تشريحي صفحو]] ھيٺان ڏنل آھي.",
"filepage-nofile": "ھن نالي سان ڪوبہ فائيل وجود نٿو رکي.",
"protectlogpage": "تحفظ لاگ",
"protectedarticle": "محفوظ ٿيل \"[[$1]]\"",
"modifiedarticleprotection": "\"[[$1]]\" جي تحفظ جي سطح تبديل ڪئي",
+ "unprotectedarticle": "\"[[$1]]\" تان تحفظ ھٽايو ويو",
"movedarticleprotection": "\"[[$2]]\" جو حفاظت درجو \"[[$1]]\" جي طرف منتقل ڪيو",
+ "unprotectedarticle-comment": "\"[[$1]]\" تان {{GENDER:$2|تحفظ ھٽايو}}",
"prot_1movedto2": "[[$1]] کي چوري [[$2]] تي رکيو ويو",
"protect-legend": "تحفظڻ جي پڪ ڪريو",
"protectcomment": "سبب:",
"htmlform-cloner-delete": "هٽايو",
"htmlform-title-not-exists": "$1 وجود نٿو رکي.",
"logentry-delete-delete": "$1 {{GENDER:$2|ڊاٿو}} صفحو $3",
+ "logentry-delete-restore": "$1 {{GENDER:$2|بحاليو}} صفحو $3 ($4)",
"logentry-delete-revision": "$1 $3: $4 صفحي تي {{PLURAL:$5|ھڪ مسودي|$5 مسودن}} جي ظاھريت {{GENDER:$2|تبديل ڪئي}}",
"revdelete-content-hid": "مواد لڪيل",
"revdelete-uname-hid": "واپرائيندڙ-نانءُ لڪل",
+ "revdelete-unrestricted": "منتظمن تان پابنديون ھٽايون ويون",
"logentry-block-block": "$1، {{GENDER:$4|$3}} تي $5 وقت جي خاتمي تائين {{GENDER:$2|بندش هئي آهي}} $6",
"logentry-move-move": "$1 {{GENDER:$2|چوريو}} صفحو $3 ڏانهن $4",
"logentry-move-move-noredirect": "$1 $3 صفحي کي $4 ڏانھن {{GENDER:$2|چوريو}} سواءِ ڪو ريڊائريڪٽ ڇڏيندي",
"logentry-patrol-patrol-auto": "$1 پاڻمرادو صفحي $3 جي $4 مسودي تي گشت ڪيل طور {{GENDER:$2|نشان لڳايو}}",
"logentry-newusers-create": "واپرائيندڙ کاتو $1 {{GENDER:$2|سرجيو ويو}}",
"logentry-newusers-autocreate": "واپرائيندڙ کاتو $1 پاڻمرادو {{GENDER:$2|کوليو ويو}}",
+ "logentry-protect-unprotect": "$1 $3 تان تحفظ {{GENDER:$2|ھٽايو}}",
"logentry-protect-protect": "$1 {{GENDER:$2|محفوظ ڪيو}} $3 $4",
"logentry-upload-upload": "$1 {{GENDER:$2|چاڙهيو}} $3",
"logentry-upload-overwrite": "$1 $3 جو ھڪ نئون ورزن {{GENDER:$2|چاڙھيو}}",
"mw-widgets-usersmultiselect-placeholder": "وڌيڪ شامل ڪيو...",
"date-range-from": "تاريخ کان:",
"date-range-to": "تاريخ تائين:",
+ "randomrootpage": "بلاترتيب پاڙ صفحو",
"log-action-filter-all": "سڀ"
}
"Suchichi02",
"神樂坂秀吉",
"WQL",
- "Looong"
+ "Looong",
+ "予弦"
]
},
"tog-underline": "链接下划线:",
* @ingroup Maintenance
*/
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\MediaWikiServices;
-
require_once __DIR__ . '/Maintenance.php';
/**
$conf = $wgLocalisationCacheConf;
$conf['manualRecache'] = false; // Allow fallbacks to create CDB files
- $conf['forceRecache'] = $force || !empty( $conf['forceRecache'] );
+ if ( $force ) {
+ $conf['forceRecache'] = true;
+ }
if ( $this->hasOption( 'outdir' ) ) {
$conf['storeDirectory'] = $this->getOption( 'outdir' );
}
- // XXX Copy-pasted from ServiceWiring.php. Do we need a factory for this one caller?
- $lc = new LocalisationCacheBulkLoad(
- new ServiceOptions(
- LocalisationCache::$constructorOptions,
- $conf,
- MediaWikiServices::getInstance()->getMainConfig()
- ),
- new LCStoreDB( [] ),
- LoggerFactory::getInstance( 'localisation' ),
- [ function () {
- MediaWikiServices::getInstance()->getResourceLoader()
- ->getMessageBlobStore()->clear();
- } ],
- MediaWikiServices::getInstance()->getLanguageNameUtils()
- );
+ $lc = new LocalisationCacheBulkLoad( $conf );
$allCodes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
if ( $this->hasOption( 'lang' ) ) {
"requires": {
"lodash": "^4.17.11"
}
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+ "dev": true
}
}
},
}
}
},
- "humanize-duration": {
- "version": "3.15.3",
- "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.15.3.tgz",
- "integrity": "sha512-BMz6w8p3NVa6QP9wDtqUkXfwgBqDaZ5z/np0EYdoWrLqL849Onp6JWMXMhbHtuvO9jUThLN5H1ThRQ8dUWnYkA==",
- "dev": true
- },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
}
},
"lodash": {
- "version": "4.17.11",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
- "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.get": {
}
},
"mixin-deep": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
- "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
"dev": true
},
"set-value": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
- "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
}
},
"union-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
- "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
- "set-value": "^0.4.3"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
- "set-value": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
- "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.1",
- "to-object-path": "^0.3.0"
- }
- }
+ "set-value": "^2.0.1"
}
},
"uniq": {
"sauce-connect-launcher": "~1.2.3"
}
},
- "wdio-spec-reporter": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.5.tgz",
- "integrity": "sha512-MqvgTow8hFwhFT47q67JwyJyeynKodGRQCxF7ijKPGfsaG1NLssbXYc0JhiL7SiAyxnQxII0UxzTCd3I6sEdkg==",
- "dev": true,
- "requires": {
- "babel-runtime": "~6.26.0",
- "chalk": "^2.3.0",
- "humanize-duration": "~3.15.0"
- }
- },
"wdio-sync": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.3.tgz",
"postcss-less": "2.0.0",
"qunit": "2.9.1",
"stylelint-config-wikimedia": "0.6.0",
+ "wdio-dot-reporter": "0.0.10",
"wdio-junit-reporter": "0.4.4",
"wdio-mediawiki": "file:tests/selenium/wdio-mediawiki",
"wdio-mocha-framework": "0.6.4",
"wdio-sauce-service": "0.4.14",
- "wdio-spec-reporter": "0.1.5",
"webdriverio": "4.14.4"
}
}
)
);
- if ( this.cache ) {
- this.cache.set( pageData );
- }
-
// Offer the exact text as a suggestion if the page exists
if ( this.addQueryInput && pageExists && !pageExistsExact ) {
titles.unshift( this.getQueryValue() );
+ // Ensure correct page metadata gets used
+ pageData[ this.getQueryValue() ] = pageData[ titleObj.getPrefixedText() ];
+ }
+
+ if ( this.cache ) {
+ this.cache.set( pageData );
}
for ( i = 0, len = titles.length; i < len; i++ ) {
# 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",
'class' => NullLockManager::class,
] ];
$reset = function () {
- LockManagerGroup::destroySingletons();
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
};
$setup[] = $reset;
$teardown[] = $reset;
$wgRequest = new FauxRequest();
MediaWiki\Session\SessionManager::resetCache();
- Language::clearCaches();
}
public function run( PHPUnit_Framework_TestResult $result = null ) {
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use GuzzleHttp\Psr7\Stream;
+use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
+use MediaWiki\Rest\Handler;
+use MediaWiki\Rest\EntryPoint;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+use RequestContext;
+use WebResponse;
+
+/**
+ * @covers \MediaWiki\Rest\EntryPoint
+ * @covers \MediaWiki\Rest\Router
+ */
+class EntryPointTest extends \MediaWikiTestCase {
+ private static $mockHandler;
+
+ private function createRouter() {
+ global $IP;
+
+ return new Router(
+ [ "$IP/tests/phpunit/unit/includes/Rest/testRoutes.json" ],
+ [],
+ '/rest',
+ new EmptyBagOStuff(),
+ new ResponseFactory(),
+ new StaticBasicAuthorizer() );
+ }
+
+ private function createWebResponse() {
+ return $this->getMockBuilder( WebResponse::class )
+ ->setMethods( [ 'header' ] )
+ ->getMock();
+ }
+
+ public static function mockHandlerHeader() {
+ return new class extends Handler {
+ public function execute() {
+ $response = $this->getResponseFactory()->create();
+ $response->setHeader( 'Foo', 'Bar' );
+ return $response;
+ }
+ };
+ }
+
+ public function testHeader() {
+ $webResponse = $this->createWebResponse();
+ $webResponse->expects( $this->any() )
+ ->method( 'header' )
+ ->withConsecutive(
+ [ 'HTTP/1.1 200 OK', true, null ],
+ [ 'Foo: Bar', true, null ]
+ );
+
+ $entryPoint = new EntryPoint(
+ RequestContext::getMain(),
+ new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
+ $webResponse,
+ $this->createRouter() );
+ $entryPoint->execute();
+ $this->assertTrue( true );
+ }
+
+ public static function mockHandlerBodyRewind() {
+ return new class extends Handler {
+ public function execute() {
+ $response = $this->getResponseFactory()->create();
+ $stream = new Stream( fopen( 'php://memory', 'w+' ) );
+ $stream->write( 'hello' );
+ $response->setBody( $stream );
+ return $response;
+ }
+ };
+ }
+
+ /**
+ * Make sure EntryPoint rewinds a seekable body stream before reading.
+ */
+ public function testBodyRewind() {
+ $entryPoint = new EntryPoint(
+ RequestContext::getMain(),
+ new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
+ $this->createWebResponse(),
+ $this->createRouter() );
+ ob_start();
+ $entryPoint->execute();
+ $this->assertSame( 'hello', ob_get_clean() );
+ }
+
+}
*/
private function mockResultClosure( $title, $setters = [] ) {
return function () use ( $title, $setters ){
- $result = MockSearchResult::newFromTitle( Title::newFromText( $title ) );
+ $result = new MockSearchResult( Title::newFromText( $title ) );
foreach ( $setters as $method => $param ) {
$result->$method( $param );
'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(
--- /dev/null
+<?php
+
+/**
+ * @covers ConverterRule
+ */
+class ConverterRuleTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ parent::setUp();
+ $this->setMwGlobals( 'wgUser', new User );
+ }
+
+ public function testParseEmpty() {
+ $converter = new LanguageConverter( new Language(), 'en' );
+ $rule = new ConverterRule( '', $converter );
+ $rule->parse();
+
+ $this->assertSame( false, $rule->getTitle(), 'title' );
+ $this->assertSame( [], $rule->getConvTable(), 'conversion table' );
+ $this->assertSame( 'none', $rule->getRulesAction(), 'rules action' );
+ }
+
+}
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();
}
*/
public function testCheckPopularPasswordBlacklist( $expected, $password ) {
global $IP;
- $this->hideDeprecated( 'PasswordPolicyChecks::checkPopularPasswordBlacklist' );
$this->setMwGlobals( [
'wgSitename' => 'sitename',
'wgPopularPasswordFile' => "$IP/includes/password/commonpasswords.cdb"
+++ /dev/null
-<?php
-
-class SearchResultTest extends MediawikiTestCase {
- /**
- * @covers SearchResult::getExtensionData
- * @covers SearchResult::setExtensionData
- */
- public function testExtensionData() {
- $result = SearchResult::newFromTitle( Title::newMainPage() );
- $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
-
- $data = [ 'hello' => 'world' ];
- $result->setExtensionData( function () use ( &$data ) {
- return $data;
- } );
- $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
- $data['this'] = 'that';
- $this->assertEquals( $data, $result->getExtensionData(), 'refetches from callback' );
- }
-
- /**
- * @covers SearchResult::getExtensionData
- * @covers SearchResult::setExtensionData
- */
- public function testExtensionDataArrayBC() {
- $result = SearchResult::newFromTitle( Title::newMainPage() );
- $data = [ 'hello' => 'world' ];
- $this->hideDeprecated( 'SearchResult::setExtensionData with array argument' );
- $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
- $result->setExtensionData( $data );
- $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
- $data['this'] = 'that';
- $this->assertNotEquals( $data, $result->getExtensionData(), 'shouldnt hold any reference' );
-
- $result->setExtensionData( $data );
- $this->assertEquals( $data, $result->getExtensionData(), 'can replace extension data' );
- }
-}
--- /dev/null
+<?php
+
+class SearchResultTraitTest extends MediawikiTestCase {
+ /**
+ * @covers SearchResultTrait::getExtensionData
+ * @covers SearchResultTrait::setExtensionData
+ */
+ public function testExtensionData() {
+ $result = new class() {
+ use SearchResultTrait;
+ };
+ $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
+
+ $data = [ 'hello' => 'world' ];
+ $result->setExtensionData( function () use ( &$data ) {
+ return $data;
+ } );
+ $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
+ $data['this'] = 'that';
+ $this->assertEquals( $data, $result->getExtensionData(), 'refetches from callback' );
+ }
+
+ /**
+ * @covers SearchResultTrait::getExtensionData
+ * @covers SearchResultTrait::setExtensionData
+ */
+ public function testExtensionDataArrayBC() {
+ $result = new class() {
+ use SearchResultTrait;
+ };
+ $data = [ 'hello' => 'world' ];
+ $this->hideDeprecated( 'SearchResultTrait::setExtensionData with array argument' );
+ $this->assertEquals( [], $result->getExtensionData(), 'starts empty' );
+ $result->setExtensionData( $data );
+ $this->assertEquals( $data, $result->getExtensionData(), 'can set extension data' );
+ $data['this'] = 'that';
+ $this->assertNotEquals( $data, $result->getExtensionData(), 'shouldnt hold any reference' );
+
+ $result->setExtensionData( $data );
+ $this->assertEquals( $data, $result->getExtensionData(), 'can replace extension data' );
+ }
+}
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' ) );
- }
}
<?php
-class MockSearchResult extends SearchResult {
+class MockSearchResult extends RevisionSearchResult {
private $isMissingRevision = false;
private $isBrokenTitle = false;
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use EmptyBagOStuff;
-use GuzzleHttp\Psr7\Uri;
-use GuzzleHttp\Psr7\Stream;
-use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
-use MediaWiki\Rest\Handler;
-use MediaWiki\Rest\EntryPoint;
-use MediaWiki\Rest\RequestData;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWiki\Rest\Router;
-use WebResponse;
-
-/**
- * @covers \MediaWiki\Rest\EntryPoint
- * @covers \MediaWiki\Rest\Router
- */
-class EntryPointTest extends \MediaWikiUnitTestCase {
- private static $mockHandler;
-
- private function createRouter() {
- return new Router(
- [ __DIR__ . '/testRoutes.json' ],
- [],
- '/rest',
- new EmptyBagOStuff(),
- new ResponseFactory(),
- new StaticBasicAuthorizer() );
- }
-
- private function createWebResponse() {
- return $this->getMockBuilder( WebResponse::class )
- ->setMethods( [ 'header' ] )
- ->getMock();
- }
-
- public static function mockHandlerHeader() {
- return new class extends Handler {
- public function execute() {
- $response = $this->getResponseFactory()->create();
- $response->setHeader( 'Foo', 'Bar' );
- return $response;
- }
- };
- }
-
- public function testHeader() {
- $webResponse = $this->createWebResponse();
- $webResponse->expects( $this->any() )
- ->method( 'header' )
- ->withConsecutive(
- [ 'HTTP/1.1 200 OK', true, null ],
- [ 'Foo: Bar', true, null ]
- );
-
- $entryPoint = new EntryPoint(
- new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
- $webResponse,
- $this->createRouter() );
- $entryPoint->execute();
- $this->assertTrue( true );
- }
-
- public static function mockHandlerBodyRewind() {
- return new class extends Handler {
- public function execute() {
- $response = $this->getResponseFactory()->create();
- $stream = new Stream( fopen( 'php://memory', 'w+' ) );
- $stream->write( 'hello' );
- $response->setBody( $stream );
- return $response;
- }
- };
- }
-
- /**
- * Make sure EntryPoint rewinds a seekable body stream before reading.
- */
- public function testBodyRewind() {
- $entryPoint = new EntryPoint(
- new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
- $this->createWebResponse(),
- $this->createRouter() );
- ob_start();
- $entryPoint->execute();
- $this->assertSame( 'hello', ob_get_clean() );
- }
-
-}
--- /dev/null
+<?php
+
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * @covers MediaWiki\FileBackend\LockManager\LockManagerGroupFactory
+ * @todo Should we somehow test that the LockManagerGroup objects are as we expect? How do we do
+ * that without getting into testing LockManagerGroup itself?
+ */
+class LockManagerGroupFactoryTest extends MediaWikiUnitTestCase {
+ public function testGetLockManagerGroup() {
+ $mockLbFactory = $this->createMock( LBFactory::class );
+ $mockLbFactory->expects( $this->never() )->method( $this->anything() );
+
+ $factory = new LockManagerGroupFactory( 'defaultDomain', [], $mockLbFactory );
+ $lbmUnspecified = $factory->getLockManagerGroup();
+ $lbmFalse = $factory->getLockManagerGroup( false );
+ $lbmDefault = $factory->getLockManagerGroup( 'defaultDomain' );
+ $lbmOther = $factory->getLockManagerGroup( 'otherDomain' );
+
+ $this->assertSame( $lbmUnspecified, $lbmFalse );
+ $this->assertSame( $lbmFalse, $lbmDefault );
+ $this->assertSame( $lbmDefault, $lbmUnspecified );
+ $this->assertNotEquals( $lbmUnspecified, $lbmOther );
+ $this->assertNotEquals( $lbmFalse, $lbmOther );
+ $this->assertNotEquals( $lbmDefault, $lbmOther );
+
+ $this->assertSame( $lbmUnspecified, $factory->getLockManagerGroup() );
+ $this->assertSame( $lbmFalse, $factory->getLockManagerGroup( false ) );
+ $this->assertSame( $lbmDefault, $factory->getLockManagerGroup( 'defaultDomain' ) );
+ $this->assertSame( $lbmOther, $factory->getLockManagerGroup( 'otherDomain' ) );
+ }
+}
--- /dev/null
+<?php
+
+use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Since this is a unit test, we don't test the singleton() or destroySingletons() methods. We also
+ * can't test get() with a valid argument, because that winds up calling static methods of
+ * ObjectCache and LoggerFactory that aren't yet compatible with proper unit tests. Those will be
+ * tested in the integration test for now.
+ *
+ * @covers LockManagerGroup
+ */
+class LockManagerGroupTest extends MediaWikiUnitTestCase {
+ private function getMockLBFactory() {
+ $mock = $this->createMock( LBFactory::class );
+ $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
+ return $mock;
+ }
+
+ public function testConstructorNoConfigs() {
+ new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+ $this->assertTrue( true, 'No exception thrown' );
+ }
+
+ public function testConstructorConfigWithNoName() {
+ $this->setExpectedException( Exception::class,
+ 'Cannot register a lock manager with no name.' );
+
+ new LockManagerGroup( 'domain',
+ [ [ 'name' => 'a', 'class' => 'b' ], [ 'class' => 'c' ] ], $this->getMockLBFactory() );
+ }
+
+ public function testConstructorConfigWithNoClass() {
+ $this->setExpectedException( Exception::class,
+ 'Cannot register lock manager `c` with no class.' );
+
+ new LockManagerGroup( 'domain',
+ [ [ 'name' => 'a', 'class' => 'b' ], [ 'name' => 'c' ] ], $this->getMockLBFactory() );
+ }
+
+ public function testGetUndefined() {
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `c`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->get( 'c' );
+ }
+
+ public function testConfigUndefined() {
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `c`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->config( 'c' );
+ }
+
+ public function testConfig() {
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b', 'foo' => 'c' ] ],
+ $this->getMockLBFactory() );
+ $this->assertSame(
+ [ 'class' => 'b', 'name' => 'a', 'foo' => 'c', 'domain' => 'domain' ],
+ $lmg->config( 'a' )
+ );
+ }
+
+ public function testGetDefaultNull() {
+ $lmg = new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+ $expected = new NullLockManager( [] );
+ $actual = $lmg->getDefault();
+ // Have to get rid of the $sessions for equality check to work
+ TestingAccessWrapper::newFromObject( $actual )->session = null;
+ TestingAccessWrapper::newFromObject( $expected )->session = null;
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public function testGetAnyException() {
+ // XXX Isn't the name 'getAny' misleading if we don't get whatever's available?
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `fsLockManager`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->getAny();
+ }
+}
+++ /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;
- }
-}
// Test reporter for stdout.
// See also: http://webdriver.io/guide/testrunner/reporters.html
- reporters: [ 'spec', 'junit' ],
+ reporters: [ 'dot', 'junit' ],
reporterOptions: {
junit: {
outputDir: logPath