summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Evans <grknight@gentoo.org>2020-10-02 15:24:06 -0400
committerBrian Evans <grknight@gentoo.org>2020-10-02 15:24:06 -0400
commit60dd5fd95847643eab04ce173f0774c9c584e795 (patch)
tree52299ac4e3c5c69df75997bfd7d62b71ef9e0089 /MLEB/Translate/specials/SpecialManageGroups.php
parentUpdate Widgets to 1.35 (diff)
downloadextensions-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.php642
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 );
+ }
}