Skip to content

Allow wpml_cf_preferences: 2 on image/gallery fields (Options Page house rule) - #29

Merged
parisek merged 5 commits into
mainfrom
fix/wpml-media-options-page
Jul 25, 2026
Merged

Allow wpml_cf_preferences: 2 on image/gallery fields (Options Page house rule)#29
parisek merged 5 commits into
mainfrom
fix/wpml-media-options-page

Conversation

@parisek

@parisek parisek commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

Four commits. The first widens a schema enum; the next three build and then harden a new --wpml linter check that the widening made necessary.

1. 487875e — allow wpml_cf_preferences: 2 on image/gallery (Options Page house rule)

  • field-image.schema.json / field-gallery.schema.json enforced an unconditional const: 1, curated against a live install in a post_type/block location context.
  • On an ACF Options Page, ACFML permanently locks a 1-flagged ("copy") field to its default-language value — there's no post duplication to copy from. tailwind-base's wordpress/gutenberg.md § Key Requirements and wordpress/wpml.md's gotcha table both document 2 for every value field on an options page, image/gallery included.
  • The schema has no per-location context threaded into per-field-type validation (a nested field doesn't know its ancestor group's location), so a conditional rule isn't expressible without a large restructuring. Both refs now accept enum: [1, 2] — the same trade-off already made for field-link/field-select/field-url.
  • Discovered while auditing parisek/definition-kit PR fix: unify field validation across nesting, complete per-type stubs (closes #8) #10's store-locator corpus fixture.

2. 43d40a4 — new --wpml location-value cross-check

The enum widening fixed the options-page false positive but opened a false negative in the far more common post/block context: an image/gallery field mistakenly set to 2 now validated silently, and translators lose per-language image swapping.

AcfLinter is the only place with both fields and location in view at once, so the check lives there (wpmlLocationValueFindings(), gated behind --wpml) rather than in the schema, which stays context-neutral by design.

3. 15b3ca6 — heterogeneous OR groups misclassified

classifyLocationContext() tracked only options_page vs post_type/block booleans; every other ACF param was invisible. A group located at options_page OR taxonomy collapsed to pure options-page context and demanded the options-page value from fields that also render on a term screen — contradicting the method's own documented policy of leaving genuinely dual-context groups alone.

4. 5c1a0ce — per-OR-group classification

The 15b3ca6 fix used one global flag, which could not distinguish "two AND-rules in one OR-group" from "two separate OR-groups". That silenced post_type AND page_template — one of the most common real ACF shapes — trading a false positive on a rare shape for a false negative on a common one.

Classification now runs per OR-group and unions the verdicts. All 24 params from location-rule.schema.json are assigned deliberately:

Bucket Params
Primary post/block post_type, block
Primary options options_page
Post-context qualifier page_template, post_template, post_status, post_format, post_category, post_taxonomy, post, page_type, page_parent, page, attachment
Neutral current_user, current_user_role
Other (distinct context) taxonomy, user_form, user_role, user, comment, widget, nav_menu, nav_menu_item

Behavioural widening worth calling out: besides fixing the AND-group regression, this also newly activates standalone qualifier groups. A group whose entire location is e.g. [[page_template == consulting.php]], with no post_type at all, previously resolved to ambiguous and was skipped; it now resolves to post-context and is checked. This is intentional — page_template can only ever apply to a post/page screen — but it is broader than "an AND-group fix" and will surface findings on files that were silently skipped before.

operator is deliberately ignored: post_type != page still means a post-type context, so the demanded value is unchanged by negation. Documented in the classifier's docblock.

Test plan

  • Failing tests written first at each step and confirmed to fail for the right reason before the fix.
  • composer test green — 162 tests, 607 assertions (1 pre-existing skip: SnapshotTest, requires a live WP install).
  • composer phpstan green, no baseline or ignores added.
  • src/templates/refs/ (source of truth) and schemas/refs/ (distribution mirror) edited identically — SchemaConsistencyTest passes.
  • Real-corpus run (--wpml --strict, read-only) against eprukaz2025 unchanged before/after 5c1a0ce. Noted explicitly: that corpus is all single-context block locations, so it does not exercise this bug — the unit tests are the evidence, not the unchanged count.
  • Cross-project validation: four real multi-param location shapes found across sibling projects (page_template + post_type, post_template + post_type, user_form + user_role, and a 6-group post_type/post_template mix) all classify correctly, no false positives.
  • CHANGELOG.md [Unreleased] entry added.

Known gap (non-blocking)

Regression coverage is thin relative to the 24-param table: only page_template and post_status are exercised as qualifier regressions, and no other-bucket param has a dedicated "mixed with post_type stays null" test. A future refactor could silently flip one of the 9 untested qualifier assignments without any test failing. Worth a follow-up; not a merge blocker.

https://claude.ai/code/session_01J51xpeny2K3mm3LCFVGc2w

…e house rule)

