Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
ImportTranslatableBundleMaintenanceScript.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
6use ForeignTitle;
7use IDBAccessObject;
13use MediaWiki\MediaWikiServices;
14use MediaWiki\Title\MalformedTitleException;
15use MediaWiki\Title\Title;
16use MediaWiki\User\UserIdentity;
17
25 private int $pageImportCount = 0;
26 private int $totalPagesBeingImported = 0;
27
28 public function __construct() {
29 parent::__construct();
30 $this->addArg(
31 'xml-path',
32 'Path to the XML file to be imported',
33 self::REQUIRED
34 );
35 $this->addOption(
36 'user',
37 'Name of the user performing the import',
38 self::REQUIRED,
39 self::HAS_ARG
40 );
41 $this->addOption(
42 'interwiki-prefix',
43 'Prefix to apply to unknown (and possibly also known) usernames',
44 self::REQUIRED,
45 self::HAS_ARG
46 );
47 $this->addOption(
48 'comment',
49 'Comment added to the log for the import',
50 self::OPTIONAL,
51 self::HAS_ARG
52 );
53 $this->addOption(
54 'assign-known-users',
55 'Whether to apply the prefix to usernames that exist locally',
56 self::OPTIONAL
57 );
58 $this->addOption(
59 'target-name',
60 'Target page name to import the page to',
61 self::OPTIONAL,
62 self::HAS_ARG
63 );
64 $this->addOption(
65 'override',
66 'Override existing target page if it exists',
67 self::OPTIONAL
68 );
69
70 // Options related to marking a page for translation
71 $this->addOption(
72 'skip-translating-title',
73 'Should translation of title be skipped',
74 self::OPTIONAL
75 );
76 $this->addOption(
77 'priority-languages',
78 'Comma separated list of priority language codes',
79 self::OPTIONAL,
80 self::HAS_ARG
81 );
82 $this->addOption(
83 'priority-languages-reason',
84 'Reason for setting the priority languages',
85 self::OPTIONAL,
86 self::HAS_ARG
87 );
88 $this->addOption(
89 'force-priority-languages',
90 'Only allow translations to the priority languages',
91 self::OPTIONAL
92 );
93 $this->addOption(
94 'disallow-transclusion',
95 'Disable translation aware transclusion for this page',
96 self::OPTIONAL
97 );
98 $this->addOption(
99 'use-old-syntax-version',
100 'Use the old syntax version for translatable pages',
101 self::OPTIONAL
102 );
103
104 $this->requireExtension( 'Translate' );
105 }
106
108 public function execute() {
109 $this->pageImportCount = 0;
110 $importFilePath = $this->getPathOfFileToImport();
111 $importUser = $this->getImportUser();
112 $comment = $this->getOption( 'comment' );
113 $interwikiPrefix = $this->getInterwikiPrefix();
114 $assignKnownUsers = $this->hasOption( 'assign-known-users' );
115 $targetPage = $this->getTargetPageName();
116 $translatablePageSettings = $this->getTranslatablePageSettings();
117
118 // First import the page
119 try {
120 $this->totalPagesBeingImported = substr_count( file_get_contents( $importFilePath ), '</page>' );
121 $importer = Services::getInstance()->getTranslatableBundleImporter();
122 $this->output( "Starting import for file '$importFilePath'...\n" );
123 $importer->setPageImportCompleteCallback( [ $this, 'logPageImportComplete' ] );
124 $bundleTitle = $importer->import(
125 $importFilePath,
126 $interwikiPrefix,
127 $assignKnownUsers,
128 $importUser,
129 $targetPage,
130 $comment
131 );
132 } catch ( TranslatableBundleImportException $e ) {
133 $this->error( "An error occurred during import: {$e->getMessage()}\n" );
134 $this->error( "Stacktrace: {$e->getTraceAsString()} .\n" );
135 $this->fatalError( 'Stopping import.' );
136 }
137
138 $this->output(
139 "Translatable bundle {$bundleTitle->getPrefixedText()} was imported. " .
140 "Total {$this->pageImportCount} page(s) were created\n"
141 );
142
143 $this->output( "Now marking {$bundleTitle->getPrefixedText()} for translation...\n" );
144 // Try to mark the page for translation
145 $this->markPageForTranslation( $bundleTitle, $translatablePageSettings, $importUser );
146 }
147
148 public function logPageImportComplete( Title $title, ForeignTitle $foreignTitle ): void {
149 ++$this->pageImportCount;
150 $currentProgress = str_pad(
151 (string)$this->pageImportCount,
152 strlen( (string)$this->totalPagesBeingImported ),
153 ' ',
154 STR_PAD_LEFT
155 );
156
157 $progressCounter = "($currentProgress/$this->totalPagesBeingImported)";
158 $this->output( "$progressCounter {$foreignTitle->getFullText()} --> {$title->getFullText()}\n" );
159 }
160
161 private function getPathOfFileToImport(): string {
162 $xmlPath = $this->getArg( 'xml-path' );
163 if ( !file_exists( $xmlPath ) ) {
164 $this->fatalError( "File '$xmlPath' does not exist" );
165 }
166
167 if ( !is_readable( $xmlPath ) ) {
168 $this->fatalError( "File '$xmlPath' is not readable" );
169 }
170
171 return $xmlPath;
172 }
173
174 private function getImportUser(): UserIdentity {
175 $username = $this->getOption( 'user' );
176
177 $userFactory = MediaWikiServices::getInstance()->getUserFactory();
178 $user = $userFactory->newFromName( $username );
179
180 if ( $user === null || !$user->isNamed() ) {
181 $this->fatalError( "User $username does not exist." );
182 }
183
184 return $user;
185 }
186
187 private function getInterwikiPrefix(): string {
188 $interwikiPrefix = trim( $this->getOption( 'interwiki-prefix', '' ) );
189 if ( $interwikiPrefix === '' ) {
190 $this->fatalError( 'Argument interwiki-prefix cannot be empty.' );
191 }
192
193 return $interwikiPrefix;
194 }
195
196 private function getPriorityLanguages(): array {
197 $priorityLanguageCodes = self::commaList2Array( $this->getOption( 'priority-languages' ) ?? '' );
198 $knownLanguageCodes = array_keys( Utilities::getLanguageNames( 'en' ) );
199 $invalidLanguageCodes = array_diff( $priorityLanguageCodes, $knownLanguageCodes );
200
201 if ( $invalidLanguageCodes ) {
202 $this->fatalError(
203 'Unknown priority language code(s): ' . implode( ', ', $invalidLanguageCodes )
204 );
205 }
206
207 return $priorityLanguageCodes;
208 }
209
210 private function markPageForTranslation(
211 Title $bundleTitle,
212 TranslatablePageSettings $translatablePageSettings,
213 UserIdentity $importUser
214 ): void {
215 $translatablePageMarker = Services::getInstance()->getTranslatablePageMarker();
216 $user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $importUser );
217 try {
218 $operation = $translatablePageMarker->getMarkOperation(
219 $bundleTitle->toPageRecord( IDBAccessObject::READ_LATEST ),
220 null,
221 $translatablePageSettings->shouldTranslateTitle()
222 );
223 } catch ( TranslatablePageMarkException $e ) {
224 $this->error( "Error while marking page {$bundleTitle->getPrefixedText()} for translation.\n" );
225 $this->error( "Fix the issues and mark the page for translation using Special:PageTranslation.\n\n" );
226 $this->fatalError( wfMessage( $e->getMessageObject() )->text() );
227 }
228
229 $unitNameValidationResult = $operation->getUnitValidationStatus();
230 if ( !$unitNameValidationResult->isOK() ) {
231 $this->output( "Unit validation failed for {$bundleTitle->getPrefixedText()}.\n" );
232 $this->fatalError( $unitNameValidationResult->getMessage()->text() );
233 }
234
235 try {
236 $translatablePageMarker->markForTranslation( $operation, $translatablePageSettings, $user );
237 $this->output( "The page {$bundleTitle->getPrefixedText()} has been marked for translation.\n" );
238 } catch ( TranslatablePageMarkException $e ) {
239 $this->error( "Error while marking page {$bundleTitle->getPrefixedText()} for translation.\n" );
240 $this->error( "Fix the issues and mark the page for translation using Special:PageTranslation.\n\n" );
241 $this->fatalError( $e->getMessageObject()->text() );
242 }
243 }
244
245 private function getTranslatablePageSettings(): TranslatablePageSettings {
246 return new TranslatablePageSettings(
247 $this->getPriorityLanguages(),
248 $this->hasOption( 'force-priority-languages' ),
249 $this->getOption( 'priority-languages-reason' ) ?? '',
250 [],
251 !$this->hasOption( 'skip-translating-title' ),
252 !$this->hasOption( 'use-old-syntax-version' ),
253 !$this->hasOption( 'disallow-transclusion' ),
254 );
255 }
256
257 private function getTargetPageName(): ?Title {
258 $targetPage = $this->getOption( 'target-name' );
259 if ( $targetPage === null ) {
260 return null;
261 }
262
263 try {
264 $targetPageTitle = MediaWikiServices::getInstance()->getTitleFactory()->newFromTextThrow( $targetPage );
265 } catch ( MalformedTitleException $e ) {
266 $this->fatalError(
267 "Target page name $targetPage does not appear to be valid: {$e->getMessage()}"
268 );
269 }
270
271 $shouldOverride = $this->hasOption( 'override' );
272 if ( $targetPageTitle->exists() && !$shouldOverride ) {
273 $this->fatalError(
274 "Specified target page $targetPage already exists. Use '--override' if you still want to import"
275 );
276 }
277
278 if ( !$targetPageTitle->canExist() ) {
279 $this->fatalError( "The target page name $targetPage cannot be created" );
280 }
281
282 return $targetPageTitle;
283 }
284}
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
Exception thrown when an error occurs when importing a translatable bundle via TranslatableBundleImpo...
Exception thrown when TranslatablePageMarker is unable to unmark a page for translation.
Value object containing user configurable settings when marking a page for translation.
Minimal service container.
Definition Services.php:58
Base maintenance script containing constants and methods used in multiple scripts Hopefully the const...
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31