Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ tests/IntegrationTests/src
#IDE
.idea

# macOS
.DS_Store
*-backups.zip

# Playwright E2E
.env.playwright
tests/playwright/.auth/
Expand Down
2 changes: 1 addition & 1 deletion inc/Smartling/ApiWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function __construct(SettingsManager $manager, string $pluginName, string
*/
private function getConfigurationProfile(SubmissionEntity $submission): ConfigurationProfileEntity
{
$profile = $this->settings->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->settings->getProfileBySubmission($submission);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 critical
Credentials/project now come from the stored profile, but the locale passed to the API still comes from the active profile: SettingsManager::getSmartlingLocaleBySubmission() (SettingsManager.php:95) still calls getSingleSettingsProfile($submission->getSourceBlogId()). It is used by downloadFile() (line 189) and getStatus() (line 215) here, plus UploadQueueManager::getSmartlingLocale() and SmartlingCoreTrait.

Scenario: submission stamped with profile A (project A, target blog 2 → de-DE), user switches active profile to B (blog 2 → de, or blog 2 not mapped). Status check / download for A's file goes to project A with B's locale, or throws SmartlingConfigException → automated delivery fails, which is exactly the WP-1021 case.

Fix:

public function getSmartlingLocaleBySubmission(SubmissionEntity $submission): string
{
    $profile = $this->getProfileBySubmission($submission);
    // ...

plus a test with stored ≠ active profile.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — getSmartlingLocaleBySubmission() now calls getProfileBySubmission() instead of getSingleSettingsProfile(), matching ApiWrapper::getConfigurationProfile(). It falls back to the active profile exactly the same way getProfileBySubmission() already does for the no-stamp/deleted-profile cases, so downloadFile()/getStatus()/UploadQueueManager::getSmartlingLocale()/SmartlingCoreTrait all pick this up automatically. Added a test with stored profile A (locale de-DE) vs. active profile B, asserting A's locale is used and getSingleSettingsProfile() is never called.

LogContextMixinHelper::addToContext('projectId', $profile->getProjectId());

if (TestRunHelper::isTestRunBlog($submission->getTargetBlogId())) {
Expand Down
2 changes: 1 addition & 1 deletion inc/Smartling/Base/SmartlingCoreAttachments.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function syncAttachment(SubmissionEntity $submission): void
$submission->getTargetId(),
])
);
$profile = $this->getSettingsManager()->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->getSettingsManager()->getProfileBySubmission($submission);
if (1 === $profile->getAlwaysSyncImagesOnUpload() || ($submission->getStatus() === SubmissionEntity::SUBMISSION_STATUS_NEW && !$targetFileExists)) {
$this->syncMediaFile($submission);
}
Expand Down
8 changes: 4 additions & 4 deletions inc/Smartling/Base/SmartlingCoreDownloadTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function downloadTranslationBySubmission(SubmissionEntity $entity): void
LiveNotificationController::pushNotification(
$this
->getSettingsManager()
->getSingleSettingsProfile($entity->getSourceBlogId())
->getProfileBySubmission($entity)
->getProjectId(),
LiveNotificationController::getContentId($entity),
LiveNotificationController::SEVERITY_SUCCESS,
Expand All @@ -65,7 +65,7 @@ public function downloadTranslationBySubmission(SubmissionEntity $entity): void
LiveNotificationController::pushNotification(
$this
->getSettingsManager()
->getSingleSettingsProfile($entity->getSourceBlogId())
->getProfileBySubmission($entity)
->getProjectId(),
LiveNotificationController::getContentId($entity),
LiveNotificationController::SEVERITY_SUCCESS,
Expand All @@ -78,7 +78,7 @@ public function downloadTranslationBySubmission(SubmissionEntity $entity): void
LiveNotificationController::pushNotification(
$this
->getSettingsManager()
->getSingleSettingsProfile($entity->getSourceBlogId())
->getProfileBySubmission($entity)
->getProjectId(),
LiveNotificationController::getContentId($entity),
LiveNotificationController::SEVERITY_SUCCESS,
Expand All @@ -100,7 +100,7 @@ public function downloadTranslationBySubmission(SubmissionEntity $entity): void
LiveNotificationController::pushNotification(
$this
->getSettingsManager()
->getSingleSettingsProfile($entity->getSourceBlogId())
->getProfileBySubmission($entity)
->getProjectId(),
LiveNotificationController::getContentId($entity),
LiveNotificationController::SEVERITY_ERROR,
Expand Down
15 changes: 12 additions & 3 deletions inc/Smartling/Base/SmartlingCoreUploadTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ protected function getFunctionProxyHelper(): WordpressFunctionProxyHelper

public function prepareUpload(SubmissionEntity $submission): SubmissionEntity
{
// Only stamp if nothing stamped it yet (e.g. a creation path that bypasses
// SubmissionManager::getSubmissionEntity()). This method also runs on every
// getXMLFiltered() call, including read-only content fetches (ContentProvider),
// so re-stamping unconditionally would silently rebind an in-flight submission to
// whatever profile is currently active, not the one it was requested under.
if ($submission->getConfigurationProfileId() === null) {
$this->getSubmissionManager()->stampConfigurationProfile($submission);
}

return $this->renewContentHash(
$this->createTargetContent(
$this->setFileUriIfNullId($submission)
Expand Down Expand Up @@ -259,7 +268,7 @@ public function applyXML(SubmissionEntity $submission, string $xml, XmlHelper $x
$targetContent = $targetContent->fromArray($translation['entity']);
}
$configurationProfile = $this->getSettingsManager()
->getSingleSettingsProfile($submission->getSourceBlogId());
->getProfileBySubmission($submission);

$percentage = $submission->getCompletionPercentage();
$this->getLogger()->debug(vsprintf('Current percentage is %s', [$percentage]));
Expand Down Expand Up @@ -424,7 +433,7 @@ public function bulkSubmit(UploadQueueItem $item): void
}
$submission = $item->getSubmissions()[0];
$locales = $item->getSmartlingLocales()->getList();
$profile = $this->getSettingsManager()->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->getSettingsManager()->getProfileBySubmission($submission);
try {
$xml = $this->getXMLFiltered($submission);
if ($xml === '') {
Expand Down Expand Up @@ -530,7 +539,7 @@ public function sendForTranslation(UploadQueueItem $item): void
return;
}

$configurationProfile = $this->getSettingsManager()->getSingleSettingsProfile($item->getSubmissions()[0]->getSourceBlogId());
$configurationProfile = $this->getSettingsManager()->getProfileBySubmission($item->getSubmissions()[0]);

// Clone attachment submission instead of uploading it, if "Clone attachment"
// option is enabled in configuration profile.
Expand Down
1 change: 1 addition & 0 deletions inc/Smartling/Base/SmartlingEntityAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ abstract class SmartlingEntityAbstract implements SmartlingTableDefinitionInterf
public const DB_TYPE_DEFAULT_EMPTYSTRING = 'DEFAULT \'\'';

public const DB_TYPE_U_BIGINT = 'INT(20) UNSIGNED NOT NULL'; // BIGINT alias of INT(20)
public const DB_TYPE_U_BIGINT_NULL = 'INT(20) UNSIGNED NULL';
public const DB_TYPE_DATETIME = 'DATETIME NOT NULL DEFAULT \'0000-00-00 00:00:00\'';
public const DB_TYPE_DATETIME_NULL = 'DATETIME NULL DEFAULT NULL';
public const DB_TYPE_STRING_STANDARD = 'VARCHAR(255) NOT NULL';
Expand Down
43 changes: 43 additions & 0 deletions inc/Smartling/DbAl/Migrations/Migration261001.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Smartling\DbAl\Migrations;

use Smartling\DbAl\DB;
use Smartling\Submissions\SubmissionEntity;

/**
* Stores the configuration profile a submission was requested with.
*
* Existing rows are left NULL: until stamped, they keep resolving the profile by source
* blog. SubmissionManager::getSubmissionEntity() backfills the column with the currently
* active profile the first time a pre-existing submission is (re)submitted for translation,
* so an in-flight row stays NULL only until it is next touched by an upload/resubmit flow.
*/
class Migration261001 implements SmartlingDbMigrationInterface
{
public function getVersion(): int
{
return 261001;
}

public function getQueries($tablePrefix = 'wp_'): array
{
$db = new DB();
$tableName = $db->completeTableName(SubmissionEntity::getTableName());

// Migration240315 may already have created the table with the current field definitions.
$existingColumns = $db->getColumnArray("SHOW COLUMNS FROM `$tableName`");
if (in_array(SubmissionEntity::FIELD_CONFIGURATION_PROFILE_ID, $existingColumns, true)) {
return [];
}

return [
sprintf(
'ALTER TABLE `%s` ADD COLUMN `%s` %s',
$tableName,
SubmissionEntity::FIELD_CONFIGURATION_PROFILE_ID,
SubmissionEntity::getFieldDefinitions()[SubmissionEntity::FIELD_CONFIGURATION_PROFILE_ID]
),
];
}
}
9 changes: 5 additions & 4 deletions inc/Smartling/DbAl/UploadQueueManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,16 @@ public function purge(): void
if ($submission === null) {
continue;
}
if (!array_key_exists($submission->getSourceBlogId(), $profiles)) {
$profileKey = $submission->getConfigurationProfileId() ?? "blog{$submission->getSourceBlogId()}";
if (!array_key_exists($profileKey, $profiles)) {
try {
$profile = $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->settingsManager->getProfileBySubmission($submission);
} catch (SmartlingDbException) {
$profile = null;
}
$profiles[$submission->getSourceBlogId()] = $profile;
$profiles[$profileKey] = $profile;
}
$profile = $profiles[$submission->getSourceBlogId()];
$profile = $profiles[$profileKey];
if (!$profile instanceof ConfigurationProfileEntity) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion inc/Smartling/FTS/FtsApiWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __construct(
*/
private function getConfigurationProfile(SubmissionEntity $submission): ConfigurationProfileEntity
{
return $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId());
return $this->settingsManager->getProfileBySubmission($submission);
}

private function getFileTranslationsApi(ConfigurationProfileEntity $profile): FileTranslationsApiExtended
Expand Down
6 changes: 3 additions & 3 deletions inc/Smartling/FTS/FtsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public function requestInstantTranslationBatch(array $submissions): array
try {
$fileUid = $this->uploadFile($firstSubmission);

$profile = $this->settingsManager->getSingleSettingsProfile($firstSubmission->getSourceBlogId());
$profile = $this->settingsManager->getProfileBySubmission($firstSubmission);
$sourceLocale = $this->apiWrapper->getSourceLocale($profile);
$targetLocales = [];

Expand Down Expand Up @@ -304,7 +304,7 @@ private function submitFile(SubmissionEntity $submission, string $fileUid): stri
{
$this->getLogger()->debug("Submitting file for instant translation, submissionId={$submission->getId()}, fileUid=$fileUid");

$profile = $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->settingsManager->getProfileBySubmission($submission);
$sourceLocale = $this->apiWrapper->getSourceLocale($profile);
$targetLocale = $profile->getSmartlingLocale($submission->getTargetBlogId());

Expand Down Expand Up @@ -398,7 +398,7 @@ private function downloadAndApply(SubmissionEntity $submission, string $fileUid,
{
$this->getLogger()->info("Downloading and applying translation, submissionId={$submission->getId()}, fileUid=$fileUid, mtUid=$mtUid");

$profile = $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->settingsManager->getProfileBySubmission($submission);
$targetLocale = $profile->getSmartlingLocale($submission->getTargetBlogId());

if (empty($targetLocale)) {
Expand Down
9 changes: 5 additions & 4 deletions inc/Smartling/Helpers/ContentSerializationHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Smartling\Helpers;

use Smartling\Exception\SmartlingDbException;
use Smartling\Settings\SettingsManager;
use Smartling\Submissions\SubmissionEntity;

Expand Down Expand Up @@ -87,8 +88,6 @@ private function collectSubmissionSourceContent(SubmissionEntity $submission): a

public function prepareFieldProcessorValues(SubmissionEntity $submission): array
{
$profiles = $this->settingsManager->findEntityByMainLocale($submission->getSourceBlogId());

$filter = [
'ignore' => [],
'key' => [
Expand All @@ -100,15 +99,17 @@ public function prepareFieldProcessorValues(SubmissionEntity $submission): array
],
];

if (0 < count($profiles)) {
$profile = ArrayHelper::first($profiles);
try {
$profile = $this->settingsManager->getProfileBySubmission($submission);

$filter['ignore'] = $profile->getFilterSkipArray();
$filter['key']['seo'] = array_map('trim', explode(PHP_EOL, $profile->getFilterFlagSeo()));
$filter['copy']['name'] = array_map('trim', explode(PHP_EOL, $profile->getFilterCopyByFieldName()));
$filter['copy']['regexp'] = array_map('trim', explode(PHP_EOL, $profile->getFilterCopyByFieldValueRegex()));

LogContextMixinHelper::addToContext('projectId', $profile->getProjectId());
} catch (SmartlingDbException) {
// no profile available (stored or active) for this submission's source blog; leave filter at defaults
}

return $filter;
Expand Down
2 changes: 1 addition & 1 deletion inc/Smartling/Helpers/DetectChangesHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private function update(SubmissionEntity $submission, bool $needUpdateStatus, st

LiveNotificationController::pushNotification(
$this->settingsManager
->getSingleSettingsProfile($submission->getSourceBlogId())
->getProfileBySubmission($submission)
->getProjectId(),
LiveNotificationController::getContentId($submission),
LiveNotificationController::SEVERITY_WARNING,
Expand Down
13 changes: 8 additions & 5 deletions inc/Smartling/Helpers/FieldsFilterHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public function removeIgnoringFields(SubmissionEntity $submission, array $data):
$this->prepareSourceData($data)
),
$this->contentSerializationHelper->prepareFieldProcessorValues($submission)['ignore'],
$this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId())->getFilterFieldNameRegExp()),
$this->settingsManager->getProfileBySubmission($submission)->getFilterFieldNameRegExp()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion
getProfileBySubmission($submission) is now called 2-3 times back-to-back for the same $submission within single methods (also at lines 138 & 142 in processStringsBeforeEncoding, and 183 & 187 in filterArray), each doing its own getEntityById round-trip when a profile id is stamped. Consider resolving the profile once per method and reusing it:

$profile = $this->settingsManager->getProfileBySubmission($submission);
// ... $profile->getFilterFieldNameRegExp()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved getFilterFieldNameRegExp() once per method in both processStringsBeforeEncoding and filterArray and reused it.

);
}

Expand All @@ -128,18 +128,19 @@ public function processStringsBeforeEncoding(
}

$settings = $this->contentSerializationHelper->prepareFieldProcessorValues($submission);
$filterFieldNameRegExp = $this->settingsManager->getProfileBySubmission($submission)->getFilterFieldNameRegExp();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 warning
The regexp flag now comes from the stored profile, but the ignore/copy lists in $settings (line above) still come from the active one: ContentSerializationHelper::prepareFieldProcessorValues() (ContentSerializationHelper.php:90) uses findEntityByMainLocale($submission->getSourceBlogId()). After a profile switch, a single upload/apply mixes filter settings from two profiles (skip list / copy-by-name / SEO keys from B, regexp mode from A).

Fix: resolve the profile in prepareFieldProcessorValues() via getProfileBySubmission() (keeping the empty-filter fallback on SmartlingDbException).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — ContentSerializationHelper::prepareFieldProcessorValues() now resolves the profile via getProfileBySubmission() instead of findEntityByMainLocale(), with the filter left at empty defaults on SmartlingDbException (same fallback behavior as before, just profile-aware). Added tests for both the stamped-profile-is-used case and the exception fallback.


return $this->passConnectionProfileFilters(
$this->passFieldProcessorsBeforeSendFilters(
$submission,
$this->removeFields(
$this->flattenArray($data),
$settings['ignore'],
$this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId())->getFilterFieldNameRegExp(),
$filterFieldNameRegExp,
)
),
$strategy,
$this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId())->getFilterFieldNameRegExp(),
$filterFieldNameRegExp,
$settings,
);
}
Expand Down Expand Up @@ -174,17 +175,19 @@ public function applyTranslatedValues(SubmissionEntity $submission, array $origi

private function filterArray(array $array, SubmissionEntity $submission, string $strategy, array $settings): array
{
$filterFieldNameRegExp = $this->settingsManager->getProfileBySubmission($submission)->getFilterFieldNameRegExp();

return $this->passConnectionProfileFilters(
$this->passFieldProcessorsFilters(
$submission,
$this->removeFields(
$array,
$settings['ignore'],
$this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId())->getFilterFieldNameRegExp(),
$filterFieldNameRegExp,
),
),
$strategy,
$this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId())->getFilterFieldNameRegExp(),
$filterFieldNameRegExp,
$this->contentSerializationHelper->prepareFieldProcessorValues($submission),
);
}
Expand Down
5 changes: 5 additions & 0 deletions inc/Smartling/Helpers/RuntimeCacheHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ public function set($key, $value, $scope = self::DEFAULT_SCOPE)
{
$this->storage[$scope][$key] = $value;
}

public function clear(): void
{
$this->storage = [];
}
}
2 changes: 1 addition & 1 deletion inc/Smartling/Helpers/SubstringProcessorHelperAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ private function passProfileFilters(array $attributes)
$fFilter = $this->getFieldsFilter();

$settings = $this->contentSerializationHelper->prepareFieldProcessorValues($submission);
$removeAsRegExp = $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId())->getFilterFieldNameRegExp();
$removeAsRegExp = $this->settingsManager->getProfileBySubmission($submission)->getFilterFieldNameRegExp();
$attributes = $fFilter->removeFields($attributes, $settings['ignore'], $removeAsRegExp);
$attributes = $fFilter->removeFields($attributes, $settings['copy']['name'], $removeAsRegExp);

Expand Down
2 changes: 1 addition & 1 deletion inc/Smartling/Helpers/XmlHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function xmlEncode(array $source, SubmissionEntity $submission, array $or
{
$this->getLogger()->debug(sprintf('Started creating XML for fields: %s', base64_encode(var_export($source, true))));
try {
$profile = $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->settingsManager->getProfileBySubmission($submission);
} catch (SmartlingDbException) {
$profile = null;
}
Expand Down
6 changes: 3 additions & 3 deletions inc/Smartling/Jobs/LastModifiedCheckJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private function lastModifiedCheck(string $queueName, bool $failMissing): void
protected function processDownloadOnChange(array $submissions): void
{
foreach ($submissions as $submission) {
$profile = $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->settingsManager->getProfileBySubmission($submission);

if (ConfigurationProfileEntity::TRANSLATION_DOWNLOAD_MODE_PROGRESS_CHANGES === $profile->getDownloadOnChange()) {
$this->getLogger()
Expand Down Expand Up @@ -238,7 +238,7 @@ public function statusCheck(array $submissions): void
$submissions = $this->submissionManager->storeSubmissions($statusCheckResult);

foreach ($submissions as $submission) {
$profile = $this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId());
$profile = $this->settingsManager->getProfileBySubmission($submission);
if ($profile->getDownloadOnChange() !== ConfigurationProfileEntity::TRANSLATION_DOWNLOAD_MODE_MANUAL) {
$this->checkEntityForDownload($submission);
}
Expand Down Expand Up @@ -273,7 +273,7 @@ public function getSmartlingLocaleIdBySubmission(SubmissionEntity $submission):
{
return $this->settingsManager
->getSmartlingLocaleIdBySettingsProfile(
$this->settingsManager->getSingleSettingsProfile($submission->getSourceBlogId()),
$this->settingsManager->getProfileBySubmission($submission),
$submission->getTargetBlogId()
);
}
Expand Down
Loading
Loading