Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageIndex.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageLoading;
5
6use Exception;
7use JobQueueGroup;
8use MapCacheLRU;
9use MediaWiki\Config\ServiceOptions;
13use MediaWiki\Title\Title;
14use MessageGroup;
15use Psr\Log\LoggerInterface;
16use Wikimedia\ObjectCache\BagOStuff;
17use Wikimedia\ObjectCache\WANObjectCache;
18use Wikimedia\Rdbms\IConnectionProvider;
19
33 // TODO: Use dependency injection
34 private const CACHE_KEY = 'Translate-MessageIndex-interim';
35 private const READ_LATEST = true;
36 private MessageIndexStore $messageIndexStore;
37 private MapCacheLRU $keysCache;
38 protected BagOStuff $interimCache;
39 private WANObjectCache $statusCache;
40 private JobQueueGroup $jobQueueGroup;
41 private HookRunner $hookRunner;
42 private LoggerInterface $logger;
43 private IConnectionProvider $dbProvider;
44 private array $translateMessageNamespaces;
45 public const SERVICE_OPTIONS = [
46 'TranslateMessageNamespaces'
47 ];
48
49 public function __construct(
50 MessageIndexStore $store,
51 WANObjectCache $statusCache,
52 JobQueueGroup $jobQueueGroup,
53 HookRunner $hookRunner,
54 LoggerInterface $logger,
55 BagOStuff $interimCache,
56 IConnectionProvider $dbProvider,
57 ServiceOptions $options
58 ) {
59 $this->messageIndexStore = $store;
60 $this->keysCache = new MapCacheLRU( 30 );
61 $this->statusCache = $statusCache;
62 $this->jobQueueGroup = $jobQueueGroup;
63 $this->hookRunner = $hookRunner;
64 $this->logger = $logger;
65 $this->interimCache = $interimCache;
66 $this->dbProvider = $dbProvider;
67 $options->assertRequiredOptions( self::SERVICE_OPTIONS );
68 $this->translateMessageNamespaces = $options->get( 'TranslateMessageNamespaces' );
69 }
70
72 private function normaliseKey( int $namespace, string $key ): string {
73 $key = lcfirst( $key );
74
75 return strtr( "$namespace:$key", ' ', '_' );
76 }
77
82 public function getGroupIds( MessageHandle $handle ): array {
83 $title = $handle->getTitle();
84
85 if ( !$title->inNamespaces( $this->translateMessageNamespaces ) ) {
86 return [];
87 }
88
89 $namespace = $title->getNamespace();
90 $key = $handle->getKey();
91 $normalisedKey = $this->normaliseKey( $namespace, $key );
92
93 $value = $this->keysCache->get( $normalisedKey );
94 if ( $value === null ) {
95 $value = (array)$this->getWithCache( $normalisedKey );
96 $this->keysCache->set( $normalisedKey, $value );
97 }
98
99 return $value;
100 }
101
109 public function getGroupIdsForDatabaseTitle( int $namespace, string $title ): array {
110 $normalisedKey = $this->normaliseKey( $namespace, $title );
111
112 // Optimization 1: skip LRU cache assuming that hit rate is very low for this use case
113 // Optimization 2: skip interim cache as not essential
114
115 return (array)$this->get( $normalisedKey ) ?? [];
116 }
117
118 public function getPrimaryGroupId( MessageHandle $handle ): ?string {
119 $groups = $this->getGroupIds( $handle );
120
121 return count( $groups ) ? array_shift( $groups ) : null;
122 }
123
125 private function getWithCache( string $key ) {
126 $interimCacheValue = $this->getInterimCache()->get( self::CACHE_KEY );
127 if ( $interimCacheValue && isset( $interimCacheValue['newKeys'][$key] ) ) {
128 $this->logger->debug(
129 '[MessageIndex] interim cache hit: {messageKey} with value {groupId}',
130 [ 'messageKey' => $key, 'groupId' => $interimCacheValue['newKeys'][$key] ]
131 );
132 return $interimCacheValue['newKeys'][$key];
133 }
134
135 return $this->messageIndexStore->get( $key );
136 }
137
138 public function get( string $key ) {
139 return $this->messageIndexStore->get( $key );
140 }
141
143 public function getKeys(): array {
144 return $this->messageIndexStore->getKeys();
145 }
146
147 private function lock(): bool {
148 $dbw = $this->dbProvider->getPrimaryDatabase();
149
150 // Any transaction should be flushed after getting the lock to avoid
151 // stale pre-lock REPEATABLE-READ snapshot data.
152 $ok = $dbw->lock( 'translate-messageindex', __METHOD__, 5 );
153 if ( $ok ) {
154 $dbw->commit( __METHOD__, 'flush' );
155 }
156
157 return $ok;
158 }
159
160 private function unlock(): void {
161 $fname = __METHOD__;
162 $dbw = $this->dbProvider->getPrimaryDatabase();
163 // Unlock once the rows are actually unlocked to avoid deadlocks
164 if ( !$dbw->trxLevel() ) {
165 $dbw->unlock( 'translate-messageindex', $fname );
166 } else {
167 $dbw->onTransactionResolution( static function () use ( $dbw, $fname ) {
168 $dbw->unlock( 'translate-messageindex', $fname );
169 }, $fname );
170 }
171 }
172
179 public function rebuild( ?float $timestamp = null ): array {
180 static $recursion = 0;
181
182 if ( $recursion > 0 ) {
183 $msg = __METHOD__ . ': trying to recurse - building the index first time?';
184 wfWarn( $msg );
185
186 $recursion--;
187 return [];
188 }
189 $recursion++;
190
191 $this->logger->info( '[MessageIndex] Started rebuild.' );
192
193 $tsStart = microtime( true );
194 if ( !$this->lock() ) {
195 throw new MessageIndexException( __CLASS__ . ': unable to acquire lock' );
196 }
197
198 $lockWaitDuration = microtime( true ) - $tsStart;
199 $this->logger->info(
200 '[MessageIndex] Got lock in {duration}',
201 [ 'duration' => $lockWaitDuration ]
202 );
203
204 $groups = MessageGroups::singleton()->getGroups();
205 $this->keysCache->clear();
206
207 $new = [];
208 $old = $this->messageIndexStore->retrieve( self::READ_LATEST );
209 $postponed = [];
210
211 foreach ( $groups as $messageGroup ) {
212 if ( !$messageGroup->exists() ) {
213 $id = $messageGroup->getId();
214 wfWarn( __METHOD__ . ": group '$id' is registered but does not exist" );
215 continue;
216 }
217
218 # Skip meta thingies
219 if ( $messageGroup->isMeta() ) {
220 $postponed[] = $messageGroup;
221 continue;
222 }
223
224 $this->checkAndAdd( $new, $messageGroup );
225 }
226
227 foreach ( $postponed as $messageGroup ) {
228 $this->checkAndAdd( $new, $messageGroup, true );
229 }
230
231 $diff = self::getArrayDiff( $old, $new );
232 $this->messageIndexStore->store( $new, $diff['keys'] );
233
234 $cache = $this->getInterimCache();
235 $interimCacheValue = $cache->get( self::CACHE_KEY );
236 if ( $interimCacheValue ) {
237 $timestamp ??= microtime( true );
238 if ( $interimCacheValue['timestamp'] <= $timestamp ) {
239 $cache->delete( self::CACHE_KEY );
240 $this->logger->debug(
241 '[MessageIndex] Deleted interim cache with timestamp {cacheTimestamp} <= {currentTimestamp}.',
242 [
243 'cacheTimestamp' => $interimCacheValue['timestamp'],
244 'currentTimestamp' => $timestamp,
245 ]
246 );
247 } else {
248 // Cache has a later timestamp. This may be caused due to
249 // job deduplication. Just in case, spin off a new job to clean up the cache.
250 $job = RebuildMessageIndexJob::newJob( __METHOD__ );
251 $this->jobQueueGroup->push( $job );
252 $this->logger->debug(
253 '[MessageIndex] Kept interim cache with timestamp {cacheTimestamp} > {currentTimestamp}.',
254 [
255 'cacheTimestamp' => $interimCacheValue['timestamp'],
256 'currentTimestamp' => $timestamp,
257 ]
258 );
259 }
260 }
261
262 $this->unlock();
263 $criticalSectionDuration = microtime( true ) - $tsStart - $lockWaitDuration;
264 $this->logger->info(
265 '[MessageIndex] Finished critical section in {duration}',
266 [ 'duration' => $criticalSectionDuration ]
267 );
268
269 // Other caches can check this key to know when they need to refresh
270 $this->statusCache->touchCheckKey( $this->getStatusCacheKey() );
271
272 $this->clearMessageGroupStats( $diff );
273
274 $recursion--;
275
276 return $new;
277 }
278
279 public function getStatusCacheKey(): string {
280 return $this->statusCache->makeKey( 'Translate', 'MessageIndex', 'status' );
281 }
282
283 private function getInterimCache(): BagOStuff {
284 return $this->interimCache;
285 }
286
287 public function storeInterim( MessageGroup $group, array $newKeys ): void {
288 $namespace = $group->getNamespace();
289 $id = $group->getId();
290
291 $normalizedNewKeys = [];
292 foreach ( $newKeys as $key ) {
293 $normalizedNewKeys[$this->normaliseKey( $namespace, $key )] = $id;
294 }
295
296 $cache = $this->getInterimCache();
297 // Merge with existing keys (if present)
298 $interimCacheValue = $cache->get( self::CACHE_KEY, $cache::READ_LATEST );
299 if ( $interimCacheValue ) {
300 $normalizedNewKeys = array_merge( $interimCacheValue['newKeys'], $normalizedNewKeys );
301 $this->logger->debug(
302 '[MessageIndex] interim cache: merging with existing cache of size {count}',
303 [ 'count' => count( $interimCacheValue['newKeys'] ) ]
304 );
305 }
306
307 $value = [
308 'timestamp' => microtime( true ),
309 'newKeys' => $normalizedNewKeys,
310 ];
311
312 $cache->set( self::CACHE_KEY, $value, $cache::TTL_DAY );
313 $this->logger->debug(
314 '[MessageIndex] interim cache: added group {groupId} with new size {count} keys and ' .
315 'timestamp {cacheTimestamp}',
316 [ 'groupId' => $id, 'count' => count( $normalizedNewKeys ), 'cacheTimestamp' => $value['timestamp'] ]
317 );
318 }
319
348 public function getArrayDiff( array $old, array $new ): array {
349 $values = [];
350 $record = static function ( $groups ) use ( &$values ) {
351 foreach ( $groups as $group ) {
352 $values[$group] = true;
353 }
354 };
355
356 $keys = [
357 'add' => [],
358 'del' => [],
359 'mod' => [],
360 ];
361
362 foreach ( $new as $key => $groups ) {
363 if ( !isset( $old[$key] ) ) {
364 $keys['add'][$key] = [ [], (array)$groups ];
365 $record( (array)$groups );
366 // Using != here on purpose to ignore the order of items
367 } elseif ( $groups != $old[$key] ) {
368 $keys['mod'][$key] = [ (array)$old[$key], (array)$groups ];
369 $record( array_diff( (array)$old[$key], (array)$groups ) );
370 $record( array_diff( (array)$groups, (array)$old[$key] ) );
371 }
372 }
373
374 foreach ( $old as $key => $groups ) {
375 if ( !isset( $new[$key] ) ) {
376 $keys['del'][$key] = [ (array)$groups, [] ];
377 $record( (array)$groups );
378 }
379 // We already checked for diffs above
380 }
381
382 return [
383 'keys' => $keys,
384 'values' => array_keys( $values ),
385 ];
386 }
387
389 protected function clearMessageGroupStats( array $diff ): void {
390 $job = RebuildMessageGroupStatsJob::newRefreshGroupsJob( $diff['values'] );
391 $this->jobQueueGroup->push( $job );
392
393 foreach ( $diff['keys'] as $keys ) {
394 foreach ( $keys as $key => $data ) {
395 [ $ns, $pageName ] = explode( ':', $key, 2 );
396 $title = Title::makeTitle( (int)$ns, $pageName );
397 $handle = new MessageHandle( $title );
398 [ $oldGroups, $newGroups ] = $data;
399 $this->hookRunner->onTranslateEventMessageMembershipChange(
400 $handle, $oldGroups, $newGroups );
401 }
402 }
403 }
404
405 protected function checkAndAdd( array &$hugeArray, MessageGroup $g, bool $ignore = false ): void {
406 $keys = $g->getKeys();
407 $id = $g->getId();
408 $namespace = $g->getNamespace();
409
410 foreach ( $keys as $key ) {
411 # Force all keys to lower case, because the case doesn't matter and it is
412 # easier to do comparing when the case of first letter is unknown, because
413 # mediawiki forces it to upper case
414 $key = $this->normaliseKey( $namespace, $key );
415 if ( isset( $hugeArray[$key] ) ) {
416 if ( !$ignore ) {
417 $to = implode( ', ', (array)$hugeArray[$key] );
418 wfWarn( "Key $key already belongs to $to, conflict with $id" );
419 }
420
421 if ( is_array( $hugeArray[$key] ) ) {
422 // Hard work is already done, just add a new reference
423 $hugeArray[$key][] = & $id;
424 } else {
425 // Store the actual reference, then remove it from array, to not
426 // replace the references value, but to store an array of new
427 // references instead. References are hard!
428 $value = & $hugeArray[$key];
429 unset( $hugeArray[$key] );
430 $hugeArray[$key] = [ &$value, &$id ];
431 }
432 } else {
433 $hugeArray[$key] = & $id;
434 }
435 }
436 unset( $id ); // Disconnect the previous references to this $id
437 }
438}
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):MessageGroupSubscriptionHookHandler { return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory());}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
Hook runner for the Translate extension.
Factory class for accessing message groups individually by id or all of them as a list.
Class for pointing to messages, like Title class is for titles.
getKey()
Returns the identified or guessed message key.
Creates a database of keys in all groups, so that namespace and key can be used to get the groups the...
getGroupIds(MessageHandle $handle)
Retrieves a list of groups given MessageHandle belongs to.
getArrayDiff(array $old, array $new)
Compares two associative arrays.
clearMessageGroupStats(array $diff)
Purge stuff when set of keys have changed.
rebuild(?float $timestamp=null)
Creates the index from scratch.
getGroupIdsForDatabaseTitle(int $namespace, string $title)
Fast-path to retrieve groups for database titles.
Interface for message groups.
getNamespace()
Returns the namespace where messages are placed.
getId()
Returns the unique identifier for this group.