23 public function __construct() {
24 parent::__construct();
25 $this->addDescription(
'Review and move translatable bundles including their subpages' );
29 ' Current name of the page representing a translatable bundle',
35 'New translatable bundle name',
41 'User performing the move',
47 'Reason for moving the translatable bundle',
54 'Skip moving subpages under the current page'
59 'Skip moving talk pages under pages being moved'
62 $this->requireExtension(
'Translate' );
67 $this->bundleMover = Services::getInstance()->getTranslatableBundleMover();
69 $mwService = MediaWikiServices::getInstance();
70 $this->titleParser = $mwService->getTitleParser();
72 $currentBundleName = $this->getArg( 0 );
73 $newBundleName = $this->getArg( 1 );
74 $username = $this->getArg( 2 );
75 $reason = $this->getOption(
'reason',
'' );
76 $moveSubpages = !$this->hasOption(
'skip-subpages' );
77 $moveTalkpages = !$this->hasOption(
'skip-talkpages' );
79 $userFactory = $mwService->getUserFactory();
80 $user = $userFactory->newFromName( $username );
82 if ( $user ===
null || !$user->isRegistered() ) {
83 $this->fatalError(
"User $username does not exist." );
86 $outputMsg =
"Check if '$currentBundleName' can be moved to '$newBundleName'";
87 $subpageMsg =
'excluding subpages';
88 if ( $moveSubpages ) {
89 $subpageMsg =
'including subpages';
92 $talkpageMsg =
'excluding talkpages';
93 if ( $moveTalkpages ) {
94 $talkpageMsg =
'including talkpages';
97 $this->output(
"$outputMsg ($subpageMsg; $talkpageMsg)\n" );
100 $currentTitle = $this->getTitleFromInput( $currentBundleName ??
'' );
101 $newTitle = $this->getTitleFromInput( $newBundleName ??
'' );
102 }
catch ( MalformedTitleException $e ) {
103 $this->error(
'Invalid title: current-bundle or new-bundle' );
104 $this->fatalError( $e->getMessageObject()->text() );
109 $this->bundleMover->disablePageMoveLimit();
111 $pageCollection = $this->bundleMover->getPageMoveCollection(
120 $fatalErrorMsg = $this->parseErrorMessage( $e->getBlockers() );
121 $this->fatalError( $fatalErrorMsg );
124 $this->displayPagesToMove( $pageCollection );
126 $haveConfirmation = $this->getConfirmation();
127 if ( !$haveConfirmation ) {
128 $this->output(
"Exiting...\n" );
132 $this->output(
"Starting page move\n" );
134 $pagesToMove = $pageCollection->getListOfPages();
136 $this->bundleMover->moveSynchronously(
142 Closure::fromCallable( [ $this,
'progressCallback' ] )
145 $this->logSeparator();
146 $this->output(
"Finished moving '$currentBundleName' to '$newBundleName' $subpageMsg\n" );
149 private function parseErrorMessage( SplObjectStorage $errors ): string {
150 $errorMsg = wfMessage(
'pt-movepage-blockers', count( $errors ) )->text() .
"\n";
151 foreach ( $errors as $title ) {
152 $titleText = $title->getPrefixedText();
153 $errorMsg .=
"$titleText\n";
154 $errorMsg .= $errors[ $title ]->getWikiText(
false,
'pt-movepage-error-placeholder',
'en' );
161 private function progressCallback( Title $previous, Title $new, Status $status,
int $total,
int $processed ): void {
162 $previousTitleText = $previous->getPrefixedText();
163 $newTitleText = $new->getPrefixedText();
164 $paddedProcessed = str_pad( (
string)$processed, strlen( (
string)$total ),
' ', STR_PAD_LEFT );
165 $progressCounter =
"($paddedProcessed/$total)";
167 if ( $status->isOK() ) {
168 $this->output(
"$progressCounter $previousTitleText --> $newTitleText\n" );
170 $this->output(
"$progressCounter Failed to move $previousTitleText to $newTitleText\n" );
171 $this->output(
"\tReason:" . $status->getWikiText() .
"\n" );
175 private function displayPagesToMove( PageMoveCollection $pageCollection ): void {
176 $infoMessage =
"\nThe following pages will be moved:\n";
183 'pt-movepage-list-pages' => [ $pageCollection->getTranslatablePage() ],
184 'pt-movepage-list-translation' => $pageCollection->getTranslationPagesPair(),
185 'pt-movepage-list-section' => $pageCollection->getUnitPagesPair()
188 $subpages = $pageCollection->getSubpagesPair();
190 $pagesToMove[
'pt-movepage-list-other'] = $subpages;
193 foreach ( $pagesToMove as $type => $pages ) {
195 $infoMessage .= $this->getSectionHeader( $type, $pages );
196 if ( !count( $pages ) ) {
200 foreach ( $pages as $pagePairs ) {
203 if ( $type ===
'pt-movepage-list-other' ) {
207 $old = $pagePairs->getOldTitle();
208 $new = $pagePairs->getNewTitle();
211 $line =
'* ' . $old->getPrefixedText() .
' → ' . $new->getPrefixedText();
212 if ( $pagePairs->hasTalkpage() ) {
215 $line .=
' ' . $this->message(
'pt-movepage-talkpage-exists' )->text();
222 $infoMessage .= implode(
"\n", $lines ) .
"\n";
225 $translatableSubpages = $pageCollection->getTranslatableSubpages();
226 $infoMessage .= $this->getSectionHeader(
'pt-movepage-list-translatable', $translatableSubpages );
228 if ( $translatableSubpages ) {
230 $infoMessage .= $this->message(
'pt-movepage-list-translatable-note' )->text() .
"\n";
231 foreach ( $translatableSubpages as $page ) {
232 $lines[] =
'* ' . $page->getPrefixedText();
235 $infoMessage .= implode(
"\n", $lines ) .
"\n";
238 $this->output( $infoMessage );
240 $this->logSeparator();
242 $this->message(
'pt-movepage-list-count' )
243 ->numParams( $count, $subpagesCount, $talkpagesCount )
246 $this->logSeparator();
247 $this->output(
"\n" );
250 private function getSectionHeader(
string $type, array $pages ): string {
251 $infoMessage = $this->getSeparator();
252 $pageCount = count( $pages );
256 $infoMessage .= $this->message( $type )->numParams( $pageCount )->text() .
"\n\n";
258 $infoMessage .= $this->message(
'pt-movepage-list-no-pages' )->text() .
"\n";
264 private function getConfirmation(): bool {
265 $line = self::readconsole(
'Type "MOVE" to begin the move operation: ' );
266 return strtolower( $line ) ===
'move';
269 private function getSeparator(
int $width = 15 ): string {
270 return str_repeat(
'-', $width ) .
"\n";
273 private function logSeparator(
int $width = 15 ): void {
274 $this->output( $this->getSeparator( $width ) );
277 private function message(
string $key ): Message {
278 return ( new Message( $key ) )->inLanguage(
'en' );
281 private function getTitleFromInput(
string $pageName ): Title {
282 $titleValue = $this->titleParser->parseTitle( $pageName );
283 return Title::newFromLinkTarget( $titleValue );
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), MessageIndex::singleton());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer());}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(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());}, '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