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' );
40 protected function getUpdateKey() {
41 return __CLASS__ .
'_v' . self::SCRIPT_VERSION;
44 protected function doDBUpdates() {
45 $this->output(
"... Syncing translatable bundle status ...\n\n" );
47 $this->output(
"Fetching translatable bundles and their statues\n\n" );
48 $translatableBundles = $this->fetchTranslatableBundles();
49 $translatableBundleStatuses = Services::getInstance()
50 ->getTranslatableBundleStatusStore()
53 $differences = $this->identifyDifferences( $translatableBundles, $translatableBundleStatuses );
55 $this->outputDifferences( $differences[
'missing'],
'Missing' );
56 $this->outputDifferences( $differences[
'incorrect'],
'Incorrect' );
57 $this->outputDifferences( $differences[
'extra'],
'Extra' );
59 $this->output(
"\nSynchronizing...\n\n" );
61 $this->syncStatus( $differences[
'missing'],
'Missing' );
62 $this->syncStatus( $differences[
'incorrect'],
'Incorrect' );
63 $this->removeStatus( $differences[
'extra'] );
65 $this->output(
"\n...Done syncing translatable status...\n" );
70 private function fetchTranslatableBundles(): array {
85 private function identifyDifferences(
86 array $translatableBundles,
87 array $translatableBundleStatuses
95 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
96 foreach ( $translatableBundles as $bundleId => $bundleInfo ) {
97 $title = $bundleInfo[
'title'];
98 $bundle = $this->getTranslatableBundle( $bundleFactory, $title );
99 $bundleStatus = $this->determineStatus( $bundle, $bundleInfo );
101 if ( !$bundleStatus ) {
106 if ( !isset( $translatableBundleStatuses[$bundleId] ) ) {
110 'status' => $bundleStatus,
111 'page_id' => $bundleId
113 $result[
'missing'][] = $response;
114 } elseif ( !$bundleStatus->isEqual( $translatableBundleStatuses[$bundleId] ) ) {
118 'status' => $bundleStatus,
119 'page_id' => $bundleId
121 $result[
'incorrect'][] = $response;
126 $extraStatusBundleIds = array_diff_key( $translatableBundleStatuses, $translatableBundles );
127 foreach ( $extraStatusBundleIds as $extraBundleId => $statusId ) {
128 $title = Title::newFromID( $extraBundleId );
133 'page_id' => $extraBundleId
136 $result[
'extra'][] = $response;
142 private function determineStatus(
147 return $bundle->determineStatus(
148 $bundleInfo[RevTagStore::TP_READY_TAG] ??
null,
149 $bundleInfo[RevTagStore::TP_MARK_TAG] ??
null,
150 $bundleInfo[
'latest']
155 throw new RuntimeException(
'Method determineStatus not implemented for MessageBundle' );
159 private function getTranslatableBundle(
163 $bundle = $tbFactory->
getBundle( $title );
171 return TranslatablePage::newFromTitle( $title );
174 private function syncStatus( array $bundlesWithDifference,
string $differenceType ):
void {
175 if ( !$bundlesWithDifference ) {
176 $this->output(
"No \"$differenceType\" bundle statuses\n" );
180 $this->output(
"Syncing \"$differenceType\" bundle statuses\n" );
182 $bundleFactory = Services::getInstance()->getTranslatableBundleFactory();
183 $tpStore = Services::getInstance()->getTranslatablePageStore();
184 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
186 $bundleCountProcessed = 0;
187 foreach ( $bundlesWithDifference as $bundleInfo ) {
188 $pageId = $bundleInfo[
'page_id'];
189 $bundleTitle = $bundleInfo[
'title'] ??
null;
190 if ( !$bundleTitle instanceof Title ) {
191 $this->fatalError(
"No title for page with id: $pageId \n" );
194 $bundle = $this->getTranslatableBundle( $bundleFactory, $bundleTitle );
199 $tpStore->updateStatus( $bundleTitle );
202 if ( $bundleCountProcessed % self::SYNC_BATCH_STATUS === 0 ) {
203 $lbFactory->waitForReplication();
206 ++$bundleCountProcessed;
209 $this->output(
"Completed sync for \"$differenceType\" bundle statuses\n" );
212 private function removeStatus( array $extraBundleInfo ):
void {
213 if ( !$extraBundleInfo ) {
214 $this->output(
"No \"extra\" bundle statuses\n" );
217 $this->output(
"Removing \"extra\" bundle statuses\n" );
219 foreach ( $extraBundleInfo as $bundleInfo ) {
220 $pageIds[] = $bundleInfo[
'page_id'];
223 $tbStatusStore = Services::getInstance()->getTranslatableBundleStatusStore();
224 $tbStatusStore->removeStatus( ...$pageIds );
225 $this->output(
"Removed \"extra\" bundle statuses\n" );
228 private function outputDifferences( array $bundlesWithDifference,
string $differenceType ):
void {
229 if ( $bundlesWithDifference ) {
230 $this->output(
"$differenceType translatable bundles statuses:\n" );
231 foreach ( $bundlesWithDifference as $bundle ) {
232 $this->outputBundleInfo( $bundle );
235 $this->output(
"No \"$differenceType\" translatable bundle statuses found!\n" );
239 private function outputBundleInfo( array $bundle ):
void {
240 $titlePrefixedDbKey = $bundle[
'title'] instanceof Title ?
241 $bundle[
'title']->getPrefixedDBkey() :
'<Title not available>';
242 $id = str_pad( (
string)$bundle[
'page_id'], 7,
' ', STR_PAD_LEFT );
243 $status = self::STATUS_NAME_MAPPING[ $bundle[
'status']->getId() ];
244 $this->output( self::INDENT_SPACER .
"* [Id: $id] $titlePrefixedDbKey: $status\n" );