Skip to content

[6.x] Ensure set preview images are updated when assets are renamed/replaced/deleted#13013

Open
duncanmcclean wants to merge 23 commits into
6.xfrom
update-set-preview-images
Open

[6.x] Ensure set preview images are updated when assets are renamed/replaced/deleted#13013
duncanmcclean wants to merge 23 commits into
6.xfrom
update-set-preview-images

Conversation

@duncanmcclean
Copy link
Copy Markdown
Member

@duncanmcclean duncanmcclean commented Nov 11, 2025

This pull request ensures that Set Preview Images on Bard & Replicator fields are updated when assets are renamed/replaced/deleted, like we do with content.

I've also added Blueprint::all() which returns blueprints from all collections, taxonomies, etc.

Closes #12680

@duncanmcclean duncanmcclean marked this pull request as ready for review November 12, 2025 11:57
Copy link
Copy Markdown
Member

@jasonvarga jasonvarga left a comment

Choose a reason for hiding this comment

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

Unless I've done something silly, this doesn't seem to work at all. I can't get it to rename anything.

Plus, I think it might not work on a second level if you've defined statamic.assets.set_preview_images.folder as the path won't get saved in the blueprint - just the filename. We might need to change that.

When assets are deleted, the reference updater is called, which subsequently attempts to find the configured container for set preview images.

However, the default container, `assets` doesn't exist, but if we don't mock it, the tests fail.
@jasonvarga jasonvarga changed the base branch from master to 6.x January 28, 2026 16:29
Copy link
Copy Markdown
Member

@jasonvarga jasonvarga left a comment

Choose a reason for hiding this comment

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

This review was generated via a Claude-powered code review.

Solid, well-tested PR overall. The asset-listener flow is straightforward and the test coverage is comprehensive for the happy paths. A few real bugs and notes below.


Warning — Container is not filtered for blueprint/fieldset updates

src/Listeners/UpdateAssetReferences.php:117-135 calls the updater without filterByContainer($container), and src/Assets/AssetReferenceUpdater.php:76-134 (updateBlueprintFields) never checks the asset's container against Sets::previewImageConfig()['container']. The folder string is the only thing being matched.

Consequence: if statamic.assets.set_preview_images.container is previews but an asset is renamed in some other container (say documents) where the path happens to start with the same folder/, the blueprint's set.image (which logically points at the previews container) will be rewritten — corrupting the reference.

Suggested fix in updateBlueprintFields:

if (
    ! ($config = Sets::previewImageConfig())
    || $this->container !== $config['container']
    || ! Str::startsWith($this->originalValue, $config['folder'].'/')
) {
    return;
}

…and have the listener call ->filterByContainer($container) before ->updateReferences(...) on the Blueprint::all() / Fieldset::all() loops, mirroring the entry loop above.

A regression test where two containers share the same folder/filename would catch this.


Warning — Blueprint::all() misses several real namespaces

src/Fields/BlueprintRepository.php:27-37 hardcodes 'navigation', 'assets', 'globals', 'forms' plus collections/taxonomies/additional namespaces, but does not include:

  • user and user_group (root-level, used at src/Auth/UserRepository.php:66 and src/Auth/UserGroupRepository.php:19)
  • anything stored in the root via Blueprint::in('')

User blueprints can contain bard/replicator fields, so a preview image inside a user blueprint won't be updated. Worth either including the user/user_group blueprints explicitly, or iterating every file under directory() rather than enumerating namespaces.

The hardcoded list is also brittle for the future — any new core blueprint namespace will silently be missed by every reference updater.


Note — setParent(null) in makeBlueprintFromFile is unrelated and undocumented

src/Fields/BlueprintRepository.php:350 adds ->setParent(null) to every blueprint coming out of makeBlueprintFromFile. There's no test exercising it and the PR description doesn't mention it. It only runs when BLINK_FROM_FILE misses (so it doesn't clobber a parent set on a later find against the same path), but it will clobber any parent set during construction by something else that holds the same instance.

If this was needed because Blueprint::all() iterations were producing parent-leaked instances, that motivation should be either in a code comment or split into its own commit so the reasoning is recoverable.


Note — Performance: full blueprint/fieldset enumeration on every asset save

UpdateAssetReferences::replaceReferences now walks Blueprint::all() and Fieldset::all() on every save/replace/delete, regardless of whether statamic.assets.set_preview_images is configured. The early-return inside updateBlueprintFields saves the inner work, but instantiating every blueprint/fieldset still pays disk I/O.

A cheap up-front check at the top of replaceReferences would skip both loops entirely when the feature is off:

if (Sets::previewImageConfig()) {
    Blueprint::all()->each(...);
    Fieldset::all()->each(...);
}

(Combined with the container fix above, this can also short-circuit on container mismatch.)


Note — findFieldsInBlueprintContents matches anywhere type is a string

src/Data/DataReferenceUpdater.php:102-116 walks all nested arrays and triggers on any 'type' => 'bard' / 'type' => 'replicator' key/value pair. It doesn't constrain to "this is actually a fieldtype declaration." In practice, blueprint contents are unlikely to have these strings in validate/if/etc., and the downstream if (! isset($fieldContents['sets'])) skip makes false matches harmless. But it's worth a comment noting the heuristic — or restricting to keys living under field to make the intent obvious.


Note — Old set schema (no group wrapper) is silently skipped

updateBlueprintFields assumes the modern grouped structure (sets.<group>.sets.<setHandle>). A blueprint authored with the older flat schema (sets.<setHandle> directly, no group) hits the ! isset($setGroup['sets']) branch and returns untouched, even if it has an image. If 6.x still loads old-format blueprints from disk this is a gap; if not, ignore.


Tests look good

The cases in tests/Listeners/UpdateAssetReferencesTest.php cover rename, move-out-of-folder, delete, no-config, and the wrong-folder negative case. Two cases worth adding given the findings above:

  1. Two containers: configure set_preview_images.container = 'A', place a same-named asset in container B, rename it, assert no blueprint changes. (Would catch the missing-container-filter bug.)
  2. User blueprint with a bard set image. (Would catch the Blueprint::all() namespace gap.)

Misc

  • src/Fields/FieldsetRepository.php:176, 187 — refreshing the $fieldsets cache on save/delete is a nice correctness fix; useful independently of this PR.
  • Blueprint::all() is a generally useful new public API; might want a short PHPDoc explaining the namespace ordering since callers (and the it_gets_all_blueprints test) rely on it.

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.

Renaming an asset doesn't update the image value for replicators and bards.

2 participants