From 7b0b48cc30cd24f14ba39e77d1e3f47b2d044984 Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Fri, 19 Feb 2021 09:43:29 +0100 Subject: [PATCH 01/10] Safely get asset meta values --- src/Assets/Asset.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index c11106cc4f3..209bb771cd1 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -134,6 +134,21 @@ public function meta() }); } + protected function metaValue($key) + { + $value = array_get($this->meta(), $key); + + if (! is_null($value)) { + return $value; + } + + Cache::forget($this->metaCacheKey()); + + $this->writeMeta($meta = $this->generateMeta()); + + return array_get($meta, $key); + } + public function generateMeta() { $meta = ['data' => $this->data->all()]; @@ -360,7 +375,7 @@ public function extension() */ public function lastModified() { - return Carbon::createFromTimestamp($this->meta()['last_modified']); + return Carbon::createFromTimestamp($this->metaValue('last_modified')); } /** @@ -492,7 +507,7 @@ public function move($folder, $filename = null) */ public function dimensions() { - return [$this->meta()['width'], $this->meta()['height']]; + return [$this->metaValue('width'), $this->metaValue('height')]; } /** @@ -554,7 +569,7 @@ public function ratio() */ public function size() { - return $this->meta()['size']; + return $this->metaValue('size'); } /** From b652b7f80dd25becd5b53585be8a026104519bdb Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Mon, 22 Feb 2021 09:17:19 +0100 Subject: [PATCH 02/10] Add mimetypes validation rule for assets --- src/Assets/Asset.php | 14 +++++++- src/Fieldtypes/Assets/Assets.php | 10 ++++-- src/Fieldtypes/Assets/MimetypesRule.php | 48 +++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/Fieldtypes/Assets/MimetypesRule.php diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index 209bb771cd1..e2cbb5f6b9f 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -10,7 +10,6 @@ use Statamic\Contracts\Data\Augmentable; use Statamic\Contracts\Data\Augmented; use Statamic\Data\ContainsData; -use Statamic\Data\Data; use Statamic\Data\HasAugmentedInstance; use Statamic\Events\AssetDeleted; use Statamic\Events\AssetSaved; @@ -27,6 +26,7 @@ use Statamic\Support\Traits\FluentlyGetsAndSets; use Stringy\Stringy; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\Mime\MimeTypes; class Asset implements AssetContract, Augmentable { @@ -159,6 +159,8 @@ public function generateMeta() $meta = array_merge($meta, [ 'size' => $this->disk()->size($this->path()), 'last_modified' => $this->disk()->lastModified($this->path()), + 'mime_type' => $mimeType = $this->disk()->mimeType($this->path()), + 'guessed_extension' => MimeTypes::getDefault()->getExtensions($mimeType)[0] ?? null, 'width' => $dimensions[0], 'height' => $dimensions[1], ]); @@ -548,6 +550,16 @@ public function orientation() return null; } + /** + * Get the asset's MIME type. + * + * @return + */ + public function mimeType() + { + return $this->metaValue('mime_type'); + } + /** * Get the asset's ratio. * diff --git a/src/Fieldtypes/Assets/Assets.php b/src/Fieldtypes/Assets/Assets.php index aa31ffa86c2..f57b6462e1c 100644 --- a/src/Fieldtypes/Assets/Assets.php +++ b/src/Fieldtypes/Assets/Assets.php @@ -171,17 +171,23 @@ public function fieldRules() { return collect(parent::fieldRules())->map(function ($rule) { $name = Str::before($rule, ':'); + + if ($parameters = Str::after($rule, ':')) { + $parameters = explode(',', $rule); + } if ($name === 'image') { return new ImageRule(); } if ($name === 'mimes') { - $parameters = explode(',', Str::after($rule, ':')); - return new MimesRule($parameters); } + if ($name === 'mimetypes') { + return new MimetypesRule($parameters); + } + return $rule; })->all(); } diff --git a/src/Fieldtypes/Assets/MimetypesRule.php b/src/Fieldtypes/Assets/MimetypesRule.php new file mode 100644 index 00000000000..1aaab34d98c --- /dev/null +++ b/src/Fieldtypes/Assets/MimetypesRule.php @@ -0,0 +1,48 @@ +parameters = $parameters; + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value) + { + return collect($value)->every(function ($id) { + if ($id instanceof UploadedFile) { + $mimeType = $id->getMimeType(); + } else if (! ($mimeType = optional(Asset::find($id))->mimeType())) { + return false; + } + + return in_array($mimeType, $this->parameters) || + in_array(explode('/', $mimeType)[0].'/*', $this->parameters); + }); + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return str_replace(':values', join(', ', $this->parameters), __('statamic::validation.mimetypes')); + } +} From ddaba26c68066eff14606adcfd79616ce6dace77 Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Mon, 22 Feb 2021 09:42:48 +0100 Subject: [PATCH 03/10] Add a test for the new metaValue method --- tests/Assets/AssetTest.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index b31f260d787..c2d247ce9e0 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -376,6 +376,38 @@ public function it_generates_meta_on_demand_if_it_doesnt_exist() $this->assertEquals($metaWithData, Cache::get($asset->metaCacheKey())); } + /** @test */ + public function it_generates_meta_on_demand_if_a_required_value_is_missing() + { + Storage::fake('test'); + + $file = UploadedFile::fake()->image('image.jpg', 30, 60); // creates a 723 byte image + Storage::disk('test')->putFileAs('foo', $file, 'image.jpg'); + $realFilePath = Storage::disk('test')->getAdapter()->getPathPrefix().'foo/image.jpg'; + touch($realFilePath, $timestamp = Carbon::parse('2021-02-22 09:41:42')->timestamp); + + $container = Facades\AssetContainer::make('test')->disk('test'); + $asset = (new Asset)->container($container)->path('foo/image.jpg'); + + $incompleteMeta = [ + 'data' => [], + ]; + + $completeMeta = [ + 'data' => [], + 'size' => 723, + 'last_modified' => $timestamp, + 'width' => 30, + 'height' => 60, + ]; + + Storage::disk('test')->put('foo/.meta/image.jpg.yaml', YAML::dump($incompleteMeta)); + + $asset->size(); + + $this->assertEquals($completeMeta, YAML::parse(Storage::disk('test')->get('foo/.meta/image.jpg.yaml'))); + } + /** @test */ public function it_hydrates_data_from_meta_file() { From 11e52b024ed981053e08f3944ef19d3fc1332ccb Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Mon, 22 Feb 2021 10:19:45 +0100 Subject: [PATCH 04/10] Use guessed extension in mime validation and fix tests --- src/Assets/Asset.php | 20 ++++++++++++++++++++ src/Fieldtypes/Assets/ImageRule.php | 8 +++++--- src/Fieldtypes/Assets/MimesRule.php | 2 +- tests/Assets/AssetRepositoryTest.php | 2 ++ tests/Assets/AssetTest.php | 6 ++++++ 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index e2cbb5f6b9f..ea7e3e30a76 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -370,6 +370,16 @@ public function extension() return Path::extension($this->path()); } + /** + * Get the guessed file extension of the asset based on its MIME type. + * + * @return string + */ + public function guessedExtension() + { + return $this->metaValue('guessed_extension'); + } + /** * Get the last modified time of the asset. * @@ -687,6 +697,16 @@ public function extensionIsOneOf($filetypes = []) return in_array(strtolower($this->extension()), $filetypes); } + /** + * Check if asset's guessed file extension is one of a given list. + * + * @return string + */ + public function guessedExtensionIsOneOf($filetypes = []) + { + return in_array(strtolower($this->guessedExtension()), $filetypes); + } + public function __toString() { return $this->url() ?? $this->id(); diff --git a/src/Fieldtypes/Assets/ImageRule.php b/src/Fieldtypes/Assets/ImageRule.php index 97919cca0ff..0d965e82c6a 100644 --- a/src/Fieldtypes/Assets/ImageRule.php +++ b/src/Fieldtypes/Assets/ImageRule.php @@ -24,16 +24,18 @@ public function __construct($parameters = null) */ public function passes($attribute, $value) { - return collect($value)->every(function ($id) { + $extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; + + return collect($value)->every(function ($id) use ($extensions) { if ($id instanceof UploadedFile) { - return in_array($id->guessExtension(), ['jpg', 'jpeg', 'png', 'gif', 'webp']); + return in_array($id->guessExtension(), $extensions); } if (! $asset = Asset::find($id)) { return false; } - return $asset->isImage(); + return $asset->guessedExtensionIsOneOf($extensions); }); } diff --git a/src/Fieldtypes/Assets/MimesRule.php b/src/Fieldtypes/Assets/MimesRule.php index 1196472e111..441fe7d8c7e 100644 --- a/src/Fieldtypes/Assets/MimesRule.php +++ b/src/Fieldtypes/Assets/MimesRule.php @@ -37,7 +37,7 @@ public function passes($attribute, $value) return false; } - return $asset->extensionIsOneOf($this->parameters); + return $asset->guessedExtensionIsOneOf($this->parameters); }); } diff --git a/tests/Assets/AssetRepositoryTest.php b/tests/Assets/AssetRepositoryTest.php index a8f055282f9..6c2d231a250 100644 --- a/tests/Assets/AssetRepositoryTest.php +++ b/tests/Assets/AssetRepositoryTest.php @@ -33,6 +33,8 @@ public function it_saves_the_meta_file_to_disk() data: { } size: 723 last_modified: $timestamp +mime_type: image/jpeg +guessed_extension: jpg width: 30 height: 60 diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index c2d247ce9e0..0cf0c5283b3 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -346,6 +346,8 @@ public function it_generates_meta_on_demand_if_it_doesnt_exist() 'data' => [], 'size' => 723, 'last_modified' => Carbon::parse('2012-01-02 4:57pm')->timestamp, + 'mime_type' => 'image/jpeg', + 'guessed_extension' => 'jpg', 'width' => 30, 'height' => 60, ]; @@ -354,6 +356,8 @@ public function it_generates_meta_on_demand_if_it_doesnt_exist() 'data' => ['foo' => 'bar'], 'size' => 723, 'last_modified' => Carbon::parse('2012-01-02 4:57pm')->timestamp, + 'mime_type' => 'image/jpeg', + 'guessed_extension' => 'jpg', 'width' => 30, 'height' => 60, ]; @@ -397,6 +401,8 @@ public function it_generates_meta_on_demand_if_a_required_value_is_missing() 'data' => [], 'size' => 723, 'last_modified' => $timestamp, + 'mime_type' => 'image/jpeg', + 'guessed_extension' => 'jpg', 'width' => 30, 'height' => 60, ]; From 25a850fe508565382d61df17fe0186c469cbbd0e Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Mon, 22 Feb 2021 10:55:47 +0100 Subject: [PATCH 05/10] Fix code style issues --- src/Fieldtypes/Assets/Assets.php | 2 +- src/Fieldtypes/Assets/MimetypesRule.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fieldtypes/Assets/Assets.php b/src/Fieldtypes/Assets/Assets.php index f57b6462e1c..63e80a37e17 100644 --- a/src/Fieldtypes/Assets/Assets.php +++ b/src/Fieldtypes/Assets/Assets.php @@ -171,7 +171,7 @@ public function fieldRules() { return collect(parent::fieldRules())->map(function ($rule) { $name = Str::before($rule, ':'); - + if ($parameters = Str::after($rule, ':')) { $parameters = explode(',', $rule); } diff --git a/src/Fieldtypes/Assets/MimetypesRule.php b/src/Fieldtypes/Assets/MimetypesRule.php index 1aaab34d98c..d74cb00e174 100644 --- a/src/Fieldtypes/Assets/MimetypesRule.php +++ b/src/Fieldtypes/Assets/MimetypesRule.php @@ -27,7 +27,7 @@ public function passes($attribute, $value) return collect($value)->every(function ($id) { if ($id instanceof UploadedFile) { $mimeType = $id->getMimeType(); - } else if (! ($mimeType = optional(Asset::find($id))->mimeType())) { + } elseif (! ($mimeType = optional(Asset::find($id))->mimeType())) { return false; } From 08de696460d964d166fd845be80a7b1a03794547 Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Mon, 22 Feb 2021 11:03:34 +0100 Subject: [PATCH 06/10] Fix failing tests --- tests/Assets/AssetRepositoryTest.php | 2 +- tests/Assets/AssetTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Assets/AssetRepositoryTest.php b/tests/Assets/AssetRepositoryTest.php index 6c2d231a250..b5837813dc6 100644 --- a/tests/Assets/AssetRepositoryTest.php +++ b/tests/Assets/AssetRepositoryTest.php @@ -34,7 +34,7 @@ public function it_saves_the_meta_file_to_disk() size: 723 last_modified: $timestamp mime_type: image/jpeg -guessed_extension: jpg +guessed_extension: jpeg width: 30 height: 60 diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 0cf0c5283b3..b7d9728af62 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -347,7 +347,7 @@ public function it_generates_meta_on_demand_if_it_doesnt_exist() 'size' => 723, 'last_modified' => Carbon::parse('2012-01-02 4:57pm')->timestamp, 'mime_type' => 'image/jpeg', - 'guessed_extension' => 'jpg', + 'guessed_extension' => 'jpeg', 'width' => 30, 'height' => 60, ]; @@ -357,7 +357,7 @@ public function it_generates_meta_on_demand_if_it_doesnt_exist() 'size' => 723, 'last_modified' => Carbon::parse('2012-01-02 4:57pm')->timestamp, 'mime_type' => 'image/jpeg', - 'guessed_extension' => 'jpg', + 'guessed_extension' => 'jpeg', 'width' => 30, 'height' => 60, ]; @@ -402,7 +402,7 @@ public function it_generates_meta_on_demand_if_a_required_value_is_missing() 'size' => 723, 'last_modified' => $timestamp, 'mime_type' => 'image/jpeg', - 'guessed_extension' => 'jpg', + 'guessed_extension' => 'jpeg', 'width' => 30, 'height' => 60, ]; From 147f2cb09a8000cd8ce30f6bd4a5926210f9d16a Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Mon, 22 Feb 2021 11:09:46 +0100 Subject: [PATCH 07/10] Refix failing tests?? --- tests/Assets/AssetRepositoryTest.php | 2 +- tests/Assets/AssetTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Assets/AssetRepositoryTest.php b/tests/Assets/AssetRepositoryTest.php index b5837813dc6..6c2d231a250 100644 --- a/tests/Assets/AssetRepositoryTest.php +++ b/tests/Assets/AssetRepositoryTest.php @@ -34,7 +34,7 @@ public function it_saves_the_meta_file_to_disk() size: 723 last_modified: $timestamp mime_type: image/jpeg -guessed_extension: jpeg +guessed_extension: jpg width: 30 height: 60 diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index b7d9728af62..0cf0c5283b3 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -347,7 +347,7 @@ public function it_generates_meta_on_demand_if_it_doesnt_exist() 'size' => 723, 'last_modified' => Carbon::parse('2012-01-02 4:57pm')->timestamp, 'mime_type' => 'image/jpeg', - 'guessed_extension' => 'jpeg', + 'guessed_extension' => 'jpg', 'width' => 30, 'height' => 60, ]; @@ -357,7 +357,7 @@ public function it_generates_meta_on_demand_if_it_doesnt_exist() 'size' => 723, 'last_modified' => Carbon::parse('2012-01-02 4:57pm')->timestamp, 'mime_type' => 'image/jpeg', - 'guessed_extension' => 'jpeg', + 'guessed_extension' => 'jpg', 'width' => 30, 'height' => 60, ]; @@ -402,7 +402,7 @@ public function it_generates_meta_on_demand_if_a_required_value_is_missing() 'size' => 723, 'last_modified' => $timestamp, 'mime_type' => 'image/jpeg', - 'guessed_extension' => 'jpeg', + 'guessed_extension' => 'jpg', 'width' => 30, 'height' => 60, ]; From 91f57a7321c3a78a3ef7511e081931aefca58d79 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 10 Mar 2021 11:30:25 -0500 Subject: [PATCH 08/10] fix merge --- src/Assets/Asset.php | 12 ------------ tests/Assets/AssetRepositoryTest.php | 2 -- 2 files changed, 14 deletions(-) diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index a2c31b11a6b..05e7336c130 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -164,8 +164,6 @@ public function generateMeta() $meta = array_merge($meta, [ 'size' => $this->disk()->size($this->path()), 'last_modified' => $this->disk()->lastModified($this->path()), - 'mime_type' => $mimeType = $this->disk()->mimeType($this->path()), - 'guessed_extension' => MimeTypes::getDefault()->getExtensions($mimeType)[0] ?? null, 'width' => $dimensions[0], 'height' => $dimensions[1], 'mime_type' => $this->disk()->mimeType($this->path()), @@ -587,16 +585,6 @@ public function orientation() return null; } - /** - * Get the asset's MIME type. - * - * @return - */ - public function mimeType() - { - return $this->metaValue('mime_type'); - } - /** * Get the asset's ratio. * diff --git a/tests/Assets/AssetRepositoryTest.php b/tests/Assets/AssetRepositoryTest.php index 29f1d293a5c..d0fc95a799d 100644 --- a/tests/Assets/AssetRepositoryTest.php +++ b/tests/Assets/AssetRepositoryTest.php @@ -33,8 +33,6 @@ public function it_saves_the_meta_file_to_disk() data: { } size: 723 last_modified: $timestamp -mime_type: image/jpeg -guessed_extension: jpg width: 30 height: 60 mime_type: image/jpeg From 1ca7478ba57a599059715bb094d6bb75c36e1d4b Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 10 Mar 2021 12:05:23 -0500 Subject: [PATCH 09/10] add extra extensions from the laravel validation rule --- src/Fieldtypes/Assets/ImageRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/Assets/ImageRule.php b/src/Fieldtypes/Assets/ImageRule.php index 0d965e82c6a..ba3f6fb6e36 100644 --- a/src/Fieldtypes/Assets/ImageRule.php +++ b/src/Fieldtypes/Assets/ImageRule.php @@ -24,7 +24,7 @@ public function __construct($parameters = null) */ public function passes($attribute, $value) { - $extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; + $extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']; return collect($value)->every(function ($id) use ($extensions) { if ($id instanceof UploadedFile) { From 9db3baca81e25153c8391bc205f4d0ff53104739 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 10 Mar 2021 12:26:53 -0500 Subject: [PATCH 10/10] fix mimes: being passed into parameters. conditional didnt seem necessary --- src/Fieldtypes/Assets/Assets.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Fieldtypes/Assets/Assets.php b/src/Fieldtypes/Assets/Assets.php index 66eab764f9a..f812987ef17 100644 --- a/src/Fieldtypes/Assets/Assets.php +++ b/src/Fieldtypes/Assets/Assets.php @@ -173,10 +173,7 @@ public function fieldRules() { return collect(parent::fieldRules())->map(function ($rule) { $name = Str::before($rule, ':'); - - if ($parameters = Str::after($rule, ':')) { - $parameters = explode(',', $rule); - } + $parameters = explode(',', Str::after($rule, ':')); if ($name === 'image') { return new ImageRule();