diff --git a/src/Assets/AssetReferenceUpdater.php b/src/Assets/AssetReferenceUpdater.php index e0e86c1c77f..2eff491fa02 100644 --- a/src/Assets/AssetReferenceUpdater.php +++ b/src/Assets/AssetReferenceUpdater.php @@ -4,6 +4,7 @@ use Statamic\Data\DataReferenceUpdater; use Statamic\Facades\AssetContainer; +use Statamic\Fieldtypes\UpdatesReferences; use Statamic\Support\Arr; class AssetReferenceUpdater extends DataReferenceUpdater @@ -34,6 +35,7 @@ public function filterByContainer(string $container) protected function recursivelyUpdateFields($fields, $dottedPrefix = null) { $this + ->updateCustomFieldtypeValues($fields, $dottedPrefix) ->updateAssetsFieldValues($fields, $dottedPrefix) ->updateLinkFieldValues($fields, $dottedPrefix) ->updateBardFieldValues($fields, $dottedPrefix) @@ -41,6 +43,49 @@ protected function recursivelyUpdateFields($fields, $dottedPrefix = null) ->updateNestedFieldValues($fields, $dottedPrefix); } + /** + * Update custom fieldtype values that have asset references. + * + * @param \Illuminate\Support\Collection $fields + * @param null|string $dottedPrefix + * @return $this + */ + protected function updateCustomFieldtypeValues($fields, $dottedPrefix) + { + $fields + ->filter(fn ($field) => in_array(UpdatesReferences::class, class_uses_recursive($field->fieldtype()))) + ->each(function ($field) use ($dottedPrefix) { + $data = $this->item->data()->all(); + $dottedKey = $dottedPrefix.$field->handle(); + $oldData = Arr::get($data, $dottedKey); + + if (! $oldData) { + return; + } + + $newData = $field->fieldtype()->replaceAssetReferences( + $oldData, + $this->newValue, + $this->originalValue + ); + + if (json_encode($oldData) === json_encode($newData)) { + return; + } + + if ($newData === null && $this->isRemovingValue()) { + Arr::forget($data, $dottedKey); + } else { + Arr::set($data, $dottedKey, $newData); + } + + $this->item->data($data); + $this->updated = true; + }); + + return $this; + } + /** * Update assets field values. * diff --git a/src/Data/DataReferenceUpdater.php b/src/Data/DataReferenceUpdater.php index 8c5cc343a5b..4053187bc83 100644 --- a/src/Data/DataReferenceUpdater.php +++ b/src/Data/DataReferenceUpdater.php @@ -3,6 +3,7 @@ namespace Statamic\Data; use Statamic\Fields\Fields; +use Statamic\Fieldtypes\UpdatesReferences; use Statamic\Git\Subscriber as GitSubscriber; use Statamic\Support\Arr; @@ -107,9 +108,42 @@ protected function updateNestedFieldValues($fields, $dottedPrefix) $this->{$method}($field, $dottedKey); }); + // Handle custom fieldtypes with nested fields + $fields + ->filter(fn ($field) => in_array(UpdatesReferences::class, class_uses_recursive($field->fieldtype()))) + ->each(function ($field) use ($dottedPrefix) { + $this->updateNestedFieldsInCustomFieldtype($field, $dottedPrefix); + }); + return $this; } + /** + * Update nested fields in custom fieldtype. + * + * @param \Statamic\Fields\Field $field + * @param null|string $dottedPrefix + */ + protected function updateNestedFieldsInCustomFieldtype($field, $dottedPrefix) + { + $fieldKey = $dottedPrefix.$field->handle(); + $fieldData = Arr::get($this->item->data()->all(), $fieldKey); + + if (! $fieldData) { + return; + } + + $fieldtype = $field->fieldtype(); + + $fieldtype->processNestedFieldsForReferences( + $fieldData, + function ($nestedFields, $relativePrefix) use ($fieldKey) { + $absolutePrefix = $fieldKey.'.'.$relativePrefix; + $this->recursivelyUpdateFields($nestedFields->all(), $absolutePrefix); + } + ); + } + /** * Update replicator field children. * diff --git a/src/Fieldtypes/UpdatesReferences.php b/src/Fieldtypes/UpdatesReferences.php new file mode 100644 index 00000000000..025cf671b94 --- /dev/null +++ b/src/Fieldtypes/UpdatesReferences.php @@ -0,0 +1,136 @@ +resolveFieldsConfigForReferenceUpdates($fieldsConfig); + $processFields($fields, ''); + } + + /** + * Helper: Process fields for an array structure at root level. + * Resulting prefix: "0.", "1.", "2."... + * + * @param mixed $data + * @param callable $processFields + * @param array|string $fieldsConfig + */ + protected function processArrayNestedFields($data, callable $processFields, $fieldsConfig) + { + $fields = $this->resolveFieldsConfigForReferenceUpdates($fieldsConfig); + + foreach (array_keys($data ?? []) as $idx) { + $processFields($fields, "{$idx}."); + } + } + + /** + * Helper: Process fields for an array nested under a specific key. + * Resulting prefix: "{key}.0.", "{key}.1."... + * + * @param mixed $data + * @param callable $processFields + * @param string $key + * @param array|string $fieldsConfig + */ + protected function processArrayNestedFieldsAtKey($data, callable $processFields, $key, $fieldsConfig) + { + $fields = $this->resolveFieldsConfigForReferenceUpdates($fieldsConfig); + $arrayData = $data[$key] ?? []; + + foreach (array_keys($arrayData) as $idx) { + $processFields($fields, "{$key}.{$idx}."); + } + } + + /** + * Helper: Process fields for a single structure nested under a key. + * Resulting prefix: "{key}." + * + * @param callable $processFields + * @param string $key + * @param array|string $fieldsConfig + */ + protected function processSingleNestedFieldsAtKey(callable $processFields, $key, $fieldsConfig) + { + $fields = $this->resolveFieldsConfigForReferenceUpdates($fieldsConfig); + $processFields($fields, "{$key}."); + } + + /** + * Resolve fields config to Fields instance. + * + * @param array|string $fieldsConfig + * @return \Statamic\Fields\Fields + */ + private function resolveFieldsConfigForReferenceUpdates($fieldsConfig) + { + if (is_string($fieldsConfig)) { + $config = $this->config($fieldsConfig); + } else { + $config = $fieldsConfig; + } + + return new Fields($config ?? []); + } +} diff --git a/src/Taxonomies/TermReferenceUpdater.php b/src/Taxonomies/TermReferenceUpdater.php index 633ccfaf405..dd3e1de3438 100644 --- a/src/Taxonomies/TermReferenceUpdater.php +++ b/src/Taxonomies/TermReferenceUpdater.php @@ -3,6 +3,7 @@ namespace Statamic\Taxonomies; use Statamic\Data\DataReferenceUpdater; +use Statamic\Fieldtypes\UpdatesReferences; use Statamic\Support\Arr; class TermReferenceUpdater extends DataReferenceUpdater @@ -38,11 +39,55 @@ public function filterByTaxonomy(string $taxonomy) protected function recursivelyUpdateFields($fields, $dottedPrefix = null) { $this + ->updateCustomFieldtypeValues($fields, $dottedPrefix) ->updateTermsFieldValues($fields, $dottedPrefix) ->updateScopedTermsFieldValues($fields, $dottedPrefix) ->updateNestedFieldValues($fields, $dottedPrefix); } + /** + * Update custom fieldtype values that have term references. + * + * @param \Illuminate\Support\Collection $fields + * @param null|string $dottedPrefix + * @return $this + */ + protected function updateCustomFieldtypeValues($fields, $dottedPrefix) + { + $fields + ->filter(fn ($field) => in_array(UpdatesReferences::class, class_uses_recursive($field->fieldtype()))) + ->each(function ($field) use ($dottedPrefix) { + $data = $this->item->data()->all(); + $dottedKey = $dottedPrefix.$field->handle(); + $oldData = Arr::get($data, $dottedKey); + + if (! $oldData) { + return; + } + + $newData = $field->fieldtype()->replaceTermReferences( + $oldData, + $this->newValue, + $this->originalValue + ); + + if (json_encode($oldData) === json_encode($newData)) { + return; + } + + if ($newData === null && $this->isRemovingValue()) { + Arr::forget($data, $dottedKey); + } else { + Arr::set($data, $dottedKey, $newData); + } + + $this->item->data($data); + $this->updated = true; + }); + + return $this; + } + /** * Update terms field values. *