30 private JobQueueGroup $jobQueueGroup;
31 private LoggerInterface $logger;
33 private TitleFactory $titleFactory;
35 private bool $isGroupSyncCacheEnabled;
37 public const IMPORT_NONE = 1;
39 public const IMPORT_SAFE = 2;
42 public const IMPORT_NON_RENAMES = 3;
43 public const CONSTRUCTOR_OPTIONS = [
'TranslateGroupSynchronizationCache' ];
45 public function __construct(
47 JobQueueGroup $jobQueueGroup,
48 LoggerInterface $logger,
50 TitleFactory $titleFactory,
52 ServiceOptions $options
54 $this->groupSynchronizationCache = $groupSynchronizationCache;
55 $this->jobQueueGroup = $jobQueueGroup;
56 $this->logger = $logger;
57 $this->messageIndex = $messageIndex;
58 $this->titleFactory = $titleFactory;
59 $this->messageGroupSubscription = $messageGroupSubscription;
60 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
61 $this->isGroupSyncCacheEnabled = $options->get(
'TranslateGroupSynchronizationCache' );
70 public function import( array $changeData,
string $name,
int $importStrategy ): array {
75 foreach ( $changeData as $groupId => $changesForGroup ) {
76 $group = MessageGroups::getGroup( $groupId );
78 unset( $changeData[$groupId] );
83 $this->logger->warning(
84 '[ExternalMessageSourceStateImporter] Expected FileBasedMessageGroup, ' .
85 'but got {class} for group {groupId}',
87 'class' => get_class( $group ),
91 unset( $changeData[$groupId] );
95 $processed[$groupId] = [];
96 $languages = $changesForGroup->getLanguages();
99 $groupSafeLanguages = $this->identifySafeLanguages( $group, $changesForGroup, $importStrategy );
101 foreach ( $languages as $language ) {
102 if ( !$groupSafeLanguages[ $language ] ) {
103 $skipped[$groupId] =
true;
107 $additions = $changesForGroup->getAdditions( $language );
108 if ( $additions === [] ) {
112 [ $groupLanguageJobs, $groupProcessed ] = $this->createUpdateMessageJobs(
113 $group, $additions, $language
116 $groupJobs = array_merge( $groupJobs, $groupLanguageJobs );
117 $processed[$groupId][$language] = $groupProcessed;
121 $changesForGroup->removeAdditions( $language,
null );
122 $group->getMessageGroupCache( $language )->create();
126 if ( $this->isGroupSyncCacheEnabled && isset( $skipped[$groupId] ) ) {
127 $this->groupSynchronizationCache->markGroupAsInReview( $groupId );
130 if ( $groupJobs !== [] ) {
131 if ( $this->isGroupSyncCacheEnabled ) {
132 $this->updateGroupSyncInfo( $groupId, $groupJobs );
134 $jobs = array_merge( $jobs, $groupJobs );
138 $this->messageGroupSubscription->queueNotificationJob();
146 $processed = array_filter( $processed );
150 $this->jobQueueGroup->push( $jobs );
153 'processed' => $processed,
154 'skipped' => $skipped,
159 public function canImportGroup(
MessageGroup $group,
bool $skipGroupSyncCache ): Status {
160 $groupId = $group->getId();
162 $error =
"Group $groupId expected to be FileBasedMessageGroup, got " . get_class( $group ) .
" instead.";
163 return Status::newFatal(
new RawMessage( $error ) );
166 if ( $this->isGroupSyncCacheEnabled && !$skipGroupSyncCache ) {
167 if ( $this->groupSynchronizationCache->isGroupBeingProcessed( $groupId ) ) {
168 $error =
"Group $groupId is currently being synchronized; skipping processing of changes\n";
169 return Status::newFatal(
new RawMessage( $error ) );
172 if ( $this->groupSynchronizationCache->groupHasErrors( $groupId ) ) {
173 $error =
"Skipping $groupId due to an error during synchronization\n";
174 return Status::newFatal(
new RawMessage( $error ) );
178 return Status::newGood();
185 private function createUpdateMessageJobs(
190 $groupId = $group->getId();
194 foreach ( $additions as $addition ) {
196 $name =
"{$addition['key']}/$language";
198 $title = $this->titleFactory->makeTitleSafe( $namespace, $name );
200 wfWarn(
"Invalid title for group $groupId key {$addition['key']}" );
204 $jobs[] = UpdateMessageJob::newJob( $title, $addition[
'content'] );
207 if ( $isSourceLanguage ) {
208 $this->messageGroupSubscription->queueMessage(
210 MessageGroupSubscription::STATE_ADDED,
216 return [ $jobs, $processed ];
223 private function updateGroupSyncInfo(
string $groupId, array $groupJobs ): void {
225 $groupMessageKeys = [];
226 foreach ( $groupJobs as $job ) {
227 $messageParams[] = MessageUpdateParameter::createFromJob( $job );
230 $groupMessageKeys[(
new MessageHandle( $job->getTitle() ) )->getKey()] =
true;
233 $group = MessageGroups::getGroup( $groupId );
234 if ( $group ===
null ) {
236 throw new RuntimeException(
"Did not find group $groupId" );
239 $this->messageIndex->storeInterim( $group, array_keys( $groupMessageKeys ) );
241 $this->groupSynchronizationCache->addMessages( $groupId, ...$messageParams );
242 $this->groupSynchronizationCache->markGroupForSync( $groupId );
245 '[ExternalMessageSourceStateImporter] Synchronization started for {groupId}',
246 [
'groupId' => $groupId ]
254 private function identifySafeLanguages(
256 MessageSourceChange $changesForGroup,
259 $sourceLanguage = $group->getSourceLanguage();
260 $safeLanguagesMap = [];
261 $modifiedLanguages = $changesForGroup->getLanguages();
264 $safeLanguagesMap[ $sourceLanguage ] =
false;
265 foreach ( $modifiedLanguages as $language ) {
266 $safeLanguagesMap[ $language ] =
false;
269 if ( !self::isLanguageSafe( $changesForGroup, $sourceLanguage, $importStrategy ) ) {
270 return $safeLanguagesMap;
273 $sourceLanguageKeyCache = [];
274 foreach ( $changesForGroup->getAdditions( $sourceLanguage ) as $change ) {
275 if ( $change[
'content'] ===
'' ) {
276 return $safeLanguagesMap;
279 $sourceLanguageKeyCache[ $change[
'key'] ] =
true;
282 $safeLanguagesMap[ $sourceLanguage ] =
true;
288 if ( $sourceLanguageKeyCache ) {
289 array_splice( $modifiedLanguages, array_search( $sourceLanguage, $modifiedLanguages ), 1 );
292 foreach ( $modifiedLanguages as $language ) {
293 if ( !self::isLanguageSafe( $changesForGroup, $sourceLanguage, $importStrategy ) ) {
297 foreach ( $changesForGroup->getAdditions( $language ) as $change ) {
298 if ( $change[
'content'] ===
'' ) {
302 $msgKey = $change[
'key'];
304 if ( !isset( $sourceLanguageKeyCache[ $msgKey ] ) ) {
308 $sourceHandle =
new MessageHandle( $this->titleFactory->makeTitle( $groupNamespace, $msgKey ) );
309 $sourceLanguageKeyCache[ $msgKey ] = $sourceHandle->isValid();
312 if ( !$sourceLanguageKeyCache[ $msgKey ] ) {
317 $safeLanguagesMap[ $language ] =
true;
320 return $safeLanguagesMap;
323 private static function isLanguageSafe(
324 MessageSourceChange $changesForGroup,
325 string $languageCode,
328 if ( $importStrategy === self::IMPORT_NONE ) {
333 if ( $importStrategy === self::IMPORT_SAFE ) {
334 return $changesForGroup->hasOnly( $languageCode, MessageSourceChange::ADDITION );
339 return $changesForGroup->getDeletions( $languageCode ) === [];