mirror of
https://github.com/10h30/laravel-file-upload-series.git
synced 2026-06-05 15:08:44 +09:00
Complete Part 8
This commit is contained in:
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
|
||||
+28
-15
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
Generated
+519
-1
@@ -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",
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
* The disk on which to store added files and derived images by default. Choose
|
||||
* one or more of the disks you've configured in config/filesystems.php.
|
||||
*/
|
||||
'disk_name' => 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),
|
||||
];
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('media', function (Blueprint $table) {
|
||||
$table->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();
|
||||
});
|
||||
}
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('uploads', function (Blueprint $table) {
|
||||
// Check if the columns exist before trying to drop them (optional, but good practice)
|
||||
if (Schema::hasColumn('uploads', 'filename')) {
|
||||
$table->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();
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user