Skip to content

acf-lint --wpml: the repeater 'forced to 3' rule is options-page-only, and fires everywhere #39

Description

@parisek

Summary

acf-lint --wpml rejects any repeater / flexible_content whose wpml_cf_preferences is not 3, on the grounds that ACFML forces the value at runtime and anything else is dead configuration (src/Lint/AcfLinter.php:243).

That is true on the options-page path and not true on the post/block path. The rule does not distinguish them, so on a post_type field group it reports a real, deliberate configuration as dead — and its advice, followed literally, reverts a production bug fix.

Where the claim holds

On options pages, ACFML\Options\EditorHooks::onUpdateMainValue() (acfml/classes/Options/EditorHooks.php:174) returns early for wrappers:

if ( Fields::isWrapperOrGroup( $field ) ) {
    $this->maybeCopyWrapperToTranslations( $value, $field );
    return $value;
}

The preference switch below is never reached, so the configured value genuinely does not drive copying. Same early return at :114 and :287.

Where it does not

For post/block field groups the relevant path is WPML_ACF_Field_Settings. field_should_be_set_to_copy_once() (class-wpml-acf-field-settings.php:331) is used in exactly one place — as an || inside is_field_parsable() (:127-128):

return ( isset( $field['wpml_cf_preferences'], $field['name'] ) && $this->isValidFieldPreference( … ) && $field['name'] )
    || $this->field_should_be_set_to_copy_once( $field );

It widens which fields get processed. It does not set a value. save_field_settings() (:161) then writes the configured preference into the translation-management settings:

$this->maybe_set_new_preference( $setting_index, $field['name'], $field['wpml_cf_preferences'] );

Fields::WRAPPER_FIELDS, the other constant the message cites, backs only isWrapper() / isWrapperOrGroup() — structural predicates for "does this field wrap sub_fields". The two places that select WPML_COPY_ONCE_CUSTOM_FIELD (class-wpml-acf-field-settings.php:283, :439) choose it from the field group's Mode, for system fields, not by field type.

So the mechanism the message names does not support the claim, even in the context where the conclusion happens to hold.

Empirical half

Downstream on fellows, templates/room_type/acf.json has a style_galleries repeater on a post_type field group. It was moved from 3 to 1 on purpose (portadesign/fellows#96): Czech flats were falling back to the shared gallery because WPML copied the repeater's sub-field rows into translations without the row-count meta ACF reads first. The change worked, and shipped with a data migration to recompute counts on room types translated earlier.

A value the plugin ignored could not have done that.

Today acf-lint --wpml flags that field, and the obvious response — flip it back to 3 — is precisely what that bug report and migration moved away from. With no local ignore by design, the project is left with a permanently red lint or a regression.

Suggested direction

The package already has the machinery: classifyLocationContext() (src/Lint/AcfLinter.php:321) exists for the image/gallery preference rules and encodes the same options-page-vs-post distinction the doctrine documents. The pluginForced branch at :243 simply does not consult it.

Scoping that branch to the options-page context would make the rule match the plugin. I have not opened a PR because I am not sure whether you would prefer scoping it, downgrading it to a warning, or dropping it — and the wording of the message needs revisiting either way, since the cited mechanism is not the one doing the work.

Happy to do the PR if you say which shape you want.

Found while bumping a consuming project to 0.7.6.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions