diff --git a/inc/Smartling/ApiWrapper.php b/inc/Smartling/ApiWrapper.php index 25425ca8..2fd6ba5c 100644 --- a/inc/Smartling/ApiWrapper.php +++ b/inc/Smartling/ApiWrapper.php @@ -103,7 +103,7 @@ private function getAuthProvider(ConfigurationProfileEntity $profile): AuthToken return $authProvider; } - public function acquireLock(ConfigurationProfileEntity $profile, string $key, int $ttlSeconds): \DateTime + public function acquireLock(ConfigurationProfileEntity $profile, string $key, float $ttlSeconds): \DateTime { return DistributedLockServiceApi::create($this->getAuthProvider($profile), $profile->getProjectId(), new NullLogger()) ->acquireLock("{$profile->getProjectId()}-$key", $ttlSeconds); @@ -125,7 +125,7 @@ public function getSourceLocale(ConfigurationProfileEntity $profile): string return $details['sourceLocaleId']; } - public function renewLock(ConfigurationProfileEntity $profile, string $key, int $ttlSeconds): \DateTime + public function renewLock(ConfigurationProfileEntity $profile, string $key, float $ttlSeconds): \DateTime { return DistributedLockServiceApi::create($this->getAuthProvider($profile), $profile->getProjectId(), $this->getLogger()) ->renewLock("{$profile->getProjectId()}-$key", $ttlSeconds); diff --git a/inc/Smartling/ApiWrapperInterface.php b/inc/Smartling/ApiWrapperInterface.php index 3cec18ad..dfce0609 100644 --- a/inc/Smartling/ApiWrapperInterface.php +++ b/inc/Smartling/ApiWrapperInterface.php @@ -48,14 +48,14 @@ interface ApiWrapperInterface /** * @throws SmartlingApiException */ - public function acquireLock(ConfigurationProfileEntity $profile, string $key, int $ttlSeconds): \DateTime; + public function acquireLock(ConfigurationProfileEntity $profile, string $key, float $ttlSeconds): \DateTime; public function getSourceLocale(ConfigurationProfileEntity $profile): string; /** * @throws SmartlingApiException */ - public function renewLock(ConfigurationProfileEntity $profile, string $key, int $ttlSeconds): \DateTime; + public function renewLock(ConfigurationProfileEntity $profile, string $key, float $ttlSeconds): \DateTime; /** * @throws SmartlingApiException diff --git a/inc/Smartling/ApiWrapperWithRetries.php b/inc/Smartling/ApiWrapperWithRetries.php index dcbece89..ecf32543 100644 --- a/inc/Smartling/ApiWrapperWithRetries.php +++ b/inc/Smartling/ApiWrapperWithRetries.php @@ -23,7 +23,7 @@ public function __construct(private ApiWrapperInterface $base, private int $retr { } - public function acquireLock(ConfigurationProfileEntity $profile, string $key, int $ttlSeconds): \DateTime + public function acquireLock(ConfigurationProfileEntity $profile, string $key, float $ttlSeconds): \DateTime { return $this->withRetry(function () use ($profile, $key, $ttlSeconds) { return $this->base->acquireLock($profile, $key, $ttlSeconds); @@ -37,7 +37,7 @@ public function getSourceLocale(ConfigurationProfileEntity $profile): string }); } - public function renewLock(ConfigurationProfileEntity $profile, string $key, int $ttlSeconds): \DateTime + public function renewLock(ConfigurationProfileEntity $profile, string $key, float $ttlSeconds): \DateTime { return $this->withRetry(function () use ($profile, $key, $ttlSeconds) { return $this->base->renewLock($profile, $key, $ttlSeconds); diff --git a/inc/Smartling/ContentTypes/Elementor/ElementAbstract.php b/inc/Smartling/ContentTypes/Elementor/ElementAbstract.php index c2dd8084..aef00331 100644 --- a/inc/Smartling/ContentTypes/Elementor/ElementAbstract.php +++ b/inc/Smartling/ContentTypes/Elementor/ElementAbstract.php @@ -193,7 +193,6 @@ public function setTargetContent( } $this->raw['elements'] = $this->elements; foreach ($info->getOwnRelatedContent($this->id) as $path => $content) { - assert($content instanceof Content); $this->raw = $this->setRelations($content, $externalContentElementor, $path, $submission)->toArray(); } diff --git a/inc/Smartling/DbAl/WordpressContentEntities/PostEntityStd.php b/inc/Smartling/DbAl/WordpressContentEntities/PostEntityStd.php index e4b0a133..efea6be5 100644 --- a/inc/Smartling/DbAl/WordpressContentEntities/PostEntityStd.php +++ b/inc/Smartling/DbAl/WordpressContentEntities/PostEntityStd.php @@ -103,7 +103,7 @@ public function getContentTypeProperty(): string public function getEditLink(): ?string { - return $this->wordpressFunctionProxyHelper->get_edit_post_link($this->ID); + return $this->wordpressFunctionProxyHelper->get_edit_post_link($this->ID, 'edit'); } public function getId(): ?int diff --git a/inc/Smartling/WP/Table/QueueManagerTableWidget.php b/inc/Smartling/WP/Table/QueueManagerTableWidget.php index 7a7333a0..5bc497eb 100644 --- a/inc/Smartling/WP/Table/QueueManagerTableWidget.php +++ b/inc/Smartling/WP/Table/QueueManagerTableWidget.php @@ -16,6 +16,7 @@ use Smartling\Settings\ConfigurationProfileEntity; use Smartling\Settings\SettingsManager; use Smartling\Submissions\SubmissionManager; +use Smartling\Vendor\GuzzleHttp\Exception\RequestException; use Smartling\Vendor\Smartling\Exceptions\SmartlingApiException; use Smartling\WP\Controller\ConfigurationProfilesController; use Smartling\WP\Controller\SmartlingListTable; @@ -260,13 +261,25 @@ private function queuePurgeLink(bool $isQueueEmpty, string $queueName): string private function getRunningMessage(SmartlingApiException $e): string { - if (count($e->getErrorsByKey('resource.locked')) > 0) { + if ($this->isResourceLocked($e)) { return self::MESSAGE_RUNNING; } return '' . __('API error (check credentials)') . ': ' . esc_html($e->getMessage()); } + private function isResourceLocked(SmartlingApiException $e): bool + { + if (count($e->getErrorsByKey('resource.locked')) > 0) { + return true; + } + + $previous = $e->getPrevious(); + return $previous instanceof RequestException + && $previous->hasResponse() + && $previous->getResponse()->getStatusCode() === 423; + } + /** * @throws SmartlingApiException */ diff --git a/tests/Smartling/WP/Table/QueueManagerTableWidgetTest.php b/tests/Smartling/WP/Table/QueueManagerTableWidgetTest.php index 76749311..94ecdf32 100644 --- a/tests/Smartling/WP/Table/QueueManagerTableWidgetTest.php +++ b/tests/Smartling/WP/Table/QueueManagerTableWidgetTest.php @@ -52,6 +52,9 @@ function admin_url($path = '') use Smartling\Settings\Locale; use Smartling\Settings\SettingsManager; use Smartling\Submissions\SubmissionManager; + use Smartling\Vendor\GuzzleHttp\Exception\RequestException; + use Smartling\Vendor\GuzzleHttp\Psr7\Request; + use Smartling\Vendor\GuzzleHttp\Psr7\Response; use Smartling\Vendor\Smartling\Exceptions\SmartlingApiException; use Smartling\WP\Table\QueueManagerTableWidget; @@ -149,5 +152,31 @@ public function testPrepareItemsShowsRunningMessageWhenLockHeld(): void $uploadRow = $widget->items[0]; $this->assertStringContainsString('Running', $uploadRow['run_cron']); } + + public function testPrepareItemsShowsRunningMessageWhenSdkWrapsGuzzle423(): void + { + // Reproduces the real SDK behavior: BaseApiAbstract::sendRequest() catches + // Guzzle's ClientException for a 423 Locked response and wraps it in a + // SmartlingApiException whose errors array is empty (only the message is set). + $guzzleException = new RequestException( + 'Client error: `POST https://api.smartling.com/distributed-lock-api/v2/projects/x/locks` resulted in a `423 Locked` response', + new Request('POST', 'https://api.smartling.com/distributed-lock-api/v2/projects/x/locks'), + new Response(423, [], '{"response":{"code":"RESOURCE_LOCKED","errors":[{"key":"resource.locked","message":"Failed to acquire a lock"}]}}'), + ); + $api = $this->createMock(ApiWrapperInterface::class); + $api->method('acquireLock')->willThrowException(new SmartlingApiException( + 'Guzzle:RequestException: ' . $guzzleException->getMessage(), + 0, + $guzzleException, + )); + + $widget = $this->buildWidget($api, uploadQueueCount: 3); + + $widget->prepare_items(); + + $uploadRow = $widget->items[0]; + $this->assertStringContainsString('Running', $uploadRow['run_cron']); + $this->assertStringNotContainsString('API error', $uploadRow['run_cron']); + } } }