24 private const INDENT_SPACER =
' ';
26 private const STATUS_NAME_MAPPING = [
27 TranslatablePageStatus::PROPOSED =>
'Proposed',
28 TranslatablePageStatus::ACTIVE =>
'Active',
29 TranslatablePageStatus::OUTDATED =>
'Outdated',
30 TranslatablePageStatus::BROKEN =>
'Broken'
33 private const SYNC_BATCH_STATUS = 15;
35 private const SCRIPT_VERSION = 1;
37 public function __construct() {
38 parent::__construct();
39 $this->addDescription(
'Sync translatable bundle status with values from the rev_tag table' );
40 $this->requireExtension(
'Translate' );
43 protected function getUpdateKey() {
44 return __CLASS__ .
'_v' . self::SCRIPT_VERSION;
47 protected function doDBUpdates() {
48 $this->output(
"... Syncing translatable bundle status ...\n\n" );
50 $this->output(
"Fetching translatable bundles and their statues\n\n" );
51 $translatableBundles = $this->fetchTranslatableBundles();
52 $translatableBundleStatuses = Services::getInstance()
53 ->getTranslatableBundleStatusStore()
56 $differences = $this->identifyDifferences( $translatableBundles, $translatableBundleStatuses );
58 $this->outputDifferences( $differences[
'missing'],
'Missing' );
59 $this->outputDifferences( $differences[
'incorrect'],
'Incorrect' );
60 $this->outputDifferences( $differences[
'extra'],
'Extra' );
62 $this->output(
"\nSynchronizing...\n\n" );
64 $this->syncStatus( $differences[
'missing'],
'Missing' );
65 $this->syncStatus( $differences[
'incorrect'],
'Incorrect' );
66 $this->removeStatus( $differences[
'extra'] );
68 $this->output(
"\n...Done syncing translatable status...\n" );
73 private function fetchTranslatableBundles(): array {
88 private function identifyDifferences(
89 array $translatableBundles,
90 array $translatableBundleStatuses
98 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
99 foreach ( $translatableBundles as $bundleId => $bundleInfo ) {
100 $title = $bundleInfo[
'title'];
101 $bundle = $this->getTranslatableBundle( $bundleFactory, $title );
102 $bundleStatus = $this->determineStatus( $bundle, $bundleInfo );
104 if ( !$bundleStatus ) {
109 if ( !isset( $translatableBundleStatuses[$bundleId] ) ) {
113 'status' => $bundleStatus,
114 'page_id' => $bundleId
116 $result[
'missing'][] = $response;
117 } elseif ( !$bundleStatus->isEqual( $translatableBundleStatuses[$bundleId] ) ) {
121 'status' => $bundleStatus,
122 'page_id' => $bundleId
124 $result[
'incorrect'][] = $response;
129 $extraStatusBundleIds = array_diff_key( $translatableBundleStatuses, $translatableBundles );
130 foreach ( $extraStatusBundleIds as $extraBundleId => $statusId ) {
131 $title = Title::newFromID( $extraBundleId );
136 'page_id' => $extraBundleId
139 $result[
'extra'][] = $response;
145 private function determineStatus(
150 return $bundle->determineStatus(
151 $bundleInfo[RevTagStore::TP_READY_TAG] ??
null,
152 $bundleInfo[RevTagStore::TP_MARK_TAG] ??
null,
153 $bundleInfo[
'latest']
158 throw new RuntimeException(
'Method determineStatus not implemented for MessageBundle' );
162 private function getTranslatableBundle(
166 $bundle = $tbFactory->
getBundle( $title );
174 $tpPage = TranslatablePage::newFromTitle( $title );
178 private function syncStatus( array $bundlesWithDifference,
string $differenceType ):
void {
179 if ( !$bundlesWithDifference ) {
180 $this->output(
"No \"$differenceType\" bundle statuses\n" );
184 $this->output(
"Syncing \"$differenceType\" bundle statuses\n" );
186 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
187 $tpStore = Services::getInstance()->getTranslatablePageStore();
188 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
190 $bundleCountProcessed = 0;
191 foreach ( $bundlesWithDifference as $bundleInfo ) {
192 $pageId = $bundleInfo[
'page_id'];
193 $bundleTitle = $bundleInfo[
'title'] ??
null;
194 if ( !$bundleTitle instanceof Title ) {
195 $this->fatalError(
"No title for page with id: $pageId \n" );
198 $bundle = $this->getTranslatableBundle( $bundleFactory, $bundleTitle );
203 $tpStore->updateStatus( $bundleTitle );
206 if ( $bundleCountProcessed % self::SYNC_BATCH_STATUS === 0 ) {
207 $lbFactory->waitForReplication();
210 ++$bundleCountProcessed;
213 $this->output(
"Completed sync for \"$differenceType\" bundle statuses\n" );
216 private function removeStatus( array $extraBundleInfo ):
void {
217 if ( !$extraBundleInfo ) {
218 $this->output(
"No \"extra\" bundle statuses\n" );
221 $this->output(
"Removing \"extra\" bundle statuses\n" );
223 foreach ( $extraBundleInfo as $bundleInfo ) {
224 $pageIds[] = $bundleInfo[
'page_id'];
227 $tbStatusStore = Services::getInstance()->getTranslatableBundleStatusStore();
228 $tbStatusStore->removeStatus( ...$pageIds );
229 $this->output(
"Removed \"extra\" bundle statuses\n" );
232 private function outputDifferences( array $bundlesWithDifference,
string $differenceType ):
void {
233 if ( $bundlesWithDifference ) {
234 $this->output(
"$differenceType translatable bundles statuses:\n" );
235 foreach ( $bundlesWithDifference as $bundle ) {
236 $this->outputBundleInfo( $bundle );
239 $this->output(
"No \"$differenceType\" translatable bundle statuses found!\n" );
243 private function outputBundleInfo( array $bundle ):
void {
244 $titlePrefixedDbKey = $bundle[
'title'] instanceof Title ?
245 $bundle[
'title']->getPrefixedDBkey() :
'<Title not available>';
246 $id = str_pad( (
string)$bundle[
'page_id'], 7,
' ', STR_PAD_LEFT );
247 $status = self::STATUS_NAME_MAPPING[ $bundle[
'status']->getId() ];
248 $this->output( self::INDENT_SPACER .
"* [Id: $id] $titlePrefixedDbKey: $status\n" );