31 private const LOCK_TIMEOUT = 3600 * 2;
32 private const FETCH_TRANSLATABLE_SUBPAGES =
true;
33 private MovePageFactory $movePageFactory;
34 private ?
int $pageMoveLimit;
35 private JobQueueGroup $jobQueue;
36 private LinkBatchFactory $linkBatchFactory;
39 private IConnectionProvider $dbProvider;
40 private BagOStuff $cache;
41 private bool $pageMoveLimitEnabled =
true;
43 private const REDIRECTABLE_PAGE_TYPES = [
44 'pt-movepage-list-source' =>
true,
45 'pt-movepage-list-section' =>
false,
46 'pt-movepage-list-nonmovable' =>
false,
47 'pt-movepage-list-translatable' =>
false,
48 'pt-movepage-list-translation' =>
false,
49 'pt-movepage-list-other' => true
52 public function __construct(
53 MovePageFactory $movePageFactory,
54 JobQueueGroup $jobQueue,
55 LinkBatchFactory $linkBatchFactory,
58 IConnectionProvider $dbProvider,
59 ObjectCacheFactory $objectCacheFactory,
62 $this->movePageFactory = $movePageFactory;
63 $this->jobQueue = $jobQueue;
64 $this->pageMoveLimit = $pageMoveLimit;
65 $this->linkBatchFactory = $linkBatchFactory;
66 $this->bundleFactory = $bundleFactory;
67 $this->subpageBuilder = $subpageBuilder;
68 $this->dbProvider = $dbProvider;
69 $this->cache = $objectCacheFactory->getInstance( CACHE_ANYTHING );
82 $blockers = new SplObjectStorage();
85 $blockers[$source] = Status::newFatal(
'pt-movepage-block-base-invalid' );
89 if ( $target->inNamespaces( NS_MEDIAWIKI, NS_TRANSLATIONS ) ) {
90 $blockers[$source] = Status::newFatal(
'immobile-target-namespace', $target->getNsText() );
95 Hooks::$allowTargetEdit =
true;
96 $movePage = $this->movePageFactory->newMovePage( $source, $target );
97 $status = $movePage->isValidMove();
98 $status->merge( $movePage->probablyCanMove( $user, $reason ) );
99 Hooks::$allowTargetEdit =
false;
100 if ( !$status->isOK() ) {
101 $blockers[$source] = $status;
105 if ( count( $blockers ) ) {
106 throw new ImpossiblePageMove( $blockers );
109 $pageCollection = $this->getPagesToMove(
110 $source, $target, $moveSubPages, self::FETCH_TRANSLATABLE_SUBPAGES, $moveTalkPages, $leaveRedirect
115 'tp' => $pageCollection->getTranslationPagesPair(),
116 'subpage' => $pageCollection->getSubpagesPair(),
117 'section' => $pageCollection->getUnitPagesPair()
122 $lb = $this->linkBatchFactory->newLinkBatch();
123 foreach ( $titles as $type => $list ) {
124 $moveCount += count( $list );
128 foreach ( $list as $pair ) {
129 $old = $pair->getOldTitle();
130 $new = $pair->getNewTitle();
132 if ( $new ===
null ) {
133 $blockers[$old] = $this->getRenameMoveBlocker( $old, $type, $pair->getRenameErrorCode() );
141 if ( $this->pageMoveLimitEnabled ) {
142 if ( $this->pageMoveLimit !==
null && $moveCount > $this->pageMoveLimit ) {
143 $blockers[$source] = Status::newFatal(
144 'pt-movepage-page-count-limit',
145 Message::numParam( $this->pageMoveLimit )
151 if ( count( $blockers ) ) {
152 throw new ImpossiblePageMove( $blockers );
156 $lb->setCaller( __METHOD__ )->execute();
157 foreach ( $titles as $type => $list ) {
158 foreach ( $list as $pair ) {
159 $old = $pair->getOldTitle();
160 $new = $pair->getNewTitle();
166 Hooks::$allowTargetEdit =
true;
167 $movePage = $this->movePageFactory->newMovePage( $old, $new );
168 $status = $movePage->isValidMove();
169 Hooks::$allowTargetEdit =
false;
173 if ( !$status->isOK() ) {
174 $blockers[$old] = $status;
180 if ( $type ===
'section' ) {
186 if ( count( $blockers ) ) {
187 throw new ImpossiblePageMove( $blockers );
190 return $pageCollection;
193 public function moveAsynchronously(
201 array $userSessionInfo
203 $pageCollection = $this->getPagesToMove(
204 $source, $target, $moveSubPages, !self::FETCH_TRANSLATABLE_SUBPAGES, $moveTalkPages, $leaveRedirect
206 $pagesToMove = $pageCollection->getListOfPages();
207 $pagesToLeaveRedirect = $pageCollection->getListOfPagesToRedirect();
213 $pagesToLeaveRedirect,
219 $this->lock( array_keys( $pagesToMove ) );
220 $this->lock( array_values( $pagesToMove ) );
222 $this->jobQueue->push( $job );
238 array $pagesToRedirect,
241 ?callable $progressCallback =
null
243 $this->lock( array_keys( $pagesToMove ) );
244 $this->lock( array_values( $pagesToMove ) );
246 $sourceBundle = $this->bundleFactory->getValidBundle( $source );
248 $this->move( $sourceBundle, $performer, $pagesToMove, $pagesToRedirect, $moveReason, $progressCallback );
250 $this->bundleFactory->getStore( $sourceBundle )->move( $source, $target );
252 $this->bundleFactory->getPageMoveLogger( $sourceBundle )
253 ->logSuccess( $performer, $target, $moveReason );
256 public function disablePageMoveLimit(): void {
257 $this->pageMoveLimitEnabled = false;
260 public function enablePageMoveLimit(): void {
261 $this->pageMoveLimitEnabled = true;
264 public static function shouldLeaveRedirect(
string $pageType,
bool $leaveRedirect ): bool {
265 return self::REDIRECTABLE_PAGE_TYPES[ $pageType ] && $leaveRedirect;
268 private function getPagesToMove(
272 bool $fetchTranslatableSubpages,
275 ): PageMoveCollection {
276 $sourceBundle = $this->bundleFactory->getValidBundle( $source );
278 $classifiedSubpages = $this->subpageBuilder->getSubpagesPerType( $sourceBundle, $moveTalkPages );
280 $talkPages = $moveTalkPages ? $classifiedSubpages[
'talkPages'] : [];
281 $subpages = $moveSubPages ? $classifiedSubpages[
'normalSubpages'] : [];
282 $nonMovableSubpages = [];
284 $relatedTranslatablePageList = [];
285 if ( $fetchTranslatableSubpages ) {
286 $relatedTranslatablePageList = array_merge(
287 $classifiedSubpages[
'translatableSubpages'],
288 $classifiedSubpages[
'translatableTalkPages']
292 $movePageFactory = $this->movePageFactory;
293 $pageTitleRenamer =
new PageTitleRenamer( $source, $target );
294 $createOps =
function ( array $pages,
string $pageType )
295 use ( $pageTitleRenamer, $talkPages, &$nonMovableSubpages, $leaveRedirect, $movePageFactory ) {
296 $leaveRedirect = self::shouldLeaveRedirect( $pageType, $leaveRedirect );
298 foreach ( $pages as $from ) {
299 $to = $pageTitleRenamer->getNewTitle( $from );
300 $op =
new PageMoveOperation( $from, $to );
302 if ( $pageType ===
'pt-movepage-list-other' ) {
306 $movePage = $movePageFactory->newMovePage( $from, $to );
307 $status = $movePage->isValidMove();
310 if ( !$status->isOK() ) {
311 $nonMovableSubpages[ $from->getPrefixedText() ] = $status;
316 $op->setLeaveRedirect( $leaveRedirect );
318 $talkPage = $talkPages[ $from->getPrefixedDBkey() ] ??
null;
320 $op->setTalkpage( $talkPage, $pageTitleRenamer->getNewTitle( $talkPage ) );
328 return new PageMoveCollection(
329 $createOps( [ $source ],
'pt-movepage-list-source' )[0],
330 $createOps( $classifiedSubpages[
'translationPages'],
'pt-movepage-list-translation' ),
331 $createOps( $classifiedSubpages[
'translationUnitPages'],
'pt-movepage-list-section' ),
332 $createOps( $subpages,
'pt-movepage-list-other' ),
334 $relatedTranslatablePageList
339 private function lock( array $titles ): void {
341 foreach ( $titles as $title ) {
342 $data[$this->cache->makeKey(
'pt-lock', sha1( $title ) )] =
'locked';
348 $this->cache->setMulti( $data, self::LOCK_TIMEOUT );
352 private function unlock( array $titles ): void {
353 foreach ( $titles as $title ) {
354 $this->cache->delete( $this->cache->makeKey(
'pt-lock', sha1( $title ) ) );
366 private function move(
367 TranslatableBundle $sourceBundle,
370 array $pagesToRedirect,
372 ?callable $progressCallback =
null
374 $fuzzyBot = FuzzyBot::getUser();
376 Hooks::$allowTargetEdit =
true;
380 $this->dbProvider->getPrimaryDatabase()->startAtomic( __METHOD__ );
381 foreach ( $pagesToMove as $source => $target ) {
382 $sourceTitle = Title::newFromText( $source );
383 $targetTitle = Title::newFromText( $target );
385 if ( $source === $sourceBundle->getTitle()->getPrefixedText() ) {
387 $moveSummary = $reason;
390 $moveSummary = wfMessage(
391 'pt-movepage-logreason', $sourceBundle->getTitle()->getPrefixedText()
395 $mover = $this->movePageFactory->newMovePage( $sourceTitle, $targetTitle );
396 $status = $mover->move( $user, $moveSummary, $pagesToRedirect[$source] ??
false );
399 if ( $progressCallback ) {
404 count( $pagesToMove ),
409 if ( !$status->isOK() ) {
410 $this->bundleFactory->getPageMoveLogger( $sourceBundle )
411 ->logError( $performer, $sourceTitle, $targetTitle, $status );
414 $this->unlock( [ $source, $target ] );
416 $this->dbProvider->getPrimaryDatabase()->endAtomic( __METHOD__ );
418 Hooks::$allowTargetEdit =
false;
421 private function getRenameMoveBlocker( Title $old,
string $pageType,
int $renameError ): Status {
422 if ( $renameError === PageTitleRenamer::NO_ERROR ) {
423 throw new LogicException(
424 'Trying to fetch MoveBlocker when there was no error during rename. Title: ' .
425 $old->getPrefixedText() .
', page type: ' . $pageType
429 if ( $renameError === PageTitleRenamer::UNKNOWN_PAGE ) {
430 $status = Status::newFatal(
'pt-movepage-block-unknown-page', $old->getPrefixedText() );
431 } elseif ( $renameError === PageTitleRenamer::NS_TALK_UNSUPPORTED ) {
432 $status = Status::newFatal(
'pt-movepage-block-ns-talk-unsupported', $old->getPrefixedText() );
433 } elseif ( $renameError === PageTitleRenamer::RENAME_FAILED ) {
434 $status = Status::newFatal(
'pt-movepage-block-rename-failed', $old->getPrefixedText() );
436 return Status::newFatal(
"pt-movepage-block-$pageType-invalid", $old->getPrefixedText() );
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(), $services->getContentLanguageCode() ->toString(), 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 { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} 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(), $services->get( 'Translate:HookRunner'),);}, '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->getConnectionProvider());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);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array