maccesar / laravel-dropzone-enhanced
A enhanced Dropzone.js component for Laravel with image processing capabilities
Requires
- php: ^7.4|^8.0|^8.2
- laravel/framework: ^8.0|^9.0|^10.0|^12.0
Requires (Dev)
- orchestra/testbench: ^6.0|^7.0|^8.0
- phpunit/phpunit: ^9.0
Suggests
- maccesar/laravel-glide-enhanced: For advanced image processing capabilities
README
A powerful and customizable Laravel package that enhances Dropzone.js to provide elegant and efficient image uploading, processing, and management for your Laravel 8+ applications.
Features
- Simple Integration: Easily add Dropzone to any model with a simple trait
- Image Processing: Resize, crop, and optimize images with Laravel Glide Enhanced (optional)
- Drag & Drop Reordering: Intuitive drag-and-drop interface for sorting images
- Main Image Selection: Designate a main image for your models with toggle capability
- Lightbox Preview: View full-size images with an integrated lightbox
- Responsive Design: Works beautifully on any device or screen size
- Multi-language Support: Built-in translations for English and Spanish
- Local Assets: No external CDN dependencies for improved reliability
- Customizable: Extensively configurable through a simple config file
- Secure: Built with security best practices
Requirements
- PHP 7.4 or higher
- Laravel 8.0 or higher
Installation
Step 1: Install the Package
composer require maccesar/laravel-dropzone-enhanced
Step 2: Publish Assets and Configuration
You can use the included command to set up everything automatically:
php artisan dropzone-enhanced:install
Or manually publish the assets:
# Publish config file php artisan vendor:publish --tag=dropzone-enhanced-config # Publish migrations php artisan vendor:publish --tag=dropzone-enhanced-migrations # Publish assets php artisan vendor:publish --tag=dropzone-enhanced-assets
Step 3: Run the Migrations
php artisan migrate
Usage
Add the HasPhotos
trait to your model:
use MacCesar\LaravelDropzoneEnhanced\Traits\HasPhotos; class Product extends Model { use HasPhotos; // ... }
Then use the component in your views:
<x-dropzone-enhanced::area :object="$product" directory="products" />
The component accepts the following parameters:
Parameter | Description | Default |
---|---|---|
object |
The model to attach photos to | Required |
directory |
Directory where photos will be stored | Required |
dimensions |
Max dimensions for resize (format: "widthxheight") | From config default_dimensions |
preResize |
Whether to resize the image in browser before upload | From config pre_resize |
maxFiles |
Maximum number of files allowed | From config max_files |
maxFilesize |
Maximum file size in MB | From config max_filesize |
Configuration
The package is highly configurable through the config/dropzone.php
file. All component parameters use these centralized config values by default:
return [ 'routes' => [ 'prefix' => '', // Change route prefix if needed 'middleware' => ['web'], ], 'storage' => [ 'disk' => 'public', 'directory' => 'images', ], 'images' => [ 'default_dimensions' => '1920x1080', 'pre_resize' => true, 'quality' => 90, 'max_files' => 10, 'max_filesize' => 10000, // in KB 'thumbnails' => [ 'enabled' => true, 'dimensions' => '288x288', ], ], 'security' => [ // If true, any authenticated user can delete photos (use with caution) 'allow_all_authenticated_users' => false, // Custom access key for API or JavaScript requests without authentication 'access_key' => null, ], ];
Advanced Usage
Using with Specific Storage Disks
By default, the package uses your public
disk. To change this:
// In config/dropzone.php 'storage' => [ 'disk' => 's3', 'directory' => 'uploads/images', ],
Image Processing with Laravel Glide Enhanced
For advanced image processing, install the optional Laravel Glide Enhanced package:
composer require maccesar/laravel-glide-enhanced
The package will automatically detect and use Laravel Glide Enhanced for image processing, which provides:
- Image optimization
- Watermarking capabilities
- Format conversion (WebP, JPG, PNG)
- Dynamic image resizing and cropping
- Automatic caching for improved performance
Example of advanced usage with Laravel Glide Enhanced:
// In your controller or view use MacCesar\LaravelGlideEnhanced\Facades\ImageProcessor; // Create optimized WebP thumbnails $optimizedUrl = ImageProcessor::webpUrl($photo->getPath(), [ 'q' => 90, 'w' => 300, 'h' => 300, 'fit' => 'crop' ]); // Use predefined presets from the Laravel Glide Enhanced configuration $thumbnailUrl = ImageProcessor::preset($photo->getPath(), 'thumbnail'); // Generate responsive srcset attributes // For 1x, 2x, and 3x pixel densities (default) $srcset = ImageProcessor::srcset($photo->getPath(), [ 'w' => 300, 'fm' => 'webp' ]); // Output: "/img/storage/path/to/image.jpg?w=300&fm=webp 1x, /img/storage/path/to/image.jpg?w=600&fm=webp 2x, /img/storage/path/to/image.jpg?w=900&fm=webp 3x" // Control the maximum density factor (e.g., up to 2x) $srcset = ImageProcessor::srcset($photo->getPath(), [ 'w' => 300, 'h' => 200, 'fm' => 'webp' ], 2); // Output: "/img/storage/path/to/image.jpg?w=300&h=200&fm=webp 1x, /img/storage/path/to/image.jpg?w=600&h=400&fm=webp 2x"
For complete documentation, visit the Laravel Glide Enhanced repository.
Working with Photos
The HasPhotos
trait adds several useful methods to your model:
// Get all photos $product->photos; // Get the main photo $photo = $product->mainPhoto(); // Get the URL of the main photo $url = $product->getMainPhotoUrl(); // Get the thumbnail URL of the main photo $url = $product->getMainPhotoThumbnailUrl('288x288'); // Set a photo as the main photo $product->setMainPhoto($photoId); // Check if the model has photos if ($product->hasPhotos()) { // Do something } // Delete all photos $product->deleteAllPhotos();
Displaying Photos
To display the uploaded photos, use the photos component:
<x-dropzone-enhanced::photos :object="$product" :lightbox="true" />
This component will show all uploaded images with options to:
- View in lightbox
- Set as main image (with toggle capability)
- Delete
- Reorder by drag-and-drop
Custom Component Options
The dropzone component accepts the following props:
object
: The model instance (required)directory
: Storage subdirectory for the images (required)dimensions
: Target dimensions for resizing images (default: from config)preResize
: Whether to resize images on upload (default: from config)maxFiles
: Maximum number of files allowed (default: 10)maxFilesize
: Maximum file size in MB (default: 5)
Advanced Configuration Options
Custom Routes and Middleware
By default, all package routes use the web
middleware and have no prefix. You can customize this in your configuration:
// config/dropzone.php 'routes' => [ 'prefix' => 'admin', // Add a route prefix if needed 'middleware' => ['web', 'auth:admin'], // Add authentication or other middleware ],
This is particularly useful when:
- You need to protect uploads with specific middleware
- You want to integrate with your existing admin panel
- You use different route structures for different environments
Photo URLs
The package generates URLs for your photos using relative paths:
// Getting the URL for the original image $photo->getUrl(); // Getting the URL for a thumbnail $photo->getThumbnailUrl(); // Uses default dimensions from config $photo->getThumbnailUrl('400x300'); // Custom dimensions
These URLs work consistently across different environments and domains.
API Reference
Photo Model
Method | Description |
---|---|
getUrl() |
Returns the URL for the original image using relative path |
getThumbnailUrl($dimensions = null) |
Returns the URL for a thumbnail with optional custom dimensions |
deletePhoto() |
Deletes the photo and associated files from storage |
getPath() |
Returns the relative path to the image file within storage |
Photo Management
The package includes several routes for photo management:
Route | Method | Description |
---|---|---|
/dropzone/upload |
POST | Upload a new photo |
/dropzone/photos/{id} |
DELETE | Delete a photo |
/dropzone/photos/{id}/main |
POST | Toggle a photo as the main image |
/dropzone/photos/{id}/is-main |
GET | Check if a photo is marked as main |
/dropzone/photos/reorder |
POST | Reorder photos |
You can add a custom prefix (like /admin
) in the config file if needed.
Security Features
Photo Deletion Protection
Starting from version 1.2.1, the package includes security features to protect photo deletion:
- Photos can only be deleted by users who have proper authorization
- The
userCanDeletePhoto()
method in the controller verifies ownership permissions - Multiple ownership verification mechanisms are supported:
- User authentication check
- Direct model ownership (via
user_id
field) - Custom ownership methods (
isOwnedBy()
) - Session tokens for public forms
When unauthorized deletion is attempted, a 403 Forbidden response is returned with an error message that is displayed to the user.
Allowing Photo Deletion
Here are specific ways to enable photo deletion in different scenarios:
For Authenticated Users
If your application uses Laravel's authentication, ensure your models have a relationship to the user:
// In your model (e.g., Product.php) public function user() { return $this->belongsTo(User::class); }
Or simply add a user_id
field to your model's table.
For Public Forms
If you need to allow deletion in public forms (without authentication), use session tokens:
// When creating an entry with photos $sessionKey = "photo_access_" . get_class($model) . "_{$model->id}"; $request->session()->put($sessionKey, true);
Using a Custom Controller
For more advanced authorization, extend the controller:
use MacCesar\LaravelDropzoneEnhanced\Http\Controllers\DropzoneController; class CustomDropzoneController extends DropzoneController { protected function userCanDeletePhoto(Request $request, Photo $photo, $model) { // Example: Check if model belongs to current user if (auth()->check() && $model->user_id == auth()->id()) { return true; } // Example: Check if model has a specific status if ($model->status == 'draft') { return true; } // Example: Check against a permission system if (auth()->check() && auth()->user()->can('delete-photos')) { return true; } return false; } }
Then, update your routes to use your custom controller:
// In a service provider or routes file Route::delete('dropzone/photos/{id}', [CustomDropzoneController::class, 'destroy']) ->name('dropzone.destroy');
Customizing Authorization Logic
You can extend or override the authorization logic by:
- Creating a custom controller that extends
DropzoneController
- Overriding the
userCanDeletePhoto()
method - Implementing your own authorization rules
// In your custom controller protected function userCanDeletePhoto(Request $request, Photo $photo, $model) { // Your custom authorization logic return true; // Always allow (not recommended for production) }
Photo Deletion Authorization
The package includes a comprehensive authorization system for photo deletion. Here are all the supported authorization methods:
For Authenticated Users
When a user is authenticated, the following checks are performed (in order):
-
Direct Model Ownership - If the model has a
user_id
field that matches the authenticated user's IDif (isset($model->user_id) && $model->user_id === auth()->id()) { return true; }
-
User Relationship - If the model has a
user()
relationship that returns the authenticated userif (method_exists($model, 'user') && $model->user && $model->user->id === auth()->id()) { return true; }
-
Custom Ownership Method - If the model implements an
isOwnedBy()
method that returns trueif (method_exists($model, 'isOwnedBy') && $model->isOwnedBy(auth()->user())) { return true; }
-
Admin Check - If the authenticated user has an
isAdmin()
method that returns trueif (method_exists(auth()->user(), 'isAdmin') && auth()->user()->isAdmin()) { return true; }
-
Laravel Gates - If the user passes a
delete-photos
gate checkif (method_exists(auth(), 'can') && auth()->can('delete-photos')) { return true; }
-
Spatie Permissions - If using the Spatie Permissions package and the user has the right permission
if (method_exists(auth()->user(), 'hasPermissionTo') && auth()->user()->hasPermissionTo('delete photos')) { return true; }
-
Allow All Authenticated - If enabled in config, any authenticated user is allowed
// In config/dropzone.php 'security' => [ 'allow_all_authenticated_users' => true, // Default is false ],
For Non-Authenticated Requests
When no user is authenticated, the following options are available:
-
Session Tokens - Creates a temporary token in the session for public forms
// Format 1: Using model ID $sessionKey = "photo_access_" . get_class($model) . "_{$model->id}"; session()->put($sessionKey, true); // Format 2: Using photo ID (also supported) $sessionKey = "photo_access_" . get_class($model) . "_{$photo->id}"; session()->put($sessionKey, true);
-
Access Key Header - For API or JavaScript requests
// In config/dropzone.php 'security' => [ 'access_key' => env('DROPZONE_ACCESS_KEY', 'your-secret-key'), ] // Then in fetch or axios requests: headers: { 'X-Access-Key': 'your-secret-key' }
-
Custom Controller Logic - Override the controller to implement your own authorization
class CustomDropzoneController extends DropzoneController { protected function userCanDeletePhoto(Request $request, Photo $photo, $model) { // Your custom logic here return true; } }
When authorization fails, a detailed 403 response is returned with information about why the deletion was not permitted.
JavaScript Events
The package dispatches the following events:
PhotoUploaded
: When a photo is successfully uploadedPhotoDeleted
: When a photo is deletedMainPhotoChanged
: When the main photo is changed
Troubleshooting
Images not showing after upload
Make sure your storage is properly linked:
php artisan storage:link
Upload errors with 422 response
If you're getting 422 Unprocessable Content errors, check:
- File size limits (both server and configuration)
- Image format is supported
- Permissions on your storage directory
Error messages appear as [object Object]
Update to the latest version (1.2.0+) which includes improved error handling.
Custom upload paths
If you need custom upload paths (e.g., user-specific folders), modify the directory parameter:
<x-dropzone-enhanced::area :object="$product" directory="products/{{ $product->id }}" />
Projects Without Authentication
If your project doesn't use any authentication system, you have several options:
- Use Session Tokens (simplest approach):
// In your controller when showing the form with photos public function show($id) { $product = Product::findOrFail($id); // Create a session token to allow photo management $sessionKey = "photo_access_" . get_class($product) . "_{$product->id}"; session()->put($sessionKey, true); return view('products.show', compact('product')); }
- Use the Package's Access Key (for API or JavaScript requests):
Configure a custom access key in your config/dropzone.php
:
'security' => [ 'access_key' => env('DROPZONE_ACCESS_KEY', 'your-secret-key'), ],
Then, when making requests to delete photos, include this key in the X-Access-Key header:
fetch('/dropzone/photos/' + photoId, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'X-Access-Key': '{{ config("dropzone.security.access_key") }}' } })
This approach is especially useful for:
- Single-page applications
- API-based projects
- Forms that don't have user authentication
- Create a Custom Controller:
// In your AppServiceProvider or a custom service provider public function boot() { // Override the controller $this->app->singleton('MacCesar\LaravelDropzoneEnhanced\Http\Controllers\DropzoneController', function ($app) { return new class extends \MacCesar\LaravelDropzoneEnhanced\Http\Controllers\DropzoneController { protected function userCanDeletePhoto(Request $request, Photo $photo, $model) { // Allow all deletions (use with caution!) return true; // OR: Check against some condition return $model->created_at->isToday(); // OR: Use request data for validation return $request->has('secret_token') && $request->secret_token === 'your-secret-key'; } }; }); }
Support
If you discover any issues with this package, including bugs, feature requests, or questions, please create an issue on the GitHub repository.
License
The MIT License (MIT). Please see License File for more information.
Credits
- Mac Cesar
- All Contributors
- Dropzone.js - The core library
Contributing
Please see CONTRIBUTING.md for details.