21 private TitleParser $titleParser;
22 private FormatterFactory $formatterFactory;
24 public function __construct() {
25 parent::__construct();
26 $this->addDescription(
'Review and move translatable bundles including their subpages' );
30 ' Current name of the page representing a translatable bundle',
36 'New translatable bundle name',
42 'User performing the move',
48 'Reason for moving the translatable bundle',
55 'Skip leaving a redirect behind for translatable bundle, subpages and related talk pages',
60 'Skip moving subpages under the current page'
65 'Skip moving talk pages under pages being moved'
68 $this->requireExtension(
'Translate' );
73 $this->bundleMover = Services::getInstance()->getTranslatableBundleMover();
75 $mwService = MediaWikiServices::getInstance();
76 $this->titleParser = $mwService->getTitleParser();
77 $this->formatterFactory = $mwService->getFormatterFactory();
79 $currentBundleName = $this->getArg( 0 );
80 $newBundleName = $this->getArg( 1 );
81 $username = $this->getArg( 2 );
82 $reason = $this->getOption(
'reason',
'' );
83 $leaveRedirect = !$this->hasOption(
'skip-redirect' );
84 $moveSubpages = !$this->hasOption(
'skip-subpages' );
85 $moveTalkpages = !$this->hasOption(
'skip-talkpages' );
87 $userFactory = $mwService->getUserFactory();
88 $user = $userFactory->newFromName( $username );
90 if ( $user ===
null || !$user->isRegistered() ) {
91 $this->fatalError(
"User $username does not exist." );
94 $outputMsg =
"Check if '$currentBundleName' can be moved to '$newBundleName'";
95 $subpageMsg =
'excluding subpages';
96 if ( $moveSubpages ) {
97 $subpageMsg =
'including subpages';
100 $talkpageMsg =
'excluding talkpages';
101 if ( $moveTalkpages ) {
102 $talkpageMsg =
'including talkpages';
105 $leaveRedirectMsg =
'without leaving redirects';
106 if ( $leaveRedirect ) {
107 $leaveRedirectMsg =
'leaving redirects';
110 $this->output(
"$outputMsg ($subpageMsg; $talkpageMsg; $leaveRedirectMsg)\n" );
113 $currentTitle = $this->getTitleFromInput( $currentBundleName ??
'' );
114 $newTitle = $this->getTitleFromInput( $newBundleName ??
'' );
115 }
catch ( MalformedTitleException $e ) {
116 $this->error(
'Invalid title: current-bundle or new-bundle' );
117 $this->fatalError( $e->getMessageObject()->text() );
122 $this->bundleMover->disablePageMoveLimit();
124 $pageCollection = $this->bundleMover->getPageMoveCollection(
134 $fatalErrorMsg = $this->parseErrorMessage( $e->getBlockers() );
135 $this->fatalError( $fatalErrorMsg );
138 $this->displayPagesToMove( $pageCollection, $leaveRedirect );
140 $haveConfirmation = $this->getConfirmation();
141 if ( !$haveConfirmation ) {
142 $this->output(
"Exiting...\n" );
146 $this->output(
"Starting page move\n" );
147 $pagesToMove = $pageCollection->getListOfPages();
148 $pagesToRedirect = $pageCollection->getListOfPagesToRedirect();
150 $this->bundleMover->moveSynchronously(
157 Closure::fromCallable( [ $this,
'progressCallback' ] )
160 $this->logSeparator();
161 $this->output(
"Finished moving '$currentBundleName' to '$newBundleName' $subpageMsg\n" );
164 private function parseErrorMessage( SplObjectStorage $errors ): string {
165 $errorMsg = wfMessage(
'pt-movepage-blockers', count( $errors ) )->text() .
"\n";
166 foreach ( $errors as $title ) {
167 $titleText = $title->getPrefixedText();
168 $errorMsg .=
"$titleText\n";
169 $errorMsg .= $errors[ $title ]->getWikiText(
false,
'pt-movepage-error-placeholder',
'en' );
176 private function progressCallback( Title $previous, Title $new, Status $status,
int $total,
int $processed ): void {
177 $previousTitleText = $previous->getPrefixedText();
178 $newTitleText = $new->getPrefixedText();
179 $paddedProcessed = str_pad( (
string)$processed, strlen( (
string)$total ),
' ', STR_PAD_LEFT );
180 $progressCounter =
"($paddedProcessed/$total)";
182 if ( $status->isOK() ) {
183 $this->output(
"$progressCounter $previousTitleText --> $newTitleText\n" );
185 $reason = $this->formatterFactory
186 ->getStatusFormatter( RequestContext::getMain() )
187 ->getWikiText( $status );
188 $this->output(
"$progressCounter Failed to move $previousTitleText to $newTitleText\n" );
189 $this->output(
"\tReason: $reason\n" );
193 private function displayPagesToMove( PageMoveCollection $pageCollection,
bool $leaveRedirect ): void {
194 $infoMessage =
"\nThe following pages will be moved:\n";
201 'pt-movepage-list-source' => [ $pageCollection->getTranslatablePage() ],
202 'pt-movepage-list-translation' => $pageCollection->getTranslationPagesPair(),
203 'pt-movepage-list-section' => $pageCollection->getUnitPagesPair()
206 $subpages = $pageCollection->getSubpagesPair();
208 $pagesToMove[
'pt-movepage-list-other'] = $subpages;
211 foreach ( $pagesToMove as $type => $pages ) {
213 $infoMessage .= $this->getSectionHeader( $type, $pages, $leaveRedirect );
214 if ( !count( $pages ) ) {
218 foreach ( $pages as $pagePairs ) {
221 if ( $type ===
'pt-movepage-list-other' ) {
225 $old = $pagePairs->getOldTitle();
226 $new = $pagePairs->getNewTitle();
229 $line =
'* ' . $old->getPrefixedText() .
' → ' . $new->getPrefixedText();
230 if ( $pagePairs->hasTalkpage() ) {
233 $line .=
' ' . $this->message(
'pt-movepage-talkpage-exists' )->text();
240 $infoMessage .= implode(
"\n", $lines ) .
"\n";
243 $translatableSubpages = $pageCollection->getTranslatableSubpages();
244 $infoMessage .= $this->getSectionHeader(
245 'pt-movepage-list-translatable', $translatableSubpages, $leaveRedirect
248 if ( $translatableSubpages ) {
250 $infoMessage .= $this->message(
'pt-movepage-list-translatable-note' )->text() .
"\n";
251 foreach ( $translatableSubpages as $page ) {
252 $lines[] =
'* ' . $page->getPrefixedText();
255 $infoMessage .= implode(
"\n", $lines ) .
"\n";
258 $nonMovableSubpages = $pageCollection->getNonMovableSubpages();
259 if ( $nonMovableSubpages ) {
261 $infoMessage .= $this->getSectionHeader(
262 'pt-movepage-list-nonmovable', $nonMovableSubpages, $leaveRedirect
264 $infoMessage .= $this->message(
'pt-movepage-list-nonmovable-note' )
265 ->numParams( count( $nonMovableSubpages ) )->text() .
"\n";
266 foreach ( $nonMovableSubpages as $page => $status ) {
267 $invalidityReason = $this->formatterFactory
268 ->getStatusFormatter( RequestContext::getMain() )
269 ->getWikiText( $status );
270 $lines[] =
'* ' . $page .
' (' . str_replace(
"\n",
" ", $invalidityReason ) .
')';
273 $infoMessage .= implode(
"\n", $lines ) .
"\n";
276 $this->output( $infoMessage );
278 $this->logSeparator();
280 $this->message(
'pt-movepage-list-count' )
281 ->numParams( $count, $subpagesCount, $talkpagesCount )
284 $this->logSeparator();
285 $this->output(
"\n" );
288 private function getSectionHeader(
string $type, array $pages,
bool $leaveRedirect ): string {
289 $infoMessage = $this->getSeparator();
290 $pageCount = count( $pages );
291 $shouldRedirect = TranslatableBundleMover::shouldLeaveRedirect( $type, $leaveRedirect );
295 $infoMessage .= $this->message( $type )->numParams( $pageCount )->text() .
' ';
297 if ( $shouldRedirect ) {
298 $infoMessage .=
'(leave redirect)';
301 $infoMessage .=
"\n\n";
304 $infoMessage .= $this->message(
'pt-movepage-list-no-pages' )->text() .
"\n";
310 private function getConfirmation(): bool {
311 $line = self::readconsole(
'Type "MOVE" to begin the move operation: ' );
312 return strtolower( $line ) ===
'move';
315 private function getSeparator(
int $width = 15 ): string {
316 return str_repeat(
'-', $width ) .
"\n";
319 private function logSeparator(
int $width = 15 ): void {
320 $this->output( $this->getSeparator( $width ) );
323 private function message(
string $key ): Message {
324 return ( new Message( $key ) )->inLanguage(
'en' );
327 private function getTitleFromInput(
string $pageName ): Title {
328 $titleValue = $this->titleParser->parseTitle( $pageName );
329 return Title::newFromLinkTarget( $titleValue );
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):MessageGroupSubscriptionHookHandler { return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory());}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array