From df72a1899ad274b466c1655c41689455c3801671 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 16 Jan 2024 16:39:24 +0000 Subject: [PATCH] Add validation to prevent duplicate field handles --- resources/js/components/blueprints/Field.vue | 3 +++ resources/js/components/blueprints/Fields.vue | 2 ++ .../js/components/blueprints/ImportField.vue | 2 ++ .../js/components/blueprints/RegularField.vue | 2 ++ resources/js/components/fields/Settings.vue | 6 ++++- .../js/components/fieldsets/EditForm.vue | 2 +- resources/lang/en/validation.php | 1 + .../CP/Fields/FieldsController.php | 25 ++++++++++++++++++- 8 files changed, 40 insertions(+), 3 deletions(-) diff --git a/resources/js/components/blueprints/Field.vue b/resources/js/components/blueprints/Field.vue index ad1b97710f8..ef53d41ddec 100644 --- a/resources/js/components/blueprints/Field.vue +++ b/resources/js/components/blueprints/Field.vue @@ -10,6 +10,9 @@ export default { type: Boolean, default: true }, + fields: { + type: Array, + }, isEditing: Boolean } diff --git a/resources/js/components/blueprints/Fields.vue b/resources/js/components/blueprints/Fields.vue index ed896ef0982..f8912cf3ee6 100644 --- a/resources/js/components/blueprints/Fields.vue +++ b/resources/js/components/blueprints/Fields.vue @@ -14,6 +14,7 @@ :is="fieldComponent(field)" :key="field._id" :field="field" + :fields="fields" :is-editing="editingField === field._id" :suggestable-condition-fields="suggestableConditionFields" :can-define-localizable="canDefineLocalizable" @@ -55,6 +56,7 @@ ref="settings" :type="pendingCreatedField.config.type" :root="true" + :fields="fields" :config="pendingCreatedField.config" :suggestable-condition-fields="suggestableConditionFields" @committed="fieldCreated" diff --git a/resources/js/components/blueprints/ImportField.vue b/resources/js/components/blueprints/ImportField.vue index 98326105870..a63d91b28d2 100644 --- a/resources/js/components/blueprints/ImportField.vue +++ b/resources/js/components/blueprints/ImportField.vue @@ -16,7 +16,9 @@ [] }, type: String, root: Boolean, + fields: Array, suggestableConditionFields: Array, }, @@ -240,8 +242,10 @@ export default { this.clearErrors(); this.$axios.post(cp_url('fields/update'), { + id: this.id, type: this.type, - values: this.values + values: this.values, + fields: this.fields }).then(response => { this.$emit('committed', response.data, this.editedFields); this.close(); diff --git a/resources/js/components/fieldsets/EditForm.vue b/resources/js/components/fieldsets/EditForm.vue index 575ecbd2430..8f526a6c17a 100644 --- a/resources/js/components/fieldsets/EditForm.vue +++ b/resources/js/components/fieldsets/EditForm.vue @@ -34,7 +34,7 @@
'Duplicate URI :value', 'reserved' => 'This is a reserved word.', 'reserved_field_handle' => 'Field with a handle of :handle is a reserved word.', + 'duplicate_field_handle' => 'A field with a handle of :handle already exists.', 'parent_causes_root_children' => 'This would cause the root page to have children.', 'parent_cannot_be_itself' => 'Cannot be its own parent.', 'time' => 'Not a valid time.', diff --git a/src/Http/Controllers/CP/Fields/FieldsController.php b/src/Http/Controllers/CP/Fields/FieldsController.php index 14e64050568..31f178b24aa 100644 --- a/src/Http/Controllers/CP/Fields/FieldsController.php +++ b/src/Http/Controllers/CP/Fields/FieldsController.php @@ -5,6 +5,7 @@ use Facades\Statamic\Fields\FieldtypeRepository; use Illuminate\Http\Request; use Statamic\Facades\Blueprint; +use Statamic\Facades\Fieldset; use Statamic\Http\Controllers\CP\CpController; use Statamic\Http\Middleware\CP\CanManageBlueprints; @@ -49,8 +50,10 @@ public function edit(Request $request) public function update(Request $request) { $request->validate([ + 'id' => 'nullable', 'type' => 'required', 'values' => 'required|array', + 'fields' => 'sometimes|array', ]); $fieldtype = FieldtypeRepository::find($request->type); @@ -61,7 +64,27 @@ public function update(Request $request) ->fields() ->addValues($request->values); - $extraRules = []; + $extraRules = [ + 'handle' => [ + function ($attribute, $value, $fail) use ($request) { + $existingFieldWithHandle = collect($request->fields ?? []) + ->when($request->id, fn ($collection) => $collection->reject(fn ($field) => $field['_id'] === $request->id)) + ->flatMap(function (array $field) { + if ($field['type'] === 'import') { + return Fieldset::find($field['fieldset'])->fields()->all()->map->handle()->toArray(); + } + + return [$field['handle']]; + }) + ->values() + ->contains($request->values['handle']); + + if ($existingFieldWithHandle) { + $fail(__('statamic::validation.duplicate_field_handle', ['handle' => $value])); + } + }, + ], + ]; $customMessages = [ 'handle.not_in' => __('statamic::validation.reserved'), ];