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 $renamedTitle = $this->handleRename( $params[
'target'], $params[
'replacement'] );
91 if ( $renamedTitle ===
null ) {
94 'Rename process could not find the source title.',
96 'replacement' => $params[
'replacement'],
97 'target' => $params[
'target']
101 $this->removeFromCache( $originalTitle );
104 $this->title = $renamedTitle;
106 $title = $this->title;
107 $baseRevId = $title->getLatestRevId();
108 $updater = $this->fuzzyBotEdit( $title, $params[
'content'] );
109 if ( !$updater->getStatus()->isOK() ) {
111 'Failed to update content for source message',
113 'content' => ContentHandler::makeContent( $params[
'content'], $this->title ),
114 'errors' => $updater->getStatus()->getMessages()
121 $this->processTranslationChanges( $otherLangs, $params[
'replacement'], $params[
'namespace'] );
124 $this->handleFuzzy( $title, $isFuzzy, $updater, $baseRevId );
126 $this->removeFromCache( $originalTitle );
130 private function handleRename(
string $target,
string $replacement ): ?Title {
131 $newSourceTitle = null;
133 $sourceMessageHandle =
new MessageHandle( $this->title );
134 $movableTitles = TranslateReplaceTitle::getTitlesForMove( $sourceMessageHandle, $replacement );
136 if ( $movableTitles === [] ) {
138 'No movable titles found with target text.',
140 'title' => $this->title->getPrefixedText(),
141 'replacement' => $replacement,
148 $renameSummary = wfMessage(
'translate-manage-import-rename-summary' )
149 ->inContentLanguage()->plain();
151 foreach ( $movableTitles as [ $sourceTitle, $replacementTitle ] ) {
152 $mv = MediaWikiServices::getInstance()
153 ->getMovePageFactory()
154 ->newMovePage( $sourceTitle, $replacementTitle );
156 $status = $mv->move( $this->getFuzzyBot(), $renameSummary,
false );
157 if ( !$status->isOK() ) {
159 'Error moving message',
161 'target' => $sourceTitle->getPrefixedText(),
162 'replacement' => $replacementTitle->getPrefixedText(),
163 'errors' => $status->getMessages()
168 [ , $targetCode ] = Utilities::figureMessage( $replacementTitle->getText() );
169 if ( !$newSourceTitle && $sourceMessageHandle->getCode() === $targetCode ) {
170 $newSourceTitle = $replacementTitle;
174 if ( $newSourceTitle ) {
175 return $newSourceTitle;
180 'Source title was not in the list of movable titles.',
181 [
'title' => $this->title->getPrefixedText() ]
197 private function handleFuzzy( Title $title,
bool $invalidate, PageUpdater $updater,
int $baseTranver ): void {
198 global $wgTranslateDocumentationLanguageCode;
199 $editResult = $updater->getEditResult();
200 if ( !$invalidate && !$editResult->isExactRevert() ) {
203 $oldRevId = $editResult->getOriginalRevisionId();
204 $handle =
new MessageHandle( $title );
206 $languages = Utilities::getLanguageNames(
'en' );
209 unset( $languages[$wgTranslateDocumentationLanguageCode] );
210 unset( $languages[$handle->getCode()] );
212 $languages = array_keys( $languages );
216 $mwInstance = MediaWikiServices::getInstance();
217 $revTagStore = Services::getInstance()->getRevTagStore();
218 $revStore = $mwInstance->getRevisionStore();
220 if ( $oldRevId || $invalidate ) {
223 $batch = $mwInstance->getLinkBatchFactory()->newLinkBatch();
224 $batch->setCaller( __METHOD__ );
225 foreach ( $languages as $code ) {
226 $batch->addObj( $handle->getTitleForLanguage( $code ) );
230 $newRevision = $updater->getNewRevision();
232 $targetSha = $newRevision ? $newRevision->getSha1() :
null;
234 foreach ( $languages as $code ) {
235 $otherTitle = $handle->getTitleForLanguage( $code );
236 $shouldUnfuzzy =
false;
237 if ( !$otherTitle->exists() ) {
241 $transverId = $revTagStore->getTransver( $otherTitle );
242 if ( !$transverId ) {
245 $latest = $otherTitle->getLatestRevID();
246 if ( $invalidate && !$revTagStore->isRevIdFuzzy( $otherTitle->getId(), $latest ) && $newRevision ) {
251 $revTagStore->setTransver( $otherTitle, $latest, $baseTranver );
254 } elseif ( $oldRevId && $newRevision && $editResult->isExactRevert() ) {
256 $transver = $revStore->getRevisionById( $transverId, 0, $title );
257 if ( $oldRevId == $transverId ) {
259 $shouldUnfuzzy =
true;
260 } elseif ( $transver ) {
261 $transverSha = $transver->getSha1();
262 if ( $transverSha == $targetSha ) {
264 $shouldUnfuzzy =
true;
270 if ( $shouldUnfuzzy ) {
273 $otherHandle =
new MessageHandle( $otherTitle );
274 $wikiPage = $mwInstance->getWikiPageFactory()->newFromTitle( $otherTitle );
275 $content = $wikiPage->getContent();
276 if ( !$content instanceof TextContent ) {
281 $text = $content->getText();
282 if ( $otherHandle->isFuzzy() && !$otherHandle->needsFuzzy( $text ) ) {
283 $unfuzzies[] = $otherTitle;
288 } elseif ( $invalidate ) {
289 $fuzzies[] = $otherTitle;
293 $dbw = $mwInstance->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
295 if ( $fuzzies !== [] ) {
297 foreach ( $fuzzies as $otherTitle ) {
299 'rt_type' => RevTagStore::FUZZY_TAG,
300 'rt_page' => $otherTitle->getId(),
301 'rt_revision' => $otherTitle->getLatestRevID(),
304 $dbw->newReplaceQueryBuilder()
305 ->replaceInto(
'revtag' )
306 ->uniqueIndexFields( [
'rt_type',
'rt_page',
'rt_revision' ] )
308 ->caller( __METHOD__ )
311 if ( $unfuzzies !== [] ) {
312 foreach ( $unfuzzies as $otherTitle ) {
313 $dbw->newDeleteQueryBuilder()
314 ->deleteFrom(
'revtag' )
316 'rt_type' => RevTagStore::FUZZY_TAG,
317 'rt_page' => $otherTitle->getId(),
318 'rt_revision' => $otherTitle->getLatestRevID(),
320 ->caller( __METHOD__ )
327 private function processTranslationChanges(
332 foreach ( $langChanges as $code => $contentStr ) {
333 $titleStr = Utilities::title( $baseTitle, $code, $groupNamespace );
334 $title = Title::newFromText( $titleStr, $groupNamespace );
335 $updater = $this->fuzzyBotEdit( $title, $contentStr );
337 if ( !$updater->getStatus()->isOK() ) {
339 'Failed to update content for non-source message',
341 'title' => $title->getPrefixedText(),
342 'errors' => $updater->getStatus()->getMessages()
349 private function removeFromCache( Title $title ): void {
350 $config = MediaWikiServices::getInstance()->getMainConfig();
352 if ( !$config->get(
'TranslateGroupSynchronizationCache' ) ) {
356 $currentTitle = $title;
359 if ( $this->title && $this->title->getPrefixedDBkey() !== $title->getPrefixedDBkey() ) {
360 $currentTitle = $this->title;
363 $sourceMessageHandle =
new MessageHandle( $currentTitle );
364 $groupIds = $sourceMessageHandle->getGroupIds();
367 "Could not find group Id for message title: {$currentTitle->getPrefixedDBkey()}",
373 $groupId = $groupIds[0];
374 $group = MessageGroups::getGroup( $groupId );
380 $groupSyncCache = Services::getInstance()->getGroupSynchronizationCache();
381 $messageKey = $title->getPrefixedDBkey();
383 if ( $groupSyncCache->isMessageBeingProcessed( $groupId, $messageKey ) ) {
384 $groupSyncCache->removeMessages( $groupId, $messageKey );
385 $groupSyncCache->extendGroupExpiryTime( $groupId );
388 "Did not find key: $messageKey; in group: $groupId in group sync cache",
394 private function fuzzyBotEdit( Title $title,
string $content ): PageUpdater {
395 $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
396 $content = ContentHandler::makeContent( $content, $title );
397 $page = $wikiPageFactory->newFromTitle( $title );
398 $updater = $page->newPageUpdater( $this->getFuzzyBot() )
399 ->setContent( SlotRecord::MAIN, $content );
401 if ( $this->getFuzzyBot()->authorizeWrite(
'autopatrol', $title ) ) {
402 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
405 $summary = wfMessage(
'translate-manage-import-summary' )
406 ->inContentLanguage()->plain();
407 $updater->saveRevision(
408 CommentStoreComment::newUnsavedComment( $summary ),
414 private function getFuzzyBot(): User {
415 $this->fuzzyBot ??= FuzzyBot::getUser();
416 return $this->fuzzyBot;