Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.x] Add validation to asset containers #10227

Merged
merged 25 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion resources/js/components/assets/Uploader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export default {

response.status === 200
? this.handleUploadSuccess(id, json)
: this.handleUploadError(id, status, json);
: this.handleUploadError(id, response.status, json);
});
},

Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions resources/lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -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?',
Expand Down
16 changes: 16 additions & 0 deletions src/Assets/AssetContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class AssetContainer implements Arrayable, ArrayAccess, AssetContainerContract,
protected $withEvents = true;
protected $sortField;
protected $sortDirection;
protected $validation;

public function id($id = null)
{
Expand Down Expand Up @@ -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('/'), '/');
Expand Down Expand Up @@ -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, [
Expand Down
8 changes: 8 additions & 0 deletions src/Contracts/Assets/AssetContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
9 changes: 8 additions & 1 deletion src/Http/Controllers/CP/Assets/AssetContainersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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'),
],
],
],
]);
Expand Down
6 changes: 4 additions & 2 deletions src/Http/Controllers/CP/Assets/AssetsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(), '/');

Expand Down
3 changes: 2 additions & 1 deletion src/Stache/Stores/AssetContainersStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
}
}
12 changes: 12 additions & 0 deletions tests/Assets/AssetContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
12 changes: 12 additions & 0 deletions tests/Feature/Assets/StoreAssetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
5 changes: 4 additions & 1 deletion tests/Stache/Stores/AssetContainersStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading