29 private JobQueueGroup $jobQueueGroup;
30 private LoggerInterface $logger;
32 private TitleFactory $titleFactory;
33 private bool $isGroupSyncCacheEnabled;
35 public const IMPORT_NONE = 1;
37 public const IMPORT_SAFE = 2;
40 public const IMPORT_NON_RENAMES = 3;
41 public const CONSTRUCTOR_OPTIONS = [
'TranslateGroupSynchronizationCache' ];
43 public function __construct(
45 JobQueueGroup $jobQueueGroup,
46 LoggerInterface $logger,
48 TitleFactory $titleFactory,
49 ServiceOptions $options
51 $this->groupSynchronizationCache = $groupSynchronizationCache;
52 $this->jobQueueGroup = $jobQueueGroup;
53 $this->logger = $logger;
54 $this->messageIndex = $messageIndex;
55 $this->titleFactory = $titleFactory;
56 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
57 $this->isGroupSyncCacheEnabled = $options->get(
'TranslateGroupSynchronizationCache' );
66 public function import( array $changeData,
string $name,
int $importStrategy ): array {
71 foreach ( $changeData as $groupId => $changesForGroup ) {
72 $group = MessageGroups::getGroup( $groupId );
74 unset( $changeData[$groupId] );
79 $this->logger->warning(
80 '[ExternalMessageSourceStateImporter] Expected FileBasedMessageGroup, ' .
81 'but got {class} for group {groupId}',
83 'class' => get_class( $group ),
87 unset( $changeData[$groupId] );
91 $processed[$groupId] = [];
92 $languages = $changesForGroup->getLanguages();
95 $groupSafeLanguages = $this->identifySafeLanguages( $group, $changesForGroup, $importStrategy );
97 foreach ( $languages as $language ) {
98 if ( !$groupSafeLanguages[ $language ] ) {
99 $skipped[$groupId] =
true;
103 $additions = $changesForGroup->getAdditions( $language );
104 if ( $additions === [] ) {
108 [ $groupLanguageJobs, $groupProcessed ] = $this->createUpdateMessageJobs(
109 $group, $additions, $language
112 $groupJobs = array_merge( $groupJobs, $groupLanguageJobs );
113 $processed[$groupId][$language] = $groupProcessed;
117 $changesForGroup->removeAdditions( $language,
null );
118 $group->getMessageGroupCache( $language )->create();
122 if ( $this->isGroupSyncCacheEnabled && isset( $skipped[$groupId] ) ) {
123 $this->groupSynchronizationCache->markGroupAsInReview( $groupId );
126 if ( $groupJobs !== [] ) {
127 if ( $this->isGroupSyncCacheEnabled ) {
128 $this->updateGroupSyncInfo( $groupId, $groupJobs );
130 $jobs = array_merge( $jobs, $groupJobs );
140 $processed = array_filter( $processed );
144 $this->jobQueueGroup->push( $jobs );
147 'processed' => $processed,
148 'skipped' => $skipped,
153 public function canImportGroup(
MessageGroup $group,
bool $skipGroupSyncCache ): Status {
154 $groupId = $group->getId();
156 $error =
"Group $groupId expected to be FileBasedMessageGroup, got " . get_class( $group ) .
" instead.";
157 return Status::newFatal(
new RawMessage( $error ) );
160 if ( $this->isGroupSyncCacheEnabled && !$skipGroupSyncCache ) {
161 if ( $this->groupSynchronizationCache->isGroupBeingProcessed( $groupId ) ) {
162 $error =
"Group $groupId is currently being synchronized; skipping processing of changes\n";
163 return Status::newFatal(
new RawMessage( $error ) );
166 if ( $this->groupSynchronizationCache->groupHasErrors( $groupId ) ) {
167 $error =
"Skipping $groupId due to an error during synchronization\n";
168 return Status::newFatal(
new RawMessage( $error ) );
172 return Status::newGood();
176 private function createUpdateMessageJobs(
181 $groupId = $group->getId();
184 foreach ( $additions as $addition ) {
186 $name =
"{$addition['key']}/$language";
188 $title = $this->titleFactory->makeTitleSafe( $namespace, $name );
190 wfWarn(
"Invalid title for group $groupId key {$addition['key']}" );
194 $jobs[] = UpdateMessageJob::newJob( $title, $addition[
'content'] );
198 return [ $jobs, $processed ];
205 private function updateGroupSyncInfo(
string $groupId, array $groupJobs ): void {
207 $groupMessageKeys = [];
208 foreach ( $groupJobs as $job ) {
209 $messageParams[] = MessageUpdateParameter::createFromJob( $job );
212 $groupMessageKeys[(
new MessageHandle( $job->getTitle() ) )->getKey()] =
true;
215 $group = MessageGroups::getGroup( $groupId );
216 if ( $group ===
null ) {
218 throw new RuntimeException(
"Did not find group $groupId" );
221 $this->messageIndex->storeInterim( $group, array_keys( $groupMessageKeys ) );
223 $this->groupSynchronizationCache->addMessages( $groupId, ...$messageParams );
224 $this->groupSynchronizationCache->markGroupForSync( $groupId );
227 '[ExternalMessageSourceStateImporter] Synchronization started for {groupId}',
228 [
'groupId' => $groupId ]
236 private function identifySafeLanguages(
238 MessageSourceChange $changesForGroup,
241 $sourceLanguage = $group->getSourceLanguage();
242 $safeLanguagesMap = [];
243 $modifiedLanguages = $changesForGroup->getLanguages();
246 $safeLanguagesMap[ $sourceLanguage ] =
false;
247 foreach ( $modifiedLanguages as $language ) {
248 $safeLanguagesMap[ $language ] =
false;
251 if ( !self::isLanguageSafe( $changesForGroup, $sourceLanguage, $importStrategy ) ) {
252 return $safeLanguagesMap;
255 $sourceLanguageKeyCache = [];
256 foreach ( $changesForGroup->getAdditions( $sourceLanguage ) as $change ) {
257 if ( $change[
'content'] ===
'' ) {
258 return $safeLanguagesMap;
261 $sourceLanguageKeyCache[ $change[
'key'] ] =
true;
264 $safeLanguagesMap[ $sourceLanguage ] =
true;
270 if ( $sourceLanguageKeyCache ) {
271 array_splice( $modifiedLanguages, array_search( $sourceLanguage, $modifiedLanguages ), 1 );
274 foreach ( $modifiedLanguages as $language ) {
275 if ( !self::isLanguageSafe( $changesForGroup, $sourceLanguage, $importStrategy ) ) {
279 foreach ( $changesForGroup->getAdditions( $language ) as $change ) {
280 if ( $change[
'content'] ===
'' ) {
284 $msgKey = $change[
'key'];
286 if ( !isset( $sourceLanguageKeyCache[ $msgKey ] ) ) {
290 $sourceHandle =
new MessageHandle( $this->titleFactory->makeTitle( $groupNamespace, $msgKey ) );
291 $sourceLanguageKeyCache[ $msgKey ] = $sourceHandle->isValid();
294 if ( !$sourceLanguageKeyCache[ $msgKey ] ) {
299 $safeLanguagesMap[ $language ] =
true;
302 return $safeLanguagesMap;
305 private static function isLanguageSafe(
306 MessageSourceChange $changesForGroup,
307 string $languageCode,
310 if ( $importStrategy === self::IMPORT_NONE ) {
315 if ( $importStrategy === self::IMPORT_SAFE ) {
316 return $changesForGroup->hasOnly( $languageCode, MessageSourceChange::ADDITION );
321 return $changesForGroup->getDeletions( $languageCode ) === [];