From f8bb31f3567400a604b9db6c33f16e669c1186d3 Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Mon, 30 Mar 2026 16:22:46 +0100 Subject: [PATCH] upload images to nova --- app/Nova/Actions/BulkUploadMediaFiles.php | 84 ++++++++++++++++++----- composer.json | 1 + composer.lock | 66 +++++++++++++++++- 3 files changed, 133 insertions(+), 18 deletions(-) diff --git a/app/Nova/Actions/BulkUploadMediaFiles.php b/app/Nova/Actions/BulkUploadMediaFiles.php index ee8e91869..7f0ef256c 100644 --- a/app/Nova/Actions/BulkUploadMediaFiles.php +++ b/app/Nova/Actions/BulkUploadMediaFiles.php @@ -3,15 +3,17 @@ namespace App\Nova\Actions; use App\MediaUpload; +use DigitalCreative\Filepond\Filepond; +use DigitalCreative\Filepond\Data\Data; use Illuminate\Bus\Queueable; use Illuminate\Http\UploadedFile; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Laravel\Nova\Actions\Action; use Laravel\Nova\Fields\ActionFields; -use Laravel\Nova\Fields\File; use Laravel\Nova\Http\Requests\NovaRequest; class BulkUploadMediaFiles extends Action @@ -53,20 +55,22 @@ public function handle(ActionFields $fields, Collection $models) continue; } - $extension = strtolower((string) $file->getClientOriginalExtension()); + $originalName = $this->extractOriginalName($file); + if ($originalName === null) { + $skipped++; + continue; + } + $extension = strtolower((string) pathinfo($originalName, PATHINFO_EXTENSION)); if (!in_array($extension, $allowedExtensions, true)) { $skipped++; continue; } - $baseName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); + $baseName = pathinfo($originalName, PATHINFO_FILENAME); $safeBaseName = Str::slug($baseName) ?: 'upload-file'; $destination = 'nova/uploads/' . now()->format('Y/m'); - $storedPath = $file->storeAs( - $destination, - $safeBaseName . '-' . Str::random(8) . '.' . $extension, - 'resources' - ); + $finalFileName = $safeBaseName . '-' . Str::random(8) . '.' . $extension; + $storedPath = $this->storeFile($file, $destination, $finalFileName); if (!$storedPath) { $skipped++; @@ -94,14 +98,10 @@ public function handle(ActionFields $fields, Collection $models) public function fields(NovaRequest $request): array { return [ - File::make('Files', 'files') - ->withMeta([ - 'extraAttributes' => [ - 'name' => 'files[]', - 'multiple' => true, - 'accept' => '.jpg,.jpeg,.png,.gif,.webp,.svg,.pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.txt', - ], - ]) + Filepond::make('Files', 'files') + ->multiple() + ->limit(50) + ->acceptedTypes('.jpg,.jpeg,.png,.gif,.webp,.svg,.pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.txt') ->rules('required') ->help('Drag and drop multiple files or click to select. Supported: images, PDF, Office docs, and TXT.'), ]; @@ -120,6 +120,11 @@ protected function collectUploadedFiles(ActionFields $fields): array $candidates[] = $fields->files; } + $requestItems = request()->collect('files')->all(); + if (!empty($requestItems)) { + $candidates[] = $requestItems; + } + $requestFiles = request()->allFiles(); if (!empty($requestFiles)) { $candidates[] = $requestFiles; @@ -130,6 +135,10 @@ protected function collectUploadedFiles(ActionFields $fields): array return [$value]; } + if (is_string($value)) { + return [$value]; + } + if (!is_array($value)) { return []; } @@ -150,4 +159,47 @@ protected function collectUploadedFiles(ActionFields $fields): array return $files; } + protected function extractOriginalName(UploadedFile|string $file): ?string + { + if ($file instanceof UploadedFile) { + return $file->getClientOriginalName(); + } + + try { + $data = Data::fromEncrypted($file); + return $data->filename; + } catch (\Throwable $e) { + return null; + } + } + + protected function storeFile(UploadedFile|string $file, string $destination, string $finalFileName): ?string + { + if ($file instanceof UploadedFile) { + return $file->storeAs($destination, $finalFileName, 'resources'); + } + + try { + $data = Data::fromEncrypted($file); + } catch (\Throwable $e) { + return null; + } + + $stream = Storage::disk($data->disk)->readStream($data->path); + if ($stream === false) { + return null; + } + + $targetPath = $destination . '/' . $finalFileName; + $stored = Storage::disk('resources')->put($targetPath, $stream, 'public'); + if (is_resource($stream)) { + fclose($stream); + } + + // Clean temporary Filepond directory. + $data->deleteDirectory(); + + return $stored ? $targetPath : null; + } + } diff --git a/composer.json b/composer.json index d61d8d086..68c6dae96 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "ext-zip": "*", "aws/aws-sdk-php": "^3.131", "ctessier/nova-advanced-image-field": "^2.1", + "digital-creative/nova-filepond": "^1.0", "doctrine/dbal": "^3.5", "guzzlehttp/guzzle": "^7.2", "intervention/image": "^2.7", diff --git a/composer.lock b/composer.lock index de3c97fed..ae27ef404 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1005558c6f232eb68ca7680739855a59", + "content-hash": "555647c3f7d64f5720946ef6cd5de3c1", "packages": [ { "name": "archtechx/enums", @@ -836,6 +836,68 @@ }, "time": "2024-07-08T12:26:09+00:00" }, + { + "name": "digital-creative/nova-filepond", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/dcasia/nova-filepond.git", + "reference": "d2301c01e17da3fa2fff4a9ea3cddef7c9511af2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dcasia/nova-filepond/zipball/d2301c01e17da3fa2fff4a9ea3cddef7c9511af2", + "reference": "d2301c01e17da3fa2fff4a9ea3cddef7c9511af2", + "shasum": "" + }, + "require": { + "laravel/nova": "^4.0", + "php": ">=8.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "DigitalCreative\\Filepond\\FilepondServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "DigitalCreative\\Filepond\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rafael Milewski" + } + ], + "description": "A Nova field for uploading File, Image and Video using filepond.", + "keywords": [ + "field", + "file", + "laravel", + "nova", + "nova4", + "upload", + "video" + ], + "support": { + "issues": "https://github.com/dcasia/nova-filepond/issues", + "source": "https://github.com/dcasia/nova-filepond/tree/v1.0.6" + }, + "funding": [ + { + "url": "https://github.com/milewski", + "type": "github" + } + ], + "time": "2024-01-31T04:51:42+00:00" + }, { "name": "doctrine/dbal", "version": "3.10.4", @@ -13828,5 +13890,5 @@ "ext-zip": "*" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" }