Skip to content

rework ACF fields processing (WP-991)#616

Open
vsolovei-smartling wants to merge 13 commits into
masterfrom
WP-991-fix-attachment-cloning-to-target-site
Open

rework ACF fields processing (WP-991)#616
vsolovei-smartling wants to merge 13 commits into
masterfrom
WP-991-fix-attachment-cloning-to-target-site

Conversation

@vsolovei-smartling
Copy link
Copy Markdown
Contributor

No description provided.

vsolovei-smartling and others added 9 commits April 29, 2026 15:29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
add max recursion depth, stricter types, improve run if needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
self::MAX_ACF_FIELD_DEPTH,
$field['key'],
));
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should it return? Can it just clamp the value?

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.

Not sure I've understand the clamping part, the return causes the function to stop any code below from running, so any deeper nested elements are not processed

public function runIfRequired(): void
{
if ($this->definitions === null) {
$this->run();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks weird. Hope it is done this way intentionally )

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.

Collecting definitions is an expensive action, so we don't load them every time plugin loads. External callers should not explicitly call run or runIfRequired. Reworked

public function getReferencedTypeByKey($key): string
public function getReferencedTypeByKey(string $key): string
{
if ($this->definitions === null) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we call runIfRequired here to reduce similar code?

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.

Done

}
}

return $fieldKey;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How did it work before? Was $fieldKey boolean? It is still boolean?

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.

Simplified code

return CustomFieldFilterHandler::getProcessor(Bootstrap::getContainer(), $config);
$configuration['pattern'] = sprintf('^%s$', $fieldName);
$result = CustomFieldFilterHandler::getProcessor(Bootstrap::getContainer(), $configuration);
return $result ?: null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What is purpose to check return value for null here?

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.

CustomFieldFilterHandler::getProcessor return false when a processor is not found, and null is used in newer code. This function must return a null or MetaFieldProcessorInterface, returning false would result in a fatal error.

Copy link
Copy Markdown
Contributor

@PavelLoparev PavelLoparev left a comment

Choose a reason for hiding this comment

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

Issues

🔴 Critical

inc/Smartling/WP/Table/QueueManagerTableWidget.php:276 — Float 0.001 passed to acquireLock() which has int $ttlSeconds type hint.

Without declare(strict_types=1), PHP silently coerces 0.0010, making the lock expire immediately — the duplicate-run guard never fires. The test covering this call path (testPrepareItemsShowsRunningMessageWhenSdkWrapsGuzzle423) was deleted in this same PR, so there is no safety net.

// Fix:
$this->api->acquireLock($profile, JobAbstract::CRON_FLAG_PREFIX . $jobName, 1);

🟡 Warning

inc/Smartling/Extensions/Acf/AcfDynamicSupport.php:141,174acf_get_fields() returns false when a group has no fields; iterating false with foreach raises a PHP warning.

// Fix:
foreach ((acf_get_fields($group) ?: []) as $field) {

inc/Smartling/Extensions/Acf/AcfDynamicSupport.phploadDefinitions — If ACF is inactive, $this->definitions stays null after tryRegisterACF, so runIfRequired() re-enters run() on every subsequent call. Fix: assign $this->definitions = [] in the else branch.

tests/Smartling/Extensions/Acf/AcfTypeDetectorTest.php:testGetProcessorForGutenberg — Test still sets the $acf_stores global which is now dead code after the refactor to acf_get_field_groups(). The test is fragile and its intent is unclear.

inc/Smartling/Helpers/RelativeLinkedAttachmentCoreHelper.php:63-65 — The count($this->acfDefinitions) === 0 guard is now indistinguishable between "not loaded" and "ACF has no fields," causing redundant run() calls. Use runIfRequired() instead.

inc/Smartling/Extensions/Acf/AcfDynamicSupport.php:addAcfFieldToDefs — Container fields with both a DB ID and inline sub_fields will have sub-fields processed twice (once from DB, once from inline array).


🔵 Suggestion

inc/Smartling/ContentTypes/Elementor/ElementAbstract.php:113assert() is disabled in production (zend.assertions = -1). Replace with an explicit instanceof check and log/exception.

inc/Smartling/DbAl/WordpressContentEntities/PostEntityStd.php:106get_edit_post_link() default context changed from 'edit' to 'display' — different URL encoding. Verify intent.

inc/Smartling/Extensions/Acf/AcfDynamicSupport.phpgetDefinitions() returns [] for both "not loaded" and "zero fields" — misleading API. Consider returning null when uninitialized.


Recommendations

Behavioral change — local ACF field groups: The old code handled PHP/JSON-registered ACF groups via $acf_local/$acf_stores. The new code skips groups where groupId <= 0, meaning fields defined via register_field_group() without a DB post will no longer appear in $definitions. Needs product/QA sign-off.

Test coverage gap: testPrepareItemsShowsRunningMessageWhenSdkWrapsGuzzle423 was deleted with no replacement. The behavioral change that motivated it makes sense, but the regression path is now uncovered.

Strict types: None of these files declare strict_types=1 — the critical float-to-int coercion bug is a direct consequence.


Assessment

Ready to merge? No — With fixes

Reasoning: The float 0.001 silently coercing to int 0 disables the distributed lock probe in QueueManagerTableWidget, which is the production guard against concurrent job runs — a silent behavioral regression that could cause duplicate job execution. The acf_get_fields() returning false without a null guard will also produce PHP warnings in any field group with zero children.


🤖 Review generated with Claude Code (claude-pr-review-local skill)

vsolovei-smartling and others added 4 commits May 15, 2026 15:23
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vsolovei-smartling
Copy link
Copy Markdown
Contributor Author

Issues

🔴 Critical

inc/Smartling/WP/Table/QueueManagerTableWidget.php:276 — Float 0.001 passed to acquireLock() which has int $ttlSeconds type hint.
Merged master into this branch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants