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 $updater = $this->fuzzyBotEdit( $title, $params[
'content'] );
107 if ( !$updater->getStatus()->isOK() ) {
109 'Failed to update content for source message',
111 'content' => ContentHandler::makeContent( $params[
'content'], $this->title ),
112 'errors' => $updater->getStatus()->getMessages()
119 $this->processTranslationChanges( $otherLangs, $params[
'replacement'], $params[
'namespace'] );
122 $this->handleFuzzy( $title, $isFuzzy, $updater );
124 $this->removeFromCache( $originalTitle );
128 private function handleRename(
string $target,
string $replacement ): ?Title {
129 $newSourceTitle = null;
131 $sourceMessageHandle =
new MessageHandle( $this->title );
132 $movableTitles = TranslateReplaceTitle::getTitlesForMove( $sourceMessageHandle, $replacement );
134 if ( $movableTitles === [] ) {
136 'No movable titles found with target text.',
138 'title' => $this->title->getPrefixedText(),
139 'replacement' => $replacement,
146 $renameSummary = wfMessage(
'translate-manage-import-rename-summary' )
147 ->inContentLanguage()->plain();
149 foreach ( $movableTitles as [ $sourceTitle, $replacementTitle ] ) {
150 $mv = MediaWikiServices::getInstance()
151 ->getMovePageFactory()
152 ->newMovePage( $sourceTitle, $replacementTitle );
154 $status = $mv->move( $this->getFuzzyBot(), $renameSummary,
false );
155 if ( !$status->isOK() ) {
157 'Error moving message',
159 'target' => $sourceTitle->getPrefixedText(),
160 'replacement' => $replacementTitle->getPrefixedText(),
161 'errors' => $status->getMessages()
166 [ , $targetCode ] = Utilities::figureMessage( $replacementTitle->getText() );
167 if ( !$newSourceTitle && $sourceMessageHandle->getCode() === $targetCode ) {
168 $newSourceTitle = $replacementTitle;
172 if ( $newSourceTitle ) {
173 return $newSourceTitle;
178 'Source title was not in the list of movable titles.',
179 [
'title' => $this->title->getPrefixedText() ]
195 private function handleFuzzy( Title $title,
bool $invalidate, PageUpdater $updater ): void {
196 global $wgTranslateDocumentationLanguageCode;
197 $editResult = $updater->getEditResult();
198 if ( !$invalidate && !$editResult->isExactRevert() ) {
201 $oldRevId = $editResult->getOriginalRevisionId();
202 $handle =
new MessageHandle( $title );
204 $languages = Utilities::getLanguageNames(
'en' );
207 unset( $languages[$wgTranslateDocumentationLanguageCode] );
208 unset( $languages[$handle->getCode()] );
210 $languages = array_keys( $languages );
214 $mwInstance = MediaWikiServices::getInstance();
215 $revTagStore = Services::getInstance()->getRevTagStore();
216 $revStore = $mwInstance->getRevisionStore();
218 if ( $oldRevId || $invalidate ) {
221 $batch = $mwInstance->getLinkBatchFactory()->newLinkBatch();
222 $batch->setCaller( __METHOD__ );
223 foreach ( $languages as $code ) {
224 $batch->addObj( $handle->getTitleForLanguage( $code ) );
228 $targetSha = $updater->getNewRevision()->getSha1();
230 foreach ( $languages as $code ) {
231 $otherTitle = $handle->getTitleForLanguage( $code );
232 $shouldUnfuzzy =
false;
233 if ( $oldRevId && $otherTitle->exists() ) {
234 $transver = $revTagStore->getTransver( $otherTitle );
235 if ( $oldRevId == $transver ) {
237 $shouldUnfuzzy =
true;
238 } elseif ( $transver ) {
239 $transverSha = $revStore->getRevisionById( $transver, 0, $title )->getSha1();
240 if ( $transverSha == $targetSha ) {
242 $shouldUnfuzzy =
true;
246 if ( $shouldUnfuzzy ) {
249 $otherHandle =
new MessageHandle( $otherTitle );
250 $wikiPage = $mwInstance->getWikiPageFactory()->newFromTitle( $otherTitle );
251 $content = $wikiPage->getContent();
252 if ( !$content instanceof TextContent ) {
257 $text = $content->getText();
258 if ( $otherHandle->isFuzzy() && !$otherHandle->needsFuzzy( $text ) ) {
259 $unfuzzies[] = $otherTitle;
264 } elseif ( $invalidate ) {
265 $fuzzies[] = $otherTitle;
269 $dbw = $mwInstance->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
271 if ( $fuzzies !== [] ) {
273 foreach ( $fuzzies as $otherTitle ) {
275 'rt_type' => RevTagStore::FUZZY_TAG,
276 'rt_page' => $otherTitle->getId(),
277 'rt_revision' => $otherTitle->getLatestRevID(),
282 [ [
'rt_type',
'rt_page',
'rt_revision' ] ],
287 if ( $unfuzzies !== [] ) {
288 foreach ( $unfuzzies as $otherTitle ) {
289 $dbw->delete(
'revtag', [
290 'rt_type' => RevTagStore::FUZZY_TAG,
291 'rt_page' => $otherTitle->getId(),
292 'rt_revision' => $otherTitle->getLatestRevID(),
299 private function processTranslationChanges(
304 foreach ( $langChanges as $code => $contentStr ) {
305 $titleStr = Utilities::title( $baseTitle, $code, $groupNamespace );
306 $title = Title::newFromText( $titleStr, $groupNamespace );
307 $updater = $this->fuzzyBotEdit( $title, $contentStr );
309 if ( !$updater->getStatus()->isOK() ) {
311 'Failed to update content for non-source message',
313 'title' => $title->getPrefixedText(),
314 'errors' => $updater->getStatus()->getMessages()
321 private function removeFromCache( Title $title ): void {
322 $config = MediaWikiServices::getInstance()->getMainConfig();
324 if ( !$config->get(
'TranslateGroupSynchronizationCache' ) ) {
328 $currentTitle = $title;
331 if ( $this->title && $this->title->getPrefixedDBkey() !== $title->getPrefixedDBkey() ) {
332 $currentTitle = $this->title;
335 $sourceMessageHandle =
new MessageHandle( $currentTitle );
336 $groupIds = $sourceMessageHandle->getGroupIds();
339 "Could not find group Id for message title: {$currentTitle->getPrefixedDBkey()}",
345 $groupId = $groupIds[0];
346 $group = MessageGroups::getGroup( $groupId );
352 $groupSyncCache = Services::getInstance()->getGroupSynchronizationCache();
353 $messageKey = $title->getPrefixedDBkey();
355 if ( $groupSyncCache->isMessageBeingProcessed( $groupId, $messageKey ) ) {
356 $groupSyncCache->removeMessages( $groupId, $messageKey );
357 $groupSyncCache->extendGroupExpiryTime( $groupId );
360 "Did not find key: $messageKey; in group: $groupId in group sync cache",
366 private function fuzzyBotEdit( Title $title,
string $content ): PageUpdater {
367 $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
368 $content = ContentHandler::makeContent( $content, $title );
369 $page = $wikiPageFactory->newFromTitle( $title );
370 $updater = $page->newPageUpdater( $this->getFuzzyBot() )
371 ->setContent( SlotRecord::MAIN, $content );
373 if ( $this->getFuzzyBot()->authorizeWrite(
'autopatrol', $title ) ) {
374 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
377 $summary = wfMessage(
'translate-manage-import-summary' )
378 ->inContentLanguage()->plain();
379 $updater->saveRevision(
380 CommentStoreComment::newUnsavedComment( $summary ),
386 private function getFuzzyBot(): User {
387 $this->fuzzyBot ??= FuzzyBot::getUser();
388 return $this->fuzzyBot;