24 private const INDENT_SPACER =
' ';
25 private const STATUS_NAME_MAPPING = [
26 TranslatablePageStatus::PROPOSED =>
'Proposed',
27 TranslatablePageStatus::ACTIVE =>
'Active',
28 TranslatablePageStatus::OUTDATED =>
'Outdated',
29 TranslatablePageStatus::BROKEN =>
'Broken'
31 private const SYNC_BATCH_STATUS = 15;
32 private const SCRIPT_VERSION = 1;
34 public function __construct() {
35 parent::__construct();
36 $this->addDescription(
'Sync translatable bundle status with values from the rev_tag table' );
37 $this->requireExtension(
'Translate' );
42 return __CLASS__ .
'_v' . self::SCRIPT_VERSION;
47 $this->output(
"... Syncing translatable bundle status ...\n\n" );
49 $this->output(
"Fetching translatable bundles and their statues\n\n" );
50 $translatableBundles = $this->fetchTranslatableBundles();
51 $translatableBundleStatuses = Services::getInstance()
52 ->getTranslatableBundleStatusStore()
55 $differences = $this->identifyDifferences( $translatableBundles, $translatableBundleStatuses );
57 $this->outputDifferences( $differences[
'missing'],
'Missing' );
58 $this->outputDifferences( $differences[
'incorrect'],
'Incorrect' );
59 $this->outputDifferences( $differences[
'extra'],
'Extra' );
61 $this->output(
"\nSynchronizing...\n\n" );
63 $this->syncStatus( $differences[
'missing'],
'Missing' );
64 $this->syncStatus( $differences[
'incorrect'],
'Incorrect' );
65 $this->removeStatus( $differences[
'extra'] );
67 $this->output(
"\n...Done syncing translatable status...\n" );
72 private function fetchTranslatableBundles(): array {
75 return PageTranslationSpecialPage::buildPageArray( $resultWrapper );
87 private function identifyDifferences(
88 array $translatableBundles,
89 array $translatableBundleStatuses
97 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
98 foreach ( $translatableBundles as $bundleId => $bundleInfo ) {
99 $title = $bundleInfo[
'title'];
100 $bundle = $this->getTranslatableBundle( $bundleFactory, $title );
101 $bundleStatus = $this->determineStatus( $bundle, $bundleInfo );
103 if ( !$bundleStatus ) {
108 if ( !isset( $translatableBundleStatuses[$bundleId] ) ) {
112 'status' => $bundleStatus,
113 'page_id' => $bundleId
115 $result[
'missing'][] = $response;
116 } elseif ( !$bundleStatus->isEqual( $translatableBundleStatuses[$bundleId] ) ) {
120 'status' => $bundleStatus,
121 'page_id' => $bundleId
123 $result[
'incorrect'][] = $response;
128 $extraStatusBundleIds = array_diff_key( $translatableBundleStatuses, $translatableBundles );
129 foreach ( $extraStatusBundleIds as $extraBundleId => $statusId ) {
130 $title = Title::newFromID( $extraBundleId );
134 'status' =>
new TranslatablePageStatus( $statusId ),
135 'page_id' => $extraBundleId
138 $result[
'extra'][] = $response;
144 private function determineStatus(
145 TranslatableBundle $bundle,
147 ): ?TranslatableBundleStatus {
148 if ( $bundle instanceof TranslatablePage ) {
149 return $bundle::determineStatus(
150 $bundleInfo[RevTagStore::TP_READY_TAG] ??
null,
151 $bundleInfo[RevTagStore::TP_MARK_TAG] ??
null,
152 $bundleInfo[
'latest']
157 throw new RuntimeException(
'Method determineStatus not implemented for MessageBundle' );
161 private function getTranslatableBundle(
162 TranslatableBundleFactory $tbFactory,
164 ): TranslatableBundle {
165 $bundle = $tbFactory->getBundle( $title );
173 return TranslatablePage::newFromTitle( $title );
176 private function syncStatus( array $bundlesWithDifference,
string $differenceType ): void {
177 if ( !$bundlesWithDifference ) {
178 $this->output(
"No \"$differenceType\" bundle statuses\n" );
182 $this->output(
"Syncing \"$differenceType\" bundle statuses\n" );
184 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
185 $tpStore = Services::getInstance()->getTranslatablePageStore();
186 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
188 $bundleCountProcessed = 0;
189 foreach ( $bundlesWithDifference as $bundleInfo ) {
190 $pageId = $bundleInfo[
'page_id'];
191 $bundleTitle = $bundleInfo[
'title'] ??
null;
192 if ( !$bundleTitle instanceof Title ) {
193 $this->fatalError(
"No title for page with id: $pageId \n" );
196 $bundle = $this->getTranslatableBundle( $bundleFactory, $bundleTitle );
197 if ( $bundle instanceof TranslatablePage ) {
201 $tpStore->updateStatus( $bundleTitle );
204 if ( $bundleCountProcessed % self::SYNC_BATCH_STATUS === 0 ) {
205 $lbFactory->waitForReplication();
208 ++$bundleCountProcessed;
211 $this->output(
"Completed sync for \"$differenceType\" bundle statuses\n" );
214 private function removeStatus( array $extraBundleInfo ): void {
215 if ( !$extraBundleInfo ) {
216 $this->output(
"No \"extra\" bundle statuses\n" );
219 $this->output(
"Removing \"extra\" bundle statuses\n" );
221 foreach ( $extraBundleInfo as $bundleInfo ) {
222 $pageIds[] = $bundleInfo[
'page_id'];
225 $tbStatusStore = Services::getInstance()->getTranslatableBundleStatusStore();
226 $tbStatusStore->removeStatus( ...$pageIds );
227 $this->output(
"Removed \"extra\" bundle statuses\n" );
230 private function outputDifferences( array $bundlesWithDifference,
string $differenceType ): void {
231 if ( $bundlesWithDifference ) {
232 $this->output(
"$differenceType translatable bundles statuses:\n" );
233 foreach ( $bundlesWithDifference as $bundle ) {
234 $this->outputBundleInfo( $bundle );
237 $this->output(
"No \"$differenceType\" translatable bundle statuses found!\n" );
241 private function outputBundleInfo( array $bundle ): void {
242 $titlePrefixedDbKey = $bundle[
'title'] instanceof Title ?
243 $bundle[
'title']->getPrefixedDBkey() :
'<Title not available>';
244 $id = str_pad( (
string)$bundle[
'page_id'], 7,
' ', STR_PAD_LEFT );
245 $status = self::STATUS_NAME_MAPPING[ $bundle[
'status']->getId() ];
246 $this->output( self::INDENT_SPACER .
"* [Id: $id] $titlePrefixedDbKey: $status\n" );
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