From 00033694f595c68bac72211fd219371e4fcaf09b Mon Sep 17 00:00:00 2001 From: Vitalii Solovei Date: Wed, 30 Sep 2026 11:57:34 +0200 Subject: [PATCH] stop cron jobs from failing instant translation submissions (WP-1023) Instant translation stores "fileUid:mtUid" in file_uri, which is not a Smartling file URI. The collector and last-modified check jobs treated it as one and marked the submissions as failed. Skip such submissions in both jobs, and do not move them to New or enqueue an upload on source changes. Co-Authored-By: Claude Sonnet 5.5 --- inc/Smartling/Helpers/DetectChangesHelper.php | 2 +- inc/Smartling/Jobs/LastModifiedCheckJob.php | 5 +- inc/Smartling/Jobs/SubmissionCollectorJob.php | 3 + .../Submissions/SubmissionEntity.php | 13 ++++ .../Helpers/DetectChangesHelperTest.php | 45 ++++++++++++ .../Jobs/LastModifiedCheckJobTest.php | 69 +++++++++++++++++++ .../Jobs/SubmissionCollectorJobTest.php | 46 +++++++++++++ .../Submissions/SubmissionEntityTest.php | 21 ++++++ 8 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 tests/Smartling/Jobs/LastModifiedCheckJobTest.php create mode 100644 tests/Smartling/Jobs/SubmissionCollectorJobTest.php diff --git a/inc/Smartling/Helpers/DetectChangesHelper.php b/inc/Smartling/Helpers/DetectChangesHelper.php index 876989393..1bc577c2b 100644 --- a/inc/Smartling/Helpers/DetectChangesHelper.php +++ b/inc/Smartling/Helpers/DetectChangesHelper.php @@ -67,7 +67,7 @@ private function update(SubmissionEntity $submission, bool $needUpdateStatus, st vsprintf('Submission id=%s has outdated hash. Setting up Outdated flag.', [$submission->getId()]) ); $submission->setOutdated(SubmissionEntity::FLAG_CONTENT_IS_OUT_OF_DATE); - if ($needUpdateStatus) { + if ($needUpdateStatus && !$submission->isInstantTranslation()) { $newStatus = SubmissionEntity::SUBMISSION_STATUS_NEW; $this->getLogger()->debug( diff --git a/inc/Smartling/Jobs/LastModifiedCheckJob.php b/inc/Smartling/Jobs/LastModifiedCheckJob.php index 00cc4c22e..5a9902e95 100644 --- a/inc/Smartling/Jobs/LastModifiedCheckJob.php +++ b/inc/Smartling/Jobs/LastModifiedCheckJob.php @@ -149,7 +149,10 @@ private function lastModifiedCheck(string $queueName, bool $failMissing): void continue; } foreach ($serializedPair as $serializedSubmissions) { - $submissionList = $this->processTestRun($this->submissionManager->findByIds($serializedSubmissions)); + $submissionList = array_values(array_filter( + $this->processTestRun($this->submissionManager->findByIds($serializedSubmissions)), + static fn (SubmissionEntity $submission) => !$submission->isInstantTranslation(), + )); try { $submissions = $this->processFileUriSet($submissionList, $failMissing); diff --git a/inc/Smartling/Jobs/SubmissionCollectorJob.php b/inc/Smartling/Jobs/SubmissionCollectorJob.php index cd4169b81..bdf0585f7 100644 --- a/inc/Smartling/Jobs/SubmissionCollectorJob.php +++ b/inc/Smartling/Jobs/SubmissionCollectorJob.php @@ -62,6 +62,9 @@ public function run(string $source): void if (0 < count($preparedList)) { foreach ($preparedList as $_result) { $fileUri = &$_result['fileUri']; + if (SubmissionEntity::isInstantTranslationFileUri((string)$fileUri)) { + continue; + } $idsList = explode(',', $_result['ids']); array_walk($idsList, function (& $id) { $id = (int)$id; diff --git a/inc/Smartling/Submissions/SubmissionEntity.php b/inc/Smartling/Submissions/SubmissionEntity.php index 7f496da0d..766d63cc6 100644 --- a/inc/Smartling/Submissions/SubmissionEntity.php +++ b/inc/Smartling/Submissions/SubmissionEntity.php @@ -238,6 +238,19 @@ public function setOutdated(?int $outdated): void $this->stateFields[static::FIELD_OUTDATED] = $outdated; } + /** + * Instant translation stores "fileUid:mtUid" in file_uri instead of a Smartling file URI. + */ + public static function isInstantTranslationFileUri(string $fileUri): bool + { + return preg_match('/^[A-Za-z0-9_-]+:[A-Za-z0-9_-]+$/', $fileUri) === 1; + } + + public function isInstantTranslation(): bool + { + return self::isInstantTranslationFileUri($this->getFileUri()); + } + public function isCloned(): bool { return $this->getIsCloned() === 1; diff --git a/tests/Smartling/Helpers/DetectChangesHelperTest.php b/tests/Smartling/Helpers/DetectChangesHelperTest.php index 7a742f44a..d819da309 100644 --- a/tests/Smartling/Helpers/DetectChangesHelperTest.php +++ b/tests/Smartling/Helpers/DetectChangesHelperTest.php @@ -107,6 +107,51 @@ public function testSubmissionStatusChange() ->detectChanges(1, 2, AcfDynamicSupport::REFERENCED_TYPE_POST); } + /** + * @dataProvider outdatedSubmissionDataProvider + */ + public function testOutdatedSubmissionUploadEnqueue(bool $instantTranslation): void + { + $profile = $this->createMock(ConfigurationProfileEntity::class); + $profile->method('getUploadOnUpdate')->willReturn(ConfigurationProfileEntity::UPLOAD_ON_CHANGE_AUTO); + $settingsManager = $this->createMock(SettingsManager::class); + $settingsManager->method('findEntityByMainLocale')->willReturn([$profile]); + $settingsManager->method('getSingleSettingsProfile')->willReturn($profile); + + $submission = $this->createMock(SubmissionEntity::class); + $submission->method('getId')->willReturn(5); + $submission->method('getSourceContentHash')->willReturn('old'); + $submission->method('isInstantTranslation')->willReturn($instantTranslation); + $submission->expects($instantTranslation ? $this->never() : $this->once())->method('setStatus') + ->with(SubmissionEntity::SUBMISSION_STATUS_NEW); + $submission->expects($this->once())->method('setOutdated')->with(SubmissionEntity::FLAG_CONTENT_IS_OUT_OF_DATE); + + $submissionManager = $this->createMock(SubmissionManager::class); + $submissionManager->method('find')->willReturn([$submission]); + + $contentSerializationHelper = $this->createMock(ContentSerializationHelper::class); + $contentSerializationHelper->method('calculateHash')->willReturn('new'); + + $uploadQueueManager = $this->createMock(UploadQueueManager::class); + $uploadQueueManager->expects($instantTranslation ? $this->never() : $this->once())->method('enqueue'); + + (new DetectChangesHelper( + $this->createMock(AcfDynamicSupport::class), + $contentSerializationHelper, + $uploadQueueManager, + $settingsManager, + $submissionManager, + ))->detectChanges(1, 2, 'post'); + } + + public function outdatedSubmissionDataProvider(): array + { + return [ + 'regular submission is moved to New and enqueued' => [false], + 'instant translation submission is only flagged outdated' => [true], + ]; + } + public function testAcfFieldGroupNoSubmissionStatusChange() { $settingsManager = $this->createMock(SettingsManager::class); diff --git a/tests/Smartling/Jobs/LastModifiedCheckJobTest.php b/tests/Smartling/Jobs/LastModifiedCheckJobTest.php new file mode 100644 index 000000000..084b366d6 --- /dev/null +++ b/tests/Smartling/Jobs/LastModifiedCheckJobTest.php @@ -0,0 +1,69 @@ +createMock(SubmissionEntity::class); + $submission->method('isInstantTranslation')->willReturn(true); + $submission->expects($this->never())->method('setStatus'); + + $submissionManager = $this->createMock(SubmissionManager::class); + $submissionManager->method('findByIds')->willReturn([$submission]); + $submissionManager->expects($this->never())->method('setErrorMessage'); + + $api = $this->createMock(ApiWrapperInterface::class); + $api->expects($this->never())->method('lastModified'); + $api->expects($this->never())->method('getStatusForAllLocales'); + + $queue = $this->createMock(QueueInterface::class); + $pending = [ + QueueInterface::QUEUE_NAME_LAST_MODIFIED_CHECK_QUEUE => [], + QueueInterface::QUEUE_NAME_LAST_MODIFIED_CHECK_AND_FAIL_QUEUE => [], + ]; + $pending[$queueName][] = ['fileuid:mtuid' => [1]]; + $queue->method('dequeue')->willReturnCallback( + static function (string $name) use (&$pending) { + return array_shift($pending[$name]) ?? false; + } + ); + + (new LastModifiedCheckJob( + $api, + $this->createMock(Cache::class), + $this->createMock(SettingsManager::class), + $submissionManager, + 0, + '', + $queue, + ))->run(''); + } + + public function queueDataProvider(): array + { + return [ + 'last modified check queue' => [QueueInterface::QUEUE_NAME_LAST_MODIFIED_CHECK_QUEUE], + 'last modified check and fail queue' => [QueueInterface::QUEUE_NAME_LAST_MODIFIED_CHECK_AND_FAIL_QUEUE], + ]; + } +} diff --git a/tests/Smartling/Jobs/SubmissionCollectorJobTest.php b/tests/Smartling/Jobs/SubmissionCollectorJobTest.php new file mode 100644 index 000000000..218de1cc1 --- /dev/null +++ b/tests/Smartling/Jobs/SubmissionCollectorJobTest.php @@ -0,0 +1,46 @@ +createMock(SubmissionManager::class); + $submissionManager->method('find')->willReturn([]); + $submissionManager->method('getGroupedIdsByFileUri')->willReturn([ + ['fileUri' => '/page_post_1_5.xml', 'ids' => '1,2'], + ['fileUri' => 'fileuid123:mtuid456', 'ids' => '3'], + ]); + + $queue = $this->createMock(QueueInterface::class); + $queue->expects($this->once())->method('enqueue') + ->with(['/page_post_1_5.xml' => [1, 2]], QueueInterface::QUEUE_NAME_LAST_MODIFIED_CHECK_QUEUE); + + (new SubmissionCollectorJob( + $this->createMock(ApiWrapperInterface::class), + $this->createMock(Cache::class), + $this->createMock(FileUriHelper::class), + $this->createMock(SettingsManager::class), + $submissionManager, + 0, + '', + $queue, + ))->run(''); + } +} diff --git a/tests/Smartling/Submissions/SubmissionEntityTest.php b/tests/Smartling/Submissions/SubmissionEntityTest.php index 208b376d6..18d0c6f42 100644 --- a/tests/Smartling/Submissions/SubmissionEntityTest.php +++ b/tests/Smartling/Submissions/SubmissionEntityTest.php @@ -32,4 +32,25 @@ public function completionPercentageDataProvider(): array [1000000, 0, 999999, 0, 99, 'Must return 99% even if 99.9999% translated'], ]; } + + /** + * @dataProvider instantTranslationFileUriDataProvider + */ + public function testIsInstantTranslation(string $fileUri, bool $expected): void + { + $x = new SubmissionEntity(); + $x->setFileUri($fileUri); + + $this->assertSame($expected, $x->isInstantTranslation()); + } + + public function instantTranslationFileUriDataProvider(): array + { + return [ + 'fts file uid and mt uid' => ['abc123def456:xyz789', true], + 'regular file uri' => ['/sample-page_post_1_5.xml', false], + 'regular file uri with colon in title' => ['Title: subtitle_post_1_5.xml', false], + 'empty' => ['', false], + ]; + } }