The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── LICENSE.md
├── README.md
├── composer.json
├── config
    └── media-library.php
├── database
    └── migrations
    │   └── create_media_table.php.stub
├── resources
    └── views
    │   ├── image.blade.php
    │   ├── placeholderSvg.blade.php
    │   ├── responsiveImage.blade.php
    │   └── responsiveImageWithPlaceholder.blade.php
└── src
    ├── Conversions
        ├── Actions
        │   ├── PerformConversionAction.php
        │   └── PerformManipulationsAction.php
        ├── Commands
        │   └── RegenerateCommand.php
        ├── Conversion.php
        ├── ConversionCollection.php
        ├── Events
        │   ├── ConversionHasBeenCompletedEvent.php
        │   └── ConversionWillStartEvent.php
        ├── FileManipulator.php
        ├── ImageGenerators
        │   ├── Avif.php
        │   ├── Image.php
        │   ├── ImageGenerator.php
        │   ├── ImageGeneratorFactory.php
        │   ├── Pdf.php
        │   ├── Svg.php
        │   ├── Video.php
        │   └── Webp.php
        ├── Jobs
        │   └── PerformConversionsJob.php
        └── Manipulations.php
    ├── Downloaders
        ├── DefaultDownloader.php
        ├── Downloader.php
        └── HttpFacadeDownloader.php
    ├── Enums
        └── CollectionPosition.php
    ├── HasMedia.php
    ├── InteractsWithMedia.php
    ├── MediaCollections
        ├── Commands
        │   ├── CleanCommand.php
        │   └── ClearCommand.php
        ├── Contracts
        │   └── MediaLibraryRequest.php
        ├── Events
        │   ├── CollectionHasBeenClearedEvent.php
        │   └── MediaHasBeenAddedEvent.php
        ├── Exceptions
        │   ├── DiskCannotBeAccessed.php
        │   ├── DiskDoesNotExist.php
        │   ├── FileCannotBeAdded.php
        │   ├── FileDoesNotExist.php
        │   ├── FileIsTooBig.php
        │   ├── FileNameNotAllowed.php
        │   ├── FileUnacceptableForCollection.php
        │   ├── FunctionalityNotAvailable.php
        │   ├── InvalidBase64Data.php
        │   ├── InvalidConversion.php
        │   ├── InvalidFileRemover.php
        │   ├── InvalidPathGenerator.php
        │   ├── InvalidUrl.php
        │   ├── InvalidUrlGenerator.php
        │   ├── MediaCannotBeDeleted.php
        │   ├── MediaCannotBeUpdated.php
        │   ├── MimeTypeNotAllowed.php
        │   ├── RequestDoesNotHaveFile.php
        │   ├── UnknownType.php
        │   └── UnreachableUrl.php
        ├── File.php
        ├── FileAdder.php
        ├── FileAdderFactory.php
        ├── Filesystem.php
        ├── HtmlableMedia.php
        ├── MediaCollection.php
        ├── MediaRepository.php
        └── Models
        │   ├── Collections
        │       └── MediaCollection.php
        │   ├── Concerns
        │       ├── CustomMediaProperties.php
        │       ├── HasUuid.php
        │       └── IsSorted.php
        │   ├── Media.php
        │   └── Observers
        │       └── MediaObserver.php
    ├── MediaLibraryServiceProvider.php
    ├── ResponsiveImages
        ├── Events
        │   └── ResponsiveImagesGeneratedEvent.php
        ├── Exceptions
        │   └── InvalidTinyJpg.php
        ├── Jobs
        │   └── GenerateResponsiveImagesJob.php
        ├── RegisteredResponsiveImages.php
        ├── ResponsiveImage.php
        ├── ResponsiveImageGenerator.php
        ├── TinyPlaceholderGenerator
        │   ├── Blurred.php
        │   └── TinyPlaceholderGenerator.php
        └── WidthCalculator
        │   ├── FileSizeOptimizedWidthCalculator.php
        │   └── WidthCalculator.php
    └── Support
        ├── Factories
            └── TemporaryUploadFactory.php
        ├── File.php
        ├── FileNamer
            ├── DefaultFileNamer.php
            └── FileNamer.php
        ├── FileRemover
            ├── DefaultFileRemover.php
            ├── FileBaseFileRemover.php
            ├── FileRemover.php
            └── FileRemoverFactory.php
        ├── ImageFactory.php
        ├── MediaLibraryPro.php
        ├── MediaStream.php
        ├── PathGenerator
            ├── DefaultPathGenerator.php
            ├── PathGenerator.php
            └── PathGeneratorFactory.php
        ├── RemoteFile.php
        ├── TemporaryDirectory.php
        └── UrlGenerator
            ├── BaseUrlGenerator.php
            ├── DefaultUrlGenerator.php
            ├── UrlGenerator.php
            └── UrlGeneratorFactory.php


