Repository navigation
add filter for target content id, improve elementor related ids detection (WP-1020) - #637
Conversation
… (WP-1020) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d (WP-1020) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…y (WP-1020) - TargetIdFilter: tolerate any argument type, never throw, cache lookups in the object cache, log misses and ambiguity at notice, return invalid source id unchanged when falling back - Elementor query exclude_term_ids/exclude_ids are remapped to translated ids but no longer submitted for translation (Content::isRemapOnly) - validate Elementor query ids as integers, move loop widget related content into ElementorQueryRelated::addLoopRelated - add tests Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
sl-mmuradov
left a comment
There was a problem hiding this comment.
Thanks, the direction looks good. Replacing the trait with the composed ElementorQueryRelated removes the duplicated code in Posts and Loop Carousel, and the remap-only flag on Content is small and backward compatible.
Blocking
- Term ID type (please verify): as far as I know, Elementor Pro stores query term IDs as
term_taxonomy_ids, while we resolve and remap them asterm_ids. On sites where they differ, this submits the wrong terms and writes broken IDs back, which is the WP-1020 symptom itself. This existed before forinclude_term_ids, but the PR extends it to more settings and widgets. - Leftover query settings: IDs are collected regardless of the widget's query mode. Since
posts_idsis now submitted, an old hidden manual selection can queue many posts. Please check{prefix}post_type/{prefix}include/{prefix}excludefirst.
Should fix
normalizeReferences()overwrites taxonomy references instead of merging, which drops detected query terms when the document has its own terms in that taxonomy. Fix here or file a follow-up.- Document that excluded IDs are only remapped if the content is already translated when the translation is applied.
Notes on code not changed by this PR
inc/Smartling/Services/ContentRelationsDiscoveryService.php:576-578:$result[$taxonomy] = $ids;overwrites the result instead of merging. If an Elementor document has terms of its own in a taxonomy (e.g. a post with categories), any query terms from the same taxonomy that this PR detects are dropped from the related list. Suggest$result[$taxonomy] = array_merge($result[$taxonomy] ?? [], $ids);, either here or in a follow-up ticket.inc/Smartling/ContentTypes/Elementor/ElementAbstract.php:138: for query settings,getTerm()here receives aterm_taxonomy_id, not aterm_id(see the comment onElementorQueryRelated.php:22).tests/Mocks/WordpressFunctionsMockHelper.php:212:term_taxonomy_id => 0means the tests can't tellterm_idandterm_taxonomy_idapart. Worth giving it a different value once the ID type is handled.
Note: I couldn't run the test suite locally, so this review is based on reading the code.
| { | ||
| use LoggerSafeTrait; | ||
|
|
||
| private const TERM_ID_SUFFIXES = ['include_term_ids' => false, 'exclude_term_ids' => true]; |
There was a problem hiding this comment.
As far as I remember, Elementor Pro stores *_include_term_ids / *_exclude_term_ids as term_taxonomy_ids, not term_ids. Elementor_Post_Query::build_terms_query() resolves them with get_term_by('term_taxonomy_id', $id). Could you check this against the Elementor Pro source?
If that's right, this list is resolved wrongly downstream:
ElementAbstract::setRelations()(line 138) andContentRelationsDiscoveryService::normalizeReferences()both callgetTerm($id), which takes aterm_id.- Submissions are matched by
source_id, which is aterm_id.
On sites where the two IDs differ (older or multisite installs, terms that were split), we submit the wrong term, and on download we write a target term_id where Elementor expects a target term_taxonomy_id. That's the symptom described in WP-1020. Converting tt_id → term_id on the way in and term_id → tt_id when writing back should fix it.
There was a problem hiding this comment.
Confirmed against Elementor Pro 3.33.2 (modules/query-control/classes/elementor-post-query.php): build_terms_query() resolves the IDs with get_term_by( 'term_taxonomy_id', $id ) and builds the tax_query with 'field' => 'term_taxonomy_id'.
Fixed in faf5950: the query term lists are flagged with Content::isTermTaxonomyId(). ExternalContentElementorAbstract::getData() converts them to term_ids in the source blog (so discovery, submissions and the taxonomy lookup in setRelations() all work with term_ids), and setRelations() converts the translated term_id back to its term_taxonomy_id in the target blog before writing. Terms that cannot be resolved are dropped and logged instead of being submitted as a different term. The mock in WordpressFunctionsMockHelper now returns a term_taxonomy_id different from term_id, and there are handler tests for both directions. Documented in ELEMENTOR_DEVELOPMENT.md.
Only unit tests so far, I have not run it against a multisite where the two IDs differ.
| use LoggerSafeTrait; | ||
|
|
||
| private const TERM_ID_SUFFIXES = ['include_term_ids' => false, 'exclude_term_ids' => true]; | ||
| private const POST_ID_SUFFIXES = ['posts_ids' => false, 'exclude_ids' => true]; |
There was a problem hiding this comment.
These IDs are collected no matter which query mode the widget uses. Elementor keeps values of controls that are hidden, but only uses them when the mode matches:
posts_idsonly when{prefix}post_type === 'by_id'include_term_idsonly when{prefix}includecontainstermsexclude_ids/exclude_term_idsonly when{prefix}excludecontainsmanual_selection/terms
Collecting posts_ids is new in this PR. A widget that once had a manual selection and was then switched to "by category" will now submit all of those posts as related content, each with its own related content. Could we check the mode settings before adding the IDs?
There was a problem hiding this comment.
Agreed, and the source is stricter than the list in the review: with post_type = by_id only posts_ids is used, and the exclude, avoid-duplicates, terms and author args are skipped entirely. Otherwise posts_ids is ignored and the other lists need their mode to contain the expected value.
Fixed in 40e4c58 and 347aaee: ElementorQueryRelated now collects posts_ids only for by_id, and include_term_ids / exclude_term_ids / exclude_ids only when the mode is not by_id and {prefix}include / {prefix}exclude contain terms / terms / manual_selection. Tests cover both modes and leftover values; the mode table is in ELEMENTOR_DEVELOPMENT.md. On the data many widgets carry such leftovers (for example exclude_ids in by_id mode and posts_ids when the type is not by_id).
| $return = []; | ||
| foreach ($flat as $item) { | ||
| assert($item instanceof Content); | ||
| if ($item->isRemapOnly()) { |
There was a problem hiding this comment.
Remap-only IDs are only replaced if the content is already translated when the translation is applied. If the excluded term or post is translated later, the page keeps the source ID until it's downloaded again. That's fine as a trade-off, but please document it in ELEMENTOR_DEVELOPMENT.md and the release notes so support knows what to expect.
There was a problem hiding this comment.
Documented in ELEMENTOR_DEVELOPMENT.md (Pattern 5, "Excluded content is remap-only") and added to the 5.8.1 entry in the readme.txt changelog: excluded IDs are replaced only if the content is already translated when the page translation is applied; if it is translated later, the page has to be downloaded again.
…rge taxonomy references, document remap-only (WP-1020) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… mode (WP-1020) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Addressed in 40e4c58, 347aaee and faf5950:
Unit tests pass (754); not yet verified on a multisite where the IDs differ. |
|
|
||
| public function addRelated(RelatedContentInfo $info, array $settings, string $containerId, string $prefix): RelatedContentInfo | ||
| { | ||
| $isManualSelection = ($settings[$prefix . 'post_type'] ?? null) === self::MANUAL_SELECTION; |
There was a problem hiding this comment.
High: $isManualSelection only separates by_id from every other mode, so current_query (and possibly related) still collects leftover IDs.
As far as I remember, Elementor_Post_Query::get_query_args() returns early for {prefix}post_type === 'current_query': it uses the main $wp_query vars and ignores include/exclude completely. A widget switched from "by category" to "Current Query" still has include = ['terms'] and the term IDs saved, so those terms would be submitted as related content. That's the same leftover problem as before, in another mode.
Could you check current_query against the 3.33.2 source you used? Please also check related (Elementor_Related_Query), which I think ignores include_term_ids. If confirmed, skip everything for current_query, add the mode to the table in ELEMENTOR_DEVELOPMENT.md, and add a case to testIdsOfInactiveQueryModesAreIgnored.
There was a problem hiding this comment.
Confirmed against Elementor Pro 3.33.2:
current_query:Elementor_Post_Query::get_query_args()returns the main$wp_query->query_varsright away, so none of the include/exclude/ID settings is used.related:Elementor_Related_Queryoverridesbuild_terms_query_include()and takes the terms of the current post (related_taxonomies,tt_ids), soinclude_term_idsis ignored (the control is also hidden for it).exclude_term_idsandexclude_idsstill apply, so those are kept.
Fixed in 8537b2d: everything is skipped for current_query, only include_term_ids is skipped for related. The mode table in ELEMENTOR_DEVELOPMENT.md is now by post_type, testIdsOfInactiveQueryModesAreIgnored has the current-query case, and there is a separate related test.
| @@ -63,6 +63,9 @@ Additional information on the Smartling Connector for WordPress can be found [he | |||
|
|
|||
| == Changelog == | |||
| = 5.8.0 = | |||
There was a problem hiding this comment.
Medium (blocks merge until confirmed): following up on your note about moving the bullets. The merge base 804a95d3 already set Stable tag: 5.8.0 and Version: 5.8.0 in smartling-connector.php. If 5.8.0 has been published, these three bullets need a new version entry (5.8.1 / 5.9.0), plus the version bump in smartling-connector.php and composer.json.
There was a problem hiding this comment.
5.8.0 is published, so the entries moved to a new 5.8.1 section and I bumped the version in smartling-connector.php, composer.json and Stable tag in readme.txt (3d139b5). The 5.8.0 changelog entry is back to what was published. I also added a bullet for the numeric-key fix below.
| } | ||
|
|
||
| return new ExternalData(strings: $strings, relatedContentInfo: $related); | ||
| return new ExternalData(strings: $strings, relatedContentInfo: $this->convertTermTaxonomyIds($related, $sourceBlogId)); |
There was a problem hiding this comment.
Medium (existing code, but it affects the new query IDs): getData() combines each top-level element's related info with RelatedContentInfo::merge() (RelatedContentInfo.php:126). That uses array_merge(), which renumbers numeric keys. arrayMergePreserveKeys() exists because Elementor sometimes generates all-digit element IDs. A top-level container with such an ID loses its key, so setTargetContent() finds nothing at $info->getInfo()[$this->id] and none of the query IDs under it are remapped.
Suggest $result->info = $this->arrayMergePreserveKeys($result->info, $info->info); in merge(), here or in a follow-up ticket.
There was a problem hiding this comment.
Good catch. RelatedContentInfo::merge() now uses arrayMergePreserveKeys() (8537b2d), so all-digit container IDs keep their keys. ExternalData also uses merge() and benefits from it. The new test fails without the change. Added to the 5.8.1 changelog.
| $targetId = max(self::NOT_FOUND, current($submissions)->getTargetId()); | ||
| } | ||
| if ($targetId === self::NOT_FOUND) { | ||
| $this->getLogger()->notice(count($submissions) > 1 |
There was a problem hiding this comment.
Medium: this filter runs from theme/snippet code on front-end page views. Without a persistent object cache, WpObjectCache lasts one request. That means one find() query and one notice per untranslated ID per page view, and the logger runs at DEBUG by default (inc/config/logger.yml:6). A missing translation is expected here (the filter called on the source blog, content that isn't submitted). Suggest debug, or keep notice only for the ambiguous-match case.
There was a problem hiding this comment.
Agreed. A missing translation is now logged at debug; notice stays only for the ambiguous-match case (8537b2d).
…reserve numeric keys on merge, log missing target id at debug (WP-1020) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
No description provided.