Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslatableBundleMover.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\PageTranslation;
5
6use JobQueueGroup;
7use LogicException;
8use MediaWiki\Cache\LinkBatchFactory;
14use MediaWiki\Page\MovePageFactory;
15use MediaWiki\Status\Status;
16use MediaWiki\Title\Title;
17use MediaWiki\User\User;
18use Message;
19use ObjectCache;
20use SplObjectStorage;
21use Wikimedia\Rdbms\IConnectionProvider;
22
30 private const LOCK_TIMEOUT = 3600 * 2;
31 private const FETCH_TRANSLATABLE_SUBPAGES = true;
32 private MovePageFactory $movePageFactory;
33 private ?int $pageMoveLimit;
34 private JobQueueGroup $jobQueue;
35 private LinkBatchFactory $linkBatchFactory;
36 private TranslatableBundleFactory $bundleFactory;
37 private SubpageListBuilder $subpageBuilder;
38 private IConnectionProvider $connectionProvider;
39 private bool $pageMoveLimitEnabled = true;
40
41 private const REDIRECTABLE_PAGE_TYPES = [
42 'pt-movepage-list-source' => true,
43 'pt-movepage-list-section' => false,
44 'pt-movepage-list-nonmovable' => false,
45 'pt-movepage-list-translatable' => false,
46 'pt-movepage-list-translation' => false,
47 'pt-movepage-list-other' => true
48 ];
49
50 public function __construct(
51 MovePageFactory $movePageFactory,
52 JobQueueGroup $jobQueue,
53 LinkBatchFactory $linkBatchFactory,
54 TranslatableBundleFactory $bundleFactory,
55 SubpageListBuilder $subpageBuilder,
56 IConnectionProvider $connectionProvider,
57 ?int $pageMoveLimit
58 ) {
59 $this->movePageFactory = $movePageFactory;
60 $this->jobQueue = $jobQueue;
61 $this->pageMoveLimit = $pageMoveLimit;
62 $this->linkBatchFactory = $linkBatchFactory;
63 $this->bundleFactory = $bundleFactory;
64 $this->subpageBuilder = $subpageBuilder;
65 $this->connectionProvider = $connectionProvider;
66 }
67
68 public function getPageMoveCollection(
69 Title $source,
70 ?Title $target,
71 User $user,
72 string $reason,
73 bool $moveSubPages,
74 bool $moveTalkPages,
75 bool $leaveRedirect
77 $blockers = new SplObjectStorage();
78
79 if ( !$target ) {
80 $blockers[$source] = Status::newFatal( 'pt-movepage-block-base-invalid' );
81 throw new ImpossiblePageMove( $blockers );
82 }
83
84 if ( $target->inNamespaces( NS_MEDIAWIKI, NS_TRANSLATIONS ) ) {
85 $blockers[$source] = Status::newFatal( 'immobile-target-namespace', $target->getNsText() );
86 throw new ImpossiblePageMove( $blockers );
87 }
88
89 $movePage = $this->movePageFactory->newMovePage( $source, $target );
90 $status = $movePage->isValidMove();
91 $status->merge( $movePage->probablyCanMove( $user, $reason ) );
92 if ( !$status->isOK() ) {
93 $blockers[$source] = $status;
94 }
95
96 // Don't spam the same errors for all pages if base page fails
97 if ( count( $blockers ) ) {
98 throw new ImpossiblePageMove( $blockers );
99 }
100
101 $pageCollection = $this->getPagesToMove(
102 $source, $target, $moveSubPages, self::FETCH_TRANSLATABLE_SUBPAGES, $moveTalkPages, $leaveRedirect
103 );
104
105 // Collect all the old and new titles for checks
106 $titles = [
107 'tp' => $pageCollection->getTranslationPagesPair(),
108 'subpage' => $pageCollection->getSubpagesPair(),
109 'section' => $pageCollection->getUnitPagesPair()
110 ];
111
112 // Check that all new titles are valid and count them. Add 1 for source page.
113 $moveCount = 1;
114 $lb = $this->linkBatchFactory->newLinkBatch();
115 foreach ( $titles as $type => $list ) {
116 $moveCount += count( $list );
117 // Give grep a chance to find the usages:
118 // pt-movepage-block-tp-invalid, pt-movepage-block-section-invalid,
119 // pt-movepage-block-subpage-invalid
120 foreach ( $list as $pair ) {
121 $old = $pair->getOldTitle();
122 $new = $pair->getNewTitle();
123
124 if ( $new === null ) {
125 $blockers[$old] = $this->getRenameMoveBlocker( $old, $type, $pair->getRenameErrorCode() );
126 continue;
127 }
128 $lb->addObj( $old );
129 $lb->addObj( $new );
130 }
131 }
132
133 if ( $this->pageMoveLimitEnabled ) {
134 if ( $this->pageMoveLimit !== null && $moveCount > $this->pageMoveLimit ) {
135 $blockers[$source] = Status::newFatal(
136 'pt-movepage-page-count-limit',
137 Message::numParam( $this->pageMoveLimit )
138 );
139 }
140 }
141
142 // Stop further validation if there are blockers already.
143 if ( count( $blockers ) ) {
144 throw new ImpossiblePageMove( $blockers );
145 }
146
147 // Check that there are no move blockers
148 $lb->setCaller( __METHOD__ )->execute();
149 foreach ( $titles as $type => $list ) {
150 foreach ( $list as $pair ) {
151 $old = $pair->getOldTitle();
152 $new = $pair->getNewTitle();
153
154 /* This method has terrible performance:
155 * - 2 queries by core
156 * - 3 queries by lqt
157 * - and no obvious way to preload the data! */
158 $movePage = $this->movePageFactory->newMovePage( $old, $new );
159 $status = $movePage->isValidMove();
160 // Do not check for permissions here, as these pages are not editable/movable
161 // in regular use
162 if ( !$status->isOK() ) {
163 if ( $type === 'subpage' ) {
164 $pageCollection->addNonMovableSubpage( $old, $status );
165 } else {
166 $blockers[$old] = $status;
167 }
168 }
169
170 /* Because of the poor performance, check only one of the possibly thousands
171 * of section pages and assume rest are fine. This assumes section pages are
172 * listed last in the array. */
173 if ( $type === 'section' ) {
174 break;
175 }
176 }
177 }
178
179 if ( count( $blockers ) ) {
180 throw new ImpossiblePageMove( $blockers );
181 }
182
183 return $pageCollection;
184 }
185
186 public function moveAsynchronously(
187 Title $source,
188 Title $target,
189 bool $moveSubPages,
190 User $user,
191 string $moveReason,
192 bool $moveTalkPages,
193 bool $leaveRedirect
194 ): void {
195 $pageCollection = $this->getPagesToMove(
196 $source, $target, $moveSubPages, !self::FETCH_TRANSLATABLE_SUBPAGES, $moveTalkPages, $leaveRedirect
197 );
198 $pagesToMove = $pageCollection->getListOfPages();
199 $pagesToLeaveRedirect = $pageCollection->getListOfPagesToRedirect();
200
202 $source,
203 $target,
204 $pagesToMove,
205 $pagesToLeaveRedirect,
206 $moveReason,
207 $user,
208 );
209
210 $this->lock( array_keys( $pagesToMove ) );
211 $this->lock( array_values( $pagesToMove ) );
212
213 $this->jobQueue->push( $job );
214 }
215
225 public function moveSynchronously(
226 Title $source,
227 Title $target,
228 array $pagesToMove,
229 array $pagesToRedirect,
230 User $performer,
231 string $moveReason,
232 ?callable $progressCallback = null
233 ): void {
234 $sourceBundle = $this->bundleFactory->getValidBundle( $source );
235
236 $this->move( $sourceBundle, $performer, $pagesToMove, $pagesToRedirect, $moveReason, $progressCallback );
237
238 $this->bundleFactory->getStore( $sourceBundle )->move( $source, $target );
239
240 $this->bundleFactory->getPageMoveLogger( $sourceBundle )
241 ->logSuccess( $performer, $target, $moveReason );
242 }
243
244 public function disablePageMoveLimit(): void {
245 $this->pageMoveLimitEnabled = false;
246 }
247
248 public function enablePageMoveLimit(): void {
249 $this->pageMoveLimitEnabled = true;
250 }
251
252 public static function shouldLeaveRedirect( string $pageType, bool $leaveRedirect ): bool {
253 return self::REDIRECTABLE_PAGE_TYPES[ $pageType ] && $leaveRedirect;
254 }
255
256 private function getPagesToMove(
257 Title $source,
258 Title $target,
259 bool $moveSubPages,
260 bool $fetchTranslatableSubpages,
261 bool $moveTalkPages,
262 bool $leaveRedirect
263 ): PageMoveCollection {
264 $sourceBundle = $this->bundleFactory->getValidBundle( $source );
265
266 $classifiedSubpages = $this->subpageBuilder->getSubpagesPerType( $sourceBundle, $moveTalkPages );
267
268 $talkPages = $moveTalkPages ? $classifiedSubpages['talkPages'] : [];
269 $subpages = $moveSubPages ? $classifiedSubpages['normalSubpages'] : [];
270 $relatedTranslatablePageList = [];
271 if ( $fetchTranslatableSubpages ) {
272 $relatedTranslatablePageList = array_merge(
273 $classifiedSubpages['translatableSubpages'],
274 $classifiedSubpages['translatableTalkPages']
275 );
276 }
277
278 $pageTitleRenamer = new PageTitleRenamer( $source, $target );
279 $createOps = static function ( array $pages, string $pageType )
280 use ( $pageTitleRenamer, $talkPages, $leaveRedirect ) {
281 $leaveRedirect = self::shouldLeaveRedirect( $pageType, $leaveRedirect );
282 $ops = [];
283 foreach ( $pages as $from ) {
284 $to = $pageTitleRenamer->getNewTitle( $from );
285 $op = new PageMoveOperation( $from, $to );
286 $op->setLeaveRedirect( $leaveRedirect );
287
288 $talkPage = $talkPages[ $from->getPrefixedDBkey() ] ?? null;
289 if ( $talkPage ) {
290 $op->setTalkpage( $talkPage, $pageTitleRenamer->getNewTitle( $talkPage ) );
291 }
292 $ops[] = $op;
293 }
294
295 return $ops;
296 };
297
298 return new PageMoveCollection(
299 $createOps( [ $source ], 'pt-movepage-list-source' )[0],
300 $createOps( $classifiedSubpages['translationPages'], 'pt-movepage-list-translation' ),
301 $createOps( $classifiedSubpages['translationUnitPages'], 'pt-movepage-list-section' ),
302 $createOps( $subpages, 'pt-movepage-list-other' ),
303 $relatedTranslatablePageList
304 );
305 }
306
308 private function lock( array $titles ): void {
309 $cache = ObjectCache::getInstance( CACHE_ANYTHING );
310 $data = [];
311 foreach ( $titles as $title ) {
312 $data[$cache->makeKey( 'pt-lock', sha1( $title ) )] = 'locked';
313 }
314
315 // Do not lock pages indefinitely during translatable page moves since
316 // they can fail. Add a timeout so that the locks expire by themselves.
317 // Timeout value has been chosen by a gut feeling
318 $cache->setMulti( $data, self::LOCK_TIMEOUT );
319 }
320
322 private function unlock( array $titles ): void {
323 $cache = ObjectCache::getInstance( CACHE_ANYTHING );
324 foreach ( $titles as $title ) {
325 $cache->delete( $cache->makeKey( 'pt-lock', sha1( $title ) ) );
326 }
327 }
328
337 private function move(
338 TranslatableBundle $sourceBundle,
339 User $performer,
340 array $pagesToMove,
341 array $pagesToRedirect,
342 string $reason,
343 ?callable $progressCallback = null
344 ): void {
345 $fuzzyBot = FuzzyBot::getUser();
346
347 Hooks::$allowTargetEdit = true;
348
349 $processed = 0;
350
351 $this->connectionProvider->getPrimaryDatabase()->startAtomic( __METHOD__ );
352 foreach ( $pagesToMove as $source => $target ) {
353 $sourceTitle = Title::newFromText( $source );
354 $targetTitle = Title::newFromText( $target );
355
356 if ( $source === $sourceBundle->getTitle()->getPrefixedText() ) {
357 $user = $performer;
358 $moveSummary = $reason;
359 } else {
360 $user = $fuzzyBot;
361 $moveSummary = wfMessage(
362 'pt-movepage-logreason', $sourceBundle->getTitle()->getPrefixedText()
363 )->text();
364 }
365
366 $mover = $this->movePageFactory->newMovePage( $sourceTitle, $targetTitle );
367 $status = $mover->move( $user, $moveSummary, $pagesToRedirect[$source] ?? false );
368 $processed++;
369
370 if ( $progressCallback ) {
371 $progressCallback(
372 $sourceTitle,
373 $targetTitle,
374 $status,
375 count( $pagesToMove ),
376 $processed
377 );
378 }
379
380 if ( !$status->isOK() ) {
381 $this->bundleFactory->getPageMoveLogger( $sourceBundle )
382 ->logError( $performer, $sourceTitle, $targetTitle, $status );
383 }
384
385 $this->unlock( [ $source, $target ] );
386 }
387 $this->connectionProvider->getPrimaryDatabase()->endAtomic( __METHOD__ );
388
389 Hooks::$allowTargetEdit = false;
390 }
391
392 private function getRenameMoveBlocker( Title $old, string $pageType, int $renameError ): Status {
393 if ( $renameError === PageTitleRenamer::NO_ERROR ) {
394 throw new LogicException(
395 'Trying to fetch MoveBlocker when there was no error during rename. Title: ' .
396 $old->getPrefixedText() . ', page type: ' . $pageType
397 );
398 }
399
400 if ( $renameError === PageTitleRenamer::UNKNOWN_PAGE ) {
401 $status = Status::newFatal( 'pt-movepage-block-unknown-page', $old->getPrefixedText() );
402 } elseif ( $renameError === PageTitleRenamer::NS_TALK_UNSUPPORTED ) {
403 $status = Status::newFatal( 'pt-movepage-block-ns-talk-unsupported', $old->getPrefixedText() );
404 } elseif ( $renameError === PageTitleRenamer::RENAME_FAILED ) {
405 $status = Status::newFatal( 'pt-movepage-block-rename-failed', $old->getPrefixedText() );
406 } else {
407 return Status::newFatal( "pt-movepage-block-$pageType-invalid", $old->getPrefixedText() );
408 }
409
410 return $status;
411 }
412}
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
static newJob(Title $source, Title $target, array $moves, array $redirects, string $reason, User $performer)
Generates list of subpages for the translatable bundle that can be moved or deleted.
Create instances of various classes based on the type of TranslatableBundle.
Translatable bundle represents a message group where its translatable content is defined on a wiki pa...
Exception thrown when a translatable page move is not possible.
Collection of pages potentially affected by a page move operation.
Contains the core logic to validate and move translatable bundles.
moveSynchronously(Title $source, Title $target, array $pagesToMove, array $pagesToRedirect, User $performer, string $moveReason, ?callable $progressCallback=null)
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15