Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/Assets/AssetReferenceUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Statamic\Data\DataReferenceUpdater;
use Statamic\Facades\AssetContainer;
use Statamic\Fieldtypes\UpdatesReferences;
use Statamic\Support\Arr;

class AssetReferenceUpdater extends DataReferenceUpdater
Expand Down Expand Up @@ -34,13 +35,57 @@ public function filterByContainer(string $container)
protected function recursivelyUpdateFields($fields, $dottedPrefix = null)
{
$this
->updateCustomFieldtypeValues($fields, $dottedPrefix)
->updateAssetsFieldValues($fields, $dottedPrefix)
->updateLinkFieldValues($fields, $dottedPrefix)
->updateBardFieldValues($fields, $dottedPrefix)
->updateMarkdownFieldValues($fields, $dottedPrefix)
->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.
*
Expand Down
34 changes: 34 additions & 0 deletions src/Data/DataReferenceUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
*
Expand Down
136 changes: 136 additions & 0 deletions src/Fieldtypes/UpdatesReferences.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

namespace Statamic\Fieldtypes;

use Statamic\Fields\Fields;

/**
* Trait for custom fieldtypes to participate in reference updates (assets, terms, etc.).
*
* Override only the methods you need:
* - replaceAssetReferences() for direct asset references
* - replaceTermReferences() for direct term references
* - processNestedFieldsForReferences() for nested Statamic fields
*/
trait UpdatesReferences
{
/**
* Replace asset references in the fieldtype's data.
* Override this if your fieldtype stores direct asset references.
*
* @param mixed $data Current field data
* @param string|null $newValue New asset path (null if removing)
* @param string $oldValue Old asset path
* @return mixed Modified data (or null to remove field value)
*/
public function replaceAssetReferences($data, $newValue, $oldValue)
{
return $data;
}

/**
* Replace term references in the fieldtype's data.
* Override this if your fieldtype stores direct term references.
*
* @param mixed $data Current field data
* @param string|null $newValue New term slug (null if removing)
* @param string $oldValue Old term slug
* @return mixed Modified data (or null to remove field value)
*/
public function replaceTermReferences($data, $newValue, $oldValue)
{
return $data;
}

/**
* Process nested fields for reference updates.
* Override this if your fieldtype contains nested Statamic fields.
*
* @param mixed $data Current field data
* @param callable $processFields fn(Fields $fields, string $relativeDottedPrefix): void
*/
public function processNestedFieldsForReferences($data, callable $processFields)
{
// Default: no nested fields to process
}

/**
* Helper: Process fields for a single (group-like) structure.
* Resulting prefix: ""
*
* @param callable $processFields
* @param array|string $fieldsConfig
*/
protected function processSingleNestedFields(callable $processFields, $fieldsConfig)
{
$fields = $this->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 ?? []);
}
}
45 changes: 45 additions & 0 deletions src/Taxonomies/TermReferenceUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Statamic\Taxonomies;

use Statamic\Data\DataReferenceUpdater;
use Statamic\Fieldtypes\UpdatesReferences;
use Statamic\Support\Arr;

class TermReferenceUpdater extends DataReferenceUpdater
Expand Down Expand Up @@ -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.
*
Expand Down
Loading