Skip to content

Commit

Permalink
feat(upload): add upload rules and upload custom validation
Browse files Browse the repository at this point in the history
  • Loading branch information
mikaelpopowicz committed Sep 9, 2022
1 parent d499da0 commit adfea3f
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 9 deletions.
1 change: 1 addition & 0 deletions lang/en/errors.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
return [
'file' => [
'upload' => 'The file could not be uploaded!',
'upload_validation' => 'The uploaded file is invalid!',
'rename' => 'The file could not be renamed!',
'delete' => 'The file could not be deleted!',
],
Expand Down
18 changes: 17 additions & 1 deletion src/Contracts/Support/InteractsWithFilesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* @property ?\Closure $filesystemCallback
*/
interface InteractsWithFilesystem
interface InteractsWithFilesystem extends ResolvesUrl
{
public function filesystem(Closure $callback): static;

Expand Down Expand Up @@ -71,5 +71,21 @@ public function canDeleteFile(Closure $callback): static;

public function resolveCanDeleteFile(NovaRequest $request): bool;

public function hasUploadValidator(): bool;

public function getUploadValidator(): ?Closure;

/**
* Set the validation rules for the upload.
*
* @param callable|array<int, string|\Illuminate\Validation\Rule|\Illuminate\Contracts\Validation\Rule|callable>|string ...$rules
* @return $this
*/
public function uploadRules($rules): static;

public function getUploadRules(): array;

public function validateUploadUsing(Closure $callback): static;

public function options(): array;
}
4 changes: 1 addition & 3 deletions src/FileManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@

use BBSLab\NovaFileManager\Contracts\Services\FileManagerContract;
use BBSLab\NovaFileManager\Contracts\Support\InteractsWithFilesystem;
use BBSLab\NovaFileManager\Contracts\Support\ResolvesUrl;
use BBSLab\NovaFileManager\Support\Asset;
use Closure;
use JsonException;
use Laravel\Nova\Fields\Field;
use Laravel\Nova\Http\Requests\NovaRequest;

class FileManager extends Field implements InteractsWithFilesystem, ResolvesUrl
class FileManager extends Field implements InteractsWithFilesystem
{
use Traits\Support\InteractsWithFilesystem;
use Traits\Support\ResolvesUrl;

public $component = 'nova-file-manager-field';

Expand Down
13 changes: 13 additions & 0 deletions src/Filesystem/Upload/Uploader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use BBSLab\NovaFileManager\Events\FileUploaded;
use BBSLab\NovaFileManager\Http\Requests\UploadFileRequest;
use Illuminate\Http\UploadedFile;
use Illuminate\Validation\ValidationException;
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
use Pion\Laravel\ChunkUpload\Handler\HandlerFactory;
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
Expand All @@ -20,6 +21,12 @@ class Uploader implements UploaderContract
*/
public function handle(UploadFileRequest $request, string $index = 'file'): array
{
if (! $request->validateUpload()) {
throw ValidationException::withMessages([
'file' => [__('nova-file-manager::errors.file.upload_validation')],
]);
}

$receiver = new FileReceiver($index, $request, HandlerFactory::classFromRequest($request));

if ($receiver->isUploaded() === false) {
Expand All @@ -42,6 +49,12 @@ public function handle(UploadFileRequest $request, string $index = 'file'): arra

public function saveFile(UploadFileRequest $request, UploadedFile $file): array
{
if (! $request->validateUpload($file, true)) {
throw ValidationException::withMessages([
'file' => [__('nova-file-manager::errors.file.upload_validation')],
]);
}

$path = $request->manager()->filesystem()->putFileAs(
path: $request->path,
file: $file,
Expand Down
26 changes: 24 additions & 2 deletions src/Http/Requests/UploadFileRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

namespace BBSLab\NovaFileManager\Http\Requests;

use BBSLab\NovaFileManager\Filesystem\Support\GetID3;
use BBSLab\NovaFileManager\Rules\DiskExistsRule;
use BBSLab\NovaFileManager\Rules\ExistsInFilesystem;
use BBSLab\NovaFileManager\Rules\FileMissingInFilesystem;
use Illuminate\Http\UploadedFile;

/**
* @property-read string|null $disk
* @property-read string $path
* @property-read string $file
* @property-read \Illuminate\Http\UploadedFile $file
*/
class UploadFileRequest extends BaseRequest
{
Expand All @@ -25,7 +27,27 @@ public function rules(): array
return [
'disk' => ['sometimes', 'string', new DiskExistsRule()],
'path' => ['required', 'string', new ExistsInFilesystem($this)],
'file' => ['required', 'file', new FileMissingInFilesystem($this)],
'file' => array_merge(
['required', 'file', new FileMissingInFilesystem($this)],
$this->element()->getUploadRules(),
),
];
}

public function validateUpload(?UploadedFile $file = null, bool $saving = false): bool
{
if (!$this->element()->hasUploadValidator()) {
return true;
}

$file ??= $this->file('file');

return call_user_func(
$this->element()->getUploadValidator(),
$this,
$file,
(new GetID3())->analyze($file->path()),
$saving,
);
}
}
4 changes: 1 addition & 3 deletions src/NovaFileManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
namespace BBSLab\NovaFileManager;

use BBSLab\NovaFileManager\Contracts\Support\InteractsWithFilesystem;
use BBSLab\NovaFileManager\Contracts\Support\ResolvesUrl;
use Illuminate\Http\Request;
use Laravel\Nova\Menu\MenuSection;
use Laravel\Nova\Tool;

class NovaFileManager extends Tool implements InteractsWithFilesystem, ResolvesUrl
class NovaFileManager extends Tool implements InteractsWithFilesystem
{
use Traits\Support\InteractsWithFilesystem;
use Traits\Support\ResolvesUrl;

public function menu(Request $request): mixed
{
Expand Down
46 changes: 46 additions & 0 deletions src/Traits/Support/InteractsWithFilesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

use Closure;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Validation\Rule;
use Laravel\Nova\Http\Requests\NovaRequest;

trait InteractsWithFilesystem
{
use ResolvesUrl;

protected ?Closure $filesystemCallback = null;

protected ?Closure $showCreateFolder = null;
Expand Down Expand Up @@ -38,6 +41,10 @@ trait InteractsWithFilesystem

protected ?Closure $canDeleteFile = null;

protected array $uploadRules = [];

protected ?Closure $uploadValidator = null;

public function filesystem(Closure $callback): static
{
$this->filesystemCallback = $callback;
Expand Down Expand Up @@ -239,6 +246,45 @@ public function resolveCanDeleteFile(NovaRequest $request): bool
: $this->shouldShowDeleteFile($request);
}

public function hasUploadValidator(): bool
{
return $this->uploadValidator !== null && is_callable($this->uploadValidator);
}

public function getUploadValidator(): ?Closure
{
return $this->uploadValidator;
}

/**
* Set the validation rules for the upload.
*
* @param callable|array<int, string|\Illuminate\Validation\Rule|\Illuminate\Contracts\Validation\Rule|callable>|string ...$rules
* @return $this
*/
public function uploadRules($rules): static
{
if ($rules instanceof Closure) {
$this->uploadRules = [$rules];
} else {
$this->uploadRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules;
}

return $this;
}

public function getUploadRules(): array
{
return $this->uploadRules;
}

public function validateUploadUsing(Closure $callback): static
{
$this->uploadValidator = $callback;

return $this;
}

public function options(): array
{
return with(app(NovaRequest::class), function (NovaRequest $request) {
Expand Down
60 changes: 60 additions & 0 deletions tests/Feature/File/UploadFilePermissionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
use BBSLab\NovaFileManager\Http\Requests\UploadFileRequest;
use BBSLab\NovaFileManager\NovaFileManager;
use Illuminate\Foundation\Auth\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
use function Pest\Laravel\actingAs;

beforeEach(function () {
$this->disk = 'public';
Expand Down Expand Up @@ -88,3 +90,61 @@

$this->performUnauthorizedUploadChecks($message);
});

it('can validate upload', function () {
Nova::$tools = [
NovaFileManager::make()
->validateUploadUsing(function (UploadFileRequest $request, UploadedFile $file, array $meta, bool $saving) {
return str_contains($file->getClientOriginalName(), 'foo');
}),
];

actingAs($this->user)
->postJson(
uri: route('nova-file-manager.files.upload'),
data: [
'disk' => $this->disk,
'path' => '/',
'file' => UploadedFile::fake()->image($path = 'image.jpeg'),
],
)
->assertUnprocessable()
->assertJsonValidationErrors([
'file' => [__('nova-file-manager::errors.file.upload_validation')],
]);

Storage::disk($this->disk)->assertMissing($path);
});

it('can throw a custom validation message using validateUploadUsing', function () {
$message = 'File name must contains `foo`';

Nova::$tools = [
NovaFileManager::make()
->validateUploadUsing(function (UploadFileRequest $request, UploadedFile $file, array $meta, bool $saving) use ($message) {
if (!str_contains($request->path, 'foo')) {
throw ValidationException::withMessages([
'file' => [$message],
]);
}

return true;
}),
];

actingAs($this->user)
->postJson(
uri: route('nova-file-manager.files.upload'),
data: [
'disk' => $this->disk,
'path' => '/',
'file' => UploadedFile::fake()->image($path = 'image.jpeg'),
],
)
->assertUnprocessable()
->assertJsonValidationErrors([
'file' => [$message],
]);

Storage::disk($this->disk)->assertMissing($path);
});

0 comments on commit adfea3f

Please sign in to comment.