From f1d3149dcdcbbd8f78b57cab0fea80c1955a80c7 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 29 Nov 2024 13:19:55 +0000 Subject: [PATCH] Allow importing asset alt text (#50) * Allow importing asset alt text * Fix styling * wip * wip * Make this `values` instead. --------- Co-authored-by: duncanmcclean --- DOCUMENTATION.md | 2 + lang/en/messages.php | 1 + src/Fieldtypes/ImportMappingsFieldtype.php | 2 +- src/Jobs/ImportItemJob.php | 2 +- src/Transformers/AbstractTransformer.php | 3 +- src/Transformers/AssetsTransformer.php | 28 +++++++- tests/Transformers/AssetsTransformerTest.php | 70 +++++++++++++++++++- tests/WordPress/GutenbergTest.php | 4 +- 8 files changed, 105 insertions(+), 7 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index e71c2af..53e21e4 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -58,6 +58,8 @@ When you're configuring mappings for an Assets field, or a Bard field, a few add * However, if you wish, the importer can download any missing assets for you into the configured asset container. * **Folder** * By default, downloaded assets will use same folder structure as the original URL. If you'd like to download them into a specific folder, you can select one here. +* **Alt Text** + * Determine which field in the row contains the alt text for the asset. This option will only be shown when the Asset blueprint has an `alt` field. When importing a Bard field, assets will only be imported when the "Container" config option has been set in the blueprint. diff --git a/lang/en/messages.php b/lang/en/messages.php index 13c5ef2..da21005 100644 --- a/lang/en/messages.php +++ b/lang/en/messages.php @@ -15,6 +15,7 @@ 'strategy_instructions' => 'Choose what should happen when importing.', 'unique_field_instructions' => 'Select a "unique field" to determine if an item already exists.', + 'assets_alt_instructions' => 'Which field should be used for the alt text?', 'assets_base_url_instructions' => 'The base URL to prepend to the path.', 'assets_download_when_missing_instructions' => 'If the asset can\'t be found in the asset container, should it be downloaded?', 'assets_folder_instructions' => 'By default, downloaded assets will use same folder structure as the original URL. You can specify a different folder here.', diff --git a/src/Fieldtypes/ImportMappingsFieldtype.php b/src/Fieldtypes/ImportMappingsFieldtype.php index 15fc759..d396621 100644 --- a/src/Fieldtypes/ImportMappingsFieldtype.php +++ b/src/Fieldtypes/ImportMappingsFieldtype.php @@ -102,7 +102,7 @@ private function fields(): Collection $fields = []; if ($transformer = Importer::getTransformer($field->type())) { - $fields = (new $transformer(field: $field))->fieldItems(); + $fields = (new $transformer(import: $import, field: $field))->fieldItems(); } $blueprint = Blueprint::makeFromFields([ diff --git a/src/Jobs/ImportItemJob.php b/src/Jobs/ImportItemJob.php index dc06262..d7283d3 100644 --- a/src/Jobs/ImportItemJob.php +++ b/src/Jobs/ImportItemJob.php @@ -41,7 +41,7 @@ public function handle(): void } if ($transformer = Importer::getTransformer($field->type())) { - $value = (new $transformer($this->import, $blueprint, $field, $mapping))->transform($value); + $value = (new $transformer($this->import, $blueprint, $field, $mapping, $this->item))->transform($value); } return [$fieldHandle => $value]; diff --git a/src/Transformers/AbstractTransformer.php b/src/Transformers/AbstractTransformer.php index 7de4f0d..d6ed85c 100644 --- a/src/Transformers/AbstractTransformer.php +++ b/src/Transformers/AbstractTransformer.php @@ -17,7 +17,8 @@ public function __construct( protected ?Import $import = null, protected ?Blueprint $blueprint = null, protected ?Field $field = null, - protected ?array $config = null + protected ?array $config = null, + protected ?array $values = null, ) {} abstract public function transform(string $value); diff --git a/src/Transformers/AssetsTransformer.php b/src/Transformers/AssetsTransformer.php index c03d084..e81b4ee 100644 --- a/src/Transformers/AssetsTransformer.php +++ b/src/Transformers/AssetsTransformer.php @@ -9,6 +9,9 @@ use Statamic\Facades\Asset; use Statamic\Facades\AssetContainer; use Statamic\Facades\Path; +use Statamic\Importer\Sources\Csv; +use Statamic\Importer\Sources\Xml; +use Statamic\Support\Arr; use Statamic\Support\Str; class AssetsTransformer extends AbstractTransformer @@ -53,6 +56,10 @@ public function transform(string $value): null|string|array $asset->save(); } + if ($alt = $this->config('alt')) { + $asset?->set('alt', Arr::get($this->values, $alt))->save(); + } + return $asset?->path(); })->filter(); @@ -86,7 +93,7 @@ private function assetPath(AssetContainerContract $assetContainer, string $path) public function fieldItems(): array { - return [ + $fieldItems = [ 'related_field' => [ 'type' => 'select', 'display' => __('Related Field'), @@ -119,5 +126,24 @@ public function fieldItems(): array 'max_items' => 1, ], ]; + + if (AssetContainer::find($this->field->get('container'))->blueprint()->hasField('alt')) { + $row = match ($this->import?->get('type')) { + 'csv' => (new Csv($this->import))->getItems($this->import->get('path'))->first(), + 'xml' => (new Xml($this->import))->getItems($this->import->get('path'))->first(), + }; + + $fieldItems['alt'] = [ + 'type' => 'select', + 'display' => __('Alt Text'), + 'instructions' => __('importer::messages.assets_alt_instructions'), + 'options' => collect($row)->map(fn ($value, $key) => [ + 'key' => $key, + 'value' => "<{$key}>: ".Str::truncate($value, 200), + ])->values()->all(), + ]; + } + + return $fieldItems; } } diff --git a/tests/Transformers/AssetsTransformerTest.php b/tests/Transformers/AssetsTransformerTest.php index 3b9d640..d8e5cb1 100644 --- a/tests/Transformers/AssetsTransformerTest.php +++ b/tests/Transformers/AssetsTransformerTest.php @@ -17,6 +17,7 @@ class AssetsTransformerTest extends TestCase { use PreventsSavingStacheItemsToDisk; + public $assetContainer; public $collection; public $blueprint; public $field; @@ -26,7 +27,7 @@ protected function setUp(): void { parent::setUp(); - AssetContainer::make('assets')->disk('public')->save(); + $this->assetContainer = tap(AssetContainer::make('assets')->disk('public'))->save(); $this->collection = tap(Collection::make('pages'))->save(); @@ -160,4 +161,71 @@ public function it_doesnt_download_new_asset_when_download_when_missing_option_i Storage::disk('public')->assertMissing('2024/10/image.png'); } + + #[Test] + public function it_sets_alt_text_on_existing_asset() + { + Http::preventStrayRequests(); + + Storage::disk('public')->put('2024/10/image.png', 'original'); + + $transformer = new AssetsTransformer( + import: $this->import, + blueprint: $this->blueprint, + field: $this->field, + config: [ + 'related_field' => 'url', + 'base_url' => 'https://example.com/wp-content/uploads', + 'alt' => 'Image Alt Text', + ], + values: [ + 'Image Alt Text' => 'A photo taken by someone.', + ], + ); + + $asset = $this->assetContainer->asset('2024/10/image.png'); + $this->assertNull($asset->get('alt')); + + $output = $transformer->transform('https://example.com/wp-content/uploads/2024/10/image.png'); + $this->assertEquals('2024/10/image.png', $output); + + $asset = $this->assetContainer->asset('2024/10/image.png'); + $this->assertEquals('A photo taken by someone.', $asset->get('alt')); + } + + #[Test] + public function it_sets_alt_text_on_downloaded_asset() + { + Http::fake([ + 'https://example.com/wp-content/uploads/2024/10/image.png' => Http::response(UploadedFile::fake()->image('image.png')->size(100)->get()), + ]); + + Storage::disk('public')->assertMissing('2024/10/image.png'); + + $transformer = new AssetsTransformer( + import: $this->import, + blueprint: $this->blueprint, + field: $this->field, + config: [ + 'related_field' => 'url', + 'base_url' => 'https://example.com/wp-content/uploads', + 'download_when_missing' => true, + 'alt' => 'Image Alt Text', + ], + values: [ + 'Image Alt Text' => 'A photo taken by someone.', + ], + ); + + $this->assertNull($this->assetContainer->asset('2024/10/image.png')); + + $output = $transformer->transform('https://example.com/wp-content/uploads/2024/10/image.png'); + + $this->assertEquals('2024/10/image.png', $output); + + Storage::disk('public')->assertExists('2024/10/image.png'); + + $asset = $this->assetContainer->asset('2024/10/image.png'); + $this->assertEquals('A photo taken by someone.', $asset->get('alt')); + } } diff --git a/tests/WordPress/GutenbergTest.php b/tests/WordPress/GutenbergTest.php index f8636d9..9b8e5c2 100644 --- a/tests/WordPress/GutenbergTest.php +++ b/tests/WordPress/GutenbergTest.php @@ -199,7 +199,7 @@ public function it_transforms_image_blocks() field: $this->blueprint->field('content'), value: <<<'HTML' -
+
A cool photo of a thing.
HTML ); @@ -212,7 +212,7 @@ public function it_transforms_image_blocks() 'type' => 'image', 'attrs' => [ 'src' => 'assets::2024/10/image.png', - 'alt' => null, + 'alt' => 'A cool photo of a thing.', ], ], ],