Skip to content

fix(entries): accept stored asset paths on write (#41) - #42

Merged
sylvesterdamgaard merged 2 commits into
mainfrom
fix/41-asset-path-round-trip
Sep 4, 2026
Merged

fix(entries): accept stored asset paths on write (#41)#42
sylvesterdamgaard merged 2 commits into
mainfrom
fix/41-asset-path-round-trip

Conversation

@sylvesterdamgaard

@sylvesterdamgaard sylvesterdamgaard commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #41.

The bug

statamic-entries get returns an assets field the way Statamic stores it — a container-relative path, icons/heart.svg. Everything downstream of a Control Panel form submission expects the other form: the canonical container::path asset ID, in a list.

So a value round-tripped from get into update fails:

  • Statamic\Fieldtypes\Assets\MimesRule::passes() does Asset::find($id), and a bare path finds nothing → "must be a file of type: svg"
  • on a field with no file rules it would have got further and then broken in Assets::process(), which calls Asset::findOrFail()

It also hit fields the caller never touched: update validates the incoming data merged with the entry's stored data, and stored data is all paths, so any other assets field carrying required + mimes failed an unrelated update.

The Control Panel bridges this gap in Assets::preProcess() when it loads the form. Anything handing stored values to the validator without that step has to bridge it itself.

Second bug, same root cause

Double-checking the fix turned up content_validate, which sweeps content already on disk and so hands the validator stored values directly. It reported three false errors for every valid single-file assets field:

The Icon field must be a file of type: svg.
The Icon field must be an array.
The Icon field must not have more than 1 items.

The last two come from the fieldtype's own array/max:1 rules, which expect a list rather than the bare string Statamic stores.

The fix

Container and ID resolution live in a new ResolvesAssetIds concern, used from both sides:

  • Write path (SanitizesFieldData, so entries, terms and globals alike) — incoming asset paths are resolved before validation, covering nested replicator, bard, grid and group fields as well as top-level ones, and the stored data an update merges in.
  • Read path (ValidatesContentRecords) — the rule pass gets a normalized copy of the record. The structural pass keeps the raw values, so a missing_asset finding still quotes the reference exactly as it appears on disk.

Container resolution mirrors the fieldtype's own: the configured container, or the only one that exists. That last part also fixes assetFindings, which previously skipped single-container fields entirely. References that resolve to no asset are passed through untouched, so validation reports the real problem instead of silently dropping content. Canonical IDs continue to work unchanged.

Tests

  • tests/Feature/Routers/EntriesAssetRoundTripTest.php — 5 cases covering top-level, replicator, grid and bard-set asset fields on both create and update, an unrelated update that must not trip stored paths, plus regression guards (canonical IDs still accepted, missing asset still rejected).
  • ContentValidateTest::test_does_not_report_stored_asset_paths_as_rule_violations — top-level and nested.

Verified against main: 4 of these fail there, all pass here. Full gate green — pint, PHPStan level 9 (no ignores, no baseline), 1106 tests / 5609 assertions, license check.

Statamic stores an assets field as a container-relative path, which is
what `get` returns, but the validation rules and the fieldtype pipeline
both expect the asset ID the CP submits. Sending a value back unchanged
failed `mimes` — MimesRule does an Asset::find() on the value — and on a
rule-free field would have blown up later in Assets::process(), which
calls Asset::findOrFail().

Resolve incoming asset paths to canonical `container::path` IDs in the
shared sanitizer, so it covers nested replicator/grid/group/bard fields
and the stored data an update merges in as well as top-level fields, and
applies to entries, terms and globals alike. Unresolvable values are left
untouched so validation reports the real problem rather than silently
dropping content.
Extends the #41 fix to the read-side sweep, which had the same root
cause: content_validate ran the blueprint's rules against stored values,
so every valid single-file assets field produced up to three false
errors — the file rules resolve the value with Asset::find(), which a
bare path misses, and the fieldtype's own array/max rules expect a list
rather than the string Statamic stores.

Move the container and ID resolution into a shared ResolvesAssetIds
concern, and give the rule pass a normalized copy of the record that
recurses through replicator, bard, grid and group. The structural pass
keeps the raw values, so a missing_asset finding still quotes what is on
disk. assetFindings now resolves single-container fields too, matching
the fieldtype.
@sylvesterdamgaard
sylvesterdamgaard merged commit 772284c into main Sep 4, 2026
26 of 27 checks passed
@sylvesterdamgaard
sylvesterdamgaard deleted the fix/41-asset-path-round-trip branch September 4, 2026 09:32
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.

Statamic-entries update fails SVG MIME validation for asset paths returned by get

1 participant