From f9b1d5185a755245edb99156fc28fcdf16d19637 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 11 Nov 2025 15:31:57 +0000 Subject: [PATCH 01/18] Add tests --- tests/Listeners/UpdateAssetReferencesTest.php | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/Listeners/UpdateAssetReferencesTest.php b/tests/Listeners/UpdateAssetReferencesTest.php index ca38fb315ef..c1b2b683105 100644 --- a/tests/Listeners/UpdateAssetReferencesTest.php +++ b/tests/Listeners/UpdateAssetReferencesTest.php @@ -1850,6 +1850,137 @@ public function it_only_saves_items_when_there_is_something_to_update() $this->assetHoff->path('hoff-new.jpg')->save(); } + #[Test] + public function it_updates_references_in_bard_and_replicator_fields_in_blueprints() + { + $collection = tap(Facades\Collection::make('articles'))->save(); + $blueprint = $collection->entryBlueprint(); + + $blueprint->setContents([ + 'fields' => [ + [ + 'handle' => 'content', + 'field' => [ + 'type' => 'bard', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => $this->assetHoff->path(), + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + 'second_set' => [ + 'image' => 'marty.png', + 'fields' => [['handle' => 'bar', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + [ + 'handle' => 'page_builder', + 'field' => [ + 'type' => 'replicator', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => $this->assetHoff->path(), + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + 'second_set' => [ + 'image' => 'marty.png', + 'fields' => [['handle' => 'bar', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + ], + ])->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + $this->assertEquals('marty.png', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.second_set.image')); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.first_set.image')); + $this->assertEquals('marty.png', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.second_set.image')); + + $this->assetHoff->path('destination/hoff.jpg')->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('destination/hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('marty.png', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.second_set.image')); + $this->assertEquals('destination/hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('marty.png', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.second_set.image')); + } + + #[Test] + public function it_updates_references_in_bard_and_replicator_fields_in_fieldsets() + { + $fieldset = Facades\Fieldset::make('stuff'); + + $fieldset->setContents([ + 'fields' => [ + [ + 'handle' => 'content', + 'field' => [ + 'type' => 'bard', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => $this->assetHoff->path(), + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + 'second_set' => [ + 'image' => 'marty.png', + 'fields' => [['handle' => 'bar', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + [ + 'handle' => 'page_builder', + 'field' => [ + 'type' => 'replicator', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => $this->assetHoff->path(), + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + 'second_set' => [ + 'image' => 'marty.png', + 'fields' => [['handle' => 'bar', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + ], + ])->save(); + + $fieldset = Facades\Fieldset::find('stuff'); + $this->assertEquals('hoff.jpg', Arr::get($fieldset->contents(), 'fields.0.field.sets.set_group.sets.first_set.image')); + $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.0.field.sets.set_group.sets.second_set.image')); + $this->assertEquals('hoff.jpg', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.first_set.image')); + $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.second_set.image')); + + $this->assetHoff->path('destination/hoff.jpg')->save(); + + $fieldset = Facades\Fieldset::find('stuff'); + $this->assertEquals('destination/hoff.jpg', Arr::get($fieldset->contents(), 'fields.0.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.0.field.sets.set_group.sets.second_set.image')); + $this->assertEquals('destination/hoff.jpg', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.second_set.image')); + } + protected function setSingleBlueprint($namespace, $blueprintContents) { $blueprint = tap(Facades\Blueprint::make('single-blueprint')->setContents($blueprintContents))->save(); From 43fe66907bf90fe9dc8830c242f80329b3e375ff Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 11 Nov 2025 15:32:06 +0000 Subject: [PATCH 02/18] wip --- src/Listeners/UpdateAssetReferences.php | 81 +++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/Listeners/UpdateAssetReferences.php b/src/Listeners/UpdateAssetReferences.php index a7deae9e6fb..7765cd2aeb1 100644 --- a/src/Listeners/UpdateAssetReferences.php +++ b/src/Listeners/UpdateAssetReferences.php @@ -9,6 +9,14 @@ use Statamic\Events\AssetReplaced; use Statamic\Events\AssetSaved; use Statamic\Events\Subscriber; +use Statamic\Facades\AssetContainer; +use Statamic\Facades\Blueprint; +use Statamic\Facades\Collection; +use Statamic\Facades\Form; +use Statamic\Facades\GlobalSet; +use Statamic\Facades\Nav; +use Statamic\Facades\Taxonomy; +use Statamic\Support\Arr; class UpdateAssetReferences extends Subscriber implements ShouldQueue { @@ -99,8 +107,81 @@ protected function replaceReferences($asset, $originalPath, $newPath) } }); + $this + ->getBlueprintsContainingData() + ->each(function ($blueprint) use ($originalPath, $newPath) { + $hasUpdatedBlueprint = false; + + $contents = $blueprint->contents(); + $fieldtypes = ['bard', 'replicator']; + $bigArrayOfFields = []; + + foreach ($contents as $key => $value) { + $this->findFields($value, $fieldtypes, $key, $bigArrayOfFields); + } + + foreach ($bigArrayOfFields as $path) { + $fieldContents = Arr::get($contents, $path); + + $fieldContents['sets'] = collect($fieldContents['sets']) + ->map(function ($setGroup) use ($originalPath, $newPath, &$hasUpdatedBlueprint) { + $setGroup['sets'] = collect($setGroup['sets']) + ->map(function ($set) use ($originalPath, $newPath, &$hasUpdatedBlueprint) { + if ($set['image'] === $originalPath) { + $set['image'] = $newPath; + $hasUpdatedBlueprint = true; + } + + return $set; + }) + ->all(); + + return $setGroup; + }) + ->all(); + + Arr::set($contents, $path, $fieldContents); + } + + if ($hasUpdatedBlueprint) { + $blueprint->setContents($contents)->save(); + } + }); + if ($hasUpdatedItems) { AssetReferencesUpdated::dispatch($asset); } } + + protected function findFields(array $array, array $fieldtypes, string $dottedPrefix, array &$bigArrayOfFields) + { + foreach ($array as $key => $value) { + if (is_array($value)) { + $this->findFields($value, $fieldtypes, "$dottedPrefix.$key", $bigArrayOfFields); + } elseif (is_string($value)) { + if ($key === 'type' && in_array($value, $fieldtypes)) { + $bigArrayOfFields[] = $dottedPrefix; + } + } + } + } + + protected function getBlueprintsContainingData() + { + // $additionalBlueprints = Blueprint::getAdditionalNamespaces() + // ->keys() + // ->map(fn ($namespace) => Blueprint::in($namespace)) + // ->all(); + + $additionalBlueprints = []; + + return collect() + ->merge(Collection::all()->flatMap->entryBlueprints()) + ->merge(Taxonomy::all()->flatMap->termBlueprints()) + ->merge(Nav::all()->map->blueprint()) + ->merge(AssetContainer::all()->map->blueprint()) + ->merge(GlobalSet::all()->map->blueprint()) + ->merge(Form::all()->map->blueprint()) + ->merge($additionalBlueprints); + } } From f25348a563cac9b09b372fc54d4d536f02f82646 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 11 Nov 2025 16:25:39 +0000 Subject: [PATCH 03/18] Merge & extract --- src/Assets/AssetReferenceUpdater.php | 37 +++++++++++++++++++ src/Data/DataReferenceUpdater.php | 41 ++++++++++++++++++++- src/Listeners/UpdateAssetReferences.php | 47 +++++++------------------ src/Taxonomies/TermReferenceUpdater.php | 10 ++++++ 4 files changed, 100 insertions(+), 35 deletions(-) diff --git a/src/Assets/AssetReferenceUpdater.php b/src/Assets/AssetReferenceUpdater.php index e0e86c1c77f..0f4247a08aa 100644 --- a/src/Assets/AssetReferenceUpdater.php +++ b/src/Assets/AssetReferenceUpdater.php @@ -333,4 +333,41 @@ private function updateStatamicUrlsInLinkNodes($field, $dottedPrefix) $this->updated = true; } + + /** + * Update fields in blueprints and fieldsets. + * + * @return void + */ + protected function updateBlueprintFields() + { + $contents = $this->item->contents(); + + $fieldPaths = $this->findFieldsInBlueprintContents($contents, fieldtypes: ['bard', 'replicator']); + + foreach ($fieldPaths as $fieldPath) { + $fieldContents = Arr::get($contents, $fieldPath); + + $fieldContents['sets'] = collect($fieldContents['sets']) + ->map(function ($setGroup) { + $setGroup['sets'] = collect($setGroup['sets']) + ->map(function ($set) { + if (isset($set['image']) && $set['image'] === $this->originalValue) { + $set['image'] = $this->newValue; + $this->updated = true; + } + + return $set; + }) + ->all(); + + return $setGroup; + }) + ->all(); + + Arr::set($contents, $fieldPath, $fieldContents); + } + + $this->item->setContents($contents); + } } diff --git a/src/Data/DataReferenceUpdater.php b/src/Data/DataReferenceUpdater.php index 8c5cc343a5b..5f5c094a271 100644 --- a/src/Data/DataReferenceUpdater.php +++ b/src/Data/DataReferenceUpdater.php @@ -2,7 +2,9 @@ namespace Statamic\Data; +use Statamic\Fields\Blueprint; use Statamic\Fields\Fields; +use Statamic\Fields\Fieldset; use Statamic\Git\Subscriber as GitSubscriber; use Statamic\Support\Arr; @@ -60,7 +62,11 @@ public function updateReferences($originalValue, $newValue) $this->originalValue = $originalValue; $this->newValue = $newValue; - $this->recursivelyUpdateFields($this->getTopLevelFields()); + if ($this->item instanceof Blueprint || $this->item instanceof Fieldset) { + $this->updateBlueprintFields(); + } else { + $this->recursivelyUpdateFields($this->getTopLevelFields()); + } if ($this->updated) { $this->saveItem(); @@ -317,6 +323,39 @@ protected function updateArrayValue($field, $dottedPrefix) $this->updated = true; } + /** + * Update fields in blueprints and fieldsets. + * + * @return void + */ + abstract protected function updateBlueprintFields(); + + /** + * Finds fields of a given type in the contents of a blueprint. + * Returns dot-notation paths to the fields. + * + * @param array $array + * @param array $fieldtypes + * @param string|null $dottedPrefix + * @param array $fieldPaths + * @return array + */ + protected function findFieldsInBlueprintContents($array, $fieldtypes, $dottedPrefix = '', &$fieldPaths = []) + { + foreach ($array as $key => $value) { + if (is_array($value)) { + $fieldPath = $dottedPrefix ? "$dottedPrefix.$key" : $key; + $this->findFieldsInBlueprintContents($value, $fieldtypes, $fieldPath, $fieldPaths); + } + + if (is_string($value) && $key === 'type' && in_array($value, $fieldtypes)) { + $fieldPaths[] = $dottedPrefix; + } + } + + return $fieldPaths; + } + /** * Save item without triggering individual git commits, as these should be batched into one larger commit. */ diff --git a/src/Listeners/UpdateAssetReferences.php b/src/Listeners/UpdateAssetReferences.php index 7765cd2aeb1..8314fff7aab 100644 --- a/src/Listeners/UpdateAssetReferences.php +++ b/src/Listeners/UpdateAssetReferences.php @@ -10,13 +10,12 @@ use Statamic\Events\AssetSaved; use Statamic\Events\Subscriber; use Statamic\Facades\AssetContainer; -use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; +use Statamic\Facades\Fieldset; use Statamic\Facades\Form; use Statamic\Facades\GlobalSet; use Statamic\Facades\Nav; use Statamic\Facades\Taxonomy; -use Statamic\Support\Arr; class UpdateAssetReferences extends Subscriber implements ShouldQueue { @@ -109,42 +108,22 @@ protected function replaceReferences($asset, $originalPath, $newPath) $this ->getBlueprintsContainingData() - ->each(function ($blueprint) use ($originalPath, $newPath) { - $hasUpdatedBlueprint = false; - - $contents = $blueprint->contents(); - $fieldtypes = ['bard', 'replicator']; - $bigArrayOfFields = []; + ->each(function ($blueprint) use ($originalPath, $newPath, &$hasUpdatedItems) { + $updated = AssetReferenceUpdater::item($blueprint) + ->updateReferences($originalPath, $newPath); - foreach ($contents as $key => $value) { - $this->findFields($value, $fieldtypes, $key, $bigArrayOfFields); + if ($updated) { + $hasUpdatedItems = true; } + }); - foreach ($bigArrayOfFields as $path) { - $fieldContents = Arr::get($contents, $path); - - $fieldContents['sets'] = collect($fieldContents['sets']) - ->map(function ($setGroup) use ($originalPath, $newPath, &$hasUpdatedBlueprint) { - $setGroup['sets'] = collect($setGroup['sets']) - ->map(function ($set) use ($originalPath, $newPath, &$hasUpdatedBlueprint) { - if ($set['image'] === $originalPath) { - $set['image'] = $newPath; - $hasUpdatedBlueprint = true; - } - - return $set; - }) - ->all(); - - return $setGroup; - }) - ->all(); - - Arr::set($contents, $path, $fieldContents); - } + Fieldset::all() + ->each(function ($fieldset) use ($originalPath, $newPath, &$hasUpdatedItems) { + $updated = AssetReferenceUpdater::item($fieldset) + ->updateReferences($originalPath, $newPath); - if ($hasUpdatedBlueprint) { - $blueprint->setContents($contents)->save(); + if ($updated) { + $hasUpdatedItems = true; } }); diff --git a/src/Taxonomies/TermReferenceUpdater.php b/src/Taxonomies/TermReferenceUpdater.php index 633ccfaf405..900bca925c1 100644 --- a/src/Taxonomies/TermReferenceUpdater.php +++ b/src/Taxonomies/TermReferenceUpdater.php @@ -112,4 +112,14 @@ protected function newValue() { return $this->scope.$this->newValue; } + + /** + * Update fields in blueprints and fieldsets. + * + * @return void + */ + protected function updateBlueprintFields() + { + // + } } From f7381e0cfd8a42a94398ff9f7f5b3df3759be395 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 11 Nov 2025 16:25:57 +0000 Subject: [PATCH 04/18] Update property cache when saving and deleting fieldsets --- src/Fields/FieldsetRepository.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Fields/FieldsetRepository.php b/src/Fields/FieldsetRepository.php index 0d0df4ce586..b9ba607d6ca 100644 --- a/src/Fields/FieldsetRepository.php +++ b/src/Fields/FieldsetRepository.php @@ -172,6 +172,8 @@ public function save(Fieldset $fieldset) "{$directory}/{$handle}.yaml", YAML::dump($fieldset->contents()) ); + + $this->fieldsets[$fieldset->handle()] = $fieldset; } public function delete(Fieldset $fieldset) @@ -181,6 +183,8 @@ public function delete(Fieldset $fieldset) } File::delete($fieldset->path()); + + unset($this->fieldsets[$fieldset->handle()]); } public function reset(Fieldset $fieldset) From 6bcada365378c17c9a84f08b674acc422b331f46 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 11 Nov 2025 17:07:04 +0000 Subject: [PATCH 05/18] Add `Blueprint::all()` --- src/Fields/BlueprintRepository.php | 19 +++++++++++++++++ src/Listeners/UpdateAssetReferences.php | 23 ++------------------- tests/Fields/BlueprintRepositoryTest.php | 26 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php index 970426ad5a3..341aa64c291 100644 --- a/src/Fields/BlueprintRepository.php +++ b/src/Fields/BlueprintRepository.php @@ -12,6 +12,7 @@ use Statamic\Facades\YAML; use Statamic\Support\Arr; use Statamic\Support\Str; +use Statamic\Facades; class BlueprintRepository { @@ -23,6 +24,24 @@ class BlueprintRepository protected $fallbacks = []; protected $additionalNamespaces = []; + public function all() + { + $additionalBlueprints = Blueprint::getAdditionalNamespaces() + ->keys() + ->flatMap(fn ($namespace) => Blueprint::in($namespace)->values()); + + return collect() + ->merge(Facades\Collection::all()->flatMap->entryBlueprints()) + ->merge(Facades\Taxonomy::all()->flatMap->termBlueprints()) + ->merge(Facades\Nav::all()->map->blueprint()) + ->merge(Facades\AssetContainer::all()->map->blueprint()) + ->merge(Facades\GlobalSet::all()->map->blueprint()) + ->merge(Facades\Form::all()->map->blueprint()) + ->merge($additionalBlueprints) + ->filter() + ->values(); + } + public function setDirectories(string|array $directories) { if (is_string($directories)) { diff --git a/src/Listeners/UpdateAssetReferences.php b/src/Listeners/UpdateAssetReferences.php index 8314fff7aab..be06ff03575 100644 --- a/src/Listeners/UpdateAssetReferences.php +++ b/src/Listeners/UpdateAssetReferences.php @@ -10,6 +10,7 @@ use Statamic\Events\AssetSaved; use Statamic\Events\Subscriber; use Statamic\Facades\AssetContainer; +use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; use Statamic\Facades\Fieldset; use Statamic\Facades\Form; @@ -106,8 +107,7 @@ protected function replaceReferences($asset, $originalPath, $newPath) } }); - $this - ->getBlueprintsContainingData() + Blueprint::all() ->each(function ($blueprint) use ($originalPath, $newPath, &$hasUpdatedItems) { $updated = AssetReferenceUpdater::item($blueprint) ->updateReferences($originalPath, $newPath); @@ -144,23 +144,4 @@ protected function findFields(array $array, array $fieldtypes, string $dottedPre } } } - - protected function getBlueprintsContainingData() - { - // $additionalBlueprints = Blueprint::getAdditionalNamespaces() - // ->keys() - // ->map(fn ($namespace) => Blueprint::in($namespace)) - // ->all(); - - $additionalBlueprints = []; - - return collect() - ->merge(Collection::all()->flatMap->entryBlueprints()) - ->merge(Taxonomy::all()->flatMap->termBlueprints()) - ->merge(Nav::all()->map->blueprint()) - ->merge(AssetContainer::all()->map->blueprint()) - ->merge(GlobalSet::all()->map->blueprint()) - ->merge(Form::all()->map->blueprint()) - ->merge($additionalBlueprints); - } } diff --git a/tests/Fields/BlueprintRepositoryTest.php b/tests/Fields/BlueprintRepositoryTest.php index 80571d8c0f7..a8101f75662 100644 --- a/tests/Fields/BlueprintRepositoryTest.php +++ b/tests/Fields/BlueprintRepositoryTest.php @@ -10,10 +10,13 @@ use Statamic\Fields\Blueprint; use Statamic\Fields\BlueprintRepository; use Statamic\Support\FileCollection; +use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; class BlueprintRepositoryTest extends TestCase { + use PreventSavingStacheItemsToDisk; + private $repo; public function setUp(): void @@ -26,6 +29,29 @@ public function setUp(): void Facades\Blueprint::swap($this->repo); } + #[Test] + public function it_gets_all_blueprints() + { + Facades\Form::all()->each->delete(); + + Facades\Collection::make('test')->save(); + Facades\Taxonomy::make('test')->save(); + Facades\Nav::make('test')->save(); + Facades\AssetContainer::make('test')->save(); + Facades\Form::make('test')->save(); + + $all = $this->repo->all(); + + $this->assertEveryItemIsInstanceOf(Blueprint::class, $all); + $this->assertEquals([ + 'collections.test.test', + 'taxonomies.test.test', + 'navigation.test', + 'assets.test', + 'forms.test', + ], $all->map->fullyQualifiedHandle()->all()); + } + #[Test] public function it_gets_a_blueprint() { From 935923c40b01edc5bacd69b61a1d9ac1a3dff7d4 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 12 Nov 2025 11:22:24 +0000 Subject: [PATCH 06/18] Don't need this. --- src/Listeners/UpdateAssetReferences.php | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/Listeners/UpdateAssetReferences.php b/src/Listeners/UpdateAssetReferences.php index be06ff03575..28ef9ef8075 100644 --- a/src/Listeners/UpdateAssetReferences.php +++ b/src/Listeners/UpdateAssetReferences.php @@ -9,14 +9,8 @@ use Statamic\Events\AssetReplaced; use Statamic\Events\AssetSaved; use Statamic\Events\Subscriber; -use Statamic\Facades\AssetContainer; use Statamic\Facades\Blueprint; -use Statamic\Facades\Collection; use Statamic\Facades\Fieldset; -use Statamic\Facades\Form; -use Statamic\Facades\GlobalSet; -use Statamic\Facades\Nav; -use Statamic\Facades\Taxonomy; class UpdateAssetReferences extends Subscriber implements ShouldQueue { @@ -131,17 +125,4 @@ protected function replaceReferences($asset, $originalPath, $newPath) AssetReferencesUpdated::dispatch($asset); } } - - protected function findFields(array $array, array $fieldtypes, string $dottedPrefix, array &$bigArrayOfFields) - { - foreach ($array as $key => $value) { - if (is_array($value)) { - $this->findFields($value, $fieldtypes, "$dottedPrefix.$key", $bigArrayOfFields); - } elseif (is_string($value)) { - if ($key === 'type' && in_array($value, $fieldtypes)) { - $bigArrayOfFields[] = $dottedPrefix; - } - } - } - } } From 508d6c4e917d84ac6b674b32c8c30cad84f6fecb Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 12 Nov 2025 11:31:15 +0000 Subject: [PATCH 07/18] Write blueprints to disk and ensure global blueprints are included --- tests/Fields/BlueprintRepositoryTest.php | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/Fields/BlueprintRepositoryTest.php b/tests/Fields/BlueprintRepositoryTest.php index a8101f75662..b71cebe5696 100644 --- a/tests/Fields/BlueprintRepositoryTest.php +++ b/tests/Fields/BlueprintRepositoryTest.php @@ -32,13 +32,25 @@ public function setUp(): void #[Test] public function it_gets_all_blueprints() { - Facades\Form::all()->each->delete(); + $this->repo->setDirectories($this->fakeStacheDirectory . '/dev-null/blueprints'); - Facades\Collection::make('test')->save(); - Facades\Taxonomy::make('test')->save(); - Facades\Nav::make('test')->save(); - Facades\AssetContainer::make('test')->save(); - Facades\Form::make('test')->save(); + $collection = tap(Facades\Collection::make('test'))->save(); + $collection->entryBlueprint()->save(); + + $taxonomy = tap(Facades\Taxonomy::make('test'))->save(); + $taxonomy->termBlueprint()->save(); + + $nav = tap(Facades\Nav::make('test'))->save(); + $nav->blueprint()->save(); + + $assetContainer = tap(Facades\AssetContainer::make('test'))->save(); + $assetContainer->blueprint()->save(); + + Facades\GlobalSet::make('test')->save(); + $this->repo->make('test')->setNamespace('globals')->save(); + + $form = tap(Facades\Form::make('test'))->save(); + $form->blueprint()->save(); $all = $this->repo->all(); @@ -48,6 +60,7 @@ public function it_gets_all_blueprints() 'taxonomies.test.test', 'navigation.test', 'assets.test', + 'globals.test', 'forms.test', ], $all->map->fullyQualifiedHandle()->all()); } From a05f42299e73b71853be7e1e3f7744fbf0a5b621 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 12 Nov 2025 11:40:48 +0000 Subject: [PATCH 08/18] Refactor `Blueprint::all()` to avoid calling all the facades... Because the facade calls cause issues with mocks in existing tests. Doing it this way avoids (as many) facade calls. --- src/Fields/BlueprintRepository.php | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php index 341aa64c291..29cda4585da 100644 --- a/src/Fields/BlueprintRepository.php +++ b/src/Fields/BlueprintRepository.php @@ -26,20 +26,14 @@ class BlueprintRepository public function all() { - $additionalBlueprints = Blueprint::getAdditionalNamespaces() - ->keys() - ->flatMap(fn ($namespace) => Blueprint::in($namespace)->values()); - - return collect() - ->merge(Facades\Collection::all()->flatMap->entryBlueprints()) - ->merge(Facades\Taxonomy::all()->flatMap->termBlueprints()) - ->merge(Facades\Nav::all()->map->blueprint()) - ->merge(Facades\AssetContainer::all()->map->blueprint()) - ->merge(Facades\GlobalSet::all()->map->blueprint()) - ->merge(Facades\Form::all()->map->blueprint()) - ->merge($additionalBlueprints) - ->filter() - ->values(); + $namespaces = [ + ...Facades\Collection::all()->map(fn ($collection) => "collections/{$collection->handle()}")->all(), + ...Facades\Taxonomy::all()->map(fn ($taxonomy) => "taxonomies/{$taxonomy->handle()}")->all(), + 'navigation', 'assets', 'globals', 'forms', + ...$this->getAdditionalNamespaces()->keys()->all(), + ]; + + return collect($namespaces)->flatMap(fn ($namespace) => $this->in($namespace)->values()); } public function setDirectories(string|array $directories) From 4195ff655cdfa794d8199b765504edaa0280873a Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 12 Nov 2025 11:42:04 +0000 Subject: [PATCH 09/18] formatting --- src/Fields/BlueprintRepository.php | 2 +- tests/Fields/BlueprintRepositoryTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php index 29cda4585da..d0418a9d1e6 100644 --- a/src/Fields/BlueprintRepository.php +++ b/src/Fields/BlueprintRepository.php @@ -6,13 +6,13 @@ use Exception; use Illuminate\Support\Collection; use Statamic\Exceptions\BlueprintNotFoundException; +use Statamic\Facades; use Statamic\Facades\Blink; use Statamic\Facades\File; use Statamic\Facades\Path; use Statamic\Facades\YAML; use Statamic\Support\Arr; use Statamic\Support\Str; -use Statamic\Facades; class BlueprintRepository { diff --git a/tests/Fields/BlueprintRepositoryTest.php b/tests/Fields/BlueprintRepositoryTest.php index b71cebe5696..aa575f19cbb 100644 --- a/tests/Fields/BlueprintRepositoryTest.php +++ b/tests/Fields/BlueprintRepositoryTest.php @@ -32,7 +32,7 @@ public function setUp(): void #[Test] public function it_gets_all_blueprints() { - $this->repo->setDirectories($this->fakeStacheDirectory . '/dev-null/blueprints'); + $this->repo->setDirectories($this->fakeStacheDirectory.'/dev-null/blueprints'); $collection = tap(Facades\Collection::make('test'))->save(); $collection->entryBlueprint()->save(); From 5e03125a4bc8364b041e0a3a0f8a07ad0cab3798 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 12 Nov 2025 11:48:14 +0000 Subject: [PATCH 10/18] Avoid errors when array offsets don't exist --- src/Assets/AssetReferenceUpdater.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Assets/AssetReferenceUpdater.php b/src/Assets/AssetReferenceUpdater.php index 0f4247a08aa..0ad5907b6bf 100644 --- a/src/Assets/AssetReferenceUpdater.php +++ b/src/Assets/AssetReferenceUpdater.php @@ -348,8 +348,16 @@ protected function updateBlueprintFields() foreach ($fieldPaths as $fieldPath) { $fieldContents = Arr::get($contents, $fieldPath); + if (! isset($fieldContents['sets'])) { + continue; + } + $fieldContents['sets'] = collect($fieldContents['sets']) ->map(function ($setGroup) { + if (! isset($setGroup['sets'])) { + return $setGroup; + } + $setGroup['sets'] = collect($setGroup['sets']) ->map(function ($set) { if (isset($set['image']) && $set['image'] === $this->originalValue) { From 499abb02c794b59d44eae3d8ca3af7248f928fce Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 12 Nov 2025 11:48:36 +0000 Subject: [PATCH 11/18] Add Blueprint::all() expectation where necessary --- tests/Git/GitEventTest.php | 1 + tests/Listeners/UpdateAssetReferencesTest.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/tests/Git/GitEventTest.php b/tests/Git/GitEventTest.php index 44cbfb18325..0388e8df60f 100644 --- a/tests/Git/GitEventTest.php +++ b/tests/Git/GitEventTest.php @@ -612,6 +612,7 @@ public function it_batches_asset_references_changes_into_one_commit() ], ]); + BlueprintRepository::shouldReceive('all')->andReturn(collect([$blueprint])); BlueprintRepository::shouldReceive('in')->with('collections/pages')->andReturn(collect([$blueprint])); foreach (range(1, 3) as $i) { diff --git a/tests/Listeners/UpdateAssetReferencesTest.php b/tests/Listeners/UpdateAssetReferencesTest.php index c1b2b683105..b9dcf07037a 100644 --- a/tests/Listeners/UpdateAssetReferencesTest.php +++ b/tests/Listeners/UpdateAssetReferencesTest.php @@ -1985,6 +1985,7 @@ protected function setSingleBlueprint($namespace, $blueprintContents) { $blueprint = tap(Facades\Blueprint::make('single-blueprint')->setContents($blueprintContents))->save(); + Facades\Blueprint::shouldReceive('all')->andReturn(collect([$blueprint])); Facades\Blueprint::shouldReceive('find')->with($namespace)->andReturn($blueprint); } @@ -1992,6 +1993,7 @@ protected function setInBlueprints($namespace, $blueprintContents) { $blueprint = tap(Facades\Blueprint::make('set-in-blueprints')->setContents($blueprintContents))->save(); + Facades\Blueprint::shouldReceive('all')->andReturn(collect([$blueprint])); Facades\Blueprint::shouldReceive('in')->with($namespace)->andReturn(collect([$blueprint])); } From 664d053ce165d088ab20db039195fbafa8cb390b Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 18 Nov 2025 19:47:17 +0000 Subject: [PATCH 12/18] Ensure parent is null when getting blueprints --- src/Fields/BlueprintRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php index 8c227c49463..a817e4900aa 100644 --- a/src/Fields/BlueprintRepository.php +++ b/src/Fields/BlueprintRepository.php @@ -347,7 +347,7 @@ protected function makeBlueprintFromFile($path, $namespace = null) ->setInitialPath($path) ->setNamespace($namespace ?? null) ->setContents($contents); - }); + })->setParent(null); } protected function getNamespaceAndHandle($blueprint) From 7da3fcaa5696f4f17f4c415d119cf29c6b63cc4e Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 18 Nov 2025 20:01:11 +0000 Subject: [PATCH 13/18] Preview images are saved sans folder --- src/Assets/AssetReferenceUpdater.php | 24 ++++++++++-- tests/Listeners/UpdateAssetReferencesTest.php | 38 +++++++++++++------ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/Assets/AssetReferenceUpdater.php b/src/Assets/AssetReferenceUpdater.php index 0ad5907b6bf..6cda3cf47e6 100644 --- a/src/Assets/AssetReferenceUpdater.php +++ b/src/Assets/AssetReferenceUpdater.php @@ -4,7 +4,10 @@ use Statamic\Data\DataReferenceUpdater; use Statamic\Facades\AssetContainer; +use Statamic\Fieldtypes\Sets; use Statamic\Support\Arr; +use Statamic\Support\Str; +use Statamic\Tags\Set; class AssetReferenceUpdater extends DataReferenceUpdater { @@ -341,6 +344,13 @@ private function updateStatamicUrlsInLinkNodes($field, $dottedPrefix) */ protected function updateBlueprintFields() { + if ( + ! Sets::previewImageConfig() + || ! Str::startsWith($this->originalValue, Sets::previewImageConfig()['folder'].'/') + ) { + return; + } + $contents = $this->item->contents(); $fieldPaths = $this->findFieldsInBlueprintContents($contents, fieldtypes: ['bard', 'replicator']); @@ -360,9 +370,13 @@ protected function updateBlueprintFields() $setGroup['sets'] = collect($setGroup['sets']) ->map(function ($set) { - if (isset($set['image']) && $set['image'] === $this->originalValue) { - $set['image'] = $this->newValue; - $this->updated = true; + if (isset($set['image'])) { + $fullPath = Sets::previewImageConfig()['folder'].'/'.$set['image']; + + if ($fullPath === $this->originalValue) { + $set['image'] = Str::after($this->newValue, Sets::previewImageConfig()['folder'].'/'); + $this->updated = true; + } } return $set; @@ -376,6 +390,8 @@ protected function updateBlueprintFields() Arr::set($contents, $fieldPath, $fieldContents); } - $this->item->setContents($contents); + if ($this->updated) { + $this->item->setContents($contents); + } } } diff --git a/tests/Listeners/UpdateAssetReferencesTest.php b/tests/Listeners/UpdateAssetReferencesTest.php index b9dcf07037a..386d4ca9906 100644 --- a/tests/Listeners/UpdateAssetReferencesTest.php +++ b/tests/Listeners/UpdateAssetReferencesTest.php @@ -1851,8 +1851,15 @@ public function it_only_saves_items_when_there_is_something_to_update() } #[Test] - public function it_updates_references_in_bard_and_replicator_fields_in_blueprints() + public function it_updates_references_in_set_configs_in_blueprints() { + $this->assetHoff->path('set-previews/hoff.jpg')->save(); + + config()->set('statamic.assets.set_preview_images', [ + 'container' => 'test_container', + 'folder' => 'set-previews', + ]); + $collection = tap(Facades\Collection::make('articles'))->save(); $blueprint = $collection->entryBlueprint(); @@ -1866,7 +1873,7 @@ public function it_updates_references_in_bard_and_replicator_fields_in_blueprint 'set_group' => [ 'sets' => [ 'first_set' => [ - 'image' => $this->assetHoff->path(), + 'image' => 'hoff.jpg', 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], ], 'second_set' => [ @@ -1886,7 +1893,7 @@ public function it_updates_references_in_bard_and_replicator_fields_in_blueprint 'set_group' => [ 'sets' => [ 'first_set' => [ - 'image' => $this->assetHoff->path(), + 'image' => 'hoff.jpg', 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], ], 'second_set' => [ @@ -1907,18 +1914,25 @@ public function it_updates_references_in_bard_and_replicator_fields_in_blueprint $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.first_set.image')); $this->assertEquals('marty.png', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.second_set.image')); - $this->assetHoff->path('destination/hoff.jpg')->save(); + $this->assetHoff->path('set-previews/renamed-hoff.jpg')->save(); $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); - $this->assertEquals('destination/hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('renamed-hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); // changed $this->assertEquals('marty.png', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.second_set.image')); - $this->assertEquals('destination/hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('renamed-hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.first_set.image')); // changed $this->assertEquals('marty.png', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.2.field.sets.set_group.sets.second_set.image')); } #[Test] - public function it_updates_references_in_bard_and_replicator_fields_in_fieldsets() + public function it_updates_references_in_set_configs_in_fieldsets() { + $this->assetHoff->path('set-previews/hoff.jpg')->save(); + + config()->set('statamic.assets.set_preview_images', [ + 'container' => 'test_container', + 'folder' => 'set-previews', + ]); + $fieldset = Facades\Fieldset::make('stuff'); $fieldset->setContents([ @@ -1931,7 +1945,7 @@ public function it_updates_references_in_bard_and_replicator_fields_in_fieldsets 'set_group' => [ 'sets' => [ 'first_set' => [ - 'image' => $this->assetHoff->path(), + 'image' => 'hoff.jpg', 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], ], 'second_set' => [ @@ -1951,7 +1965,7 @@ public function it_updates_references_in_bard_and_replicator_fields_in_fieldsets 'set_group' => [ 'sets' => [ 'first_set' => [ - 'image' => $this->assetHoff->path(), + 'image' => 'hoff.jpg', 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], ], 'second_set' => [ @@ -1972,12 +1986,12 @@ public function it_updates_references_in_bard_and_replicator_fields_in_fieldsets $this->assertEquals('hoff.jpg', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.first_set.image')); $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.second_set.image')); - $this->assetHoff->path('destination/hoff.jpg')->save(); + $this->assetHoff->path('set-previews/renamed-hoff.jpg')->save(); $fieldset = Facades\Fieldset::find('stuff'); - $this->assertEquals('destination/hoff.jpg', Arr::get($fieldset->contents(), 'fields.0.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('renamed-hoff.jpg', Arr::get($fieldset->contents(), 'fields.0.field.sets.set_group.sets.first_set.image')); // changed $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.0.field.sets.set_group.sets.second_set.image')); - $this->assertEquals('destination/hoff.jpg', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.first_set.image')); // changed + $this->assertEquals('renamed-hoff.jpg', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.first_set.image')); // changed $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.second_set.image')); } From d2c0eab5828d5982d5e24167e45b52f5f390d995 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 18 Nov 2025 20:01:22 +0000 Subject: [PATCH 14/18] Add additional tests --- tests/Listeners/UpdateAssetReferencesTest.php | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/Listeners/UpdateAssetReferencesTest.php b/tests/Listeners/UpdateAssetReferencesTest.php index 386d4ca9906..7b48887b81d 100644 --- a/tests/Listeners/UpdateAssetReferencesTest.php +++ b/tests/Listeners/UpdateAssetReferencesTest.php @@ -1995,6 +1995,87 @@ public function it_updates_references_in_set_configs_in_fieldsets() $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.second_set.image')); } + #[Test] + public function it_doesnt_update_references_in_set_configs_unless_path_contains_set_preview_images_folder() + { + config()->set('statamic.assets.set_preview_images', [ + 'container' => 'test_container', + 'folder' => 'set-previews', + ]); + + $collection = tap(Facades\Collection::make('articles'))->save(); + $blueprint = $collection->entryBlueprint(); + + $blueprint->setContents([ + 'fields' => [ + [ + 'handle' => 'content', + 'field' => [ + 'type' => 'bard', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => 'hoff.jpg', // asset exists in the base folder + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + ], + ])->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + + $this->assetHoff->path('renamed-hoff.jpg')->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + } + + #[Test] + public function it_doesnt_update_references_in_set_configs_when_set_preview_images_arent_configured() + { + $this->assetHoff->path('set-previews/hoff.jpg')->save(); + + config()->set('statamic.assets.set_preview_images', null); + + $collection = tap(Facades\Collection::make('articles'))->save(); + $blueprint = $collection->entryBlueprint(); + + $blueprint->setContents([ + 'fields' => [ + [ + 'handle' => 'content', + 'field' => [ + 'type' => 'bard', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => 'hoff.jpg', + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + ], + ])->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + + $this->assetHoff->path('set-previews/renamed-hoff.jpg')->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + } + protected function setSingleBlueprint($namespace, $blueprintContents) { $blueprint = tap(Facades\Blueprint::make('single-blueprint')->setContents($blueprintContents))->save(); From 3aa802e8a0213fdb5433638d35441c03c27beb5b Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 18 Nov 2025 20:11:50 +0000 Subject: [PATCH 15/18] Handle assets being deleted & moved out of configured folder --- src/Assets/AssetReferenceUpdater.php | 11 ++- tests/Listeners/UpdateAssetReferencesTest.php | 86 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/Assets/AssetReferenceUpdater.php b/src/Assets/AssetReferenceUpdater.php index 6cda3cf47e6..0ba43d6cd0f 100644 --- a/src/Assets/AssetReferenceUpdater.php +++ b/src/Assets/AssetReferenceUpdater.php @@ -373,10 +373,17 @@ protected function updateBlueprintFields() if (isset($set['image'])) { $fullPath = Sets::previewImageConfig()['folder'].'/'.$set['image']; - if ($fullPath === $this->originalValue) { + if ($fullPath !== $this->originalValue) { + return $set; + } + + if (Str::startsWith($this->newValue, Sets::previewImageConfig()['folder'].'/')) { $set['image'] = Str::after($this->newValue, Sets::previewImageConfig()['folder'].'/'); - $this->updated = true; + } else { + unset($set['image']); } + + $this->updated = true; } return $set; diff --git a/tests/Listeners/UpdateAssetReferencesTest.php b/tests/Listeners/UpdateAssetReferencesTest.php index 7b48887b81d..cbaa87a9f6c 100644 --- a/tests/Listeners/UpdateAssetReferencesTest.php +++ b/tests/Listeners/UpdateAssetReferencesTest.php @@ -1995,6 +1995,92 @@ public function it_updates_references_in_set_configs_in_fieldsets() $this->assertEquals('marty.png', Arr::get($fieldset->contents(), 'fields.1.field.sets.set_group.sets.second_set.image')); } + #[Test] + public function it_removes_preview_image_from_set_config_when_asset_is_moved_out_of_configured_folder() + { + $this->assetHoff->path('set-previews/hoff.jpg')->save(); + + config()->set('statamic.assets.set_preview_images', [ + 'container' => 'test_container', + 'folder' => 'set-previews', + ]); + + $collection = tap(Facades\Collection::make('articles'))->save(); + $blueprint = $collection->entryBlueprint(); + + $blueprint->setContents([ + 'fields' => [ + [ + 'handle' => 'content', + 'field' => [ + 'type' => 'bard', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => 'hoff.jpg', + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + ], + ])->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + + $this->assetHoff->path('other-folder/hoff.jpg')->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertFalse(Arr::has($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + } + + #[Test] + public function it_removes_preview_image_from_set_config_when_asset_is_deleted() + { + $this->assetHoff->path('set-previews/hoff.jpg')->save(); + + config()->set('statamic.assets.set_preview_images', [ + 'container' => 'test_container', + 'folder' => 'set-previews', + ]); + + $collection = tap(Facades\Collection::make('articles'))->save(); + $blueprint = $collection->entryBlueprint(); + + $blueprint->setContents([ + 'fields' => [ + [ + 'handle' => 'content', + 'field' => [ + 'type' => 'bard', + 'sets' => [ + 'set_group' => [ + 'sets' => [ + 'first_set' => [ + 'image' => 'hoff.jpg', + 'fields' => [['handle' => 'foo', 'field' => ['type' => 'text']]], + ], + ], + ], + ], + ], + ], + ], + ])->save(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertEquals('hoff.jpg', Arr::get($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + + $this->assetHoff->delete(); + + $blueprint = Facades\Blueprint::find($blueprint->fullyQualifiedHandle()); + $this->assertFalse(Arr::has($blueprint->contents(), 'tabs.main.sections.0.fields.1.field.sets.set_group.sets.first_set.image')); + } + #[Test] public function it_doesnt_update_references_in_set_configs_unless_path_contains_set_preview_images_folder() { From 61f63391c606deeea92d1cd651ef0297659f90c8 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 18 Nov 2025 20:14:39 +0000 Subject: [PATCH 16/18] Fix failing tests 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. --- tests/Assets/AssetFolderTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Assets/AssetFolderTest.php b/tests/Assets/AssetFolderTest.php index e9e6093ba93..e2101a4947e 100644 --- a/tests/Assets/AssetFolderTest.php +++ b/tests/Assets/AssetFolderTest.php @@ -1174,6 +1174,7 @@ private function containerWithDisk() Storage::fake('local'); $container = Facades\AssetContainer::make('test')->disk('local'); + Facades\AssetContainer::shouldReceive('find')->with('assets')->andReturn(null); Facades\AssetContainer::shouldReceive('findByHandle')->with('test')->andReturn($container); Facades\AssetContainer::shouldReceive('save')->with($container); From b0c39df037a825b83183efb7051168245919e609 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 18 Nov 2025 20:27:35 +0000 Subject: [PATCH 17/18] Fix more failing tests --- tests/Assets/AssetTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 3c10d8e448f..e585aa49996 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -1162,6 +1162,7 @@ public function it_can_be_moved_to_another_folder_with_a_new_filename() $disk->put('old/asset.txt', 'The asset contents'); $container = Facades\AssetContainer::make('test')->disk('local'); Facades\AssetContainer::shouldReceive('save')->with($container); + Facades\AssetContainer::shouldReceive('find')->with('assets')->andReturnNull(); Facades\AssetContainer::shouldReceive('findByHandle')->with('test')->andReturn($container); $asset = $container->makeAsset('old/asset.txt')->data(['foo' => 'bar']); $asset->save(); @@ -1202,6 +1203,7 @@ public function it_lowercases_when_moving_to_another_folder_with_a_new_filename( $disk->put('old/asset.txt', 'The asset contents'); $container = Facades\AssetContainer::make('test')->disk('local'); Facades\AssetContainer::shouldReceive('save')->with($container); + Facades\AssetContainer::shouldReceive('find')->with('assets')->andReturnNull(); Facades\AssetContainer::shouldReceive('findByHandle')->with('test')->andReturn($container); $asset = $container->makeAsset('old/asset.txt'); $asset->save(); @@ -1225,6 +1227,7 @@ public function it_doesnt_lowercase_moved_files_when_configured() $disk->put('old/asset.txt', 'The asset contents'); $container = Facades\AssetContainer::make('test')->disk('local'); Facades\AssetContainer::shouldReceive('save')->with($container); + Facades\AssetContainer::shouldReceive('find')->with('assets')->andReturnNull(); Facades\AssetContainer::shouldReceive('findByHandle')->with('test')->andReturn($container); $asset = $container->makeAsset('old/asset.txt'); $asset->save(); From d393254436d7c987d816b1f91868bf56139512f7 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 5 Jan 2026 10:17:47 +0000 Subject: [PATCH 18/18] add missing imports --- tests/Listeners/UpdateAssetReferencesTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Listeners/UpdateAssetReferencesTest.php b/tests/Listeners/UpdateAssetReferencesTest.php index a56b74f05f6..affa798423c 100644 --- a/tests/Listeners/UpdateAssetReferencesTest.php +++ b/tests/Listeners/UpdateAssetReferencesTest.php @@ -4,10 +4,12 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\LazyCollection; use Orchestra\Testbench\Attributes\DefineEnvironment; use PHPUnit\Framework\Attributes\Test; use Statamic\Assets\AssetFolder; use Statamic\Facades; +use Statamic\Listeners\UpdateAssetReferences; use Statamic\Support\Arr; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase;