34 private User $fuzzyBot;
38 Title $target,
string $content,
bool $fuzzy =
false
41 'content' => $content,
45 return new self( $target, $params );
64 array $otherLangContents = []
67 'target' => $targetStr,
68 'replacement' => $replacement,
71 'content' => $content,
72 'otherLangs' => $otherLangContents
75 return new self( $target, $params );
78 public function __construct( Title $title, array $params = [] ) {
79 parent::__construct(
'UpdateMessageJob', $title, $params );
82 public function run(): bool {
83 $params = $this->params;
84 $isRename = $params[
'rename'] ??
false;
85 $isFuzzy = $params[
'fuzzy'] ??
false;
86 $otherLangs = $params[
'otherLangs'] ?? [];
87 $originalTitle = Title::newFromLinkTarget( $this->title->getTitleValue(), Title::NEW_CLONE );
90 $this->title = $this->handleRename( $params[
'target'], $params[
'replacement'] );
91 if ( $this->title ===
null ) {
94 'Rename process could not find the source title.',
96 'replacement' => $params[
'replacement'],
97 'target' => $params[
'target']
101 $this->removeFromCache( $originalTitle );
105 $title = $this->title;
106 $baseRevId = $title->getLatestRevId();
107 $updater = $this->fuzzyBotEdit( $title, $params[
'content'] );
108 if ( !$updater->getStatus()->isOK() ) {
110 'Failed to update content for source message',
112 'content' => ContentHandler::makeContent( $params[
'content'], $this->title ),
113 'errors' => $updater->getStatus()->getMessages()
120 $this->processTranslationChanges( $otherLangs, $params[
'replacement'], $params[
'namespace'] );
123 $this->handleFuzzy( $title, $isFuzzy, $updater, $baseRevId );
125 $this->removeFromCache( $originalTitle );
129 private function handleRename(
string $target,
string $replacement ): ?Title {
130 $newSourceTitle = null;
132 $sourceMessageHandle =
new MessageHandle( $this->title );
133 $movableTitles = TranslateReplaceTitle::getTitlesForMove( $sourceMessageHandle, $replacement );
135 if ( $movableTitles === [] ) {
137 'No movable titles found with target text.',
139 'title' => $this->title->getPrefixedText(),
140 'replacement' => $replacement,
147 $renameSummary = wfMessage(
'translate-manage-import-rename-summary' )
148 ->inContentLanguage()->plain();
150 foreach ( $movableTitles as [ $sourceTitle, $replacementTitle ] ) {
151 $mv = MediaWikiServices::getInstance()
152 ->getMovePageFactory()
153 ->newMovePage( $sourceTitle, $replacementTitle );
155 $status = $mv->move( $this->getFuzzyBot(), $renameSummary,
false );
156 if ( !$status->isOK() ) {
158 'Error moving message',
160 'target' => $sourceTitle->getPrefixedText(),
161 'replacement' => $replacementTitle->getPrefixedText(),
162 'errors' => $status->getMessages()
167 [ , $targetCode ] = Utilities::figureMessage( $replacementTitle->getText() );
168 if ( !$newSourceTitle && $sourceMessageHandle->getCode() === $targetCode ) {
169 $newSourceTitle = $replacementTitle;
173 if ( $newSourceTitle ) {
174 return $newSourceTitle;
179 'Source title was not in the list of movable titles.',
180 [
'title' => $this->title->getPrefixedText() ]
196 private function handleFuzzy( Title $title,
bool $invalidate, PageUpdater $updater,
int $baseTranver ): void {
197 global $wgTranslateDocumentationLanguageCode;
198 $editResult = $updater->getEditResult();
199 if ( !$invalidate && !$editResult->isExactRevert() ) {
202 $oldRevId = $editResult->getOriginalRevisionId();
203 $handle =
new MessageHandle( $title );
205 $languages = Utilities::getLanguageNames(
'en' );
208 unset( $languages[$wgTranslateDocumentationLanguageCode] );
209 unset( $languages[$handle->getCode()] );
211 $languages = array_keys( $languages );
215 $mwInstance = MediaWikiServices::getInstance();
216 $revTagStore = Services::getInstance()->getRevTagStore();
217 $revStore = $mwInstance->getRevisionStore();
219 if ( $oldRevId || $invalidate ) {
222 $batch = $mwInstance->getLinkBatchFactory()->newLinkBatch();
223 $batch->setCaller( __METHOD__ );
224 foreach ( $languages as $code ) {
225 $batch->addObj( $handle->getTitleForLanguage( $code ) );
229 $newRevision = $updater->getNewRevision();
231 $targetSha = $newRevision ? $newRevision->getSha1() :
null;
233 foreach ( $languages as $code ) {
234 $otherTitle = $handle->getTitleForLanguage( $code );
235 $shouldUnfuzzy =
false;
236 if ( !$otherTitle->exists() ) {
240 $transverId = $revTagStore->getTransver( $otherTitle );
241 if ( !$transverId ) {
244 $latest = $otherTitle->getLatestRevID();
245 if ( $invalidate && !$revTagStore->isRevIdFuzzy( $otherTitle->getId(), $latest ) && $newRevision ) {
250 $revTagStore->setTransver( $otherTitle, $latest, $baseTranver );
253 } elseif ( $oldRevId && $newRevision && $editResult->isExactRevert() ) {
255 $transver = $revStore->getRevisionById( $transverId, 0, $title );
256 if ( $oldRevId == $transverId ) {
258 $shouldUnfuzzy =
true;
259 } elseif ( $transver ) {
260 $transverSha = $transver->getSha1();
261 if ( $transverSha == $targetSha ) {
263 $shouldUnfuzzy =
true;
269 if ( $shouldUnfuzzy ) {
272 $otherHandle =
new MessageHandle( $otherTitle );
273 $wikiPage = $mwInstance->getWikiPageFactory()->newFromTitle( $otherTitle );
274 $content = $wikiPage->getContent();
275 if ( !$content instanceof TextContent ) {
280 $text = $content->getText();
281 if ( $otherHandle->isFuzzy() && !$otherHandle->needsFuzzy( $text ) ) {
282 $unfuzzies[] = $otherTitle;
287 } elseif ( $invalidate ) {
288 $fuzzies[] = $otherTitle;
292 $dbw = $mwInstance->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
294 if ( $fuzzies !== [] ) {
296 foreach ( $fuzzies as $otherTitle ) {
298 'rt_type' => RevTagStore::FUZZY_TAG,
299 'rt_page' => $otherTitle->getId(),
300 'rt_revision' => $otherTitle->getLatestRevID(),
303 $dbw->newReplaceQueryBuilder()
304 ->replaceInto(
'revtag' )
305 ->uniqueIndexFields( [
'rt_type',
'rt_page',
'rt_revision' ] )
307 ->caller( __METHOD__ )
310 if ( $unfuzzies !== [] ) {
311 foreach ( $unfuzzies as $otherTitle ) {
312 $dbw->newDeleteQueryBuilder()
313 ->deleteFrom(
'revtag' )
315 'rt_type' => RevTagStore::FUZZY_TAG,
316 'rt_page' => $otherTitle->getId(),
317 'rt_revision' => $otherTitle->getLatestRevID(),
319 ->caller( __METHOD__ )
326 private function processTranslationChanges(
331 foreach ( $langChanges as $code => $contentStr ) {
332 $titleStr = Utilities::title( $baseTitle, $code, $groupNamespace );
333 $title = Title::newFromText( $titleStr, $groupNamespace );
334 $updater = $this->fuzzyBotEdit( $title, $contentStr );
336 if ( !$updater->getStatus()->isOK() ) {
338 'Failed to update content for non-source message',
340 'title' => $title->getPrefixedText(),
341 'errors' => $updater->getStatus()->getMessages()
348 private function removeFromCache( Title $title ): void {
349 $config = MediaWikiServices::getInstance()->getMainConfig();
351 if ( !$config->get(
'TranslateGroupSynchronizationCache' ) ) {
355 $currentTitle = $title;
358 if ( $this->title && $this->title->getPrefixedDBkey() !== $title->getPrefixedDBkey() ) {
359 $currentTitle = $this->title;
362 $sourceMessageHandle =
new MessageHandle( $currentTitle );
363 $groupIds = $sourceMessageHandle->getGroupIds();
366 "Could not find group Id for message title: {$currentTitle->getPrefixedDBkey()}",
372 $groupId = $groupIds[0];
373 $group = MessageGroups::getGroup( $groupId );
379 $groupSyncCache = Services::getInstance()->getGroupSynchronizationCache();
380 $messageKey = $title->getPrefixedDBkey();
382 if ( $groupSyncCache->isMessageBeingProcessed( $groupId, $messageKey ) ) {
383 $groupSyncCache->removeMessages( $groupId, $messageKey );
384 $groupSyncCache->extendGroupExpiryTime( $groupId );
387 "Did not find key: $messageKey; in group: $groupId in group sync cache",
393 private function fuzzyBotEdit( Title $title,
string $content ): PageUpdater {
394 $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
395 $content = ContentHandler::makeContent( $content, $title );
396 $page = $wikiPageFactory->newFromTitle( $title );
397 $updater = $page->newPageUpdater( $this->getFuzzyBot() )
398 ->setContent( SlotRecord::MAIN, $content );
400 if ( $this->getFuzzyBot()->authorizeWrite(
'autopatrol', $title ) ) {
401 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
404 $summary = wfMessage(
'translate-manage-import-summary' )
405 ->inContentLanguage()->plain();
406 $updater->saveRevision(
407 CommentStoreComment::newUnsavedComment( $summary ),
413 private function getFuzzyBot(): User {
414 $this->fuzzyBot ??= FuzzyBot::getUser();
415 return $this->fuzzyBot;