field-image.schema.json and field-gallery.schema.json enforced an
unconditional const:1, curated against a post_type/block-context live
install. On an ACF Options Page, ACFML permanently locks a 1-flagged
("copy") field to its default-language value since there's no post
duplication to copy from — tailwind-base's wordpress/gutenberg.md and
wordpress/wpml.md both document 2 for every options-page value field,
image/gallery included. The schema had no location context to enforce
1-only-outside-options-pages, so both refs now accept enum [1, 2].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq8VHGjkVG28SMVLCdu7B7
parisek and others added 4 commits July 24, 2026 14:29
location-aware --wpml cross-check

The enum: [1, 2] widening for image/gallery fixed the options-page
false positive but opened a false negative in the far more common
post/block context: an image/gallery field mistakenly set to
wpml_cf_preferences: 2 now validates silently there, and translators
lose per-language image swapping. Nothing caught this -- --wpml mode
only checked presence, never value-against-location -- confirmed
independently by both reviewers.

Per both reviewers' converged recommendation: this is a linter
concern, not a schema concern -- AcfLinter is the only place with both
`fields` and `location` in view at once, so the schema stays
context-neutral (per field-image.schema.json's own description) and
the check lives in a new wpmlLocationValueFindings() gated behind the
existing --wpml opt-in.

Behaviour:
- location resolves unambiguously to options_page-only -> requires 2
  for every image/gallery field (root + nested via sub_fields/layouts)
- location resolves unambiguously to post_type/block-only -> requires 1
- mixed location (targets both) is left alone -- no single correct
  value to demand without false-flagging a legitimate dual-context
  group

New tests cover both directions of the value mismatch, the previously
-fixed options-page-value-under-options-page-location case (regression
guard), the mixed-location non-flag, and a nested repeater sub-field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq8VHGjkVG28SMVLCdu7B7
The classifier tracked only options_page vs post_type/block booleans,
so any other ACF location param (taxonomy, nav_menu_item, user_form,
attachment, widget, comment, page_template, ...) was invisible to it.
A group whose location was `options_page OR taxonomy` collapsed to a
pure options-page context and wrongly demanded the options-page value
from fields that also render on a taxonomy term screen.

This contradicted the method's own documented policy: a group genuinely
targeting BOTH an options page and a post type is left alone (no single
correct value to demand) -- the same must hold for any other
unrecognized context mixed in, for the same reason.

Now tracks whether any unrecognized param was seen and returns null
(ambiguous, don't guess) whenever it coexists with a recognized
context. Pure single-context classification (options_page-only,
post_type/block-only) is unchanged and still flags correctly.

Also documents why `operator` (e.g. `!=`) is deliberately ignored in
this classifier: `post_type != page` still targets a post_type context,
just narrowed by value -- negation doesn't change which context a
param belongs to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq8VHGjkVG28SMVLCdu7B7
15b3ca6 introduced a single global $hasOther flag that made the
classifier bail to null whenever ANY unrecognized param appeared
anywhere in the location — including post-context qualifiers
(page_template, post_status, …) paired with post_type in the SAME
AND-group. That silently regressed the common real-world shape "field
group scoped to a post type AND a specific page template": it went
from correctly flagging to silently skipping.

Replace the global flag with per-OR-group classification
(classifyOrGroup()), enumerating all 24 declared location params into
three buckets: primary (post_type/block, options_page), post-context
qualifiers (page_template, post_template, post_status, post_format,
post_category, post_taxonomy, post, page_type, page_parent, page,
attachment — compatible with post_type_or_block, never competing for
context), neutral (current_user, current_user_role — coexist with
anything), and other (taxonomy, user_form, user_role, user, comment,
widget, nav_menu, nav_menu_item — genuinely distinct contexts).
classifyLocationContext() then unions group verdicts across OR-groups,
returning null only when groups genuinely disagree or a group resolves
to 'other'.

Regression tests: post_type AND page_template / post_type AND
post_status in a single OR-group now flag again. The 15b3ca6 fix's own
intent (options_page OR taxonomy, post_type OR user_form as separate
groups) stays silent — no regression there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J51xpeny2K3mm3LCFVGc2w
The [Unreleased] section documented only the enum widening (the first of
four commits). The new location-aware --wpml check that the widening made
necessary, and the two classifier fixes that followed, were missing --
including the behavioural widening to standalone qualifier groups, which
will surface findings on files previously skipped.
@parisek
parisek marked this pull request as ready for review July 25, 2026 08:58
@parisek
parisek merged commit 50e3f7d into main Jul 25, 2026
3 checks passed
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.

1 participant