Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageIndex.php
Go to the documentation of this file.
1<?php
11use Cdb\Reader;
12use Cdb\Writer;
17use MediaWiki\Logger\LoggerFactory;
18use MediaWiki\MediaWikiServices;
19
28abstract class MessageIndex {
29 private const CACHEKEY = 'Translate-MessageIndex-interim';
30
31 private const READ_LATEST = true;
32
34 protected static $instance;
36 private static $keysCache;
38 protected $interimCache;
40 private $statusCache;
42 private $jobQueueGroup;
44 private $hookRunner;
45
46 public function __construct() {
47 // TODO: Use dependency injection
48 $mwInstance = MediaWikiServices::getInstance();
49 $this->statusCache = $mwInstance->getMainWANObjectCache();
50 $this->jobQueueGroup = $mwInstance->getJobQueueGroup();
51 $this->hookRunner = Services::getInstance()->getHookRunner();
52 }
53
58 public static function singleton(): self {
59 if ( self::$instance === null ) {
60 self::$instance = Services::getInstance()->getMessageIndex();
61 }
62
63 return self::$instance;
64 }
65
72 public static function setInstance( self $instance ) {
73 self::$instance = $instance;
74 }
75
82 public static function getGroupIds( MessageHandle $handle ): array {
83 global $wgTranslateMessageNamespaces;
84
85 $title = $handle->getTitle();
86
87 if ( !$title->inNamespaces( $wgTranslateMessageNamespaces ) ) {
88 return [];
89 }
90
91 $namespace = $title->getNamespace();
92 $key = $handle->getKey();
93 $normkey = Utilities::normaliseKey( $namespace, $key );
94
95 $cache = self::getCache();
96 $value = $cache->get( $normkey );
97 if ( $value === null ) {
98 $value = (array)self::singleton()->getWithCache( $normkey );
99 $cache->set( $normkey, $value );
100 }
101
102 return $value;
103 }
104
106 private static function getCache() {
107 if ( self::$keysCache === null ) {
108 self::$keysCache = new MapCacheLRU( 30 );
109 }
110 return self::$keysCache;
111 }
112
118 public static function getPrimaryGroupId( MessageHandle $handle ): ?string {
119 $groups = self::getGroupIds( $handle );
120
121 return count( $groups ) ? array_shift( $groups ) : null;
122 }
123
124 private function getWithCache( $key ) {
125 $interimCacheValue = $this->getInterimCache()->get( self::CACHEKEY );
126 if ( $interimCacheValue && isset( $interimCacheValue['newKeys'][$key] ) ) {
127 return $interimCacheValue['newKeys'][$key];
128 }
129
130 return $this->get( $key );
131 }
132
139 protected function get( $key ) {
140 // Default implementation
141 $mi = $this->retrieve();
142 return $mi[$key] ?? null;
143 }
144
145 abstract public function retrieve( bool $readLatest = false ): array;
146
151 public function getKeys() {
152 return array_keys( $this->retrieve() );
153 }
154
155 abstract protected function store( array $array, array $diff );
156
157 protected function lock() {
158 return true;
159 }
160
161 protected function unlock() {
162 return true;
163 }
164
172 public function rebuild( float $timestamp = null ): array {
173 $logger = LoggerFactory::getInstance( 'Translate' );
174
175 static $recursion = 0;
176
177 if ( $recursion > 0 ) {
178 $msg = __METHOD__ . ': trying to recurse - building the index first time?';
179 wfWarn( $msg );
180
181 $recursion--;
182 return [];
183 }
184 $recursion++;
185
186 $logger->info(
187 '[MessageIndex] Started rebuild. Initiated by {callers}',
188 [ 'callers' => wfGetAllCallers( 20 ) ]
189 );
190
191 $groups = MessageGroups::singleton()->getGroups();
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 $logger->info(
200 '[MessageIndex] Got lock in {duration}',
201 [ 'duration' => $lockWaitDuration ]
202 );
203
204 self::getCache()->clear();
205
206 $new = [];
207 $old = $this->retrieve( self::READ_LATEST );
208 $postponed = [];
209
211 foreach ( $groups as $g ) {
212 if ( !$g->exists() ) {
213 $id = $g->getId();
214 wfWarn( __METHOD__ . ": group '$id' is registered but does not exist" );
215 continue;
216 }
217
218 # Skip meta thingies
219 if ( $g->isMeta() ) {
220 $postponed[] = $g;
221 continue;
222 }
223
224 $this->checkAndAdd( $new, $g );
225 }
226
227 foreach ( $postponed as $g ) {
228 $this->checkAndAdd( $new, $g, true );
229 }
230
231 $diff = self::getArrayDiff( $old, $new );
232 $this->store( $new, $diff['keys'] );
233 $this->unlock();
234
235 $criticalSectionDuration = microtime( true ) - $tsStart - $lockWaitDuration;
236 $logger->info(
237 '[MessageIndex] Finished critical section in {duration}',
238 [ 'duration' => $criticalSectionDuration ]
239 );
240
241 $cache = $this->getInterimCache();
242 $interimCacheValue = $cache->get( self::CACHEKEY );
243 if ( $interimCacheValue ) {
244 $timestamp ??= microtime( true );
245 if ( $interimCacheValue['timestamp'] <= $timestamp ) {
246 $cache->delete( self::CACHEKEY );
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.
251 $this->jobQueueGroup->push( $job );
252 }
253 }
254
255 // Other caches can check this key to know when they need to refresh
256 $this->statusCache->touchCheckKey( $this->getStatusCacheKey() );
257
258 $this->clearMessageGroupStats( $diff );
259
260 $recursion--;
261
262 return $new;
263 }
264
269 public function getStatusCacheKey(): string {
270 return $this->statusCache->makeKey( 'Translate', 'MessageIndex', 'status' );
271 }
272
273 private function getInterimCache(): BagOStuff {
274 return ObjectCache::getInstance( CACHE_ANYTHING );
275 }
276
277 public function storeInterim( MessageGroup $group, array $newKeys ): void {
278 $namespace = $group->getNamespace();
279 $id = $group->getId();
280
281 $normalizedNewKeys = [];
282 foreach ( $newKeys as $key ) {
283 $normalizedNewKeys[Utilities::normaliseKey( $namespace, $key )] = $id;
284 }
285
286 $cache = $this->getInterimCache();
287 // Merge existing with existing keys
288 $interimCacheValue = $cache->get( self::CACHEKEY, $cache::READ_LATEST );
289 if ( $interimCacheValue ) {
290 $normalizedNewKeys = array_merge( $interimCacheValue['newKeys'], $normalizedNewKeys );
291 }
292
293 $value = [
294 'timestamp' => microtime( true ),
295 'newKeys' => $normalizedNewKeys,
296 ];
297
298 $cache->set( self::CACHEKEY, $value, $cache::TTL_DAY );
299 }
300
329 public static function getArrayDiff( array $old, array $new ) {
330 $values = [];
331 $record = static function ( $groups ) use ( &$values ) {
332 foreach ( $groups as $group ) {
333 $values[$group] = true;
334 }
335 };
336
337 $keys = [
338 'add' => [],
339 'del' => [],
340 'mod' => [],
341 ];
342
343 foreach ( $new as $key => $groups ) {
344 if ( !isset( $old[$key] ) ) {
345 $keys['add'][$key] = [ [], (array)$groups ];
346 $record( (array)$groups );
347 // Using != here on purpose to ignore the order of items
348 } elseif ( $groups != $old[$key] ) {
349 $keys['mod'][$key] = [ (array)$old[$key], (array)$groups ];
350 $record( array_diff( (array)$old[$key], (array)$groups ) );
351 $record( array_diff( (array)$groups, (array)$old[$key] ) );
352 }
353 }
354
355 foreach ( $old as $key => $groups ) {
356 if ( !isset( $new[$key] ) ) {
357 $keys['del'][$key] = [ (array)$groups, [] ];
358 $record( (array)$groups );
359 }
360 // We already checked for diffs above
361 }
362
363 return [
364 'keys' => $keys,
365 'values' => array_keys( $values ),
366 ];
367 }
368
374 protected function clearMessageGroupStats( array $diff ) {
375 $job = MessageGroupStatsRebuildJob::newRefreshGroupsJob( $diff['values'] );
376 $this->jobQueueGroup->push( $job );
377
378 foreach ( $diff['keys'] as $keys ) {
379 foreach ( $keys as $key => $data ) {
380 [ $ns, $pagename ] = explode( ':', $key, 2 );
381 $title = Title::makeTitle( (int)$ns, $pagename );
382 $handle = new MessageHandle( $title );
383 [ $oldGroups, $newGroups ] = $data;
384 $this->hookRunner->onTranslateEventMessageMembershipChange(
385 $handle, $oldGroups, $newGroups );
386 }
387 }
388 }
389
395 protected function checkAndAdd( &$hugearray, MessageGroup $g, $ignore = false ) {
396 $keys = $g->getKeys();
397 $id = $g->getId();
398 $namespace = $g->getNamespace();
399
400 foreach ( $keys as $key ) {
401 # Force all keys to lower case, because the case doesn't matter and it is
402 # easier to do comparing when the case of first letter is unknown, because
403 # mediawiki forces it to upper case
404 $key = Utilities::normaliseKey( $namespace, $key );
405 if ( isset( $hugearray[$key] ) ) {
406 if ( !$ignore ) {
407 $to = implode( ', ', (array)$hugearray[$key] );
408 wfWarn( "Key $key already belongs to $to, conflict with $id" );
409 }
410
411 if ( is_array( $hugearray[$key] ) ) {
412 // Hard work is already done, just add a new reference
413 $hugearray[$key][] = & $id;
414 } else {
415 // Store the actual reference, then remove it from array, to not
416 // replace the references value, but to store an array of new
417 // references instead. References are hard!
418 $value = & $hugearray[$key];
419 unset( $hugearray[$key] );
420 $hugearray[$key] = [ &$value, &$id ];
421 }
422 } else {
423 $hugearray[$key] = & $id;
424 }
425 }
426 unset( $id ); // Disconnect the previous references to this $id
427 }
428
436 protected function serialize( $data ) {
437 return is_array( $data ) ? implode( '|', $data ) : $data;
438 }
439
440 protected function unserialize( $data ) {
441 $array = explode( '|', $data );
442 return count( $array ) > 1 ? $array : $data;
443 }
444}
445
462 protected $index;
463 protected $filename = 'translate_messageindex.ser';
464
465 public function retrieve( bool $readLatest = false ): array {
466 if ( $this->index !== null ) {
467 return $this->index;
468 }
469
470 $file = Utilities::cacheFile( $this->filename );
471 if ( file_exists( $file ) ) {
472 $this->index = unserialize( file_get_contents( $file ) );
473 } else {
474 $this->index = $this->rebuild();
475 }
476
477 return $this->index;
478 }
479
480 protected function store( array $array, array $diff ) {
481 $file = Utilities::cacheFile( $this->filename );
482 file_put_contents( $file, serialize( $array ) );
483 $this->index = $array;
484 }
485}
486
500 protected $index;
501
502 protected function lock() {
503 $dbw = wfGetDB( DB_PRIMARY );
504
505 // Any transaction should be flushed after getting the lock to avoid
506 // stale pre-lock REPEATABLE-READ snapshot data.
507 $ok = $dbw->lock( 'translate-messageindex', __METHOD__, 30 );
508 if ( $ok ) {
509 $dbw->commit( __METHOD__, 'flush' );
510 }
511
512 return $ok;
513 }
514
515 protected function unlock() {
516 $fname = __METHOD__;
517 $dbw = wfGetDB( DB_PRIMARY );
518 // Unlock once the rows are actually unlocked to avoid deadlocks
519 if ( !$dbw->trxLevel() ) {
520 $dbw->unlock( 'translate-messageindex', $fname );
521 } elseif ( is_callable( [ $dbw, 'onTransactionResolution' ] ) ) { // 1.28
522 $dbw->onTransactionResolution( static function () use ( $dbw, $fname ) {
523 $dbw->unlock( 'translate-messageindex', $fname );
524 }, $fname );
525 } else {
526 $dbw->onTransactionCommitOrIdle( static function () use ( $dbw, $fname ) {
527 $dbw->unlock( 'translate-messageindex', $fname );
528 }, $fname );
529 }
530
531 return true;
532 }
533
534 public function retrieve( bool $readLatest = false ): array {
535 if ( $this->index !== null && !$readLatest ) {
536 return $this->index;
537 }
538
539 $dbr = wfGetDB( $readLatest ? DB_PRIMARY : DB_REPLICA );
540 $res = $dbr->select( 'translate_messageindex', '*', [], __METHOD__ );
541 $this->index = [];
542 foreach ( $res as $row ) {
543 $this->index[$row->tmi_key] = $this->unserialize( $row->tmi_value );
544 }
545
546 return $this->index;
547 }
548
549 protected function get( $key ) {
550 $dbr = wfGetDB( DB_REPLICA );
551 $value = $dbr->selectField(
552 'translate_messageindex',
553 'tmi_value',
554 [ 'tmi_key' => $key ],
555 __METHOD__
556 );
557
558 return is_string( $value ) ? $this->unserialize( $value ) : null;
559 }
560
561 protected function store( array $array, array $diff ) {
562 $updates = [];
563
564 foreach ( [ $diff['add'], $diff['mod'] ] as $changes ) {
565 foreach ( $changes as $key => $data ) {
566 [ , $new ] = $data;
567 $updates[] = [
568 'tmi_key' => $key,
569 'tmi_value' => $this->serialize( $new ),
570 ];
571 }
572 }
573
574 $index = [ 'tmi_key' ];
575 $deletions = array_keys( $diff['del'] );
576
577 $dbw = wfGetDB( DB_PRIMARY );
578 $dbw->startAtomic( __METHOD__ );
579
580 if ( $updates !== [] ) {
581 $dbw->replace( 'translate_messageindex', [ $index ], $updates, __METHOD__ );
582 }
583
584 if ( $deletions !== [] ) {
585 $dbw->delete( 'translate_messageindex', [ 'tmi_key' => $deletions ], __METHOD__ );
586 }
587
588 $dbw->endAtomic( __METHOD__ );
589
590 $this->index = $array;
591 }
592}
593
603 protected $key = 'translate-messageindex';
604 protected $cache;
606 protected $index;
607
608 protected function __construct() {
609 parent::__construct();
610 $this->cache = ObjectCache::getInstance( CACHE_ANYTHING );
611 }
612
613 public function retrieve( bool $readLatest = false ): array {
614 if ( $this->index !== null ) {
615 return $this->index;
616 }
617
618 $key = $this->cache->makeKey( $this->key );
619 $data = $this->cache->get( $key );
620 if ( is_array( $data ) ) {
621 $this->index = $data;
622 } else {
623 $this->index = $this->rebuild();
624 }
625
626 return $this->index;
627 }
628
629 protected function store( array $array, array $diff ) {
630 $key = $this->cache->makeKey( $this->key );
631 $this->cache->set( $key, $array );
632
633 $this->index = $array;
634 }
635}
636
652 protected $index;
654 protected $reader;
656 protected $filename = 'translate_messageindex.cdb';
657
662 public function retrieve( bool $readLatest = false ): array {
663 $reader = $this->getReader();
664 // This must be below the line above, which may fill the index
665 if ( $this->index !== null ) {
666 return $this->index;
667 }
668
669 $this->index = [];
670 foreach ( $this->getKeys() as $key ) {
671 $this->index[$key] = $this->unserialize( $reader->get( $key ) );
672 }
673
674 return $this->index;
675 }
676
677 public function getKeys() {
678 $reader = $this->getReader();
679 $keys = [];
680 $key = $reader->firstkey();
681 while ( $key !== false ) {
682 $keys[] = $key;
683 $key = $reader->nextkey();
684 }
685
686 return $keys;
687 }
688
689 protected function get( $key ) {
690 $reader = $this->getReader();
691 // We might have the full cache loaded
692 if ( $this->index !== null ) {
693 return $this->index[$key] ?? null;
694 }
695
696 $value = $reader->get( $key );
697 return is_string( $value ) ? $this->unserialize( $value ) : null;
698 }
699
700 protected function store( array $array, array $diff ) {
701 $this->reader = null;
702
703 $file = Utilities::cacheFile( $this->filename );
704 $cache = Writer::open( $file );
705
706 foreach ( $array as $key => $value ) {
707 $value = $this->serialize( $value );
708 $cache->set( $key, $value );
709 }
710
711 $cache->close();
712
713 $this->index = $array;
714 }
715
716 protected function getReader() {
717 if ( $this->reader ) {
718 return $this->reader;
719 }
720
721 $file = Utilities::cacheFile( $this->filename );
722 if ( !file_exists( $file ) ) {
723 // Create an empty index to allow rebuild
724 $this->store( [], [] );
725 $this->index = $this->rebuild();
726 }
727
728 $this->reader = Reader::open( $file );
729 return $this->reader;
730 }
731}
732
742 protected $index = [];
743
744 public function retrieve( bool $readLatest = false ): array {
745 return $this->index;
746 }
747
752 protected function get( $key ) {
753 return $this->index[$key] ?? null;
754 }
755
756 protected function store( array $array, array $diff ) {
757 $this->index = $array;
758 }
759
760 protected function clearMessageGroupStats( array $diff ) {
761 }
762}
return[ '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:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'));}, '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:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, '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->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, '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());}, '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->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'),);}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getDBLoadBalancer());}, '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
Storage on CDB files.
retrieve(bool $readLatest=false)
Storage on the object cache.
Storage on the database itself.
Storage on hash.
clearMessageGroupStats(array $diff)
Purge stuff when set of keys have changed.
Hook runner for the Translate extension.
Factory class for accessing message groups individually by id or all of them as a list.
Minimal service container.
Definition Services.php:44
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
Class for pointing to messages, like Title class is for titles.
getTitle()
Get the original title.
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...
clearMessageGroupStats(array $diff)
Purge stuff when set of keys have changed.
checkAndAdd(&$hugearray, MessageGroup $g, $ignore=false)
static getPrimaryGroupId(MessageHandle $handle)
serialize( $data)
These are probably slower than serialize and unserialize, but they are more space efficient because w...
rebuild(float $timestamp=null)
Creates the index from scratch.
static singleton()
static setInstance(self $instance)
Override the global instance, for testing.
static getArrayDiff(array $old, array $new)
Compares two associative arrays.
static getGroupIds(MessageHandle $handle)
Retrieves a list of groups given MessageHandle belongs to.
Storage on serialized file.
Interface for message groups.
getNamespace()
Returns the namespace where messages are placed.
getId()
Returns the unique identifier for this group.
getKeys()
Shortcut for array_keys( getDefinitions() ) that can be optimized by the implementing classes.