From c826e88c458288f7f33bd8ac1a3b60b8336e3ebf Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 19 Dec 2024 20:46:28 +0000 Subject: [PATCH] Drop "Unique Field" option for term & user imports --- DOCUMENTATION.md | 2 +- lang/en/validation.php | 2 + src/Fieldtypes/ImportMappingsFieldtype.php | 1 + src/Imports/Blueprint.php | 19 ++++++-- src/Jobs/ImportItemJob.php | 6 +-- tests/Imports/UpdateImportTest.php | 52 +++++++++++++++++++++- 6 files changed, 73 insertions(+), 9 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 9abc5b3..c47632a 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -32,7 +32,7 @@ Before importing, you will need to do some preparation: 3. You can then map fields from your blueprint to fields/columns in your file. * Depending on the fieldtype, some fields may have additional options, like "Related Key" or "Create when missing". You can read more about these below. * Mapping is disabled for some fieldtypes, like the [Replicator fieldtype](https://statamic.dev/fieldtypes/replicator#content). If you wish to import these fields, you will need to build a [custom transformer](#transformers). -4. You will also need to specify a "Unique Field". This field will be used to determine if an item already exists in Statamic. +4. If you're importing entries, you will also need to specify a "Unique Field". This field will be used to determine if an entry already exists in Statamic. 5. Then, run the import and watch the magic happen! ✨ You can run the importer as many times as you like as you tweak the mappings. It'll update existing content and create new content as needed. diff --git a/lang/en/validation.php b/lang/en/validation.php index 6d093c0..21bd931 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -2,7 +2,9 @@ return [ 'file_type_not_allowed' => 'Only CSV and XML files can be imported at this time.', + 'mappings_email_missing' => 'The Email field must be mapped on user imports.', 'mappings_not_provided' => 'You must map at least one field.', + 'mappings_slug_missing' => 'The Slug field must be mapped on taxonomy imports.', 'site_not_configured_in_collection' => 'The chosen collection is not available on this site.', 'site_not_configured_in_taxonomy' => 'The chosen taxonomy is not available on this site.', 'unique_field_without_mapping' => 'Please configure a mapping for this field.', diff --git a/src/Fieldtypes/ImportMappingsFieldtype.php b/src/Fieldtypes/ImportMappingsFieldtype.php index d396621..71681b5 100644 --- a/src/Fieldtypes/ImportMappingsFieldtype.php +++ b/src/Fieldtypes/ImportMappingsFieldtype.php @@ -69,6 +69,7 @@ public function extraRules(): array return collect($this->field->value()) ->reject(fn ($row) => empty($row['key'])) + ->filter(fn ($row) => $fields->has($row['key'])) ->flatMap(function (array $row, string $field) use ($fields) { $rules = $fields ->get($field) diff --git a/src/Imports/Blueprint.php b/src/Imports/Blueprint.php index b099350..be3c577 100644 --- a/src/Imports/Blueprint.php +++ b/src/Imports/Blueprint.php @@ -199,9 +199,19 @@ function (string $attribute, mixed $value, Closure $fail) { 'required', 'array', function (string $attribute, mixed $value, Closure $fail) { + $type = Arr::get(request()->destination, 'type'); + if (collect($value)->reject(fn (array $mapping) => empty($mapping['key']))->isEmpty()) { $fail('importer::validation.mappings_not_provided')->translate(); } + + if ($type === 'terms' && Arr::get($value, 'slug.key') === null) { + $fail('importer::validation.mappings_slug_missing')->translate(); + } + + if ($type === 'users' && Arr::get($value, 'email.key') === null) { + $fail('importer::validation.mappings_email_missing')->translate(); + } }, ], 'if' => $import ? static::buildFieldConditions($import) : null, @@ -213,19 +223,22 @@ function (string $attribute, mixed $value, Closure $fail) { 'type' => 'radio', 'display' => __('Unique Field'), 'instructions' => __('importer::messages.unique_field_instructions'), - 'options' => $import?->destinationBlueprint()->fields()->all() + 'options' => $import?->mappingFields()->all() ->filter(fn ($field) => in_array($field->type(), ['text', 'integer', 'slug'])) ->map(fn ($field) => ['key' => $field->handle(), 'value' => $field->display()]) ->values(), 'validate' => [ - 'required', + 'required_if:destination.type,entries', function (string $attribute, mixed $value, Closure $fail) { if (! collect(request()->mappings)->reject(fn ($mapping) => empty($mapping['key']))->has($value)) { $fail('importer::validation.unique_field_without_mapping')->translate(); } }, ], - 'if' => $import ? static::buildFieldConditions($import) : null, + 'if' => [ + 'destination.type' => 'entries', + ], +// 'if' => $import ? static::buildFieldConditions($import) : null, ], ], ], diff --git a/src/Jobs/ImportItemJob.php b/src/Jobs/ImportItemJob.php index c2110a9..e12aa6d 100644 --- a/src/Jobs/ImportItemJob.php +++ b/src/Jobs/ImportItemJob.php @@ -120,8 +120,8 @@ protected function findOrCreateEntry(array $data): void protected function findOrCreateTerm(array $data): void { $term = Term::query() + ->where('slug', $data['slug']) ->where('taxonomy', $this->import->get('destination.taxonomy')) - ->where($this->import->get('unique_field'), $data[$this->import->get('unique_field')]) ->first(); if (! $term) { @@ -153,9 +153,7 @@ protected function findOrCreateTerm(array $data): void protected function findOrCreateUser(array $data): void { - $user = User::query() - ->where($this->import->get('unique_field'), $data[$this->import->get('unique_field')]) - ->first(); + $user = User::findByEmail($data['email']); if (! $user) { if (! in_array('create', $this->import->get('strategy'))) { diff --git a/tests/Imports/UpdateImportTest.php b/tests/Imports/UpdateImportTest.php index e208079..6a2b319 100644 --- a/tests/Imports/UpdateImportTest.php +++ b/tests/Imports/UpdateImportTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Storage; use PHPUnit\Framework\Attributes\Test; use Statamic\Facades\Collection; +use Statamic\Facades\Taxonomy; use Statamic\Facades\User; use Statamic\Importer\Facades\Import; use Statamic\Importer\Tests\TestCase; @@ -224,7 +225,56 @@ public function validation_error_is_thrown_without_any_mappings() } #[Test] - public function throws_validation_errors_for_mapping_fields() + public function validation_error_is_thrown_for_terms_import_without_slug_mapping() + { + Taxonomy::make('tags')->save(); + + $this + ->actingAs(User::make()->makeSuper()->save()) + ->patch("/cp/utilities/importer/{$this->import->id()}", [ + 'name' => 'Posts', + 'file' => ['posts.csv'], + 'destination' => ['type' => 'terms', 'taxonomy' => ['tags'], 'blueprint' => 'tag'], + 'strategy' => ['create', 'update'], + 'mappings' => [ + 'title' => ['key' => 'Title'], + 'slug' => ['key' => null], + ], + ]) + ->assertSessionHasErrors('mappings'); + } + + #[Test] + public function validation_error_is_thrown_for_users_import_without_email_mapping() + { + User::blueprint()->setContents([ + 'sections' => [ + 'main' => [ + 'fields' => [ + ['handle' => 'name', 'field' => ['type' => 'text']], + ['handle' => 'email', 'field' => ['type' => 'text']], + ], + ], + ], + ]); + + $this + ->actingAs(User::make()->makeSuper()->save()) + ->patch("/cp/utilities/importer/{$this->import->id()}", [ + 'name' => 'Posts', + 'file' => ['posts.csv'], + 'destination' => ['type' => 'users'], + 'strategy' => ['create', 'update'], + 'mappings' => [ + 'name' => ['key' => 'Name'], + 'email' => ['key' => null], + ], + ]) + ->assertSessionHasErrors('mappings'); + } + + #[Test] + public function validation_errors_are_thrown_for_transformer_fields() { $this ->actingAs(User::make()->makeSuper()->save())