diff options
author | Brian Evans <grknight@gentoo.org> | 2020-10-02 15:24:06 -0400 |
---|---|---|
committer | Brian Evans <grknight@gentoo.org> | 2020-10-02 15:24:06 -0400 |
commit | 60dd5fd95847643eab04ce173f0774c9c584e795 (patch) | |
tree | 52299ac4e3c5c69df75997bfd7d62b71ef9e0089 /MLEB/Translate/specials/SpecialManageGroups.php | |
parent | Update Widgets to 1.35 (diff) | |
download | extensions-60dd5fd95847643eab04ce173f0774c9c584e795.tar.gz extensions-60dd5fd95847643eab04ce173f0774c9c584e795.tar.bz2 extensions-60dd5fd95847643eab04ce173f0774c9c584e795.zip |
Update MLEB to 2020.07
Signed-off-by: Brian Evans <grknight@gentoo.org>
Diffstat (limited to 'MLEB/Translate/specials/SpecialManageGroups.php')
-rw-r--r-- | MLEB/Translate/specials/SpecialManageGroups.php | 642 |
1 files changed, 554 insertions, 88 deletions
diff --git a/MLEB/Translate/specials/SpecialManageGroups.php b/MLEB/Translate/specials/SpecialManageGroups.php index eec1d4be..71f28021 100644 --- a/MLEB/Translate/specials/SpecialManageGroups.php +++ b/MLEB/Translate/specials/SpecialManageGroups.php @@ -9,6 +9,10 @@ * @license GPL-2.0-or-later */ +use MediaWiki\Extensions\Translate\MessageSync\MessageSourceChange; +use MediaWiki\MediaWikiServices; +use MediaWiki\Revision\SlotRecord; + /** * Class for special page Special:ManageMessageGroups. On this special page * file based message groups can be managed (FileBasedMessageGroup). This page @@ -19,7 +23,7 @@ * Rewritten in 2012-04-23 */ class SpecialManageGroups extends SpecialPage { - const RIGHT = 'translate-manage'; + private const RIGHT = 'translate-manage'; /** * @var DifferenceEngine @@ -31,6 +35,11 @@ class SpecialManageGroups extends SpecialPage { */ protected $cdb; + /** + * @var bool Has the necessary right specified by the RIGHT constant + */ + protected $hasRight = false; + public function __construct() { // Anyone is allowed to see, but actions are restricted parent::__construct( 'ManageMessageGroups' ); @@ -41,7 +50,7 @@ class SpecialManageGroups extends SpecialPage { } protected function getGroupName() { - return 'wiki'; + return 'translation'; } public function getDescription() { @@ -50,8 +59,10 @@ class SpecialManageGroups extends SpecialPage { public function execute( $par ) { $this->setHeaders(); + $out = $this->getOutput(); - $out->addModuleStyles( 'ext.translate.special.managegroups' ); + $out->addModuleStyles( 'ext.translate.special.managegroups.styles' ); + $out->addModules( 'ext.translate.special.managegroups' ); $out->addHelpLink( 'Help:Extension:Translate/Group_management' ); $name = $par ?: MessageChangeStorage::DEFAULT_NAME; @@ -66,17 +77,17 @@ class SpecialManageGroups extends SpecialPage { } $user = $this->getUser(); - $allowed = $user->isAllowed( self::RIGHT ); + $this->hasRight = $user->isAllowed( self::RIGHT ); $req = $this->getRequest(); if ( !$req->wasPosted() ) { - $this->showChanges( $allowed, $this->getLimit() ); + $this->showChanges( $this->getLimit() ); return; } $token = $req->getVal( 'token' ); - if ( !$allowed || !$user->matchEditToken( $token ) ) { + if ( !$this->hasRight || !$user->matchEditToken( $token ) ) { throw new PermissionsError( self::RIGHT ); } @@ -109,8 +120,8 @@ class SpecialManageGroups extends SpecialPage { return Html::rawElement( 'div', [ 'class' => 'mw-translate-smg-header' ], $text ); } - protected function showChanges( $allowed, $limit ) { - global $wgContLang; + protected function showChanges( $limit ) { + $contLang = MediaWikiServices::getInstance()->getContentLanguage(); $diff = new DifferenceEngine( $this->getContext() ); $diff->showDiffStyle(); @@ -120,48 +131,58 @@ class SpecialManageGroups extends SpecialPage { $out = $this->getOutput(); $out->addHTML( '' . - Html::openElement( 'form', [ 'method' => 'post' ] ) . - Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . - Html::hidden( 'token', $this->getUser()->getEditToken() ) . - $this->getLegend() + Html::openElement( 'form', [ 'method' => 'post' ] ) . + Html::hidden( 'title', $this->getPageTitle()->getPrefixedText(), [ + 'id' => 'smgPageTitle' + ] ) . + Html::hidden( 'token', $this->getUser()->getEditToken() ) . + Html::hidden( 'changesetModifiedTime', + MessageChangeStorage::getLastModifiedTime( $this->cdb ) ) . + $this->getLegend() ); - // The above count as two - $limit = $limit - 2; + // The above count as three + $limit = $limit - 3; $reader = \Cdb\Reader::open( $this->cdb ); - $groups = unserialize( $reader->get( '#keys' ) ); - foreach ( $groups as $id ) { - $group = MessageGroups::getGroup( $id ); - if ( !$group ) { - continue; - } - - $changes = unserialize( $reader->get( $id ) ); + $groups = $this->getGroupsFromCdb( $reader ); + foreach ( $groups as $id => $group ) { + $sourceChanges = MessageSourceChange::loadModifications( + TranslateUtils::deserialize( $reader->get( $id ) ) + ); $out->addHTML( Html::element( 'h2', [], $group->getLabel() ) ); // Reduce page existance queries to one per group $lb = new LinkBatch(); $ns = $group->getNamespace(); $isCap = MWNamespace::isCapitalized( $ns ); - foreach ( $changes as $code => $subchanges ) { - foreach ( $subchanges as $messages ) { - foreach ( $messages as $params ) { + $languages = $sourceChanges->getLanguages(); + + foreach ( $languages as $language ) { + $languageChanges = $sourceChanges->getModificationsForLanguage( $language ); + foreach ( $languageChanges as $type => $changes ) { + foreach ( $changes as $params ) { // Constructing title objects is way slower $key = $params['key']; if ( $isCap ) { - $key = $wgContLang->ucfirst( $key ); + $key = $contLang->ucfirst( $key ); } - $lb->add( $ns, "$key/$code" ); + $lb->add( $ns, "$key/$language" ); } } } $lb->execute(); - foreach ( $changes as $code => $subchanges ) { - foreach ( $subchanges as $type => $messages ) { + foreach ( $languages as $language ) { + // Handle and generate UI for additions, deletions, change + $changes = []; + $changes[ MessageSourceChange::ADDITION ] = $sourceChanges->getAdditions( $language ); + $changes[ MessageSourceChange::DELETION ] = $sourceChanges->getDeletions( $language ); + $changes[ MessageSourceChange::CHANGE ] = $sourceChanges->getChanges( $language ); + + foreach ( $changes as $type => $messages ) { foreach ( $messages as $params ) { - $change = $this->formatChange( $group, $code, $type, $params, $limit ); + $change = $this->formatChange( $group, $language, $type, $params, $limit ); $out->addHTML( $change ); if ( $limit <= 0 ) { @@ -172,11 +193,14 @@ class SpecialManageGroups extends SpecialPage { } } } + + // Handle and generate UI for renames + $this->showRenames( $group, $sourceChanges, $out, $language, $limit ); } } $attribs = [ 'type' => 'submit', 'class' => 'mw-translate-smg-submit' ]; - if ( !$allowed ) { + if ( !$this->hasRight ) { $attribs['disabled'] = 'disabled'; $attribs['title'] = $this->msg( 'translate-smg-notallowed' )->text(); } @@ -187,16 +211,16 @@ class SpecialManageGroups extends SpecialPage { /** * @param MessageGroup $group - * @param string $code + * @param string $language * @param string $type * @param array $params * @param int &$limit * @return string HTML */ - protected function formatChange( MessageGroup $group, $code, $type, $params, &$limit ) { + protected function formatChange( MessageGroup $group, $language, $type, $params, &$limit ) { $key = $params['key']; - $title = Title::makeTitleSafe( $group->getNamespace(), "$key/$code" ); - $id = self::changeId( $group->getId(), $code, $type, $key ); + $title = Title::makeTitleSafe( $group->getNamespace(), "$key/$language" ); + $id = self::changeId( $group->getId(), $language, $type, $key ); if ( $title && $type === 'addition' && $title->exists() ) { // The message has for some reason dropped out from cache @@ -208,6 +232,8 @@ class SpecialManageGroups extends SpecialPage { // leads to many other annoying problems. $type = 'change'; } elseif ( $title && ( $type === 'deletion' || $type === 'change' ) && !$title->exists() ) { + // This happens if a message key has been renamed + // The change can be ignored. return ''; } @@ -215,7 +241,12 @@ class SpecialManageGroups extends SpecialPage { $titleLink = $this->getLinkRenderer()->makeLink( $title ); if ( $type === 'deletion' ) { - $wiki = ContentHandler::getContentText( Revision::newFromTitle( $title )->getContent() ); + $wiki = ContentHandler::getContentText( + MediaWikiServices::getInstance() + ->getRevisionLookup() + ->getRevisionByTitle( $title ) + ->getContent( SlotRecord::MAIN ) + ); $oldContent = ContentHandler::makeContent( $wiki, $title ); $newContent = ContentHandler::makeContent( '', $title ); @@ -227,26 +258,32 @@ class SpecialManageGroups extends SpecialPage { $newContent = ContentHandler::makeContent( $params['content'], $title ); $this->diff->setContent( $oldContent, $newContent ); - - $text = $this->diff->getDiff( '', $titleLink ); + $menu = ''; + if ( $group->getSourceLanguage() === $language && $this->hasRight ) { + $menu = Html::rawElement( 'button', [ + 'class' => 'smg-rename-actions', 'type' => 'button', + 'data-group-id' => $group->getId(), 'data-lang' => $language, 'data-msgkey' => $key, + 'data-msgtitle' => $title->getFullText() ], '' ); + } + $text = $this->diff->getDiff( '', $titleLink . $menu ); } elseif ( $type === 'change' ) { - $wiki = ContentHandler::getContentText( Revision::newFromTitle( $title )->getContent() ); + $wiki = TranslateUtils::getContentForTitle( $title, true ); - $handle = new MessageHandle( $title ); - if ( $handle->isFuzzy() ) { - $wiki = '!!FUZZY!!' . str_replace( TRANSLATE_FUZZY, '', $wiki ); + $actions = ''; + $importSelected = true; + if ( $group->getSourceLanguage() === $language ) { + $importSelected = false; + $label = $this->msg( 'translate-manage-action-fuzzy' )->text(); + $actions .= Xml::radioLabel( $label, "msg/$id", "fuzzy", "f/$id", true ); } + $label = $this->msg( 'translate-manage-action-import' )->text(); + $actions .= Xml::radioLabel( $label, "msg/$id", "import", "imp/$id", $importSelected ); + $label = $this->msg( 'translate-manage-action-ignore' )->text(); - $actions = Xml::checkLabel( $label, "i/$id", "i/$id" ); + $actions .= Xml::radioLabel( $label, "msg/$id", "ignore", "i/$id" ); $limit--; - if ( $group->getSourceLanguage() === $code ) { - $label = $this->msg( 'translate-manage-action-fuzzy' )->text(); - $actions .= ' ' . Xml::checkLabel( $label, "f/$id", "f/$id", true ); - $limit--; - } - $oldContent = ContentHandler::makeContent( $wiki, $title ); $newContent = ContentHandler::makeContent( $params['content'], $title ); @@ -270,62 +307,85 @@ class SpecialManageGroups extends SpecialPage { protected function processSubmit() { $req = $this->getRequest(); $out = $this->getOutput(); + $errorGroups = []; - $jobs = []; - $jobs[] = MessageIndexRebuildJob::newJob(); + $modificationJobs = $renameJobData = []; + $lastModifiedTime = intval( $req->getVal( 'changesetModifiedTime' ) ); - $reader = \Cdb\Reader::open( $this->cdb ); - $groups = unserialize( $reader->get( '#keys' ) ); + if ( !MessageChangeStorage::isModifiedSince( $this->cdb, $lastModifiedTime ) ) { + $out->addWikiMsg( 'translate-smg-changeset-modified' ); + return; + } + $reader = \Cdb\Reader::open( $this->cdb ); + $groups = $this->getGroupsFromCdb( $reader ); $postponed = []; - foreach ( $groups as $groupId ) { - $group = MessageGroups::getGroup( $groupId ); - $changes = unserialize( $reader->get( $groupId ) ); - - foreach ( $changes as $code => $subchanges ) { - foreach ( $subchanges as $type => $messages ) { - foreach ( $messages as $index => $params ) { - $id = self::changeId( $groupId, $code, $type, $params['key'] ); - if ( $req->getVal( $id ) === null ) { - // We probably hit the limit with number of post parameters. - $postponed[$groupId][$code][$type][$index] = $params; - continue; - } - - if ( $type === 'deletion' || $req->getCheck( "i/$id" ) ) { - continue; - } - - $fuzzy = $req->getCheck( "f/$id" ) ? 'fuzzy' : false; - $key = $params['key']; - $title = Title::makeTitleSafe( $group->getNamespace(), "$key/$code" ); - $jobs[] = MessageUpdateJob::newJob( $title, $params['content'], $fuzzy ); + foreach ( $groups as $groupId => $group ) { + try { + $sourceChanges = MessageSourceChange::loadModifications( + TranslateUtils::deserialize( $reader->get( $groupId ) ) + ); + $groupModificationJobs = []; + $groupRenameJobData = []; + $languages = $sourceChanges->getLanguages(); + foreach ( $languages as $language ) { + // Handle changes, additions, deletions + $this->handleModificationsSubmit( $group, $sourceChanges, $req, + $language, $postponed, $groupModificationJobs ); + + // Handle renames, this might also add modification jobs based on user selection. + $this->handleRenameSubmit( $group, $sourceChanges, $req, $language, + $postponed, $groupRenameJobData, $groupModificationJobs ); + + if ( !isset( $postponed[$groupId][$language] ) ) { + $group->getMessageGroupCache( $language )->create(); } } - if ( !isset( $postponed[$groupId][$code] ) ) { - $cache = new MessageGroupCache( $groupId, $code ); - $cache->create(); - } + $modificationJobs = array_merge( $modificationJobs, $groupModificationJobs ); + $renameJobData = array_merge( $renameJobData, $groupRenameJobData ); + } catch ( Exception $e ) { + error_log( + "SpecialManageGroups: Error in processSubmit. Group: $groupId\n" . + "Exception: $e" + ); + + $errorGroups[] = $group->getLabel(); } } - JobQueueGroup::singleton()->push( $jobs ); + JobQueueGroup::singleton()->push( MessageIndexRebuildJob::newJob() ); + JobQueueGroup::singleton()->push( $modificationJobs ); + JobQueueGroup::singleton()->push( $this->createRenameJobs( $renameJobData ) ); $reader->close(); rename( $this->cdb, $this->cdb . '-' . wfTimestamp() ); + if ( $errorGroups ) { + $errorMsg = $this->getProcessingErrorMessage( $errorGroups, count( $groups ) ); + $out->addElement( + 'p', + [ 'class' => 'warningbox mw-translate-smg-submitted' ], + $errorMsg + ); + } + if ( count( $postponed ) ) { - MessageChangeStorage::writeChanges( $postponed, $this->cdb ); - $this->showChanges( true, $this->getLimit() ); - } else { + $postponedSourceChanges = []; + foreach ( $postponed as $groupId => $changes ) { + $postponedSourceChanges[$groupId] = MessageSourceChange::loadModifications( $changes ); + } + MessageChangeStorage::writeChanges( $postponedSourceChanges, $this->cdb ); + + $this->showChanges( $this->getLimit() ); + } elseif ( $errorGroups === [] ) { $out->addWikiMsg( 'translate-smg-submitted' ); } } - protected static function changeId( $groupId, $code, $type, $key ) { - return 'smg/' . substr( sha1( "$groupId/$code/$type/$key" ), 0, 7 ); + protected static function changeId( $groupId, $language, $type, $key ) { + return 'smg/' . substr( sha1( "$groupId/$language/$type/$key" ), 0, 7 ); } /** @@ -338,7 +398,8 @@ class SpecialManageGroups extends SpecialPage { */ public static function tabify( Skin $skin, array &$tabs ) { $title = $skin->getTitle(); - list( $alias, ) = TranslateUtils::resolveSpecialPageAlias( $title->getText() ); + $specialPageFactory = MediaWikiServices::getInstance()->getSpecialPageFactory(); + [ $alias, ] = $specialPageFactory->resolveAlias( $title->getText() ); $pagesInGroup = [ 'ManageMessageGroups' => 'namespaces', @@ -354,8 +415,10 @@ class SpecialManageGroups extends SpecialPage { $tabs['namespaces'] = []; foreach ( $pagesInGroup as $spName => $section ) { - $spClass = TranslateUtils::getSpecialPage( $spName ); - if ( $spClass === null ) { + $spClass = $specialPageFactory->getPage( $spName ); + + // DisabledSpecialPage was added in MW 1.33 + if ( $spClass === null || $spClass instanceof DisabledSpecialPage ) { continue; // Page explicitly disabled } $spTitle = $spClass->getPageTitle(); @@ -369,4 +432,407 @@ class SpecialManageGroups extends SpecialPage { return true; } + + /** + * Displays renames + * @param MessageGroup $group + * @param MessageSourceChange $sourceChanges + * @param OutputPage $out + * @param string $language + * @param int &$limit + */ + protected function showRenames( + MessageGroup $group, MessageSourceChange $sourceChanges, OutputPage $out, $language, &$limit + ) { + $changes = $sourceChanges->getRenames( $language ); + foreach ( $changes as $key => $params ) { + if ( !isset( $changes[$key] ) ) { + continue; + } + + if ( $group->getSourceLanguage() !== $language && + $sourceChanges->isEqual( $language, $key ) ) { + // This is a translation rename, that does not have any changes. + // We can group this along with the source rename. + continue; + } + + // Determine added key, and corresponding removed key. + $firstMsg = $params; + $secondKey = $sourceChanges->getMatchedKey( $language, $key ); + $secondMsg = $sourceChanges->getMatchedMessage( $language, $key ); + + if ( $sourceChanges->isPreviousState( + $language, $key, [ MessageSourceChange::ADDITION, MessageSourceChange::CHANGE ] + ) ) { + $addedMsg = $firstMsg; + $deletedMsg = $secondMsg; + } else { + $addedMsg = $secondMsg; + $deletedMsg = $firstMsg; + } + + $change = $this->formatRename( + $group, + $addedMsg, + $deletedMsg, + $language, + $sourceChanges->isEqual( $language, $key ), + $limit + ); + $out->addHTML( $change ); + + // no need to process the second key again. + unset( $changes[$secondKey] ); + + if ( $limit <= 0 ) { + // We need to restrict the changes per page per form submission + // limitations as well as performance. + $out->wrapWikiMsg( "<div class=warning>\n$1\n</div>", 'translate-smg-more' ); + break; + } + } + } + + /** + * @param MessageGroup $group + * @param array $addedMsg + * @param array $deletedMsg + * @param string $language + * @param bool $isEqual Are the renamed messages equal + * @param int &$limit + * @return string HTML + */ + protected function formatRename( + MessageGroup $group, $addedMsg, $deletedMsg, $language, $isEqual, &$limit + ) { + $addedKey = $addedMsg['key']; + $deletedKey = $deletedMsg['key']; + $actions = ''; + + $addedTitle = Title::makeTitleSafe( $group->getNamespace(), "$addedKey/$language" ); + $deletedTitle = Title::makeTitleSafe( $group->getNamespace(), "$deletedKey/$language" ); + $id = self::changeId( $group->getId(), $language, MessageSourceChange::RENAME, $addedKey ); + + $addedTitleLink = $this->getLinkRenderer()->makeLink( $addedTitle ); + $deletedTitleLink = $this->getLinkRenderer()->makeLink( $deletedTitle ); + + $renameSelected = true; + if ( $group->getSourceLanguage() === $language ) { + if ( !$isEqual ) { + $renameSelected = false; + $label = $this->msg( 'translate-manage-action-rename-fuzzy' )->text(); + $actions .= Xml::radioLabel( $label, "msg/$id", "renamefuzzy", "rf/$id", true ); + } + + $label = $this->msg( 'translate-manage-action-rename' )->text(); + $actions .= Xml::radioLabel( $label, "msg/$id", "rename", "imp/$id", $renameSelected ); + } else { + $label = $this->msg( 'translate-manage-action-import' )->text(); + $actions .= Xml::radioLabel( $label, "msg/$id", "import", "imp/$id", true ); + } + + if ( $group->getSourceLanguage() !== $language ) { + // Allow user to ignore changes to non-source languages. + $label = $this->msg( 'translate-manage-action-ignore-change' )->text(); + $actions .= Xml::radioLabel( $label, "msg/$id", "ignore", "i/$id" ); + } + $limit--; + + $addedContent = ContentHandler::makeContent( $addedMsg['content'], $addedTitle ); + $deletedContent = ContentHandler::makeContent( $deletedMsg['content'], $deletedTitle ); + $this->diff->setContent( $deletedContent, $addedContent ); + + $menu = ''; + if ( $group->getSourceLanguage() === $language && $this->hasRight ) { + // Only show rename and add as new option for source language. + $menu = Html::rawElement( 'button', [ + 'class' => 'smg-rename-actions', 'type' => 'button', + 'data-group-id' => $group->getId(), 'data-msgkey' => $addedKey, + 'data-msgtitle' => $addedTitle->getFullText() + ], '' ); + } + + $actions = Html::rawElement( 'div', [ 'class' => 'smg-change-import-options' ], $actions ); + + $text = $this->diff->getDiff( + $deletedTitleLink, + $addedTitleLink . $menu . $actions, + $isEqual ? htmlspecialchars( $addedMsg['content'] ) : '' + ); + + $hidden = Html::hidden( $id, 1 ); + $limit--; + $text .= $hidden; + + return Html::rawElement( 'div', + [ 'class' => 'mw-translate-smg-change smg-change-rename' ], $text ); + } + + /** + * @param array $currentMsg + * @param MessageSourceChange $sourceChanges + * @param string $languageCode + * @param int $groupNamespace + * @param string $selectedVal + * @param bool $isSourceLang + * @return ?array + */ + protected function getRenameJobParams( + $currentMsg, MessageSourceChange $sourceChanges, $languageCode, + $groupNamespace, $selectedVal, $isSourceLang = true + ) { + if ( $selectedVal === 'ignore' ) { + return null; + } + + $params = []; + $replacementContent = ''; + $currentMsgKey = $currentMsg['key']; + $matchedMsg = $sourceChanges->getMatchedMessage( $languageCode, $currentMsgKey ); + $matchedMsgKey = $matchedMsg['key']; + + if ( $sourceChanges->isPreviousState( $languageCode, $currentMsgKey, [ + MessageSourceChange::ADDITION, MessageSourceChange::CHANGE + ] ) ) { + $params['target'] = $matchedMsgKey; + $params['replacement'] = $currentMsgKey; + $replacementContent = $currentMsg['content']; + } else { + $params['target'] = $currentMsgKey; + $params['replacement'] = $matchedMsgKey; + $replacementContent = $matchedMsg['content']; + } + + if ( $selectedVal === 'renamefuzzy' ) { + $params['fuzzy'] = 'fuzzy'; + } else { + $params['fuzzy'] = false; + } + + $params['content'] = $replacementContent; + + if ( $isSourceLang ) { + $params['targetTitle'] = Title::newFromText( + TranslateUtils::title( $params['target'], $languageCode, $groupNamespace ), + $groupNamespace + ); + $params['others'] = []; + } + + return $params; + } + + protected function handleRenameSubmit( MessageGroup $group, MessageSourceChange $sourceChanges, + WebRequest $req, $language, &$postponed, &$jobData, &$modificationJobs + ) { + $groupId = $group->getId(); + $renames = $sourceChanges->getRenames( $language ); + $isSourceLang = $group->getSourceLanguage() === $language; + $groupNamespace = $group->getNamespace(); + + foreach ( $renames as $key => $params ) { + if ( !isset( $renames[ $key] ) ) { + continue; + } + + $id = self::changeId( $groupId, $language, MessageSourceChange::RENAME, $key ); + + [ $renameMissing, $isCurrentKeyPresent ] = $this->isRenameMissing( + $req, $sourceChanges, $id, $key, $language, $groupId, $isSourceLang + ); + + if ( $renameMissing ) { + // we probably hit the limit with number of post parameters since neither + // addition or deletion key is present. + $postponed[$groupId][$language][MessageSourceChange::RENAME][$key] = $params; + continue; + } + + if ( !$isCurrentKeyPresent ) { + // still don't process this key, and wait for the matched rename + continue; + } + + $selectedVal = $req->getVal( "msg/$id" ); + $jobParams = $this->getRenameJobParams( + $params, $sourceChanges, $language, $groupNamespace, $selectedVal, $isSourceLang + ); + + if ( $jobParams === null ) { + continue; + } + + $targetStr = $jobParams[ 'target' ]; + if ( $isSourceLang ) { + $jobData[ $targetStr ] = $jobParams; + } elseif ( isset( $jobData[ $targetStr ] ) ) { + // We are grouping the source rename, and content changes in other languages + // for the message together into a single job in order to avoid race conditions + // since jobs are not guaranteed to be run in order. + $jobData[ $targetStr ][ 'others' ][ $language ] = $jobParams[ 'content' ]; + } else { + // the source was probably ignored, we should add this as a modification instead, + // since the source is not going to be renamed. + $title = Title::newFromText( + TranslateUtils::title( $targetStr, $language, $groupNamespace ), + $groupNamespace + ); + $modificationJobs[] = MessageUpdateJob::newJob( $title, $jobParams['content'] ); + } + + // remove the matched key in order to avoid double processing. + $matchedKey = $sourceChanges->getMatchedKey( $language, $key ); + unset( $renames[$matchedKey] ); + } + } + + protected function handleModificationsSubmit( + MessageGroup $group, MessageSourceChange $sourceChanges, WebRequest $req, + $language, &$postponed, &$messageUpdateJob + ) { + $groupId = $group->getId(); + $subchanges = $sourceChanges->getModificationsForLanguage( $language ); + + // Ignore renames + unset( $subchanges[ MessageSourceChange::RENAME ] ); + + // Handle additions, deletions, and changes. + foreach ( $subchanges as $type => $messages ) { + foreach ( $messages as $index => $params ) { + $key = $params['key']; + $id = self::changeId( $groupId, $language, $type, $key ); + $title = Title::makeTitleSafe( $group->getNamespace(), "$key/$language" ); + + if ( !$this->isTitlePresent( $title, $type ) ) { + continue; + } + + if ( !$req->getCheck( $id ) ) { + // We probably hit the limit with number of post parameters. + $postponed[$groupId][$language][$type][$index] = $params; + continue; + } + + $selectedVal = $req->getVal( "msg/$id" ); + if ( $type === MessageSourceChange::DELETION || $selectedVal === 'ignore' ) { + continue; + } + + $fuzzy = $selectedVal === 'fuzzy' ? 'fuzzy' : false; + $messageUpdateJob[] = MessageUpdateJob::newJob( $title, $params['content'], $fuzzy ); + } + } + } + + protected function createRenameJobs( $jobParams ) { + $jobs = []; + foreach ( $jobParams as $params ) { + $jobs[] = MessageUpdateJob::newRenameJob( + $params['targetTitle'], $params['target'], + $params['replacement'], $params['fuzzy'], $params['content'], + $params['others'] + ); + } + + return $jobs; + } + + /** + * Checks if a title still exists and can be processed. + * + * @param Title $title + * @param string $type + * @return bool + */ + protected function isTitlePresent( Title $title, $type ) { + if ( ( $type === MessageSourceChange::DELETION || $type === MessageSourceChange::CHANGE ) && + !$title->exists() + ) { + // This means that this change was probably introduced due to a rename + // which removed the key. No need to process. + return false; + } + return true; + } + + /** + * Checks if a renamed message key is missing from the user request submission. + * Checks the current key and the matched key. This is needed because as the + * keys in the wiki are not submitted along with the request, only the incoming + * modified keys are submitted. + * @param WebRequest $req + * @param MessageSourceChange $sourceChanges + * @param string $id + * @param string $key + * @param string $language + * @param string $groupId + * @param bool $isSourceLang + * @return bool[] $response + * $response = [ + * 0 => (bool) True if rename is missing, false otherwise. + * 1 => (bool) Was the current $id found? + * ] + */ + protected function isRenameMissing( + WebRequest $req, MessageSourceChange $sourceChanges, $id, $key, + $language, $groupId, $isSourceLang + ) { + if ( $req->getCheck( $id ) ) { + return [ false, true ]; + } + + $isCurrentKeyPresent = false; + + // Checked the matched key is also missing to confirm if its truly missing + $matchedKey = $sourceChanges->getMatchedKey( $language, $key ); + $matchedId = self::changeId( $groupId, $language, MessageSourceChange::RENAME, $matchedKey ); + if ( $req->getCheck( $matchedId ) ) { + return [ false, $isCurrentKeyPresent ]; + } + + if ( $isSourceLang === false && $sourceChanges->isEqual( $language, $matchedKey ) ) { + // For non source language, if strings are equal, they are not shown on the UI + // and hence not submitted. + return [ false, $isCurrentKeyPresent ]; + } + + return [ true, $isCurrentKeyPresent ]; + } + + protected function getProcessingErrorMessage( + array $errorGroups, int $totalGroupCount + ): string { + // Number of error groups, are less than the total groups processed. + if ( count( $errorGroups ) < $totalGroupCount ) { + $errorMsg = $this->msg( 'translate-smg-submitted-with-failure' ) + ->numParams( count( $errorGroups ) ) + ->params( + $this->getLanguage()->commaList( $errorGroups ), + $this->msg( 'translate-smg-submitted-others-processing' ) + )->text(); + } else { + $errorMsg = trim( + $this->msg( 'translate-smg-submitted-with-failure' ) + ->numParams( count( $errorGroups ) ) + ->params( $this->getLanguage()->commaList( $errorGroups ), '' ) + ->text() + ); + } + + return $errorMsg; + } + + /** + * @param \Cdb\Reader $reader + * @return FileBasedMessageGroup[] + */ + private function getGroupsFromCdb( \Cdb\Reader $reader ): array { + $groups = []; + $groupIds = TranslateUtils::deserialize( $reader->get( '#keys' ) ); + foreach ( $groupIds as $id ) { + $groups[$id] = MessageGroups::getGroup( $id ); + } + return array_filter( $groups ); + } } |