diff --git a/README.md b/README.md index 8216af7..487f351 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,10 @@ | 5 | Upload file lên Amazon S3 | [`part-5-upload-to-s3`](https://github.com/10h30/laravel-file-upload-series/tree/part-5-upload-to-s3) | | 6 | Temporary URL & Upload lên MinIO | [`part-6-s3-temporary-url-minio`](https://github.com/10h30/laravel-file-upload-series/tree/part-6-s3-temporary-url-minio) | | 7 | Create thumbnail with Intervetion Image | [`part-7-thumbnail-intervention`](https://github.com/10h30/laravel-file-upload-series/tree/part-7-thumbnail-intervention) | +| 8 | File Upload with Spatie Media Library | [`part8-spatie-media-library`](https://github.com/10h30/laravel-file-upload-series/tree/part8-spatie-media-library) | | | + > 📖 Mỗi branch tương ứng với một phần trong series blog. Bạn có thể clone và chạy từng phần riêng biệt để dễ theo dõi. --- diff --git a/app/Http/Controllers/UploadController.php b/app/Http/Controllers/UploadController.php index a45ade7..de735a1 100644 --- a/app/Http/Controllers/UploadController.php +++ b/app/Http/Controllers/UploadController.php @@ -4,9 +4,6 @@ namespace App\Http\Controllers; use App\Models\Upload; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Storage; -use Intervention\Image\Drivers\Imagick\Driver; -use Intervention\Image\ImageManager; class UploadController extends Controller { @@ -36,13 +33,10 @@ class UploadController extends Controller ]); // Tạo biến mới để lưu đường dẫn và tên file gốc - $storedFilePaths = []; // Array lưu đường dẫn các file đã lưu thành công $originalFilenames = []; // Array lưu tên gốc của các file $uploadedFiles = $request->file('files'); // Lấy array các đối tượng file đã upload $numberOfFiles = count($uploadedFiles); // Đếm số lượng file đã upload - $manager = new ImageManager(Driver::class); - // Lặp qua từng file trong array $uploadedFiles foreach ($uploadedFiles as $file) { @@ -50,74 +44,28 @@ class UploadController extends Controller $originalFilename = $file->getClientOriginalName(); $originalFilenames[] = $originalFilename; // Thêm tên gốc vào array - // Chuẩn bị các phần của tên file - $filenameWithoutExtension = pathinfo($originalFilename, PATHINFO_FILENAME); // Lấy tên file không có phần mở rộng - $extension = $file->getClientOriginalExtension(); // Lấy phần mở rộng - $directory = 'uploads'; // Thư mục lưu file trên disk - $disk = 'minio'; // Disk S3 sẽ sử dụng (được định nghĩa trong config/filesystems.php) - - // Xác định tên file duy nhất - $finalFilename = $originalFilename; // Bắt đầu với tên gốc - $counter = 1; - - // Kiểm tra xem file đã tồn tại chưa - while (Storage::disk($disk)->exists($directory . '/' . $finalFilename)) { - // Nếu tồn tại, tạo tên mới với hậu tố 1,2,3,... - $finalFilename = $filenameWithoutExtension . '-' . $counter . '.' . $extension; - $counter++; - } - - // Lưu file bằng storeAs với tên file mới và trả về đường dẫn tương đối: 'uploads/ten_file_cuoi_cung.jpg' - $storedFilePath = $file->storeAs($directory, $finalFilename, $disk); - - // Tạo thumbnail bằng Intervention Image - $thumbnail = $manager->read($file->getRealPath()) - ->resize(100, 100); // Resize to fit 100x100, maintaining aspect ratio - - // Đường dẫn tương đối của file thumbnail: 'uploads/thumbnail-ten_file_cuoi_cung.jpg' - $thumbnailStoragePath = $directory . '/thumbnail-' . $finalFilename; - - // Lưu thumbnail vào disk - Storage::disk($disk)->put($thumbnailStoragePath, $thumbnail->encode()); - - // Trả về Temporary URL của file - $urlFilePath = Storage::disk($disk)->temporaryUrl($thumbnailStoragePath, now()->addMinutes(5)); - - // Thêm đường dẫn file đã lưu vào array $storedFilePaths - $storedFilePaths[] = $urlFilePath; - - // Tạo bản ghi mới trong table uploads của database - Upload::create([ - 'filename' => $storedFilePath, + // 3. Tạo bản ghi trong database cho model Upload: + // Lưu ý: Chúng ta chỉ cần lưu 'original_filename'. + // Các thông tin về đường dẫn file gốc và thumbnail sẽ do media-library quản lý. + $uploadEntry = Upload::create([ 'original_filename' => $originalFilename, - 'thumbnail' => $thumbnailStoragePath, ]); + // 4. Đây là phần quan trọng nhất - Thêm file vào Media Library: + $uploadEntry->addMedia($file) // Thêm file vào Media Library + ->toMediaCollection('images'); // Thêm file vào collection 'images' } // Chuyển hướng về trang trước đó return back()->with('success', 'You have successfully uploaded ' . $numberOfFiles . ' files') - // Gửi kèm array các đường dẫn file đã lưu vào session flash data với key 'stored_paths' - ->with('stored_paths', $storedFilePaths) // Gửi kèm array các tên file gốc vào session flash data với key 'original_filenames' ->with('original_filenames', $originalFilenames); } public function destroy(Upload $upload) { - // Xoá file vật lý khỏi disk 'public' dựa vào đường dẫn lưu trong $upload->filename - // The disk used for storing was 's3'. - $disk = 'minio'; - - if (Storage::disk($disk)->exists($upload->filename)) { - Storage::disk($disk)->delete($upload->filename); - } - - if (Storage::disk($disk)->exists($upload->thumbnail)) { - Storage::disk($disk)->delete($upload->thumbnail); - } - - // Xoá bản ghi tương ứng trong database + // Spatie Media Library sẽ tự động xoá các file liên quan (file gốc và các file chuyển đổi) + // khỏi disk khi model bị xoá. $upload->delete(); // Chuyển hướng người dùng về trang trước đó với thông báo thành công diff --git a/app/Models/Upload.php b/app/Models/Upload.php index 3af39a2..edeb7d3 100644 --- a/app/Models/Upload.php +++ b/app/Models/Upload.php @@ -4,35 +4,48 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Storage; +use Spatie\MediaLibrary\HasMedia; +use Spatie\MediaLibrary\InteractsWithMedia; +use Spatie\MediaLibrary\MediaCollections\Models\Media; -class Upload extends Model + +class Upload extends Model implements HasMedia { + use InteractsWithMedia; + protected $fillable = [ - 'filename', 'original_filename', - 'thumbnail', ]; + public function registerMediaConversions(?Media $media = null): void + { + $this->addMediaConversion('thumbnail') + ->width(100) + ->height(100) + ->nonQueued() + ->performOnCollections('images'); // images là tên collection lưu ảnh + } + public function getUrlAttribute(): string { - // Đảm bảo 's3' là tên disk chính xác được sử dụng để lưu trữ các file này. - $disk = 'minio'; - if ($this->filename) { - // Thao tác này tạo ra một URL tạm thời mới mỗi khi thuộc tính 'url' được truy cập. - // URL sẽ có hiệu lực trong 5 phút kể từ thời điểm nó được tạo. - return Storage::disk($disk)->temporaryUrl($this->filename, now()->addMinutes(5)); // Fixed typo: $this-> to $this->filename + + $mediaItem = $this->getFirstMedia('images'); + if ($mediaItem) { + // Tạo URL tạm thời cho file gốc. + return $mediaItem->getTemporaryUrl(now()->addMinutes(5)); } - return ''; // Hoặc xử lý một cách thích hợp nếu tên tệp là null + return ''; // Trả về chuỗi rỗng hoặc một URL placeholder nếu không tìm thấy file } // Tạo URL tạm thời mới mỗi khi thuộc tính 'thumbnail_url' được truy cập public function getThumbnailUrlAttribute(): string { - $disk = 'minio'; // Ensure this matches the disk used for storing thumbnails - if ($this->thumbnail) { - // Generate temporary URL for the thumbnail path - return Storage::disk($disk)->temporaryUrl($this->thumbnail, now()->addMinutes(5)); + $mediaItem = $this->getFirstMedia('images'); + + if ($mediaItem) { + // Tạo URL tạm thời cho file thumbnail (conversion) + return $mediaItem->getTemporaryUrl(now()->addMinutes(5), 'thumbnail'); } - return ''; // Hoặc xử lý một cách thích hợp nếu tên tệp là null + return ''; // Trả về chuỗi rỗng hoặc một URL placeholder nếu không tìm thấy thumbnail } } diff --git a/composer.json b/composer.json index 322db9b..cdf5bb0 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "intervention/image": "^3.11", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", - "league/flysystem-aws-s3-v3": "^3.0" + "league/flysystem-aws-s3-v3": "^3.0", + "spatie/laravel-medialibrary": "^11.12" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 8d00e50..2a07993 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": "c0294f265e4412b55b1e7491761d059f", + "content-hash": "f4b1d2eeabd1197b47a6aaeee6c183a7", "packages": [ { "name": "aws/aws-crt-php", @@ -286,6 +286,87 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -2356,6 +2437,84 @@ ], "time": "2024-12-08T08:18:47+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.2" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^11.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-01-27T12:07:53+00:00" + }, { "name": "monolog/monolog", "version": "3.9.0", @@ -3702,6 +3861,365 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "spatie/image", + "version": "3.8.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/image.git", + "reference": "54a7331a4d1ba7712603dd058522613506d2dfe0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/image/zipball/54a7331a4d1ba7712603dd058522613506d2dfe0", + "reference": "54a7331a4d1ba7712603dd058522613506d2dfe0", + "shasum": "" + }, + "require": { + "ext-exif": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.2", + "spatie/image-optimizer": "^1.7.5", + "spatie/temporary-directory": "^2.2", + "symfony/process": "^6.4|^7.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-imagick": "*", + "laravel/sail": "^1.34", + "pestphp/pest": "^2.28", + "phpstan/phpstan": "^1.10.50", + "spatie/pest-plugin-snapshots": "^2.1", + "spatie/pixelmatch-php": "^1.0", + "spatie/ray": "^1.40.1", + "symfony/var-dumper": "^6.4|7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Image\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Manipulate images with an expressive API", + "homepage": "https://github.com/spatie/image", + "keywords": [ + "image", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/image/tree/3.8.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-04-25T08:04:51+00:00" + }, + { + "name": "spatie/image-optimizer", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/image-optimizer.git", + "reference": "4fd22035e81d98fffced65a8c20d9ec4daa9671c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/4fd22035e81d98fffced65a8c20d9ec4daa9671c", + "reference": "4fd22035e81d98fffced65a8c20d9ec4daa9671c", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.3|^8.0", + "psr/log": "^1.0 | ^2.0 | ^3.0", + "symfony/process": "^4.2|^5.0|^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^1.21", + "phpunit/phpunit": "^8.5.21|^9.4.4", + "symfony/var-dumper": "^4.2|^5.0|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ImageOptimizer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily optimize images using PHP", + "homepage": "https://github.com/spatie/image-optimizer", + "keywords": [ + "image-optimizer", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/image-optimizer/issues", + "source": "https://github.com/spatie/image-optimizer/tree/1.8.0" + }, + "time": "2024-11-04T08:24:54+00:00" + }, + { + "name": "spatie/laravel-medialibrary", + "version": "11.12.9", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-medialibrary.git", + "reference": "2435e90009c36906c33668d26c96c86acdb39af7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/2435e90009c36906c33668d26c96c86acdb39af7", + "reference": "2435e90009c36906c33668d26c96c86acdb39af7", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "ext-exif": "*", + "ext-fileinfo": "*", + "ext-json": "*", + "illuminate/bus": "^10.2|^11.0|^12.0", + "illuminate/conditionable": "^10.2|^11.0|^12.0", + "illuminate/console": "^10.2|^11.0|^12.0", + "illuminate/database": "^10.2|^11.0|^12.0", + "illuminate/pipeline": "^10.2|^11.0|^12.0", + "illuminate/support": "^10.2|^11.0|^12.0", + "maennchen/zipstream-php": "^3.1", + "php": "^8.2", + "spatie/image": "^3.3.2", + "spatie/laravel-package-tools": "^1.16.1", + "spatie/temporary-directory": "^2.2", + "symfony/console": "^6.4.1|^7.0" + }, + "conflict": { + "php-ffmpeg/php-ffmpeg": "<0.6.1" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.293.10", + "ext-imagick": "*", + "ext-pdo_sqlite": "*", + "ext-zip": "*", + "guzzlehttp/guzzle": "^7.8.1", + "larastan/larastan": "^2.7|^3.0", + "league/flysystem-aws-s3-v3": "^3.22", + "mockery/mockery": "^1.6.7", + "orchestra/testbench": "^7.0|^8.17|^9.0|^10.0", + "pestphp/pest": "^2.28|^3.5", + "phpstan/extension-installer": "^1.3.1", + "spatie/laravel-ray": "^1.33", + "spatie/pdf-to-image": "^2.2|^3.0", + "spatie/pest-plugin-snapshots": "^2.1" + }, + "suggest": { + "league/flysystem-aws-s3-v3": "Required to use AWS S3 file storage", + "php-ffmpeg/php-ffmpeg": "Required for generating video thumbnails", + "spatie/pdf-to-image": "Required for generating thumbnails of PDFs and SVGs" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\MediaLibrary\\MediaLibraryServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\MediaLibrary\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Associate files with Eloquent models", + "homepage": "https://github.com/spatie/laravel-medialibrary", + "keywords": [ + "cms", + "conversion", + "downloads", + "images", + "laravel", + "laravel-medialibrary", + "media", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-medialibrary/issues", + "source": "https://github.com/spatie/laravel-medialibrary/tree/11.12.9" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-03-31T07:55:00+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.92.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d20b1969f836d210459b78683d85c9cd5c5f508c", + "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.28|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", + "pestphp/pest": "^1.23|^2.1|^3.1", + "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", + "phpunit/phpunit": "^9.5.24|^10.5|^11.5", + "spatie/pest-plugin-test-time": "^1.1|^2.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.4" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-04-11T15:27:14+00:00" + }, + { + "name": "spatie/temporary-directory", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-01-13T13:04:43+00:00" + }, { "name": "symfony/clock", "version": "v7.2.0", diff --git a/config/media-library.php b/config/media-library.php new file mode 100644 index 0000000..fac25c6 --- /dev/null +++ b/config/media-library.php @@ -0,0 +1,285 @@ + env('MEDIA_DISK', 'public'), + + /* + * The maximum file size of an item in bytes. + * Adding a larger file will result in an exception. + */ + 'max_file_size' => 1024 * 1024 * 10, // 10MB + + /* + * This queue connection will be used to generate derived and responsive images. + * Leave empty to use the default queue connection. + */ + 'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'), + + /* + * This queue will be used to generate derived and responsive images. + * Leave empty to use the default queue. + */ + 'queue_name' => env('MEDIA_QUEUE', ''), + + /* + * By default all conversions will be performed on a queue. + */ + 'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true), + + /* + * Should database transactions be run after database commits? + */ + 'queue_conversions_after_database_commit' => env('QUEUE_CONVERSIONS_AFTER_DB_COMMIT', true), + + /* + * The fully qualified class name of the media model. + */ + 'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class, + + /* + * The fully qualified class name of the media observer. + */ + 'media_observer' => Spatie\MediaLibrary\MediaCollections\Models\Observers\MediaObserver::class, + + /* + * When enabled, media collections will be serialised using the default + * laravel model serialization behaviour. + * + * Keep this option disabled if using Media Library Pro components (https://medialibrary.pro) + */ + 'use_default_collection_serialization' => false, + + /* + * The fully qualified class name of the model used for temporary uploads. + * + * This model is only used in Media Library Pro (https://medialibrary.pro) + */ + 'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class, + + /* + * When enabled, Media Library Pro will only process temporary uploads that were uploaded + * in the same session. You can opt to disable this for stateless usage of + * the pro components. + */ + 'enable_temporary_uploads_session_affinity' => true, + + /* + * When enabled, Media Library pro will generate thumbnails for uploaded file. + */ + 'generate_thumbnails_for_temporary_uploads' => true, + + /* + * This is the class that is responsible for naming generated files. + */ + 'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class, + + /* + * The class that contains the strategy for determining a media file's path. + */ + 'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class, + + /* + * The class that contains the strategy for determining how to remove files. + */ + 'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class, + + /* + * Here you can specify which path generator should be used for the given class. + */ + 'custom_path_generators' => [ + // Model::class => PathGenerator::class + // or + // 'model_morph_alias' => PathGenerator::class + ], + + /* + * When urls to files get generated, this class will be called. Use the default + * if your files are stored locally above the site root or on s3. + */ + 'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class, + + /* + * Moves media on updating to keep path consistent. Enable it only with a custom + * PathGenerator that uses, for example, the media UUID. + */ + 'moves_media_on_update' => false, + + /* + * Whether to activate versioning when urls to files get generated. + * When activated, this attaches a ?v=xx query string to the URL. + */ + 'version_urls' => false, + + /* + * The media library will try to optimize all converted images by removing + * metadata and applying a little bit of compression. These are + * the optimizers that will be used by default. + */ + 'image_optimizers' => [ + Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [ + '-m85', // set maximum quality to 85% + '--force', // ensure that progressive generation is always done also if a little bigger + '--strip-all', // this strips out all text information such as comments and EXIF data + '--all-progressive', // this will make sure the resulting image is a progressive one + ], + Spatie\ImageOptimizer\Optimizers\Pngquant::class => [ + '--force', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Optipng::class => [ + '-i0', // this will result in a non-interlaced, progressive scanned image + '-o2', // this set the optimization level to two (multiple IDAT compression trials) + '-quiet', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Svgo::class => [ + '--disable=cleanupIDs', // disabling because it is known to cause troubles + ], + Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [ + '-b', // required parameter for this package + '-O3', // this produces the slowest but best results + ], + Spatie\ImageOptimizer\Optimizers\Cwebp::class => [ + '-m 6', // for the slowest compression method in order to get the best compression. + '-pass 10', // for maximizing the amount of analysis pass. + '-mt', // multithreading for some speed improvements. + '-q 90', // quality factor that brings the least noticeable changes. + ], + Spatie\ImageOptimizer\Optimizers\Avifenc::class => [ + '-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63). + '-j all', // number of jobs (worker threads, "all" uses all available cores). + '--min 0', // min quantizer for color (0-63). + '--max 63', // max quantizer for color (0-63). + '--minalpha 0', // min quantizer for alpha (0-63). + '--maxalpha 63', // max quantizer for alpha (0-63). + '-a end-usage=q', // rate control mode set to Constant Quality mode. + '-a tune=ssim', // SSIM as tune the encoder for distortion metric. + ], + ], + + /* + * These generators will be used to create an image of media files. + */ + 'image_generators' => [ + Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class, + ], + + /* + * The path where to store temporary files while performing image conversions. + * If set to null, storage_path('media-library/temp') will be used. + */ + 'temporary_directory_path' => null, + + /* + * The engine that should perform the image conversions. + * Should be either `gd` or `imagick`. + */ + 'image_driver' => env('IMAGE_DRIVER', 'gd'), + + /* + * FFMPEG & FFProbe binaries paths, only used if you try to generate video + * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer + * dependency. + */ + 'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'), + 'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'), + + /* + * Here you can override the class names of the jobs used by this package. Make sure + * your custom jobs extend the ones provided by the package. + */ + 'jobs' => [ + 'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class, + 'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class, + ], + + /* + * When using the addMediaFromUrl method you may want to replace the default downloader. + * This is particularly useful when the url of the image is behind a firewall and + * need to add additional flags, possibly using curl. + */ + 'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class, + + /* + * When using the addMediaFromUrl method the SSL is verified by default. + * This is option disables SSL verification when downloading remote media. + * Please note that this is a security risk and should only be false in a local environment. + */ + 'media_downloader_ssl' => env('MEDIA_DOWNLOADER_SSL', true), + + 'remote' => [ + /* + * Any extra headers that should be included when uploading media to + * a remote disk. Even though supported headers may vary between + * different drivers, a sensible default has been provided. + * + * Supported by S3: CacheControl, Expires, StorageClass, + * ServerSideEncryption, Metadata, ACL, ContentEncoding + */ + 'extra_headers' => [ + 'CacheControl' => 'max-age=604800', + ], + ], + + 'responsive_images' => [ + /* + * This class is responsible for calculating the target widths of the responsive + * images. By default we optimize for filesize and create variations that each are 30% + * smaller than the previous one. More info in the documentation. + * + * https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images + */ + 'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class, + + /* + * By default rendering media to a responsive image will add some javascript and a tiny placeholder. + * This ensures that the browser can already determine the correct layout. + * When disabled, no tiny placeholder is generated. + */ + 'use_tiny_placeholders' => true, + + /* + * This class will generate the tiny placeholder used for progressive image loading. By default + * the media library will use a tiny blurred jpg image. + */ + 'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class, + ], + + /* + * When enabling this option, a route will be registered that will enable + * the Media Library Pro Vue and React components to move uploaded files + * in a S3 bucket to their right place. + */ + 'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false), + + /* + * When converting Media instances to response the media library will add + * a `loading` attribute to the `img` tag. Here you can set the default + * value of that attribute. + * + * Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction. + * + * More info: https://css-tricks.com/native-lazy-loading/ + */ + 'default_loading_attribute_value' => null, + + /* + * You can specify a prefix for that is used for storing all media. + * If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory. + */ + 'prefix' => env('MEDIA_PREFIX', ''), + + /* + * When forcing lazy loading, media will be loaded even if you don't eager load media and you have + * disabled lazy loading globally in the service provider. + */ + 'force_lazy_loading' => env('FORCE_MEDIA_LIBRARY_LAZY_LOADING', true), +]; diff --git a/database/migrations/2025_05_10_003801_create_media_table.php b/database/migrations/2025_05_10_003801_create_media_table.php new file mode 100644 index 0000000..47a4be9 --- /dev/null +++ b/database/migrations/2025_05_10_003801_create_media_table.php @@ -0,0 +1,32 @@ +id(); + + $table->morphs('model'); + $table->uuid()->nullable()->unique(); + $table->string('collection_name'); + $table->string('name'); + $table->string('file_name'); + $table->string('mime_type')->nullable(); + $table->string('disk'); + $table->string('conversions_disk')->nullable(); + $table->unsignedBigInteger('size'); + $table->json('manipulations'); + $table->json('custom_properties'); + $table->json('generated_conversions'); + $table->json('responsive_images'); + $table->unsignedInteger('order_column')->nullable()->index(); + + $table->nullableTimestamps(); + }); + } +}; diff --git a/database/migrations/2025_05_10_012150_remove_filename_and_thumbnail_from_uploads_table.php b/database/migrations/2025_05_10_012150_remove_filename_and_thumbnail_from_uploads_table.php new file mode 100644 index 0000000..8b14ad9 --- /dev/null +++ b/database/migrations/2025_05_10_012150_remove_filename_and_thumbnail_from_uploads_table.php @@ -0,0 +1,38 @@ +dropColumn('filename'); + } + if (Schema::hasColumn('uploads', 'thumbnail')) { + $table->dropColumn('thumbnail'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('uploads', function (Blueprint $table) { + // Re-add the columns if rolling back. + // Adjust the type if they were different (e.g., text) + // Making them nullable as they might not have data if rolled back after new entries. + $table->string('filename')->nullable(); + $table->string('thumbnail')->nullable(); + }); + } +};