/LICENSE.md:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) Spatie bvba <info@spatie.be>
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | <div align="left">
  2 |     <a href="https://spatie.be/open-source?utm_source=github&utm_medium=banner&utm_campaign=laravel-medialibrary">
  3 |       <picture>
  4 |         <source media="(prefers-color-scheme: dark)" srcset="https://spatie.be/packages/header/laravel-medialibrary/html/dark.webp">
  5 |         <img alt="Logo for laravel-medialibrary" src="https://spatie.be/packages/header/laravel-medialibrary/html/light.webp">
  6 |       </picture>
  7 |     </a>
  8 | 
  9 | <h1>Associate files with Eloquent models</h1>
 10 | 
 11 | [![Latest Version](https://img.shields.io/github/release/spatie/laravel-medialibrary.svg?style=flat-square)](https://github.com/spatie/laravel-medialibrary/releases)
 12 | [![run-tests](https://github.com/spatie/laravel-medialibrary/actions/workflows/run-tests.yml/badge.svg)](https://github.com/spatie/laravel-medialibrary/actions/workflows/run-tests.yml)
 13 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-medialibrary.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-medialibrary)
 14 |     
 15 | </div>
 16 | 
 17 | This package can associate all sorts of files with Eloquent models. It provides a
 18 | simple API to work with. To learn all about it, head over to [the extensive documentation](https://spatie.be/docs/laravel-medialibrary).
 19 | 
 20 | Here are a few short examples of what you can do:
 21 | 
 22 | ```php
 23 | $newsItem = News::find(1);
 24 | $newsItem->addMedia($pathToFile)->toMediaCollection('images');
 25 | ```
 26 | 
 27 | It can handle your uploads directly:
 28 | 
 29 | ```php
 30 | $newsItem->addMedia($request->file('image'))->toMediaCollection('images');
 31 | ```
 32 | 
 33 | Want to store some large files on another filesystem? No problem:
 34 | 
 35 | ```php
 36 | $newsItem->addMedia($smallFile)->toMediaCollection('downloads', 'local');
 37 | $newsItem->addMedia($bigFile)->toMediaCollection('downloads', 's3');
 38 | ```
 39 | 
 40 | The storage of the files is handled by [Laravel's Filesystem](https://laravel.com/docs/filesystem),
 41 | so you can use any filesystem you like. Additionally, the package can create image manipulations
 42 | on images and pdfs that have been added in the media library.
 43 | 
 44 | Spatie is a webdesign agency in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource).
 45 | 
 46 | ## Support us
 47 | 
 48 | [<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-medialibrary.jpg?t=2" width="419px" />](https://spatie.be/github-ad-click/laravel-medialibrary)
 49 | 
 50 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
 51 | 
 52 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
 53 | 
 54 | ## Documentation
 55 | 
 56 | You'll find the documentation on [https://spatie.be/docs/laravel-medialibrary](https://spatie.be/docs/laravel-medialibrary/v11).
 57 | 
 58 | Find yourself stuck using the package? Found a bug? Do you have general questions or suggestions for improving the media library? Feel free to [create an issue on GitHub](https://github.com/spatie/laravel-medialibrary/issues), we'll try to address it as soon as possible.
 59 | 
 60 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.
 61 | 
 62 | ## Testing
 63 | 
 64 | You can run the tests with:
 65 | 
 66 | ```bash
 67 | ./vendor/bin/pest
 68 | ```
 69 | 
 70 | You can run the Github actions locally with [act](https://github.com/nektos/act). To run the tests locally, run:
 71 | 
 72 | ```
 73 | act -j run-tests
 74 | ```
 75 | 
 76 | To run tests for a specific PHP/Laravel version, run:
 77 | 
 78 | ```
 79 | act -j run-tests --matrix php:8.3 --matrix laravel:"11.*" --matrix dependency-version:prefer-stable 
 80 | ```
 81 | 
 82 | Available `matrix` options are available in the [workflow file](.github/workflows/run-tests.yml).
 83 | 
 84 | ## Upgrading
 85 | 
 86 | Please see [UPGRADING](UPGRADING.md) for details.
 87 | 
 88 | ### Changelog
 89 | 
 90 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
 91 | 
 92 | ## Contributing
 93 | 
 94 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.
 95 | 
 96 | ## Security
 97 | 
 98 | If you discover any security related issues, please email [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.
 99 | 
100 | ## Credits
101 | 
102 | - [Freek Van der Herten](https://github.com/freekmurze)
103 | - [All Contributors](../../contributors)
104 | 
105 | A big thank you to [Nicolas Beauvais](https://github.com/nicolasbeauvais) for helping out with the issues on this repo.
106 | 
107 | Special thanks to [Caneco](https://twitter.com/caneco) for the original logo.
108 | 
109 | ## Alternatives
110 | 
111 | - [laravel-mediable](https://github.com/plank/laravel-mediable)
112 | - [laravel-stapler](https://github.com/CodeSleeve/laravel-stapler)
113 | - [media-manager](https://github.com/talvbansal/media-manager)
114 | 
115 | ## License
116 | 
117 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
118 | 


--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "spatie/laravel-medialibrary",
 3 |     "description": "Associate files with Eloquent models",
 4 |     "license": "MIT",
 5 |     "keywords": [
 6 |         "spatie",
 7 |         "laravel-medialibrary",
 8 |         "media",
 9 |         "conversion",
10 |         "images",
11 |         "downloads",
12 |         "cms",
13 |         "laravel"
14 |     ],
15 |     "authors": [
16 |         {
17 |             "name": "Freek Van der Herten",
18 |             "email": "freek@spatie.be",
19 |             "homepage": "https://spatie.be",
20 |             "role": "Developer"
21 |         }
22 |     ],
23 |     "homepage": "https://github.com/spatie/laravel-medialibrary",
24 |     "require": {
25 |         "php": "^8.2",
26 |         "ext-exif": "*",
27 |         "ext-fileinfo": "*",
28 |         "ext-json": "*",
29 |         "composer/semver": "^3.4",
30 |         "illuminate/bus": "^10.2|^11.0|^12.0",
31 |         "illuminate/conditionable": "^10.2|^11.0|^12.0",
32 |         "illuminate/console": "^10.2|^11.0|^12.0",
33 |         "illuminate/database": "^10.2|^11.0|^12.0",
34 |         "illuminate/pipeline": "^10.2|^11.0|^12.0",
35 |         "illuminate/support": "^10.2|^11.0|^12.0",
36 |         "maennchen/zipstream-php": "^3.1",
37 |         "spatie/image": "^3.3.2",
38 |         "spatie/laravel-package-tools": "^1.16.1",
39 |         "spatie/temporary-directory": "^2.2",
40 |         "symfony/console": "^6.4.1|^7.0"
41 |     },
42 |     "require-dev": {
43 |         "ext-imagick": "*",
44 |         "ext-pdo_sqlite": "*",
45 |         "ext-zip": "*",
46 |         "aws/aws-sdk-php": "^3.293.10",
47 |         "guzzlehttp/guzzle": "^7.8.1",
48 |         "league/flysystem-aws-s3-v3": "^3.22",
49 |         "mockery/mockery": "^1.6.7",
50 |         "larastan/larastan": "^2.7|^3.0",
51 |         "orchestra/testbench": "^7.0|^8.17|^9.0|^10.0",
52 |         "pestphp/pest": "^2.28|^3.5",
53 |         "phpstan/extension-installer": "^1.3.1",
54 |         "spatie/laravel-ray": "^1.33",
55 |         "spatie/pdf-to-image": "^2.2|^3.0",
56 |         "spatie/pest-plugin-snapshots": "^2.1"
57 |     },
58 |     "conflict": {
59 |         "php-ffmpeg/php-ffmpeg": "<0.6.1"
60 |     },
61 |     "suggest": {
62 |         "league/flysystem-aws-s3-v3": "Required to use AWS S3 file storage",
63 |         "php-ffmpeg/php-ffmpeg": "Required for generating video thumbnails",
64 |         "spatie/pdf-to-image": "Required for generating thumbnails of PDFs and SVGs"
65 |     },
66 |     "minimum-stability": "dev",
67 |     "prefer-stable": true,
68 |     "autoload": {
69 |         "psr-4": {
70 |             "Spatie\\MediaLibrary\\": "src"
71 |         }
72 |     },
73 |     "autoload-dev": {
74 |         "psr-4": {
75 |             "Spatie\\MediaLibrary\\Tests\\": "tests"
76 |         }
77 |     },
78 |     "config": {
79 |         "allow-plugins": {
80 |             "pestphp/pest-plugin": true,
81 |             "phpstan/extension-installer": true
82 |         },
83 |         "sort-packages": true
84 |     },
85 |     "extra": {
86 |         "laravel": {
87 |             "providers": [
88 |                 "Spatie\\MediaLibrary\\MediaLibraryServiceProvider"
89 |             ]
90 |         }
91 |     },
92 |     "scripts": {
93 |         "analyse": "vendor/bin/phpstan analyse",
94 |         "baseline": "vendor/bin/phpstan analyse --generate-baseline",
95 |         "test": "vendor/bin/pest"
96 |     }
97 | }
98 | 


--------------------------------------------------------------------------------
/config/media-library.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | return [
  4 | 
  5 |     /*
  6 |      * The disk on which to store added files and derived images by default. Choose
  7 |      * one or more of the disks you've configured in config/filesystems.php.
  8 |      */
  9 |     'disk_name' => env('MEDIA_DISK', 'public'),
 10 | 
 11 |     /*
 12 |      * The maximum file size of an item in bytes.
 13 |      * Adding a larger file will result in an exception.
 14 |      */
 15 |     'max_file_size' => 1024 * 1024 * 10, // 10MB
 16 | 
 17 |     /*
 18 |      * This queue connection will be used to generate derived and responsive images.
 19 |      * Leave empty to use the default queue connection.
 20 |      */
 21 |     'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'),
 22 | 
 23 |     /*
 24 |      * This queue will be used to generate derived and responsive images.
 25 |      * Leave empty to use the default queue.
 26 |      */
 27 |     'queue_name' => env('MEDIA_QUEUE', ''),
 28 | 
 29 |     /*
 30 |      * By default all conversions will be performed on a queue.
 31 |      */
 32 |     'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true),
 33 | 
 34 |     /*
 35 |      * Should database transactions be run after database commits?
 36 |      */
 37 |     'queue_conversions_after_database_commit' => env('QUEUE_CONVERSIONS_AFTER_DB_COMMIT', true),
 38 | 
 39 |     /*
 40 |      * The fully qualified class name of the media model.
 41 |      */
 42 |     'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
 43 | 
 44 |     /*
 45 |      * The fully qualified class name of the media observer.
 46 |      */
 47 |     'media_observer' => Spatie\MediaLibrary\MediaCollections\Models\Observers\MediaObserver::class,
 48 | 
 49 |     /*
 50 |      * When enabled, media collections will be serialised using the default
 51 |      * laravel model serialization behaviour.
 52 |      *
 53 |      * Keep this option disabled if using Media Library Pro components (https://medialibrary.pro)
 54 |      */
 55 |     'use_default_collection_serialization' => false,
 56 | 
 57 |     /*
 58 |      * The fully qualified class name of the model used for temporary uploads.
 59 |      *
 60 |      * This model is only used in Media Library Pro (https://medialibrary.pro)
 61 |      */
 62 |     'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class,
 63 | 
 64 |     /*
 65 |      * When enabled, Media Library Pro will only process temporary uploads that were uploaded
 66 |      * in the same session. You can opt to disable this for stateless usage of
 67 |      * the pro components.
 68 |      */
 69 |     'enable_temporary_uploads_session_affinity' => true,
 70 | 
 71 |     /*
 72 |      * When enabled, Media Library pro will generate thumbnails for uploaded file.
 73 |      */
 74 |     'generate_thumbnails_for_temporary_uploads' => true,
 75 | 
 76 |     /*
 77 |      * This is the class that is responsible for naming generated files.
 78 |      */
 79 |     'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class,
 80 | 
 81 |     /*
 82 |      * The class that contains the strategy for determining a media file's path.
 83 |      */
 84 |     'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,
 85 | 
 86 |     /*
 87 |      * The class that contains the strategy for determining how to remove files.
 88 |      */
 89 |     'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class,
 90 | 
 91 |     /*
 92 |      * Here you can specify which path generator should be used for the given class.
 93 |      */
 94 |     'custom_path_generators' => [
 95 |         // Model::class => PathGenerator::class
 96 |         // or
 97 |         // 'model_morph_alias' => PathGenerator::class
 98 |     ],
 99 | 
100 |     /*
101 |      * When urls to files get generated, this class will be called. Use the default
102 |      * if your files are stored locally above the site root or on s3.
103 |      */
104 |     'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class,
105 | 
106 |     /*
107 |      * Moves media on updating to keep path consistent. Enable it only with a custom
108 |      * PathGenerator that uses, for example, the media UUID.
109 |      */
110 |     'moves_media_on_update' => false,
111 | 
112 |     /*
113 |      * Whether to activate versioning when urls to files get generated.
114 |      * When activated, this attaches a ?v=xx query string to the URL.
115 |      */
116 |     'version_urls' => false,
117 | 
118 |     /*
119 |      * The media library will try to optimize all converted images by removing
120 |      * metadata and applying a little bit of compression. These are
121 |      * the optimizers that will be used by default.
122 |      */
123 |     'image_optimizers' => [
124 |         Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
125 |             '-m85', // set maximum quality to 85%
126 |             '--force', // ensure that progressive generation is always done also if a little bigger
127 |             '--strip-all', // this strips out all text information such as comments and EXIF data
128 |             '--all-progressive', // this will make sure the resulting image is a progressive one
129 |         ],
130 |         Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
131 |             '--force', // required parameter for this package
132 |         ],
133 |         Spatie\ImageOptimizer\Optimizers\Optipng::class => [
134 |             '-i0', // this will result in a non-interlaced, progressive scanned image
135 |             '-o2', // this set the optimization level to two (multiple IDAT compression trials)
136 |             '-quiet', // required parameter for this package
137 |         ],
138 |         Spatie\ImageOptimizer\Optimizers\Svgo::class => [
139 |             '--disable=cleanupIDs', // disabling because it is known to cause troubles
140 |         ],
141 |         Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [
142 |             '-b', // required parameter for this package
143 |             '-O3', // this produces the slowest but best results
144 |         ],
145 |         Spatie\ImageOptimizer\Optimizers\Cwebp::class => [
146 |             '-m 6', // for the slowest compression method in order to get the best compression.
147 |             '-pass 10', // for maximizing the amount of analysis pass.
148 |             '-mt', // multithreading for some speed improvements.
149 |             '-q 90', // quality factor that brings the least noticeable changes.
150 |         ],
151 |         Spatie\ImageOptimizer\Optimizers\Avifenc::class => [
152 |             '-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63).
153 |             '-j all', // number of jobs (worker threads, "all" uses all available cores).
154 |             '--min 0', // min quantizer for color (0-63).
155 |             '--max 63', // max quantizer for color (0-63).
156 |             '--minalpha 0', // min quantizer for alpha (0-63).
157 |             '--maxalpha 63', // max quantizer for alpha (0-63).
158 |             '-a end-usage=q', // rate control mode set to Constant Quality mode.
159 |             '-a tune=ssim', // SSIM as tune the encoder for distortion metric.
160 |         ],
161 |     ],
162 | 
163 |     /*
164 |      * These generators will be used to create an image of media files.
165 |      */
166 |     'image_generators' => [
167 |         Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class,
168 |         Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class,
169 |         Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class,
170 |         Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class,
171 |         Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class,
172 |         Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class,
173 |     ],
174 | 
175 |     /*
176 |      * The path where to store temporary files while performing image conversions.
177 |      * If set to null, storage_path('media-library/temp') will be used.
178 |      */
179 |     'temporary_directory_path' => null,
180 | 
181 |     /*
182 |      * The engine that should perform the image conversions.
183 |      * Should be either `gd` or `imagick`.
184 |      */
185 |     'image_driver' => env('IMAGE_DRIVER', 'gd'),
186 | 
187 |     /*
188 |      * FFMPEG & FFProbe binaries paths, only used if you try to generate video
189 |      * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer
190 |      * dependency.
191 |      */
192 |     'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'),
193 |     'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'),
194 | 
195 |     /*
196 |      * Here you can override the class names of the jobs used by this package. Make sure
197 |      * your custom jobs extend the ones provided by the package.
198 |      */
199 |     'jobs' => [
200 |         'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class,
201 |         'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class,
202 |     ],
203 | 
204 |     /*
205 |      * When using the addMediaFromUrl method you may want to replace the default downloader.
206 |      * This is particularly useful when the url of the image is behind a firewall and
207 |      * need to add additional flags, possibly using curl.
208 |      */
209 |     'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class,
210 | 
211 |     /*
212 |      * When using the addMediaFromUrl method the SSL is verified by default.
213 |      * This is option disables SSL verification when downloading remote media.
214 |      * Please note that this is a security risk and should only be false in a local environment.
215 |      */
216 |     'media_downloader_ssl' => env('MEDIA_DOWNLOADER_SSL', true),
217 | 
218 |     'remote' => [
219 |         /*
220 |          * Any extra headers that should be included when uploading media to
221 |          * a remote disk. Even though supported headers may vary between
222 |          * different drivers, a sensible default has been provided.
223 |          *
224 |          * Supported by S3: CacheControl, Expires, StorageClass,
225 |          * ServerSideEncryption, Metadata, ACL, ContentEncoding
226 |          */
227 |         'extra_headers' => [
228 |             'CacheControl' => 'max-age=604800',
229 |         ],
230 |     ],
231 | 
232 |     'responsive_images' => [
233 |         /*
234 |          * This class is responsible for calculating the target widths of the responsive
235 |          * images. By default we optimize for filesize and create variations that each are 30%
236 |          * smaller than the previous one. More info in the documentation.
237 |          *
238 |          * https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images
239 |          */
240 |         'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class,
241 | 
242 |         /*
243 |          * By default rendering media to a responsive image will add some javascript and a tiny placeholder.
244 |          * This ensures that the browser can already determine the correct layout.
245 |          * When disabled, no tiny placeholder is generated.
246 |          */
247 |         'use_tiny_placeholders' => true,
248 | 
249 |         /*
250 |          * This class will generate the tiny placeholder used for progressive image loading. By default
251 |          * the media library will use a tiny blurred jpg image.
252 |          */
253 |         'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class,
254 |     ],
255 | 
256 |     /*
257 |      * When enabling this option, a route will be registered that will enable
258 |      * the Media Library Pro Vue and React components to move uploaded files
259 |      * in a S3 bucket to their right place.
260 |      */
261 |     'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false),
262 | 
263 |     /*
264 |      * When converting Media instances to response the media library will add
265 |      * a `loading` attribute to the `img` tag. Here you can set the default
266 |      * value of that attribute.
267 |      *
268 |      * Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction.
269 |      *
270 |      * More info: https://css-tricks.com/native-lazy-loading/
271 |      */
272 |     'default_loading_attribute_value' => null,
273 | 
274 |     /*
275 |      * You can specify a prefix for that is used for storing all media.
276 |      * If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory.
277 |      */
278 |     'prefix' => env('MEDIA_PREFIX', ''),
279 | 
280 |     /*
281 |      * When forcing lazy loading, media will be loaded even if you don't eager load media and you have
282 |      * disabled lazy loading globally in the service provider.
283 |      */
284 |     'force_lazy_loading' => env('FORCE_MEDIA_LIBRARY_LAZY_LOADING', true),
285 | ];
286 | 


--------------------------------------------------------------------------------
/database/migrations/create_media_table.php.stub:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | use Illuminate\Database\Migrations\Migration;
 4 | use Illuminate\Database\Schema\Blueprint;
 5 | use Illuminate\Support\Facades\Schema;
 6 | 
 7 | return new class extends Migration
 8 | {
 9 |     public function up(): void
10 |     {
11 |         Schema::create('media', function (Blueprint $table) {
12 |             $table->id();
13 | 
14 |             $table->morphs('model');
15 |             $table->uuid()->nullable()->unique();
16 |             $table->string('collection_name');
17 |             $table->string('name');
18 |             $table->string('file_name');
19 |             $table->string('mime_type')->nullable();
20 |             $table->string('disk');
21 |             $table->string('conversions_disk')->nullable();
22 |             $table->unsignedBigInteger('size');
23 |             $table->json('manipulations');
24 |             $table->json('custom_properties');
25 |             $table->json('generated_conversions');
26 |             $table->json('responsive_images');
27 |             $table->unsignedInteger('order_column')->nullable()->index();
28 | 
29 |             $table->nullableTimestamps();
30 |         });
31 |     }
32 | };
33 | 


--------------------------------------------------------------------------------
/resources/views/image.blade.php:
--------------------------------------------------------------------------------
1 | <img{!! $attributeString !!}@if($loadingAttributeValue) loading="{{ $loadingAttributeValue }}"@endif src="{{ $media->getUrl($conversion) }}" alt="{{ $media->name }}">


--------------------------------------------------------------------------------
/resources/views/placeholderSvg.blade.php:
--------------------------------------------------------------------------------
1 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2 | <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0"
3 |  y="0" viewBox="0 0 {{ $originalImageWidth }} {{ $originalImageHeight }}">
4 | 	<image width="{{ $originalImageWidth }}" height="{{ $originalImageHeight }}" xlink:href="{{ $tinyImageBase64 }}">
5 | 	</image>
6 | </svg>


--------------------------------------------------------------------------------
/resources/views/responsiveImage.blade.php:
--------------------------------------------------------------------------------
1 | <img{!! $attributeString !!}@if($loadingAttributeValue) loading="{{ $loadingAttributeValue }}"@endif srcset="{{ $media->getSrcset($conversion) }}" src="{{ $media->getUrl($conversion) }}" width="{{ $width }}" height="{{ $height }}" alt="{{ $media->name }}">


--------------------------------------------------------------------------------
/resources/views/responsiveImageWithPlaceholder.blade.php:
--------------------------------------------------------------------------------
1 | <img{!! $attributeString !!} @if($loadingAttributeValue) loading="{{ $loadingAttributeValue }}"@endif srcset="{{ $media->getSrcset($conversion) }}" onload="window.requestAnimationFrame(function(){if(!(size=getBoundingClientRect().width))return;onload=null;sizes=Math.ceil(size/window.innerWidth*100)+'vw';});" sizes="1px" src="{{ $media->getUrl($conversion) }}" width="{{ $width }}" height="{{ $height }}" alt="{{ $media->name }}">
2 | 


--------------------------------------------------------------------------------
/src/Conversions/Actions/PerformConversionAction.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\Actions;
 4 | 
 5 | use Spatie\MediaLibrary\Conversions\Conversion;
 6 | use Spatie\MediaLibrary\Conversions\Events\ConversionHasBeenCompletedEvent;
 7 | use Spatie\MediaLibrary\Conversions\Events\ConversionWillStartEvent;
 8 | use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory;
 9 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
10 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
11 | use Spatie\MediaLibrary\ResponsiveImages\ResponsiveImageGenerator;
12 | 
13 | class PerformConversionAction
14 | {
15 |     public function execute(
16 |         Conversion $conversion,
17 |         Media $media,
18 |         string $copiedOriginalFile
19 |     ): void {
20 |         $imageGenerator = ImageGeneratorFactory::forMedia($media);
21 | 
22 |         $copiedOriginalFile = $imageGenerator->convert($copiedOriginalFile, $conversion);
23 | 
24 |         if (! $copiedOriginalFile) {
25 |             return;
26 |         }
27 | 
28 |         event(new ConversionWillStartEvent($media, $conversion, $copiedOriginalFile));
29 | 
30 |         $manipulationResult = (new PerformManipulationsAction)->execute($media, $conversion, $copiedOriginalFile);
31 | 
32 |         $newFileName = $conversion->getConversionFile($media);
33 | 
34 |         $renamedFile = $this->renameInLocalDirectory($manipulationResult, $newFileName);
35 | 
36 |         if ($conversion->shouldGenerateResponsiveImages()) {
37 |             /** @var ResponsiveImageGenerator $responsiveImageGenerator */
38 |             $responsiveImageGenerator = app(ResponsiveImageGenerator::class);
39 | 
40 |             $responsiveImageGenerator->generateResponsiveImagesForConversion(
41 |                 $media,
42 |                 $conversion,
43 |                 $renamedFile
44 |             );
45 |         }
46 | 
47 |         app(Filesystem::class)->copyToMediaLibrary($renamedFile, $media, 'conversions');
48 | 
49 |         $media->markAsConversionGenerated($conversion->getName());
50 | 
51 |         event(new ConversionHasBeenCompletedEvent($media, $conversion));
52 |     }
53 | 
54 |     protected function renameInLocalDirectory(
55 |         string $fileNameWithDirectory,
56 |         string $newFileNameWithoutDirectory
57 |     ): string {
58 |         $targetFile = pathinfo($fileNameWithDirectory, PATHINFO_DIRNAME).'/'.$newFileNameWithoutDirectory;
59 | 
60 |         rename($fileNameWithDirectory, $targetFile);
61 | 
62 |         return $targetFile;
63 |     }
64 | }
65 | 


--------------------------------------------------------------------------------
/src/Conversions/Actions/PerformManipulationsAction.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\Actions;
 4 | 
 5 | use Illuminate\Support\Facades\File;
 6 | use Illuminate\Support\Str;
 7 | use Spatie\Image\Exceptions\UnsupportedImageFormat;
 8 | use Spatie\Image\Image;
 9 | use Spatie\MediaLibrary\Conversions\Conversion;
10 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
11 | 
12 | class PerformManipulationsAction
13 | {
14 |     public function execute(
15 |         Media $media,
16 |         Conversion $conversion,
17 |         string $imageFile,
18 |     ): string {
19 | 
20 |         if ($conversion->getManipulations()->isEmpty()) {
21 |             return $imageFile;
22 |         }
23 | 
24 |         $conversionTempFile = $this->getConversionTempFileName($media, $conversion, $imageFile);
25 | 
26 |         File::copy($imageFile, $conversionTempFile);
27 | 
28 |         $supportedFormats = ['jpg', 'jpeg', 'pjpg', 'png', 'gif', 'webp'];
29 |         if ($conversion->shouldKeepOriginalImageFormat() && in_array($media->extension, $supportedFormats)) {
30 |             $conversion->format($media->extension);
31 |         }
32 | 
33 |         $image = Image::useImageDriver(config('media-library.image_driver'))
34 |             ->loadFile($conversionTempFile)
35 |             ->format('jpg');
36 | 
37 |         try {
38 |             $conversion->getManipulations()->apply($image);
39 | 
40 |             $image->save();
41 |         } catch (UnsupportedImageFormat) {
42 | 
43 |         }
44 | 
45 |         return $conversionTempFile;
46 |     }
47 | 
48 |     protected function getConversionTempFileName(
49 |         Media $media,
50 |         Conversion $conversion,
51 |         string $imageFile,
52 |     ): string {
53 |         $directory = pathinfo($imageFile, PATHINFO_DIRNAME);
54 | 
55 |         $extension = $media->extension;
56 | 
57 |         if ($extension === '') {
58 |             $extension = 'jpg';
59 |         }
60 | 
61 |         $fileName = Str::random(32)."{$conversion->getName()}.{$extension}";
62 | 
63 |         return "{$directory}/{$fileName}";
64 |     }
65 | }
66 | 


--------------------------------------------------------------------------------
/src/Conversions/Commands/RegenerateCommand.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\Conversions\Commands;
  4 | 
  5 | use Exception;
  6 | use Illuminate\Console\Command;
  7 | use Illuminate\Console\ConfirmableTrait;
  8 | use Illuminate\Support\Arr;
  9 | use Illuminate\Support\LazyCollection;
 10 | use Illuminate\Support\Str;
 11 | use Spatie\MediaLibrary\Conversions\FileManipulator;
 12 | use Spatie\MediaLibrary\MediaCollections\MediaRepository;
 13 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 14 | 
 15 | class RegenerateCommand extends Command
 16 | {
 17 |     use ConfirmableTrait;
 18 | 
 19 |     protected $signature = 'media-library:regenerate {modelType?} {--ids=*}
 20 |     {--only=* : Regenerate specific conversions}
 21 |     {--starting-from-id= : Regenerate media with an id equal to or higher than the provided value}
 22 |     {--X|exclude-starting-id : Exclude the provided id when regenerating from a specific id}
 23 |     {--only-missing : Regenerate only missing conversions}
 24 |     {--with-responsive-images : Regenerate responsive images}
 25 |     {--force : Force the operation to run when in production}
 26 |     {--queue-all : Queue all conversions, even non-queued ones}';
 27 | 
 28 |     protected $description = 'Regenerate the derived images of media';
 29 | 
 30 |     protected MediaRepository $mediaRepository;
 31 | 
 32 |     protected FileManipulator $fileManipulator;
 33 | 
 34 |     protected array $errorMessages = [];
 35 | 
 36 |     public function handle(MediaRepository $mediaRepository, FileManipulator $fileManipulator): void
 37 |     {
 38 |         $this->mediaRepository = $mediaRepository;
 39 | 
 40 |         $this->fileManipulator = $fileManipulator;
 41 | 
 42 |         if (! $this->confirmToProceed()) {
 43 |             return;
 44 |         }
 45 | 
 46 |         $mediaFiles = $this->getMediaToBeRegenerated();
 47 | 
 48 |         $progressBar = $this->output->createProgressBar($mediaFiles->count());
 49 | 
 50 |         if (config('media-library.queue_connection_name') === 'sync') {
 51 |             set_time_limit(0);
 52 |         }
 53 | 
 54 |         $mediaFiles->each(function (Media $media) use ($progressBar) {
 55 |             try {
 56 |                 $this->fileManipulator->createDerivedFiles(
 57 |                     $media,
 58 |                     Arr::wrap($this->option('only')),
 59 |                     $this->option('only-missing'),
 60 |                     $this->option('with-responsive-images'),
 61 |                     $this->option('queue-all'),
 62 |                 );
 63 |             } catch (Exception $exception) {
 64 |                 $this->errorMessages[$media->getKey()] = $exception->getMessage();
 65 |             }
 66 | 
 67 |             $progressBar->advance();
 68 |         });
 69 | 
 70 |         $progressBar->finish();
 71 | 
 72 |         if (count($this->errorMessages)) {
 73 |             $this->warn('All done, but with some error messages:');
 74 | 
 75 |             foreach ($this->errorMessages as $mediaId => $message) {
 76 |                 $this->warn("Media id {$mediaId}: `{$message}`");
 77 |             }
 78 |         }
 79 | 
 80 |         $this->newLine(2);
 81 | 
 82 |         $this->info('All done!');
 83 |     }
 84 | 
 85 |     public function getMediaToBeRegenerated(): LazyCollection
 86 |     {
 87 |         // Get this arg first as it can also be passed to the greater-than-id branch
 88 |         $modelType = $this->argument('modelType');
 89 | 
 90 |         $startingFromId = (int) $this->option('starting-from-id');
 91 |         if ($startingFromId !== 0) {
 92 |             $excludeStartingId = (bool) $this->option('exclude-starting-id') ?: false;
 93 | 
 94 |             return $this->mediaRepository->getByIdGreaterThan($startingFromId, $excludeStartingId, is_string($modelType) ? $modelType : '');
 95 |         }
 96 | 
 97 |         if (is_string($modelType)) {
 98 |             return $this->mediaRepository->getByModelType($modelType);
 99 |         }
100 | 
101 |         $mediaIds = $this->getMediaIds();
102 |         if (count($mediaIds) > 0) {
103 |             return $this->mediaRepository->getByIds($mediaIds);
104 |         }
105 | 
106 |         return $this->mediaRepository->all();
107 |     }
108 | 
109 |     protected function getMediaIds(): array
110 |     {
111 |         $mediaIds = $this->option('ids');
112 | 
113 |         if (! is_array($mediaIds)) {
114 |             $mediaIds = explode(',', (string) $mediaIds);
115 |         }
116 | 
117 |         if (count($mediaIds) === 1 && Str::contains((string) $mediaIds[0], ',')) {
118 |             $mediaIds = explode(',', (string) $mediaIds[0]);
119 |         }
120 | 
121 |         return $mediaIds;
122 |     }
123 | }
124 | 


--------------------------------------------------------------------------------
/src/Conversions/Conversion.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\Conversions;
  4 | 
  5 | use Illuminate\Support\Arr;
  6 | use Illuminate\Support\Traits\Conditionable;
  7 | use Spatie\ImageOptimizer\OptimizerChainFactory;
  8 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
  9 | use Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\WidthCalculator;
 10 | use Spatie\MediaLibrary\Support\FileNamer\FileNamer;
 11 | 
 12 | /** @mixin \Spatie\Image\Drivers\ImageDriver */
 13 | class Conversion
 14 | {
 15 |     use Conditionable;
 16 | 
 17 |     protected FileNamer $fileNamer;
 18 | 
 19 |     protected float $extractVideoFrameAtSecond = 0;
 20 | 
 21 |     protected Manipulations $manipulations;
 22 | 
 23 |     protected array $performOnCollections = [];
 24 | 
 25 |     protected bool $performOnQueue;
 26 | 
 27 |     protected bool $keepOriginalImageFormat = false;
 28 | 
 29 |     protected bool $generateResponsiveImages = false;
 30 | 
 31 |     protected ?WidthCalculator $widthCalculator = null;
 32 | 
 33 |     protected ?string $loadingAttributeValue;
 34 | 
 35 |     protected int $pdfPageNumber = 1;
 36 | 
 37 |     public function __construct(
 38 |         protected string $name,
 39 |     ) {
 40 |         $optimizerChain = OptimizerChainFactory::create(config('media-library.image_optimizers'));
 41 | 
 42 |         $this->manipulations = new Manipulations;
 43 |         $this->manipulations->optimize($optimizerChain)->format('jpg');
 44 | 
 45 |         $this->fileNamer = app(config('media-library.file_namer'));
 46 | 
 47 |         $this->loadingAttributeValue = config('media-library.default_loading_attribute_value');
 48 | 
 49 |         $this->performOnQueue = config('media-library.queue_conversions_by_default', true);
 50 |     }
 51 | 
 52 |     public static function create(string $name): self
 53 |     {
 54 |         return new static($name);
 55 |     }
 56 | 
 57 |     public function getName(): string
 58 |     {
 59 |         return $this->name;
 60 |     }
 61 | 
 62 |     public function getPerformOnCollections(): array
 63 |     {
 64 |         if (! count($this->performOnCollections)) {
 65 |             return ['default'];
 66 |         }
 67 | 
 68 |         return $this->performOnCollections;
 69 |     }
 70 | 
 71 |     public function extractVideoFrameAtSecond(float $timeCode): self
 72 |     {
 73 |         $this->extractVideoFrameAtSecond = $timeCode;
 74 | 
 75 |         return $this;
 76 |     }
 77 | 
 78 |     public function getExtractVideoFrameAtSecond(): float
 79 |     {
 80 |         return $this->extractVideoFrameAtSecond;
 81 |     }
 82 | 
 83 |     public function keepOriginalImageFormat(): self
 84 |     {
 85 |         $this->keepOriginalImageFormat = true;
 86 | 
 87 |         return $this;
 88 |     }
 89 | 
 90 |     public function shouldKeepOriginalImageFormat(): bool
 91 |     {
 92 |         return $this->keepOriginalImageFormat;
 93 |     }
 94 | 
 95 |     public function getManipulations(): Manipulations
 96 |     {
 97 |         return $this->manipulations;
 98 |     }
 99 | 
100 |     public function removeManipulation(string $manipulationName): self
101 |     {
102 |         $this->manipulations->removeManipulation($manipulationName);
103 | 
104 |         return $this;
105 |     }
106 | 
107 |     public function withoutManipulations(): self
108 |     {
109 |         $this->manipulations = new Manipulations;
110 | 
111 |         return $this;
112 |     }
113 | 
114 |     public function __call($name, $arguments): self
115 |     {
116 |         $this->manipulations->$name(...$arguments);
117 | 
118 |         return $this;
119 |     }
120 | 
121 |     public function setManipulations($manipulations): self
122 |     {
123 |         if ($manipulations instanceof Manipulations) {
124 |             $this->manipulations = $this->manipulations->mergeManipulations($manipulations);
125 |         }
126 | 
127 |         if (is_callable($manipulations)) {
128 |             $manipulations($this->manipulations);
129 |         }
130 | 
131 |         return $this;
132 |     }
133 | 
134 |     public function addAsFirstManipulations(Manipulations $manipulations): self
135 |     {
136 |         $newManipulations = $manipulations->toArray();
137 | 
138 |         $currentManipulations = $this->manipulations->toArray();
139 | 
140 |         $allManipulations = array_merge($newManipulations, Arr::except($currentManipulations, array_keys($newManipulations)));
141 | 
142 |         $this->manipulations = new Manipulations($allManipulations);
143 | 
144 |         return $this;
145 |     }
146 | 
147 |     public function performOnCollections(...$collectionNames): self
148 |     {
149 |         $this->performOnCollections = $collectionNames;
150 | 
151 |         return $this;
152 |     }
153 | 
154 |     public function shouldBePerformedOn(string $collectionName): bool
155 |     {
156 |         // if no collections were specified, perform conversion on all collections
157 |         if (! count($this->performOnCollections)) {
158 |             return true;
159 |         }
160 | 
161 |         if (in_array('*', $this->performOnCollections)) {
162 |             return true;
163 |         }
164 | 
165 |         return in_array($collectionName, $this->performOnCollections);
166 |     }
167 | 
168 |     public function queued(): self
169 |     {
170 |         $this->performOnQueue = true;
171 | 
172 |         return $this;
173 |     }
174 | 
175 |     public function nonQueued(): self
176 |     {
177 |         $this->performOnQueue = false;
178 | 
179 |         return $this;
180 |     }
181 | 
182 |     public function nonOptimized(): self
183 |     {
184 |         $this->removeManipulation('optimize');
185 | 
186 |         return $this;
187 |     }
188 | 
189 |     public function withResponsiveImages(bool $withResponsiveImages = true): self
190 |     {
191 |         $this->generateResponsiveImages = $withResponsiveImages;
192 | 
193 |         return $this;
194 |     }
195 | 
196 |     public function withWidthCalculator(WidthCalculator $widthCalculator): self
197 |     {
198 |         $this->widthCalculator = $widthCalculator;
199 | 
200 |         return $this;
201 |     }
202 | 
203 |     public function getWidthCalculator(): ?WidthCalculator
204 |     {
205 |         return $this->widthCalculator;
206 |     }
207 | 
208 |     public function shouldGenerateResponsiveImages(): bool
209 |     {
210 |         return $this->generateResponsiveImages;
211 |     }
212 | 
213 |     public function shouldBeQueued(): bool
214 |     {
215 |         return $this->performOnQueue;
216 |     }
217 | 
218 |     public function getResultExtension(string $originalFileExtension = ''): string
219 |     {
220 |         if ($this->shouldKeepOriginalImageFormat()) {
221 |             if (in_array(strtolower($originalFileExtension), ['jpg', 'jpeg', 'pjpg', 'png', 'gif', 'webp', 'avif'])) {
222 |                 return $originalFileExtension;
223 |             }
224 |         }
225 | 
226 |         if ($manipulationArgument = Arr::get($this->manipulations->getManipulationArgument('format'), 0)) {
227 |             return $manipulationArgument;
228 |         }
229 | 
230 |         return $originalFileExtension;
231 |     }
232 | 
233 |     public function getConversionFile(Media $media): string
234 |     {
235 |         $fileName = $this->fileNamer->conversionFileName($media->file_name, $this);
236 | 
237 |         $fileExtension = $this->fileNamer->extensionFromBaseImage($media->file_name);
238 |         $extension = $this->getResultExtension($fileExtension) ?: $fileExtension;
239 | 
240 |         return "{$fileName}.{$extension}";
241 |     }
242 | 
243 |     public function useLoadingAttributeValue(string $value): self
244 |     {
245 |         $this->loadingAttributeValue = $value;
246 | 
247 |         return $this;
248 |     }
249 | 
250 |     public function getLoadingAttributeValue(): ?string
251 |     {
252 |         return $this->loadingAttributeValue;
253 |     }
254 | 
255 |     public function pdfPageNumber(int $pageNumber): self
256 |     {
257 |         $this->pdfPageNumber = $pageNumber;
258 | 
259 |         return $this;
260 |     }
261 | 
262 |     public function getPdfPageNumber(): int
263 |     {
264 |         return $this->pdfPageNumber;
265 |     }
266 | }
267 | 


--------------------------------------------------------------------------------
/src/Conversions/ConversionCollection.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\Conversions;
  4 | 
  5 | use Illuminate\Database\Eloquent\Relations\Relation;
  6 | use Illuminate\Support\Arr;
  7 | use Illuminate\Support\Collection;
  8 | use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidConversion;
  9 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 10 | 
 11 | /**
 12 |  * @template TKey of array-key
 13 |  * @template TValue of Conversion
 14 |  *
 15 |  * @extends Collection<TKey, TValue>
 16 |  */
 17 | class ConversionCollection extends Collection
 18 | {
 19 |     protected Media $media;
 20 | 
 21 |     public static function createForMedia(Media $media): self
 22 |     {
 23 |         return (new static)->setMedia($media);
 24 |     }
 25 | 
 26 |     /**
 27 |      * @return $this
 28 |      */
 29 |     public function setMedia(Media $media): self
 30 |     {
 31 |         $this->media = $media;
 32 | 
 33 |         $this->items = [];
 34 | 
 35 |         $this->addConversionsFromRelatedModel($media);
 36 | 
 37 |         $this->addManipulationsFromDb($media);
 38 | 
 39 |         return $this;
 40 |     }
 41 | 
 42 |     public function getByName(string $name): Conversion
 43 |     {
 44 |         $conversion = $this->first(fn (Conversion $conversion) => $conversion->getName() === $name);
 45 | 
 46 |         if (! $conversion) {
 47 |             throw InvalidConversion::unknownName($name);
 48 |         }
 49 | 
 50 |         return $conversion;
 51 |     }
 52 | 
 53 |     protected function addConversionsFromRelatedModel(Media $media): void
 54 |     {
 55 |         $modelName = Arr::get(Relation::morphMap(), $media->model_type, $media->model_type);
 56 | 
 57 |         if (! class_exists($modelName)) {
 58 |             return;
 59 |         }
 60 | 
 61 |         /** @var \Spatie\MediaLibrary\HasMedia $model */
 62 |         $model = new $modelName;
 63 | 
 64 |         /*
 65 |          * In some cases the user might want to get the actual model
 66 |          * instance so conversion parameters can depend on model
 67 |          * properties. This will causes extra queries.
 68 |          */
 69 |         if ($model->registerMediaConversionsUsingModelInstance && $media->model) {
 70 |             $model = $media->model;
 71 | 
 72 |             $model->mediaConversions = [];
 73 |         }
 74 | 
 75 |         $model->registerAllMediaConversions($media);
 76 | 
 77 |         $this->items = $model->mediaConversions;
 78 |     }
 79 | 
 80 |     protected function addManipulationsFromDb(Media $media): void
 81 |     {
 82 |         collect(Arr::except($media->manipulations, '*'))->each(function ($manipulation, $conversionName) {
 83 |             $manipulations = new Manipulations($manipulation);
 84 | 
 85 |             $this->addManipulationToConversion($manipulations, $conversionName);
 86 |         });
 87 | 
 88 |         if (array_key_exists('*', $media->manipulations)) {
 89 |             $globalManipulations = new Manipulations($media->manipulations['*']);
 90 | 
 91 |             $this->addManipulationToConversion($globalManipulations, '*');
 92 |         }
 93 |     }
 94 | 
 95 |     public function getConversions(string $collectionName = ''): self
 96 |     {
 97 |         if ($collectionName === '') {
 98 |             return $this;
 99 |         }
100 | 
101 |         return $this->filter(fn (Conversion $conversion) => $conversion->shouldBePerformedOn($collectionName));
102 |     }
103 | 
104 |     protected function addManipulationToConversion(Manipulations $manipulations, string $conversionName): void
105 |     {
106 |         /** @var Conversion|null $conversion */
107 |         $conversion = $this->first(function (Conversion $conversion) use ($conversionName) {
108 |             if (! $conversion->shouldBePerformedOn($this->media->collection_name)) {
109 |                 return false;
110 |             }
111 | 
112 |             if ($conversion->getName() !== $conversionName) {
113 |                 return false;
114 |             }
115 | 
116 |             return true;
117 |         });
118 | 
119 |         if ($conversion) {
120 |             $conversion->addAsFirstManipulations($manipulations);
121 |         }
122 | 
123 |         if ($conversionName === '*') {
124 |             $this->each(
125 |                 fn (Conversion $conversion) => $conversion->addAsFirstManipulations(clone $manipulations)
126 |             );
127 |         }
128 |     }
129 | 
130 |     public function getConversionsFiles(string $collectionName = ''): self
131 |     {
132 |         return $this
133 |             ->getConversions($collectionName)
134 |             ->map(fn (Conversion $conversion) => $conversion->getConversionFile($this->media));
135 |     }
136 | }
137 | 


--------------------------------------------------------------------------------
/src/Conversions/Events/ConversionHasBeenCompletedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\Events;
 4 | 
 5 | use Illuminate\Queue\SerializesModels;
 6 | use Spatie\MediaLibrary\Conversions\Conversion;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | 
 9 | class ConversionHasBeenCompletedEvent
10 | {
11 |     use SerializesModels;
12 | 
13 |     public function __construct(public Media $media, public Conversion $conversion) {}
14 | }
15 | 


--------------------------------------------------------------------------------
/src/Conversions/Events/ConversionWillStartEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\Events;
 4 | 
 5 | use Illuminate\Queue\SerializesModels;
 6 | use Spatie\MediaLibrary\Conversions\Conversion;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | 
 9 | class ConversionWillStartEvent
10 | {
11 |     use SerializesModels;
12 | 
13 |     public function __construct(
14 |         public Media $media,
15 |         public Conversion $conversion,
16 |         public string $copiedOriginalFile,
17 |     ) {}
18 | }
19 | 


--------------------------------------------------------------------------------
/src/Conversions/FileManipulator.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\Conversions;
  4 | 
  5 | use Illuminate\Support\Facades\Storage;
  6 | use Illuminate\Support\Str;
  7 | use Spatie\MediaLibrary\Conversions\Actions\PerformConversionAction;
  8 | use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory;
  9 | use Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob;
 10 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
 11 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 12 | use Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob;
 13 | use Spatie\MediaLibrary\Support\TemporaryDirectory;
 14 | 
 15 | class FileManipulator
 16 | {
 17 |     public function createDerivedFiles(
 18 |         Media $media,
 19 |         array $onlyConversionNames = [],
 20 |         bool $onlyMissing = false,
 21 |         bool $withResponsiveImages = false,
 22 |         bool $queueAll = false,
 23 |     ): void {
 24 |         if (! $this->canConvertMedia($media)) {
 25 |             return;
 26 |         }
 27 | 
 28 |         [$queuedConversions, $conversions] = ConversionCollection::createForMedia($media)
 29 |             ->filter(function (Conversion $conversion) use ($onlyConversionNames) {
 30 |                 if (count($onlyConversionNames) === 0) {
 31 |                     return true;
 32 |                 }
 33 | 
 34 |                 return in_array($conversion->getName(), $onlyConversionNames);
 35 |             })
 36 |             ->filter(fn (Conversion $conversion) => $conversion->shouldBePerformedOn($media->collection_name))
 37 |             ->partition(fn (Conversion $conversion) => $queueAll || $conversion->shouldBeQueued());
 38 | 
 39 |         $this
 40 |             ->performConversions($conversions, $media, $onlyMissing)
 41 |             ->dispatchQueuedConversions($media, $queuedConversions, $onlyMissing)
 42 |             ->generateResponsiveImages($media, $withResponsiveImages);
 43 |     }
 44 | 
 45 |     public function performConversions(
 46 |         ConversionCollection $conversions,
 47 |         Media $media,
 48 |         bool $onlyMissing = false
 49 |     ): self {
 50 |         $conversions = $conversions
 51 |             ->when(
 52 |                 $onlyMissing,
 53 |                 fn (ConversionCollection $conversions) => $conversions->reject(function (Conversion $conversion) use ($media) {
 54 |                     $relativePath = $media->getPath($conversion->getName());
 55 | 
 56 |                     if ($rootPath = config("filesystems.disks.{$media->disk}.root")) {
 57 |                         $relativePath = str_replace($rootPath, '', $relativePath);
 58 |                     }
 59 | 
 60 |                     return Storage::disk($media->disk)->exists($relativePath);
 61 |                 })
 62 |             );
 63 | 
 64 |         if ($conversions->isEmpty()) {
 65 |             return $this;
 66 |         }
 67 | 
 68 |         $temporaryDirectory = TemporaryDirectory::create();
 69 | 
 70 |         $copiedOriginalFile = app(Filesystem::class)->copyFromMediaLibrary(
 71 |             $media,
 72 |             $temporaryDirectory->path(Str::random(32).'.'.$media->extension)
 73 |         );
 74 | 
 75 |         $conversions->each(fn (Conversion $conversion) => (new PerformConversionAction)->execute($conversion, $media, $copiedOriginalFile));
 76 | 
 77 |         $temporaryDirectory->delete();
 78 | 
 79 |         return $this;
 80 |     }
 81 | 
 82 |     /**
 83 |      * @return $this
 84 |      */
 85 |     protected function dispatchQueuedConversions(
 86 |         Media $media,
 87 |         ConversionCollection $conversions,
 88 |         bool $onlyMissing = false
 89 |     ): self {
 90 |         if ($conversions->isEmpty()) {
 91 |             return $this;
 92 |         }
 93 | 
 94 |         $performConversionsJobClass = config(
 95 |             'media-library.jobs.perform_conversions',
 96 |             PerformConversionsJob::class
 97 |         );
 98 | 
 99 |         /** @var PerformConversionsJob $job */
100 |         $job = (new $performConversionsJobClass($conversions, $media, $onlyMissing))
101 |             ->onConnection(config('media-library.queue_connection_name'))
102 |             ->onQueue(config('media-library.queue_name'));
103 | 
104 |         config('media-library.queue_conversions_after_database_commit')
105 |             ? dispatch($job)->afterCommit()
106 |             : dispatch($job);
107 | 
108 |         return $this;
109 |     }
110 | 
111 |     /**
112 |      * @return $this
113 |      */
114 |     protected function generateResponsiveImages(Media $media, bool $withResponsiveImages): self
115 |     {
116 |         if (! $withResponsiveImages) {
117 |             return $this;
118 |         }
119 | 
120 |         if (! count($media->responsive_images)) {
121 |             return $this;
122 |         }
123 | 
124 |         $generateResponsiveImagesJobClass = config(
125 |             'media-library.jobs.generate_responsive_images',
126 |             GenerateResponsiveImagesJob::class
127 |         );
128 | 
129 |         /** @var GenerateResponsiveImagesJob $job */
130 |         $job = (new $generateResponsiveImagesJobClass($media))
131 |             ->onConnection(config('media-library.queue_connection_name'))
132 |             ->onQueue(config('media-library.queue_name'));
133 | 
134 |         config('media-library.queue_conversions_after_database_commit')
135 |             ? dispatch($job)->afterCommit()
136 |             : dispatch($job);
137 | 
138 |         return $this;
139 |     }
140 | 
141 |     protected function canConvertMedia(Media $media): bool
142 |     {
143 |         $imageGenerator = ImageGeneratorFactory::forMedia($media);
144 | 
145 |         return $imageGenerator ? true : false;
146 |     }
147 | }
148 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/Avif.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Spatie\MediaLibrary\Conversions\Conversion;
 7 | 
 8 | class Avif extends ImageGenerator
 9 | {
10 |     public function convert(string $file, ?Conversion $conversion = null): string
11 |     {
12 |         $pathToImageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.png';
13 | 
14 |         $image = imagecreatefromavif($file);
15 | 
16 |         imagepng($image, $pathToImageFile, 9);
17 | 
18 |         imagedestroy($image);
19 | 
20 |         return $pathToImageFile;
21 |     }
22 | 
23 |     public function requirementsAreInstalled(): bool
24 |     {
25 |         if (! function_exists('imagecreatefromavif')) {
26 |             return false;
27 |         }
28 | 
29 |         if (! function_exists('imagepng')) {
30 |             return false;
31 |         }
32 | 
33 |         if (! function_exists('imagedestroy')) {
34 |             return false;
35 |         }
36 | 
37 |         return true;
38 |     }
39 | 
40 |     public function supportedExtensions(): Collection
41 |     {
42 |         return collect(['avif']);
43 |     }
44 | 
45 |     public function supportedMimeTypes(): Collection
46 |     {
47 |         return collect(['image/avif']);
48 |     }
49 | }
50 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/Image.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Spatie\MediaLibrary\Conversions\Conversion;
 7 | 
 8 | class Image extends ImageGenerator
 9 | {
10 |     public function convert(string $path, ?Conversion $conversion = null): string
11 |     {
12 |         return $path;
13 |     }
14 | 
15 |     public function requirementsAreInstalled(): bool
16 |     {
17 |         return true;
18 |     }
19 | 
20 |     public function supportedExtensions(): Collection
21 |     {
22 |         $extensions = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'avif'];
23 |         if (config('media-library.image_driver') === 'imagick') {
24 |             $extensions[] = 'tiff';
25 |             $extensions[] = 'heic';
26 |             $extensions[] = 'heif';
27 |         }
28 | 
29 |         return collect($extensions);
30 |     }
31 | 
32 |     public function supportedMimeTypes(): Collection
33 |     {
34 |         $mimeTypes = ['image/jpeg', 'image/gif', 'image/png', 'image/webp', 'image/avif'];
35 |         if (config('media-library.image_driver') === 'imagick') {
36 |             $mimeTypes[] = 'image/tiff';
37 |             $mimeTypes[] = 'image/heic';
38 |             $mimeTypes[] = 'image/heif';
39 |         }
40 | 
41 |         return collect($mimeTypes);
42 |     }
43 | }
44 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/ImageGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Spatie\MediaLibrary\Conversions\Conversion;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | 
 9 | abstract class ImageGenerator
10 | {
11 |     /*
12 |      * This function should return a path to an image representation of the given file.
13 |      */
14 |     abstract public function convert(string $file, ?Conversion $conversion = null): ?string;
15 | 
16 |     public function canConvert(Media $media): bool
17 |     {
18 |         if (! $this->requirementsAreInstalled()) {
19 |             return false;
20 |         }
21 | 
22 |         $validExtension = $this->canHandleExtension(strtolower($media->extension));
23 | 
24 |         $validMimeType = $this->canHandleMime(strtolower($media->mime_type));
25 | 
26 |         if ($this->shouldMatchBothExtensionsAndMimeTypes()) {
27 |             return $validExtension && $validMimeType;
28 |         }
29 | 
30 |         return $validExtension || $validMimeType;
31 |     }
32 | 
33 |     public function shouldMatchBothExtensionsAndMimeTypes(): bool
34 |     {
35 |         return false;
36 |     }
37 | 
38 |     public function canHandleMime(string $mime = ''): bool
39 |     {
40 |         return $this->supportedMimeTypes()->contains($mime);
41 |     }
42 | 
43 |     public function canHandleExtension(string $extension = ''): bool
44 |     {
45 |         return $this->supportedExtensions()->contains($extension);
46 |     }
47 | 
48 |     public function getType(): string
49 |     {
50 |         return strtolower(class_basename(static::class));
51 |     }
52 | 
53 |     abstract public function requirementsAreInstalled(): bool;
54 | 
55 |     abstract public function supportedExtensions(): Collection;
56 | 
57 |     abstract public function supportedMimeTypes(): Collection;
58 | }
59 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/ImageGeneratorFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 7 | 
 8 | class ImageGeneratorFactory
 9 | {
10 |     public static function getImageGenerators(): Collection
11 |     {
12 |         return collect(config('media-library.image_generators'))
13 |             ->map(function ($imageGeneratorClassName, $key) {
14 |                 $imageGeneratorConfig = [];
15 | 
16 |                 if (! is_numeric($key)) {
17 |                     $imageGeneratorConfig = $imageGeneratorClassName;
18 |                     $imageGeneratorClassName = $key;
19 |                 }
20 | 
21 |                 return app($imageGeneratorClassName, $imageGeneratorConfig);
22 |             });
23 |     }
24 | 
25 |     public static function forExtension(?string $extension): ?ImageGenerator
26 |     {
27 |         if (is_null($extension)) {
28 |             return null;
29 |         }
30 | 
31 |         return static::getImageGenerators()
32 |             ->first(fn (ImageGenerator $imageGenerator) => $imageGenerator->canHandleExtension(strtolower($extension)));
33 |     }
34 | 
35 |     public static function forMimeType(?string $mimeType): ?ImageGenerator
36 |     {
37 |         if (is_null($mimeType)) {
38 |             return null;
39 |         }
40 | 
41 |         return static::getImageGenerators()
42 |             ->first(fn (ImageGenerator $imageGenerator) => $imageGenerator->canHandleMime($mimeType));
43 |     }
44 | 
45 |     public static function forMedia(Media $media): ?ImageGenerator
46 |     {
47 |         return static::getImageGenerators()
48 |             ->first(fn (ImageGenerator $imageGenerator) => $imageGenerator->canConvert($media));
49 |     }
50 | }
51 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/Pdf.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use Composer\InstalledVersions;
 6 | use Composer\Semver\VersionParser;
 7 | use Illuminate\Support\Collection;
 8 | use Imagick;
 9 | use Spatie\MediaLibrary\Conversions\Conversion;
10 | 
11 | class Pdf extends ImageGenerator
12 | {
13 |     public function convert(string $file, ?Conversion $conversion = null): string
14 |     {
15 |         $imageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.jpg';
16 | 
17 |         $pageNumber = $conversion ? $conversion->getPdfPageNumber() : 1;
18 | 
19 |         if ($this->usesPdfToImageV3()) {
20 |             (new \Spatie\PdfToImage\Pdf($file))->selectPage($pageNumber)->save($imageFile);
21 |         } else {
22 |             (new \Spatie\PdfToImage\Pdf($file))->setPage($pageNumber)->saveImage($imageFile);
23 |         }
24 | 
25 |         return $imageFile;
26 |     }
27 | 
28 |     private function usesPdfToImageV3(): bool
29 |     {
30 |         return InstalledVersions::satisfies(new VersionParser, 'spatie/pdf-to-image', '^3.0');
31 |     }
32 | 
33 |     public function requirementsAreInstalled(): bool
34 |     {
35 |         if (! class_exists(Imagick::class)) {
36 |             return false;
37 |         }
38 | 
39 |         if (! class_exists(\Spatie\PdfToImage\Pdf::class)) {
40 |             return false;
41 |         }
42 | 
43 |         return true;
44 |     }
45 | 
46 |     public function supportedExtensions(): Collection
47 |     {
48 |         return collect(['pdf']);
49 |     }
50 | 
51 |     public function supportedMimeTypes(): Collection
52 |     {
53 |         return collect(['application/pdf']);
54 |     }
55 | }
56 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/Svg.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Imagick;
 7 | use ImagickPixel;
 8 | use Spatie\MediaLibrary\Conversions\Conversion;
 9 | 
10 | class Svg extends ImageGenerator
11 | {
12 |     public function convert(string $file, ?Conversion $conversion = null): string
13 |     {
14 |         $imageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.png';
15 | 
16 |         $image = new Imagick;
17 |         $image->setBackgroundColor(new ImagickPixel('none'));
18 |         $image->readImage($file);
19 | 
20 |         $image->setImageFormat('png32');
21 | 
22 |         file_put_contents($imageFile, $image);
23 | 
24 |         return $imageFile;
25 |     }
26 | 
27 |     public function requirementsAreInstalled(): bool
28 |     {
29 |         return class_exists(Imagick::class);
30 |     }
31 | 
32 |     public function supportedExtensions(): Collection
33 |     {
34 |         return collect(['svg']);
35 |     }
36 | 
37 |     public function supportedMimeTypes(): Collection
38 |     {
39 |         return collect(['image/svg+xml']);
40 |     }
41 | }
42 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/Video.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use FFMpeg\Coordinate\TimeCode;
 6 | use FFMpeg\FFMpeg;
 7 | use FFMpeg\Media\Video as FFMpegVideo;
 8 | use Illuminate\Support\Collection;
 9 | use Spatie\MediaLibrary\Conversions\Conversion;
10 | 
11 | class Video extends ImageGenerator
12 | {
13 |     public function convert(string $file, ?Conversion $conversion = null): ?string
14 |     {
15 |         $ffmpeg = FFMpeg::create([
16 |             'ffmpeg.binaries' => config('media-library.ffmpeg_path'),
17 |             'ffprobe.binaries' => config('media-library.ffprobe_path'),
18 |         ]);
19 | 
20 |         $video = $ffmpeg->open($file);
21 | 
22 |         if (! ($video instanceof FFMpegVideo)) {
23 |             return null;
24 |         }
25 | 
26 |         $duration = $ffmpeg->getFFProbe()->format($file)->get('duration');
27 | 
28 |         $seconds = $conversion ? $conversion->getExtractVideoFrameAtSecond() : 0;
29 |         $seconds = $duration <= $seconds ? 0 : $seconds;
30 | 
31 |         $imageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.jpg';
32 | 
33 |         $frame = $video->frame(TimeCode::fromSeconds($seconds));
34 |         $frame->save($imageFile);
35 | 
36 |         return $imageFile;
37 |     }
38 | 
39 |     public function requirementsAreInstalled(): bool
40 |     {
41 |         return class_exists('\\FFMpeg\\FFMpeg');
42 |     }
43 | 
44 |     public function supportedExtensions(): Collection
45 |     {
46 |         return collect(['webm', 'mov', 'mp4']);
47 |     }
48 | 
49 |     public function supportedMimeTypes(): Collection
50 |     {
51 |         return collect(['video/webm', 'video/mpeg', 'video/mp4', 'video/quicktime']);
52 |     }
53 | }
54 | 


--------------------------------------------------------------------------------
/src/Conversions/ImageGenerators/Webp.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\ImageGenerators;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Spatie\MediaLibrary\Conversions\Conversion;
 7 | 
 8 | class Webp extends ImageGenerator
 9 | {
10 |     public function convert(string $file, ?Conversion $conversion = null): string
11 |     {
12 |         $pathToImageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.png';
13 | 
14 |         $image = imagecreatefromwebp($file);
15 | 
16 |         imagepng($image, $pathToImageFile, 9);
17 | 
18 |         imagedestroy($image);
19 | 
20 |         return $pathToImageFile;
21 |     }
22 | 
23 |     public function requirementsAreInstalled(): bool
24 |     {
25 |         if (! function_exists('imagecreatefromwebp')) {
26 |             return false;
27 |         }
28 | 
29 |         if (! function_exists('imagepng')) {
30 |             return false;
31 |         }
32 | 
33 |         if (! function_exists('imagedestroy')) {
34 |             return false;
35 |         }
36 | 
37 |         return true;
38 |     }
39 | 
40 |     public function supportedExtensions(): Collection
41 |     {
42 |         return collect(['webp']);
43 |     }
44 | 
45 |     public function supportedMimeTypes(): Collection
46 |     {
47 |         return collect(['image/webp']);
48 |     }
49 | }
50 | 


--------------------------------------------------------------------------------
/src/Conversions/Jobs/PerformConversionsJob.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Conversions\Jobs;
 4 | 
 5 | use Illuminate\Bus\Queueable;
 6 | use Illuminate\Contracts\Queue\ShouldQueue;
 7 | use Illuminate\Queue\InteractsWithQueue;
 8 | use Illuminate\Queue\SerializesModels;
 9 | use Spatie\MediaLibrary\Conversions\ConversionCollection;
10 | use Spatie\MediaLibrary\Conversions\FileManipulator;
11 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
12 | 
13 | class PerformConversionsJob implements ShouldQueue
14 | {
15 |     use InteractsWithQueue;
16 |     use Queueable;
17 |     use SerializesModels;
18 | 
19 |     public $deleteWhenMissingModels = true;
20 | 
21 |     public function __construct(
22 |         protected ConversionCollection $conversions,
23 |         protected Media $media,
24 |         protected bool $onlyMissing = false,
25 |     ) {}
26 | 
27 |     public function handle(FileManipulator $fileManipulator): bool
28 |     {
29 |         $fileManipulator->performConversions(
30 |             $this->conversions,
31 |             $this->media,
32 |             $this->onlyMissing
33 |         );
34 | 
35 |         return true;
36 |     }
37 | }
38 | 


--------------------------------------------------------------------------------
/src/Conversions/Manipulations.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\Conversions;
  4 | 
  5 | use Spatie\Image\Drivers\ImageDriver;
  6 | use Spatie\Image\Enums\AlignPosition;
  7 | use Spatie\Image\Enums\BorderType;
  8 | use Spatie\Image\Enums\Constraint;
  9 | use Spatie\Image\Enums\CropPosition;
 10 | use Spatie\Image\Enums\Fit;
 11 | use Spatie\Image\Enums\FlipDirection;
 12 | use Spatie\Image\Enums\Orientation;
 13 | 
 14 | /** @mixin \Spatie\Image\Drivers\ImageDriver */
 15 | class Manipulations
 16 | {
 17 |     protected array $manipulations = [];
 18 | 
 19 |     public function __construct(array $manipulations = [])
 20 |     {
 21 |         $this->manipulations = $manipulations;
 22 |     }
 23 | 
 24 |     public function __call(string $method, array $parameters): self
 25 |     {
 26 |         $this->addManipulation($method, $parameters);
 27 | 
 28 |         return $this;
 29 |     }
 30 | 
 31 |     /**
 32 |      * @return $this
 33 |      */
 34 |     public function addManipulation(string $name, array $parameters = []): self
 35 |     {
 36 |         $this->manipulations[$name] = $parameters;
 37 | 
 38 |         return $this;
 39 |     }
 40 | 
 41 |     public function getManipulationArgument(string $manipulationName): null|string|array
 42 |     {
 43 |         return $this->manipulations[$manipulationName] ?? null;
 44 |     }
 45 | 
 46 |     public function getFirstManipulationArgument(string $manipulationName): null|string|int
 47 |     {
 48 |         $manipulationArgument = $this->getManipulationArgument($manipulationName);
 49 | 
 50 |         if (! is_array($manipulationArgument)) {
 51 |             return null;
 52 |         }
 53 | 
 54 |         return $manipulationArgument[0];
 55 |     }
 56 | 
 57 |     public function isEmpty(): bool
 58 |     {
 59 |         return count($this->manipulations) === 0;
 60 |     }
 61 | 
 62 |     public function apply(ImageDriver $image): void
 63 |     {
 64 |         foreach ($this->manipulations as $manipulationName => $parameters) {
 65 |             $parameters = $this->transformParameters($manipulationName, $parameters);
 66 |             $image->$manipulationName(...$parameters);
 67 |         }
 68 |     }
 69 | 
 70 |     /**
 71 |      * @return $this
 72 |      */
 73 |     public function mergeManipulations(self $manipulations): self
 74 |     {
 75 |         foreach ($manipulations->toArray() as $name => $parameters) {
 76 |             $this->manipulations[$name] = array_merge($this->manipulations[$name] ?? [], $parameters ?: []);
 77 |         }
 78 | 
 79 |         return $this;
 80 |     }
 81 | 
 82 |     /**
 83 |      * @return $this
 84 |      */
 85 |     public function removeManipulation(string $name): self
 86 |     {
 87 |         unset($this->manipulations[$name]);
 88 | 
 89 |         return $this;
 90 |     }
 91 | 
 92 |     public function toArray(): array
 93 |     {
 94 |         return $this->manipulations;
 95 |     }
 96 | 
 97 |     public function transformParameters(int|string $manipulationName, mixed $parameters): mixed
 98 |     {
 99 |         switch ($manipulationName) {
100 |             case 'border':
101 |                 if (isset($parameters['type']) && ! $parameters['type'] instanceof BorderType) {
102 |                     $parameters['type'] = BorderType::from($parameters['type']);
103 |                 }
104 |                 break;
105 |             case 'watermark':
106 |                 if (isset($parameters['fit']) && ! $parameters['fit'] instanceof Fit) {
107 |                     $parameters['fit'] = Fit::from($parameters['fit']);
108 |                 }
109 |                 // Fallthrough intended for position
110 |             case 'resizeCanvas':
111 |             case 'insert':
112 |                 if (isset($parameters['position']) && ! $parameters['position'] instanceof AlignPosition) {
113 |                     $parameters['position'] = AlignPosition::from($parameters['position']);
114 |                 }
115 |                 break;
116 |             case 'resize':
117 |             case 'width':
118 |             case 'height':
119 |                 if (isset($parameters['constraints']) && is_array($parameters['constraints'])) {
120 |                     foreach ($parameters['constraints'] as &$constraint) {
121 |                         if (! $constraint instanceof Constraint) {
122 |                             $constraint = Constraint::from($constraint);
123 |                         }
124 |                     }
125 |                 }
126 |                 break;
127 |             case 'crop':
128 |                 if (isset($parameters['position']) && ! $parameters['position'] instanceof CropPosition) {
129 |                     $parameters['position'] = CropPosition::from($parameters['position']);
130 |                 }
131 |                 break;
132 |             case 'fit':
133 |                 if (isset($parameters['fit']) && ! $parameters['fit'] instanceof Fit) {
134 |                     $parameters['fit'] = Fit::from($parameters['fit']);
135 |                 }
136 |                 break;
137 |             case 'flip':
138 |                 if (isset($parameters['flip']) && ! $parameters['flip'] instanceof FlipDirection) {
139 |                     $parameters['flip'] = FlipDirection::from($parameters['flip']);
140 |                 }
141 |                 break;
142 |             case 'orientation':
143 |                 if (isset($parameters['orientation']) && ! $parameters['orientation'] instanceof Orientation) {
144 |                     $parameters['orientation'] = Orientation::from($parameters['orientation']);
145 |                 }
146 |                 break;
147 |             default:
148 |                 break;
149 |         }
150 | 
151 |         return $parameters;
152 |     }
153 | }
154 | 


--------------------------------------------------------------------------------
/src/Downloaders/DefaultDownloader.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Downloaders;
 4 | 
 5 | use Spatie\MediaLibrary\MediaCollections\Exceptions\UnreachableUrl;
 6 | 
 7 | class DefaultDownloader implements Downloader
 8 | {
 9 |     public function getTempFile(string $url): string
10 |     {
11 |         $context = stream_context_create([
12 |             'ssl' => [
13 |                 'verify_peer' => config('media-library.media_downloader_ssl'),
14 |                 'verify_peer_name' => config('media-library.media_downloader_ssl'),
15 |             ],
16 |             'http' => [
17 |                 'header' => 'User-Agent: Spatie MediaLibrary',
18 |             ],
19 |         ]);
20 | 
21 |         if (! $stream = @fopen($url, 'r', false, $context)) {
22 |             throw UnreachableUrl::create($url);
23 |         }
24 | 
25 |         $temporaryFile = tempnam(sys_get_temp_dir(), 'media-library');
26 | 
27 |         file_put_contents($temporaryFile, $stream);
28 | 
29 |         fclose($stream);
30 | 
31 |         return $temporaryFile;
32 |     }
33 | }
34 | 


--------------------------------------------------------------------------------
/src/Downloaders/Downloader.php:
--------------------------------------------------------------------------------
1 | <?php
2 | 
3 | namespace Spatie\MediaLibrary\Downloaders;
4 | 
5 | interface Downloader
6 | {
7 |     public function getTempFile(string $url): string;
8 | }
9 | 


--------------------------------------------------------------------------------
/src/Downloaders/HttpFacadeDownloader.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Downloaders;
 4 | 
 5 | use Illuminate\Support\Facades\Http;
 6 | use Spatie\MediaLibrary\MediaCollections\Exceptions\UnreachableUrl;
 7 | 
 8 | class HttpFacadeDownloader implements Downloader
 9 | {
10 |     public function getTempFile(string $url): string
11 |     {
12 |         $temporaryFile = tempnam(sys_get_temp_dir(), 'media-library');
13 | 
14 |         Http::withUserAgent('Spatie MediaLibrary')
15 |             ->throw(fn () => throw new UnreachableUrl($url))
16 |             ->sink($temporaryFile)
17 |             ->get($url);
18 | 
19 |         return $temporaryFile;
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/Enums/CollectionPosition.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Enums;
 4 | 
 5 | enum CollectionPosition: string
 6 | {
 7 |     case First = 'first';
 8 |     case Last = 'last';
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/HasMedia.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary;
 4 | 
 5 | use Illuminate\Database\Eloquent\Relations\MorphMany;
 6 | use Illuminate\Support\Collection;
 7 | use Spatie\MediaLibrary\Conversions\Conversion;
 8 | use Spatie\MediaLibrary\MediaCollections\FileAdder;
 9 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
10 | use Symfony\Component\HttpFoundation\File\UploadedFile;
11 | 
12 | /**
13 |  * @mixin \Illuminate\Database\Eloquent\Model
14 |  *
15 |  * @method void prepareToAttachMedia(Media $media, FileAdder $fileAdder)
16 |  *
17 |  * @property bool $registerMediaConversionsUsingModelInstance
18 |  * @property ?\Spatie\MediaLibrary\MediaCollections\MediaCollection $mediaCollections
19 |  */
20 | interface HasMedia
21 | {
22 |     public function media(): MorphMany;
23 | 
24 |     public function addMedia(string|UploadedFile $file): FileAdder;
25 | 
26 |     public function copyMedia(string|UploadedFile $file): FileAdder;
27 | 
28 |     public function hasMedia(string $collectionName = ''): bool;
29 | 
30 |     public function getMedia(string $collectionName = 'default', array|callable $filters = []): Collection;
31 | 
32 |     public function clearMediaCollection(string $collectionName = 'default'): HasMedia;
33 | 
34 |     public function clearMediaCollectionExcept(string $collectionName = 'default', array|Collection $excludedMedia = []): HasMedia;
35 | 
36 |     public function shouldDeletePreservingMedia(): bool;
37 | 
38 |     public function loadMedia(string $collectionName);
39 | 
40 |     public function addMediaConversion(string $name): Conversion;
41 | 
42 |     public function registerMediaConversions(?Media $media = null): void;
43 | 
44 |     public function registerMediaCollections(): void;
45 | 
46 |     public function registerAllMediaConversions(): void;
47 | 
48 |     public function getMediaModel(): string;
49 | }
50 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Commands/CleanCommand.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\MediaCollections\Commands;
  4 | 
  5 | use Illuminate\Console\Command;
  6 | use Illuminate\Console\ConfirmableTrait;
  7 | use Illuminate\Contracts\Filesystem\Factory;
  8 | use Illuminate\Support\LazyCollection;
  9 | use Illuminate\Support\Str;
 10 | use Spatie\MediaLibrary\Conversions\Conversion;
 11 | use Spatie\MediaLibrary\Conversions\ConversionCollection;
 12 | use Spatie\MediaLibrary\Conversions\FileManipulator;
 13 | use Spatie\MediaLibrary\MediaCollections\Exceptions\DiskDoesNotExist;
 14 | use Spatie\MediaLibrary\MediaCollections\MediaRepository;
 15 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 16 | use Spatie\MediaLibrary\ResponsiveImages\RegisteredResponsiveImages;
 17 | use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory;
 18 | 
 19 | class CleanCommand extends Command
 20 | {
 21 |     use ConfirmableTrait;
 22 | 
 23 |     protected $signature = 'media-library:clean {modelType?} {collectionName?} {disk?}
 24 |     {--dry-run : List files that will be removed without removing them},
 25 |     {--force : Force the operation to run when in production},
 26 |     {--rate-limit= : Limit the number of requests per second},
 27 |     {--delete-orphaned : Delete orphaned media items},
 28 |     {--skip-conversions : Do not remove deprecated conversions}';
 29 | 
 30 |     protected $description = 'Clean deprecated conversions and files without related model.';
 31 | 
 32 |     protected MediaRepository $mediaRepository;
 33 | 
 34 |     protected FileManipulator $fileManipulator;
 35 | 
 36 |     protected Factory $fileSystem;
 37 | 
 38 |     protected bool $isDryRun = false;
 39 | 
 40 |     protected int $rateLimit = 0;
 41 | 
 42 |     public function handle(
 43 |         MediaRepository $mediaRepository,
 44 |         FileManipulator $fileManipulator,
 45 |         Factory $fileSystem,
 46 |     ): void {
 47 |         $this->mediaRepository = $mediaRepository;
 48 |         $this->fileManipulator = $fileManipulator;
 49 |         $this->fileSystem = $fileSystem;
 50 | 
 51 |         if (! $this->confirmToProceed()) {
 52 |             return;
 53 |         }
 54 | 
 55 |         $this->isDryRun = $this->option('dry-run');
 56 |         $this->rateLimit = (int) $this->option('rate-limit');
 57 | 
 58 |         if ($this->option('delete-orphaned')) {
 59 |             $this->deleteOrphanedMediaItems();
 60 |         }
 61 | 
 62 |         if (! $this->option('skip-conversions')) {
 63 |             $this->deleteFilesGeneratedForDeprecatedConversions();
 64 |         }
 65 | 
 66 |         $this->deleteOrphanedDirectories();
 67 | 
 68 |         $this->info('All done!');
 69 |     }
 70 | 
 71 |     /** @return LazyCollection<int, Media> */
 72 |     public function getMediaItems(): LazyCollection
 73 |     {
 74 |         $modelType = $this->argument('modelType');
 75 |         $collectionName = $this->argument('collectionName');
 76 | 
 77 |         if (is_string($modelType) && is_string($collectionName)) {
 78 |             return $this->mediaRepository->getByModelTypeAndCollectionName(
 79 |                 $modelType,
 80 |                 $collectionName
 81 |             );
 82 |         }
 83 | 
 84 |         if (is_string($modelType)) {
 85 |             return $this->mediaRepository->getByModelType($modelType);
 86 |         }
 87 | 
 88 |         if (is_string($collectionName)) {
 89 |             return $this->mediaRepository->getByCollectionName($collectionName);
 90 |         }
 91 | 
 92 |         return $this->mediaRepository->all();
 93 |     }
 94 | 
 95 |     protected function deleteOrphanedMediaItems(): void
 96 |     {
 97 |         $this->getOrphanedMediaItems()->each(function (Media $media): void {
 98 |             if ($this->isDryRun) {
 99 |                 $this->info("Orphaned Media[id={$media->id}] found");
100 | 
101 |                 return;
102 |             }
103 | 
104 |             $media->delete();
105 | 
106 |             if ($this->rateLimit) {
107 |                 usleep((1 / $this->rateLimit) * 1_000_000);
108 |             }
109 | 
110 |             $this->info("Orphaned Media[id={$media->id}] has been removed");
111 |         });
112 |     }
113 | 
114 |     /** @return LazyCollection<int, Media> */
115 |     protected function getOrphanedMediaItems(): LazyCollection
116 |     {
117 |         $collectionName = $this->argument('collectionName');
118 | 
119 |         if (is_string($collectionName)) {
120 |             return $this->mediaRepository->getOrphansByCollectionName($collectionName);
121 |         }
122 | 
123 |         return $this->mediaRepository->getOrphans();
124 |     }
125 | 
126 |     protected function deleteFilesGeneratedForDeprecatedConversions(): void
127 |     {
128 |         $this->getMediaItems()->each(function (Media $media) {
129 |             $this->deleteConversionFilesForDeprecatedConversions($media);
130 | 
131 |             if ($media->responsive_images) {
132 |                 $this->deleteDeprecatedResponsiveImages($media);
133 |             }
134 | 
135 |             if ($this->rateLimit) {
136 |                 usleep((1 / $this->rateLimit) * 1_000_000 * 2);
137 |             }
138 |         });
139 |     }
140 | 
141 |     protected function deleteConversionFilesForDeprecatedConversions(Media $media): void
142 |     {
143 |         $conversionFilePaths = ConversionCollection::createForMedia($media)->getConversionsFiles($media->collection_name);
144 | 
145 |         $conversionPath = PathGeneratorFactory::create($media)->getPathForConversions($media);
146 |         $currentFilePaths = $this->fileSystem->disk($media->disk)->files($conversionPath);
147 | 
148 |         collect($currentFilePaths)
149 |             ->reject(fn (string $currentFilePath) => $conversionFilePaths->contains(basename($currentFilePath)))
150 |             ->reject(fn (string $currentFilePath) => $media->file_name === basename($currentFilePath))
151 |             ->each(function (string $currentFilePath) use ($media) {
152 |                 if (! $this->isDryRun) {
153 |                     $this->fileSystem->disk($media->disk)->delete($currentFilePath);
154 | 
155 |                     $this->markConversionAsRemoved($media, $currentFilePath);
156 |                 }
157 | 
158 |                 $this->info("Deprecated conversion file `{$currentFilePath}` ".($this->isDryRun ? 'found' : 'has been removed'));
159 |             });
160 |     }
161 | 
162 |     protected function deleteDeprecatedResponsiveImages(Media $media): void
163 |     {
164 |         $conversionNamesWithResponsiveImages = ConversionCollection::createForMedia($media)
165 |             ->filter(fn (Conversion $conversion) => $conversion->shouldGenerateResponsiveImages())
166 |             ->map(fn (Conversion $conversion) => $conversion->getName())
167 |             ->push('media_library_original');
168 | 
169 |         /** @var array<int, string> $responsiveImagesGeneratedFor */
170 |         $responsiveImagesGeneratedFor = array_keys($media->responsive_images);
171 | 
172 |         collect($responsiveImagesGeneratedFor)
173 |             ->map(fn (string $generatedFor) => $media->responsiveImages($generatedFor))
174 |             ->reject(fn (RegisteredResponsiveImages $responsiveImages) => $conversionNamesWithResponsiveImages->contains($responsiveImages->generatedFor))
175 |             ->each(function (RegisteredResponsiveImages $responsiveImages) {
176 |                 if (! $this->isDryRun) {
177 |                     $responsiveImages->delete();
178 |                 }
179 |             });
180 |     }
181 | 
182 |     protected function deleteOrphanedDirectories(): void
183 |     {
184 |         $diskName = $this->argument('disk') ?: config('media-library.disk_name');
185 | 
186 |         if (is_null(config("filesystems.disks.{$diskName}"))) {
187 |             throw DiskDoesNotExist::create($diskName);
188 |         }
189 | 
190 |         $prefix = config('media-library.prefix', '');
191 | 
192 |         if ($prefix !== '') {
193 |             $prefix = trim($prefix, '/').'/';
194 |         }
195 | 
196 |         $mediaIds = $this->mediaRepository->allIds();
197 | 
198 |         /** @var array<int, string> */
199 |         $directories = $this->fileSystem->disk($diskName)->directories($prefix);
200 | 
201 |         collect($directories)
202 |             ->map(fn (string $directory) => str_replace($prefix, '', $directory))
203 |             ->filter(fn (string $directory) => is_numeric($directory))
204 |             ->reject(fn (string $directory) => $mediaIds->contains((int) $directory))
205 |             ->each(function (string $directory) use ($diskName, $prefix) {
206 |                 $directory = $prefix.$directory;
207 | 
208 |                 if (! $this->isDryRun) {
209 |                     $this->fileSystem->disk($diskName)->deleteDirectory($directory);
210 |                 }
211 | 
212 |                 if ($this->rateLimit) {
213 |                     usleep((1 / $this->rateLimit) * 1_000_000);
214 |                 }
215 | 
216 |                 $this->info("Orphaned media directory `{$directory}` ".($this->isDryRun ? 'found' : 'has been removed'));
217 |             });
218 |     }
219 | 
220 |     protected function markConversionAsRemoved(Media $media, string $conversionPath): void
221 |     {
222 |         $conversionFile = pathinfo($conversionPath, PATHINFO_FILENAME);
223 | 
224 |         $generatedConversionName = null;
225 | 
226 |         $media->getGeneratedConversions()
227 |             ->dot()
228 |             ->filter(
229 |                 fn (bool $isGenerated, string $generatedConversionName) => Str::contains($conversionFile, $generatedConversionName)
230 |             )
231 |             ->each(
232 |                 fn (bool $isGenerated, string $conversionName) => $media->markAsConversionNotGenerated($conversionName)
233 |             );
234 | 
235 |         $media->save();
236 |     }
237 | }
238 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Commands/ClearCommand.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Commands;
 4 | 
 5 | use Illuminate\Console\Command;
 6 | use Illuminate\Console\ConfirmableTrait;
 7 | use Illuminate\Support\LazyCollection;
 8 | use Spatie\MediaLibrary\MediaCollections\MediaRepository;
 9 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
10 | 
11 | class ClearCommand extends Command
12 | {
13 |     use ConfirmableTrait;
14 | 
15 |     protected $signature = 'media-library:clear {modelType?} {collectionName?}
16 |     {-- force : Force the operation to run when in production}';
17 | 
18 |     protected $description = 'Delete all items in a media collection.';
19 | 
20 |     protected MediaRepository $mediaRepository;
21 | 
22 |     public function handle(MediaRepository $mediaRepository): void
23 |     {
24 |         $this->mediaRepository = $mediaRepository;
25 | 
26 |         if (! $this->confirmToProceed()) {
27 |             return;
28 |         }
29 | 
30 |         $mediaItems = $this->getMediaItems();
31 | 
32 |         $progressBar = $this->output->createProgressBar($mediaItems->count());
33 | 
34 |         $mediaItems->each(function (Media $media) use ($progressBar) {
35 |             $media->delete();
36 |             $progressBar->advance();
37 |         });
38 | 
39 |         $progressBar->finish();
40 | 
41 |         $this->info('All done!');
42 |     }
43 | 
44 |     /** @return LazyCollection<int, Media> */
45 |     public function getMediaItems(): LazyCollection
46 |     {
47 |         $modelType = $this->argument('modelType');
48 |         $collectionName = $this->argument('collectionName');
49 | 
50 |         if (is_string($modelType) && is_string($collectionName)) {
51 |             return $this->mediaRepository->getByModelTypeAndCollectionName(
52 |                 $modelType,
53 |                 $collectionName
54 |             );
55 |         }
56 | 
57 |         if (is_string($modelType)) {
58 |             return $this->mediaRepository->getByModelType($modelType);
59 |         }
60 | 
61 |         if (is_string($collectionName)) {
62 |             return $this->mediaRepository->getByCollectionName($collectionName);
63 |         }
64 | 
65 |         return $this->mediaRepository->all();
66 |     }
67 | }
68 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Contracts/MediaLibraryRequest.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Contracts;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | 
 7 | interface MediaLibraryRequest
 8 | {
 9 |     public function mediaLibraryRequestItems(string $key): Collection;
10 | }
11 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Events/CollectionHasBeenClearedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Events;
 4 | 
 5 | use Illuminate\Queue\SerializesModels;
 6 | use Spatie\MediaLibrary\HasMedia;
 7 | 
 8 | class CollectionHasBeenClearedEvent
 9 | {
10 |     use SerializesModels;
11 | 
12 |     public function __construct(public HasMedia $model, public string $collectionName) {}
13 | }
14 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Events/MediaHasBeenAddedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Events;
 4 | 
 5 | use Illuminate\Queue\SerializesModels;
 6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 7 | 
 8 | class MediaHasBeenAddedEvent
 9 | {
10 |     use SerializesModels;
11 | 
12 |     public function __construct(public Media $media) {}
13 | }
14 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/DiskCannotBeAccessed.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class DiskCannotBeAccessed extends FileCannotBeAdded
 6 | {
 7 |     public static function create(string $diskName): self
 8 |     {
 9 |         return new static("Disk named `{$diskName}` cannot be accessed");
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/DiskDoesNotExist.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class DiskDoesNotExist extends FileCannotBeAdded
 6 | {
 7 |     public static function create(string $diskName): self
 8 |     {
 9 |         return new static("There is no filesystem disk named `{$diskName}`");
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/FileCannotBeAdded.php:
--------------------------------------------------------------------------------
1 | <?php
2 | 
3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
4 | 
5 | use Exception;
6 | 
7 | abstract class FileCannotBeAdded extends Exception {}
8 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/FileDoesNotExist.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class FileDoesNotExist extends FileCannotBeAdded
 6 | {
 7 |     public static function create(string $path): self
 8 |     {
 9 |         return new static("File `{$path}` does not exist");
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/FileIsTooBig.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Spatie\MediaLibrary\Support\File;
 6 | 
 7 | class FileIsTooBig extends FileCannotBeAdded
 8 | {
 9 |     public static function create(string $path, ?int $size = null): self
10 |     {
11 |         $fileSize = File::getHumanReadableSize($size ?: filesize($path));
12 | 
13 |         $maxFileSize = File::getHumanReadableSize(config('media-library.max_file_size'));
14 | 
15 |         return new static("File `{$path}` has a size of {$fileSize} which is greater than the maximum allowed {$maxFileSize}");
16 |     }
17 | }
18 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/FileNameNotAllowed.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class FileNameNotAllowed extends FileCannotBeAdded
 6 | {
 7 |     public static function create(string $orignalName, string $sanitizedName): self
 8 |     {
 9 |         return new static("The file name `{$orignalName}` was sanitized to `{$sanitizedName}`. This sanitized file name is not allowed because it is a PHP file.");
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/FileUnacceptableForCollection.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Spatie\MediaLibrary\HasMedia;
 6 | use Spatie\MediaLibrary\MediaCollections\File;
 7 | use Spatie\MediaLibrary\MediaCollections\MediaCollection;
 8 | 
 9 | class FileUnacceptableForCollection extends FileCannotBeAdded
10 | {
11 |     public static function create(File $file, MediaCollection $mediaCollection, HasMedia $hasMedia): self
12 |     {
13 |         $modelType = $hasMedia::class;
14 | 
15 |         return new static("The file with properties `{$file}` was not accepted into the collection named `{$mediaCollection->name}` of model `{$modelType}` with id `{$hasMedia->getKey()}`");
16 |     }
17 | }
18 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/FunctionalityNotAvailable.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | 
 7 | class FunctionalityNotAvailable extends Exception
 8 | {
 9 |     public static function mediaLibraryProRequired(): self
10 |     {
11 |         return new static('You need to have media library pro installed to make this work.');
12 |     }
13 | }
14 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/InvalidBase64Data.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class InvalidBase64Data extends FileCannotBeAdded
 6 | {
 7 |     public static function create(): self
 8 |     {
 9 |         return new static('Invalid base64 data provided');
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/InvalidConversion.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | 
 7 | class InvalidConversion extends Exception
 8 | {
 9 |     public static function unknownName(string $name): self
10 |     {
11 |         return new static("There is no conversion named `{$name}`");
12 |     }
13 | }
14 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/InvalidFileRemover.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | use Spatie\MediaLibrary\Support\FileRemover\FileRemover;
 7 | 
 8 | class InvalidFileRemover extends Exception
 9 | {
10 |     public static function doesntExist(string $class): self
11 |     {
12 |         return new static("File remover class `{$class}` doesn't exist");
13 |     }
14 | 
15 |     public static function doesNotImplementFileRemover(string $class): self
16 |     {
17 |         $fileRemoverClass = FileRemover::class;
18 | 
19 |         return new static("File remover class `{$class}` must implement `$fileRemoverClass}`");
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/InvalidPathGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator;
 7 | 
 8 | class InvalidPathGenerator extends Exception
 9 | {
10 |     public static function doesntExist(string $class): self
11 |     {
12 |         return new static("Path generator class `{$class}` doesn't exist");
13 |     }
14 | 
15 |     public static function doesNotImplementPathGenerator(string $class): self
16 |     {
17 |         $pathGeneratorClass = PathGenerator::class;
18 | 
19 |         return new static("Path generator class `{$class}` must implement `$pathGeneratorClass}`");
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/InvalidUrl.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | 
 7 | class InvalidUrl extends Exception
 8 | {
 9 |     public static function doesNotStartWithProtocol(string $url): self
10 |     {
11 |         return new static("Could not add `{$url}` because it does not start with either `http://` or `https://`");
12 |     }
13 | }
14 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/InvalidUrlGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | use Spatie\MediaLibrary\Support\UrlGenerator\UrlGenerator;
 7 | 
 8 | class InvalidUrlGenerator extends Exception
 9 | {
10 |     public static function doesntExist(string $class): self
11 |     {
12 |         return new static("Url generator class {$class} doesn't exist");
13 |     }
14 | 
15 |     public static function doesNotImplementUrlGenerator(string $class): self
16 |     {
17 |         $urlGeneratorClass = UrlGenerator::class;
18 | 
19 |         return new static("Url generator Class {$class} must implement `{$urlGeneratorClass}`");
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/MediaCannotBeDeleted.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | use Illuminate\Database\Eloquent\Model;
 7 | 
 8 | class MediaCannotBeDeleted extends Exception
 9 | {
10 |     public static function doesNotBelongToModel($mediaId, Model $model): self
11 |     {
12 |         $modelClass = $model::class;
13 | 
14 |         return new static("Media with id `{$mediaId}` cannot be deleted because it does not exist or does not belong to model {$modelClass} with id {$model->getKey()}");
15 |     }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/MediaCannotBeUpdated.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | use Exception;
 6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 7 | 
 8 | class MediaCannotBeUpdated extends Exception
 9 | {
10 |     public static function doesNotBelongToCollection(string $collectionName, Media $media): self
11 |     {
12 |         return new static("Media id {$media->getKey()} is not part of collection `{$collectionName}`");
13 |     }
14 | }
15 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/MimeTypeNotAllowed.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class MimeTypeNotAllowed extends FileCannotBeAdded
 6 | {
 7 |     public static function create(string $file, array $allowedMimeTypes): self
 8 |     {
 9 |         $mimeType = mime_content_type($file);
10 | 
11 |         $allowedMimeTypes = implode(', ', $allowedMimeTypes);
12 | 
13 |         return new static("File has a mime type of {$mimeType}, while only {$allowedMimeTypes} are allowed");
14 |     }
15 | }
16 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/RequestDoesNotHaveFile.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class RequestDoesNotHaveFile extends FileCannotBeAdded
 6 | {
 7 |     public static function create(string $key): self
 8 |     {
 9 |         return new static("The current request does not have a file in a key named `{$key}`");
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/UnknownType.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class UnknownType extends FileCannotBeAdded
 6 | {
 7 |     public static function create(): self
 8 |     {
 9 |         return new static('Only strings, FileObjects and UploadedFileObjects can be imported');
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Exceptions/UnreachableUrl.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Exceptions;
 4 | 
 5 | class UnreachableUrl extends FileCannotBeAdded
 6 | {
 7 |     public static function create(string $url): self
 8 |     {
 9 |         return new static("Url `{$url}` cannot be reached");
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/MediaCollections/File.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections;
 4 | 
 5 | class File implements \Stringable
 6 | {
 7 |     public static function createFromMedia($media): self
 8 |     {
 9 |         return new static($media->file_name, $media->size, $media->mime_type);
10 |     }
11 | 
12 |     public function __construct(
13 |         public string $name,
14 |         public int $size,
15 |         public string $mimeType
16 |     ) {}
17 | 
18 |     public function __toString(): string
19 |     {
20 |         return "name: {$this->name}, size: {$this->size}, mime: {$this->mimeType}";
21 |     }
22 | }
23 | 


--------------------------------------------------------------------------------
/src/MediaCollections/FileAdderFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections;
 4 | 
 5 | use Illuminate\Database\Eloquent\Model;
 6 | use Illuminate\Support\Collection;
 7 | use Spatie\MediaLibrary\MediaCollections\Exceptions\RequestDoesNotHaveFile;
 8 | use Spatie\MediaLibrary\Support\RemoteFile;
 9 | use Spatie\MediaLibraryPro\Dto\PendingMediaItem;
10 | use Symfony\Component\HttpFoundation\File\UploadedFile;
11 | 
12 | class FileAdderFactory
13 | {
14 |     public static function create(Model $subject, string|UploadedFile $file): FileAdder
15 |     {
16 |         /** @var FileAdder $fileAdder */
17 |         $fileAdder = app(FileAdder::class);
18 | 
19 |         return $fileAdder
20 |             ->setSubject($subject)
21 |             ->setFile($file);
22 |     }
23 | 
24 |     public static function createFromDisk(Model $subject, string $key, string $disk): FileAdder
25 |     {
26 |         /** @var FileAdder $fileAdder */
27 |         $fileAdder = app(FileAdder::class);
28 | 
29 |         return $fileAdder
30 |             ->setSubject($subject)
31 |             ->setFile(new RemoteFile($key, $disk));
32 |     }
33 | 
34 |     public static function createFromRequest(Model $subject, string $key): FileAdder
35 |     {
36 |         return static::createMultipleFromRequest($subject, [$key])->first();
37 |     }
38 | 
39 |     public static function createMultipleFromRequest(Model $subject, array $keys = []): Collection
40 |     {
41 |         return collect($keys)
42 |             ->map(function (string $key) use ($subject) {
43 |                 $key = trim(basename($key), './');
44 | 
45 |                 if (! request()->hasFile($key)) {
46 |                     throw RequestDoesNotHaveFile::create($key);
47 |                 }
48 | 
49 |                 $files = request()->file($key);
50 | 
51 |                 if (! is_array($files)) {
52 |                     return static::create($subject, $files);
53 |                 }
54 | 
55 |                 return array_map(fn ($file) => static::create($subject, $file), $files);
56 |             })->flatten();
57 |     }
58 | 
59 |     public static function createAllFromRequest(Model $subject): Collection
60 |     {
61 |         $fileKeys = array_keys(request()->allFiles());
62 | 
63 |         return static::createMultipleFromRequest($subject, $fileKeys);
64 |     }
65 | 
66 |     public static function createForPendingMedia(Model $subject, PendingMediaItem $pendingMedia): FileAdder
67 |     {
68 |         /** @var FileAdder $fileAdder */
69 |         $fileAdder = app(FileAdder::class);
70 | 
71 |         return $fileAdder
72 |             ->setSubject($subject)
73 |             ->setFile($pendingMedia->temporaryUpload)
74 |             ->setName($pendingMedia->name)
75 |             ->setOrder($pendingMedia->order);
76 |     }
77 | }
78 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Filesystem.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\MediaCollections;
  4 | 
  5 | use Illuminate\Contracts\Filesystem\Factory;
  6 | use Illuminate\Support\Facades\Storage;
  7 | use Illuminate\Support\Str;
  8 | use Spatie\MediaLibrary\Conversions\ConversionCollection;
  9 | use Spatie\MediaLibrary\Conversions\FileManipulator;
 10 | use Spatie\MediaLibrary\MediaCollections\Events\MediaHasBeenAddedEvent;
 11 | use Spatie\MediaLibrary\MediaCollections\Exceptions\DiskCannotBeAccessed;
 12 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 13 | use Spatie\MediaLibrary\Support\File;
 14 | use Spatie\MediaLibrary\Support\FileNamer\FileNamer;
 15 | use Spatie\MediaLibrary\Support\FileRemover\FileRemoverFactory;
 16 | use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory;
 17 | use Spatie\MediaLibrary\Support\RemoteFile;
 18 | 
 19 | class Filesystem
 20 | {
 21 |     protected array $customRemoteHeaders = [];
 22 | 
 23 |     public function __construct(
 24 |         protected Factory $filesystem
 25 |     ) {}
 26 | 
 27 |     public function add(string $file, Media $media, ?string $targetFileName = null): bool
 28 |     {
 29 |         try {
 30 |             $this->copyToMediaLibrary($file, $media, null, $targetFileName);
 31 |         } catch (DiskCannotBeAccessed $exception) {
 32 |             return false;
 33 |         }
 34 | 
 35 |         event(new MediaHasBeenAddedEvent($media));
 36 | 
 37 |         app(FileManipulator::class)->createDerivedFiles($media);
 38 | 
 39 |         return true;
 40 |     }
 41 | 
 42 |     public function addRemote(RemoteFile $file, Media $media, ?string $targetFileName = null): bool
 43 |     {
 44 |         try {
 45 |             $this->copyToMediaLibraryFromRemote($file, $media, null, $targetFileName);
 46 |         } catch (DiskCannotBeAccessed $exception) {
 47 |             return false;
 48 |         }
 49 | 
 50 |         event(new MediaHasBeenAddedEvent($media));
 51 | 
 52 |         app(FileManipulator::class)->createDerivedFiles($media);
 53 | 
 54 |         return true;
 55 |     }
 56 | 
 57 |     public function prepareCopyFileOnDisk(RemoteFile $file, Media $media, string $destination): void
 58 |     {
 59 |         $this->copyFileOnDisk($file->getKey(), $destination, $media->disk);
 60 |     }
 61 | 
 62 |     public function copyToMediaLibraryFromRemote(RemoteFile $file, Media $media, ?string $type = null, ?string $targetFileName = null): void
 63 |     {
 64 |         $destinationFileName = $targetFileName ?: $file->getFilename();
 65 | 
 66 |         $destination = $this->getMediaDirectory($media, $type).$destinationFileName;
 67 | 
 68 |         $diskDriverName = (in_array($type, ['conversions', 'responsiveImages']))
 69 |             ? $media->getConversionsDiskDriverName()
 70 |             : $media->getDiskDriverName();
 71 | 
 72 |         if ($this->shouldCopyFileOnDisk($file, $media, $diskDriverName)) {
 73 |             $this->prepareCopyFileOnDisk($file, $media, $destination);
 74 | 
 75 |             return;
 76 |         }
 77 | 
 78 |         $storage = Storage::disk($file->getDisk());
 79 | 
 80 |         $headers = $diskDriverName === 'local'
 81 |             ? []
 82 |             : $this->getRemoteHeadersForFile(
 83 |                 $file->getKey(),
 84 |                 $media->getCustomHeaders(),
 85 |                 $storage->mimeType($file->getKey())
 86 |             );
 87 | 
 88 |         $this->streamFileToDisk(
 89 |             $storage->getDriver()->readStream($file->getKey()),
 90 |             $destination,
 91 |             $media->disk,
 92 |             $headers
 93 |         );
 94 |     }
 95 | 
 96 |     protected function shouldCopyFileOnDisk(RemoteFile $file, Media $media, string $diskDriverName): bool
 97 |     {
 98 |         if ($file->getDisk() !== $media->disk) {
 99 |             return false;
100 |         }
101 | 
102 |         if ($diskDriverName === 'local') {
103 |             return true;
104 |         }
105 | 
106 |         if (count($media->getCustomHeaders()) > 0) {
107 |             return false;
108 |         }
109 | 
110 |         if ((is_countable(config('media-library.remote.extra_headers')) ? count(config('media-library.remote.extra_headers')) : 0) > 0) {
111 |             return false;
112 |         }
113 | 
114 |         return true;
115 |     }
116 | 
117 |     protected function copyFileOnDisk(string $file, string $destination, string $disk): void
118 |     {
119 |         $this->filesystem->disk($disk)
120 |             ->copy($file, $destination);
121 |     }
122 | 
123 |     protected function streamFileToDisk($stream, string $destination, string $disk, array $headers): void
124 |     {
125 |         $this->filesystem->disk($disk)
126 |             ->getDriver()->writeStream(
127 |                 $destination,
128 |                 $stream,
129 |                 $headers
130 |             );
131 |     }
132 | 
133 |     public function copyToMediaLibrary(string $pathToFile, Media $media, ?string $type = null, ?string $targetFileName = null): void
134 |     {
135 |         $destinationFileName = $targetFileName ?: pathinfo($pathToFile, PATHINFO_BASENAME);
136 | 
137 |         $destination = $this->getMediaDirectory($media, $type).$destinationFileName;
138 | 
139 |         $file = fopen($pathToFile, 'r');
140 | 
141 |         $diskName = (in_array($type, ['conversions', 'responsiveImages']))
142 |             ? $media->conversions_disk
143 |             : $media->disk;
144 | 
145 |         $diskDriverName = (in_array($type, ['conversions', 'responsiveImages']))
146 |             ? $media->getConversionsDiskDriverName()
147 |             : $media->getDiskDriverName();
148 | 
149 |         if ($diskDriverName === 'local') {
150 |             $success = $this->filesystem
151 |                 ->disk($diskName)
152 |                 ->put($destination, $file);
153 | 
154 |             fclose($file);
155 | 
156 |             if (! $success) {
157 |                 throw DiskCannotBeAccessed::create($diskName);
158 |             }
159 | 
160 |             return;
161 |         }
162 | 
163 |         $success = $this->filesystem
164 |             ->disk($diskName)
165 |             ->put(
166 |                 $destination,
167 |                 $file,
168 |                 $this->getRemoteHeadersForFile($pathToFile, $media->getCustomHeaders()),
169 |             );
170 | 
171 |         if (is_resource($file)) {
172 |             fclose($file);
173 |         }
174 | 
175 |         if (! $success) {
176 |             throw DiskCannotBeAccessed::create($diskName);
177 |         }
178 |     }
179 | 
180 |     public function addCustomRemoteHeaders(array $customRemoteHeaders): void
181 |     {
182 |         $this->customRemoteHeaders = $customRemoteHeaders;
183 |     }
184 | 
185 |     public function getRemoteHeadersForFile(
186 |         string $file,
187 |         array $mediaCustomHeaders = [],
188 |         ?string $mimeType = null
189 |     ): array {
190 |         $mimeTypeHeader = ['ContentType' => $mimeType ?: File::getMimeType($file)];
191 | 
192 |         $extraHeaders = config('media-library.remote.extra_headers');
193 | 
194 |         return array_merge(
195 |             $mimeTypeHeader,
196 |             $extraHeaders,
197 |             $this->customRemoteHeaders,
198 |             $mediaCustomHeaders
199 |         );
200 |     }
201 | 
202 |     public function getStream(Media $media)
203 |     {
204 |         $sourceFile = $this->getMediaDirectory($media).'/'.$media->file_name;
205 | 
206 |         return $this->filesystem->disk($media->disk)->readStream($sourceFile);
207 |     }
208 | 
209 |     public function getConversionStream(Media $media, string $conversion)
210 |     {
211 |         $sourceFile = $media->getPathRelativeToRoot($conversion);
212 | 
213 |         return $this->filesystem->disk($media->conversions_disk)->readStream($sourceFile);
214 |     }
215 | 
216 |     public function copyFromMediaLibrary(Media $media, string $targetFile): string
217 |     {
218 |         file_put_contents($targetFile, $this->getStream($media));
219 | 
220 |         return $targetFile;
221 |     }
222 | 
223 |     public function removeAllFiles(Media $media): void
224 |     {
225 |         $fileRemover = FileRemoverFactory::create($media);
226 | 
227 |         $fileRemover->removeAllFiles($media);
228 |     }
229 | 
230 |     public function removeFile(Media $media, string $path): void
231 |     {
232 |         $fileRemover = FileRemoverFactory::create($media);
233 | 
234 |         $fileRemover->removeFile($path, $media->disk);
235 |     }
236 | 
237 |     public function removeResponsiveImages(Media $media, string $conversionName = 'media_library_original'): void
238 |     {
239 |         /** @var FileNamer $fileNamer */
240 |         $fileNamer = app(config('media-library.file_namer'));
241 |         $mediaFilename = $fileNamer->responsiveFileName($media->name);
242 | 
243 |         $responsiveImagesDirectory = $this->getResponsiveImagesDirectory($media);
244 | 
245 |         $allFilePaths = $this->filesystem->disk($media->disk)->allFiles($responsiveImagesDirectory);
246 | 
247 |         $responsiveImagePaths = array_filter(
248 |             $allFilePaths,
249 |             static fn (string $path) => Str::contains($path, $mediaFilename.'___'.$conversionName)
250 |         );
251 | 
252 |         $this->filesystem->disk($media->disk)->delete($responsiveImagePaths);
253 |     }
254 | 
255 |     public function syncFileNames(Media $media): void
256 |     {
257 |         $this->renameMediaFile($media);
258 | 
259 |         $this->renameConversionFiles($media);
260 |     }
261 | 
262 |     public function syncMediaPath(Media $media): void
263 |     {
264 |         $factory = PathGeneratorFactory::create($media);
265 | 
266 |         $oldMedia = (clone $media)->fill($media->getOriginal());
267 | 
268 |         $oldPath = $factory->getPath($oldMedia);
269 |         $newPath = $factory->getPath($media);
270 | 
271 |         if ($oldPath === $newPath) {
272 |             return;
273 |         }
274 | 
275 |         // If the media is stored on S3, we need to move all files in the directory
276 |         if ($media->getDiskDriverName() === 's3') {
277 |             $allFiles = $this->filesystem->disk($media->disk)->allFiles($oldPath);
278 | 
279 |             foreach ($allFiles as $file) {
280 |                 $newFilePath = str_replace($oldPath, $newPath, $file);
281 |                 $this->filesystem->disk($media->disk)->move($file, $newFilePath);
282 |             }
283 | 
284 |             return;
285 |         }
286 | 
287 |         $this->filesystem->disk($media->disk)->move($oldPath, $newPath);
288 |     }
289 | 
290 |     protected function renameMediaFile(Media $media): void
291 |     {
292 |         $newFileName = $media->file_name;
293 |         $oldFileName = $media->getOriginal('file_name');
294 | 
295 |         $mediaDirectory = $this->getMediaDirectory($media);
296 | 
297 |         $oldFile = "{$mediaDirectory}/{$oldFileName}";
298 |         $newFile = "{$mediaDirectory}/{$newFileName}";
299 | 
300 |         $this->filesystem->disk($media->disk)->move($oldFile, $newFile);
301 |     }
302 | 
303 |     protected function renameConversionFiles(Media $media): void
304 |     {
305 |         $mediaWithOldFileName = config('media-library.media_model')::find($media->getKey());
306 |         $mediaWithOldFileName->file_name = $mediaWithOldFileName->getOriginal('file_name');
307 | 
308 |         $conversionDirectory = $this->getConversionDirectory($media);
309 | 
310 |         $conversionCollection = ConversionCollection::createForMedia($media);
311 | 
312 |         foreach ($media->getMediaConversionNames() as $conversionName) {
313 |             $conversion = $conversionCollection->getByName($conversionName);
314 | 
315 |             $oldFile = $conversionDirectory.$conversion->getConversionFile($mediaWithOldFileName);
316 |             $newFile = $conversionDirectory.$conversion->getConversionFile($media);
317 | 
318 |             $disk = $this->filesystem->disk($media->conversions_disk);
319 | 
320 |             // A media conversion file might be missing, waiting to be generated, failed etc.
321 |             if (! $disk->exists($oldFile)) {
322 |                 continue;
323 |             }
324 | 
325 |             $disk->move($oldFile, $newFile);
326 |         }
327 |     }
328 | 
329 |     public function getMediaDirectory(Media $media, ?string $type = null): string
330 |     {
331 |         $directory = null;
332 |         $pathGenerator = PathGeneratorFactory::create($media);
333 | 
334 |         if (! $type) {
335 |             $directory = $pathGenerator->getPath($media);
336 |         }
337 | 
338 |         if ($type === 'conversions') {
339 |             $directory = $pathGenerator->getPathForConversions($media);
340 |         }
341 | 
342 |         if ($type === 'responsiveImages') {
343 |             $directory = $pathGenerator->getPathForResponsiveImages($media);
344 |         }
345 | 
346 |         return $directory;
347 |     }
348 | 
349 |     public function getConversionDirectory(Media $media): string
350 |     {
351 |         return $this->getMediaDirectory($media, 'conversions');
352 |     }
353 | 
354 |     public function getResponsiveImagesDirectory(Media $media): string
355 |     {
356 |         return $this->getMediaDirectory($media, 'responsiveImages');
357 |     }
358 | }
359 | 


--------------------------------------------------------------------------------
/src/MediaCollections/HtmlableMedia.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\MediaCollections;
  4 | 
  5 | use Illuminate\Contracts\Support\Htmlable;
  6 | use Illuminate\Support\Arr;
  7 | use Spatie\MediaLibrary\Conversions\ConversionCollection;
  8 | use Spatie\MediaLibrary\Conversions\ImageGenerators\Image;
  9 | use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory;
 10 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 11 | 
 12 | class HtmlableMedia implements \Stringable, Htmlable
 13 | {
 14 |     protected string $conversionName = '';
 15 | 
 16 |     protected array $extraAttributes = [];
 17 | 
 18 |     protected string $loadingAttributeValue = '';
 19 | 
 20 |     public function __construct(
 21 |         protected Media $media
 22 |     ) {}
 23 | 
 24 |     /**
 25 |      * @return $this
 26 |      */
 27 |     public function attributes(array $attributes): self
 28 |     {
 29 |         if (is_array($attributes['class'] ?? null)) {
 30 |             $attributes['class'] = Arr::toCssClasses($attributes['class']);
 31 |         }
 32 | 
 33 |         if (is_array($attributes['style'] ?? null)) {
 34 |             $attributes['style'] = Arr::toCssStyles($attributes['style']);
 35 |         }
 36 | 
 37 |         $this->extraAttributes = $attributes;
 38 | 
 39 |         return $this;
 40 |     }
 41 | 
 42 |     /**
 43 |      * @return $this
 44 |      */
 45 |     public function conversion(string $conversionName): self
 46 |     {
 47 |         $this->conversionName = $conversionName;
 48 | 
 49 |         return $this;
 50 |     }
 51 | 
 52 |     /**
 53 |      * @return $this
 54 |      */
 55 |     public function lazy(): self
 56 |     {
 57 |         $this->loadingAttributeValue = ('lazy');
 58 | 
 59 |         return $this;
 60 |     }
 61 | 
 62 |     public function toHtml(): string
 63 |     {
 64 |         $imageGenerator = ImageGeneratorFactory::forMedia($this->media) ?? new Image;
 65 | 
 66 |         if (! $imageGenerator->canHandleMime($this->media->mime_type)) {
 67 |             return '';
 68 |         }
 69 | 
 70 |         $attributeString = collect($this->extraAttributes)
 71 |             ->map(fn ($value, $name) => $name.'="'.$value.'"')->implode(' ');
 72 | 
 73 |         if (strlen($attributeString)) {
 74 |             $attributeString = ' '.$attributeString;
 75 |         }
 76 | 
 77 |         $loadingAttributeValue = config('media-library.default_loading_attribute_value');
 78 | 
 79 |         if ($this->conversionName !== '') {
 80 |             $conversionObject = ConversionCollection::createForMedia($this->media)->getByName($this->conversionName);
 81 | 
 82 |             $loadingAttributeValue = $conversionObject->getLoadingAttributeValue();
 83 |         }
 84 | 
 85 |         if ($this->loadingAttributeValue !== '') {
 86 |             $loadingAttributeValue = $this->loadingAttributeValue;
 87 |         }
 88 | 
 89 |         $viewName = 'image';
 90 |         $width = '';
 91 |         $height = '';
 92 | 
 93 |         if ($this->media->hasResponsiveImages($this->conversionName)) {
 94 |             $viewName = config('media-library.responsive_images.use_tiny_placeholders')
 95 |                 ? 'responsiveImageWithPlaceholder'
 96 |                 : 'responsiveImage';
 97 | 
 98 |             $responsiveImage = $this->media->responsiveImages($this->conversionName)->files->first();
 99 | 
100 |             $width = $responsiveImage->width();
101 |             $height = $responsiveImage->height();
102 |         }
103 | 
104 |         $media = $this->media;
105 |         $conversion = $this->conversionName;
106 | 
107 |         return view("media-library::{$viewName}", compact(
108 |             'media',
109 |             'conversion',
110 |             'attributeString',
111 |             'loadingAttributeValue',
112 |             'width',
113 |             'height',
114 |         ))->render();
115 |     }
116 | 
117 |     public function __toString(): string
118 |     {
119 |         return $this->toHtml();
120 |     }
121 | }
122 | 


--------------------------------------------------------------------------------
/src/MediaCollections/MediaCollection.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\MediaCollections;
  4 | 
  5 | use Illuminate\Support\Traits\Macroable;
  6 | use InvalidArgumentException;
  7 | 
  8 | class MediaCollection
  9 | {
 10 |     use Macroable;
 11 | 
 12 |     public string $diskName = '';
 13 | 
 14 |     public string $conversionsDiskName = '';
 15 | 
 16 |     /** @var callable */
 17 |     public $mediaConversionRegistrations;
 18 | 
 19 |     public bool $generateResponsiveImages = false;
 20 | 
 21 |     /** @var callable */
 22 |     public $acceptsFile;
 23 | 
 24 |     public array $acceptsMimeTypes = [];
 25 | 
 26 |     /** @var bool|int */
 27 |     public $collectionSizeLimit = false;
 28 | 
 29 |     public bool $singleFile = false;
 30 | 
 31 |     /** @var array<string, string> */
 32 |     public array $fallbackUrls = [];
 33 | 
 34 |     /** @var array<string, string> */
 35 |     public array $fallbackPaths = [];
 36 | 
 37 |     public function __construct(
 38 |         public string $name
 39 |     ) {
 40 |         $this->mediaConversionRegistrations = function () {};
 41 | 
 42 |         $this->acceptsFile = fn () => true;
 43 |     }
 44 | 
 45 |     public static function create($name): self
 46 |     {
 47 |         return new static($name);
 48 |     }
 49 | 
 50 |     /**
 51 |      * @return $this
 52 |      */
 53 |     public function useDisk(string $diskName): self
 54 |     {
 55 |         $this->diskName = $diskName;
 56 | 
 57 |         return $this;
 58 |     }
 59 | 
 60 |     /**
 61 |      * @return $this
 62 |      */
 63 |     public function storeConversionsOnDisk(string $conversionsDiskName): self
 64 |     {
 65 |         $this->conversionsDiskName = $conversionsDiskName;
 66 | 
 67 |         return $this;
 68 |     }
 69 | 
 70 |     /**
 71 |      * @return $this
 72 |      */
 73 |     public function acceptsFile(callable $acceptsFile): self
 74 |     {
 75 |         $this->acceptsFile = $acceptsFile;
 76 | 
 77 |         return $this;
 78 |     }
 79 | 
 80 |     /**
 81 |      * @return $this
 82 |      */
 83 |     public function acceptsMimeTypes(array $mimeTypes): self
 84 |     {
 85 |         $this->acceptsMimeTypes = $mimeTypes;
 86 | 
 87 |         return $this;
 88 |     }
 89 | 
 90 |     public function singleFile(): self
 91 |     {
 92 |         return $this->onlyKeepLatest(1);
 93 |     }
 94 | 
 95 |     /**
 96 |      * @return $this
 97 |      */
 98 |     public function onlyKeepLatest(int $maximumNumberOfItemsInCollection): self
 99 |     {
100 |         if ($maximumNumberOfItemsInCollection < 1) {
101 |             throw new InvalidArgumentException("You should pass a value higher than 0. `{$maximumNumberOfItemsInCollection}` given.");
102 |         }
103 | 
104 |         $this->singleFile = ($maximumNumberOfItemsInCollection === 1);
105 | 
106 |         $this->collectionSizeLimit = $maximumNumberOfItemsInCollection;
107 | 
108 |         return $this;
109 |     }
110 | 
111 |     public function registerMediaConversions(callable $mediaConversionRegistrations): void
112 |     {
113 |         $this->mediaConversionRegistrations = $mediaConversionRegistrations;
114 |     }
115 | 
116 |     /**
117 |      * @return $this
118 |      */
119 |     public function useFallbackUrl(string $url, string $conversionName = ''): self
120 |     {
121 |         if ($conversionName === '') {
122 |             $conversionName = 'default';
123 |         }
124 | 
125 |         $this->fallbackUrls[$conversionName] = $url;
126 | 
127 |         return $this;
128 |     }
129 | 
130 |     /**
131 |      * @return $this
132 |      */
133 |     public function useFallbackPath(string $path, string $conversionName = ''): self
134 |     {
135 |         if ($conversionName === '') {
136 |             $conversionName = 'default';
137 |         }
138 | 
139 |         $this->fallbackPaths[$conversionName] = $path;
140 | 
141 |         return $this;
142 |     }
143 | 
144 |     /**
145 |      * @return $this
146 |      */
147 |     public function withResponsiveImages(): self
148 |     {
149 |         $this->generateResponsiveImages = true;
150 | 
151 |         return $this;
152 |     }
153 | 
154 |     /**
155 |      * @return $this
156 |      */
157 |     public function withResponsiveImagesIf($condition): self
158 |     {
159 |         $this->generateResponsiveImages = (bool) (is_callable($condition) ? $condition() : $condition);
160 | 
161 |         return $this;
162 |     }
163 | }
164 | 


--------------------------------------------------------------------------------
/src/MediaCollections/MediaRepository.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\MediaCollections;
  4 | 
  5 | use Closure;
  6 | use Illuminate\Database\Eloquent\Builder;
  7 | use Illuminate\Support\Arr;
  8 | use Illuminate\Support\Collection;
  9 | use Illuminate\Support\LazyCollection;
 10 | use Spatie\MediaLibrary\HasMedia;
 11 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 12 | 
 13 | class MediaRepository
 14 | {
 15 |     public function __construct(
 16 |         protected Media $model
 17 |     ) {}
 18 | 
 19 |     /**
 20 |      * Get all media in the collection.
 21 |      */
 22 |     public function getCollection(
 23 |         HasMedia $model,
 24 |         string $collectionName,
 25 |         array|callable $filter = []
 26 |     ): Collection {
 27 |         return $this->applyFilterToMediaCollection($model->loadMedia($collectionName), $filter);
 28 |     }
 29 | 
 30 |     /**
 31 |      * Apply given filters on media.
 32 |      */
 33 |     protected function applyFilterToMediaCollection(
 34 |         Collection $media,
 35 |         array|callable $filter
 36 |     ): Collection {
 37 |         if (is_array($filter)) {
 38 |             $filter = $this->getDefaultFilterFunction($filter);
 39 |         }
 40 | 
 41 |         return $media->filter($filter);
 42 |     }
 43 | 
 44 |     public function all(): LazyCollection
 45 |     {
 46 |         return $this->query()->cursor();
 47 |     }
 48 | 
 49 |     public function allIds(): Collection
 50 |     {
 51 |         return $this->query()->pluck($this->model->getKeyName());
 52 |     }
 53 | 
 54 |     public function getByModelType(string $modelType): LazyCollection
 55 |     {
 56 |         return $this->query()->where('model_type', $modelType)->cursor();
 57 |     }
 58 | 
 59 |     public function getByIds(array $ids): LazyCollection
 60 |     {
 61 |         return $this->query()->whereIn($this->model->getKeyName(), $ids)->cursor();
 62 |     }
 63 | 
 64 |     public function getByIdGreaterThan(int $startingFromId, bool $excludeStartingId = false, string $modelType = ''): LazyCollection
 65 |     {
 66 |         return $this->query()
 67 |             ->where($this->model->getKeyName(), $excludeStartingId ? '>' : '>=', $startingFromId)
 68 |             ->when($modelType !== '', fn (Builder $q) => $q->where('model_type', $modelType))
 69 |             ->cursor();
 70 |     }
 71 | 
 72 |     public function getByModelTypeAndCollectionName(string $modelType, string $collectionName): LazyCollection
 73 |     {
 74 |         return $this->query()
 75 |             ->where('model_type', $modelType)
 76 |             ->where('collection_name', $collectionName)
 77 |             ->cursor();
 78 |     }
 79 | 
 80 |     public function getByCollectionName(string $collectionName): LazyCollection
 81 |     {
 82 |         return $this->query()
 83 |             ->where('collection_name', $collectionName)
 84 |             ->cursor();
 85 |     }
 86 | 
 87 |     public function getOrphans(): LazyCollection
 88 |     {
 89 |         return $this->orphansQuery()
 90 |             ->cursor();
 91 |     }
 92 | 
 93 |     public function getOrphansByCollectionName(string $collectionName): LazyCollection
 94 |     {
 95 |         return $this->orphansQuery()
 96 |             ->where('collection_name', $collectionName)
 97 |             ->cursor();
 98 |     }
 99 | 
100 |     protected function query(): Builder
101 |     {
102 |         return $this->model->newQuery();
103 |     }
104 | 
105 |     protected function orphansQuery(): Builder
106 |     {
107 |         return $this->query()->where(fn (Builder $query) => $query->whereDoesntHave(
108 |             'model',
109 |             fn (Builder $q) => $q->hasMacro('withTrashed') ? $q->withTrashed() : $q,
110 |         ));
111 |     }
112 | 
113 |     protected function getDefaultFilterFunction(array $filters): Closure
114 |     {
115 |         return function (Media $media) use ($filters) {
116 |             foreach ($filters as $property => $value) {
117 |                 if (! Arr::has($media->custom_properties, $property)) {
118 |                     return false;
119 |                 }
120 | 
121 |                 if (Arr::get($media->custom_properties, $property) !== $value) {
122 |                     return false;
123 |                 }
124 |             }
125 | 
126 |             return true;
127 |         };
128 |     }
129 | }
130 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Models/Collections/MediaCollection.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Models\Collections;
 4 | 
 5 | use Illuminate\Contracts\Support\Htmlable;
 6 | use Illuminate\Database\Eloquent\Collection;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | 
 9 | /**
10 |  * @template TKey of array-key
11 |  * @template TModel of \Spatie\MediaLibrary\MediaCollections\Models\Media
12 |  *
13 |  * @extends Collection<TKey, TModel>
14 |  */
15 | class MediaCollection extends Collection implements Htmlable
16 | {
17 |     public ?string $collectionName = null;
18 | 
19 |     public ?string $formFieldName = null;
20 | 
21 |     /**
22 |      * @return $this
23 |      */
24 |     public function collectionName(string $collectionName): self
25 |     {
26 |         $this->collectionName = $collectionName;
27 | 
28 |         return $this;
29 |     }
30 | 
31 |     /**
32 |      * @return $this
33 |      */
34 |     public function formFieldName(string $formFieldName): self
35 |     {
36 |         $this->formFieldName = $formFieldName;
37 | 
38 |         return $this;
39 |     }
40 | 
41 |     public function totalSizeInBytes(): int
42 |     {
43 |         return $this->sum('size');
44 |     }
45 | 
46 |     public function toHtml(): string
47 |     {
48 |         return e(json_encode(old($this->formFieldName ?? $this->collectionName) ?? $this->map(function (Media $media) {
49 |             return [
50 |                 'name' => $media->name,
51 |                 'file_name' => $media->file_name,
52 |                 'uuid' => $media->uuid,
53 |                 'preview_url' => $media->preview_url,
54 |                 'original_url' => $media->original_url,
55 |                 'order' => $media->order_column,
56 |                 'custom_properties' => $media->custom_properties,
57 |                 'extension' => $media->extension,
58 |                 'size' => $media->size,
59 |             ];
60 |         })->keyBy('uuid')));
61 |     }
62 | 
63 |     public function jsonSerialize(): array
64 |     {
65 |         if (config('media-library.use_default_collection_serialization')) {
66 |             return parent::jsonSerialize();
67 |         }
68 | 
69 |         if (! ($this->formFieldName ?? $this->collectionName)) {
70 |             return [];
71 |         }
72 | 
73 |         return old($this->formFieldName ?? $this->collectionName) ?? $this->map(function (Media $media) {
74 |             return [
75 |                 'name' => $media->name,
76 |                 'file_name' => $media->file_name,
77 |                 'uuid' => $media->uuid,
78 |                 'preview_url' => $media->preview_url,
79 |                 'original_url' => $media->original_url,
80 |                 'order' => $media->order_column,
81 |                 'custom_properties' => $media->custom_properties,
82 |                 'extension' => $media->extension,
83 |                 'size' => $media->size,
84 |             ];
85 |         })->keyBy('uuid')->toArray();
86 |     }
87 | }
88 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Models/Concerns/CustomMediaProperties.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Models\Concerns;
 4 | 
 5 | trait CustomMediaProperties
 6 | {
 7 |     /**
 8 |      * @return $this
 9 |      */
10 |     public function setCustomHeaders(array $customHeaders): self
11 |     {
12 |         $this->setCustomProperty('custom_headers', $customHeaders);
13 | 
14 |         return $this;
15 |     }
16 | 
17 |     public function getCustomHeaders(): array
18 |     {
19 |         return $this->getCustomProperty('custom_headers', []);
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Models/Concerns/HasUuid.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Models\Concerns;
 4 | 
 5 | use Illuminate\Database\Eloquent\Model;
 6 | use Illuminate\Support\Str;
 7 | 
 8 | trait HasUuid
 9 | {
10 |     public static function bootHasUuid(): void
11 |     {
12 |         static::creating(function (Model $model) {
13 |             /** @var \Spatie\MediaLibrary\MediaCollections\Models\Media $model */
14 |             if (empty($model->uuid)) {
15 |                 $model->uuid = (string) Str::uuid();
16 |             }
17 |         });
18 |     }
19 | 
20 |     public static function findByUuid(string $uuid): ?Model
21 |     {
22 |         return static::where('uuid', $uuid)->first();
23 |     }
24 | }
25 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Models/Concerns/IsSorted.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Models\Concerns;
 4 | 
 5 | use Illuminate\Database\Eloquent\Builder;
 6 | 
 7 | trait IsSorted
 8 | {
 9 |     public function setHighestOrderNumber(): void
10 |     {
11 |         $orderColumnName = $this->determineOrderColumnName();
12 | 
13 |         $this->$orderColumnName = $this->getHighestOrderNumber() + 1;
14 |     }
15 | 
16 |     public function getHighestOrderNumber(): int
17 |     {
18 |         return (int) static::where('model_type', $this->model_type)
19 |             ->where('model_id', $this->model_id)
20 |             ->max($this->determineOrderColumnName());
21 |     }
22 | 
23 |     public function scopeOrdered(Builder $query): Builder
24 |     {
25 |         return $query->orderBy($this->determineOrderColumnName());
26 |     }
27 | 
28 |     /*
29 |      * This function reorders the records: the record with the first id in the array
30 |      * will get the starting order (defaults to 1), the record with the second id
31 |      * will get the starting order + 1, and so on.
32 |      *
33 |      * A starting order number can be optionally supplied.
34 |      */
35 |     public static function setNewOrder(array $ids, int $startOrder = 1): void
36 |     {
37 |         foreach ($ids as $id) {
38 |             $model = static::find($id);
39 |             if (! $model) {
40 |                 continue;
41 |             }
42 | 
43 |             $orderColumnName = $model->determineOrderColumnName();
44 | 
45 |             $model->$orderColumnName = $startOrder++;
46 | 
47 |             $model->save();
48 |         }
49 |     }
50 | 
51 |     protected function determineOrderColumnName(): string
52 |     {
53 |         return $this->sortable['order_column_name'] ?? 'order_column';
54 |     }
55 | 
56 |     public function shouldSortWhenCreating(): bool
57 |     {
58 |         return $this->sortable['sort_when_creating'] ?? true;
59 |     }
60 | }
61 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Models/Media.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\MediaCollections\Models;
  4 | 
  5 | use Closure;
  6 | use DateTimeInterface;
  7 | use Illuminate\Contracts\Mail\Attachable;
  8 | use Illuminate\Contracts\Support\Htmlable;
  9 | use Illuminate\Contracts\Support\Responsable;
 10 | use Illuminate\Database\Eloquent\Builder;
 11 | use Illuminate\Database\Eloquent\Casts\Attribute;
 12 | use Illuminate\Database\Eloquent\Collection as EloquentCollection;
 13 | use Illuminate\Database\Eloquent\Model;
 14 | use Illuminate\Database\Eloquent\Relations\BelongsTo;
 15 | use Illuminate\Database\Eloquent\Relations\MorphTo;
 16 | use Illuminate\Mail\Attachment;
 17 | use Illuminate\Support\Arr;
 18 | use Illuminate\Support\Collection;
 19 | use Illuminate\Support\Str;
 20 | use Spatie\MediaLibrary\Conversions\Conversion;
 21 | use Spatie\MediaLibrary\Conversions\ConversionCollection;
 22 | use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory;
 23 | use Spatie\MediaLibrary\HasMedia;
 24 | use Spatie\MediaLibrary\MediaCollections\FileAdder;
 25 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
 26 | use Spatie\MediaLibrary\MediaCollections\HtmlableMedia;
 27 | use Spatie\MediaLibrary\MediaCollections\Models\Collections\MediaCollection;
 28 | use Spatie\MediaLibrary\MediaCollections\Models\Concerns\CustomMediaProperties;
 29 | use Spatie\MediaLibrary\MediaCollections\Models\Concerns\HasUuid;
 30 | use Spatie\MediaLibrary\MediaCollections\Models\Concerns\IsSorted;
 31 | use Spatie\MediaLibrary\ResponsiveImages\RegisteredResponsiveImages;
 32 | use Spatie\MediaLibrary\Support\File;
 33 | use Spatie\MediaLibrary\Support\MediaLibraryPro;
 34 | use Spatie\MediaLibrary\Support\TemporaryDirectory;
 35 | use Spatie\MediaLibrary\Support\UrlGenerator\UrlGenerator;
 36 | use Spatie\MediaLibrary\Support\UrlGenerator\UrlGeneratorFactory;
 37 | use Spatie\MediaLibraryPro\Models\TemporaryUpload;
 38 | use Symfony\Component\HttpFoundation\StreamedResponse;
 39 | 
 40 | /**
 41 |  * @property string $uuid
 42 |  * @property string $model_type
 43 |  * @property string|int $model_id
 44 |  * @property string $collection_name
 45 |  * @property string $name
 46 |  * @property string $file_name
 47 |  * @property string $mime_type
 48 |  * @property string $disk
 49 |  * @property string $conversions_disk
 50 |  * @property string $type
 51 |  * @property string $extension
 52 |  * @property-read string $humanReadableSize
 53 |  * @property-read string $preview_url
 54 |  * @property-read string $original_url
 55 |  * @property int $size
 56 |  * @property ?int $order_column
 57 |  * @property array $manipulations
 58 |  * @property array $custom_properties
 59 |  * @property array $generated_conversions
 60 |  * @property array $responsive_images
 61 |  * @property-read ?\Illuminate\Support\Carbon $created_at
 62 |  * @property-read ?\Illuminate\Support\Carbon $updated_at
 63 |  */
 64 | class Media extends Model implements Attachable, Htmlable, Responsable
 65 | {
 66 |     use CustomMediaProperties;
 67 |     use HasUuid;
 68 |     use IsSorted;
 69 | 
 70 |     protected $table = 'media';
 71 | 
 72 |     public const TYPE_OTHER = 'other';
 73 | 
 74 |     protected $guarded = [];
 75 | 
 76 |     protected $appends = ['original_url', 'preview_url'];
 77 | 
 78 |     protected $casts = [
 79 |         'manipulations' => 'array',
 80 |         'custom_properties' => 'array',
 81 |         'generated_conversions' => 'array',
 82 |         'responsive_images' => 'array',
 83 |     ];
 84 | 
 85 |     protected int $streamChunkSize = (1024 * 1024); // default to 1MB chunks.
 86 | 
 87 |     public function newCollection(array $models = []): MediaCollection
 88 |     {
 89 |         return new MediaCollection($models);
 90 |     }
 91 | 
 92 |     public function model(): MorphTo
 93 |     {
 94 |         return $this->morphTo();
 95 |     }
 96 | 
 97 |     public function getFullUrl(string $conversionName = ''): string
 98 |     {
 99 |         return url($this->getUrl($conversionName));
100 |     }
101 | 
102 |     public function getUrl(string $conversionName = ''): string
103 |     {
104 |         $urlGenerator = UrlGeneratorFactory::createForMedia($this, $conversionName);
105 | 
106 |         return $urlGenerator->getUrl();
107 |     }
108 | 
109 |     public function getTemporaryUrl(DateTimeInterface $expiration, string $conversionName = '', array $options = []): string
110 |     {
111 |         $urlGenerator = $this->getUrlGenerator($conversionName);
112 | 
113 |         return $urlGenerator->getTemporaryUrl($expiration, $options);
114 |     }
115 | 
116 |     public function getPath(string $conversionName = ''): string
117 |     {
118 |         $urlGenerator = $this->getUrlGenerator($conversionName);
119 | 
120 |         return $urlGenerator->getPath();
121 |     }
122 | 
123 |     public function getPathRelativeToRoot(string $conversionName = ''): string
124 |     {
125 |         return $this->getUrlGenerator($conversionName)->getPathRelativeToRoot();
126 |     }
127 | 
128 |     public function getUrlGenerator(string $conversionName): UrlGenerator
129 |     {
130 |         return UrlGeneratorFactory::createForMedia($this, $conversionName);
131 |     }
132 | 
133 |     public function getAvailableUrl(array $conversionNames): string
134 |     {
135 |         foreach ($conversionNames as $conversionName) {
136 |             if (! $this->hasGeneratedConversion($conversionName)) {
137 |                 continue;
138 |             }
139 | 
140 |             return $this->getUrl($conversionName);
141 |         }
142 | 
143 |         return $this->getUrl();
144 |     }
145 | 
146 |     public function getDownloadFilename(): string
147 |     {
148 |         return $this->file_name;
149 |     }
150 | 
151 |     public function getAvailableFullUrl(array $conversionNames): string
152 |     {
153 |         foreach ($conversionNames as $conversionName) {
154 |             if (! $this->hasGeneratedConversion($conversionName)) {
155 |                 continue;
156 |             }
157 | 
158 |             return $this->getFullUrl($conversionName);
159 |         }
160 | 
161 |         return $this->getFullUrl();
162 |     }
163 | 
164 |     public function getAvailablePath(array $conversionNames): string
165 |     {
166 |         foreach ($conversionNames as $conversionName) {
167 |             if (! $this->hasGeneratedConversion($conversionName)) {
168 |                 continue;
169 |             }
170 | 
171 |             return $this->getPath($conversionName);
172 |         }
173 | 
174 |         return $this->getPath();
175 |     }
176 | 
177 |     protected function type(): Attribute
178 |     {
179 |         return Attribute::get(
180 |             function () {
181 |                 $type = $this->getTypeFromExtension();
182 | 
183 |                 if ($type !== self::TYPE_OTHER) {
184 |                     return $type;
185 |                 }
186 | 
187 |                 return $this->getTypeFromMime();
188 |             }
189 |         );
190 |     }
191 | 
192 |     public function getTypeFromExtension(): string
193 |     {
194 |         $imageGenerator = ImageGeneratorFactory::forExtension($this->extension);
195 | 
196 |         return $imageGenerator
197 |             ? $imageGenerator->getType()
198 |             : static::TYPE_OTHER;
199 |     }
200 | 
201 |     public function getTypeFromMime(): string
202 |     {
203 |         $imageGenerator = ImageGeneratorFactory::forMimeType($this->mime_type);
204 | 
205 |         return $imageGenerator
206 |             ? $imageGenerator->getType()
207 |             : static::TYPE_OTHER;
208 |     }
209 | 
210 |     protected function extension(): Attribute
211 |     {
212 |         return Attribute::get(fn () => pathinfo($this->file_name, PATHINFO_EXTENSION));
213 |     }
214 | 
215 |     protected function humanReadableSize(): Attribute
216 |     {
217 |         return Attribute::get(fn () => File::getHumanReadableSize($this->size));
218 |     }
219 | 
220 |     public function getDiskDriverName(): string
221 |     {
222 |         return strtolower(config("filesystems.disks.{$this->disk}.driver"));
223 |     }
224 | 
225 |     public function getConversionsDiskDriverName(): string
226 |     {
227 |         $diskName = $this->conversions_disk ?? $this->disk;
228 | 
229 |         return strtolower(config("filesystems.disks.{$diskName}.driver"));
230 |     }
231 | 
232 |     public function hasCustomProperty(string $propertyName): bool
233 |     {
234 |         return Arr::has($this->custom_properties, $propertyName);
235 |     }
236 | 
237 |     /**
238 |      * Get the value of custom property with the given name.
239 |      *
240 |      * @param  mixed  $default
241 |      */
242 |     public function getCustomProperty(string $propertyName, $default = null): mixed
243 |     {
244 |         return Arr::get($this->custom_properties, $propertyName, $default);
245 |     }
246 | 
247 |     /**
248 |      * @param  mixed  $value
249 |      * @return $this
250 |      */
251 |     public function setCustomProperty(string $name, $value): self
252 |     {
253 |         $customProperties = $this->custom_properties;
254 | 
255 |         Arr::set($customProperties, $name, $value);
256 | 
257 |         $this->custom_properties = $customProperties;
258 | 
259 |         return $this;
260 |     }
261 | 
262 |     /**
263 |      * @return $this
264 |      */
265 |     public function forgetCustomProperty(string $name): self
266 |     {
267 |         $customProperties = $this->custom_properties;
268 | 
269 |         Arr::forget($customProperties, $name);
270 | 
271 |         $this->custom_properties = $customProperties;
272 | 
273 |         return $this;
274 |     }
275 | 
276 |     public function getMediaConversionNames(): array
277 |     {
278 |         $conversions = ConversionCollection::createForMedia($this);
279 | 
280 |         return $conversions->map(fn (Conversion $conversion) => $conversion->getName())->toArray();
281 |     }
282 | 
283 |     public function getGeneratedConversions(): Collection
284 |     {
285 |         return collect($this->generated_conversions ?? []);
286 |     }
287 | 
288 |     /**
289 |      * @return $this
290 |      */
291 |     public function markAsConversionGenerated(string $conversionName): self
292 |     {
293 |         $generatedConversions = $this->generated_conversions;
294 | 
295 |         Arr::set($generatedConversions, $conversionName, true);
296 | 
297 |         $this->generated_conversions = $generatedConversions;
298 | 
299 |         $this->saveOrTouch();
300 | 
301 |         return $this;
302 |     }
303 | 
304 |     /**
305 |      * @return $this
306 |      */
307 |     public function markAsConversionNotGenerated(string $conversionName): self
308 |     {
309 |         $generatedConversions = $this->generated_conversions;
310 | 
311 |         Arr::set($generatedConversions, $conversionName, false);
312 | 
313 |         $this->generated_conversions = $generatedConversions;
314 | 
315 |         $this->saveOrTouch();
316 | 
317 |         return $this;
318 |     }
319 | 
320 |     public function hasGeneratedConversion(string $conversionName): bool
321 |     {
322 |         $generatedConversions = $this->generated_conversions;
323 | 
324 |         return Arr::get($generatedConversions, $conversionName, false);
325 |     }
326 | 
327 |     /**
328 |      * @return $this
329 |      */
330 |     public function setStreamChunkSize(int $chunkSize): self
331 |     {
332 |         $this->streamChunkSize = $chunkSize;
333 | 
334 |         return $this;
335 |     }
336 | 
337 |     public function toResponse($request): StreamedResponse
338 |     {
339 |         return $this->buildResponse($request, 'attachment');
340 |     }
341 | 
342 |     public function toInlineResponse($request): StreamedResponse
343 |     {
344 |         return $this->buildResponse($request, 'inline');
345 |     }
346 | 
347 |     private function buildResponse($request, string $contentDispositionType): StreamedResponse
348 |     {
349 |         $filename = str_replace('"', '\'', Str::ascii($this->getDownloadFilename()));
350 | 
351 |         $downloadHeaders = [
352 |             'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
353 |             'Content-Type' => $this->mime_type,
354 |             'Content-Length' => $this->size,
355 |             'Content-Disposition' => $contentDispositionType.'; filename="'.$filename.'"',
356 |             'Pragma' => 'public',
357 |         ];
358 | 
359 |         return response()->stream(function () {
360 |             $stream = $this->stream();
361 | 
362 |             while (! feof($stream)) {
363 |                 echo fread($stream, $this->streamChunkSize);
364 |                 flush();
365 |             }
366 | 
367 |             if (is_resource($stream)) {
368 |                 fclose($stream);
369 |             }
370 |         }, 200, $downloadHeaders);
371 |     }
372 | 
373 |     public function getResponsiveImageUrls(string $conversionName = ''): array
374 |     {
375 |         return $this->responsiveImages($conversionName)->getUrls();
376 |     }
377 | 
378 |     public function hasResponsiveImages(string $conversionName = ''): bool
379 |     {
380 |         return count($this->getResponsiveImageUrls($conversionName)) > 0;
381 |     }
382 | 
383 |     public function getSrcset(string $conversionName = ''): string
384 |     {
385 |         return $this->responsiveImages($conversionName)->getSrcset();
386 |     }
387 | 
388 |     protected function previewUrl(): Attribute
389 |     {
390 |         return Attribute::get(
391 |             fn () => $this->hasGeneratedConversion('preview') ? $this->getUrl('preview') : '',
392 |         );
393 |     }
394 | 
395 |     protected function originalUrl(): Attribute
396 |     {
397 |         return Attribute::get(fn () => $this->getUrl());
398 |     }
399 | 
400 |     /** @param  string  $collectionName */
401 |     public function move(HasMedia $model, $collectionName = 'default', string $diskName = '', string $fileName = ''): self
402 |     {
403 |         $newMedia = $this->copy($model, $collectionName, $diskName, $fileName);
404 | 
405 |         $this->forceDelete();
406 | 
407 |         return $newMedia;
408 |     }
409 | 
410 |     /**
411 |      * @param  null|Closure(FileAdder): FileAdder  $fileAdderCallback
412 |      */
413 |     public function copy(
414 |         HasMedia $model,
415 |         string $collectionName = 'default',
416 |         string $diskName = '',
417 |         string $fileName = '',
418 |         ?Closure $fileAdderCallback = null
419 |     ): self {
420 |         $temporaryDirectory = TemporaryDirectory::create();
421 | 
422 |         $temporaryFile = $temporaryDirectory->path('/').DIRECTORY_SEPARATOR.$this->file_name;
423 | 
424 |         /** @var Filesystem $filesystem */
425 |         $filesystem = app(Filesystem::class);
426 | 
427 |         $filesystem->copyFromMediaLibrary($this, $temporaryFile);
428 | 
429 |         $fileAdder = $model
430 |             ->addMedia($temporaryFile)
431 |             ->usingName($this->name)
432 |             ->setOrder($this->order_column)
433 |             ->withManipulations($this->manipulations)
434 |             ->withCustomProperties($this->custom_properties);
435 | 
436 |         if ($fileName !== '') {
437 |             $fileAdder->usingFileName($fileName);
438 |         }
439 | 
440 |         if ($fileAdderCallback instanceof Closure) {
441 |             $fileAdder = $fileAdderCallback($fileAdder);
442 |         }
443 | 
444 |         $newMedia = $fileAdder->toMediaCollection($collectionName, $diskName);
445 | 
446 |         $temporaryDirectory->delete();
447 | 
448 |         return $newMedia;
449 |     }
450 | 
451 |     public function responsiveImages(string $conversionName = ''): RegisteredResponsiveImages
452 |     {
453 |         return new RegisteredResponsiveImages($this, $conversionName);
454 |     }
455 | 
456 |     public function stream()
457 |     {
458 |         /** @var Filesystem $filesystem */
459 |         $filesystem = app(Filesystem::class);
460 | 
461 |         return $filesystem->getStream($this);
462 |     }
463 | 
464 |     public function toHtml(): string
465 |     {
466 |         return $this->img()->toHtml();
467 |     }
468 | 
469 |     public function img(string $conversionName = '', $extraAttributes = []): HtmlableMedia
470 |     {
471 |         return (new HtmlableMedia($this))
472 |             ->conversion($conversionName)
473 |             ->attributes($extraAttributes);
474 |     }
475 | 
476 |     public function __invoke(...$arguments): HtmlableMedia
477 |     {
478 |         return $this->img(...$arguments);
479 |     }
480 | 
481 |     public function temporaryUpload(): BelongsTo
482 |     {
483 |         MediaLibraryPro::ensureInstalled();
484 | 
485 |         /** @var class-string<TemporaryUpload> $temporaryUploadModelClass */
486 |         $temporaryUploadModelClass = config('media-library.temporary_upload_model');
487 | 
488 |         return $this->belongsTo($temporaryUploadModelClass);
489 |     }
490 | 
491 |     public static function findWithTemporaryUploadInCurrentSession(array $uuids): EloquentCollection
492 |     {
493 |         MediaLibraryPro::ensureInstalled();
494 | 
495 |         /** @var class-string<TemporaryUpload> $temporaryUploadModelClass */
496 |         $temporaryUploadModelClass = config('media-library.temporary_upload_model');
497 | 
498 |         return static::query()
499 |             ->whereIn('uuid', $uuids)
500 |             ->whereHasMorph(
501 |                 'model',
502 |                 [$temporaryUploadModelClass],
503 |                 fn (Builder $builder) => $builder->where('session_id', session()->getId())
504 |             )
505 |             ->get();
506 |     }
507 | 
508 |     public function mailAttachment(string $conversion = ''): Attachment
509 |     {
510 |         $attachment = Attachment::fromStorageDisk($this->disk, $this->getPathRelativeToRoot($conversion))->as($this->file_name);
511 | 
512 |         if ($this->mime_type) {
513 |             $attachment->withMime($this->mime_type);
514 |         }
515 | 
516 |         return $attachment;
517 |     }
518 | 
519 |     public function toMailAttachment(): Attachment
520 |     {
521 |         return $this->mailAttachment();
522 |     }
523 | 
524 |     protected function saveOrTouch(): bool
525 |     {
526 |         if (! $this->exists || $this->isDirty()) {
527 |             return $this->save();
528 |         }
529 | 
530 |         return $this->touch();
531 |     }
532 | }
533 | 


--------------------------------------------------------------------------------
/src/MediaCollections/Models/Observers/MediaObserver.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\MediaCollections\Models\Observers;
 4 | 
 5 | use Spatie\MediaLibrary\Conversions\FileManipulator;
 6 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | 
 9 | class MediaObserver
10 | {
11 |     public function creating(Media $media): void
12 |     {
13 |         if ($media->shouldSortWhenCreating()) {
14 |             if (is_null($media->order_column)) {
15 |                 $media->setHighestOrderNumber();
16 |             }
17 |         }
18 |     }
19 | 
20 |     public function updating(Media $media): void
21 |     {
22 |         /** @var Filesystem $filesystem */
23 |         $filesystem = app(Filesystem::class);
24 | 
25 |         if (config('media-library.moves_media_on_update')) {
26 |             $filesystem->syncMediaPath($media);
27 |         }
28 | 
29 |         if ($media->file_name !== $media->getOriginal('file_name')) {
30 |             $filesystem->syncFileNames($media);
31 |         }
32 |     }
33 | 
34 |     public function updated(Media $media): void
35 |     {
36 |         if (is_null($media->getOriginal('model_id'))) {
37 |             return;
38 |         }
39 | 
40 |         $original = $media->getOriginal('manipulations');
41 | 
42 |         if ($media->manipulations !== $original) {
43 |             $eventDispatcher = Media::getEventDispatcher();
44 |             Media::unsetEventDispatcher();
45 | 
46 |             /** @var FileManipulator $fileManipulator */
47 |             $fileManipulator = app(FileManipulator::class);
48 | 
49 |             $fileManipulator->createDerivedFiles($media);
50 | 
51 |             Media::setEventDispatcher($eventDispatcher);
52 |         }
53 |     }
54 | 
55 |     public function deleted(Media $media): void
56 |     {
57 |         if (method_exists($media, 'isForceDeleting') && ! $media->isForceDeleting()) {
58 |             return;
59 |         }
60 | 
61 |         /** @var Filesystem $filesystem */
62 |         $filesystem = app(Filesystem::class);
63 | 
64 |         $filesystem->removeAllFiles($media);
65 |     }
66 | }
67 | 


--------------------------------------------------------------------------------
/src/MediaLibraryServiceProvider.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary;
 4 | 
 5 | use Spatie\LaravelPackageTools\Package;
 6 | use Spatie\LaravelPackageTools\PackageServiceProvider;
 7 | use Spatie\MediaLibrary\Conversions\Commands\RegenerateCommand;
 8 | use Spatie\MediaLibrary\MediaCollections\Commands\CleanCommand;
 9 | use Spatie\MediaLibrary\MediaCollections\Commands\ClearCommand;
10 | use Spatie\MediaLibrary\MediaCollections\MediaRepository;
11 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
12 | use Spatie\MediaLibrary\MediaCollections\Models\Observers\MediaObserver;
13 | use Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\TinyPlaceholderGenerator;
14 | use Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\WidthCalculator;
15 | 
16 | class MediaLibraryServiceProvider extends PackageServiceProvider
17 | {
18 |     public function configurePackage(Package $package): void
19 |     {
20 |         $package
21 |             ->name('laravel-medialibrary')
22 |             ->hasConfigFile('media-library')
23 |             ->hasMigration('create_media_table')
24 |             ->hasViews('media-library')
25 |             ->hasCommands([
26 |                 RegenerateCommand::class,
27 |                 ClearCommand::class,
28 |                 CleanCommand::class,
29 |             ]);
30 |     }
31 | 
32 |     public function packageBooted(): void
33 |     {
34 |         $mediaClass = config('media-library.media_model', Media::class);
35 |         $mediaObserverClass = config('media-library.media_observer', MediaObserver::class);
36 | 
37 |         $mediaClass::observe(new $mediaObserverClass);
38 |     }
39 | 
40 |     public function packageRegistered(): void
41 |     {
42 |         $this->app->bind(WidthCalculator::class, config('media-library.responsive_images.width_calculator'));
43 |         $this->app->bind(TinyPlaceholderGenerator::class, config('media-library.responsive_images.tiny_placeholder_generator'));
44 | 
45 |         $this->app->scoped(MediaRepository::class, function () {
46 |             $mediaClass = config('media-library.media_model');
47 | 
48 |             return new MediaRepository(new $mediaClass);
49 |         });
50 |     }
51 | }
52 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/Events/ResponsiveImagesGeneratedEvent.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages\Events;
 4 | 
 5 | use Illuminate\Queue\SerializesModels;
 6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 7 | 
 8 | class ResponsiveImagesGeneratedEvent
 9 | {
10 |     use SerializesModels;
11 | 
12 |     public function __construct(public Media $media) {}
13 | }
14 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/Exceptions/InvalidTinyJpg.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages\Exceptions;
 4 | 
 5 | use Exception;
 6 | use Spatie\MediaLibrary\Support\File;
 7 | 
 8 | class InvalidTinyJpg extends Exception
 9 | {
10 |     public static function doesNotExist(string $tinyImageDestinationPath): self
11 |     {
12 |         return new static("The expected tiny jpg at `{$tinyImageDestinationPath}` does not exist");
13 |     }
14 | 
15 |     public static function hasWrongMimeType(string $tinyImageDestinationPath): self
16 |     {
17 |         $foundMimeType = File::getMimeType($tinyImageDestinationPath);
18 | 
19 |         return new static("Expected the file at {$tinyImageDestinationPath} have mimetype `image/jpeg`, but found a file with mimetype `{$foundMimeType}`");
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/Jobs/GenerateResponsiveImagesJob.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages\Jobs;
 4 | 
 5 | use Illuminate\Bus\Queueable;
 6 | use Illuminate\Contracts\Queue\ShouldQueue;
 7 | use Illuminate\Queue\InteractsWithQueue;
 8 | use Illuminate\Queue\SerializesModels;
 9 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
10 | use Spatie\MediaLibrary\ResponsiveImages\ResponsiveImageGenerator;
11 | 
12 | class GenerateResponsiveImagesJob implements ShouldQueue
13 | {
14 |     use InteractsWithQueue;
15 |     use Queueable;
16 |     use SerializesModels;
17 | 
18 |     public function __construct(protected Media $media) {}
19 | 
20 |     public function handle(): bool
21 |     {
22 |         /** @var ResponsiveImageGenerator $responsiveImageGenerator */
23 |         $responsiveImageGenerator = app(ResponsiveImageGenerator::class);
24 | 
25 |         $responsiveImageGenerator->generateResponsiveImages($this->media);
26 | 
27 |         return true;
28 |     }
29 | }
30 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/RegisteredResponsiveImages.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 7 | 
 8 | class RegisteredResponsiveImages
 9 | {
10 |     public Collection $files;
11 | 
12 |     public string $generatedFor;
13 | 
14 |     public function __construct(protected Media $media, string $conversionName = '')
15 |     {
16 |         $this->generatedFor = $conversionName === ''
17 |             ? 'media_library_original'
18 |             : $conversionName;
19 | 
20 |         $this->files = collect($media->responsive_images[$this->generatedFor]['urls'] ?? [])
21 |             ->map(fn (string $fileName) => new ResponsiveImage($fileName, $media))
22 |             ->filter(fn (ResponsiveImage $responsiveImage) => $responsiveImage->generatedFor() === $this->generatedFor);
23 |     }
24 | 
25 |     public function getUrls(): array
26 |     {
27 |         return $this->files
28 |             ->map(fn (ResponsiveImage $responsiveImage) => $responsiveImage->url())
29 |             ->values()
30 |             ->toArray();
31 |     }
32 | 
33 |     public function getFilenames(): array
34 |     {
35 |         return $this->files->pluck('fileName')->toArray();
36 |     }
37 | 
38 |     public function getSrcset(): string
39 |     {
40 |         $filesSrcset = $this->files
41 |             ->map(fn (ResponsiveImage $responsiveImage) => "{$responsiveImage->url()} {$responsiveImage->width()}w")
42 |             ->implode(', ');
43 | 
44 |         $shouldAddPlaceholderSvg = config('media-library.responsive_images.use_tiny_placeholders')
45 |             && $this->getPlaceholderSvg();
46 | 
47 |         if ($shouldAddPlaceholderSvg) {
48 |             $filesSrcset .= ', '.$this->getPlaceholderSvg().' 32w';
49 |         }
50 | 
51 |         return $filesSrcset;
52 |     }
53 | 
54 |     public function getPlaceholderSvg(): ?string
55 |     {
56 |         return $this->media->responsive_images[$this->generatedFor]['base64svg'] ?? null;
57 |     }
58 | 
59 |     public function delete(): void
60 |     {
61 |         $this->files->each->delete();
62 | 
63 |         $responsiveImages = $this->media->responsive_images;
64 | 
65 |         unset($responsiveImages[$this->generatedFor]);
66 | 
67 |         $this->media->responsive_images = $responsiveImages;
68 | 
69 |         $this->media->save();
70 |     }
71 | }
72 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/ResponsiveImage.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\ResponsiveImages;
  4 | 
  5 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
  6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
  7 | use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory;
  8 | use Spatie\MediaLibrary\Support\UrlGenerator\UrlGeneratorFactory;
  9 | 
 10 | class ResponsiveImage
 11 | {
 12 |     public static function register(Media $media, $fileName, $conversionName): void
 13 |     {
 14 |         $responsiveImages = $media->responsive_images;
 15 | 
 16 |         $responsiveImages[$conversionName]['urls'][] = $fileName;
 17 | 
 18 |         $media->responsive_images = $responsiveImages;
 19 | 
 20 |         $media->save();
 21 |     }
 22 | 
 23 |     public static function registerTinySvg(Media $media, string $base64Svg, string $conversionName): void
 24 |     {
 25 |         $responsiveImages = $media->responsive_images;
 26 | 
 27 |         $responsiveImages[$conversionName]['base64svg'] = $base64Svg;
 28 | 
 29 |         $media->responsive_images = $responsiveImages;
 30 | 
 31 |         $media->save();
 32 |     }
 33 | 
 34 |     public function __construct(public string $fileName, public Media $media) {}
 35 | 
 36 |     public function url(): string
 37 |     {
 38 |         $conversionName = '';
 39 | 
 40 |         if ($this->generatedFor() !== 'media_library_original') {
 41 |             $conversionName = $this->generatedFor();
 42 |         }
 43 | 
 44 |         $urlGenerator = UrlGeneratorFactory::createForMedia($this->media, $conversionName);
 45 | 
 46 |         $url = $urlGenerator->getResponsiveImagesDirectoryUrl().rawurlencode($this->fileName);
 47 | 
 48 |         if (config('media-library.version_urls') === true) {
 49 |             $url = "{$url}?v={$this->media->updated_at->timestamp}";
 50 |         }
 51 | 
 52 |         return $url;
 53 |     }
 54 | 
 55 |     public function generatedFor(): string
 56 |     {
 57 |         $propertyParts = $this->getPropertyParts();
 58 | 
 59 |         array_pop($propertyParts);
 60 | 
 61 |         array_pop($propertyParts);
 62 | 
 63 |         return implode('_', $propertyParts);
 64 |     }
 65 | 
 66 |     public function width(): int
 67 |     {
 68 |         $propertyParts = $this->getPropertyParts();
 69 | 
 70 |         array_pop($propertyParts);
 71 | 
 72 |         return (int) last($propertyParts);
 73 |     }
 74 | 
 75 |     public function height(): int
 76 |     {
 77 |         $propertyParts = $this->getPropertyParts();
 78 | 
 79 |         return (int) last($propertyParts);
 80 |     }
 81 | 
 82 |     protected function getPropertyParts(): array
 83 |     {
 84 |         $propertyString = $this->stringBetween($this->fileName, '___', '.');
 85 | 
 86 |         return explode('_', $propertyString);
 87 |     }
 88 | 
 89 |     protected function stringBetween(string $subject, string $startCharacter, string $endCharacter): string
 90 |     {
 91 |         $lastPos = strrpos($subject, $startCharacter);
 92 | 
 93 |         $between = substr($subject, $lastPos);
 94 | 
 95 |         $between = str_replace('___', '', $between);
 96 | 
 97 |         $between = strstr($between, $endCharacter, true);
 98 | 
 99 |         return $between;
100 |     }
101 | 
102 |     /**
103 |      * @return $this
104 |      */
105 |     public function delete(): self
106 |     {
107 |         $pathGenerator = PathGeneratorFactory::create($this->media);
108 | 
109 |         $path = $pathGenerator->getPathForResponsiveImages($this->media);
110 | 
111 |         $fullPath = $path.$this->fileName;
112 | 
113 |         app(Filesystem::class)->removeFile($this->media, $fullPath);
114 | 
115 |         $responsiveImages = $this->media->responsive_images;
116 | 
117 |         unset($responsiveImages[$this->generatedFor()]);
118 | 
119 |         $this->media->responsive_images = $responsiveImages;
120 | 
121 |         $this->media->save();
122 | 
123 |         return $this;
124 |     }
125 | }
126 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/ResponsiveImageGenerator.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\ResponsiveImages;
  4 | 
  5 | use Illuminate\Support\Str;
  6 | use Spatie\MediaLibrary\Conversions\Conversion;
  7 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
  8 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
  9 | use Spatie\MediaLibrary\ResponsiveImages\Events\ResponsiveImagesGeneratedEvent;
 10 | use Spatie\MediaLibrary\ResponsiveImages\Exceptions\InvalidTinyJpg;
 11 | use Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\TinyPlaceholderGenerator;
 12 | use Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\WidthCalculator;
 13 | use Spatie\MediaLibrary\Support\File;
 14 | use Spatie\MediaLibrary\Support\FileNamer\FileNamer;
 15 | use Spatie\MediaLibrary\Support\ImageFactory;
 16 | use Spatie\MediaLibrary\Support\TemporaryDirectory;
 17 | use Spatie\TemporaryDirectory\TemporaryDirectory as BaseTemporaryDirectory;
 18 | 
 19 | class ResponsiveImageGenerator
 20 | {
 21 |     protected const DEFAULT_CONVERSION_QUALITY = 90;
 22 | 
 23 |     protected FileNamer $fileNamer;
 24 | 
 25 |     public function __construct(
 26 |         protected Filesystem $filesystem,
 27 |         protected WidthCalculator $widthCalculator,
 28 |         protected TinyPlaceholderGenerator $tinyPlaceholderGenerator
 29 |     ) {
 30 |         $this->fileNamer = app(config('media-library.file_namer'));
 31 |     }
 32 | 
 33 |     public function generateResponsiveImages(Media $media): void
 34 |     {
 35 |         $temporaryDirectory = TemporaryDirectory::create();
 36 | 
 37 |         $baseImage = app(Filesystem::class)->copyFromMediaLibrary(
 38 |             $media,
 39 |             $temporaryDirectory->path(Str::random(16).'.'.$media->extension)
 40 |         );
 41 | 
 42 |         $media = $this->cleanResponsiveImages($media);
 43 | 
 44 |         foreach ($this->widthCalculator->calculateWidthsFromFile($baseImage) as $width) {
 45 |             $this->generateResponsiveImage($media, $baseImage, 'media_library_original', $width, $temporaryDirectory);
 46 |         }
 47 | 
 48 |         event(new ResponsiveImagesGeneratedEvent($media));
 49 | 
 50 |         $this->generateTinyJpg($media, $baseImage, 'media_library_original', $temporaryDirectory);
 51 | 
 52 |         $temporaryDirectory->delete();
 53 |     }
 54 | 
 55 |     public function generateResponsiveImagesForConversion(Media $media, Conversion $conversion, string $baseImage): void
 56 |     {
 57 |         $temporaryDirectory = TemporaryDirectory::create();
 58 | 
 59 |         $media = $this->cleanResponsiveImages($media, $conversion->getName());
 60 | 
 61 |         $widthCalculator = $conversion->getWidthCalculator() ?? $this->widthCalculator;
 62 | 
 63 |         foreach ($widthCalculator->calculateWidthsFromFile($baseImage) as $width) {
 64 |             $this->generateResponsiveImage($media, $baseImage, $conversion->getName(), $width, $temporaryDirectory, $this->getConversionQuality($conversion));
 65 |         }
 66 | 
 67 |         $this->generateTinyJpg($media, $baseImage, $conversion->getName(), $temporaryDirectory);
 68 | 
 69 |         $temporaryDirectory->delete();
 70 |     }
 71 | 
 72 |     private function getConversionQuality(Conversion $conversion): int
 73 |     {
 74 |         return $conversion->getManipulations()->getFirstManipulationArgument('quality') ?: self::DEFAULT_CONVERSION_QUALITY;
 75 |     }
 76 | 
 77 |     public function generateResponsiveImage(
 78 |         Media $media,
 79 |         string $baseImage,
 80 |         string $conversionName,
 81 |         int $targetWidth,
 82 |         BaseTemporaryDirectory $temporaryDirectory,
 83 |         int $conversionQuality = self::DEFAULT_CONVERSION_QUALITY
 84 |     ): void {
 85 |         $extension = $this->fileNamer->extensionFromBaseImage($baseImage);
 86 |         $responsiveImagePath = $this->fileNamer->temporaryFileName($media, $extension);
 87 | 
 88 |         $tempDestination = $temporaryDirectory->path($responsiveImagePath);
 89 | 
 90 |         ImageFactory::load($baseImage)
 91 |             ->optimize()
 92 |             ->width($targetWidth)
 93 |             ->quality($conversionQuality)
 94 |             ->save($tempDestination);
 95 | 
 96 |         $responsiveImageHeight = ImageFactory::load($tempDestination)->getHeight();
 97 | 
 98 |         // Users can customize the name like they want, but we expect the last part in a certain format
 99 |         $fileName = $this->addPropertiesToFileName(
100 |             $responsiveImagePath,
101 |             $conversionName,
102 |             $targetWidth,
103 |             $responsiveImageHeight,
104 |             $extension
105 |         );
106 | 
107 |         $responsiveImagePath = $temporaryDirectory->path($fileName);
108 | 
109 |         rename($tempDestination, $responsiveImagePath);
110 | 
111 |         $this->filesystem->copyToMediaLibrary($responsiveImagePath, $media, 'responsiveImages');
112 | 
113 |         ResponsiveImage::register($media, $fileName, $conversionName);
114 |     }
115 | 
116 |     public function generateTinyJpg(
117 |         Media $media,
118 |         string $originalImagePath,
119 |         string $conversionName,
120 |         BaseTemporaryDirectory $temporaryDirectory
121 |     ): void {
122 |         if (! config('media-library.responsive_images.use_tiny_placeholders')) {
123 |             return;
124 |         }
125 | 
126 |         $tempDestination = $temporaryDirectory->path('tiny.jpg');
127 | 
128 |         $this->tinyPlaceholderGenerator->generateTinyPlaceholder($originalImagePath, $tempDestination);
129 | 
130 |         $this->guardAgainstInvalidTinyPlaceHolder($tempDestination);
131 | 
132 |         $tinyImageDataBase64 = base64_encode(file_get_contents($tempDestination));
133 | 
134 |         $tinyImageBase64 = 'data:image/jpeg;base64,'.$tinyImageDataBase64;
135 | 
136 |         $originalImage = ImageFactory::load($originalImagePath);
137 | 
138 |         $originalImageWidth = $originalImage->getWidth();
139 | 
140 |         $originalImageHeight = $originalImage->getHeight();
141 | 
142 |         $svg = view('media-library::placeholderSvg', compact(
143 |             'originalImageWidth',
144 |             'originalImageHeight',
145 |             'tinyImageBase64'
146 |         ));
147 | 
148 |         $base64Svg = 'data:image/svg+xml;base64,'.base64_encode($svg);
149 | 
150 |         ResponsiveImage::registerTinySvg($media, $base64Svg, $conversionName);
151 |     }
152 | 
153 |     protected function appendToFileName(string $filePath, string $suffix, ?string $extensionFilePath = null): string
154 |     {
155 |         $baseName = pathinfo($filePath, PATHINFO_FILENAME);
156 | 
157 |         $extension = pathinfo($extensionFilePath ?? $filePath, PATHINFO_EXTENSION);
158 | 
159 |         return "{$baseName}{$suffix}.{$extension}";
160 |     }
161 | 
162 |     protected function guardAgainstInvalidTinyPlaceHolder(string $tinyPlaceholderPath): void
163 |     {
164 |         if (! file_exists($tinyPlaceholderPath)) {
165 |             throw InvalidTinyJpg::doesNotExist($tinyPlaceholderPath);
166 |         }
167 | 
168 |         if (File::getMimeType($tinyPlaceholderPath) !== 'image/jpeg') {
169 |             throw InvalidTinyJpg::hasWrongMimeType($tinyPlaceholderPath);
170 |         }
171 |     }
172 | 
173 |     protected function cleanResponsiveImages(Media $media, string $conversionName = 'media_library_original'): Media
174 |     {
175 |         $responsiveImages = $media->responsive_images;
176 |         $responsiveImages[$conversionName]['urls'] = [];
177 |         $media->responsive_images = $responsiveImages;
178 | 
179 |         $this->filesystem->removeResponsiveImages($media, $conversionName);
180 | 
181 |         return $media;
182 |     }
183 | 
184 |     protected function addPropertiesToFileName(string $fileName, string $conversionName, int $width, int $height, string $extension): string
185 |     {
186 |         $fileName = pathinfo($fileName, PATHINFO_FILENAME);
187 | 
188 |         return "{$fileName}___{$conversionName}_{$width}_{$height}.{$extension}";
189 |     }
190 | }
191 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/TinyPlaceholderGenerator/Blurred.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator;
 4 | 
 5 | use Spatie\MediaLibrary\Support\ImageFactory;
 6 | 
 7 | class Blurred implements TinyPlaceholderGenerator
 8 | {
 9 |     public function generateTinyPlaceholder(string $sourceImagePath, string $tinyImageDestinationPath): void
10 |     {
11 |         $sourceImage = ImageFactory::load($sourceImagePath);
12 | 
13 |         $sourceImage->width(32)->blur(5)->save($tinyImageDestinationPath);
14 |     }
15 | }
16 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/TinyPlaceholderGenerator/TinyPlaceholderGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator;
 4 | 
 5 | interface TinyPlaceholderGenerator
 6 | {
 7 |     /*
 8 |      * This function should generate a tiny jpg representation of the image
 9 |      * given in $sourceImage. The tiny jpg should be saved at $tinyImageDestination.
10 |      */
11 |     public function generateTinyPlaceholder(string $sourceImage, string $tinyImageDestination): void;
12 | }
13 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/WidthCalculator/FileSizeOptimizedWidthCalculator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages\WidthCalculator;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | use Spatie\MediaLibrary\Support\ImageFactory;
 7 | 
 8 | class FileSizeOptimizedWidthCalculator implements WidthCalculator
 9 | {
10 |     public function calculateWidthsFromFile(string $imagePath): Collection
11 |     {
12 |         $image = ImageFactory::load($imagePath);
13 | 
14 |         $width = $image->getWidth();
15 |         $height = $image->getHeight();
16 |         $fileSize = filesize($imagePath);
17 | 
18 |         return $this->calculateWidths($fileSize, $width, $height);
19 |     }
20 | 
21 |     public function calculateWidths(int $fileSize, int $width, int $height): Collection
22 |     {
23 |         $targetWidths = collect();
24 | 
25 |         $targetWidths->push($width);
26 | 
27 |         $ratio = $height / $width;
28 |         $area = $height * $width;
29 | 
30 |         $predictedFileSize = $fileSize;
31 |         $pixelPrice = $predictedFileSize / $area;
32 | 
33 |         while (true) {
34 |             $predictedFileSize *= 0.7;
35 | 
36 |             $newWidth = (int) floor(sqrt(($predictedFileSize / $pixelPrice) / $ratio));
37 | 
38 |             if ($this->finishedCalculating((int) $predictedFileSize, $newWidth)) {
39 |                 return $targetWidths;
40 |             }
41 | 
42 |             $targetWidths->push($newWidth);
43 |         }
44 |     }
45 | 
46 |     protected function finishedCalculating(int $predictedFileSize, int $newWidth): bool
47 |     {
48 |         if ($newWidth < 20) {
49 |             return true;
50 |         }
51 | 
52 |         if ($predictedFileSize < (1024 * 10)) {
53 |             return true;
54 |         }
55 | 
56 |         return false;
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/src/ResponsiveImages/WidthCalculator/WidthCalculator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\ResponsiveImages\WidthCalculator;
 4 | 
 5 | use Illuminate\Support\Collection;
 6 | 
 7 | interface WidthCalculator
 8 | {
 9 |     public function calculateWidthsFromFile(string $imagePath): Collection;
10 | 
11 |     public function calculateWidths(int $fileSize, int $width, int $height): Collection;
12 | }
13 | 


--------------------------------------------------------------------------------
/src/Support/Factories/TemporaryUploadFactory.php:
--------------------------------------------------------------------------------
1 | <?php
2 | 
3 | namespace Spatie\MediaLibrary\Support\Factories;
4 | 
5 | class TemporaryUploadFactory {}
6 | 


--------------------------------------------------------------------------------
/src/Support/File.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support;
 4 | 
 5 | use Symfony\Component\Mime\MimeTypes;
 6 | 
 7 | class File
 8 | {
 9 |     public static function getHumanReadableSize(int|float $sizeInBytes): string
10 |     {
11 |         $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
12 | 
13 |         // 0 === 0.00 return false so use double equals
14 |         if ($sizeInBytes == 0) {
15 |             return '0 '.$units[0];
16 |         }
17 | 
18 |         $index = min(count($units) - 1, floor(log(abs($sizeInBytes), 1024)));
19 | 
20 |         return sprintf('%s %s', round(num: abs($sizeInBytes) / (1024 ** $index), precision: 2), $units[$index]);
21 |     }
22 | 
23 |     public static function getMimeType(string $path): string
24 |     {
25 |         return (string) MimeTypes::getDefault()->guessMimeType($path);
26 |     }
27 | }
28 | 


--------------------------------------------------------------------------------
/src/Support/FileNamer/DefaultFileNamer.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\FileNamer;
 4 | 
 5 | use Spatie\MediaLibrary\Conversions\Conversion;
 6 | 
 7 | class DefaultFileNamer extends FileNamer
 8 | {
 9 |     public function conversionFileName(string $fileName, Conversion $conversion): string
10 |     {
11 |         $strippedFileName = pathinfo($fileName, PATHINFO_FILENAME);
12 | 
13 |         return "{$strippedFileName}-{$conversion->getName()}";
14 |     }
15 | 
16 |     public function responsiveFileName(string $fileName): string
17 |     {
18 |         return pathinfo($fileName, PATHINFO_FILENAME);
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/src/Support/FileNamer/FileNamer.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\FileNamer;
 4 | 
 5 | use Spatie\MediaLibrary\Conversions\Conversion;
 6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 7 | 
 8 | abstract class FileNamer
 9 | {
10 |     public function originalFileName(string $fileName): string
11 |     {
12 |         $extLength = strlen(pathinfo($fileName, PATHINFO_EXTENSION));
13 | 
14 |         $baseName = substr($fileName, 0, strlen($fileName) - ($extLength ? $extLength + 1 : 0));
15 | 
16 |         return $baseName;
17 |     }
18 | 
19 |     abstract public function conversionFileName(string $fileName, Conversion $conversion): string;
20 | 
21 |     abstract public function responsiveFileName(string $fileName): string;
22 | 
23 |     public function temporaryFileName(Media $media, string $extension): string
24 |     {
25 |         return "{$this->responsiveFileName($media->file_name)}.{$extension}";
26 |     }
27 | 
28 |     public function extensionFromBaseImage(string $baseImage): string
29 |     {
30 |         return pathinfo($baseImage, PATHINFO_EXTENSION);
31 |     }
32 | }
33 | 


--------------------------------------------------------------------------------
/src/Support/FileRemover/DefaultFileRemover.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\Support\FileRemover;
  4 | 
  5 | use Exception;
  6 | use Illuminate\Contracts\Filesystem\Factory;
  7 | use Illuminate\Support\Str;
  8 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
  9 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 10 | use Spatie\MediaLibrary\Support\FileNamer\FileNamer;
 11 | use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory;
 12 | 
 13 | class DefaultFileRemover implements FileRemover
 14 | {
 15 |     public function __construct(protected Filesystem $mediaFileSystem, protected Factory $filesystem) {}
 16 | 
 17 |     public function removeAllFiles(Media $media): void
 18 |     {
 19 | 
 20 |         if ($media->conversions_disk && $media->disk !== $media->conversions_disk) {
 21 |             $this->removeFromConversionsDirectory($media, $media->conversions_disk);
 22 |             $this->removeFromResponsiveImagesDirectory($media, $media->conversions_disk);
 23 |             $this->removeFromMediaDirectory($media, $media->conversions_disk);
 24 |         }
 25 | 
 26 |         $this->removeFromConversionsDirectory($media, $media->disk);
 27 |         $this->removeFromResponsiveImagesDirectory($media, $media->disk);
 28 |         $this->removeFromMediaDirectory($media, $media->disk);
 29 |     }
 30 | 
 31 |     public function removeFromMediaDirectory(Media $media, string $disk): void
 32 |     {
 33 |         $mediaDirectory = $this->mediaFileSystem->getMediaDirectory($media);
 34 | 
 35 |         collect([$mediaDirectory])
 36 |             ->each(function (string $directory) use ($media, $disk) {
 37 |                 try {
 38 |                     $allFilePaths = $this->filesystem->disk($disk)->allFiles($directory);
 39 |                     $imagePaths = array_filter(
 40 |                         $allFilePaths,
 41 |                         static fn (string $path) => Str::afterLast($path, '/') === $media->file_name
 42 |                     );
 43 |                     foreach ($imagePaths as $imagePath) {
 44 |                         $this->filesystem->disk($disk)->delete($imagePath);
 45 |                     }
 46 | 
 47 |                     if (! $this->filesystem->disk($disk)->allFiles($directory)) {
 48 |                         $this->filesystem->disk($disk)->deleteDirectory($directory);
 49 |                     }
 50 |                 } catch (Exception $exception) {
 51 |                     report($exception);
 52 |                 }
 53 |             });
 54 | 
 55 |     }
 56 | 
 57 |     public function removeFromConversionsDirectory(Media $media, string $disk): void
 58 |     {
 59 |         $conversionsDirectory = $this->mediaFileSystem->getMediaDirectory($media, 'conversions');
 60 | 
 61 |         collect([$conversionsDirectory])
 62 |             ->each(function (string $directory) use ($media, $disk) {
 63 |                 try {
 64 |                     $allFilePaths = $this->filesystem->disk($disk)->allFiles($directory);
 65 |                     $conversions = $media->getMediaConversionNames() ?: [];
 66 |                     $conversionsFilePaths = array_map(
 67 |                         static fn (string $conversion) => $media->getPathRelativeToRoot($conversion),
 68 |                         $conversions,
 69 |                     );
 70 |                     $imagePaths = array_intersect($allFilePaths, $conversionsFilePaths);
 71 |                     foreach ($imagePaths as $imagePath) {
 72 |                         $this->filesystem->disk($disk)->delete($imagePath);
 73 |                     }
 74 | 
 75 |                     if (! $this->filesystem->disk($disk)->allFiles($directory)) {
 76 |                         $this->filesystem->disk($disk)->deleteDirectory($directory);
 77 |                     }
 78 |                 } catch (Exception $exception) {
 79 |                     report($exception);
 80 |                 }
 81 |             });
 82 |     }
 83 | 
 84 |     public function removeFromResponsiveImagesDirectory(Media $media, string $disk): void
 85 |     {
 86 |         $responsiveImagesDirectory = $this->mediaFileSystem->getMediaDirectory($media, 'responsiveImages');
 87 |         $mediaRoot = PathGeneratorFactory::create($media)->getPathForResponsiveImages($media);
 88 |         /** @var FileNamer $fileNamer */
 89 |         $fileNamer = app(config('media-library.file_namer'));
 90 |         $mediaFilename = $fileNamer->responsiveFileName($media->file_name);
 91 | 
 92 |         collect([$responsiveImagesDirectory])
 93 |             ->unique()
 94 |             ->each(function (string $directory) use ($media, $disk, $mediaRoot, $mediaFilename) {
 95 |                 try {
 96 |                     $allFilePaths = $this->filesystem->disk($disk)->allFiles($directory);
 97 | 
 98 |                     $conversions = $media->getMediaConversionNames() ?: [];
 99 |                     $responsiveImagesFilePaths = collect($conversions)
100 |                         ->flatMap(static fn (string $conversion) => $media->responsiveImages($conversion)->getFilenames())
101 |                         ->map(static fn (string $imagePath) => $mediaRoot.$imagePath)
102 |                         ->toArray();
103 | 
104 |                     $imagePaths = array_merge(
105 |                         array_intersect($allFilePaths, $responsiveImagesFilePaths),
106 |                         array_filter(
107 |                             $allFilePaths,
108 |                             static fn (string $path) => Str::startsWith($path, $mediaRoot.$mediaFilename.'___media_library_original_'),
109 |                         ),
110 |                     );
111 | 
112 |                     foreach ($imagePaths as $imagePath) {
113 |                         $this->filesystem->disk($disk)->delete($imagePath);
114 |                     }
115 | 
116 |                     if (! $this->filesystem->disk($disk)->allFiles($directory)) {
117 |                         $this->filesystem->disk($disk)->deleteDirectory($directory);
118 |                     }
119 |                 } catch (Exception $exception) {
120 |                     report($exception);
121 |                 }
122 |             });
123 |     }
124 | 
125 |     public function removeResponsiveImages(Media $media, string $conversionName): void
126 |     {
127 |         $responsiveImagesDirectory = $this->mediaFileSystem->getResponsiveImagesDirectory($media);
128 | 
129 |         $allFilePaths = $this->filesystem->disk($media->disk)->allFiles($responsiveImagesDirectory);
130 | 
131 |         $responsiveImagePaths = array_filter(
132 |             $allFilePaths,
133 |             fn (string $path) => Str::contains($path, $conversionName)
134 |         );
135 | 
136 |         $this->filesystem->disk($media->disk)->delete($responsiveImagePaths);
137 |     }
138 | 
139 |     public function removeFile(string $path, string $disk): void
140 |     {
141 |         $this->filesystem->disk($disk)->delete($path);
142 |     }
143 | }
144 | 


--------------------------------------------------------------------------------
/src/Support/FileRemover/FileBaseFileRemover.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\FileRemover;
 4 | 
 5 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 6 | 
 7 | class FileBaseFileRemover extends DefaultFileRemover
 8 | {
 9 |     public function removeAllFiles(Media $media): void
10 |     {
11 |         $this->removeFile($this->mediaFileSystem->getMediaDirectory($media).$media->file_name, $media->disk);
12 | 
13 |         $this->removeConvertedImages($media);
14 |     }
15 | 
16 |     public function removeConvertedImages(Media $media): void
17 |     {
18 |         collect($media->getMediaConversionNames())->each(function ($conversionName) use ($media) {
19 |             $this->removeFile(
20 |                 path: $media->getPathRelativeToRoot($conversionName),
21 |                 disk: $media->conversions_disk
22 |             );
23 | 
24 |             $this->mediaFileSystem->removeResponsiveImages($media, $conversionName);
25 |         });
26 |     }
27 | 
28 |     public function removeFile(string $path, string $disk): void
29 |     {
30 |         $this->filesystem->disk($disk)->delete($path);
31 |     }
32 | }
33 | 


--------------------------------------------------------------------------------
/src/Support/FileRemover/FileRemover.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\FileRemover;
 4 | 
 5 | use Illuminate\Contracts\Filesystem\Factory;
 6 | use Spatie\MediaLibrary\MediaCollections\Filesystem;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | 
 9 | interface FileRemover
10 | {
11 |     public function __construct(Filesystem $mediaFileSystem, Factory $filesystem);
12 | 
13 |     /*
14 |      * Remove all files relating to the media model.
15 |      */
16 |     public function removeAllFiles(Media $media): void;
17 | 
18 |     /*
19 |      * Remove responsive files relating to the media model.
20 |      */
21 |     public function removeResponsiveImages(Media $media, string $conversionName): void;
22 | 
23 |     /*
24 |      * Remove a file relating to the media model.
25 |      */
26 |     public function removeFile(string $path, string $disk): void;
27 | }
28 | 


--------------------------------------------------------------------------------
/src/Support/FileRemover/FileRemoverFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\FileRemover;
 4 | 
 5 | use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidFileRemover;
 6 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 7 | 
 8 | class FileRemoverFactory
 9 | {
10 |     public static function create(Media $media): FileRemover
11 |     {
12 |         $fileRemoverClass = config('media-library.file_remover_class');
13 | 
14 |         static::guardAgainstInvalidFileRemover($fileRemoverClass);
15 | 
16 |         return app($fileRemoverClass);
17 |     }
18 | 
19 |     protected static function guardAgainstInvalidFileRemover(string $fileRemoverClass): void
20 |     {
21 |         if (! class_exists($fileRemoverClass)) {
22 |             throw InvalidFileRemover::doesntExist($fileRemoverClass);
23 |         }
24 | 
25 |         if (! is_subclass_of($fileRemoverClass, FileRemover::class)) {
26 |             throw InvalidFileRemover::doesNotImplementFileRemover($fileRemoverClass);
27 |         }
28 |     }
29 | }
30 | 


--------------------------------------------------------------------------------
/src/Support/ImageFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support;
 4 | 
 5 | use Spatie\Image\Drivers\ImageDriver;
 6 | use Spatie\Image\Image;
 7 | 
 8 | class ImageFactory
 9 | {
10 |     public static function load(string $path): ImageDriver
11 |     {
12 |         return Image::useImageDriver(config('media-library.image_driver'))
13 |             ->loadFile($path);
14 |     }
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Support/MediaLibraryPro.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support;
 4 | 
 5 | use Spatie\MediaLibrary\MediaCollections\Exceptions\FunctionalityNotAvailable;
 6 | use Spatie\MediaLibraryPro\Models\TemporaryUpload;
 7 | 
 8 | class MediaLibraryPro
 9 | {
10 |     public static function ensureInstalled(): void
11 |     {
12 |         if (! self::isInstalled()) {
13 |             throw FunctionalityNotAvailable::mediaLibraryProRequired();
14 |         }
15 |     }
16 | 
17 |     public static function isInstalled(): bool
18 |     {
19 |         return class_exists(TemporaryUpload::class);
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/Support/MediaStream.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | namespace Spatie\MediaLibrary\Support;
  4 | 
  5 | use Illuminate\Contracts\Support\Responsable;
  6 | use Illuminate\Support\Collection;
  7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
  8 | use Symfony\Component\HttpFoundation\StreamedResponse;
  9 | use ZipStream\ZipStream;
 10 | 
 11 | class MediaStream implements Responsable
 12 | {
 13 |     protected Collection $mediaItems;
 14 | 
 15 |     protected array $zipOptions;
 16 | 
 17 |     public static function create(string $zipName): self
 18 |     {
 19 |         return new static($zipName);
 20 |     }
 21 | 
 22 |     public function __construct(protected string $zipName)
 23 |     {
 24 |         $this->mediaItems = collect();
 25 | 
 26 |         $this->zipOptions = [];
 27 |     }
 28 | 
 29 |     /**
 30 |      * @return $this
 31 |      */
 32 |     public function useZipOptions(callable $zipOptionsCallable): self
 33 |     {
 34 |         $zipOptionsCallable($this->zipOptions);
 35 | 
 36 |         return $this;
 37 |     }
 38 | 
 39 |     /**
 40 |      * @return $this
 41 |      */
 42 |     public function addMedia(...$mediaItems): self
 43 |     {
 44 |         collect($mediaItems)
 45 |             ->flatMap(function ($item) {
 46 |                 if ($item instanceof Media) {
 47 |                     return [$item];
 48 |                 }
 49 | 
 50 |                 if ($item instanceof Collection) {
 51 |                     return $item->reduce(function (array $carry, Media $media) {
 52 |                         $carry[] = $media;
 53 | 
 54 |                         return $carry;
 55 |                     }, []);
 56 |                 }
 57 | 
 58 |                 return $item;
 59 |             })
 60 |             ->each(fn (Media $media) => $this->mediaItems->push($media));
 61 | 
 62 |         return $this;
 63 |     }
 64 | 
 65 |     public function getMediaItems(): Collection
 66 |     {
 67 |         return $this->mediaItems;
 68 |     }
 69 | 
 70 |     public function toResponse($request): StreamedResponse
 71 |     {
 72 |         $headers = [
 73 |             'Content-Disposition' => "attachment; filename=\"{$this->zipName}\"",
 74 |             'Content-Type' => 'application/octet-stream',
 75 |         ];
 76 | 
 77 |         return new StreamedResponse(fn () => $this->getZipStream(), 200, $headers);
 78 |     }
 79 | 
 80 |     public function getZipStream(): ZipStream
 81 |     {
 82 |         $this->zipOptions['outputName'] = $this->zipName;
 83 |         $zip = new ZipStream(...$this->zipOptions);
 84 | 
 85 |         $this->getZipStreamContents()->each(function (array $mediaInZip) use ($zip) {
 86 |             $stream = $mediaInZip['media']->stream();
 87 | 
 88 |             $zip->addFileFromStream($mediaInZip['fileNameInZip'], $stream);
 89 | 
 90 |             if (is_resource($stream)) {
 91 |                 fclose($stream);
 92 |             }
 93 |         });
 94 | 
 95 |         $zip->finish();
 96 | 
 97 |         return $zip;
 98 |     }
 99 | 
100 |     protected function getZipStreamContents(): Collection
101 |     {
102 | 
103 |         return $this->mediaItems->map(fn (Media $media, $mediaItemIndex) => [
104 |             'fileNameInZip' => $this->getZipFileNamePrefix($this->mediaItems, $mediaItemIndex).$this->getFileNameWithSuffix($this->mediaItems, $mediaItemIndex),
105 |             'media' => $media,
106 |         ]);
107 |     }
108 | 
109 |     protected function getFileNameWithSuffix(Collection $mediaItems, int $currentIndex): string
110 |     {
111 |         $fileNameCount = 0;
112 | 
113 |         $fileName = $mediaItems[$currentIndex]->getDownloadFilename();
114 | 
115 |         foreach ($mediaItems as $index => $media) {
116 |             if ($index >= $currentIndex) {
117 |                 break;
118 |             }
119 | 
120 |             if ($this->getZipFileNamePrefix($mediaItems, $index).$media->getDownloadFilename() === $this->getZipFileNamePrefix($mediaItems, $currentIndex).$fileName) {
121 |                 $fileNameCount++;
122 |             }
123 |         }
124 | 
125 |         if ($fileNameCount === 0) {
126 |             return $fileName;
127 |         }
128 | 
129 |         $extension = pathinfo($fileName, PATHINFO_EXTENSION);
130 |         $fileNameWithoutExtension = pathinfo($fileName, PATHINFO_FILENAME);
131 | 
132 |         return "{$fileNameWithoutExtension} ({$fileNameCount}).{$extension}";
133 |     }
134 | 
135 |     protected function getZipFileNamePrefix(Collection $mediaItems, int $currentIndex): string
136 |     {
137 |         return $mediaItems[$currentIndex]->hasCustomProperty('zip_filename_prefix') ? $mediaItems[$currentIndex]->getCustomProperty('zip_filename_prefix') : '';
138 |     }
139 | }
140 | 


--------------------------------------------------------------------------------
/src/Support/PathGenerator/DefaultPathGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\PathGenerator;
 4 | 
 5 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 6 | 
 7 | class DefaultPathGenerator implements PathGenerator
 8 | {
 9 |     /*
10 |      * Get the path for the given media, relative to the root storage path.
11 |      */
12 |     public function getPath(Media $media): string
13 |     {
14 |         return $this->getBasePath($media).'/';
15 |     }
16 | 
17 |     /*
18 |      * Get the path for conversions of the given media, relative to the root storage path.
19 |      */
20 |     public function getPathForConversions(Media $media): string
21 |     {
22 |         return $this->getBasePath($media).'/conversions/';
23 |     }
24 | 
25 |     /*
26 |      * Get the path for responsive images of the given media, relative to the root storage path.
27 |      */
28 |     public function getPathForResponsiveImages(Media $media): string
29 |     {
30 |         return $this->getBasePath($media).'/responsive-images/';
31 |     }
32 | 
33 |     /*
34 |      * Get a unique base path for the given media.
35 |      */
36 |     protected function getBasePath(Media $media): string
37 |     {
38 |         $prefix = config('media-library.prefix', '');
39 | 
40 |         if ($prefix !== '') {
41 |             return $prefix.'/'.$media->getKey();
42 |         }
43 | 
44 |         return $media->getKey();
45 |     }
46 | }
47 | 


--------------------------------------------------------------------------------
/src/Support/PathGenerator/PathGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\PathGenerator;
 4 | 
 5 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 6 | 
 7 | interface PathGenerator
 8 | {
 9 |     /*
10 |      * Get the path for the given media, relative to the root storage path.
11 |      */
12 |     public function getPath(Media $media): string;
13 | 
14 |     /*
15 |      * Get the path for conversions of the given media, relative to the root storage path.
16 |      */
17 |     public function getPathForConversions(Media $media): string;
18 | 
19 |     /*
20 |      * Get the path for responsive images of the given media, relative to the root storage path.
21 |      */
22 |     public function getPathForResponsiveImages(Media $media): string;
23 | }
24 | 


--------------------------------------------------------------------------------
/src/Support/PathGenerator/PathGeneratorFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\PathGenerator;
 4 | 
 5 | use Illuminate\Database\Eloquent\Relations\Relation;
 6 | use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidPathGenerator;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | 
 9 | class PathGeneratorFactory
10 | {
11 |     public static function create(Media $media): PathGenerator
12 |     {
13 |         $pathGeneratorClass = static::getPathGeneratorClass($media);
14 | 
15 |         static::guardAgainstInvalidPathGenerator($pathGeneratorClass);
16 | 
17 |         return app($pathGeneratorClass);
18 |     }
19 | 
20 |     protected static function getPathGeneratorClass(Media $media)
21 |     {
22 |         $defaultPathGeneratorClass = config('media-library.path_generator');
23 | 
24 |         foreach (config('media-library.custom_path_generators', []) as $modelClass => $customPathGeneratorClass) {
25 |             if (static::mediaBelongToModelClass($media, $modelClass)) {
26 |                 return $customPathGeneratorClass;
27 |             }
28 |         }
29 | 
30 |         return $defaultPathGeneratorClass;
31 |     }
32 | 
33 |     protected static function mediaBelongToModelClass(Media $media, string $modelClass): bool
34 |     {
35 |         // model doesn't have morphMap, so morph type and class are equal
36 |         if (is_a($media->model_type, $modelClass, true)) {
37 |             return true;
38 |         }
39 |         // config is set via morphMap alias
40 |         if ($media->model_type === $modelClass) {
41 |             return true;
42 |         }
43 |         // config is set via morphMap class name
44 |         if (is_a((string) Relation::getMorphedModel($media->model_type), $modelClass, true)) {
45 |             return true;
46 |         }
47 | 
48 |         return false;
49 |     }
50 | 
51 |     protected static function guardAgainstInvalidPathGenerator(string $pathGeneratorClass): void
52 |     {
53 |         if (! class_exists($pathGeneratorClass)) {
54 |             throw InvalidPathGenerator::doesntExist($pathGeneratorClass);
55 |         }
56 | 
57 |         if (! is_subclass_of($pathGeneratorClass, PathGenerator::class)) {
58 |             throw InvalidPathGenerator::doesNotImplementPathGenerator($pathGeneratorClass);
59 |         }
60 |     }
61 | }
62 | 


--------------------------------------------------------------------------------
/src/Support/RemoteFile.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support;
 4 | 
 5 | class RemoteFile
 6 | {
 7 |     public function __construct(protected string $key, protected string $disk) {}
 8 | 
 9 |     public function getKey(): string
10 |     {
11 |         return $this->key;
12 |     }
13 | 
14 |     public function getDisk(): string
15 |     {
16 |         return $this->disk;
17 |     }
18 | 
19 |     public function getFilename(): string
20 |     {
21 |         return basename($this->key);
22 |     }
23 | 
24 |     public function getName(): string
25 |     {
26 |         return pathinfo($this->getFilename(), PATHINFO_FILENAME);
27 |     }
28 | }
29 | 


--------------------------------------------------------------------------------
/src/Support/TemporaryDirectory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support;
 4 | 
 5 | use Illuminate\Support\Str;
 6 | use Spatie\TemporaryDirectory\TemporaryDirectory as BaseTemporaryDirectory;
 7 | 
 8 | class TemporaryDirectory
 9 | {
10 |     public static function create(): BaseTemporaryDirectory
11 |     {
12 |         return new BaseTemporaryDirectory(static::getTemporaryDirectoryPath());
13 |     }
14 | 
15 |     protected static function getTemporaryDirectoryPath(): string
16 |     {
17 |         $path = config('media-library.temporary_directory_path') ?? storage_path('media-library/temp');
18 | 
19 |         return $path.DIRECTORY_SEPARATOR.Str::random(32);
20 |     }
21 | }
22 | 


--------------------------------------------------------------------------------
/src/Support/UrlGenerator/BaseUrlGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\UrlGenerator;
 4 | 
 5 | use Illuminate\Contracts\Config\Repository as Config;
 6 | use Illuminate\Contracts\Filesystem\Filesystem;
 7 | use Illuminate\Support\Facades\Storage;
 8 | use Spatie\MediaLibrary\Conversions\Conversion;
 9 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
10 | use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator;
11 | 
12 | abstract class BaseUrlGenerator implements UrlGenerator
13 | {
14 |     protected ?Media $media = null;
15 | 
16 |     protected ?Conversion $conversion = null;
17 | 
18 |     protected ?PathGenerator $pathGenerator = null;
19 | 
20 |     public function __construct(protected Config $config) {}
21 | 
22 |     /**
23 |      * @return $this
24 |      */
25 |     public function setMedia(Media $media): UrlGenerator
26 |     {
27 |         $this->media = $media;
28 | 
29 |         return $this;
30 |     }
31 | 
32 |     /**
33 |      * @return $this
34 |      */
35 |     public function setConversion(Conversion $conversion): UrlGenerator
36 |     {
37 |         $this->conversion = $conversion;
38 | 
39 |         return $this;
40 |     }
41 | 
42 |     /**
43 |      * @return $this
44 |      */
45 |     public function setPathGenerator(PathGenerator $pathGenerator): UrlGenerator
46 |     {
47 |         $this->pathGenerator = $pathGenerator;
48 | 
49 |         return $this;
50 |     }
51 | 
52 |     public function getPathRelativeToRoot(): string
53 |     {
54 |         if (is_null($this->conversion)) {
55 |             return $this->pathGenerator->getPath($this->media).($this->media->file_name);
56 |         }
57 | 
58 |         return $this->pathGenerator->getPathForConversions($this->media)
59 |                 .$this->conversion->getConversionFile($this->media);
60 |     }
61 | 
62 |     protected function getDiskName(): string
63 |     {
64 |         return $this->conversion === null
65 |             ? $this->media->disk
66 |             : $this->media->conversions_disk;
67 |     }
68 | 
69 |     protected function getDisk(): Filesystem
70 |     {
71 |         return Storage::disk($this->getDiskName());
72 |     }
73 | 
74 |     public function versionUrl(string $path = ''): string
75 |     {
76 |         if (! $this->config->get('media-library.version_urls')) {
77 |             return $path;
78 |         }
79 | 
80 |         return "{$path}?v={$this->media->updated_at->timestamp}";
81 |     }
82 | }
83 | 


--------------------------------------------------------------------------------
/src/Support/UrlGenerator/DefaultUrlGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\UrlGenerator;
 4 | 
 5 | use DateTimeInterface;
 6 | use Illuminate\Support\Str;
 7 | 
 8 | class DefaultUrlGenerator extends BaseUrlGenerator
 9 | {
10 |     public function getUrl(): string
11 |     {
12 |         $url = $this->getDisk()->url($this->getPathRelativeToRoot());
13 | 
14 |         return $this->versionUrl($url);
15 |     }
16 | 
17 |     public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string
18 |     {
19 |         return $this->getDisk()->temporaryUrl($this->getPathRelativeToRoot(), $expiration, $options);
20 |     }
21 | 
22 |     public function getBaseMediaDirectoryUrl(): string
23 |     {
24 |         return $this->getDisk()->url('/');
25 |     }
26 | 
27 |     public function getPath(): string
28 |     {
29 |         return $this->getRootOfDisk().$this->getPathRelativeToRoot();
30 |     }
31 | 
32 |     public function getResponsiveImagesDirectoryUrl(): string
33 |     {
34 |         $path = $this->pathGenerator->getPathForResponsiveImages($this->media);
35 | 
36 |         return Str::finish($this->getDisk()->url($path), '/');
37 |     }
38 | 
39 |     protected function getRootOfDisk(): string
40 |     {
41 |         return $this->getDisk()->path('/');
42 |     }
43 | }
44 | 


--------------------------------------------------------------------------------
/src/Support/UrlGenerator/UrlGenerator.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\UrlGenerator;
 4 | 
 5 | use DateTimeInterface;
 6 | use Spatie\MediaLibrary\Conversions\Conversion;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator;
 9 | 
10 | interface UrlGenerator
11 | {
12 |     public function getUrl(): string;
13 | 
14 |     public function getPath(): string;
15 | 
16 |     public function setMedia(Media $media): self;
17 | 
18 |     public function setConversion(Conversion $conversion): self;
19 | 
20 |     public function setPathGenerator(PathGenerator $pathGenerator): self;
21 | 
22 |     /**
23 |      * @param  array<string, mixed>  $options
24 |      */
25 |     public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string;
26 | 
27 |     public function getResponsiveImagesDirectoryUrl(): string;
28 | }
29 | 


--------------------------------------------------------------------------------
/src/Support/UrlGenerator/UrlGeneratorFactory.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | namespace Spatie\MediaLibrary\Support\UrlGenerator;
 4 | 
 5 | use Spatie\MediaLibrary\Conversions\ConversionCollection;
 6 | use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidUrlGenerator;
 7 | use Spatie\MediaLibrary\MediaCollections\Models\Media;
 8 | use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory;
 9 | 
10 | class UrlGeneratorFactory
11 | {
12 |     public static function createForMedia(Media $media, string $conversionName = ''): UrlGenerator
13 |     {
14 |         $urlGeneratorClass = config('media-library.url_generator');
15 | 
16 |         static::guardAgainstInvalidUrlGenerator($urlGeneratorClass);
17 | 
18 |         /** @var UrlGenerator $urlGenerator */
19 |         $urlGenerator = app($urlGeneratorClass);
20 | 
21 |         $pathGenerator = PathGeneratorFactory::create($media);
22 | 
23 |         $urlGenerator
24 |             ->setMedia($media)
25 |             ->setPathGenerator($pathGenerator);
26 | 
27 |         if ($conversionName !== '') {
28 |             $conversion = ConversionCollection::createForMedia($media)->getByName($conversionName);
29 | 
30 |             $urlGenerator->setConversion($conversion);
31 |         }
32 | 
33 |         return $urlGenerator;
34 |     }
35 | 
36 |     public static function guardAgainstInvalidUrlGenerator(string $urlGeneratorClass): void
37 |     {
38 |         if (! class_exists($urlGeneratorClass)) {
39 |             throw InvalidUrlGenerator::doesntExist($urlGeneratorClass);
40 |         }
41 | 
42 |         if (! is_subclass_of($urlGeneratorClass, UrlGenerator::class)) {
43 |             throw InvalidUrlGenerator::doesNotImplementUrlGenerator($urlGeneratorClass);
44 |         }
45 |     }
46 | }
47 | 


--------------------------------------------------------------------------------