Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
GroupSynchronizationCache.php
1<?php
2declare( strict_types = 1 );
3
5
6use DateTime;
7use InvalidArgumentException;
8use LogicException;
11use MediaWiki\Logger\LoggerFactory;
12use Psr\Log\LoggerInterface;
13use RuntimeException;
14
37 private PersistentCache $cache;
38 private int $initialTimeoutSeconds;
39 private int $incrementalTimeoutSeconds;
41 private const GROUP_LIST_TAG = 'gsc_%group_in_sync%';
43 private const GROUP_ERROR_TAG = 'gsc_%group_with_error%';
45 private const GROUP_IN_REVIEW_TAG = 'gsc_%group_in_review%';
46 private LoggerInterface $logger;
47
48 public function __construct(
49 PersistentCache $cache,
50 int $initialTimeoutSeconds = 2400,
51 int $incrementalTimeoutSeconds = 600
52
53 ) {
54 $this->cache = $cache;
55 // The timeout is set to 40 minutes initially, and then incremented by 10 minutes
56 // each time a message is marked as processed if group is about to expire.
57 $this->initialTimeoutSeconds = $initialTimeoutSeconds;
58 $this->incrementalTimeoutSeconds = $incrementalTimeoutSeconds;
59 $this->logger = LoggerFactory::getInstance( 'Translate.GroupSynchronization' );
60 }
61
66 public function getGroupsInSync(): array {
67 $groupsInSyncEntries = $this->cache->getByTag( self::GROUP_LIST_TAG );
68 $groups = [];
69 foreach ( $groupsInSyncEntries as $entry ) {
70 $groups[] = $entry->value();
71 }
72
73 return $groups;
74 }
75
77 public function markGroupForSync( string $groupId ): void {
78 $expTime = $this->getExpireTime( $this->initialTimeoutSeconds );
79 $this->cache->set(
81 $this->getGroupKey( $groupId ),
82 $groupId,
83 $expTime,
84 self::GROUP_LIST_TAG
85 )
86 );
87 $this->logger->debug( 'Started sync for group {groupId}', [ 'groupId' => $groupId ] );
88 }
89
90 public function getSyncEndTime( string $groupId ): ?int {
91 $cacheEntry = $this->cache->get( $this->getGroupKey( $groupId ) );
92 return $cacheEntry ? $cacheEntry[0]->exptime() : null;
93 }
94
96 public function endSync( string $groupId ): void {
97 if ( $this->cache->hasEntryWithTag( $this->getGroupTag( $groupId ) ) ) {
98 throw new InvalidArgumentException(
99 'Cannot end synchronization for a group that still has messages to be processed.'
100 );
101 }
102
103 $groupKey = $this->getGroupKey( $groupId );
104 $this->cache->delete( $groupKey );
105 $this->logger->debug( 'Ended sync for group {groupId}', [ 'groupId' => $groupId ] );
106 }
107
109 public function forceEndSync( string $groupId ): void {
110 $this->cache->deleteEntriesWithTag( $this->getGroupTag( $groupId ) );
111 $this->endSync( $groupId );
112 }
113
115 public function addMessages( string $groupId, MessageUpdateParameter ...$messageParams ): void {
116 $messagesToAdd = [];
117 $groupTag = $this->getGroupTag( $groupId );
118 foreach ( $messageParams as $messageParam ) {
119 $titleKey = $this->getMessageKeys( $groupId, $messageParam->getPageName() )[0];
120 $messagesToAdd[] = new PersistentCacheEntry(
121 $titleKey,
122 $messageParam,
123 null,
124 $groupTag
125 );
126 }
127
128 $this->cache->set( ...$messagesToAdd );
129 }
130
132 public function isGroupBeingProcessed( string $groupId ): bool {
133 $groupEntry = $this->cache->get( $this->getGroupKey( $groupId ) );
134 return $groupEntry !== [];
135 }
136
143 public function getGroupMessages( string $groupId ): array {
144 $messageEntries = $this->cache->getByTag( $this->getGroupTag( $groupId ) );
145
146 $allMessageParams = [];
147 foreach ( $messageEntries as $entry ) {
148 $message = $entry->value();
149 if ( $message instanceof MessageUpdateParameter ) {
150 $allMessageParams[$message->getPageName()] = $message;
151 } else {
152 // Should not happen, but handle primarily to keep phan happy.
153 throw $this->invalidArgument( $message, MessageUpdateParameter::class );
154 }
155 }
156
157 return $allMessageParams;
158 }
159
161 public function isMessageBeingProcessed( string $groupId, string $messageKey ): bool {
162 $messageCacheKey = $this->getMessageKeys( $groupId, $messageKey );
163 return $this->cache->has( $messageCacheKey[0] );
164 }
165
167 public function getSynchronizationStatus( string $groupId ): GroupSynchronizationResponse {
168 if ( !$this->isGroupBeingProcessed( $groupId ) ) {
169 // Group is currently not being processed.
170 throw new LogicException(
171 'Sync requested for a group currently not being processed. Check if ' .
172 'group is being processed by calling isGroupBeingProcessed() first'
173 );
174 }
175
176 $remainingMessages = $this->getGroupMessages( $groupId );
177
178 // No messages are present
179 if ( !$remainingMessages ) {
180 return new GroupSynchronizationResponse( $groupId, [], false );
181 }
182
183 $syncExpTime = $this->getSyncEndTime( $groupId );
184 if ( $syncExpTime === null ) {
185 // This should not happen
186 throw new RuntimeException(
187 "Unexpected condition. Group: $groupId; Messages present, but group key not found."
188 );
189 }
190
191 $hasTimedOut = $this->hasGroupTimedOut( $syncExpTime );
192
193 return new GroupSynchronizationResponse(
194 $groupId,
195 $remainingMessages,
196 $hasTimedOut
197 );
198 }
199
201 public function removeMessages( string $groupId, string ...$messageKeys ): void {
202 $messageCacheKeys = $this->getMessageKeys( $groupId, ...$messageKeys );
203
204 $this->cache->delete( ...$messageCacheKeys );
205 }
206
207 public function addGroupErrors( GroupSynchronizationResponse $response ): void {
208 $groupId = $response->getGroupId();
209 $remainingMessages = $response->getRemainingMessages();
210
211 if ( !$remainingMessages ) {
212 throw new LogicException( 'Cannot add a group without any remaining messages to the errors list' );
213 }
214
215 $groupMessageErrorTag = $this->getGroupMessageErrorTag( $groupId );
216
217 $entriesToSave = [];
218 foreach ( $remainingMessages as $messageParam ) {
219 $titleErrorKey = $this->getMessageErrorKey( $groupId, $messageParam->getPageName() )[0];
220 $entriesToSave[] = new PersistentCacheEntry(
221 $titleErrorKey,
222 $messageParam,
223 null,
224 $groupMessageErrorTag
225 );
226 }
227
228 $this->cache->set( ...$entriesToSave );
229
230 $groupErrorKey = $this->getGroupErrorKey( $groupId );
231
232 // Check if the group already has errors
233 $groupInfo = $this->cache->get( $groupErrorKey );
234 if ( $groupInfo ) {
235 return;
236 }
237
238 // Group did not have an error previously, add it now. When adding,
239 // remove the remaining messages from the GroupSynchronizationResponse to
240 // avoid the value in the cache becoming too big. The remaining messages
241 // are stored as separate items in the cache.
242 $trimmedGroupSyncResponse = new GroupSynchronizationResponse(
243 $groupId,
244 [],
245 $response->hasTimedOut()
246 );
247
248 $entriesToSave[] = new PersistentCacheEntry(
249 $groupErrorKey,
250 $trimmedGroupSyncResponse,
251 null,
252 self::GROUP_ERROR_TAG
253 );
254
255 $this->cache->set( ...$entriesToSave );
256 }
257
262 public function getGroupsWithErrors(): array {
263 $groupsInSyncEntries = $this->cache->getByTag( self::GROUP_ERROR_TAG );
264 $groupIds = [];
265 foreach ( $groupsInSyncEntries as $entry ) {
266 $groupResponse = $entry->value();
267 if ( $groupResponse instanceof GroupSynchronizationResponse ) {
268 $groupIds[] = $groupResponse->getGroupId();
269 } else {
270 // Should not happen, but handle primarily to keep phan happy.
271 throw $this->invalidArgument( $groupResponse, GroupSynchronizationResponse::class );
272 }
273 }
274
275 return $groupIds;
276 }
277
279 public function getGroupErrorInfo( string $groupId ): GroupSynchronizationResponse {
280 $groupMessageErrorTag = $this->getGroupMessageErrorTag( $groupId );
281 $groupMessageEntries = $this->cache->getByTag( $groupMessageErrorTag );
282
283 $groupErrorKey = $this->getGroupErrorKey( $groupId );
284 $groupResponseEntry = $this->cache->get( $groupErrorKey );
285 $groupResponse = $groupResponseEntry[0] ? $groupResponseEntry[0]->value() : null;
286 if ( $groupResponse ) {
287 if ( !$groupResponse instanceof GroupSynchronizationResponse ) {
288 // Should not happen, but handle primarily to keep phan happy.
289 throw $this->invalidArgument( $groupResponse, GroupSynchronizationResponse::class );
290 }
291 } else {
292 throw new LogicException( 'Requested to fetch errors for a group that has no errors.' );
293 }
294
295 $messageParams = [];
296 foreach ( $groupMessageEntries as $messageEntries ) {
297 $messageParam = $messageEntries->value();
298 if ( $messageParam instanceof MessageUpdateParameter ) {
299 $messageParams[] = $messageParam;
300 } else {
301 // Should not happen, but handle primarily to keep phan happy.
302 throw $this->invalidArgument( $messageParam, MessageUpdateParameter::class );
303 }
304 }
305
306 return new GroupSynchronizationResponse(
307 $groupId,
308 $messageParams,
309 $groupResponse->hasTimedOut()
310 );
311 }
312
314 public function markGroupAsResolved( string $groupId ): GroupSynchronizationResponse {
315 $groupSyncResponse = $this->getGroupErrorInfo( $groupId );
316 $errorMessages = $groupSyncResponse->getRemainingMessages();
317
318 $errorMessageKeys = [];
319 foreach ( $errorMessages as $message ) {
320 $errorMessageKeys[] = $this->getMessageErrorKey( $groupId, $message->getPageName() )[0];
321 }
322
323 $this->cache->delete( ...$errorMessageKeys );
324 return $this->syncGroupErrors( $groupId );
325 }
326
328 public function markMessageAsResolved( string $groupId, string $messagePageName ): void {
329 $messageErrorKey = $this->getMessageErrorKey( $groupId, $messagePageName )[0];
330 $messageInCache = $this->cache->get( $messageErrorKey );
331 if ( !$messageInCache ) {
332 throw new InvalidArgumentException(
333 'Message does not appear to have synchronization errors'
334 );
335 }
336
337 $this->cache->delete( $messageErrorKey );
338 }
339
341 public function groupHasErrors( string $groupId ): bool {
342 $groupErrorKey = $this->getGroupErrorKey( $groupId );
343 return $this->cache->has( $groupErrorKey );
344 }
345
347 public function syncGroupErrors( string $groupId ): GroupSynchronizationResponse {
348 $groupSyncResponse = $this->getGroupErrorInfo( $groupId );
349 if ( $groupSyncResponse->getRemainingMessages() ) {
350 return $groupSyncResponse;
351 }
352
353 // No remaining messages left, remove group from errors list.
354 $groupErrorKey = $this->getGroupErrorKey( $groupId );
355 $this->cache->delete( $groupErrorKey );
356
357 return $groupSyncResponse;
358 }
359
364 public function getGroupsInReview(): array {
365 $groupsInReviewEntries = $this->cache->getByTag( self::GROUP_IN_REVIEW_TAG );
366 $groups = [];
367 foreach ( $groupsInReviewEntries as $entry ) {
368 $groups[] = $entry->value();
369 }
370
371 return $groups;
372 }
373
374 public function markGroupAsInReview( string $groupId ): void {
375 $groupReviewKey = $this->getGroupReviewKey( $groupId );
376 $this->cache->set(
377 new PersistentCacheEntry(
378 $groupReviewKey,
379 $groupId,
380 null,
381 self::GROUP_IN_REVIEW_TAG
382 )
383 );
384 $this->logger->debug( 'Group {groupId} marked for review', [ 'groupId' => $groupId ] );
385 }
386
387 public function markGroupAsReviewed( string $groupId ): void {
388 $groupReviewKey = $this->getGroupReviewKey( $groupId );
389 $this->cache->delete( $groupReviewKey );
390 $this->logger->debug( 'Group {groupId} removed from review', [ 'groupId' => $groupId ] );
391 }
392
393 public function isGroupInReview( string $groupId ): bool {
394 return $this->cache->has( $this->getGroupReviewKey( $groupId ) );
395 }
396
397 public function extendGroupExpiryTime( string $groupId ): void {
398 $groupKey = $this->getGroupKey( $groupId );
399 $groupEntry = $this->cache->get( $groupKey );
400
401 if ( $groupEntry === [] ) {
402 // Group is currently not being processed.
403 throw new LogicException(
404 'Requested extension of expiry time for a group that is not being processed. ' .
405 'Check if group is being processed by calling isGroupBeingProcessed() first'
406 );
407 }
408
409 if ( $groupEntry[0]->hasExpired() ) {
410 throw new InvalidArgumentException(
411 'Cannot extend expiry time for a group that has already expired.'
412 );
413 }
414
415 $newExpiryTime = $this->getExpireTime( $this->incrementalTimeoutSeconds );
416
417 // We start with the initial timeout minutes, we only change the timeout if the group
418 // is actually about to expire.
419 if ( $newExpiryTime < $groupEntry[0]->exptime() ) {
420 return;
421 }
422
423 $this->cache->setExpiry( $groupKey, $newExpiryTime );
424 }
425
427 public function getGroupExpiryTime( string $groupId ): int {
428 $groupKey = $this->getGroupKey( $groupId );
429 $groupEntry = $this->cache->get( $groupKey );
430 if ( $groupEntry === [] ) {
431 throw new InvalidArgumentException( "$groupId currently not in processing!" );
432 }
433
434 return $groupEntry[0]->exptime();
435 }
436
437 private function hasGroupTimedOut( int $syncExpTime ): bool {
438 return ( new DateTime() )->getTimestamp() > $syncExpTime;
439 }
440
441 private function getExpireTime( int $timeoutSeconds ): int {
442 $currentTime = ( new DateTime() )->getTimestamp();
443 return ( new DateTime() )
444 ->setTimestamp( $currentTime + $timeoutSeconds )
445 ->getTimestamp();
446 }
447
448 private function invalidArgument( $value, string $expectedType ): RuntimeException {
449 $valueType = $value ? get_class( $value ) : gettype( $value );
450 return new RuntimeException( "Expected $expectedType, got $valueType" );
451 }
452
453 // Cache keys / tag related functions start here.
454
455 private function getGroupTag( string $groupId ): string {
456 return 'gsc_' . $groupId;
457 }
458
459 private function getGroupKey( string $groupId ): string {
460 $hash = substr( hash( 'sha256', $groupId ), 0, 40 );
461 return substr( "{$hash}_$groupId", 0, 255 );
462 }
463
465 private function getMessageKeys( string $groupId, string ...$messages ): array {
466 $messageKeys = [];
467 foreach ( $messages as $message ) {
468 $key = $groupId . '_' . $message;
469 $hash = substr( hash( 'sha256', $key ), 0, 40 );
470 $finalKey = substr( $hash . '_' . $key, 0, 255 );
471 $messageKeys[] = $finalKey;
472 }
473
474 return $messageKeys;
475 }
476
477 private function getGroupErrorKey( string $groupId ): string {
478 $hash = substr( hash( 'sha256', $groupId ), 0, 40 );
479 return substr( "{$hash}_gsc_error_$groupId", 0, 255 );
480 }
481
483 private function getMessageErrorKey( string $groupId, string ...$messages ): array {
484 $messageKeys = [];
485 foreach ( $messages as $message ) {
486 $key = $groupId . '_' . $message;
487 $hash = substr( hash( 'sha256', $key ), 0, 40 );
488 $finalKey = substr( $hash . '_gsc_error_' . $key, 0, 255 );
489 $messageKeys[] = $finalKey;
490 }
491
492 return $messageKeys;
493 }
494
495 private function getGroupMessageErrorTag( string $groupId ): string {
496 return "gsc_%error%_$groupId";
497 }
498
499 private function getGroupReviewKey( string $groupId ): string {
500 $hash = substr( hash( 'sha256', $groupId ), 0, 40 );
501 return substr( "{$hash}_gsc_%review%_$groupId", 0, 255 );
502 }
503}
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager( $services->getTitleFactory());}, '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( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), 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: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->getDBLoadBalancer());}, '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->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( 'Translate.MessageGroupSubscription'), 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->getDBLoadBalancerFactory());}, '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( 'Translate'), $services->getMainObjectStash(), $services->getDBLoadBalancerFactory(), $services->get( 'Translate:MessageGroupSubscription'), 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->getDBLoadBalancer(), $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->getDBLoadBalancer());}, '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->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(), $services->getNamespaceInfo(), $services->getTitleFactory());}, '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->getDBLoadBalancerFactory(), $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:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getDBLoadBalancer(), $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'));}, '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->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getDBLoadBalancerFactory(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getDBLoadBalancer(), $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 { $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
Represents a single result from the persistent cache.
removeMessages(string $groupId, string ... $messageKeys)
Remove messages from the cache.
isGroupBeingProcessed(string $groupId)
Check if the group is in synchronization.
addMessages(string $groupId, MessageUpdateParameter ... $messageParams)
Add messages for a group to the cache.
isMessageBeingProcessed(string $groupId, string $messageKey)
Check if a message is being processed.
getGroupErrorInfo(string $groupId)
Fetch information about a particular group that has errors including messages that failed.
markMessageAsResolved(string $groupId, string $messagePageName)
Marks errors for a message as resolved.
markGroupForSync(string $groupId)
Start synchronization process for a group and starts the expiry time.
syncGroupErrors(string $groupId)
Checks if group has unresolved error messages.
markGroupAsResolved(string $groupId)
Marks all messages in a group and the group itself as resolved.
getSynchronizationStatus(string $groupId)
Get the current synchronization status of the group.
Class encapsulating the response returned by the GroupSynchronizationCache when requested for an upda...
Defines what method should be provided by a class implementing a persistent cache.
Finds external changes for file based message groups.