diff --git a/resources/js/components/assets/Uploader.vue b/resources/js/components/assets/Uploader.vue index 9b18d9def4..4c16383972 100644 --- a/resources/js/components/assets/Uploader.vue +++ b/resources/js/components/assets/Uploader.vue @@ -186,7 +186,7 @@ export default { response.status === 200 ? this.handleUploadSuccess(id, json) - : this.handleUploadError(id, status, json); + : this.handleUploadError(id, response.status, json); }); }, @@ -204,6 +204,10 @@ export default { } else { msg = __('Upload failed. The file might be larger than is allowed by your server.'); } + } else { + if (status === 422) { + msg = Object.values(response.errors)[0][0]; // Get first validation message. + } } upload.errorMessage = msg; this.$emit('error', upload, this.uploads); diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index 2f0aec2f17..324478e73c 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -21,6 +21,7 @@ 'asset_container_title_instructions' => 'Usually a plural noun, like Images or Documents', 'asset_container_warm_intelligent_instructions' => 'Generate appropriate presets on upload.', 'asset_container_warm_presets_instructions' => 'Specify which presets to generate on upload.', + 'asset_container_validation_rules_instructions' => 'These rules will be applied to uploaded files.', 'asset_folders_directory_instructions' => 'We recommend avoiding spaces and special characters to keep URLs clean.', 'asset_replace_confirmation' => 'References to this asset within content will be updated to the asset you select below.', 'asset_reupload_confirmation' => 'Are you sure you want to reupload this asset?', diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php index 020d6f7833..8322b8b649 100644 --- a/src/Assets/AssetContainer.php +++ b/src/Assets/AssetContainer.php @@ -49,6 +49,7 @@ class AssetContainer implements Arrayable, ArrayAccess, AssetContainerContract, protected $withEvents = true; protected $sortField; protected $sortDirection; + protected $validation; public function id($id = null) { @@ -99,6 +100,20 @@ public function title($title = null) ->args(func_get_args()); } + /** + * Get or set the validation rules. + * + * @param null|array $rules + * @return array + */ + public function validationRules($rules = null) + { + return $this + ->fluentlyGetOrSet('validation') + ->getter(fn ($rules) => $rules ?? []) + ->args(func_get_args()); + } + public function diskPath() { return rtrim($this->disk()->path('/'), '/'); @@ -625,6 +640,7 @@ public function fileData() 'create_folders' => $this->createFolders, 'source_preset' => $this->sourcePreset, 'warm_presets' => $this->warmPresets, + 'validate' => $this->validation, ]; $array = Arr::removeNullValues(array_merge($array, [ diff --git a/src/Contracts/Assets/AssetContainer.php b/src/Contracts/Assets/AssetContainer.php index 756e74e3c6..138e071317 100644 --- a/src/Contracts/Assets/AssetContainer.php +++ b/src/Contracts/Assets/AssetContainer.php @@ -101,4 +101,12 @@ public function accessible(); * @return bool */ public function private(); + + /** + * Get or set the validation rules. + * + * @param null|array $rules + * @return array + */ + // public function validationRules($rules = null); } diff --git a/src/Http/Controllers/CP/Assets/AssetContainersController.php b/src/Http/Controllers/CP/Assets/AssetContainersController.php index 57f0a50fa5..86ed6351b9 100644 --- a/src/Http/Controllers/CP/Assets/AssetContainersController.php +++ b/src/Http/Controllers/CP/Assets/AssetContainersController.php @@ -65,6 +65,7 @@ public function edit($container) 'source_preset' => $container->sourcePreset(), 'warm_intelligent' => $intelligent = $container->warmsPresetsIntelligently(), 'warm_presets' => $intelligent ? [] : $container->warmPresets(), + 'validation' => $container->validationRules(), ]; $fields = ($blueprint = $this->formBlueprint($container)) @@ -99,7 +100,8 @@ public function update(Request $request, $container) ->allowUploads($values['allow_uploads']) ->createFolders($values['create_folders']) ->sourcePreset($values['source_preset']) - ->warmPresets($values['warm_intelligent'] ? null : $values['warm_presets']); + ->warmPresets($values['warm_intelligent'] ? null : $values['warm_presets']) + ->validationRules($values['validation'] ?? null); $container->save(); @@ -265,6 +267,11 @@ protected function formBlueprint($container = null) 'instructions' => __('statamic::messages.asset_container_quick_download_instructions'), 'default' => true, ], + 'validation' => [ + 'type' => 'taggable', + 'display' => __('Validation Rules'), + 'instructions' => __('statamic::messages.asset_container_validation_rules_instructions'), + ], ], ], ]); diff --git a/src/Http/Controllers/CP/Assets/AssetsController.php b/src/Http/Controllers/CP/Assets/AssetsController.php index 1a76894d29..a276c56ba8 100644 --- a/src/Http/Controllers/CP/Assets/AssetsController.php +++ b/src/Http/Controllers/CP/Assets/AssetsController.php @@ -69,15 +69,17 @@ public function store(Request $request) $request->validate([ 'container' => 'required', 'folder' => 'required', - 'file' => ['file', new AllowedFile], ]); $container = AssetContainer::find($request->container); abort_unless($container->allowUploads(), 403); - $this->authorize('store', [AssetContract::class, $container]); + $request->validate([ + 'file' => array_merge(['file', new AllowedFile], $container->validationRules()), + ]); + $file = $request->file('file'); $path = ltrim($request->folder.'/'.$file->getClientOriginalName(), '/'); diff --git a/src/Stache/Stores/AssetContainersStore.php b/src/Stache/Stores/AssetContainersStore.php index 536ea7b000..05a1ccc040 100644 --- a/src/Stache/Stores/AssetContainersStore.php +++ b/src/Stache/Stores/AssetContainersStore.php @@ -30,6 +30,7 @@ public function makeItemFromFile($path, $contents) ->warmPresets(Arr::get($data, 'warm_presets')) ->searchIndex(Arr::get($data, 'search_index')) ->sortField(Arr::get($data, 'sort_by')) - ->sortDirection(Arr::get($data, 'sort_dir')); + ->sortDirection(Arr::get($data, 'sort_dir')) + ->validationRules(Arr::get($data, 'validate')); } } diff --git a/tests/Assets/AssetContainerTest.php b/tests/Assets/AssetContainerTest.php index 40c2b53996..41731c0459 100644 --- a/tests/Assets/AssetContainerTest.php +++ b/tests/Assets/AssetContainerTest.php @@ -230,6 +230,18 @@ public function it_gets_and_sets_whether_downloading_is_allowed() $this->assertFalse($container->allowDownloading()); } + /** @test */ + public function it_gets_and_sets_the_validation_rules() + { + $container = new AssetContainer; + $this->assertEmpty($container->validationRules()); + + $return = $container->validationRules(['max:5120']); + + $this->assertEquals($container, $return); + $this->assertEquals(['max:5120'], $container->validationRules()); + } + /** @test */ public function it_gets_and_sets_glide_source_preset_for_upload_processing() { diff --git a/tests/Feature/Assets/StoreAssetTest.php b/tests/Feature/Assets/StoreAssetTest.php index 5c059e8243..93ff220137 100644 --- a/tests/Feature/Assets/StoreAssetTest.php +++ b/tests/Feature/Assets/StoreAssetTest.php @@ -93,6 +93,18 @@ public function it_doesnt_upload_without_a_folder() ])->assertStatus(422); } + /** @test */ + public function it_doesnt_upload_when_validation_fails() + { + + $this->container->validationRules(['extensions:png'])->save(); + + $this + ->actingAs($this->userWithPermission()) + ->submit() + ->assertStatus(422); + } + private function submit($overrides = []) { return $this->postJson(cp_route('assets.store'), $this->validPayload($overrides)); diff --git a/tests/Stache/Stores/AssetContainersStoreTest.php b/tests/Stache/Stores/AssetContainersStoreTest.php index b3ea2404d9..98fb48e9a3 100644 --- a/tests/Stache/Stores/AssetContainersStoreTest.php +++ b/tests/Stache/Stores/AssetContainersStoreTest.php @@ -125,12 +125,15 @@ public function it_saves_to_disk() EOT; $this->assertStringEqualsFile($this->tempDir.'/new.yaml', $expected); - $container->allowUploads(false)->createFolders(false)->save(); + $container->allowUploads(false)->createFolders(false)->validationRules(['max:150', 'mimes:jpg'])->save(); $expected = <<<'EOT' title: 'New Container' allow_uploads: false create_folders: false +validate: + - 'max:150' + - 'mimes:jpg' EOT; $this->assertStringEqualsFile($this->tempDir.'/new.yaml', $expected);