Файловый менеджер - Редактировать - /home/clickysoft/public_html/jmapi5.clickysoft.net/spatie.tar
Назад
laravel-sluggable/.github/FUNDING.yml 0000644 00000000017 15021222773 0013311 0 ustar 00 github: spatie laravel-sluggable/.github/workflows/dependabot-auto-merge.yml 0000644 00000002775 15021222773 0020441 0 ustar 00 name: dependabot-auto-merge on: pull_request_target permissions: pull-requests: write contents: write jobs: dependabot: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v1.6.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true - name: Auto-merge Dependabot PRs for semver-minor updates if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Auto-merge Dependabot PRs for semver-patch updates if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Auto-merge Dependabot PRs for Action major versions when compatibility is higher than 90% if: ${{steps.metadata.outputs.package-ecosystem == 'github_actions' && steps.metadata.outputs.update-type == 'version-update:semver-major' && steps.metadata.outputs.compatibility-score >= 90}} run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GH_TOKEN: ${{secrets.GITHUB_TOKEN}} laravel-sluggable/.github/workflows/php-cs-fixer.yml 0000644 00000001000 15021222773 0016552 0 ustar 00 name: Check & fix styling on: [push] jobs: php-cs-fixer: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - name: Run PHP CS Fixer uses: docker://oskarstark/php-cs-fixer-ga with: args: --config=.php-cs-fixer.dist.php --allow-risky=yes - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 with: commit_message: Fix styling laravel-sluggable/.github/workflows/update-changelog.yml 0000644 00000001205 15021222773 0017463 0 ustar 00 name: "Update Changelog" on: release: types: [released] jobs: update: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: ref: main - name: Update Changelog uses: stefanzweifel/changelog-updater-action@v1 with: latest-version: ${{ github.event.release.name }} release-notes: ${{ github.event.release.body }} - name: Commit updated CHANGELOG uses: stefanzweifel/git-auto-commit-action@v4 with: branch: main commit_message: Update CHANGELOG file_pattern: CHANGELOG.md laravel-sluggable/.github/workflows/run-tests.yml 0000644 00000003761 15021222773 0016231 0 ustar 00 name: run-tests on: [push, pull_request] jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: os: [ubuntu-latest] php: [8.3, 8.2, 8.1, 8.0] laravel: [11.*, 10.*, 9.*, 8.*] stability: [prefer-stable] include: - laravel: 11.* testbench: 9.* - laravel: 10.* testbench: 8.* - laravel: 9.* testbench: 7.* - laravel: 8.* testbench: 6.23 exclude: - laravel: 11.* php: 8.1 - laravel: 11.* php: 8.0 - laravel: 10.* php: 8.0 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick coverage: none - name: Setup problem matchers run: | echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install dependencies run: | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: Execute tests run: vendor/bin/pest laravel-sluggable/.github/dependabot.yml 0000644 00000000170 15021222773 0014324 0 ustar 00 version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" laravel-sluggable/composer.json 0000644 00000002405 15021222773 0012661 0 ustar 00 { "name": "spatie/laravel-sluggable", "description": "Generate slugs when saving Eloquent models", "license": "MIT", "keywords": [ "spatie", "laravel-sluggable" ], "authors": [ { "name": "Freek Van der Herten", "email": "freek@spatie.be", "homepage": "https://spatie.be", "role": "Developer" } ], "homepage": "https://github.com/spatie/laravel-sluggable", "require": { "php": "^8.0", "illuminate/database": "^8.0|^9.0|^10.0|^11.0", "illuminate/support": "^8.0|^9.0|^10.0|^11.0" }, "require-dev": { "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", "pestphp/pest": "^1.20|^2.0", "spatie/laravel-translatable": "^5.0|^6.0" }, "minimum-stability": "dev", "prefer-stable": true, "autoload": { "psr-4": { "Spatie\\Sluggable\\": "src" } }, "autoload-dev": { "psr-4": { "Spatie\\Sluggable\\Tests\\": "tests" } }, "config": { "allow-plugins": { "pestphp/pest-plugin": true } }, "scripts": { "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", "test": "vendor/bin/pest" } } laravel-sluggable/.php-cs-fixer.dist.php 0000644 00000002300 15021222773 0014167 0 ustar 00 <?php $finder = Symfony\Component\Finder\Finder::create() ->notPath('bootstrap/*') ->notPath('storage/*') ->notPath('storage/*') ->notPath('resources/view/mail/*') ->in([ __DIR__ . '/src', __DIR__ . '/tests', ]) ->name('*.php') ->notName('*.blade.php') ->ignoreDotFiles(true) ->ignoreVCS(true); return (new PhpCsFixer\Config) ->setRules([ '@PSR12' => true, 'array_syntax' => ['syntax' => 'short'], 'ordered_imports' => ['sort_algorithm' => 'alpha'], 'no_unused_imports' => true, 'not_operator_with_successor_space' => true, 'trailing_comma_in_multiline' => true, 'phpdoc_scalar' => true, 'unary_operator_spaces' => true, 'binary_operator_spaces' => true, 'blank_line_before_statement' => [ 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], ], 'phpdoc_single_line_var_spacing' => true, 'phpdoc_var_without_name' => true, 'method_argument_space' => [ 'on_multiline' => 'ensure_fully_multiline', 'keep_multiple_spaces_after_comma' => true, ] ]) ->setFinder($finder); laravel-sluggable/.editorconfig 0000644 00000000470 15021222773 0012614 0 ustar 00 ; This file is for unifying the coding style for different editors and IDEs. ; More information at http://editorconfig.org root = true [*] charset = utf-8 indent_size = 4 indent_style = space end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false laravel-sluggable/CHANGELOG.md 0000755 00000015367 15021222773 0011766 0 ustar 00 # Changelog All notable changes to `laravel-sluggable` will be documented in this file ## 3.5.0 - 2023-05-29 ### What's Changed - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/laravel-sluggable/pull/250 - Fix badge with `build` status in `README.md` by @gomzyakov in https://github.com/spatie/laravel-sluggable/pull/252 - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/laravel-sluggable/pull/255 - feat: add findBySlug alias by @IsraelOrtuno in https://github.com/spatie/laravel-sluggable/pull/256 ### New Contributors - @gomzyakov made their first contribution in https://github.com/spatie/laravel-sluggable/pull/252 - @IsraelOrtuno made their first contribution in https://github.com/spatie/laravel-sluggable/pull/256 **Full Changelog**: https://github.com/spatie/laravel-sluggable/compare/3.4.2...3.5.0 ## 3.4.2 - 2023-01-23 - update for Laravel 10 ## 3.4.1 - 2022-12-07 ### What's Changed - Update README.md by @furiouskj in https://github.com/spatie/laravel-sluggable/pull/240 - composer allow-plugins config by @hotsaucejake in https://github.com/spatie/laravel-sluggable/pull/241 - Normalize composer.json by @patinthehat in https://github.com/spatie/laravel-sluggable/pull/247 - Add Dependabot Automation by @patinthehat in https://github.com/spatie/laravel-sluggable/pull/246 - Add PHP 8.2 Support by @patinthehat in https://github.com/spatie/laravel-sluggable/pull/245 - Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/spatie/laravel-sluggable/pull/248 - Allow set slug suffix starting number by @Vediovis in https://github.com/spatie/laravel-sluggable/pull/249 ### New Contributors - @furiouskj made their first contribution in https://github.com/spatie/laravel-sluggable/pull/240 - @hotsaucejake made their first contribution in https://github.com/spatie/laravel-sluggable/pull/241 - @dependabot made their first contribution in https://github.com/spatie/laravel-sluggable/pull/248 - @Vediovis made their first contribution in https://github.com/spatie/laravel-sluggable/pull/249 **Full Changelog**: https://github.com/spatie/laravel-sluggable/compare/3.4.0...3.4.1 ## 3.4.0 - 2022-03-28 ## What's Changed - Converts Test cases to Pest tests by @marvin-wtt in https://github.com/spatie/laravel-sluggable/pull/223 - Add ability to skip the slug generation by a condition by @masterix21 in https://github.com/spatie/laravel-sluggable/pull/227 ## New Contributors - @masterix21 made their first contribution in https://github.com/spatie/laravel-sluggable/pull/227 **Full Changelog**: https://github.com/spatie/laravel-sluggable/compare/3.3.1...3.4.0 ## 3.3.1 - 2022-03-09 ## What's Changed - Add support for spatie/laravel-translatable:^6.0 by @mziraki in https://github.com/spatie/laravel-sluggable/pull/224 ## New Contributors - @mziraki made their first contribution in https://github.com/spatie/laravel-sluggable/pull/224 **Full Changelog**: https://github.com/spatie/laravel-sluggable/compare/3.3.0...3.3.1 ## 3.3.0 - 2022-01-13 - support Laravel 9 ## 3.2.0 - 2021-12-15 ## What's Changed - Adds support for implicit route model binding with translated slugs by @marvin-wtt in https://github.com/spatie/laravel-sluggable/pull/213 ## New Contributors - @marvin-wtt made their first contribution in https://github.com/spatie/laravel-sluggable/pull/213 **Full Changelog**: https://github.com/spatie/laravel-sluggable/compare/3.1.1...3.2.0 ## 3.1.1 - 2021-12-13 ## What's Changed - Migrate to PHP-CS-Fixer 3.x by @shuvroroy in https://github.com/spatie/laravel-sluggable/pull/203 - Adds test case for replicate method by @eduarguz in https://github.com/spatie/laravel-sluggable/pull/212 - Fix Deprecation: currentSlug is null by @phh in https://github.com/spatie/laravel-sluggable/pull/218 ## New Contributors - @shuvroroy made their first contribution in https://github.com/spatie/laravel-sluggable/pull/203 - @eduarguz made their first contribution in https://github.com/spatie/laravel-sluggable/pull/212 - @phh made their first contribution in https://github.com/spatie/laravel-sluggable/pull/218 **Full Changelog**: https://github.com/spatie/laravel-sluggable/compare/3.1.0...3.1.1 ## 3.1.0 - 2021-06-04 - add extra scope callback option (#201) ## 3.0.2 - 2021-05-07 - bugfix for updating slugs generated from a callback (#200) ## 3.0.1 - 2021-04-22 - update slug on non unique names (#195) ## 3.0.0 - 2021-03-01 - require PHP 8+ - drop support for PHP 7.x - convert syntax to PHP 8 - move Exceptions to `Exceptions` folder to match structure of other packages ## 2.6.2 - 2021-03-20 - Added translatable slug overriding (#190) ## 2.6.1 - 2020-01-31 - fix Eloquent model checking (#186) ## 2.6.0 - 2020-10-28 - add `preventOverwrite` - add support for PHP 8 ## 2.5.2 - 2020-10-01 - fixed an incompatibility bug with postgresql uuid column (#173) ## 2.5.1 - 2020-09-07 - add support for Laravel 8 ## 2.5.0 - 2020-06-15 - add helper trait to integrate with `laravel-translatable` #155 ## 2.4.2 - 2020-04-20 - fix bug that causes empty slugs when dealing with multi-bytes chars (#152) ## 2.4.1 - 2020-04-09 - use method for retrieving incrementing status of the model (#151) ## 2.4.0 - 2020-03-03 - add support for Laravel 7, drop support for Laravel 6 ## 2.3.0 - 2019-12-06 - drop support for anything below PHP 7.4 and Laravel 6 ## 2.2.1 - 2019-09-16 - Changed: Updated Laravel 6 compatibility for future versions ## 2.2.0 - 2019-09-04 - Drop support for PHP 7.1 - Add support for Laravel 6.0 ## 2.1.8 - 2019-06-08 - ensure slugs are unique when using soft deletes ## 2.1.7 - 2019-02-26 - Add support for Laravel 5.8 ## 2.1.6 - 2018-02-14 - performance improvements ## 2.1.5 - 2018-01-10 - improve compatibility with json fields ## 2.1.4 - 2018-08-28 - add support for Laravel 5.7 ## 2.1.3 - 2018-02-15 - fix for models with non incrementing primary keys ## 2.1.2 - 2018-02-08 - Support Laravel 5.6 ## 2.1.1 - 2017-01-28 - improve compatibility with Lumen ## 2.1.0 - 2017-09-13 - add `usingLanguage` ## 2.0.0 - 2017-08-31 - add support for Laravel 5.5, drop support for all older versions of the framework ## 1.5.2 - 2018-05-08 - make compatible with PHP 7.2 ## 1.5.1 - 2017-08-19 - fix bugs when using a custom separator ## 1.5.0 - 2017-04-13 - add `usingSeparator()` ## 1.4.1 - 2017-04-11 - ignore global scopes when determining a unique slug ## 1.4.0 - 2017-01-24 - add support for Laravel 5.4 ## 1.3.0 - 2016-11-14 - add `doNotGenerateSlugsOnCreate` and `doNotGenerateSlugsOnUpdate` ## 1.2.0 - 2016-06-13 - Added the ability to generate slugs from a callable ## 1.1.0 - 2016-01-24 - Allow custom slugs ## 1.0.2 - 2016-01-12 - Fix bug when creating slugs from null values ## 1.0.1 - 2015-12-27 - Fix Postgres bug ## 1.0.0 - 2015-12-24 - Initial release laravel-sluggable/LICENSE.md 0000644 00000002102 15021222773 0011535 0 ustar 00 The MIT License (MIT) Copyright (c) Spatie bvba <info@spatie.be> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. laravel-sluggable/src/HasSlug.php 0000644 00000013026 15021222773 0013006 0 ustar 00 <?php namespace Spatie\Sluggable; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; use Spatie\Sluggable\Exceptions\InvalidOption; trait HasSlug { protected SlugOptions $slugOptions; abstract public function getSlugOptions(): SlugOptions; protected static function bootHasSlug(): void { static::creating(function (Model $model) { $model->generateSlugOnCreate(); }); static::updating(function (Model $model) { $model->generateSlugOnUpdate(); }); } protected function generateSlugOnCreate(): void { $this->slugOptions = $this->getSlugOptions(); if ($this->slugOptions->skipGenerate) { return; } if (! $this->slugOptions->generateSlugsOnCreate) { return; } if ($this->slugOptions->preventOverwrite) { if ($this->{$this->slugOptions->slugField} !== null) { return; } } $this->addSlug(); } protected function generateSlugOnUpdate(): void { $this->slugOptions = $this->getSlugOptions(); if ($this->slugOptions->skipGenerate) { return; } if (! $this->slugOptions->generateSlugsOnUpdate) { return; } if ($this->slugOptions->preventOverwrite) { if ($this->{$this->slugOptions->slugField} !== null) { return; } } $this->addSlug(); } public function generateSlug(): void { $this->slugOptions = $this->getSlugOptions(); $this->addSlug(); } protected function addSlug(): void { $this->ensureValidSlugOptions(); $slug = $this->generateNonUniqueSlug(); if ($this->slugOptions->generateUniqueSlugs) { $slug = $this->makeSlugUnique($slug); } $slugField = $this->slugOptions->slugField; $this->$slugField = $slug; } protected function generateNonUniqueSlug(): string { $slugField = $this->slugOptions->slugField; if ($this->hasCustomSlugBeenUsed() && ! empty($this->$slugField)) { return $this->$slugField; } return Str::slug($this->getSlugSourceString(), $this->slugOptions->slugSeparator, $this->slugOptions->slugLanguage); } protected function hasCustomSlugBeenUsed(): bool { $slugField = $this->slugOptions->slugField; return $this->getOriginal($slugField) != $this->$slugField; } protected function getSlugSourceString(): string { if (is_callable($this->slugOptions->generateSlugFrom)) { $slugSourceString = $this->getSlugSourceStringFromCallable(); return $this->generateSubstring($slugSourceString); } $slugSourceString = collect($this->slugOptions->generateSlugFrom) ->map(fn (string $fieldName): string => data_get($this, $fieldName, '')) ->implode($this->slugOptions->slugSeparator); return $this->generateSubstring($slugSourceString); } protected function getSlugSourceStringFromCallable(): string { return call_user_func($this->slugOptions->generateSlugFrom, $this); } protected function makeSlugUnique(string $slug): string { $originalSlug = $slug; $i = $this->slugOptions->startSlugSuffixFrom; while ($this->otherRecordExistsWithSlug($slug) || $slug === '') { $slug = $originalSlug.$this->slugOptions->slugSeparator.$i++; } return $slug; } protected function otherRecordExistsWithSlug(string $slug): bool { $query = static::where($this->slugOptions->slugField, $slug) ->withoutGlobalScopes(); if ($this->slugOptions->extraScopeCallback) { $query->where($this->slugOptions->extraScopeCallback); } if ($this->exists) { $query->where($this->getKeyName(), '!=', $this->getKey()); } if ($this->usesSoftDeletes()) { $query->withTrashed(); } return $query->exists(); } protected function usesSoftDeletes(): bool { return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($this), true); } protected function ensureValidSlugOptions(): void { if (is_array($this->slugOptions->generateSlugFrom) && ! count($this->slugOptions->generateSlugFrom)) { throw InvalidOption::missingFromField(); } if (! strlen($this->slugOptions->slugField)) { throw InvalidOption::missingSlugField(); } if ($this->slugOptions->maximumLength <= 0) { throw InvalidOption::invalidMaximumLength(); } } /** * Helper function to handle multi-bytes strings if the module mb_substr is present, * default to substr otherwise. */ protected function generateSubstring($slugSourceString) { if (function_exists('mb_substr')) { return mb_substr($slugSourceString, 0, $this->slugOptions->maximumLength); } return substr($slugSourceString, 0, $this->slugOptions->maximumLength); } public static function findBySlug(string $slug, array $columns = ['*']) { $modelInstance = new static(); $field = $modelInstance->getSlugOptions()->slugField; $field = in_array(HasTranslatableSlug::class, class_uses_recursive(static::class)) ? "{$field}->{$modelInstance->getLocale()}" : $field; return static::where($field, $slug)->first($columns); } } laravel-sluggable/src/HasTranslatableSlug.php 0000644 00000011637 15021222773 0015351 0 ustar 00 <?php namespace Spatie\Sluggable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Localizable; trait HasTranslatableSlug { use HasSlug; use Localizable; protected function getLocalesForSlug(): Collection { $generateSlugFrom = $this->slugOptions->generateSlugFrom; if (is_callable($generateSlugFrom)) { // returns a collection of locales that were given to the SlugOptions object // when it was instantiated with the 'createWithLocales' method. return Collection::make($this->slugOptions->translatableLocales); } // collects all locales for all translatable fields return Collection::wrap($generateSlugFrom) ->filter(fn ($fieldName) => $this->isTranslatableAttribute($fieldName)) ->flatMap(fn ($fieldName) => $this->getTranslatedLocales($fieldName)); } protected function addSlug(): void { $this->ensureValidSlugOptions(); $this->getLocalesForSlug()->unique()->each(function ($locale) { $this->withLocale($locale, function () use ($locale) { $slug = $this->generateNonUniqueSlug(); $slugField = $this->slugOptions->slugField; if ($this->slugOptions->generateUniqueSlugs) { // temporarly change the 'slugField' of the SlugOptions // so the 'otherRecordExistsWithSlug' method queries // the locale JSON column instead of the 'slugField'. $this->slugOptions->saveSlugsTo("{$slugField}->{$locale}"); $slug = $this->makeSlugUnique($slug); // revert the change for the next iteration $this->slugOptions->saveSlugsTo($slugField); } $this->setTranslation($slugField, $locale, $slug); }); }); } protected function generateNonUniqueSlug(): string { $slugField = $this->slugOptions->slugField; $slugString = $this->getSlugSourceString(); $slug = $this->getTranslations($slugField)[$this->getLocale()] ?? null; $slugGeneratedFromCallable = is_callable($this->slugOptions->generateSlugFrom); $hasCustomSlug = $this->hasCustomSlugBeenUsed() && ! empty($slug); $hasNonChangedCustomSlug = ! $slugGeneratedFromCallable && ! empty($slug) && ! $this->slugIsBasedOnTitle(); if ($hasCustomSlug || $hasNonChangedCustomSlug) { $slugString = $slug; } return Str::slug($slugString, $this->slugOptions->slugSeparator, $this->slugOptions->slugLanguage); } protected function getSlugSourceStringFromCallable(): string { return call_user_func($this->slugOptions->generateSlugFrom, $this, $this->getLocale()); } protected function slugIsBasedOnTitle(): bool { $slugField = $this->slugOptions->slugField; $titleSlug = Str::slug($this->getOriginalSourceString(), $this->slugOptions->slugSeparator, $this->slugOptions->slugLanguage); $currentSlug = $this->getTranslations($slugField)[$this->getLocale()] ?? null; if (! str_starts_with($currentSlug, $titleSlug) || $titleSlug === '') { return false; } if ($titleSlug === $currentSlug) { return true; } $slugSeparator = $currentSlug[strlen($titleSlug)]; $slugIdentifier = substr($currentSlug, strlen($titleSlug) + 1); return $slugSeparator === $this->slugOptions->slugSeparator && is_numeric($slugIdentifier); } protected function getOriginalSourceString(): string { if (is_callable($this->slugOptions->generateSlugFrom)) { $slugSourceString = $this->getSlugSourceStringFromCallable(); return $this->generateSubstring($slugSourceString); } $slugSourceString = collect($this->slugOptions->generateSlugFrom) ->map(fn (string $fieldName): string => $this->getOriginal($fieldName)[$this->getLocale()] ?? '') ->implode($this->slugOptions->slugSeparator); return $this->generateSubstring($slugSourceString); } protected function hasCustomSlugBeenUsed(): bool { $slugField = $this->slugOptions->slugField; $originalSlug = $this->getOriginal($slugField)[$this->getLocale()] ?? null; $newSlug = $this->getTranslations($slugField)[$this->getLocale()] ?? null; return $originalSlug !== $newSlug; } public function resolveRouteBindingQuery($query, $value, $field = null): Builder|Relation { $field = $field ?? $this->getRouteKeyName(); if ($field !== $this->getSlugOptions()->slugField) { return parent::resolveRouteBindingQuery($query, $value, $field); } return $query->where("{$field}->{$this->getLocale()}", $value); } } laravel-sluggable/src/Exceptions/InvalidOption.php 0000644 00000001054 15021222773 0016336 0 ustar 00 <?php namespace Spatie\Sluggable\Exceptions; use Exception; class InvalidOption extends Exception { public static function missingFromField(): static { return new static('Could not determine which fields should be sluggified'); } public static function missingSlugField(): static { return new static('Could not determine in which field the slug should be saved'); } public static function invalidMaximumLength(): static { return new static('Maximum length should be greater than zero'); } } laravel-sluggable/src/SlugOptions.php 0000644 00000005274 15021222773 0013734 0 ustar 00 <?php namespace Spatie\Sluggable; class SlugOptions { /** @var array|callable */ public $generateSlugFrom; /** @var callable */ public $extraScopeCallback; public string $slugField; public bool $generateUniqueSlugs = true; public int $maximumLength = 250; public bool $skipGenerate = false; public bool $generateSlugsOnCreate = true; public bool $generateSlugsOnUpdate = true; public bool $preventOverwrite = false; public string $slugSeparator = '-'; public string $slugLanguage = 'en'; public array $translatableLocales = []; public int $startSlugSuffixFrom = 1; public static function create(): static { return new static(); } public static function createWithLocales(array $locales): static { $slugOptions = static::create(); $slugOptions->translatableLocales = $locales; return $slugOptions; } public function generateSlugsFrom(string | array | callable $fieldName): self { if (is_string($fieldName)) { $fieldName = [$fieldName]; } $this->generateSlugFrom = $fieldName; return $this; } public function saveSlugsTo(string $fieldName): self { $this->slugField = $fieldName; return $this; } public function allowDuplicateSlugs(): self { $this->generateUniqueSlugs = false; return $this; } public function slugsShouldBeNoLongerThan(int $maximumLength): self { $this->maximumLength = $maximumLength; return $this; } public function skipGenerateWhen(callable $callable): self { $this->skipGenerate = $callable() === true; return $this; } public function doNotGenerateSlugsOnCreate(): self { $this->generateSlugsOnCreate = false; return $this; } public function doNotGenerateSlugsOnUpdate(): self { $this->generateSlugsOnUpdate = false; return $this; } public function preventOverwrite(): self { $this->preventOverwrite = true; return $this; } public function usingSeparator(string $separator): self { $this->slugSeparator = $separator; return $this; } public function usingLanguage(string $language): self { $this->slugLanguage = $language; return $this; } public function extraScope(callable $callbackMethod): self { $this->extraScopeCallback = $callbackMethod; return $this; } public function startSlugSuffixFrom(int $startSlugSuffixFrom): self { $this->startSlugSuffixFrom = max(1, $startSlugSuffixFrom); return $this; } } laravel-sluggable/README.md 0000644 00000031103 15021222773 0011413 0 ustar 00 # Generate slugs when saving Eloquent models [](https://packagist.org/packages/spatie/laravel-sluggable) [](LICENSE.md) [](https://github.com/spatie/laravel-sluggable/actions) [](https://packagist.org/packages/spatie/laravel-sluggable) This package provides a trait that will generate a unique slug when saving any Eloquent model. ```php $model = new EloquentModel(); $model->name = 'activerecord is awesome'; $model->save(); echo $model->slug; // outputs "activerecord-is-awesome" ``` The slugs are generated with Laravels `Str::slug` method, whereby spaces are converted to '-'. Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). ## Support us [<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-sluggable.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/laravel-sluggable) 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). 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). ## Installation You can install the package via composer: ``` bash composer require spatie/laravel-sluggable ``` ## Usage Your Eloquent models should use the `Spatie\Sluggable\HasSlug` trait and the `Spatie\Sluggable\SlugOptions` class. The trait contains an abstract method `getSlugOptions()` that you must implement yourself. Your models' migrations should have a field to save the generated slug to. Here's an example of how to implement the trait: ```php namespace App; use Spatie\Sluggable\HasSlug; use Spatie\Sluggable\SlugOptions; use Illuminate\Database\Eloquent\Model; class YourEloquentModel extends Model { use HasSlug; /** * Get the options for generating the slug. */ public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug'); } } ``` With its migration: ```php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateYourEloquentModelTable extends Migration { public function up() { Schema::create('your_eloquent_models', function (Blueprint $table) { $table->increments('id'); $table->string('slug'); // Field name same as your `saveSlugsTo` $table->string('name'); $table->timestamps(); }); } } ``` ### Using slugs in routes To use the generated slug in routes, remember to use Laravel's [implicit route model binding](https://laravel.com/docs/5.8/routing#implicit-binding): ```php namespace App; use Spatie\Sluggable\HasSlug; use Spatie\Sluggable\SlugOptions; use Illuminate\Database\Eloquent\Model; class YourEloquentModel extends Model { use HasSlug; /** * Get the options for generating the slug. */ public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug'); } /** * Get the route key for the model. * * @return string */ public function getRouteKeyName() { return 'slug'; } } ``` ### Using multiple fields to create the slug Want to use multiple field as the basis for a slug? No problem! ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom(['first_name', 'last_name']) ->saveSlugsTo('slug'); } ``` ### Customizing slug generation You can also pass a `callable` to `generateSlugsFrom`. By default the package will generate unique slugs by appending '-' and a number, to a slug that already exists. You can disable this behaviour by calling `allowDuplicateSlugs`. ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->allowDuplicateSlugs(); } ``` ### Limiting the length of a slug You can also put a maximum size limit on the created slug: ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->slugsShouldBeNoLongerThan(50); } ``` The slug may be slightly longer than the value specified, due to the suffix which is added to make it unique. You can also use a custom separator by calling `usingSeparator` ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->usingSeparator('_'); } ``` ### Setting the slug language To set the language used by `Str::slug` you may call `usingLanguage` ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->usingLanguage('nl'); } ``` ### Overriding slugs You can also override the generated slug just by setting it to another value than the generated slug. ```php $model = EloquentModel::create(['name' => 'my name']); //slug is now "my-name"; $model->slug = 'my-custom-url'; $model->save(); //slug is now "my-custom-url"; ``` ## Prevents slugs from being generated on some conditions If you don't want to create the slug when the model has a state, you can use the `skipGenerateWhen` function. ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->skipGenerateWhen(fn () => $this->state === 'draft'); } ``` ### Prevent slugs from being generated on creation If you don't want to create the slug when the model is initially created you can set use the `doNotGenerateSlugsOnCreate()` function. ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->doNotGenerateSlugsOnCreate(); } ``` ### Prevent slug updates Similarly, if you want to prevent the slug from being updated on model updates, call `doNotGenerateSlugsOnUpdate()`. ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->doNotGenerateSlugsOnUpdate(); } ``` This can be helpful for creating permalinks that don't change until you explicitly want it to. ```php $model = EloquentModel::create(['name' => 'my name']); //slug is now "my-name"; $model->save(); $model->name = 'changed name'; $model->save(); //slug stays "my-name" ``` ### Regenerating slugs If you want to explicitly update the slug on the model you can call `generateSlug()` on your model at any time to make the slug according to your other options. Don't forget to `save()` the model to persist the update to your database. ### Preventing overwrites You can prevent slugs from being overwritten. ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->preventOverwrite(); } ``` ### Using scopes If you have a global scope that should be taken into account, you can define this as well with `extraScope`. For example if you have a pages table containing pages of multiple websites and every website has it's own unique slugs. ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->extraScope(fn ($builder) => $builder->where('scope_id', $this->scope_id)); } ``` ### Setting the slug suffix starting index By default, suffix index starts from 1, you can set starting number. ```php public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug') ->startSlugSuffixFrom(2); } ``` ### Integration with laravel-translatable You can use this package along with [laravel-translatable](https://github.com/spatie/laravel-translatable) to generate a slug for each locale. Instead of using the `HasSlug` trait, you must use the `HasTranslatableSlug` trait, and add the name of the slug field to the `$translatable` array. For slugs that are generated from a single field _or_ multiple fields, you don't have to change anything else. ```php namespace App; use Spatie\Sluggable\HasTranslatableSlug; use Spatie\Sluggable\SlugOptions; use Spatie\Translatable\HasTranslations; use Illuminate\Database\Eloquent\Model; class YourEloquentModel extends Model { use HasTranslations, HasTranslatableSlug; public $translatable = ['name', 'slug']; /** * Get the options for generating the slug. */ public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug'); } } ``` For slugs that are generated from a callable, you need to instantiate the `SlugOptions` with the `createWithLocales` method. The callable now takes two arguments instead of one. Both the `$model` and the `$locale` are available to generate a slug from. ```php namespace App; use Spatie\Sluggable\HasTranslatableSlug; use Spatie\Sluggable\SlugOptions; use Spatie\Translatable\HasTranslations; use Illuminate\Database\Eloquent\Model; class YourEloquentModel extends Model { use HasTranslations, HasTranslatableSlug; public $translatable = ['name', 'slug']; /** * Get the options for generating the slug. */ public function getSlugOptions() : SlugOptions { return SlugOptions::createWithLocales(['en', 'nl']) ->generateSlugsFrom(function($model, $locale) { return "{$locale} {$model->id}"; }) ->saveSlugsTo('slug'); } } ``` #### Implicit route model binding You can also use Laravels [implicit route model binding](https://laravel.com/docs/8.x/routing#implicit-binding) inside your controller to automatically resolve the model. To use this feature, make sure that the slug column matches the `routeNameKey`. Currently, only some database types support JSON operations. Further information about which databases support JSON can be found in the [Laravel docs](https://laravel.com/docs/8.x/queries#json-where-clauses). ```php namespace App; use Spatie\Sluggable\HasSlug; use Spatie\Sluggable\SlugOptions; use Illuminate\Database\Eloquent\Model; class YourEloquentModel extends Model { use HasTranslations, HasTranslatableSlug; public $translatable = ['name', 'slug']; /** * Get the options for generating the slug. */ public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug'); } /** * Get the route key for the model. * * @return string */ public function getRouteKeyName() { return 'slug'; } } ``` ### Find models by slug For convenience, you can use the alias `findBySlug` to retrieve a model. The query will compare against the field passed to `saveSlugsTo` when defining the `SlugOptions`. ```php $model = Article::findBySlug('my-article'); ``` `findBySlug` also accepts a second parameter `$columns` just like the default Eloquent `find` method. ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. ## Testing ``` bash composer test ``` ## Contributing Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. ## Security If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. ## Credits - [Freek Van der Herten](https://github.com/freekmurze) - [All Contributors](../../contributors) ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. image/composer.json 0000644 00000002457 15021222773 0010361 0 ustar 00 { "name": "spatie/image", "description": "Manipulate images with an expressive API", "keywords": [ "spatie", "image" ], "homepage": "https://github.com/spatie/image", "license": "MIT", "authors": [ { "name": "Freek Van der Herten", "email": "freek@spatie.be", "homepage": "https://spatie.be", "role": "Developer" } ], "require": { "php": "^8.0", "ext-exif": "*", "ext-mbstring": "*", "ext-json": "*", "league/glide": "^2.2.2", "spatie/image-optimizer": "^1.7", "spatie/temporary-directory": "^1.0|^2.0", "symfony/process": "^3.0|^4.0|^5.0|^6.0" }, "require-dev": { "pestphp/pest": "^1.22", "phpunit/phpunit": "^9.5", "symfony/var-dumper": "^4.0|^5.0|^6.0", "vimeo/psalm": "^4.6" }, "autoload": { "psr-4": { "Spatie\\Image\\": "src" } }, "autoload-dev": { "psr-4": { "Spatie\\Image\\Test\\": "tests" } }, "scripts": { "psalm": "vendor/bin/psalm", "test": "vendor/bin/pest" }, "config": { "sort-packages": true, "allow-plugins": { "pestphp/pest-plugin": true } } } image/CHANGELOG.md 0000644 00000013650 15021222773 0007445 0 ustar 00 # Changelog All notable changes to `image` will be documented in this file ## 2.2.6 - 2023-05-06 ### What's Changed - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/image/pull/185 - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/image/pull/188 - Fit with only width or height by @gdebrauwer in https://github.com/spatie/image/pull/190 ### New Contributors - @dependabot made their first contribution in https://github.com/spatie/image/pull/185 - @gdebrauwer made their first contribution in https://github.com/spatie/image/pull/190 **Full Changelog**: https://github.com/spatie/image/compare/2.2.5...2.2.6 ## 2.2.5 - 2023-01-19 ### What's Changed - Refactor tests to pest by @AyoobMH in https://github.com/spatie/image/pull/176 - Add Dependabot Automation by @patinthehat in https://github.com/spatie/image/pull/177 - Add PHP 8.2 Support by @patinthehat in https://github.com/spatie/image/pull/180 - Update Dependabot Automation by @patinthehat in https://github.com/spatie/image/pull/181 - Add fill-max fit mode by @Tofandel in https://github.com/spatie/image/pull/183 ### New Contributors - @AyoobMH made their first contribution in https://github.com/spatie/image/pull/176 - @patinthehat made their first contribution in https://github.com/spatie/image/pull/177 - @Tofandel made their first contribution in https://github.com/spatie/image/pull/183 **Full Changelog**: https://github.com/spatie/image/compare/2.2.4...2.2.5 ## 2.2.4 - 2022-08-09 ### What's Changed - Add zero orientation support ignoring EXIF by @danielcastrobalbi in https://github.com/spatie/image/pull/171 ### New Contributors - @danielcastrobalbi made their first contribution in https://github.com/spatie/image/pull/171 **Full Changelog**: https://github.com/spatie/image/compare/2.2.3...2.2.4 ## 2.2.3 - 2022-05-21 ## What's Changed - Fix permission issue with temporary directory by @sebastianpopp in https://github.com/spatie/image/pull/163 ## New Contributors - @sebastianpopp made their first contribution in https://github.com/spatie/image/pull/163 **Full Changelog**: https://github.com/spatie/image/compare/2.2.2...2.2.3 ## 2.2.2 - 2022-02-22 - add TIFF support ## 1.11.0 - 2022-02-21 ## What's Changed - Fix docs link by @pascalbaljet in https://github.com/spatie/image/pull/154 - Update .gitattributes by @PaolaRuby in https://github.com/spatie/image/pull/158 - Add TIFF support by @Synchro in https://github.com/spatie/image/pull/159 ## New Contributors - @PaolaRuby made their first contribution in https://github.com/spatie/image/pull/158 **Full Changelog**: https://github.com/spatie/image/compare/2.2.1...1.11.0 ## 2.2.1 - 2021-12-17 ## What's Changed - Use match expression in convertToGlideParameter method by @mohprilaksono in https://github.com/spatie/image/pull/149 - [REF] updated fit docs description by @JeremyRed in https://github.com/spatie/image/pull/150 - Adding compatibility to Symfony 6 by @spackmat in https://github.com/spatie/image/pull/152 ## New Contributors - @mohprilaksono made their first contribution in https://github.com/spatie/image/pull/149 - @JeremyRed made their first contribution in https://github.com/spatie/image/pull/150 - @spackmat made their first contribution in https://github.com/spatie/image/pull/152 **Full Changelog**: https://github.com/spatie/image/compare/2.2.0...2.2.1 ## 2.2.0 - 2021-10-31 - add avif support (#148) ## 2.1.0 - 2021-07-15 - Drop support for PHP 7 - Make codebase more strict with type hinting ## 2.0.0 - 2021-07-15 - Bump league/glide to v2 [#134](https://github.com/spatie/image/pull/134) ## 1.10.4 - 2021-04-07 - Allow spatie/temporary-directory v2 ## 1.10.3 - 2021-03-10 - Bump league/glide to 2.0 [#123](https://github.com/spatie/image/pull/123) ## 1.10.2 - 2020-01-26 - change condition to delete $conversionResultDirectory (#118) ## 1.10.1 - 2020-12-27 - adds zoom option to focalCrop (#112) ## 1.9.0 - 2020-11-13 - allow usage of a custom `OptimizerChain` #110 ## 1.8.1 - 2020-11-12 - revert changes from 1.8.0 ## 1.8.0 - 2020-11-12 - allow usage of a custom `OptimizerChain` (#108) ## 1.7.7 - 2020-11-12 - add support for PHP 8 ## 1.7.6 - 2020-01-26 - change uppercase function to mb_strtoupper instead of strtoupper (#99) ## 1.7.5 - 2019-11-23 - allow symfony 5 components ## 1.7.4 - 2019-08-28 - do not export docs ## 1.7.3 - 2019-08-03 - fix duplicated files (fixes #84) ## 1.7.2 - 2019-05-13 - fixes `optimize()` when used with `apply()` (#78) ## 1.7.1 - 2019-04-17 - change GlideConversion sequence (#76) ## 1.7.0 - 2019-02-22 - add support for `webp` ## 1.6.0 - 2019-01-27 - add `setTemporaryDirectory` ## 1.5.3 - 2019-01-10 - update lower deps ## 1.5.2 - 2018-05-05 - fix exception message ## 1.5.1 - 2018-04-18 - Prevent error when trying to remove `/tmp` ## 1.5.0 - 2018-04-13 - add `flip` ## 1.4.2 - 2018-04-11 - Use the correct driver for getting widths and height of images. ## 1.4.1 - 2018-02-08 - Support symfony ^4.0 - Support phpunit ^7.0 ## 1.4.0 - 2017-12-05 - add `getWidth` and `getHeight` ## 1.3.5 - 2017-12-04 - fix for problems when creating directories in the temporary directory ## 1.3.4 - 2017-07-25 - fix `optimize` docblock ## 1.3.3 - 2017-07-11 - make `optimize` method fluent ## 1.3.2 - 2017-07-05 - swap out underlying optimization package ## 1.3.1 - 2017-07-02 - internally treat `optimize` as a manipulation ## 1.3.0 - 2017-07-02 - add `optimize` method ## 1.2.1 - 2017-06-29 - add methods to determine emptyness to `Manipulations` and `ManipulationSequence` ## 1.2.0 - 2017-04-17 - allow `Manipulations` to be constructed with an array of arrays ## 1.1.3 - 2017-04-07 - improve support for multi-volume systems ## 1.1.2 - 2017-04-04 - remove conversion directory after converting image ## 1.1.1 - 2017-03-17 - avoid processing empty manipulations groups ## 1.1.0 - 2017-02-06 - added support for watermarks ## 1.0.0 - 2017-02-06 - initial release image/LICENSE.md 0000644 00000002102 15021222773 0007226 0 ustar 00 The MIT License (MIT) Copyright (c) Spatie bvba <info@spatie.be> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. image/src/Image.php 0000644 00000013226 15021222773 0010155 0 ustar 00 <?php namespace Spatie\Image; use BadMethodCallException; use Intervention\Image\ImageManagerStatic as InterventionImage; use Spatie\Image\Exceptions\InvalidImageDriver; use Spatie\ImageOptimizer\OptimizerChain; use Spatie\ImageOptimizer\OptimizerChainFactory; use Spatie\ImageOptimizer\Optimizers\BaseOptimizer; /** @mixin \Spatie\Image\Manipulations */ class Image { protected Manipulations $manipulations; protected string $imageDriver = 'gd'; protected ?string $temporaryDirectory = null; protected ?OptimizerChain $optimizerChain = null; public function __construct(protected string $pathToImage) { $this->manipulations = new Manipulations(); } public static function load(string $pathToImage): static { return new static($pathToImage); } public function setTemporaryDirectory($tempDir): static { $this->temporaryDirectory = $tempDir; return $this; } public function setOptimizeChain(OptimizerChain $optimizerChain): static { $this->optimizerChain = $optimizerChain; return $this; } /** * @param string $imageDriver * @return $this * @throws InvalidImageDriver */ public function useImageDriver(string $imageDriver): static { if (! in_array($imageDriver, ['gd', 'imagick'])) { throw InvalidImageDriver::driver($imageDriver); } $this->imageDriver = $imageDriver; InterventionImage::configure([ 'driver' => $this->imageDriver, ]); return $this; } public function manipulate(callable | Manipulations $manipulations): static { if (is_callable($manipulations)) { $manipulations($this->manipulations); } if ($manipulations instanceof Manipulations) { $this->manipulations->mergeManipulations($manipulations); } return $this; } public function __call($name, $arguments): static { if (! method_exists($this->manipulations, $name)) { throw new BadMethodCallException("Manipulation `{$name}` does not exist"); } $this->manipulations->$name(...$arguments); return $this; } public function getWidth(): int { return InterventionImage::make($this->pathToImage)->width(); } public function getHeight(): int { return InterventionImage::make($this->pathToImage)->height(); } public function getManipulationSequence(): ManipulationSequence { return $this->manipulations->getManipulationSequence(); } public function save(string $outputPath = ''): void { if ($outputPath === '') { $outputPath = $this->pathToImage; } $this->addFormatManipulation($outputPath); $glideConversion = GlideConversion::create($this->pathToImage)->useImageDriver($this->imageDriver); if (! is_null($this->temporaryDirectory)) { $glideConversion->setTemporaryDirectory($this->temporaryDirectory); } $glideConversion->performManipulations($this->manipulations); $glideConversion->save($outputPath); if ($this->shouldOptimize()) { $optimizerChainConfiguration = $this->manipulations->getFirstManipulationArgument('optimize'); $optimizerChainConfiguration = json_decode($optimizerChainConfiguration, true); $this->performOptimization($outputPath, $optimizerChainConfiguration); } } protected function shouldOptimize(): bool { return ! is_null($this->manipulations->getFirstManipulationArgument('optimize')); } protected function performOptimization($path, array $optimizerChainConfiguration): void { $optimizerChain = $this->optimizerChain ?? OptimizerChainFactory::create(); if (count($optimizerChainConfiguration)) { $existingOptimizers = $optimizerChain->getOptimizers(); $optimizers = array_map(function (array $optimizerOptions, string $optimizerClassName) use ($existingOptimizers) { $optimizer = array_values(array_filter($existingOptimizers, function ($optimizer) use ($optimizerClassName) { return $optimizer::class === $optimizerClassName; })); $optimizer = isset($optimizer[0]) && $optimizer[0] instanceof BaseOptimizer ? $optimizer[0] : new $optimizerClassName(); return $optimizer->setOptions($optimizerOptions)->setBinaryPath($optimizer->binaryPath); }, $optimizerChainConfiguration, array_keys($optimizerChainConfiguration)); $optimizerChain->setOptimizers($optimizers); } $optimizerChain->optimize($path); } protected function addFormatManipulation($outputPath): void { if ($this->manipulations->hasManipulation('format')) { return; } $inputExtension = strtolower(pathinfo($this->pathToImage, PATHINFO_EXTENSION)); $outputExtension = strtolower(pathinfo($outputPath, PATHINFO_EXTENSION)); if ($inputExtension === $outputExtension) { return; } $supportedFormats = [ Manipulations::FORMAT_JPG, Manipulations::FORMAT_PJPG, Manipulations::FORMAT_PNG, Manipulations::FORMAT_GIF, Manipulations::FORMAT_WEBP, Manipulations::FORMAT_AVIF, ]; //gd driver doesn't support TIFF if ($this->imageDriver === 'imagick') { $supportedFormats[] = Manipulations::FORMAT_TIFF; } if (in_array($outputExtension, $supportedFormats)) { $this->manipulations->format($outputExtension); } } } image/src/ManipulationSequence.php 0000644 00000006017 15021222773 0013264 0 ustar 00 <?php namespace Spatie\Image; use ArrayIterator; use IteratorAggregate; class ManipulationSequence implements IteratorAggregate { protected array $groups = []; public function __construct(array $sequenceArray = []) { $this->startNewGroup(); $this->mergeArray($sequenceArray); } public function addManipulation(string $operation, string $argument): static { $lastIndex = count($this->groups) - 1; $this->groups[$lastIndex][$operation] = $argument; return $this; } public function merge(self $sequence): static { $sequenceArray = $sequence->toArray(); $this->mergeArray($sequenceArray); return $this; } public function mergeArray(array $sequenceArray): void { foreach ($sequenceArray as $group) { foreach ($group as $name => $argument) { $this->addManipulation($name, $argument); } if (next($sequenceArray)) { $this->startNewGroup(); } } } public function startNewGroup(): static { $this->groups[] = []; return $this; } public function toArray(): array { return $this->getGroups(); } public function getGroups(): array { return $this->sanitizeManipulationSets($this->groups); } public function getIterator(): ArrayIterator { return new ArrayIterator($this->toArray()); } public function removeManipulation(string $manipulationName): static { foreach ($this->groups as &$group) { if (array_key_exists($manipulationName, $group)) { unset($group[$manipulationName]); } } return $this; } public function isEmpty(): bool { if (count($this->groups) > 1) { return false; } if (count($this->groups[0]) > 0) { return false; } return true; } protected function sanitizeManipulationSets(array $groups): array { return array_values(array_filter($groups, function (array $manipulationSet) { return count($manipulationSet); })); } /* * Determine if the sequences contain a manipulation with the given name. */ public function getFirstManipulationArgument($searchManipulationName) { foreach ($this->groups as $group) { foreach ($group as $name => $argument) { if ($name === $searchManipulationName) { return $argument; } } } } /* * Determine if the sequences contain a manipulation with the given name. */ public function contains($searchManipulationName): bool { foreach ($this->groups as $group) { foreach ($group as $name => $argument) { if ($name === $searchManipulationName) { return true; } } return false; } return false; } } image/src/Exceptions/InvalidTemporaryDirectory.php 0000644 00000000772 15021222773 0016434 0 ustar 00 <?php namespace Spatie\Image\Exceptions; use Exception; class InvalidTemporaryDirectory extends Exception { public static function temporaryDirectoryNotCreatable(string $directory): self { return new self("the temporary directory `{$directory}` does not exist and can not be created"); } public static function temporaryDirectoryNotWritable(string $directory): self { return new self("the temporary directory `{$directory}` does exist but is not writable"); } } image/src/Exceptions/InvalidImageDriver.php 0000644 00000000402 15021222773 0014751 0 ustar 00 <?php namespace Spatie\Image\Exceptions; use Exception; class InvalidImageDriver extends Exception { public static function driver(string $driver): self { return new self("Driver must be `gd` or `imagick`. `{$driver}` provided."); } } image/src/Exceptions/InvalidManipulation.php 0000644 00000002305 15021222773 0015217 0 ustar 00 <?php namespace Spatie\Image\Exceptions; use Exception; class InvalidManipulation extends Exception { public static function invalidWidth(int $width): self { return new self("Width should be a positive number. `{$width}` given."); } public static function invalidHeight(int $height): self { return new self("Height should be a positive number. `{$height}` given."); } public static function invalidParameter(string $name, $invalidValue, array $validValues): self { $validValues = self::formatValues($validValues); $name = ucfirst($name); return new self("{$name} should be one of {$validValues}. `{$invalidValue}` given."); } public static function valueNotInRange(string $name, $invalidValue, $minValue, $maxValue): self { $name = ucfirst($name); return new self("{$name} should be a number in the range {$minValue} until {$maxValue}. `{$invalidValue}` given."); } protected static function formatValues(array $values): string { $quotedValues = array_map(function (string $value) { return "`{$value}`"; }, $values); return implode(', ', $quotedValues); } } image/src/Exceptions/CouldNotConvert.php 0000644 00000000434 15021222773 0014341 0 ustar 00 <?php namespace Spatie\Image\Exceptions; use Exception; class CouldNotConvert extends Exception { public static function unknownManipulation(string $operationName): self { return new self("Can not convert image. Unknown operation `{$operationName}` used"); } } image/src/Manipulations.php 0000644 00000043135 15021222773 0011760 0 ustar 00 <?php namespace Spatie\Image; use League\Glide\Filesystem\FileNotFoundException; use ReflectionClass; use Spatie\Image\Exceptions\InvalidManipulation; class Manipulations { public const CROP_TOP_LEFT = 'crop-top-left'; public const CROP_TOP = 'crop-top'; public const CROP_TOP_RIGHT = 'crop-top-right'; public const CROP_LEFT = 'crop-left'; public const CROP_CENTER = 'crop-center'; public const CROP_RIGHT = 'crop-right'; public const CROP_BOTTOM_LEFT = 'crop-bottom-left'; public const CROP_BOTTOM = 'crop-bottom'; public const CROP_BOTTOM_RIGHT = 'crop-bottom-right'; public const ORIENTATION_AUTO = 'auto'; public const ORIENTATION_0 = 0; public const ORIENTATION_90 = 90; public const ORIENTATION_180 = 180; public const ORIENTATION_270 = 270; public const FLIP_HORIZONTALLY = 'h'; public const FLIP_VERTICALLY = 'v'; public const FLIP_BOTH = 'both'; public const FIT_CONTAIN = 'contain'; public const FIT_MAX = 'max'; public const FIT_FILL = 'fill'; public const FIT_FILL_MAX = 'fill-max'; public const FIT_STRETCH = 'stretch'; public const FIT_CROP = 'crop'; public const BORDER_OVERLAY = 'overlay'; public const BORDER_SHRINK = 'shrink'; public const BORDER_EXPAND = 'expand'; public const FORMAT_JPG = 'jpg'; public const FORMAT_PJPG = 'pjpg'; public const FORMAT_PNG = 'png'; public const FORMAT_GIF = 'gif'; public const FORMAT_WEBP = 'webp'; public const FORMAT_AVIF = 'avif'; public const FORMAT_TIFF = 'tiff'; public const FILTER_GREYSCALE = 'greyscale'; public const FILTER_SEPIA = 'sepia'; public const UNIT_PIXELS = 'px'; public const UNIT_PERCENT = '%'; public const POSITION_TOP_LEFT = 'top-left'; public const POSITION_TOP = 'top'; public const POSITION_TOP_RIGHT = 'top-right'; public const POSITION_LEFT = 'left'; public const POSITION_CENTER = 'center'; public const POSITION_RIGHT = 'right'; public const POSITION_BOTTOM_LEFT = 'bottom-left'; public const POSITION_BOTTOM = 'bottom'; public const POSITION_BOTTOM_RIGHT = 'bottom-right'; protected ManipulationSequence $manipulationSequence; public function __construct(array $manipulations = []) { if (! $this->hasMultipleConversions($manipulations)) { $manipulations = [$manipulations]; } foreach ($manipulations as $manipulation) { $this->manipulationSequence = new ManipulationSequence($manipulation); } } public static function create(array $manipulations = []): Manipulations { return new self($manipulations); } /** * @throws InvalidManipulation */ public function orientation(string $orientation): static { if (! $this->validateManipulation($orientation, 'orientation')) { throw InvalidManipulation::invalidParameter( 'orientation', $orientation, $this->getValidManipulationOptions('orientation') ); } return $this->addManipulation('orientation', $orientation); } /** * @throws InvalidManipulation */ public function flip(string $orientation): static { if (! $this->validateManipulation($orientation, 'flip')) { throw InvalidManipulation::invalidParameter( 'flip', $orientation, $this->getValidManipulationOptions('flip') ); } return $this->addManipulation('flip', $orientation); } /** * @throws InvalidManipulation */ public function crop(string $cropMethod, int $width, int $height): static { if (! $this->validateManipulation($cropMethod, 'crop')) { throw InvalidManipulation::invalidParameter( 'cropmethod', $cropMethod, $this->getValidManipulationOptions('crop') ); } $this->width($width); $this->height($height); return $this->addManipulation('crop', $cropMethod); } /** * @param int $focalX Crop center X in percent * @param int $focalY Crop center Y in percent * * @throws InvalidManipulation */ public function focalCrop(int $width, int $height, int $focalX, int $focalY, float $zoom = 1): static { if ($zoom < 1 || $zoom > 100) { throw InvalidManipulation::valueNotInRange('zoom', $zoom, 1, 100); } $this->width($width); $this->height($height); return $this->addManipulation('crop', "crop-{$focalX}-{$focalY}-{$zoom}"); } /** * @throws InvalidManipulation */ public function manualCrop(int $width, int $height, int $x, int $y): static { if ($width < 0) { throw InvalidManipulation::invalidWidth($width); } if ($height < 0) { throw InvalidManipulation::invalidWidth($height); } return $this->addManipulation('manualCrop', "{$width},{$height},{$x},{$y}"); } /** * @throws InvalidManipulation */ public function width(int $width): static { if ($width < 0) { throw InvalidManipulation::invalidWidth($width); } return $this->addManipulation('width', (string)$width); } /** * @throws InvalidManipulation */ public function height(int $height): static { if ($height < 0) { throw InvalidManipulation::invalidHeight($height); } return $this->addManipulation('height', (string)$height); } /** * @throws InvalidManipulation */ public function fit(string $fitMethod, ?int $width = null, ?int $height = null): static { if (! $this->validateManipulation($fitMethod, 'fit')) { throw InvalidManipulation::invalidParameter( 'fit', $fitMethod, $this->getValidManipulationOptions('fit') ); } if ($width === null && $height === null) { throw new InvalidManipulation('Width or height or both must be provided'); } if ($width !== null) { $this->width($width); } if ($height !== null) { $this->height($height); } return $this->addManipulation('fit', $fitMethod); } /** * @param int $ratio A value between 1 and 8 * * @throws InvalidManipulation */ public function devicePixelRatio(int $ratio): static { if ($ratio < 1 || $ratio > 8) { throw InvalidManipulation::valueNotInRange('ratio', $ratio, 1, 8); } return $this->addManipulation('devicePixelRatio', (string)$ratio); } /** * @param int $brightness A value between -100 and 100 * * @throws InvalidManipulation */ public function brightness(int $brightness): static { if ($brightness < -100 || $brightness > 100) { throw InvalidManipulation::valueNotInRange('brightness', $brightness, -100, 100); } return $this->addManipulation('brightness', (string)$brightness); } /** * @param float $gamma A value between 0.01 and 9.99 * * @throws InvalidManipulation */ public function gamma(float $gamma): static { if ($gamma < 0.01 || $gamma > 9.99) { throw InvalidManipulation::valueNotInRange('gamma', $gamma, 0.01, 9.00); } return $this->addManipulation('gamma', (string)$gamma); } /** * @param int $contrast A value between -100 and 100 * * @throws InvalidManipulation */ public function contrast(int $contrast): static { if ($contrast < -100 || $contrast > 100) { throw InvalidManipulation::valueNotInRange('contrast', $contrast, -100, 100); } return $this->addManipulation('contrast', (string)$contrast); } /** * @param int $sharpen A value between 0 and 100 * * @throws InvalidManipulation */ public function sharpen(int $sharpen): static { if ($sharpen < 0 || $sharpen > 100) { throw InvalidManipulation::valueNotInRange('sharpen', $sharpen, 0, 100); } return $this->addManipulation('sharpen', (string)$sharpen); } /** * @param int $blur A value between 0 and 100 * * @throws InvalidManipulation */ public function blur(int $blur): static { if ($blur < 0 || $blur > 100) { throw InvalidManipulation::valueNotInRange('blur', $blur, 0, 100); } return $this->addManipulation('blur', (string)$blur); } /** * @param int $pixelate A value between 0 and 1000 * * @throws InvalidManipulation */ public function pixelate(int $pixelate): static { if ($pixelate < 0 || $pixelate > 1000) { throw InvalidManipulation::valueNotInRange('pixelate', $pixelate, 0, 1000); } return $this->addManipulation('pixelate', (string)$pixelate); } /** * @throws InvalidManipulation */ public function greyscale(): static { return $this->filter('greyscale'); } /** * @throws InvalidManipulation */ public function sepia(): static { return $this->filter('sepia'); } public function background(string $colorName): static { return $this->addManipulation('background', $colorName); } /** * @throws InvalidManipulation */ public function border(int $width, string $color, string $borderType = 'overlay'): static { if ($width < 0) { throw InvalidManipulation::invalidWidth($width); } if (! $this->validateManipulation($borderType, 'border')) { throw InvalidManipulation::invalidParameter( 'border', $borderType, $this->getValidManipulationOptions('border') ); } return $this->addManipulation('border', "{$width},{$color},{$borderType}"); } /** * @throws InvalidManipulation */ public function quality(int $quality): static { if ($quality < 0 || $quality > 100) { throw InvalidManipulation::valueNotInRange('quality', $quality, 0, 100); } return $this->addManipulation('quality', (string)$quality); } /** * @throws InvalidManipulation */ public function format(string $format): static { if (! $this->validateManipulation($format, 'format')) { throw InvalidManipulation::invalidParameter( 'format', $format, $this->getValidManipulationOptions('format') ); } return $this->addManipulation('format', $format); } /** * @throws InvalidManipulation */ protected function filter(string $filterName): static { if (! $this->validateManipulation($filterName, 'filter')) { throw InvalidManipulation::invalidParameter( 'filter', $filterName, $this->getValidManipulationOptions('filter') ); } return $this->addManipulation('filter', $filterName); } /** * @throws FileNotFoundException */ public function watermark(string $filePath): static { if (! file_exists($filePath)) { throw new FileNotFoundException($filePath); } $this->addManipulation('watermark', $filePath); return $this; } /** * @param int $width The width of the watermark in pixels (default) or percent. * @param string $unit The unit of the `$width` parameter. Use `Manipulations::UNIT_PERCENT` or `Manipulations::UNIT_PIXELS`. */ public function watermarkWidth(int $width, string $unit = 'px'): static { $width = ($unit === static::UNIT_PERCENT ? $width.'w' : $width); return $this->addManipulation('watermarkWidth', (string)$width); } /** * @param int $height The height of the watermark in pixels (default) or percent. * @param string $unit The unit of the `$height` parameter. Use `Manipulations::UNIT_PERCENT` or `Manipulations::UNIT_PIXELS`. */ public function watermarkHeight(int $height, string $unit = 'px'): static { $height = ($unit === static::UNIT_PERCENT ? $height.'h' : $height); return $this->addManipulation('watermarkHeight', (string)$height); } /** * @param string $fitMethod How is the watermark fitted into the watermarkWidth and watermarkHeight properties. * * @throws InvalidManipulation */ public function watermarkFit(string $fitMethod): static { if (! $this->validateManipulation($fitMethod, 'fit')) { throw InvalidManipulation::invalidParameter( 'watermarkFit', $fitMethod, $this->getValidManipulationOptions('fit') ); } return $this->addManipulation('watermarkFit', $fitMethod); } /** * @param int $xPadding How far is the watermark placed from the left and right edges of the image. * @param int|null $yPadding How far is the watermark placed from the top and bottom edges of the image. * @param string $unit Unit of the padding values. Use `Manipulations::UNIT_PERCENT` or `Manipulations::UNIT_PIXELS`. */ public function watermarkPadding(int $xPadding, int $yPadding = null, string $unit = 'px'): static { $yPadding = $yPadding ?? $xPadding; $xPadding = ($unit === static::UNIT_PERCENT ? $xPadding.'w' : $xPadding); $yPadding = ($unit === static::UNIT_PERCENT ? $yPadding.'h' : $yPadding); $this->addManipulation('watermarkPaddingX', (string)$xPadding); $this->addManipulation('watermarkPaddingY', (string)$yPadding); return $this; } /** * @throws InvalidManipulation */ public function watermarkPosition(string $position): static { if (! $this->validateManipulation($position, 'position')) { throw InvalidManipulation::invalidParameter( 'watermarkPosition', $position, $this->getValidManipulationOptions('position') ); } return $this->addManipulation('watermarkPosition', $position); } /** * Sets the opacity of the watermark. Only works with the `imagick` driver. * * @param int $opacity A value between 0 and 100. * * @throws InvalidManipulation */ public function watermarkOpacity(int $opacity): static { if ($opacity < 0 || $opacity > 100) { throw InvalidManipulation::valueNotInRange('opacity', $opacity, 0, 100); } return $this->addManipulation('watermarkOpacity', (string)$opacity); } /** * Shave off some kilobytes by optimizing the image. */ public function optimize(array $optimizationOptions = []): static { return $this->addManipulation('optimize', json_encode($optimizationOptions)); } public function apply(): static { $this->manipulationSequence->startNewGroup(); return $this; } public function toArray(): array { return $this->manipulationSequence->toArray(); } /** * Checks if the given manipulations has arrays inside or not. */ private function hasMultipleConversions(array $manipulations): bool { foreach ($manipulations as $manipulation) { if (isset($manipulation[0]) && is_array($manipulation[0])) { return true; } } return false; } public function removeManipulation(string $name): void { $this->manipulationSequence->removeManipulation($name); } public function hasManipulation(string $manipulationName): bool { return ! is_null($this->getManipulationArgument($manipulationName)); } public function getManipulationArgument(string $manipulationName) { foreach ($this->manipulationSequence->getGroups() as $manipulationSet) { if (array_key_exists($manipulationName, $manipulationSet)) { return $manipulationSet[$manipulationName]; } } } protected function addManipulation(string $manipulationName, string $manipulationArgument): static { $this->manipulationSequence->addManipulation($manipulationName, $manipulationArgument); return $this; } public function mergeManipulations(self $manipulations): static { $this->manipulationSequence->merge($manipulations->manipulationSequence); return $this; } public function getManipulationSequence(): ManipulationSequence { return $this->manipulationSequence; } protected function validateManipulation(string $value, string $constantNamePrefix): bool { return in_array($value, $this->getValidManipulationOptions($constantNamePrefix)); } protected function getValidManipulationOptions(string $manipulation): array { $options = (new ReflectionClass(static::class))->getConstants(); return array_filter($options, function ($value, $name) use ($manipulation) { return str_starts_with($name, mb_strtoupper($manipulation)); }, ARRAY_FILTER_USE_BOTH); } public function isEmpty(): bool { return $this->manipulationSequence->isEmpty(); } /* * Get the first manipulation with the given name. * * @return mixed */ public function getFirstManipulationArgument(string $manipulationName) { return $this->manipulationSequence->getFirstManipulationArgument($manipulationName); } } image/src/GlideConversion.php 0000644 00000013010 15021222773 0012214 0 ustar 00 <?php namespace Spatie\Image; use Exception; use FilesystemIterator; use League\Glide\Server; use League\Glide\ServerFactory; use Spatie\Image\Exceptions\CouldNotConvert; use Spatie\Image\Exceptions\InvalidTemporaryDirectory; final class GlideConversion { private string $imageDriver = 'gd'; private ?string $conversionResult = null; private string $temporaryDirectory; public function __construct(private string $inputImage) { $this->temporaryDirectory = sys_get_temp_dir(); } public static function create(string $inputImage): self { return new self($inputImage); } public function setTemporaryDirectory(string $temporaryDirectory): self { if (! is_dir($temporaryDirectory)) { try { mkdir($temporaryDirectory); } catch (Exception) { throw InvalidTemporaryDirectory::temporaryDirectoryNotCreatable($temporaryDirectory); } } if (! is_writable($temporaryDirectory)) { throw InvalidTemporaryDirectory::temporaryDirectoryNotWritable($temporaryDirectory); } $this->temporaryDirectory = $temporaryDirectory; return $this; } public function getTemporaryDirectory(): string { return $this->temporaryDirectory; } public function useImageDriver(string $imageDriver): self { $this->imageDriver = $imageDriver; return $this; } public function performManipulations(Manipulations $manipulations): GlideConversion { foreach ($manipulations->getManipulationSequence() as $manipulationGroup) { $inputFile = $this->conversionResult ?? $this->inputImage; $watermarkPath = $this->extractWatermarkPath($manipulationGroup); $glideServer = $this->createGlideServer($inputFile, $watermarkPath); $glideServer->setGroupCacheInFolders(false); $manipulatedImage = $this->temporaryDirectory.DIRECTORY_SEPARATOR.$glideServer->makeImage( pathinfo($inputFile, PATHINFO_BASENAME), $this->prepareManipulations($manipulationGroup) ); if ($this->conversionResult) { unlink($this->conversionResult); } $this->conversionResult = $manipulatedImage; } return $this; } /** * Removes the watermark path from the manipulationGroup and returns it. * This way it can be injected into the Glide server as the `watermarks` path. */ private function extractWatermarkPath(&$manipulationGroup) { if (array_key_exists('watermark', $manipulationGroup)) { $watermarkPath = dirname($manipulationGroup['watermark']); $manipulationGroup['watermark'] = basename($manipulationGroup['watermark']); return $watermarkPath; } } private function createGlideServer($inputFile, string $watermarkPath = null): Server { $config = [ 'source' => dirname($inputFile), 'cache' => $this->temporaryDirectory, 'driver' => $this->imageDriver, ]; if ($watermarkPath) { $config['watermarks'] = $watermarkPath; } return ServerFactory::create($config); } public function save(string $outputFile): void { if ($this->conversionResult === '' || $this->conversionResult === null) { copy($this->inputImage, $outputFile); return; } $conversionResultDirectory = pathinfo($this->conversionResult, PATHINFO_DIRNAME); copy($this->conversionResult, $outputFile); unlink($this->conversionResult); if ($conversionResultDirectory !== sys_get_temp_dir() && $this->directoryIsEmpty($conversionResultDirectory)) { rmdir($conversionResultDirectory); } } private function prepareManipulations(array $manipulationGroup): array { $glideManipulations = []; foreach ($manipulationGroup as $name => $argument) { if ($name !== 'optimize') { $glideManipulations[$this->convertToGlideParameter($name)] = $argument; } } return $glideManipulations; } private function convertToGlideParameter(string $manipulationName): string { return match ($manipulationName) { 'width' => 'w', 'height' => 'h', 'blur' => 'blur', 'pixelate' => 'pixel', 'crop' => 'fit', 'manualCrop' => 'crop', 'orientation' => 'or', 'flip' => 'flip', 'fit' => 'fit', 'devicePixelRatio' => 'dpr', 'brightness' => 'bri', 'contrast' => 'con', 'gamma' => 'gam', 'sharpen' => 'sharp', 'filter' => 'filt', 'background' => 'bg', 'border' => 'border', 'quality' => 'q', 'format' => 'fm', 'watermark' => 'mark', 'watermarkWidth' => 'markw', 'watermarkHeight' => 'markh', 'watermarkFit' => 'markfit', 'watermarkPaddingX' => 'markx', 'watermarkPaddingY' => 'marky', 'watermarkPosition' => 'markpos', 'watermarkOpacity' => 'markalpha', default => throw CouldNotConvert::unknownManipulation($manipulationName) }; } private function directoryIsEmpty(string $directory): bool { $iterator = new FilesystemIterator($directory); return ! $iterator->valid(); } } image/README.md 0000644 00000007254 15021222773 0007116 0 ustar 00 # Manipulate images with an expressive API [](https://packagist.org/packages/spatie/image) [](LICENSE.md) [](https://github.com/spatie/image/actions/workflows/run-tests.yml) [](https://packagist.org/packages/spatie/image) Image manipulation doesn't have to be hard. Here are a few examples on how this package makes it very easy to manipulate images. ```php use Spatie\Image\Image; // modifying the image so it fits in a 100x100 rectangle without altering aspect ratio Image::load($pathToImage) ->width(100) ->height(100) ->save($pathToNewImage); // overwriting the original image with a greyscale version Image::load($pathToImage) ->greyscale() ->save(); // make image darker and save it in low quality Image::load($pathToImage) ->brightness(-30) ->quality(25) ->save(); // rotate the image and sharpen it Image::load($pathToImage) ->orientation(90) ->sharpen(15) ->save(); ``` You'll find more examples in [the full documentation](https://docs.spatie.be/image). Under the hood [Glide](http://glide.thephpleague.com/) by [Jonathan Reinink](https://twitter.com/reinink) is used. ## Support us [<img src="https://github-ads.s3.eu-central-1.amazonaws.com/image.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/image) 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). 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). ## Installation You can install the package via composer: ``` bash composer require spatie/image ``` Please note that since version 1.5.3 this package requires exif extension to be enabled: http://php.net/manual/en/exif.installation.php ## Usage Head over to [the full documentation](https://spatie.be/docs/image). ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. ## Testing ``` bash composer test ``` ## Contributing Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. ## Security If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. ## Postcardware You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). ## Credits - [Freek Van der Herten](https://github.com/freekmurze) - [All Contributors](../../contributors) Under the hood [Glide](http://glide.thephpleague.com/) by [Jonathan Reinink](https://twitter.com/reinink) is used. We've based our documentation and docblocks on text found in [the Glide documentation](http://glide.thephpleague.com/) ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. laravel-medialibrary/composer.json 0000644 00000005557 15021222773 0013373 0 ustar 00 { "name": "spatie/laravel-medialibrary", "description": "Associate files with Eloquent models", "license": "MIT", "keywords": [ "spatie", "laravel-medialibrary", "media", "conversion", "images", "downloads", "cms", "laravel" ], "authors": [ { "name": "Freek Van der Herten", "email": "freek@spatie.be", "homepage": "https://spatie.be", "role": "Developer" } ], "homepage": "https://github.com/spatie/laravel-medialibrary", "require": { "php": "^8.0", "ext-exif": "*", "ext-fileinfo": "*", "ext-json": "*", "illuminate/bus": "^9.18|^10.0", "illuminate/conditionable": "^9.18|^10.0", "illuminate/console": "^9.18|^10.0", "illuminate/database": "^9.18|^10.0", "illuminate/pipeline": "^9.18|^10.0", "illuminate/support": "^9.18|^10.0", "maennchen/zipstream-php": "^2.0|^3.0", "spatie/image": "^2.2.7", "spatie/temporary-directory": "^2.0", "symfony/console": "^6.0" }, "require-dev": { "ext-imagick": "*", "ext-pdo_sqlite": "*", "ext-zip": "*", "aws/aws-sdk-php": "^3.133.11", "doctrine/dbal": "^2.13", "guzzlehttp/guzzle": "^7.4", "league/flysystem-aws-s3-v3": "^3.0", "mockery/mockery": "^1.4", "nunomaduro/larastan": "^2.0", "orchestra/testbench": "^7.0|^8.0", "pestphp/pest": "^1.21", "phpstan/extension-installer": "^1.1", "spatie/laravel-ray": "^1.28", "spatie/pdf-to-image": "^2.1", "spatie/phpunit-snapshot-assertions": "^4.2" }, "conflict": { "php-ffmpeg/php-ffmpeg": "<0.6.1" }, "suggest": { "league/flysystem-aws-s3-v3": "Required to use AWS S3 file storage", "php-ffmpeg/php-ffmpeg": "Required for generating video thumbnails", "spatie/pdf-to-image": "Required for generating thumbnails of PDFs and SVGs" }, "minimum-stability": "dev", "prefer-stable": true, "autoload": { "psr-4": { "Spatie\\MediaLibrary\\": "src" } }, "autoload-dev": { "psr-4": { "Spatie\\MediaLibrary\\Tests\\": "tests" } }, "config": { "allow-plugins": { "pestphp/pest-plugin": true, "phpstan/extension-installer": true }, "sort-packages": true }, "extra": { "laravel": { "providers": [ "Spatie\\MediaLibrary\\MediaLibraryServiceProvider" ] } }, "scripts": { "analyse": "vendor/bin/phpstan analyse", "baseline": "vendor/bin/phpstan analyse --generate-baseline", "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", "test": "vendor/bin/pest" } } laravel-medialibrary/LICENSE.md 0000644 00000002102 15021222773 0012234 0 ustar 00 The MIT License (MIT) Copyright (c) Spatie bvba <info@spatie.be> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. laravel-medialibrary/config/media-library.php 0000644 00000024633 15021222773 0015344 0 ustar 00 <?php return [ /* * The disk on which to store added files and derived images by default. Choose * one or more of the disks you've configured in config/filesystems.php. */ 'disk_name' => env('MEDIA_DISK', 'public'), /* * The maximum file size of an item in bytes. * Adding a larger file will result in an exception. */ 'max_file_size' => 1024 * 1024 * 10, // 10MB /* * This queue connection will be used to generate derived and responsive images. * Leave empty to use the default queue connection. */ 'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'), /* * This queue will be used to generate derived and responsive images. * Leave empty to use the default queue. */ 'queue_name' => '', /* * By default all conversions will be performed on a queue. */ 'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true), /* * The fully qualified class name of the media model. */ 'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class, /* * When enabled, media collections will be serialised using the default * laravel model serialization behaviour. * * Keep this option disabled if using Media Library Pro components (https://medialibrary.pro) */ 'use_default_collection_serialization' => false, /* * The fully qualified class name of the model used for temporary uploads. * * This model is only used in Media Library Pro (https://medialibrary.pro) */ 'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class, /* * When enabled, Media Library Pro will only process temporary uploads that were uploaded * in the same session. You can opt to disable this for stateless usage of * the pro components. */ 'enable_temporary_uploads_session_affinity' => true, /* * When enabled, Media Library pro will generate thumbnails for uploaded file. */ 'generate_thumbnails_for_temporary_uploads' => true, /* * This is the class that is responsible for naming generated files. */ 'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class, /* * The class that contains the strategy for determining a media file's path. */ 'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class, /* * The class that contains the strategy for determining how to remove files. */ 'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class, /* * Here you can specify which path generator should be used for the given class. */ 'custom_path_generators' => [ // Model::class => PathGenerator::class // or // 'model_morph_alias' => PathGenerator::class ], /* * When urls to files get generated, this class will be called. Use the default * if your files are stored locally above the site root or on s3. */ 'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class, /* * Moves media on updating to keep path consistent. Enable it only with a custom * PathGenerator that uses, for example, the media UUID. */ 'moves_media_on_update' => false, /* * Whether to activate versioning when urls to files get generated. * When activated, this attaches a ?v=xx query string to the URL. */ 'version_urls' => false, /* * The media library will try to optimize all converted images by removing * metadata and applying a little bit of compression. These are * the optimizers that will be used by default. */ 'image_optimizers' => [ Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [ '-m85', // set maximum quality to 85% '--force', // ensure that progressive generation is always done also if a little bigger '--strip-all', // this strips out all text information such as comments and EXIF data '--all-progressive', // this will make sure the resulting image is a progressive one ], Spatie\ImageOptimizer\Optimizers\Pngquant::class => [ '--force', // required parameter for this package ], Spatie\ImageOptimizer\Optimizers\Optipng::class => [ '-i0', // this will result in a non-interlaced, progressive scanned image '-o2', // this set the optimization level to two (multiple IDAT compression trials) '-quiet', // required parameter for this package ], Spatie\ImageOptimizer\Optimizers\Svgo::class => [ '--disable=cleanupIDs', // disabling because it is known to cause troubles ], Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [ '-b', // required parameter for this package '-O3', // this produces the slowest but best results ], Spatie\ImageOptimizer\Optimizers\Cwebp::class => [ '-m 6', // for the slowest compression method in order to get the best compression. '-pass 10', // for maximizing the amount of analysis pass. '-mt', // multithreading for some speed improvements. '-q 90', //quality factor that brings the least noticeable changes. ], Spatie\ImageOptimizer\Optimizers\Avifenc::class => [ '-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63). '-j all', // number of jobs (worker threads, "all" uses all available cores). '--min 0', // min quantizer for color (0-63). '--max 63', // max quantizer for color (0-63). '--minalpha 0', // min quantizer for alpha (0-63). '--maxalpha 63', // max quantizer for alpha (0-63). '-a end-usage=q', // rate control mode set to Constant Quality mode. '-a tune=ssim', // SSIM as tune the encoder for distortion metric. ], ], /* * These generators will be used to create an image of media files. */ 'image_generators' => [ Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class, Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class, Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class, Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class, Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class, Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class, ], /* * The path where to store temporary files while performing image conversions. * If set to null, storage_path('media-library/temp') will be used. */ 'temporary_directory_path' => null, /* * The engine that should perform the image conversions. * Should be either `gd` or `imagick`. */ 'image_driver' => env('IMAGE_DRIVER', 'gd'), /* * FFMPEG & FFProbe binaries paths, only used if you try to generate video * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer * dependency. */ 'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'), 'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'), /* * Here you can override the class names of the jobs used by this package. Make sure * your custom jobs extend the ones provided by the package. */ 'jobs' => [ 'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class, 'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class, ], /* * When using the addMediaFromUrl method you may want to replace the default downloader. * This is particularly useful when the url of the image is behind a firewall and * need to add additional flags, possibly using curl. */ 'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class, 'remote' => [ /* * Any extra headers that should be included when uploading media to * a remote disk. Even though supported headers may vary between * different drivers, a sensible default has been provided. * * Supported by S3: CacheControl, Expires, StorageClass, * ServerSideEncryption, Metadata, ACL, ContentEncoding */ 'extra_headers' => [ 'CacheControl' => 'max-age=604800', ], ], 'responsive_images' => [ /* * This class is responsible for calculating the target widths of the responsive * images. By default we optimize for filesize and create variations that each are 30% * smaller than the previous one. More info in the documentation. * * https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images */ 'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class, /* * By default rendering media to a responsive image will add some javascript and a tiny placeholder. * This ensures that the browser can already determine the correct layout. */ 'use_tiny_placeholders' => true, /* * This class will generate the tiny placeholder used for progressive image loading. By default * the media library will use a tiny blurred jpg image. */ 'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class, ], /* * When enabling this option, a route will be registered that will enable * the Media Library Pro Vue and React components to move uploaded files * in a S3 bucket to their right place. */ 'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false), /* * When converting Media instances to response the media library will add * a `loading` attribute to the `img` tag. Here you can set the default * value of that attribute. * * Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction. * * More info: https://css-tricks.com/native-lazy-loading/ */ 'default_loading_attribute_value' => null, /* * You can specify a prefix for that is used for storing all media. * If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory. */ 'prefix' => env('MEDIA_PREFIX', ''), ]; laravel-medialibrary/src/MediaLibraryServiceProvider.php 0000644 00000005536 15021222773 0017546 0 ustar 00 <?php namespace Spatie\MediaLibrary; use Illuminate\Support\ServiceProvider; use Spatie\MediaLibrary\Conversions\Commands\RegenerateCommand; use Spatie\MediaLibrary\MediaCollections\Commands\CleanCommand; use Spatie\MediaLibrary\MediaCollections\Commands\ClearCommand; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\MediaRepository; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\MediaCollections\Models\Observers\MediaObserver; use Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\TinyPlaceholderGenerator; use Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\WidthCalculator; class MediaLibraryServiceProvider extends ServiceProvider { public function boot() { $this->registerPublishables(); $mediaClass = config('media-library.media_model', Media::class); $mediaClass::observe(new MediaObserver()); $this->loadViewsFrom(__DIR__.'/../resources/views', 'media-library'); } public function register() { $this->mergeConfigFrom(__DIR__.'/../config/media-library.php', 'media-library'); $this->app->scoped(MediaRepository::class, function () { $mediaClass = config('media-library.media_model'); return new MediaRepository(new $mediaClass()); }); $this->registerCommands(); } protected function registerPublishables(): void { if (! $this->app->runningInConsole()) { return; } $this->publishes([ __DIR__.'/../config/media-library.php' => config_path('media-library.php'), ], 'config'); if (empty(glob(database_path('migrations/*_create_media_table.php')))) { $this->publishes([ __DIR__.'/../database/migrations/create_media_table.php.stub' => database_path('migrations/'.date('Y_m_d_His', time()).'_create_media_table.php'), ], 'migrations'); } $this->publishes([ __DIR__.'/../resources/views' => resource_path('views/vendor/media-library'), ], 'views'); } protected function registerCommands(): void { $this->app->bind(Filesystem::class, Filesystem::class); $this->app->bind(WidthCalculator::class, config('media-library.responsive_images.width_calculator')); $this->app->bind(TinyPlaceholderGenerator::class, config('media-library.responsive_images.tiny_placeholder_generator')); $this->app->bind('command.media-library:regenerate', RegenerateCommand::class); $this->app->bind('command.media-library:clear', ClearCommand::class); $this->app->bind('command.media-library:clean', CleanCommand::class); $this->commands([ 'command.media-library:regenerate', 'command.media-library:clear', 'command.media-library:clean', ]); } } laravel-medialibrary/src/Support/RemoteFile.php 0000644 00000001014 15021222773 0015640 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support; class RemoteFile { public function __construct(protected string $key, protected string $disk) { } public function getKey(): string { return $this->key; } public function getDisk(): string { return $this->disk; } public function getFilename(): string { return basename($this->key); } public function getName(): string { return pathinfo($this->getFilename(), PATHINFO_FILENAME); } } laravel-medialibrary/src/Support/MediaStream.php 0000644 00000010034 15021222773 0016002 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support; use Illuminate\Contracts\Support\Responsable; use Illuminate\Support\Collection; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Symfony\Component\HttpFoundation\StreamedResponse; use ZipStream\Option\Archive as ArchiveOptions; use ZipStream\ZipStream; class MediaStream implements Responsable { protected Collection $mediaItems; protected array|ArchiveOptions $zipOptions; public static function create(string $zipName): self { return new static($zipName); } public function __construct(protected string $zipName) { $this->mediaItems = collect(); $this->zipOptions = class_exists(ArchiveOptions::class) ? new ArchiveOptions() : []; } public function useZipOptions(callable $zipOptionsCallable): self { $zipOptionsCallable($this->zipOptions); return $this; } public function addMedia(...$mediaItems): self { collect($mediaItems) ->flatMap(function ($item) { if ($item instanceof Media) { return [$item]; } if ($item instanceof Collection) { return $item->reduce(function (array $carry, Media $media) { $carry[] = $media; return $carry; }, []); } return $item; }) ->each(fn (Media $media) => $this->mediaItems->push($media)); return $this; } public function getMediaItems(): Collection { return $this->mediaItems; } public function toResponse($request): StreamedResponse { $headers = [ 'Content-Disposition' => "attachment; filename=\"{$this->zipName}\"", 'Content-Type' => 'application/octet-stream', ]; return new StreamedResponse(fn () => $this->getZipStream(), 200, $headers); } public function getZipStream(): ZipStream { if (class_exists(ArchiveOptions::class)) { $zip = new ZipStream($this->zipName, $this->zipOptions); } else { $this->zipOptions['outputName'] = $this->zipName; $zip = new ZipStream(...$this->zipOptions); } $this->getZipStreamContents()->each(function (array $mediaInZip) use ($zip) { $stream = $mediaInZip['media']->stream(); $zip->addFileFromStream($mediaInZip['fileNameInZip'], $stream); if (is_resource($stream)) { fclose($stream); } }); $zip->finish(); return $zip; } protected function getZipStreamContents(): Collection { return $this->mediaItems->map(fn (Media $media, $mediaItemIndex) => [ 'fileNameInZip' => $this->getZipFileNamePrefix($this->mediaItems, $mediaItemIndex).$this->getFileNameWithSuffix($this->mediaItems, $mediaItemIndex), 'media' => $media, ]); } protected function getFileNameWithSuffix(Collection $mediaItems, int $currentIndex): string { $fileNameCount = 0; $fileName = $mediaItems[$currentIndex]->file_name; foreach ($mediaItems as $index => $media) { if ($index >= $currentIndex) { break; } if ($this->getZipFileNamePrefix($mediaItems, $index).$media->file_name === $this->getZipFileNamePrefix($mediaItems, $currentIndex).$fileName) { $fileNameCount++; } } if ($fileNameCount === 0) { return $fileName; } $extension = pathinfo($fileName, PATHINFO_EXTENSION); $fileNameWithoutExtension = pathinfo($fileName, PATHINFO_FILENAME); return "{$fileNameWithoutExtension} ({$fileNameCount}).{$extension}"; } protected function getZipFileNamePrefix(Collection $mediaItems, int $currentIndex): string { return $mediaItems[$currentIndex]->hasCustomProperty('zip_filename_prefix') ? $mediaItems[$currentIndex]->getCustomProperty('zip_filename_prefix') : ''; } } laravel-medialibrary/src/Support/File.php 0000644 00000001141 15021222773 0014465 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support; use Symfony\Component\Mime\MimeTypes; class File { public static function getHumanReadableSize(int $sizeInBytes): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; if ($sizeInBytes == 0) { return '0 '.$units[1]; } for ($i = 0; $sizeInBytes > 1024; $i++) { $sizeInBytes /= 1024; } return round($sizeInBytes, 2).' '.$units[$i]; } public static function getMimeType(string $path): string { return (string) MimeTypes::getDefault()->guessMimeType($path); } } laravel-medialibrary/src/Support/UrlGenerator/BaseUrlGenerator.php 0000644 00000003636 15021222773 0021436 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\UrlGenerator; use Illuminate\Contracts\Config\Repository as Config; use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Support\Facades\Storage; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator; abstract class BaseUrlGenerator implements UrlGenerator { protected ?Media $media = null; protected ?Conversion $conversion = null; protected ?PathGenerator $pathGenerator = null; public function __construct(protected Config $config) { } public function setMedia(Media $media): UrlGenerator { $this->media = $media; return $this; } public function setConversion(Conversion $conversion): UrlGenerator { $this->conversion = $conversion; return $this; } public function setPathGenerator(PathGenerator $pathGenerator): UrlGenerator { $this->pathGenerator = $pathGenerator; return $this; } public function getPathRelativeToRoot(): string { if (is_null($this->conversion)) { return $this->pathGenerator->getPath($this->media).($this->media->file_name); } return $this->pathGenerator->getPathForConversions($this->media) .$this->conversion->getConversionFile($this->media); } protected function getDiskName(): string { return $this->conversion === null ? $this->media->disk : $this->media->conversions_disk; } protected function getDisk(): Filesystem { return Storage::disk($this->getDiskName()); } public function versionUrl(string $path = ''): string { if (! $this->config->get('media-library.version_urls')) { return $path; } return "{$path}?v={$this->media->updated_at->timestamp}"; } } laravel-medialibrary/src/Support/UrlGenerator/UrlGeneratorFactory.php 0000644 00000002737 15021222773 0022174 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\UrlGenerator; use Spatie\MediaLibrary\Conversions\ConversionCollection; use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidUrlGenerator; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory; class UrlGeneratorFactory { public static function createForMedia(Media $media, string $conversionName = ''): UrlGenerator { $urlGeneratorClass = config('media-library.url_generator'); static::guardAgainstInvalidUrlGenerator($urlGeneratorClass); /** @var UrlGenerator $urlGenerator */ $urlGenerator = app($urlGeneratorClass); $pathGenerator = PathGeneratorFactory::create($media); $urlGenerator ->setMedia($media) ->setPathGenerator($pathGenerator); if ($conversionName !== '') { $conversion = ConversionCollection::createForMedia($media)->getByName($conversionName); $urlGenerator->setConversion($conversion); } return $urlGenerator; } public static function guardAgainstInvalidUrlGenerator(string $urlGeneratorClass): void { if (! class_exists($urlGeneratorClass)) { throw InvalidUrlGenerator::doesntExist($urlGeneratorClass); } if (! is_subclass_of($urlGeneratorClass, UrlGenerator::class)) { throw InvalidUrlGenerator::doesNotImplementUrlGenerator($urlGeneratorClass); } } } laravel-medialibrary/src/Support/UrlGenerator/UrlGenerator.php 0000644 00000001510 15021222773 0020630 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\UrlGenerator; use DateTimeInterface; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator; interface UrlGenerator { public function getUrl(): string; public function getPath(): string; public function setMedia(Media $media): self; public function setConversion(Conversion $conversion): self; public function setPathGenerator(PathGenerator $pathGenerator): self; /** * @param DateTimeInterface $expiration * @param array<string, mixed> $options * * @return string */ public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string; public function getResponsiveImagesDirectoryUrl(): string; } laravel-medialibrary/src/Support/UrlGenerator/DefaultUrlGenerator.php 0000644 00000002105 15021222773 0022136 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\UrlGenerator; use DateTimeInterface; use Illuminate\Support\Str; class DefaultUrlGenerator extends BaseUrlGenerator { public function getUrl(): string { $url = $this->getDisk()->url($this->getPathRelativeToRoot()); return $this->versionUrl($url); } public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string { return $this->getDisk()->temporaryUrl($this->getPathRelativeToRoot(), $expiration, $options); } public function getBaseMediaDirectoryUrl(): string { return $this->getDisk()->url('/'); } public function getPath(): string { return $this->getRootOfDisk().$this->getPathRelativeToRoot(); } public function getResponsiveImagesDirectoryUrl(): string { $path = $this->pathGenerator->getPathForResponsiveImages($this->media); return Str::finish($this->getDisk()->url($path), '/'); } protected function getRootOfDisk(): string { return $this->getDisk()->path('/'); } } laravel-medialibrary/src/Support/FileRemover/FileRemoverFactory.php 0000644 00000001550 15021222773 0021600 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\FileRemover; use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidFileRemover; use Spatie\MediaLibrary\MediaCollections\Models\Media; class FileRemoverFactory { public static function create(Media $media): FileRemover { $fileRemoverClass = config('media-library.file_remover_class'); static::guardAgainstInvalidFileRemover($fileRemoverClass); return app($fileRemoverClass); } protected static function guardAgainstInvalidFileRemover(string $fileRemoverClass): void { if (! class_exists($fileRemoverClass)) { throw InvalidFileRemover::doesntExist($fileRemoverClass); } if (! is_subclass_of($fileRemoverClass, FileRemover::class)) { throw InvalidFileRemover::doesNotImplementFileRemover($fileRemoverClass); } } } laravel-medialibrary/src/Support/FileRemover/DefaultFileRemover.php 0000644 00000003737 15021222773 0021566 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\FileRemover; use Exception; use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Support\Str; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; class DefaultFileRemover implements FileRemover { public function __construct(protected Filesystem $mediaFileSystem, protected Factory $filesystem) { } public function removeAllFiles(Media $media): void { $mediaDirectory = $this->mediaFileSystem->getMediaDirectory($media); if ($media->disk !== $media->conversions_disk) { $this->filesystem->disk($media->disk)->deleteDirectory($mediaDirectory); } $conversionsDirectory = $this->mediaFileSystem->getMediaDirectory($media, 'conversions'); $responsiveImagesDirectory = $this->mediaFileSystem->getMediaDirectory($media, 'responsiveImages'); collect([$mediaDirectory, $conversionsDirectory, $responsiveImagesDirectory]) ->unique() ->each(function (string $directory) use ($media) { try { $this->filesystem->disk($media->conversions_disk)->deleteDirectory($directory); } catch (Exception $exception) { report($exception); } }); } public function removeResponsiveImages(Media $media, string $conversionName): void { $responsiveImagesDirectory = $this->mediaFileSystem->getResponsiveImagesDirectory($media); $allFilePaths = $this->filesystem->disk($media->disk)->allFiles($responsiveImagesDirectory); $responsiveImagePaths = array_filter( $allFilePaths, fn (string $path) => Str::contains($path, $conversionName) ); $this->filesystem->disk($media->disk)->delete($responsiveImagePaths); } public function removeFile(string $path, string $disk): void { $this->filesystem->disk($disk)->delete($path); } } laravel-medialibrary/src/Support/FileRemover/FileBaseFileRemover.php 0000644 00000002261 15021222773 0021643 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\FileRemover; use Illuminate\Contracts\Filesystem\Factory; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; class FileBaseFileRemover extends DefaultFileRemover implements FileRemover { public function __construct(protected Filesystem $mediaFileSystem, protected Factory $filesystem) { } public function removeAllFiles(Media $media): void { $this->removeFile($this->mediaFileSystem->getMediaDirectory($media). $media->file_name, $media->disk); $this->removeConvertedImages($media); } public function removeConvertedImages(Media $media): void { collect($media->getMediaConversionNames())->each(function ($conversionName) use ($media) { $this->removeFile( path: $media->getPathRelativeToRoot($conversionName), disk: $media->conversions_disk ); $this->mediaFileSystem->removeResponsiveImages($media, $conversionName); }); } public function removeFile(string $path, string $disk): void { $this->filesystem->disk($disk)->delete($path); } } laravel-medialibrary/src/Support/FileRemover/FileRemover.php 0000644 00000001350 15021222773 0020246 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\FileRemover; use Illuminate\Contracts\Filesystem\Factory; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; interface FileRemover { public function __construct(Filesystem $mediaFileSystem, Factory $filesystem); /* * Remove all files relating to the media model. */ public function removeAllFiles(Media $media): void; /* * Remove responsive files relating to the media model. */ public function removeResponsiveImages(Media $media, string $conversionName): void; /* * Remove a file relating to the media model. */ public function removeFile(string $path, string $disk): void; } laravel-medialibrary/src/Support/Factories/TemporaryUploadFactory.php 0000644 00000000132 15021222773 0022203 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\Factories; class TemporaryUploadFactory { } laravel-medialibrary/src/Support/PathGenerator/DefaultPathGenerator.php 0000644 00000002250 15021222773 0022423 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\PathGenerator; use Spatie\MediaLibrary\MediaCollections\Models\Media; class DefaultPathGenerator implements PathGenerator { /* * Get the path for the given media, relative to the root storage path. */ public function getPath(Media $media): string { return $this->getBasePath($media).'/'; } /* * Get the path for conversions of the given media, relative to the root storage path. */ public function getPathForConversions(Media $media): string { return $this->getBasePath($media).'/conversions/'; } /* * Get the path for responsive images of the given media, relative to the root storage path. */ public function getPathForResponsiveImages(Media $media): string { return $this->getBasePath($media).'/responsive-images/'; } /* * Get a unique base path for the given media. */ protected function getBasePath(Media $media): string { $prefix = config('media-library.prefix', ''); if ($prefix !== '') { return $prefix . '/' . $media->getKey(); } return $media->getKey(); } } laravel-medialibrary/src/Support/PathGenerator/PathGeneratorFactory.php 0000644 00000003747 15021222773 0022462 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\PathGenerator; use Illuminate\Database\Eloquent\Relations\Relation; use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidPathGenerator; use Spatie\MediaLibrary\MediaCollections\Models\Media; class PathGeneratorFactory { public static function create(Media $media): PathGenerator { $pathGeneratorClass = static::getPathGeneratorClass($media); static::guardAgainstInvalidPathGenerator($pathGeneratorClass); return app($pathGeneratorClass); } protected static function getPathGeneratorClass(Media $media) { $defaultPathGeneratorClass = config('media-library.path_generator'); foreach (config('media-library.custom_path_generators', []) as $modelClass => $customPathGeneratorClass) { if (static::mediaBelongToModelClass($media, $modelClass)) { return $customPathGeneratorClass; } } return $defaultPathGeneratorClass; } protected static function mediaBelongToModelClass(Media $media, string $modelClass): bool { // model doesn't have morphMap, so morph type and class are equal if (is_a($media->model_type, $modelClass, true)) { return true; } // config is set via morphMap alias if ($media->model_type === $modelClass) { return true; } // config is set via morphMap class name if (is_a((string)Relation::getMorphedModel($media->model_type), $modelClass, true)) { return true; } return false; } protected static function guardAgainstInvalidPathGenerator(string $pathGeneratorClass): void { if (! class_exists($pathGeneratorClass)) { throw InvalidPathGenerator::doesntExist($pathGeneratorClass); } if (! is_subclass_of($pathGeneratorClass, PathGenerator::class)) { throw InvalidPathGenerator::doesNotImplementPathGenerator($pathGeneratorClass); } } } laravel-medialibrary/src/Support/PathGenerator/PathGenerator.php 0000644 00000001202 15021222773 0021112 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\PathGenerator; use Spatie\MediaLibrary\MediaCollections\Models\Media; interface PathGenerator { /* * Get the path for the given media, relative to the root storage path. */ public function getPath(Media $media): string; /* * Get the path for conversions of the given media, relative to the root storage path. */ public function getPathForConversions(Media $media): string; /* * Get the path for responsive images of the given media, relative to the root storage path. */ public function getPathForResponsiveImages(Media $media): string; } laravel-medialibrary/src/Support/TemporaryDirectory.php 0000644 00000001077 15021222773 0017465 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support; use Illuminate\Support\Str; use Spatie\TemporaryDirectory\TemporaryDirectory as BaseTemporaryDirectory; class TemporaryDirectory { public static function create(): BaseTemporaryDirectory { return new BaseTemporaryDirectory(static::getTemporaryDirectoryPath()); } protected static function getTemporaryDirectoryPath(): string { $path = config('media-library.temporary_directory_path') ?? storage_path('media-library/temp'); return $path.DIRECTORY_SEPARATOR.Str::random(32); } } laravel-medialibrary/src/Support/MediaLibraryPro.php 0000644 00000000763 15021222773 0016644 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support; use Spatie\MediaLibrary\MediaCollections\Exceptions\FunctionalityNotAvailable; use Spatie\MediaLibraryPro\Models\TemporaryUpload; class MediaLibraryPro { public static function ensureInstalled(): void { if (! self::isInstalled()) { throw FunctionalityNotAvailable::mediaLibraryProRequired(); } } public static function isInstalled(): bool { return class_exists(TemporaryUpload::class); } } laravel-medialibrary/src/Support/FileNamer/FileNamer.php 0000644 00000001662 15021222773 0017322 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\FileNamer; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; abstract class FileNamer { public function originalFileName(string $fileName): string { $extLength = strlen(pathinfo($fileName, PATHINFO_EXTENSION)); $baseName = substr($fileName, 0, strlen($fileName) - ($extLength ? $extLength + 1 : 0)); return $baseName; } abstract public function conversionFileName(string $fileName, Conversion $conversion): string; abstract public function responsiveFileName(string $fileName): string; public function temporaryFileName(Media $media, string $extension): string { return "{$this->responsiveFileName($media->file_name)}.{$extension}"; } public function extensionFromBaseImage(string $baseImage): string { return pathinfo($baseImage, PATHINFO_EXTENSION); } } laravel-medialibrary/src/Support/FileNamer/DefaultFileNamer.php 0000644 00000001005 15021222773 0020616 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support\FileNamer; use Spatie\MediaLibrary\Conversions\Conversion; class DefaultFileNamer extends FileNamer { public function conversionFileName(string $fileName, Conversion $conversion): string { $strippedFileName = pathinfo($fileName, PATHINFO_FILENAME); return "{$strippedFileName}-{$conversion->getName()}"; } public function responsiveFileName(string $fileName): string { return pathinfo($fileName, PATHINFO_FILENAME); } } laravel-medialibrary/src/Support/ImageFactory.php 0000644 00000000565 15021222773 0016171 0 ustar 00 <?php namespace Spatie\MediaLibrary\Support; use Spatie\Image\Image; class ImageFactory { public static function load(string $path): Image { return Image::load($path)->useImageDriver(config('media-library.image_driver')) ->setTemporaryDirectory(config('media-library.temporary_directory_path') ?? storage_path('media-library/temp')); } } laravel-medialibrary/src/InteractsWithMedia.php 0000644 00000046723 15021222773 0015701 0 ustar 00 <?php namespace Spatie\MediaLibrary; use DateTimeInterface; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Http\File; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\Downloaders\DefaultDownloader; use Spatie\MediaLibrary\MediaCollections\Events\CollectionHasBeenCleared; use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidBase64Data; use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidUrl; use Spatie\MediaLibrary\MediaCollections\Exceptions\MediaCannotBeDeleted; use Spatie\MediaLibrary\MediaCollections\Exceptions\MediaCannotBeUpdated; use Spatie\MediaLibrary\MediaCollections\Exceptions\MimeTypeNotAllowed; use Spatie\MediaLibrary\MediaCollections\FileAdder; use Spatie\MediaLibrary\MediaCollections\FileAdderFactory; use Spatie\MediaLibrary\MediaCollections\MediaCollection; use Spatie\MediaLibrary\MediaCollections\MediaRepository; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\MediaLibraryPro; use Spatie\MediaLibraryPro\PendingMediaLibraryRequestHandler; use Symfony\Component\HttpFoundation\File\UploadedFile; trait InteractsWithMedia { /** @var Conversion[] */ public array $mediaConversions = []; /** @var MediaCollection[] */ public array $mediaCollections = []; protected bool $deletePreservingMedia = false; protected array $unAttachedMediaLibraryItems = []; public static function bootInteractsWithMedia() { static::deleting(function (HasMedia $model) { if ($model->shouldDeletePreservingMedia()) { return; } if (in_array(SoftDeletes::class, class_uses_recursive($model))) { if (! $model->forceDeleting) { return; } } $model->media()->cursor()->each(fn (Media $media) => $media->delete()); }); } public function media(): MorphMany { return $this->morphMany(config('media-library.media_model'), 'model'); } /** * Add a file to the media library. * * */ public function addMedia(string|UploadedFile $file): FileAdder { return app(FileAdderFactory::class)->create($this, $file); } public function addMediaFromRequest(string $key): FileAdder { return app(FileAdderFactory::class)->createFromRequest($this, $key); } /** * Add a file from the given disk. * * */ public function addMediaFromDisk(string $key, string $disk = null): FileAdder { return app(FileAdderFactory::class)->createFromDisk($this, $key, $disk ?: config('filesystems.default')); } public function addFromMediaLibraryRequest(?array $mediaLibraryRequestItems): PendingMediaLibraryRequestHandler { MediaLibraryPro::ensureInstalled(); return new PendingMediaLibraryRequestHandler( $mediaLibraryRequestItems ?? [], $this, $preserveExisting = true ); } public function syncFromMediaLibraryRequest(?array $mediaLibraryRequestItems): PendingMediaLibraryRequestHandler { MediaLibraryPro::ensureInstalled(); return new PendingMediaLibraryRequestHandler( $mediaLibraryRequestItems ?? [], $this, $preserveExisting = false ); } /** * Add multiple files from a request by keys. * * @param string[] $keys * * @return \Spatie\MediaLibrary\MediaCollections\FileAdder[] */ public function addMultipleMediaFromRequest(array $keys): Collection { return app(FileAdderFactory::class)->createMultipleFromRequest($this, $keys); } /** * Add all files from a request. * * @return \Spatie\MediaLibrary\MediaCollections\FileAdder[] */ public function addAllMediaFromRequest(): Collection { return app(FileAdderFactory::class)->createAllFromRequest($this); } /** * Add a remote file to the media library. * * * * @throws \Spatie\MediaLibrary\MediaCollections\Exceptions\FileCannotBeAdded */ public function addMediaFromUrl(string $url, array|string ...$allowedMimeTypes): FileAdder { if (! Str::startsWith($url, ['http://', 'https://'])) { throw InvalidUrl::doesNotStartWithProtocol($url); } $downloader = config('media-library.media_downloader', DefaultDownloader::class); $temporaryFile = (new $downloader())->getTempFile($url); $this->guardAgainstInvalidMimeType($temporaryFile, $allowedMimeTypes); $filename = basename(parse_url($url, PHP_URL_PATH)); $filename = urldecode($filename); if ($filename === '') { $filename = 'file'; } if (! Str::contains($filename, '.')) { $mediaExtension = explode('/', mime_content_type($temporaryFile)); $filename = "{$filename}.{$mediaExtension[1]}"; } return app(FileAdderFactory::class) ->create($this, $temporaryFile) ->usingName(pathinfo($filename, PATHINFO_FILENAME)) ->usingFileName($filename); } /** * Add a file to the media library that contains the given string. * * @param string string */ public function addMediaFromString(string $text): FileAdder { $tmpFile = tempnam(sys_get_temp_dir(), 'media-library'); file_put_contents($tmpFile, $text); $file = app(FileAdderFactory::class) ->create($this, $tmpFile) ->usingFileName('text.txt'); return $file; } /** * Add a base64 encoded file to the media library. * * * @throws \Spatie\MediaLibrary\MediaCollections\Exceptions\FileCannotBeAdded * * @throws InvalidBase64Data */ public function addMediaFromBase64(string $base64data, array|string ...$allowedMimeTypes): FileAdder { // strip out data uri scheme information (see RFC 2397) if (str_contains($base64data, ';base64')) { [$_, $base64data] = explode(';', $base64data); [$_, $base64data] = explode(',', $base64data); } // strict mode filters for non-base64 alphabet characters $binaryData = base64_decode($base64data, true); if (false === $binaryData) { throw InvalidBase64Data::create(); } // decoding and then reencoding should not change the data if (base64_encode($binaryData) !== $base64data) { throw InvalidBase64Data::create(); } // temporarily store the decoded data on the filesystem to be able to pass it to the fileAdder $tmpFile = tempnam(sys_get_temp_dir(), 'media-library'); file_put_contents($tmpFile, $binaryData); $this->guardAgainstInvalidMimeType($tmpFile, $allowedMimeTypes); $file = app(FileAdderFactory::class)->create($this, $tmpFile); return $file; } /** * Add a file to the media library from a stream. * * @param $stream */ public function addMediaFromStream($stream): FileAdder { $tmpFile = tempnam(sys_get_temp_dir(), 'media-library'); file_put_contents($tmpFile, $stream); $file = app(FileAdderFactory::class) ->create($this, $tmpFile) ->usingFileName('text.txt'); return $file; } /** * Copy a file to the media library. * * */ public function copyMedia(string|UploadedFile $file): FileAdder { return $this->addMedia($file)->preservingOriginal(); } /* * Determine if there is media in the given collection. */ public function hasMedia(string $collectionName = 'default', array $filters = []): bool { return count($this->getMedia($collectionName, $filters)) ? true : false; } /** * Get media collection by its collectionName. * * @param array|callable $filters * */ public function getMedia(string $collectionName = 'default', array|callable $filters = []): MediaCollections\Models\Collections\MediaCollection { return $this->getMediaRepository() ->getCollection($this, $collectionName, $filters) ->collectionName($collectionName); } public function getMediaRepository(): MediaRepository { return app(MediaRepository::class); } public function getFirstMedia(string $collectionName = 'default', $filters = []): ?Media { $media = $this->getMedia($collectionName, $filters); return $media->first(); } /* * Get the url of the image for the given conversionName * for first media for the given collectionName. * If no profile is given, return the source's url. */ public function getFirstMediaUrl(string $collectionName = 'default', string $conversionName = ''): string { $media = $this->getFirstMedia($collectionName); if (! $media) { return $this->getFallbackMediaUrl($collectionName, $conversionName) ?: ''; } if ($conversionName !== '' && ! $media->hasGeneratedConversion($conversionName)) { return $media->getUrl(); } return $media->getUrl($conversionName); } /* * Get the url of the image for the given conversionName * for first media for the given collectionName. * * If no profile is given, return the source's url. */ public function getFirstTemporaryUrl( DateTimeInterface $expiration, string $collectionName = 'default', string $conversionName = '' ): string { $media = $this->getFirstMedia($collectionName); if (! $media) { return $this->getFallbackMediaUrl($collectionName, $conversionName) ?: ''; } if ($conversionName !== '' && ! $media->hasGeneratedConversion($conversionName)) { return $media->getTemporaryUrl($expiration); } return $media->getTemporaryUrl($expiration, $conversionName); } public function getRegisteredMediaCollections(): Collection { $this->registerMediaCollections(); return collect($this->mediaCollections); } public function getMediaCollection(string $collectionName = 'default'): ?MediaCollection { $this->registerMediaCollections(); return collect($this->mediaCollections) ->first(fn (MediaCollection $collection) => $collection->name === $collectionName); } public function getFallbackMediaUrl(string $collectionName = 'default', string $conversionName = ''): string { $fallbackUrls = optional($this->getMediaCollection($collectionName))->fallbackUrls; if (in_array($conversionName, ['', 'default'], true)) { return $fallbackUrls['default'] ?? ''; } return $fallbackUrls[$conversionName] ?? $fallbackUrls['default'] ?? ''; } public function getFallbackMediaPath(string $collectionName = 'default', string $conversionName = ''): string { $fallbackPaths = optional($this->getMediaCollection($collectionName))->fallbackPaths; if (in_array($conversionName, ['', 'default'], true)) { return $fallbackPaths['default'] ?? ''; } return $fallbackPaths[$conversionName] ?? $fallbackPaths['default'] ?? ''; } /* * Get the url of the image for the given conversionName * for first media for the given collectionName. * If no profile is given, return the source's url. */ public function getFirstMediaPath(string $collectionName = 'default', string $conversionName = ''): string { $media = $this->getFirstMedia($collectionName); if (! $media) { return $this->getFallbackMediaPath($collectionName, $conversionName) ?: ''; } if ($conversionName !== '' && ! $media->hasGeneratedConversion($conversionName)) { return $media->getPath(); } return $media->getPath($conversionName); } /* * Update a media collection by deleting and inserting again with new values. */ public function updateMedia(array $newMediaArray, string $collectionName = 'default'): Collection { $this->removeMediaItemsNotPresentInArray($newMediaArray, $collectionName); $mediaClass = config('media-library.media_model'); $mediaInstance = new $mediaClass(); $keyName = $mediaInstance->getKeyName(); return collect($newMediaArray) ->map(function (array $newMediaItem) use ($collectionName, $mediaClass, $keyName) { static $orderColumn = 1; $currentMedia = $mediaClass::findOrFail($newMediaItem[$keyName]); if ($currentMedia->collection_name !== $collectionName) { throw MediaCannotBeUpdated::doesNotBelongToCollection($collectionName, $currentMedia); } if (array_key_exists('name', $newMediaItem)) { $currentMedia->name = $newMediaItem['name']; } if (array_key_exists('custom_properties', $newMediaItem)) { $currentMedia->custom_properties = $newMediaItem['custom_properties']; } $currentMedia->order_column = $orderColumn++; $currentMedia->save(); return $currentMedia; }); } protected function removeMediaItemsNotPresentInArray(array $newMediaArray, string $collectionName = 'default'): void { $this ->getMedia($collectionName) ->reject(fn (Media $currentMediaItem) => in_array( $currentMediaItem->getKey(), array_column($newMediaArray, $currentMediaItem->getKeyName()), )) ->each(fn (Media $media) => $media->delete()); if ($this->mediaIsPreloaded()) { unset($this->media); } } public function clearMediaCollection(string $collectionName = 'default'): HasMedia { $this ->getMedia($collectionName) ->each(fn (Media $media) => $media->delete()); event(new CollectionHasBeenCleared($this, $collectionName)); if ($this->mediaIsPreloaded()) { unset($this->media); } return $this; } public function clearMediaCollectionExcept( string $collectionName = 'default', array|Collection|Media $excludedMedia = [] ): HasMedia { if ($excludedMedia instanceof Media) { $excludedMedia = collect()->push($excludedMedia); } $excludedMedia = collect($excludedMedia); if ($excludedMedia->isEmpty()) { return $this->clearMediaCollection($collectionName); } $this ->getMedia($collectionName) ->reject(fn (Media $media) => $excludedMedia->where($media->getKeyName(), $media->getKey())->count()) ->each(fn (Media $media) => $media->delete()); if ($this->mediaIsPreloaded()) { unset($this->media); } if ($this->getMedia($collectionName)->isEmpty()) { event(new CollectionHasBeenCleared($this, $collectionName)); } return $this; } /** * Delete the associated media with the given id. * You may also pass a media object. * * * @throws \Spatie\MediaLibrary\MediaCollections\Exceptions\MediaCannotBeDeleted */ public function deleteMedia(int|string|Media $mediaId): void { if ($mediaId instanceof Media) { $mediaId = $mediaId->getKey(); } $media = $this->media->find($mediaId); if (! $media) { throw MediaCannotBeDeleted::doesNotBelongToModel($mediaId, $this); } $media->delete(); } public function addMediaConversion(string $name): Conversion { $conversion = Conversion::create($name); $this->mediaConversions[] = $conversion; return $conversion; } public function addMediaCollection(string $name): MediaCollection { $mediaCollection = MediaCollection::create($name); $this->mediaCollections[] = $mediaCollection; return $mediaCollection; } public function deletePreservingMedia(): bool { $this->deletePreservingMedia = true; return $this->delete(); } public function shouldDeletePreservingMedia(): bool { return $this->deletePreservingMedia ?? false; } protected function mediaIsPreloaded(): bool { return $this->relationLoaded('media'); } public function loadMedia(string $collectionName): Collection { $collection = $this->exists ? $this->loadMissing('media')->media : collect($this->unAttachedMediaLibraryItems)->pluck('media'); $collection = new MediaCollections\Models\Collections\MediaCollection($collection); return $collection ->filter(fn (Media $mediaItem) => $collectionName !== '*' ? $mediaItem->collection_name === $collectionName : true) ->sortBy('order_column') ->values(); } public function prepareToAttachMedia(Media $media, FileAdder $fileAdder): void { $this->unAttachedMediaLibraryItems[] = compact('media', 'fileAdder'); } public function processUnattachedMedia(callable $callable): void { foreach ($this->unAttachedMediaLibraryItems as $item) { $callable($item['media'], $item['fileAdder']); } $this->unAttachedMediaLibraryItems = []; } protected function guardAgainstInvalidMimeType(string $file, ...$allowedMimeTypes) { $allowedMimeTypes = Arr::flatten($allowedMimeTypes); if (empty($allowedMimeTypes)) { return; } $validation = Validator::make( ['file' => new File($file)], ['file' => 'mimetypes:' . implode(',', $allowedMimeTypes)] ); if ($validation->fails()) { throw MimeTypeNotAllowed::create($file, $allowedMimeTypes); } } public function registerMediaConversions(Media $media = null): void { } public function registerMediaCollections(): void { } public function registerAllMediaConversions(Media $media = null): void { $this->registerMediaCollections(); collect($this->mediaCollections)->each(function (MediaCollection $mediaCollection) use ($media) { $actualMediaConversions = $this->mediaConversions; $this->mediaConversions = []; ($mediaCollection->mediaConversionRegistrations)($media); $preparedMediaConversions = collect($this->mediaConversions) ->each(fn (Conversion $conversion) => $conversion->performOnCollections($mediaCollection->name)) ->values() ->toArray(); $this->mediaConversions = [...$actualMediaConversions, ...$preparedMediaConversions]; }); $this->registerMediaConversions($media); } public function __sleep(): array { // do not serialize properties from the trait return collect(parent::__sleep()) ->reject( fn ($key) => in_array( $key, [ 'mediaConversions', 'mediaCollections', 'unAttachedMediaLibraryItems', 'deletePreservingMedia', ] ) )->toArray(); } } laravel-medialibrary/src/MediaCollections/MediaCollection.php 0000644 00000006147 15021222773 0020416 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; class MediaCollection { use Macroable; public string $diskName = ''; public string $conversionsDiskName = ''; /** @var callable */ public $mediaConversionRegistrations; public bool $generateResponsiveImages = false; /** @var callable */ public $acceptsFile; public array $acceptsMimeTypes = []; /** @var bool|int */ public $collectionSizeLimit = false; public bool $singleFile = false; /** @var array<string, string> */ public array $fallbackUrls = []; /** @var array<string, string> */ public array $fallbackPaths = []; public function __construct( public string $name ) { $this->mediaConversionRegistrations = function () { }; $this->acceptsFile = fn () => true; } public static function create($name) { return new static($name); } public function useDisk(string $diskName): self { $this->diskName = $diskName; return $this; } public function storeConversionsOnDisk(string $conversionsDiskName): self { $this->conversionsDiskName = $conversionsDiskName; return $this; } public function acceptsFile(callable $acceptsFile): self { $this->acceptsFile = $acceptsFile; return $this; } public function acceptsMimeTypes(array $mimeTypes): self { $this->acceptsMimeTypes = $mimeTypes; return $this; } public function singleFile(): self { return $this->onlyKeepLatest(1); } public function onlyKeepLatest(int $maximumNumberOfItemsInCollection): self { if ($maximumNumberOfItemsInCollection < 1) { throw new InvalidArgumentException("You should pass a value higher than 0. `{$maximumNumberOfItemsInCollection}` given."); } $this->singleFile = ($maximumNumberOfItemsInCollection === 1); $this->collectionSizeLimit = $maximumNumberOfItemsInCollection; return $this; } public function registerMediaConversions(callable $mediaConversionRegistrations) { $this->mediaConversionRegistrations = $mediaConversionRegistrations; } public function useFallbackUrl(string $url, string $conversionName = ''): self { if ($conversionName === '') { $conversionName = 'default'; } $this->fallbackUrls[$conversionName] = $url; return $this; } public function useFallbackPath(string $path, string $conversionName = ''): self { if ($conversionName === '') { $conversionName = 'default'; } $this->fallbackPaths[$conversionName] = $path; return $this; } public function withResponsiveImages(): self { $this->generateResponsiveImages = true; return $this; } public function withResponsiveImagesIf($condition): self { $this->generateResponsiveImages = (bool) (is_callable($condition) ? $condition() : $condition); return $this; } } laravel-medialibrary/src/MediaCollections/Models/Observers/MediaObserver.php 0000644 00000003372 15021222773 0023304 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Models\Observers; use Spatie\MediaLibrary\Conversions\FileManipulator; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; class MediaObserver { public function creating(Media $media) { if ($media->shouldSortWhenCreating()) { if (is_null($media->order_column)) { $media->setHighestOrderNumber(); } } } public function updating(Media $media) { /** @var Filesystem $filesystem */ $filesystem = app(Filesystem::class); if (config('media-library.moves_media_on_update')) { $filesystem->syncMediaPath($media); } if ($media->file_name !== $media->getOriginal('file_name')) { $filesystem->syncFileNames($media); } } public function updated(Media $media) { if (is_null($media->getOriginal('model_id'))) { return; } $original = $media->getOriginal('manipulations'); if ($media->manipulations !== $original) { $eventDispatcher = Media::getEventDispatcher(); Media::unsetEventDispatcher(); /** @var FileManipulator $fileManipulator */ $fileManipulator = app(FileManipulator::class); $fileManipulator->createDerivedFiles($media); Media::setEventDispatcher($eventDispatcher); } } public function deleted(Media $media) { if (method_exists($media, 'isForceDeleting') && ! $media->isForceDeleting()) { return; } /** @var Filesystem $filesystem */ $filesystem = app(Filesystem::class); $filesystem->removeAllFiles($media); } } laravel-medialibrary/src/MediaCollections/Models/Collections/MediaCollection.php 0000644 00000004676 15021222773 0024124 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Models\Collections; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Collection; use Spatie\MediaLibrary\MediaCollections\Models\Media; /** * @template TKey of array-key * @template TModel of \Spatie\MediaLibrary\MediaCollections\Models\Media * * @extends Collection<TKey, TModel> */ class MediaCollection extends Collection implements Htmlable { public ?string $collectionName = null; public ?string $formFieldName = null; public function collectionName(string $collectionName): self { $this->collectionName = $collectionName; return $this; } public function formFieldName(string $formFieldName): self { $this->formFieldName = $formFieldName; return $this; } public function totalSizeInBytes(): int { return $this->sum('size'); } public function toHtml() { return e(json_encode(old($this->formFieldName ?? $this->collectionName) ?? $this->map(function (Media $media) { return [ 'name' => $media->name, 'file_name' => $media->file_name, 'uuid' => $media->uuid, 'preview_url' => $media->preview_url, 'original_url' => $media->original_url, 'order' => $media->order_column, 'custom_properties' => $media->custom_properties, 'extension' => $media->extension, 'size' => $media->size, ]; })->keyBy('uuid'))); } public function jsonSerialize(): array { if (config('media-library.use_default_collection_serialization')) { return parent::jsonSerialize(); } if (! ($this->formFieldName ?? $this->collectionName)) { return []; } return old($this->formFieldName ?? $this->collectionName) ?? $this->map(function (Media $media) { return [ 'name' => $media->name, 'file_name' => $media->file_name, 'uuid' => $media->uuid, 'preview_url' => $media->preview_url, 'original_url' => $media->original_url, 'order' => $media->order_column, 'custom_properties' => $media->custom_properties, 'extension' => $media->extension, 'size' => $media->size, ]; })->keyBy('uuid')->toArray(); } } laravel-medialibrary/src/MediaCollections/Models/Concerns/HasUuid.php 0000644 00000001116 15021222773 0021711 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Models\Concerns; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; trait HasUuid { public static function bootHasUuid() { static::creating(function (Model $model) { /** @var \Spatie\MediaLibrary\MediaCollections\Models\Media $model */ if (empty($model->uuid)) { $model->uuid = (string) Str::uuid(); } }); } public static function findByUuid(string $uuid): ?Model { return static::where('uuid', $uuid)->first(); } } laravel-medialibrary/src/MediaCollections/Models/Concerns/IsSorted.php 0000644 00000003030 15021222773 0022100 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Models\Concerns; use Illuminate\Database\Eloquent\Builder; trait IsSorted { public function setHighestOrderNumber(): void { $orderColumnName = $this->determineOrderColumnName(); $this->$orderColumnName = $this->getHighestOrderNumber() + 1; } public function getHighestOrderNumber(): int { return (int) static::where('model_type', $this->model_type) ->where('model_id', $this->model_id) ->max($this->determineOrderColumnName()); } public function scopeOrdered(Builder $query): Builder { return $query->orderBy($this->determineOrderColumnName()); } /* * This function reorders the records: the record with the first id in the array * will get order 1, the record with the second it will get order 2, ... * * A starting order number can be optionally supplied. */ public static function setNewOrder(array $ids, int $startOrder = 1): void { foreach ($ids as $id) { $model = static::find($id); $orderColumnName = $model->determineOrderColumnName(); $model->$orderColumnName = $startOrder++; $model->save(); } } protected function determineOrderColumnName(): string { return $this->sortable['order_column_name'] ?? 'order_column'; } public function shouldSortWhenCreating(): bool { return $this->sortable['sort_when_creating'] ?? true; } } laravel-medialibrary/src/MediaCollections/Models/Concerns/CustomMediaProperties.php 0000644 00000000612 15021222773 0024636 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Models\Concerns; trait CustomMediaProperties { public function setCustomHeaders(array $customHeaders): self { $this->setCustomProperty('custom_headers', $customHeaders); return $this; } public function getCustomHeaders(): array { return $this->getCustomProperty('custom_headers', []); } } laravel-medialibrary/src/MediaCollections/Models/Media.php 0000644 00000033246 15021222773 0017625 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Models; use DateTimeInterface; use Illuminate\Contracts\Mail\Attachable; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Contracts\Support\Responsable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Mail\Attachment; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\Conversions\ConversionCollection; use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\HtmlableMedia; use Spatie\MediaLibrary\MediaCollections\Models\Collections\MediaCollection; use Spatie\MediaLibrary\MediaCollections\Models\Concerns\CustomMediaProperties; use Spatie\MediaLibrary\MediaCollections\Models\Concerns\HasUuid; use Spatie\MediaLibrary\MediaCollections\Models\Concerns\IsSorted; use Spatie\MediaLibrary\ResponsiveImages\RegisteredResponsiveImages; use Spatie\MediaLibrary\Support\File; use Spatie\MediaLibrary\Support\MediaLibraryPro; use Spatie\MediaLibrary\Support\TemporaryDirectory; use Spatie\MediaLibrary\Support\UrlGenerator\UrlGenerator; use Spatie\MediaLibrary\Support\UrlGenerator\UrlGeneratorFactory; use Spatie\MediaLibraryPro\Models\TemporaryUpload; /** * @property string $uuid * @property string $model_type * @property string|int $model_id * @property string $collection_name * @property string $name * @property string $file_name * @property string $mime_type * @property string $disk * @property string $conversions_disk * @property string $type * @property string $extension * @property-read string $humanReadableSize * @property-read string $preview_url * @property-read string $original_url * @property int $size * @property ?int $order_column * @property array $manipulations * @property array $custom_properties * @property array $generated_conversions * @property array $responsive_images * @property-read ?\Illuminate\Support\Carbon $created_at * @property-read ?\Illuminate\Support\Carbon $updated_at */ class Media extends Model implements Responsable, Htmlable, Attachable { use IsSorted; use CustomMediaProperties; use HasUuid; protected $table = 'media'; public const TYPE_OTHER = 'other'; protected $guarded = []; protected $appends = ['original_url', 'preview_url']; protected $casts = [ 'manipulations' => 'array', 'custom_properties' => 'array', 'generated_conversions' => 'array', 'responsive_images' => 'array', ]; public function newCollection(array $models = []) { return new MediaCollection($models); } public function model(): MorphTo { return $this->morphTo(); } public function getFullUrl(string $conversionName = ''): string { return url($this->getUrl($conversionName)); } public function getUrl(string $conversionName = ''): string { $urlGenerator = UrlGeneratorFactory::createForMedia($this, $conversionName); return $urlGenerator->getUrl(); } public function getTemporaryUrl(DateTimeInterface $expiration, string $conversionName = '', array $options = []): string { $urlGenerator = $this->getUrlGenerator($conversionName); return $urlGenerator->getTemporaryUrl($expiration, $options); } public function getPath(string $conversionName = ''): string { $urlGenerator = $this->getUrlGenerator($conversionName); return $urlGenerator->getPath(); } public function getPathRelativeToRoot(string $conversionName = ''): string { return $this->getUrlGenerator($conversionName)->getPathRelativeToRoot(); } public function getUrlGenerator(string $conversionName): UrlGenerator { return UrlGeneratorFactory::createForMedia($this, $conversionName); } public function getAvailableUrl(array $conversionNames): string { foreach ($conversionNames as $conversionName) { if (! $this->hasGeneratedConversion($conversionName)) { continue; } return $this->getUrl($conversionName); } return $this->getUrl(); } public function getAvailableFullUrl(array $conversionNames): string { foreach ($conversionNames as $conversionName) { if (! $this->hasGeneratedConversion($conversionName)) { continue; } return $this->getFullUrl($conversionName); } return $this->getFullUrl(); } public function getAvailablePath(array $conversionNames): string { foreach ($conversionNames as $conversionName) { if (! $this->hasGeneratedConversion($conversionName)) { continue; } return $this->getPath($conversionName); } return $this->getPath(); } protected function type(): Attribute { return Attribute::get( function () { $type = $this->getTypeFromExtension(); if ($type !== self::TYPE_OTHER) { return $type; } return $this->getTypeFromMime(); } ); } public function getTypeFromExtension(): string { $imageGenerator = ImageGeneratorFactory::forExtension($this->extension); return $imageGenerator ? $imageGenerator->getType() : static::TYPE_OTHER; } public function getTypeFromMime(): string { $imageGenerator = ImageGeneratorFactory::forMimeType($this->mime_type); return $imageGenerator ? $imageGenerator->getType() : static::TYPE_OTHER; } protected function extension(): Attribute { return Attribute::get(fn () => pathinfo($this->file_name, PATHINFO_EXTENSION)); } protected function humanReadableSize(): Attribute { return Attribute::get(fn () => File::getHumanReadableSize($this->size)); } public function getDiskDriverName(): string { return strtolower(config("filesystems.disks.{$this->disk}.driver")); } public function getConversionsDiskDriverName(): string { $diskName = $this->conversions_disk ?? $this->disk; return strtolower(config("filesystems.disks.{$diskName}.driver")); } public function hasCustomProperty(string $propertyName): bool { return Arr::has($this->custom_properties, $propertyName); } /** * Get the value of custom property with the given name. * * @param string $propertyName * @param mixed $default * * @return mixed */ public function getCustomProperty(string $propertyName, $default = null): mixed { return Arr::get($this->custom_properties, $propertyName, $default); } /** * @param mixed $value * * @return $this */ public function setCustomProperty(string $name, $value): self { $customProperties = $this->custom_properties; Arr::set($customProperties, $name, $value); $this->custom_properties = $customProperties; return $this; } public function forgetCustomProperty(string $name): self { $customProperties = $this->custom_properties; Arr::forget($customProperties, $name); $this->custom_properties = $customProperties; return $this; } public function getMediaConversionNames(): array { $conversions = ConversionCollection::createForMedia($this); return $conversions->map(fn (Conversion $conversion) => $conversion->getName())->toArray(); } public function getGeneratedConversions(): Collection { return collect($this->generated_conversions ?? []); } public function markAsConversionGenerated(string $conversionName): self { $generatedConversions = $this->generated_conversions; Arr::set($generatedConversions, $conversionName, true); $this->generated_conversions = $generatedConversions; $this->saveOrTouch(); return $this; } public function markAsConversionNotGenerated(string $conversionName): self { $generatedConversions = $this->generated_conversions; Arr::set($generatedConversions, $conversionName, false); $this->generated_conversions = $generatedConversions; $this->saveOrTouch(); return $this; } public function hasGeneratedConversion(string $conversionName): bool { $generatedConversions = $this->getGeneratedConversions(); return $generatedConversions[$conversionName] ?? false; } public function toResponse($request) { return $this->buildResponse($request, 'attachment'); } public function toInlineResponse($request) { return $this->buildResponse($request, 'inline'); } private function buildResponse($request, string $contentDispositionType) { $downloadHeaders = [ 'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0', 'Content-Type' => $this->mime_type, 'Content-Length' => $this->size, 'Content-Disposition' => $contentDispositionType . '; filename="' . $this->file_name . '"', 'Pragma' => 'public', ]; return response()->stream(function () { $stream = $this->stream(); fpassthru($stream); if (is_resource($stream)) { fclose($stream); } }, 200, $downloadHeaders); } public function getResponsiveImageUrls(string $conversionName = ''): array { return $this->responsiveImages($conversionName)->getUrls(); } public function hasResponsiveImages(string $conversionName = ''): bool { return count($this->getResponsiveImageUrls($conversionName)) > 0; } public function getSrcset(string $conversionName = ''): string { return $this->responsiveImages($conversionName)->getSrcset(); } protected function previewUrl(): Attribute { return Attribute::get( fn () => $this->hasGeneratedConversion('preview') ? $this->getUrl('preview') : '', ); } protected function originalUrl(): Attribute { return Attribute::get(fn () => $this->getUrl()); } /** @param string $collectionName */ public function move(HasMedia $model, $collectionName = 'default', string $diskName = '', string $fileName = ''): self { $newMedia = $this->copy($model, $collectionName, $diskName, $fileName); $this->forceDelete(); return $newMedia; } /** @param string $collectionName */ public function copy(HasMedia $model, $collectionName = 'default', string $diskName = '', string $fileName = ''): self { $temporaryDirectory = TemporaryDirectory::create(); $temporaryFile = $temporaryDirectory->path('/') . DIRECTORY_SEPARATOR . $this->file_name; /** @var Filesystem $filesystem */ $filesystem = app(Filesystem::class); $filesystem->copyFromMediaLibrary($this, $temporaryFile); $fileAdder = $model ->addMedia($temporaryFile) ->usingName($this->name) ->setOrder($this->order_column) ->withCustomProperties($this->custom_properties); if ($fileName !== '') { $fileAdder->usingFileName($fileName); } $newMedia = $fileAdder ->toMediaCollection($collectionName, $diskName); $temporaryDirectory->delete(); return $newMedia; } public function responsiveImages(string $conversionName = ''): RegisteredResponsiveImages { return new RegisteredResponsiveImages($this, $conversionName); } public function stream() { /** @var Filesystem $filesystem */ $filesystem = app(Filesystem::class); return $filesystem->getStream($this); } public function toHtml() { return $this->img()->toHtml(); } public function img(string $conversionName = '', $extraAttributes = []): HtmlableMedia { return (new HtmlableMedia($this)) ->conversion($conversionName) ->attributes($extraAttributes); } public function __invoke(...$arguments): HtmlableMedia { return $this->img(...$arguments); } public function temporaryUpload(): BelongsTo { MediaLibraryPro::ensureInstalled(); return $this->belongsTo(TemporaryUpload::class); } public static function findWithTemporaryUploadInCurrentSession(array $uuids) { MediaLibraryPro::ensureInstalled(); return static::query() ->whereIn('uuid', $uuids) ->whereHasMorph( 'model', [TemporaryUpload::class], fn (Builder $builder) => $builder->where('session_id', session()->getId()) ) ->get(); } public function mailAttachment(string $conversion = ''): Attachment { $attachment = Attachment::fromStorageDisk($this->disk, $this->getPathRelativeToRoot($conversion))->as($this->file_name); if ($this->mime_type) { $attachment->withMime($this->mime_type); } return $attachment; } public function toMailAttachment(): Attachment { return $this->mailAttachment(); } protected function saveOrTouch(): bool { if (! $this->exists || $this->isDirty()) { return $this->save(); } return $this->touch(); } } laravel-medialibrary/src/MediaCollections/HtmlableMedia.php 0000644 00000005545 15021222773 0020054 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections; use Illuminate\Contracts\Support\Htmlable; use Spatie\MediaLibrary\Conversions\ConversionCollection; use Spatie\MediaLibrary\Conversions\ImageGenerators\Image; use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory; use Spatie\MediaLibrary\MediaCollections\Models\Media; class HtmlableMedia implements Htmlable, \Stringable { protected string $conversionName = ''; protected array $extraAttributes = []; protected string $loadingAttributeValue = ''; public function __construct( protected Media $media ) { } public function attributes(array $attributes): self { $this->extraAttributes = $attributes; return $this; } public function conversion(string $conversionName): self { $this->conversionName = $conversionName; return $this; } public function lazy(): self { $this->loadingAttributeValue = ('lazy'); return $this; } public function toHtml() { $imageGenerator = ImageGeneratorFactory::forMedia($this->media) ?? new Image(); if (! $imageGenerator->canHandleMime($this->media->mime_type)) { return ''; } $attributeString = collect($this->extraAttributes) ->map(fn ($value, $name) => $name.'="'.$value.'"')->implode(' '); if (strlen($attributeString)) { $attributeString = ' '.$attributeString; } $loadingAttributeValue = config('media-library.default_loading_attribute_value'); if ($this->conversionName !== '') { $conversionObject = ConversionCollection::createForMedia($this->media)->getByName($this->conversionName); $loadingAttributeValue = $conversionObject->getLoadingAttributeValue(); } if ($this->loadingAttributeValue !== '') { $loadingAttributeValue = $this->loadingAttributeValue; } $viewName = 'image'; $width = ''; $height = ''; if ($this->media->hasResponsiveImages($this->conversionName)) { $viewName = config('media-library.responsive_images.use_tiny_placeholders') ? 'responsiveImageWithPlaceholder' : 'responsiveImage'; $responsiveImage = $this->media->responsiveImages($this->conversionName)->files->first(); $width = $responsiveImage->width(); $height = $responsiveImage->height(); } $media = $this->media; $conversion = $this->conversionName; return view("media-library::{$viewName}", compact( 'media', 'conversion', 'attributeString', 'loadingAttributeValue', 'width', 'height', ))->render(); } public function __toString(): string { return $this->toHtml(); } } laravel-medialibrary/src/MediaCollections/FileAdder.php 0000644 00000041054 15021222773 0017176 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections; use Closure; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Traits\Macroable; use Spatie\MediaLibrary\Conversions\ImageGenerators\Image as ImageGenerator; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\MediaCollections\Exceptions\DiskCannotBeAccessed; use Spatie\MediaLibrary\MediaCollections\Exceptions\DiskDoesNotExist; use Spatie\MediaLibrary\MediaCollections\Exceptions\FileDoesNotExist; use Spatie\MediaLibrary\MediaCollections\Exceptions\FileIsTooBig; use Spatie\MediaLibrary\MediaCollections\Exceptions\FileUnacceptableForCollection; use Spatie\MediaLibrary\MediaCollections\Exceptions\UnknownType; use Spatie\MediaLibrary\MediaCollections\File as PendingFile; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob; use Spatie\MediaLibrary\Support\File; use Spatie\MediaLibrary\Support\RemoteFile; use Spatie\MediaLibraryPro\Models\TemporaryUpload; use Symfony\Component\HttpFoundation\File\File as SymfonyFile; use Symfony\Component\HttpFoundation\File\UploadedFile; class FileAdder { use Macroable; protected ?HasMedia $subject = null; protected bool $preserveOriginal = false; /** @var UploadedFile|RemoteFile|SymfonyFile|string */ protected $file; protected array $properties = []; protected array $customProperties = []; protected array $manipulations = []; protected string $pathToFile = ''; protected string $fileName = ''; protected string $mediaName = ''; protected string $diskName = ''; protected ?int $fileSize = null; protected string $conversionsDiskName = ''; protected ?Closure $fileNameSanitizer; protected bool $generateResponsiveImages = false; protected array $customHeaders = []; public ?int $order = null; public function __construct( protected ?Filesystem $filesystem ) { $this->fileNameSanitizer = fn ($fileName) => $this->defaultSanitizer($fileName); } public function setSubject(Model $subject): self { /** @var HasMedia $subject */ $this->subject = $subject; return $this; } /* * Set the file that needs to be imported. * * @param string|UploadedFile $file * * @return $this */ public function setFile($file): self { $this->file = $file; if (is_string($file)) { $this->pathToFile = $file; $this->setFileName(pathinfo($file, PATHINFO_BASENAME)); $this->mediaName = pathinfo($file, PATHINFO_FILENAME); return $this; } if ($file instanceof RemoteFile) { $this->pathToFile = $file->getKey(); $this->setFileName($file->getFilename()); $this->mediaName = $file->getName(); return $this; } if ($file instanceof UploadedFile) { $this->pathToFile = $file->getPath().'/'.$file->getFilename(); $this->setFileName($file->getClientOriginalName()); $this->mediaName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); return $this; } if ($file instanceof SymfonyFile) { $this->pathToFile = $file->getPath().'/'.$file->getFilename(); $this->setFileName(pathinfo($file->getFilename(), PATHINFO_BASENAME)); $this->mediaName = pathinfo($file->getFilename(), PATHINFO_FILENAME); return $this; } if ($file instanceof TemporaryUpload) { return $this; } throw UnknownType::create(); } public function preservingOriginal(bool $preserveOriginal = true): self { $this->preserveOriginal = $preserveOriginal; return $this; } public function usingName(string $name): self { return $this->setName($name); } public function setName(string $name): self { $this->mediaName = $name; return $this; } public function setOrder(?int $order): self { $this->order = $order; return $this; } public function usingFileName(string $fileName): self { return $this->setFileName($fileName); } public function setFileName(string $fileName): self { $this->fileName = $fileName; return $this; } public function setFileSize(int $fileSize): self { $this->fileSize = $fileSize; return $this; } public function withCustomProperties(array $customProperties): self { $this->customProperties = $customProperties; return $this; } public function storingConversionsOnDisk(string $diskName): self { $this->conversionsDiskName = $diskName; return $this; } public function withManipulations(array $manipulations): self { $this->manipulations = $manipulations; return $this; } public function withProperties(array $properties): self { $this->properties = $properties; return $this; } public function withAttributes(array $properties): self { return $this->withProperties($properties); } public function withResponsiveImages(): self { $this->generateResponsiveImages = true; return $this; } public function withResponsiveImagesIf($condition): self { $this->generateResponsiveImages = (bool) (is_callable($condition) ? $condition() : $condition); return $this; } public function addCustomHeaders(array $customRemoteHeaders): self { $this->customHeaders = $customRemoteHeaders; $this->filesystem->addCustomRemoteHeaders($customRemoteHeaders); return $this; } public function toMediaCollectionOnCloudDisk(string $collectionName = 'default'): Media { return $this->toMediaCollection($collectionName, config('filesystems.cloud')); } public function toMediaCollectionFromRemote(string $collectionName = 'default', string $diskName = ''): Media { $storage = Storage::disk($this->file->getDisk()); if (! $storage->exists($this->pathToFile)) { throw FileDoesNotExist::create($this->pathToFile); } $this->fileSize ??= $storage->size($this->pathToFile); if ($this->fileSize > config('media-library.max_file_size')) { throw FileIsTooBig::create($this->pathToFile, $storage->size($this->pathToFile)); } $mediaClass = config('media-library.media_model'); /** @var Media $media */ $media = new $mediaClass(); $media->name = $this->mediaName; $sanitizedFileName = ($this->fileNameSanitizer)($this->fileName); $fileName = app(config('media-library.file_namer'))->originalFileName($sanitizedFileName); $this->fileName = $this->appendExtension($fileName, pathinfo($sanitizedFileName, PATHINFO_EXTENSION)); $media->file_name = $this->fileName; $media->disk = $this->determineDiskName($diskName, $collectionName); $this->ensureDiskExists($media->disk); $media->conversions_disk = $this->determineConversionsDiskName($media->disk, $collectionName); $this->ensureDiskExists($media->conversions_disk); $media->collection_name = $collectionName; $media->mime_type = $storage->mimeType($this->pathToFile); $media->size = $this->fileSize; $media->custom_properties = $this->customProperties; $media->generated_conversions = []; $media->responsive_images = []; $media->manipulations = $this->manipulations; if (filled($this->customHeaders)) { $media->setCustomHeaders($this->customHeaders); } $media->fill($this->properties); $this->attachMedia($media); return $media; } public function toMediaCollection(string $collectionName = 'default', string $diskName = ''): Media { $sanitizedFileName = ($this->fileNameSanitizer)($this->fileName); $fileName = app(config('media-library.file_namer'))->originalFileName($sanitizedFileName); $this->fileName = $this->appendExtension($fileName, pathinfo($sanitizedFileName, PATHINFO_EXTENSION)); if ($this->file instanceof RemoteFile) { return $this->toMediaCollectionFromRemote($collectionName, $diskName); } if ($this->file instanceof TemporaryUpload) { return $this->toMediaCollectionFromTemporaryUpload($collectionName, $diskName, $this->fileName); } if (! is_file($this->pathToFile)) { throw FileDoesNotExist::create($this->pathToFile); } $this->fileSize ??= filesize($this->pathToFile); if ($this->fileSize > config('media-library.max_file_size')) { throw FileIsTooBig::create($this->pathToFile); } $mediaClass = config('media-library.media_model'); /** @var Media $media */ $media = new $mediaClass(); $media->name = $this->mediaName; $media->file_name = $this->fileName; $media->disk = $this->determineDiskName($diskName, $collectionName); $this->ensureDiskExists($media->disk); $media->conversions_disk = $this->determineConversionsDiskName($media->disk, $collectionName); $this->ensureDiskExists($media->conversions_disk); $media->collection_name = $collectionName; $media->mime_type = File::getMimeType($this->pathToFile); $media->size = $this->fileSize; if (! is_null($this->order)) { $media->order_column = $this->order; } $media->custom_properties = $this->customProperties; $media->generated_conversions = []; $media->responsive_images = []; $media->manipulations = $this->manipulations; if (filled($this->customHeaders)) { $media->setCustomHeaders($this->customHeaders); } $media->fill($this->properties); $this->attachMedia($media); return $media; } public function toMediaLibrary(string $collectionName = 'default', string $diskName = ''): Media { return $this->toMediaCollection($collectionName, $diskName); } protected function determineDiskName(string $diskName, string $collectionName): string { if ($diskName !== '') { return $diskName; } if ($collection = $this->getMediaCollection($collectionName)) { $collectionDiskName = $collection->diskName; if ($collectionDiskName !== '') { return $collectionDiskName; } } return config('media-library.disk_name'); } protected function determineConversionsDiskName(string $originalsDiskName, string $collectionName): string { if ($this->conversionsDiskName !== '') { return $this->conversionsDiskName; } if ($collection = $this->getMediaCollection($collectionName)) { $collectionConversionsDiskName = $collection->conversionsDiskName; if ($collectionConversionsDiskName !== '') { return $collectionConversionsDiskName; } } return $originalsDiskName; } protected function ensureDiskExists(string $diskName) { if (is_null(config("filesystems.disks.{$diskName}"))) { throw DiskDoesNotExist::create($diskName); } } public function defaultSanitizer(string $fileName): string { $fileName = preg_replace('#\p{C}+#u', '', $fileName); return str_replace(['#', '/', '\\', ' '], '-', $fileName); } public function sanitizingFileName(callable $fileNameSanitizer): self { $this->fileNameSanitizer = $fileNameSanitizer; return $this; } protected function attachMedia(Media $media) { if (! $this->subject->exists) { $this->subject->prepareToAttachMedia($media, $this); $class = $this->subject::class; $class::created(function ($model) { $model->processUnattachedMedia(function (Media $media, self $fileAdder) use ($model) { $this->processMediaItem($model, $media, $fileAdder); }); }); return; } $this->processMediaItem($this->subject, $media, $this); } protected function processMediaItem(HasMedia $model, Media $media, self $fileAdder) { $this->guardAgainstDisallowedFileAdditions($media); $this->checkGenerateResponsiveImages($media); if (! $media->getConnectionName()) { $media->setConnection($model->getConnectionName()); } $model->media()->save($media); if ($fileAdder->file instanceof RemoteFile) { $addedMediaSuccessfully = $this->filesystem->addRemote($fileAdder->file, $media, $fileAdder->fileName); } else { $addedMediaSuccessfully = $this->filesystem->add($fileAdder->pathToFile, $media, $fileAdder->fileName); } if (! $addedMediaSuccessfully) { $media->forceDelete(); throw DiskCannotBeAccessed::create($media->disk); } if (! $fileAdder->preserveOriginal) { if ($fileAdder->file instanceof RemoteFile) { Storage::disk($fileAdder->file->getDisk())->delete($fileAdder->file->getKey()); } else { unlink($fileAdder->pathToFile); } } if ($this->generateResponsiveImages && (new ImageGenerator())->canConvert($media)) { $generateResponsiveImagesJobClass = config('media-library.jobs.generate_responsive_images', GenerateResponsiveImagesJob::class); $job = new $generateResponsiveImagesJobClass($media); if ($customConnection = config('media-library.queue_connection_name')) { $job->onConnection($customConnection); } if ($customQueue = config('media-library.queue_name')) { $job->onQueue($customQueue); } dispatch($job); } if ($collectionSizeLimit = optional($this->getMediaCollection($media->collection_name))->collectionSizeLimit) { /** @var HasMedia */ $subject = $this->subject->fresh(); $collectionMedia = $subject->getMedia($media->collection_name); if ($collectionMedia->count() > $collectionSizeLimit) { $model->clearMediaCollectionExcept($media->collection_name, $collectionMedia->slice(-$collectionSizeLimit, $collectionSizeLimit)); } } } protected function getMediaCollection(string $collectionName): ?MediaCollection { $this->subject->registerMediaCollections(); return collect($this->subject->mediaCollections) ->first(fn (MediaCollection $collection) => $collection->name === $collectionName); } protected function guardAgainstDisallowedFileAdditions(Media $media) { $file = PendingFile::createFromMedia($media); if (! $collection = $this->getMediaCollection($media->collection_name)) { return; } if (! ($collection->acceptsFile)($file, $this->subject)) { throw FileUnacceptableForCollection::create($file, $collection, $this->subject); } if (! empty($collection->acceptsMimeTypes) && ! in_array($file->mimeType, $collection->acceptsMimeTypes)) { throw FileUnacceptableForCollection::create($file, $collection, $this->subject); } } protected function checkGenerateResponsiveImages(Media $media) { $collection = optional($this->getMediaCollection($media->collection_name))->generateResponsiveImages; if ($collection) { $this->withResponsiveImages(); } } protected function toMediaCollectionFromTemporaryUpload(string $collectionName, string $diskName, string $fileName = ''): Media { /** @var TemporaryUpload $temporaryUpload */ $temporaryUpload = $this->file; /** @var Media */ $media = $temporaryUpload->getFirstMedia(); $media->name = $this->mediaName; $media->custom_properties = $this->customProperties; if (! is_null($this->order)) { $media->order_column = $this->order; } $media->setCustomHeaders($this->customHeaders); $media->save(); return $temporaryUpload->moveMedia($this->subject, $collectionName, $diskName, $fileName); } protected function appendExtension(string $file, ?string $extension): string { return $extension ? $file . '.' . $extension : $file; } } laravel-medialibrary/src/MediaCollections/Filesystem.php 0000644 00000024173 15021222773 0017506 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections; use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Spatie\MediaLibrary\Conversions\ConversionCollection; use Spatie\MediaLibrary\Conversions\FileManipulator; use Spatie\MediaLibrary\MediaCollections\Events\MediaHasBeenAdded; use Spatie\MediaLibrary\MediaCollections\Exceptions\DiskCannotBeAccessed; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\File; use Spatie\MediaLibrary\Support\FileRemover\FileRemoverFactory; use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory; use Spatie\MediaLibrary\Support\RemoteFile; class Filesystem { protected array $customRemoteHeaders = []; public function __construct( protected Factory $filesystem ) { } public function add(string $file, Media $media, ?string $targetFileName = null): bool { try { $this->copyToMediaLibrary($file, $media, null, $targetFileName); } catch(DiskCannotBeAccessed $exception) { return false; } event(new MediaHasBeenAdded($media)); app(FileManipulator::class)->createDerivedFiles($media); return true; } public function addRemote(RemoteFile $file, Media $media, ?string $targetFileName = null): bool { try { $this->copyToMediaLibraryFromRemote($file, $media, null, $targetFileName); } catch(DiskCannotBeAccessed $exception) { return false; } event(new MediaHasBeenAdded($media)); app(FileManipulator::class)->createDerivedFiles($media); return true; } public function prepareCopyFileOnDisk(RemoteFile $file, Media $media, string $destination): void { $this->copyFileOnDisk($file->getKey(), $destination, $media->disk); } public function copyToMediaLibraryFromRemote(RemoteFile $file, Media $media, ?string $type = null, ?string $targetFileName = null): void { $destinationFileName = $targetFileName ?: $file->getFilename(); $destination = $this->getMediaDirectory($media, $type).$destinationFileName; $diskDriverName = (in_array($type, ['conversions', 'responsiveImages'])) ? $media->getConversionsDiskDriverName() : $media->getDiskDriverName(); if ($this->shouldCopyFileOnDisk($file, $media, $diskDriverName)) { $this->prepareCopyFileOnDisk($file, $media, $destination); return; } $storage = Storage::disk($file->getDisk()); $headers = $diskDriverName === 'local' ? [] : $this->getRemoteHeadersForFile( $file->getKey(), $media->getCustomHeaders(), $storage->mimeType($file->getKey()) ); $this->streamFileToDisk( $storage->getDriver()->readStream($file->getKey()), $destination, $media->disk, $headers ); } protected function shouldCopyFileOnDisk(RemoteFile $file, Media $media, string $diskDriverName): bool { if ($file->getDisk() !== $media->disk) { return false; } if ($diskDriverName === 'local') { return true; } if (count($media->getCustomHeaders()) > 0) { return false; } if ((is_countable(config('media-library.remote.extra_headers')) ? count(config('media-library.remote.extra_headers')) : 0) > 0) { return false; } return true; } protected function copyFileOnDisk(string $file, string $destination, string $disk): void { $this->filesystem->disk($disk) ->copy($file, $destination); } protected function streamFileToDisk($stream, string $destination, string $disk, array $headers): void { $this->filesystem->disk($disk) ->getDriver()->writeStream( $destination, $stream, $headers ); } public function copyToMediaLibrary(string $pathToFile, Media $media, ?string $type = null, ?string $targetFileName = null) { $destinationFileName = $targetFileName ?: pathinfo($pathToFile, PATHINFO_BASENAME); $destination = $this->getMediaDirectory($media, $type).$destinationFileName; $file = fopen($pathToFile, 'r'); $diskName = (in_array($type, ['conversions', 'responsiveImages'])) ? $media->conversions_disk : $media->disk; $diskDriverName = (in_array($type, ['conversions', 'responsiveImages'])) ? $media->getConversionsDiskDriverName() : $media->getDiskDriverName(); if ($diskDriverName === 'local') { $success = $this->filesystem ->disk($diskName) ->put($destination, $file); fclose($file); if (! $success) { throw DiskCannotBeAccessed::create($diskName); } return; } $success = $this->filesystem ->disk($diskName) ->put( $destination, $file, $this->getRemoteHeadersForFile($pathToFile, $media->getCustomHeaders()), ); if (is_resource($file)) { fclose($file); } if (! $success) { throw DiskCannotBeAccessed::create($diskName); } } public function addCustomRemoteHeaders(array $customRemoteHeaders): void { $this->customRemoteHeaders = $customRemoteHeaders; } public function getRemoteHeadersForFile( string $file, array $mediaCustomHeaders = [], string $mimeType = null ): array { $mimeTypeHeader = ['ContentType' => $mimeType ?: File::getMimeType($file)]; $extraHeaders = config('media-library.remote.extra_headers'); return array_merge( $mimeTypeHeader, $extraHeaders, $this->customRemoteHeaders, $mediaCustomHeaders ); } public function getStream(Media $media) { $sourceFile = $this->getMediaDirectory($media).'/'.$media->file_name; return $this->filesystem->disk($media->disk)->readStream($sourceFile); } public function copyFromMediaLibrary(Media $media, string $targetFile): string { file_put_contents($targetFile, $this->getStream($media)); return $targetFile; } public function removeAllFiles(Media $media): void { $fileRemover = FileRemoverFactory::create($media); $fileRemover->removeAllFiles($media); } public function removeFile(Media $media, string $path): void { $fileRemover = FileRemoverFactory::create($media); $fileRemover->removeFile($path, $media->disk); } public function removeResponsiveImages(Media $media, string $conversionName = 'media_library_original'): void { $responsiveImagesDirectory = $this->getResponsiveImagesDirectory($media); $allFilePaths = $this->filesystem->disk($media->disk)->allFiles($responsiveImagesDirectory); $responsiveImagePaths = array_filter( $allFilePaths, fn (string $path) => Str::contains($path, $conversionName) ); $this->filesystem->disk($media->disk)->delete($responsiveImagePaths); } public function syncFileNames(Media $media): void { $this->renameMediaFile($media); $this->renameConversionFiles($media); } public function syncMediaPath(Media $media): void { $factory = PathGeneratorFactory::create($media); $oldMedia = (clone $media)->fill($media->getOriginal()); if ($factory->getPath($oldMedia) === $factory->getPath($media)) { return; } $this->filesystem->disk($media->disk) ->move($factory->getPath($oldMedia), $factory->getPath($media)); } protected function renameMediaFile(Media $media): void { $newFileName = $media->file_name; $oldFileName = $media->getOriginal('file_name'); $mediaDirectory = $this->getMediaDirectory($media); $oldFile = "{$mediaDirectory}/{$oldFileName}"; $newFile = "{$mediaDirectory}/{$newFileName}"; $this->filesystem->disk($media->disk)->move($oldFile, $newFile); } protected function renameConversionFiles(Media $media): void { $mediaWithOldFileName = config('media-library.media_model')::find($media->id); $mediaWithOldFileName->file_name = $mediaWithOldFileName->getOriginal('file_name'); $conversionDirectory = $this->getConversionDirectory($media); $conversionCollection = ConversionCollection::createForMedia($media); foreach ($media->getMediaConversionNames() as $conversionName) { $conversion = $conversionCollection->getByName($conversionName); $oldFile = $conversionDirectory.$conversion->getConversionFile($mediaWithOldFileName); $newFile = $conversionDirectory.$conversion->getConversionFile($media); $disk = $this->filesystem->disk($media->conversions_disk); // A media conversion file might be missing, waiting to be generated, failed etc. if (! $disk->exists($oldFile)) { continue; } $disk->move($oldFile, $newFile); } } public function getMediaDirectory(Media $media, ?string $type = null): string { $directory = null; $pathGenerator = PathGeneratorFactory::create($media); if (! $type) { $directory = $pathGenerator->getPath($media); } if ($type === 'conversions') { $directory = $pathGenerator->getPathForConversions($media); } if ($type === 'responsiveImages') { $directory = $pathGenerator->getPathForResponsiveImages($media); } return $directory; } public function getConversionDirectory(Media $media): string { return $this->getMediaDirectory($media, 'conversions'); } public function getResponsiveImagesDirectory(Media $media): string { return $this->getMediaDirectory($media, 'responsiveImages'); } } laravel-medialibrary/src/MediaCollections/Commands/ClearCommand.php 0000644 00000003576 15021222773 0021454 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Commands; use Illuminate\Console\Command; use Illuminate\Console\ConfirmableTrait; use Illuminate\Database\Eloquent\Collection; use Spatie\MediaLibrary\MediaCollections\MediaRepository; use Spatie\MediaLibrary\MediaCollections\Models\Media; class ClearCommand extends Command { use ConfirmableTrait; protected $signature = 'media-library:clear {modelType?} {collectionName?} {-- force : Force the operation to run when in production}'; protected $description = 'Delete all items in a media collection.'; protected MediaRepository $mediaRepository; public function handle(MediaRepository $mediaRepository) { $this->mediaRepository = $mediaRepository; if (! $this->confirmToProceed()) { return; } $mediaItems = $this->getMediaItems(); $progressBar = $this->output->createProgressBar($mediaItems->count()); $mediaItems->each(function (Media $media) use ($progressBar) { $media->delete(); $progressBar->advance(); }); $progressBar->finish(); $this->info('All done!'); } /** @return Collection<int, Media> */ public function getMediaItems(): Collection { $modelType = $this->argument('modelType'); $collectionName = $this->argument('collectionName'); if (is_string($modelType) && is_string($collectionName)) { return $this->mediaRepository->getByModelTypeAndCollectionName( $modelType, $collectionName ); } if (is_string($modelType)) { return $this->mediaRepository->getByModelType($modelType); } if (is_string($collectionName)) { return $this->mediaRepository->getByCollectionName($collectionName); } return $this->mediaRepository->all(); } } laravel-medialibrary/src/MediaCollections/Commands/CleanCommand.php 0000644 00000020436 15021222773 0021442 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Commands; use Illuminate\Console\Command; use Illuminate\Console\ConfirmableTrait; use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Str; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\Conversions\ConversionCollection; use Spatie\MediaLibrary\Conversions\FileManipulator; use Spatie\MediaLibrary\MediaCollections\Exceptions\DiskDoesNotExist; use Spatie\MediaLibrary\MediaCollections\MediaRepository; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\ResponsiveImages\RegisteredResponsiveImages; use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory; class CleanCommand extends Command { use ConfirmableTrait; protected $signature = 'media-library:clean {modelType?} {collectionName?} {disk?} {--dry-run : List files that will be removed without removing them}, {--force : Force the operation to run when in production}, {--rate-limit= : Limit the number of requests per second}, {--delete-orphaned : Delete orphaned media items}, {--skip-conversions : Do not remove deprecated conversions}'; protected $description = 'Clean deprecated conversions and files without related model.'; protected MediaRepository $mediaRepository; protected FileManipulator $fileManipulator; protected Factory $fileSystem; protected bool $isDryRun = false; protected int $rateLimit = 0; public function handle( MediaRepository $mediaRepository, FileManipulator $fileManipulator, Factory $fileSystem, ) { $this->mediaRepository = $mediaRepository; $this->fileManipulator = $fileManipulator; $this->fileSystem = $fileSystem; if (! $this->confirmToProceed()) { return; } $this->isDryRun = $this->option('dry-run'); $this->rateLimit = (int) $this->option('rate-limit'); if ($this->option('delete-orphaned')) { $this->deleteOrphanedMediaItems(); } if (! $this->option('skip-conversions')) { $this->deleteFilesGeneratedForDeprecatedConversions(); } $this->deleteOrphanedDirectories(); $this->info('All done!'); } /** @return Collection<int, Media> */ public function getMediaItems(): Collection { $modelType = $this->argument('modelType'); $collectionName = $this->argument('collectionName'); if (is_string($modelType) && is_string($collectionName)) { return $this->mediaRepository->getByModelTypeAndCollectionName( $modelType, $collectionName ); } if (is_string($modelType)) { return $this->mediaRepository->getByModelType($modelType); } if (is_string($collectionName)) { return $this->mediaRepository->getByCollectionName($collectionName); } return $this->mediaRepository->all(); } protected function deleteOrphanedMediaItems(): void { $this->getOrphanedMediaItems()->each(function (Media $media): void { if ($this->isDryRun) { $this->info("Orphaned Media[id={$media->id}] found"); return; } $media->delete(); if ($this->rateLimit) { usleep((1 / $this->rateLimit) * 1_000_000); } $this->info("Orphaned Media[id={$media->id}] has been removed"); }); } /** @return Collection<int, Media> */ protected function getOrphanedMediaItems(): Collection { $collectionName = $this->argument('collectionName'); if (is_string($collectionName)) { return $this->mediaRepository->getOrphansByCollectionName($collectionName); } return $this->mediaRepository->getOrphans(); } protected function deleteFilesGeneratedForDeprecatedConversions(): void { $this->getMediaItems()->each(function (Media $media) { $this->deleteConversionFilesForDeprecatedConversions($media); if ($media->responsive_images) { $this->deleteDeprecatedResponsiveImages($media); } if ($this->rateLimit) { usleep((1 / $this->rateLimit) * 1_000_000 * 2); } }); } protected function deleteConversionFilesForDeprecatedConversions(Media $media): void { $conversionFilePaths = ConversionCollection::createForMedia($media)->getConversionsFiles($media->collection_name); $conversionPath = PathGeneratorFactory::create($media)->getPathForConversions($media); $currentFilePaths = $this->fileSystem->disk($media->disk)->files($conversionPath); collect($currentFilePaths) ->reject(fn (string $currentFilePath) => $conversionFilePaths->contains(basename($currentFilePath))) ->reject(fn (string $currentFilePath) => $media->file_name === basename($currentFilePath)) ->each(function (string $currentFilePath) use ($media) { if (! $this->isDryRun) { $this->fileSystem->disk($media->disk)->delete($currentFilePath); $this->markConversionAsRemoved($media, $currentFilePath); } $this->info("Deprecated conversion file `{$currentFilePath}` ".($this->isDryRun ? 'found' : 'has been removed')); }); } protected function deleteDeprecatedResponsiveImages(Media $media): void { $conversionNamesWithResponsiveImages = ConversionCollection::createForMedia($media) ->filter(fn (Conversion $conversion) => $conversion->shouldGenerateResponsiveImages()) ->map(fn (Conversion $conversion) => $conversion->getName()) ->push('media_library_original'); /** @var array<int, string> $responsiveImagesGeneratedFor */ $responsiveImagesGeneratedFor = array_keys($media->responsive_images); collect($responsiveImagesGeneratedFor) ->map(fn (string $generatedFor) => $media->responsiveImages($generatedFor)) ->reject(fn (RegisteredResponsiveImages $responsiveImages) => $conversionNamesWithResponsiveImages->contains($responsiveImages->generatedFor)) ->each(function (RegisteredResponsiveImages $responsiveImages) { if (! $this->isDryRun) { $responsiveImages->delete(); } }); } protected function deleteOrphanedDirectories(): void { $diskName = $this->argument('disk') ?: config('media-library.disk_name'); if (is_null(config("filesystems.disks.{$diskName}"))) { throw DiskDoesNotExist::create($diskName); } $mediaClass = config('media-library.media_model'); $mediaInstance = new $mediaClass(); $keyName = $mediaInstance->getKeyName(); $mediaIds = collect($this->mediaRepository->all()->pluck($keyName)->toArray()); /** @var array<int, string> */ $directories = $this->fileSystem->disk($diskName)->directories(); collect($directories) ->filter(fn (string $directory) => is_numeric($directory)) ->reject(fn (string $directory) => $mediaIds->contains((int) $directory)) ->each(function (string $directory) use ($diskName) { if (! $this->isDryRun) { $this->fileSystem->disk($diskName)->deleteDirectory($directory); } if ($this->rateLimit) { usleep((1 / $this->rateLimit) * 1_000_000); } $this->info("Orphaned media directory `{$directory}` ".($this->isDryRun ? 'found' : 'has been removed')); }); } protected function markConversionAsRemoved(Media $media, string $conversionPath): void { $conversionFile = pathinfo($conversionPath, PATHINFO_FILENAME); $generatedConversionName = null; $media->getGeneratedConversions() ->filter( fn (bool $isGenerated, string $generatedConversionName) => Str::contains($conversionFile, $generatedConversionName) ) ->each( fn (bool $isGenerated, string $conversionName) => $media->markAsConversionNotGenerated($conversionName) ); $media->save(); } } laravel-medialibrary/src/MediaCollections/Events/MediaHasBeenAdded.php 0000644 00000000426 15021222773 0022010 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Events; use Illuminate\Queue\SerializesModels; use Spatie\MediaLibrary\MediaCollections\Models\Media; class MediaHasBeenAdded { use SerializesModels; public function __construct(public Media $media) { } } laravel-medialibrary/src/MediaCollections/Events/CollectionHasBeenCleared.php 0000644 00000000452 15021222773 0023421 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Events; use Illuminate\Queue\SerializesModels; use Spatie\MediaLibrary\HasMedia; class CollectionHasBeenCleared { use SerializesModels; public function __construct(public HasMedia $model, public string $collectionName) { } } laravel-medialibrary/src/MediaCollections/Exceptions/RequestDoesNotHaveFile.php 0000644 00000000436 15021222773 0024027 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class RequestDoesNotHaveFile extends FileCannotBeAdded { public static function create(string $key): self { return new static("The current request does not have a file in a key named `{$key}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/FileIsTooBig.php 0000644 00000001025 15021222773 0021751 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Spatie\MediaLibrary\Support\File; class FileIsTooBig extends FileCannotBeAdded { public static function create(string $path, int $size = null): self { $fileSize = File::getHumanReadableSize($size ?: filesize($path)); $maxFileSize = File::getHumanReadableSize(config('media-library.max_file_size')); return new static("File `{$path}` has a size of {$fileSize} which is greater than the maximum allowed {$maxFileSize}"); } } laravel-medialibrary/src/MediaCollections/Exceptions/InvalidBase64Data.php 0000644 00000000352 15021222773 0022621 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class InvalidBase64Data extends FileCannotBeAdded { public static function create(): self { return new static('Invalid base64 data provided'); } } laravel-medialibrary/src/MediaCollections/Exceptions/FileCannotBeAdded.php 0000644 00000000212 15021222773 0022702 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; abstract class FileCannotBeAdded extends Exception { } laravel-medialibrary/src/MediaCollections/Exceptions/FileDoesNotExist.php 0000644 00000000366 15021222773 0022671 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class FileDoesNotExist extends FileCannotBeAdded { public static function create(string $path): self { return new static("File `{$path}` does not exist"); } } laravel-medialibrary/src/MediaCollections/Exceptions/DiskDoesNotExist.php 0000644 00000000414 15021222773 0022676 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class DiskDoesNotExist extends FileCannotBeAdded { public static function create(string $diskName): self { return new static("There is no filesystem disk named `{$diskName}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/InvalidConversion.php 0000644 00000000415 15021222773 0023130 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; class InvalidConversion extends Exception { public static function unknownName(string $name): self { return new static("There is no conversion named `{$name}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/UnreachableUrl.php 0000644 00000000364 15021222773 0022373 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class UnreachableUrl extends FileCannotBeAdded { public static function create(string $url): self { return new static("Url `{$url}` cannot be reached"); } } laravel-medialibrary/src/MediaCollections/Exceptions/MediaCannotBeUpdated.php 0000644 00000000620 15021222773 0023432 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; use Spatie\MediaLibrary\MediaCollections\Models\Media; class MediaCannotBeUpdated extends Exception { public static function doesNotBelongToCollection(string $collectionName, Media $media): self { return new static("Media id {$media->getKey()} is not part of collection `{$collectionName}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/InvalidPathGenerator.php 0000644 00000001111 15021222773 0023540 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator; class InvalidPathGenerator extends Exception { public static function doesntExist(string $class): self { return new static("Path generator class `{$class}` doesn't exist"); } public static function doesNotImplementPathGenerator(string $class): self { $pathGeneratorClass = PathGenerator::class; return new static("Path generator class `{$class}` must implement `$pathGeneratorClass}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/FunctionalityNotAvailable.php 0000644 00000000450 15021222773 0024605 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; class FunctionalityNotAvailable extends Exception { public static function mediaLibraryProRequired() { return new static("You need to have media library pro installed to make this work."); } } laravel-medialibrary/src/MediaCollections/Exceptions/DiskCannotBeAccessed.php 0000644 00000000414 15021222773 0023432 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class DiskCannotBeAccessed extends FileCannotBeAdded { public static function create(string $diskName): self { return new static("Disk named `{$diskName}` cannot be accessed"); } } laravel-medialibrary/src/MediaCollections/Exceptions/UnknownType.php 0000644 00000000411 15021222773 0021771 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class UnknownType extends FileCannotBeAdded { public static function create(): self { return new static('Only strings, FileObjects and UploadedFileObjects can be imported'); } } laravel-medialibrary/src/MediaCollections/Exceptions/MediaCannotBeDeleted.php 0000644 00000000726 15021222773 0023421 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; use Illuminate\Database\Eloquent\Model; class MediaCannotBeDeleted extends Exception { public static function doesNotBelongToModel($mediaId, Model $model): self { $modelClass = $model::class; 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()}"); } } laravel-medialibrary/src/MediaCollections/Exceptions/MimeTypeNotAllowed.php 0000644 00000000661 15021222773 0023221 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; class MimeTypeNotAllowed extends FileCannotBeAdded { public static function create(string $file, array $allowedMimeTypes): self { $mimeType = mime_content_type($file); $allowedMimeTypes = implode(', ', $allowedMimeTypes); return new static("File has a mime type of {$mimeType}, while only {$allowedMimeTypes} are allowed"); } } laravel-medialibrary/src/MediaCollections/Exceptions/InvalidUrlGenerator.php 0000644 00000001075 15021222773 0023417 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; use Spatie\MediaLibrary\Support\UrlGenerator\UrlGenerator; class InvalidUrlGenerator extends Exception { public static function doesntExist(string $class): self { return new static("Url generator class {$class} doesn't exist"); } public static function doesNotImplementUrlGenerator(string $class): self { $urlGeneratorClass = UrlGenerator::class; return new static("Url generator Class {$class} must implement `{$urlGeneratorClass}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/FileUnacceptableForCollection.php 0000644 00000001156 15021222773 0025350 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\MediaCollections\File; use Spatie\MediaLibrary\MediaCollections\MediaCollection; class FileUnacceptableForCollection extends FileCannotBeAdded { public static function create(File $file, MediaCollection $mediaCollection, HasMedia $hasMedia): self { $modelType = $hasMedia::class; return new static("The file with properties `{$file}` was not accepted into the collection named `{$mediaCollection->name}` of model `{$modelType}` with id `{$hasMedia->getKey()}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/InvalidFileRemover.php 0000644 00000001071 15021222773 0023221 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; use Spatie\MediaLibrary\Support\FileRemover\FileRemover; class InvalidFileRemover extends Exception { public static function doesntExist(string $class): self { return new static("File remover class `{$class}` doesn't exist"); } public static function doesNotImplementPathGenerator(string $class): self { $fileRemoverClass = FileRemover::class; return new static("File remover class `{$class}` must implement `$fileRemoverClass}`"); } } laravel-medialibrary/src/MediaCollections/Exceptions/InvalidUrl.php 0000644 00000000472 15021222773 0021550 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Exceptions; use Exception; class InvalidUrl extends Exception { public static function doesNotStartWithProtocol(string $url) { return new static("Could not add `{$url}` because it does not start with either `http://` or `https://`"); } } laravel-medialibrary/src/MediaCollections/File.php 0000644 00000000775 15021222773 0016243 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections; class File implements \Stringable { public static function createFromMedia($media) { return new static($media->file_name, $media->size, $media->mime_type); } public function __construct( public string $name, public int $size, public string $mimeType ) { } public function __toString(): string { return "name: {$this->name}, size: {$this->size}, mime: {$this->mimeType}"; } } laravel-medialibrary/src/MediaCollections/FileAdderFactory.php 0000644 00000004764 15021222773 0020535 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Spatie\MediaLibrary\MediaCollections\Exceptions\RequestDoesNotHaveFile; use Spatie\MediaLibrary\Support\RemoteFile; use Spatie\MediaLibraryPro\Dto\PendingMediaItem; use Symfony\Component\HttpFoundation\File\UploadedFile; class FileAdderFactory { public static function create(Model $subject, string|UploadedFile $file): FileAdder { /** @var FileAdder $fileAdder */ $fileAdder = app(FileAdder::class); return $fileAdder ->setSubject($subject) ->setFile($file); } public static function createFromDisk(Model $subject, string $key, string $disk): FileAdder { /** @var FileAdder $fileAdder */ $fileAdder = app(FileAdder::class); return $fileAdder ->setSubject($subject) ->setFile(new RemoteFile($key, $disk)); } public static function createFromRequest(Model $subject, string $key): FileAdder { return static::createMultipleFromRequest($subject, [$key])->first(); } public static function createMultipleFromRequest(Model $subject, array $keys = []): Collection { return collect($keys) ->map(function (string $key) use ($subject) { $search = ['[', ']', '"', "'"]; $replace = ['.', '', '', '']; $key = str_replace($search, $replace, $key); if (! request()->hasFile($key)) { throw RequestDoesNotHaveFile::create($key); } $files = request()->file($key); if (! is_array($files)) { return static::create($subject, $files); } return array_map(fn ($file) => static::create($subject, $file), $files); })->flatten(); } public static function createAllFromRequest(Model $subject): Collection { $fileKeys = array_keys(request()->allFiles()); return static::createMultipleFromRequest($subject, $fileKeys); } public static function createForPendingMedia(Model $subject, PendingMediaItem $pendingMedia): FileAdder { /** @var FileAdder $fileAdder */ $fileAdder = app(FileAdder::class); return $fileAdder ->setSubject($subject) ->setFile($pendingMedia->temporaryUpload) ->setName($pendingMedia->name) ->setOrder($pendingMedia->order); } } laravel-medialibrary/src/MediaCollections/MediaRepository.php 0000644 00000007125 15021222773 0020477 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections; use Closure; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection as DbCollection; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\MediaCollections\Models\Media; class MediaRepository { public function __construct( protected Media $model ) { } /** * Get all media in the collection. * * @param array|callable $filter * */ public function getCollection( HasMedia $model, string $collectionName, array|callable $filter = [] ): Collection { return $this->applyFilterToMediaCollection($model->loadMedia($collectionName), $filter); } /** * Apply given filters on media. * * @param Collection $media * @param array|callable $filter * * @return Collection */ protected function applyFilterToMediaCollection( Collection $media, array|callable $filter ): Collection { if (is_array($filter)) { $filter = $this->getDefaultFilterFunction($filter); } return $media->filter($filter); } public function all(): DbCollection { return $this->query()->get(); } public function getByModelType(string $modelType): DbCollection { return $this->query()->where('model_type', $modelType)->get(); } public function getByIds(array $ids): DbCollection { return $this->query()->whereIn($this->model->getKeyName(), $ids)->get(); } public function getByIdGreaterThan(int $startingFromId, bool $excludeStartingId = false, string $modelType = ''): DbCollection { return $this->query() ->where($this->model->getKeyName(), $excludeStartingId ? '>' : '>=', $startingFromId) ->when($modelType !== '', fn (Builder $q) => $q->where('model_type', $modelType)) ->get(); } public function getByModelTypeAndCollectionName(string $modelType, string $collectionName): DbCollection { return $this->query() ->where('model_type', $modelType) ->where('collection_name', $collectionName) ->get(); } public function getByCollectionName(string $collectionName): DbCollection { return $this->query() ->where('collection_name', $collectionName) ->get(); } public function getOrphans(): DbCollection { return $this->orphansQuery() ->get(); } public function getOrphansByCollectionName(string $collectionName): DbCollection { return $this->orphansQuery() ->where('collection_name', $collectionName) ->get(); } protected function query(): Builder { return $this->model->newQuery(); } protected function orphansQuery(): Builder { return $this->query() ->whereDoesntHave( 'model', fn (Builder $q) => $q->hasMacro('withTrashed') ? $q->withTrashed() : $q, ); } protected function getDefaultFilterFunction(array $filters): Closure { return function (Media $media) use ($filters) { foreach ($filters as $property => $value) { if (! Arr::has($media->custom_properties, $property)) { return false; } if (Arr::get($media->custom_properties, $property) !== $value) { return false; } } return true; }; } } laravel-medialibrary/src/MediaCollections/Contracts/MediaLibraryRequest.php 0000644 00000000317 15021222773 0023231 0 ustar 00 <?php namespace Spatie\MediaLibrary\MediaCollections\Contracts; use Illuminate\Support\Collection; interface MediaLibraryRequest { public function mediaLibraryRequestItems(string $key): Collection; } laravel-medialibrary/src/ResponsiveImages/ResponsiveImageGenerator.php 0000644 00000015237 15021222773 0022377 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages; use Illuminate\Support\Str; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\ResponsiveImages\Events\ResponsiveImagesGenerated; use Spatie\MediaLibrary\ResponsiveImages\Exceptions\InvalidTinyJpg; use Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\TinyPlaceholderGenerator; use Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\WidthCalculator; use Spatie\MediaLibrary\Support\File; use Spatie\MediaLibrary\Support\FileNamer\FileNamer; use Spatie\MediaLibrary\Support\ImageFactory; use Spatie\MediaLibrary\Support\TemporaryDirectory; use Spatie\TemporaryDirectory\TemporaryDirectory as BaseTemporaryDirectory; class ResponsiveImageGenerator { protected const DEFAULT_CONVERSION_QUALITY = 90; protected FileNamer $fileNamer; public function __construct( protected Filesystem $filesystem, protected WidthCalculator $widthCalculator, protected TinyPlaceholderGenerator $tinyPlaceholderGenerator ) { $this->fileNamer = app(config('media-library.file_namer')); } public function generateResponsiveImages(Media $media): void { $temporaryDirectory = TemporaryDirectory::create(); $baseImage = app(Filesystem::class)->copyFromMediaLibrary( $media, $temporaryDirectory->path(Str::random(16).'.'.$media->extension) ); $media = $this->cleanResponsiveImages($media); foreach ($this->widthCalculator->calculateWidthsFromFile($baseImage) as $width) { $this->generateResponsiveImage($media, $baseImage, 'media_library_original', $width, $temporaryDirectory); } event(new ResponsiveImagesGenerated($media)); $this->generateTinyJpg($media, $baseImage, 'media_library_original', $temporaryDirectory); $temporaryDirectory->delete(); } public function generateResponsiveImagesForConversion(Media $media, Conversion $conversion, string $baseImage): void { $temporaryDirectory = TemporaryDirectory::create(); $media = $this->cleanResponsiveImages($media, $conversion->getName()); foreach ($this->widthCalculator->calculateWidthsFromFile($baseImage) as $width) { $this->generateResponsiveImage($media, $baseImage, $conversion->getName(), $width, $temporaryDirectory, $this->getConversionQuality($conversion)); } $this->generateTinyJpg($media, $baseImage, $conversion->getName(), $temporaryDirectory); $temporaryDirectory->delete(); } private function getConversionQuality(Conversion $conversion): int { return $conversion->getManipulations()->getManipulationArgument('quality') ?: self::DEFAULT_CONVERSION_QUALITY; } public function generateResponsiveImage( Media $media, string $baseImage, string $conversionName, int $targetWidth, BaseTemporaryDirectory $temporaryDirectory, int $conversionQuality = self::DEFAULT_CONVERSION_QUALITY ): void { $extension = $this->fileNamer->extensionFromBaseImage($baseImage); $responsiveImagePath = $this->fileNamer->temporaryFileName($media, $extension); $tempDestination = $temporaryDirectory->path($responsiveImagePath); ImageFactory::load($baseImage) ->optimize() ->width($targetWidth) ->quality($conversionQuality) ->save($tempDestination); $responsiveImageHeight = ImageFactory::load($tempDestination)->getHeight(); // Users can customize the name like they want, but we expect the last part in a certain format $fileName = $this->addPropertiesToFileName( $responsiveImagePath, $conversionName, $targetWidth, $responsiveImageHeight, $extension ); $responsiveImagePath = $temporaryDirectory->path($fileName); rename($tempDestination, $responsiveImagePath); $this->filesystem->copyToMediaLibrary($responsiveImagePath, $media, 'responsiveImages'); ResponsiveImage::register($media, $fileName, $conversionName); } public function generateTinyJpg( Media $media, string $originalImagePath, string $conversionName, BaseTemporaryDirectory $temporaryDirectory ): void { $tempDestination = $temporaryDirectory->path('tiny.jpg'); $this->tinyPlaceholderGenerator->generateTinyPlaceholder($originalImagePath, $tempDestination); $this->guardAgainstInvalidTinyPlaceHolder($tempDestination); $tinyImageDataBase64 = base64_encode(file_get_contents($tempDestination)); $tinyImageBase64 = 'data:image/jpeg;base64,'.$tinyImageDataBase64; $originalImage = ImageFactory::load($originalImagePath); $originalImageWidth = $originalImage->getWidth(); $originalImageHeight = $originalImage->getHeight(); $svg = view('media-library::placeholderSvg', compact( 'originalImageWidth', 'originalImageHeight', 'tinyImageBase64' )); $base64Svg = 'data:image/svg+xml;base64,'.base64_encode($svg); ResponsiveImage::registerTinySvg($media, $base64Svg, $conversionName); } protected function appendToFileName(string $filePath, string $suffix, string $extensionFilePath = null): string { $baseName = pathinfo($filePath, PATHINFO_FILENAME); $extension = pathinfo($extensionFilePath ?? $filePath, PATHINFO_EXTENSION); return "{$baseName}{$suffix}.{$extension}"; } protected function guardAgainstInvalidTinyPlaceHolder(string $tinyPlaceholderPath): void { if (! file_exists($tinyPlaceholderPath)) { throw InvalidTinyJpg::doesNotExist($tinyPlaceholderPath); } if (File::getMimeType($tinyPlaceholderPath) !== 'image/jpeg') { throw InvalidTinyJpg::hasWrongMimeType($tinyPlaceholderPath); } } protected function cleanResponsiveImages(Media $media, string $conversionName = 'media_library_original'): Media { $responsiveImages = $media->responsive_images; $responsiveImages[$conversionName]['urls'] = []; $media->responsive_images = $responsiveImages; $this->filesystem->removeResponsiveImages($media, $conversionName); return $media; } protected function addPropertiesToFileName(string $fileName, string $conversionName, int $width, int $height, string $extension): string { $fileName = pathinfo($fileName, PATHINFO_FILENAME); return "{$fileName}___{$conversionName}_{$width}_{$height}.{$extension}"; } } laravel-medialibrary/src/ResponsiveImages/Jobs/GenerateResponsiveImagesJob.php 0000644 00000001500 15021222773 0023702 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\ResponsiveImages\ResponsiveImageGenerator; class GenerateResponsiveImagesJob implements ShouldQueue { use InteractsWithQueue; use SerializesModels; use Queueable; public function __construct(protected Media $media) { } public function handle(): bool { /** @var ResponsiveImageGenerator $responsiveImageGenerator */ $responsiveImageGenerator = app(ResponsiveImageGenerator::class); $responsiveImageGenerator->generateResponsiveImages($this->media); return true; } } laravel-medialibrary/src/ResponsiveImages/Events/ResponsiveImagesGenerated.php 0000644 00000000436 15021222773 0023771 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages\Events; use Illuminate\Queue\SerializesModels; use Spatie\MediaLibrary\MediaCollections\Models\Media; class ResponsiveImagesGenerated { use SerializesModels; public function __construct(public Media $media) { } } laravel-medialibrary/src/ResponsiveImages/Exceptions/InvalidTinyJpg.php 0000644 00000001252 15021222773 0022434 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages\Exceptions; use Exception; use Spatie\MediaLibrary\Support\File; class InvalidTinyJpg extends Exception { public static function doesNotExist(string $tinyImageDestinationPath): self { return new static("The expected tiny jpg at `{$tinyImageDestinationPath}` does not exist"); } public static function hasWrongMimeType(string $tinyImageDestinationPath): self { $foundMimeType = File::getMimeType($tinyImageDestinationPath); return new static("Expected the file at {$tinyImageDestinationPath} have mimetype `image/jpeg`, but found a file with mimetype `{$foundMimeType}`"); } } laravel-medialibrary/src/ResponsiveImages/RegisteredResponsiveImages.php 0000644 00000003633 15021222773 0022726 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages; use Illuminate\Support\Collection; use Spatie\MediaLibrary\MediaCollections\Models\Media; class RegisteredResponsiveImages { public Collection $files; public string $generatedFor; public function __construct(protected Media $media, string $conversionName = '') { $this->generatedFor = $conversionName === '' ? 'media_library_original' : $conversionName; $this->files = collect($media->responsive_images[$this->generatedFor]['urls'] ?? []) ->map(fn (string $fileName) => new ResponsiveImage($fileName, $media)) ->filter(fn (ResponsiveImage $responsiveImage) => $responsiveImage->generatedFor() === $this->generatedFor); } public function getUrls(): array { return $this->files ->map(fn (ResponsiveImage $responsiveImage) => $responsiveImage->url()) ->values() ->toArray(); } public function getSrcset(): string { $filesSrcset = $this->files ->map(fn (ResponsiveImage $responsiveImage) => "{$responsiveImage->url()} {$responsiveImage->width()}w") ->implode(', '); $shouldAddPlaceholderSvg = config('media-library.responsive_images.use_tiny_placeholders') && $this->getPlaceholderSvg(); if ($shouldAddPlaceholderSvg) { $filesSrcset .= ', '.$this->getPlaceholderSvg().' 32w'; } return $filesSrcset; } public function getPlaceholderSvg(): ?string { return $this->media->responsive_images[$this->generatedFor]['base64svg'] ?? null; } public function delete() { $this->files->each->delete(); $responsiveImages = $this->media->responsive_images; unset($responsiveImages[$this->generatedFor]); $this->media->responsive_images = $responsiveImages; $this->media->save(); } } laravel-medialibrary/src/ResponsiveImages/ResponsiveImage.php 0000644 00000005767 15021222773 0020537 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\PathGenerator\PathGeneratorFactory; use Spatie\MediaLibrary\Support\UrlGenerator\UrlGeneratorFactory; class ResponsiveImage { public static function register(Media $media, $fileName, $conversionName) { $responsiveImages = $media->responsive_images; $responsiveImages[$conversionName]['urls'][] = $fileName; $media->responsive_images = $responsiveImages; $media->save(); } public static function registerTinySvg(Media $media, string $base64Svg, string $conversionName) { $responsiveImages = $media->responsive_images; $responsiveImages[$conversionName]['base64svg'] = $base64Svg; $media->responsive_images = $responsiveImages; $media->save(); } public function __construct(public string $fileName, public Media $media) { } public function url(): string { $conversionName = ''; if ($this->generatedFor() !== 'media_library_original') { $conversionName = $this->generatedFor(); } $urlGenerator = UrlGeneratorFactory::createForMedia($this->media, $conversionName); return $urlGenerator->getResponsiveImagesDirectoryUrl().rawurlencode($this->fileName); } public function generatedFor(): string { $propertyParts = $this->getPropertyParts(); array_pop($propertyParts); array_pop($propertyParts); return implode('_', $propertyParts); } public function width(): int { $propertyParts = $this->getPropertyParts(); array_pop($propertyParts); return (int) last($propertyParts); } public function height(): int { $propertyParts = $this->getPropertyParts(); return (int) last($propertyParts); } protected function getPropertyParts(): array { $propertyString = $this->stringBetween($this->fileName, '___', '.'); return explode('_', $propertyString); } protected function stringBetween(string $subject, string $startCharacter, string $endCharacter): string { $lastPos = strrpos($subject, $startCharacter); $between = substr($subject, $lastPos); $between = str_replace('___', '', $between); $between = strstr($between, $endCharacter, true); return $between; } public function delete(): self { $pathGenerator = PathGeneratorFactory::create($this->media); $path = $pathGenerator->getPathForResponsiveImages($this->media); $fullPath = $path.$this->fileName; app(Filesystem::class)->removeFile($this->media, $fullPath); $responsiveImages = $this->media->responsive_images; unset($responsiveImages[$this->generatedFor()]); $this->media->responsive_images = $responsiveImages; $this->media->save(); return $this; } } laravel-medialibrary/src/ResponsiveImages/WidthCalculator/WidthCalculator.php 0000644 00000000460 15021222773 0023602 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages\WidthCalculator; use Illuminate\Support\Collection; interface WidthCalculator { public function calculateWidthsFromFile(string $imagePath): Collection; public function calculateWidths(int $fileSize, int $width, int $height): Collection; } laravel-medialibrary/src/ResponsiveImages/WidthCalculator/FileSizeOptimizedWidthCalculator.php 0000644 00000002732 15021222773 0027126 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages\WidthCalculator; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Support\ImageFactory; class FileSizeOptimizedWidthCalculator implements WidthCalculator { public function calculateWidthsFromFile(string $imagePath): Collection { $image = ImageFactory::load($imagePath); $width = $image->getWidth(); $height = $image->getHeight(); $fileSize = filesize($imagePath); return $this->calculateWidths($fileSize, $width, $height); } public function calculateWidths(int $fileSize, int $width, int $height): Collection { $targetWidths = collect(); $targetWidths->push($width); $ratio = $height / $width; $area = $height * $width; $predictedFileSize = $fileSize; $pixelPrice = $predictedFileSize / $area; while (true) { $predictedFileSize *= 0.7; $newWidth = (int) floor(sqrt(($predictedFileSize / $pixelPrice) / $ratio)); if ($this->finishedCalculating((int) $predictedFileSize, $newWidth)) { return $targetWidths; } $targetWidths->push($newWidth); } } protected function finishedCalculating(int $predictedFileSize, int $newWidth): bool { if ($newWidth < 20) { return true; } if ($predictedFileSize < (1024 * 10)) { return true; } return false; } } laravel-medialibrary/src/ResponsiveImages/TinyPlaceholderGenerator/Blurred.php 0000644 00000000670 15021222773 0023757 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator; use Spatie\MediaLibrary\Support\ImageFactory; class Blurred implements TinyPlaceholderGenerator { public function generateTinyPlaceholder(string $sourceImagePath, string $tinyImageDestinationPath): void { $sourceImage = ImageFactory::load($sourceImagePath); $sourceImage->width(32)->blur(5)->save($tinyImageDestinationPath); } } laravel-medialibrary/src/ResponsiveImages/TinyPlaceholderGenerator/TinyPlaceholderGenerator.php 0000644 00000000616 15021222773 0027315 0 ustar 00 <?php namespace Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator; interface TinyPlaceholderGenerator { /* * This function should generate a tiny jpg representation of the image * given in $sourceImage. The tiny jpg should be saved at $tinyImageDestination. */ public function generateTinyPlaceholder(string $sourceImage, string $tinyImageDestination): void; } laravel-medialibrary/src/HasMedia.php 0000644 00000003056 15021222773 0013614 0 ustar 00 <?php namespace Spatie\MediaLibrary; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\FileAdder; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Symfony\Component\HttpFoundation\File\UploadedFile; /** * @mixin \Illuminate\Database\Eloquent\Model * @method void prepareToAttachMedia(Media $media, FileAdder $fileAdder) * @property bool $registerMediaConversionsUsingModelInstance * @property ?\Spatie\MediaLibrary\MediaCollections\MediaCollection $mediaCollections */ interface HasMedia { public function media(): MorphMany; public function addMedia(string|UploadedFile $file): FileAdder; public function copyMedia(string|UploadedFile $file): FileAdder; public function hasMedia(string $collectionName = ''): bool; public function getMedia(string $collectionName = 'default', array|callable $filters = []): Collection; public function clearMediaCollection(string $collectionName = 'default'): HasMedia; public function clearMediaCollectionExcept(string $collectionName = 'default', array|Collection $excludedMedia = []): HasMedia; public function shouldDeletePreservingMedia(): bool; public function loadMedia(string $collectionName); public function addMediaConversion(string $name): Conversion; public function registerMediaConversions(Media $media = null): void; public function registerMediaCollections(): void; public function registerAllMediaConversions(): void; } laravel-medialibrary/src/Conversions/Jobs/PerformConversionsJob.php 0000644 00000001720 15021222773 0021640 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Spatie\MediaLibrary\Conversions\ConversionCollection; use Spatie\MediaLibrary\Conversions\FileManipulator; use Spatie\MediaLibrary\MediaCollections\Models\Media; class PerformConversionsJob implements ShouldQueue { use InteractsWithQueue; use SerializesModels; use Queueable; public $deleteWhenMissingModels = true; public function __construct( protected ConversionCollection $conversions, protected Media $media, protected bool $onlyMissing = false, ) { } public function handle(FileManipulator $fileManipulator): bool { $fileManipulator->performConversions( $this->conversions, $this->media, $this->onlyMissing ); return true; } } laravel-medialibrary/src/Conversions/ConversionCollection.php 0000644 00000007200 15021222773 0020605 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\Image\Manipulations; use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidConversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; /** * @template TKey of array-key * @template TValue of Conversion * * @extends Collection<TKey, TValue> */ class ConversionCollection extends Collection { protected Media $media; public static function createForMedia(Media $media): self { return (new static())->setMedia($media); } public function setMedia(Media $media): self { $this->media = $media; $this->items = []; $this->addConversionsFromRelatedModel($media); $this->addManipulationsFromDb($media); return $this; } public function getByName(string $name): Conversion { $conversion = $this->first(fn (Conversion $conversion) => $conversion->getName() === $name); if (! $conversion) { throw InvalidConversion::unknownName($name); } return $conversion; } protected function addConversionsFromRelatedModel(Media $media): void { $modelName = Arr::get(Relation::morphMap(), $media->model_type, $media->model_type); if (! class_exists($modelName)) { return; } /** @var \Spatie\MediaLibrary\HasMedia $model */ $model = new $modelName(); /* * In some cases the user might want to get the actual model * instance so conversion parameters can depend on model * properties. This will causes extra queries. */ if ($model->registerMediaConversionsUsingModelInstance && $media->model) { $model = $media->model; $model->mediaConversions = []; } $model->registerAllMediaConversions($media); $this->items = $model->mediaConversions; } protected function addManipulationsFromDb(Media $media) { collect($media->manipulations)->each(function ($manipulation, $conversionName) { $manipulations = new Manipulations([$manipulation]); $this->addManipulationToConversion($manipulations, $conversionName); }); } public function getConversions(string $collectionName = ''): self { if ($collectionName === '') { return $this; } return $this->filter(fn (Conversion $conversion) => $conversion->shouldBePerformedOn($collectionName)); } protected function addManipulationToConversion(Manipulations $manipulations, string $conversionName) { /** @var Conversion|null $conversion */ $conversion = $this->first(function (Conversion $conversion) use ($conversionName) { if (! in_array($this->media->collection_name, $conversion->getPerformOnCollections())) { return false; } if ($conversion->getName() !== $conversionName) { return false; } return true; }); if ($conversion) { $conversion->addAsFirstManipulations($manipulations); } if ($conversionName === '*') { $this->each( fn (Conversion $conversion) => $conversion->addAsFirstManipulations(clone $manipulations) ); } } public function getConversionsFiles(string $collectionName = ''): self { return $this ->getConversions($collectionName) ->map(fn (Conversion $conversion) => $conversion->getConversionFile($this->media)); } } laravel-medialibrary/src/Conversions/Actions/PerformManipulationsAction.php 0000644 00000002632 15021222773 0023364 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\Actions; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\ImageFactory; class PerformManipulationsAction { public function execute( Media $media, Conversion $conversion, string $imageFile ): string { if ($conversion->getManipulations()->isEmpty()) { return $imageFile; } $conversionTempFile = $this->getConversionTempFileName($media, $conversion, $imageFile); File::copy($imageFile, $conversionTempFile); $supportedFormats = ['jpg', 'pjpg', 'png', 'gif']; if ($conversion->shouldKeepOriginalImageFormat() && in_array($media->extension, $supportedFormats)) { $conversion->format($media->extension); } ImageFactory::load($conversionTempFile) ->manipulate($conversion->getManipulations()) ->save(); return $conversionTempFile; } protected function getConversionTempFileName( Media $media, Conversion $conversion, string $imageFile ): string { $directory = pathinfo($imageFile, PATHINFO_DIRNAME); $fileName = Str::random(32)."{$conversion->getName()}.{$media->extension}"; return "{$directory}/{$fileName}"; } } laravel-medialibrary/src/Conversions/Actions/PerformConversionAction.php 0000644 00000004243 15021222773 0022666 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\Actions; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\Conversions\Events\ConversionHasBeenCompleted; use Spatie\MediaLibrary\Conversions\Events\ConversionWillStart; use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\ResponsiveImages\ResponsiveImageGenerator; class PerformConversionAction { public function execute( Conversion $conversion, Media $media, string $copiedOriginalFile ) { $imageGenerator = ImageGeneratorFactory::forMedia($media); $copiedOriginalFile = $imageGenerator->convert($copiedOriginalFile, $conversion); if (! $copiedOriginalFile) { return; } event(new ConversionWillStart($media, $conversion, $copiedOriginalFile)); $manipulationResult = (new PerformManipulationsAction())->execute($media, $conversion, $copiedOriginalFile); $newFileName = $conversion->getConversionFile($media); $renamedFile = $this->renameInLocalDirectory($manipulationResult, $newFileName); if ($conversion->shouldGenerateResponsiveImages()) { /** @var ResponsiveImageGenerator $responsiveImageGenerator */ $responsiveImageGenerator = app(ResponsiveImageGenerator::class); $responsiveImageGenerator->generateResponsiveImagesForConversion( $media, $conversion, $renamedFile ); } app(Filesystem::class)->copyToMediaLibrary($renamedFile, $media, 'conversions'); $media->markAsConversionGenerated($conversion->getName()); event(new ConversionHasBeenCompleted($media, $conversion)); } protected function renameInLocalDirectory( string $fileNameWithDirectory, string $newFileNameWithoutDirectory ): string { $targetFile = pathinfo($fileNameWithDirectory, PATHINFO_DIRNAME).'/'.$newFileNameWithoutDirectory; rename($fileNameWithDirectory, $targetFile); return $targetFile; } } laravel-medialibrary/src/Conversions/Commands/RegenerateCommand.php 0000644 00000007317 15021222773 0021576 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\Commands; use Exception; use Illuminate\Console\Command; use Illuminate\Console\ConfirmableTrait; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Spatie\MediaLibrary\Conversions\FileManipulator; use Spatie\MediaLibrary\MediaCollections\MediaRepository; use Spatie\MediaLibrary\MediaCollections\Models\Media; class RegenerateCommand extends Command { use ConfirmableTrait; protected $signature = 'media-library:regenerate {modelType?} {--ids=*} {--only=* : Regenerate specific conversions} {--starting-from-id= : Regenerate media with an id equal to or higher than the provided value} {--X|exclude-starting-id : Exclude the provided id when regenerating from a specific id} {--only-missing : Regenerate only missing conversions} {--with-responsive-images : Regenerate responsive images} {--force : Force the operation to run when in production}'; protected $description = 'Regenerate the derived images of media'; protected MediaRepository $mediaRepository; protected FileManipulator $fileManipulator; protected array $errorMessages = []; public function handle(MediaRepository $mediaRepository, FileManipulator $fileManipulator) { $this->mediaRepository = $mediaRepository; $this->fileManipulator = $fileManipulator; if (! $this->confirmToProceed()) { return; } $mediaFiles = $this->getMediaToBeRegenerated(); $progressBar = $this->output->createProgressBar($mediaFiles->count()); $mediaFiles->each(function (Media $media) use ($progressBar) { try { $this->fileManipulator->createDerivedFiles( $media, Arr::wrap($this->option('only')), $this->option('only-missing'), $this->option('with-responsive-images') ); } catch (Exception $exception) { $this->errorMessages[$media->getKey()] = $exception->getMessage(); } $progressBar->advance(); }); $progressBar->finish(); if (count($this->errorMessages)) { $this->warn('All done, but with some error messages:'); foreach ($this->errorMessages as $mediaId => $message) { $this->warn("Media id {$mediaId}: `{$message}`"); } } $this->newLine(2); $this->info('All done!'); } public function getMediaToBeRegenerated(): Collection { // Get this arg first as it can also be passed to the greater-than-id branch $modelType = $this->argument('modelType'); $startingFromId = (int)$this->option('starting-from-id'); if ($startingFromId !== 0) { $excludeStartingId = (bool) $this->option('exclude-starting-id') ?: false; return $this->mediaRepository->getByIdGreaterThan($startingFromId, $excludeStartingId, is_string($modelType) ? $modelType : ''); } if (is_string($modelType)) { return $this->mediaRepository->getByModelType($modelType); } $mediaIds = $this->getMediaIds(); if (count($mediaIds) > 0) { return $this->mediaRepository->getByIds($mediaIds); } return $this->mediaRepository->all(); } protected function getMediaIds(): array { $mediaIds = $this->option('ids'); if (! is_array($mediaIds)) { $mediaIds = explode(',', (string) $mediaIds); } if (count($mediaIds) === 1 && Str::contains((string) $mediaIds[0], ',')) { $mediaIds = explode(',', (string) $mediaIds[0]); } return $mediaIds; } } laravel-medialibrary/src/Conversions/Events/ConversionWillStart.php 0000644 00000000605 15021222773 0021705 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\Events; use Illuminate\Queue\SerializesModels; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; class ConversionWillStart { use SerializesModels; public function __construct(public Media $media, public Conversion $conversion, public string $copiedOriginalFile) { } } laravel-medialibrary/src/Conversions/Events/ConversionHasBeenCompleted.php 0000644 00000000551 15021222773 0023122 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\Events; use Illuminate\Queue\SerializesModels; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; class ConversionHasBeenCompleted { use SerializesModels; public function __construct(public Media $media, public Conversion $conversion) { } } laravel-medialibrary/src/Conversions/FileManipulator.php 0000644 00000010675 15021222773 0017551 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Spatie\MediaLibrary\Conversions\Actions\PerformConversionAction; use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory; use Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob; use Spatie\MediaLibrary\MediaCollections\Filesystem; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob; use Spatie\MediaLibrary\Support\TemporaryDirectory; class FileManipulator { public function createDerivedFiles( Media $media, array $onlyConversionNames = [], bool $onlyMissing = false, bool $withResponsiveImages = false ): void { if (! $this->canConvertMedia($media)) { return; } [$queuedConversions, $conversions] = ConversionCollection::createForMedia($media) ->filter(function (Conversion $conversion) use ($onlyConversionNames) { if (count($onlyConversionNames) === 0) { return true; } return in_array($conversion->getName(), $onlyConversionNames); }) ->filter(fn (Conversion $conversion) => $conversion->shouldBePerformedOn($media->collection_name)) ->partition(fn (Conversion $conversion) => $conversion->shouldBeQueued()); $this ->performConversions($conversions, $media, $onlyMissing) ->dispatchQueuedConversions($media, $queuedConversions, $onlyMissing) ->generateResponsiveImages($media, $withResponsiveImages); } public function performConversions( ConversionCollection $conversions, Media $media, bool $onlyMissing = false ): self { if ($conversions->isEmpty()) { return $this; } $temporaryDirectory = TemporaryDirectory::create(); $copiedOriginalFile = app(Filesystem::class)->copyFromMediaLibrary( $media, $temporaryDirectory->path(Str::random(32) . '.' . $media->extension) ); $conversions ->reject(function (Conversion $conversion) use ($onlyMissing, $media) { $relativePath = $media->getPath($conversion->getName()); if ($rootPath = config("filesystems.disks.{$media->disk}.root")) { $relativePath = str_replace($rootPath, '', $relativePath); } return $onlyMissing && Storage::disk($media->disk)->exists($relativePath); }) ->each(function (Conversion $conversion) use ($media, $copiedOriginalFile) { (new PerformConversionAction())->execute($conversion, $media, $copiedOriginalFile); }); $temporaryDirectory->delete(); return $this; } protected function dispatchQueuedConversions( Media $media, ConversionCollection $conversions, bool $onlyMissing = false ): self { if ($conversions->isEmpty()) { return $this; } $performConversionsJobClass = config( 'media-library.jobs.perform_conversions', PerformConversionsJob::class ); /** @var PerformConversionsJob $job */ $job = (new $performConversionsJobClass($conversions, $media, $onlyMissing)) ->onConnection(config('media-library.queue_connection_name')) ->onQueue(config('media-library.queue_name')); dispatch($job); return $this; } protected function generateResponsiveImages(Media $media, bool $withResponsiveImages): self { if (! $withResponsiveImages) { return $this; } if (! count($media->responsive_images)) { return $this; } $generateResponsiveImagesJobClass = config( 'media-library.jobs.generate_responsive_images', GenerateResponsiveImagesJob::class ); /** @var GenerateResponsiveImagesJob $job */ $job = (new $generateResponsiveImagesJobClass($media)) ->onConnection(config('media-library.queue_connection_name')) ->onQueue(config('media-library.queue_name')); dispatch($job); return $this; } protected function canConvertMedia(Media $media): bool { $imageGenerator = ImageGeneratorFactory::forMedia($media); return $imageGenerator ? true : false; } } laravel-medialibrary/src/Conversions/ImageGenerators/Svg.php 0000644 00000001723 15021222773 0020263 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use Illuminate\Support\Collection; use Imagick; use ImagickPixel; use Spatie\MediaLibrary\Conversions\Conversion; class Svg extends ImageGenerator { public function convert(string $file, Conversion $conversion = null): string { $imageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.jpg'; $image = new Imagick(); $image->readImage($file); $image->setBackgroundColor(new ImagickPixel('none')); $image->setImageFormat('jpg'); file_put_contents($imageFile, $image); return $imageFile; } public function requirementsAreInstalled(): bool { return class_exists(\Imagick::class); } public function supportedExtensions(): Collection { return collect(['svg']); } public function supportedMimeTypes(): Collection { return collect(['image/svg+xml']); } } laravel-medialibrary/src/Conversions/ImageGenerators/Image.php 0000644 00000002167 15021222773 0020551 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; class Image extends ImageGenerator { public function convert(string $path, Conversion $conversion = null): string { return $path; } public function requirementsAreInstalled(): bool { return true; } public function supportedExtensions(): Collection { $extensions = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'avif']; if (config('media-library.image_driver') === 'imagick') { $extensions[] = 'tiff'; $extensions[] = 'heic'; $extensions[] = 'heif'; } return collect($extensions); } public function supportedMimeTypes(): Collection { $mimeTypes = ['image/jpeg', 'image/gif', 'image/png', 'image/webp', 'image/avif']; if (config('media-library.image_driver') === 'imagick') { $mimeTypes[] = 'image/tiff'; $mimeTypes[] = 'image/heic'; $mimeTypes[] = 'image/heif'; } return collect($mimeTypes); } } laravel-medialibrary/src/Conversions/ImageGenerators/Avif.php 0000644 00000002120 15021222773 0020401 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; class Avif extends ImageGenerator { public function convert(string $file, Conversion $conversion = null): string { $pathToImageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.png'; $image = imagecreatefromavif($file); imagepng($image, $pathToImageFile, 9); imagedestroy($image); return $pathToImageFile; } public function requirementsAreInstalled(): bool { if (! function_exists('imagecreatefromavif')) { return false; } if (! function_exists('imagepng')) { return false; } if (! function_exists('imagedestroy')) { return false; } return true; } public function supportedExtensions(): Collection { return collect(['avif']); } public function supportedMimeTypes(): Collection { return collect(['image/avif']); } } laravel-medialibrary/src/Conversions/ImageGenerators/Video.php 0000644 00000002755 15021222773 0020600 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use FFMpeg\Coordinate\TimeCode; use FFMpeg\FFMpeg; use FFMpeg\Media\Video as FFMpegVideo; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; class Video extends ImageGenerator { public function convert(string $file, Conversion $conversion = null): ?string { $ffmpeg = FFMpeg::create([ 'ffmpeg.binaries' => config('media-library.ffmpeg_path'), 'ffprobe.binaries' => config('media-library.ffprobe_path'), ]); $video = $ffmpeg->open($file); if (! ($video instanceof FFMpegVideo)) { return null; } $duration = $ffmpeg->getFFProbe()->format($file)->get('duration'); $seconds = $conversion ? $conversion->getExtractVideoFrameAtSecond() : 0; $seconds = $duration <= $seconds ? 0 : $seconds; $imageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.jpg'; $frame = $video->frame(TimeCode::fromSeconds($seconds)); $frame->save($imageFile); return $imageFile; } public function requirementsAreInstalled(): bool { return class_exists('\\FFMpeg\\FFMpeg'); } public function supportedExtensions(): Collection { return collect(['webm', 'mov', 'mp4']); } public function supportedMimeTypes(): Collection { return collect(['video/webm', 'video/mpeg', 'video/mp4', 'video/quicktime']); } } laravel-medialibrary/src/Conversions/ImageGenerators/Pdf.php 0000644 00000002040 15021222773 0020226 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; class Pdf extends ImageGenerator { public function convert(string $file, Conversion $conversion = null): string { $imageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.jpg'; $pageNumber = $conversion ? $conversion->getPdfPageNumber() : 1; (new \Spatie\PdfToImage\Pdf($file))->setPage($pageNumber)->saveImage($imageFile); return $imageFile; } public function requirementsAreInstalled(): bool { if (! class_exists(\Imagick::class)) { return false; } if (! class_exists(\Spatie\PdfToImage\Pdf::class)) { return false; } return true; } public function supportedExtensions(): Collection { return collect(['pdf']); } public function supportedMimeTypes(): Collection { return collect(['application/pdf']); } } laravel-medialibrary/src/Conversions/ImageGenerators/Webp.php 0000644 00000002120 15021222773 0020411 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; class Webp extends ImageGenerator { public function convert(string $file, Conversion $conversion = null): string { $pathToImageFile = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.png'; $image = imagecreatefromwebp($file); imagepng($image, $pathToImageFile, 9); imagedestroy($image); return $pathToImageFile; } public function requirementsAreInstalled(): bool { if (! function_exists('imagecreatefromwebp')) { return false; } if (! function_exists('imagepng')) { return false; } if (! function_exists('imagedestroy')) { return false; } return true; } public function supportedExtensions(): Collection { return collect(['webp']); } public function supportedMimeTypes(): Collection { return collect(['image/webp']); } } laravel-medialibrary/src/Conversions/ImageGenerators/ImageGenerator.php 0000644 00000003115 15021222773 0022412 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; use Spatie\MediaLibrary\MediaCollections\Models\Media; abstract class ImageGenerator { /* * This function should return a path to an image representation of the given file. */ abstract public function convert(string $file, Conversion $conversion = null): ?string; public function canConvert(Media $media): bool { if (! $this->requirementsAreInstalled()) { return false; } $validExtension = $this->canHandleExtension(strtolower($media->extension)); $validMimeType = $this->canHandleMime(strtolower($media->mime_type)); if ($this->shouldMatchBothExtensionsAndMimeTypes()) { return $validExtension && $validMimeType; } return $validExtension || $validMimeType; } public function shouldMatchBothExtensionsAndMimeTypes(): bool { return false; } public function canHandleMime(string $mime = ''): bool { return $this->supportedMimeTypes()->contains($mime); } public function canHandleExtension(string $extension = ''): bool { return $this->supportedExtensions()->contains($extension); } public function getType(): string { return strtolower(class_basename(static::class)); } abstract public function requirementsAreInstalled(): bool; abstract public function supportedExtensions(): Collection; abstract public function supportedMimeTypes(): Collection; } laravel-medialibrary/src/Conversions/ImageGenerators/ImageGeneratorFactory.php 0000644 00000003033 15021222773 0023741 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions\ImageGenerators; use Illuminate\Support\Collection; use Spatie\MediaLibrary\MediaCollections\Models\Media; class ImageGeneratorFactory { public static function getImageGenerators(): Collection { return collect(config('media-library.image_generators')) ->map(function ($imageGeneratorClassName, $key) { $imageGeneratorConfig = []; if (! is_numeric($key)) { $imageGeneratorConfig = $imageGeneratorClassName; $imageGeneratorClassName = $key; } return app($imageGeneratorClassName, $imageGeneratorConfig); }); } public static function forExtension(?string $extension): ?ImageGenerator { if (is_null($extension)) { return null; } return static::getImageGenerators() ->first(fn (ImageGenerator $imageGenerator) => $imageGenerator->canHandleExtension(strtolower($extension))); } public static function forMimeType(?string $mimeType): ?ImageGenerator { if (is_null($mimeType)) { return null; } return static::getImageGenerators() ->first(fn (ImageGenerator $imageGenerator) => $imageGenerator->canHandleMime($mimeType)); } public static function forMedia(Media $media): ?ImageGenerator { return static::getImageGenerators() ->first(fn (ImageGenerator $imageGenerator) => $imageGenerator->canConvert($media)); } } laravel-medialibrary/src/Conversions/Conversion.php 0000644 00000014131 15021222773 0016572 0 ustar 00 <?php namespace Spatie\MediaLibrary\Conversions; use BadMethodCallException; use Illuminate\Support\Traits\Conditionable; use Spatie\Image\Manipulations; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\Support\FileNamer\FileNamer; /** @mixin \Spatie\Image\Manipulations */ class Conversion { use Conditionable; protected FileNamer $fileNamer; protected float $extractVideoFrameAtSecond = 0; protected Manipulations $manipulations; protected array $performOnCollections = []; protected bool $performOnQueue; protected bool $keepOriginalImageFormat = false; protected bool $generateResponsiveImages = false; protected ?string $loadingAttributeValue; protected int $pdfPageNumber = 1; public function __construct( protected string $name ) { $this->manipulations = (new Manipulations()) ->optimize(config('media-library.image_optimizers')) ->format(Manipulations::FORMAT_JPG); $this->fileNamer = app(config('media-library.file_namer')); $this->loadingAttributeValue = config('media-library.default_loading_attribute_value'); $this->performOnQueue = config('media-library.queue_conversions_by_default', true); } public static function create(string $name) { return new static($name); } public function getName(): string { return $this->name; } public function getPerformOnCollections(): array { if (! count($this->performOnCollections)) { return ['default']; } return $this->performOnCollections; } public function extractVideoFrameAtSecond(float $timeCode): self { $this->extractVideoFrameAtSecond = $timeCode; return $this; } public function getExtractVideoFrameAtSecond(): float { return $this->extractVideoFrameAtSecond; } public function keepOriginalImageFormat(): self { $this->keepOriginalImageFormat = true; return $this; } public function shouldKeepOriginalImageFormat(): bool { return $this->keepOriginalImageFormat; } public function getManipulations(): Manipulations { return $this->manipulations; } public function removeManipulation(string $manipulationName): self { $this->manipulations->removeManipulation($manipulationName); return $this; } public function withoutManipulations(): self { $this->manipulations = new Manipulations(); return $this; } public function __call($name, $arguments) { if (! method_exists($this->manipulations, $name)) { throw new BadMethodCallException("Manipulation `{$name}` does not exist"); } $this->manipulations->$name(...$arguments); return $this; } public function setManipulations($manipulations): self { if ($manipulations instanceof Manipulations) { $this->manipulations = $this->manipulations->mergeManipulations($manipulations); } if (is_callable($manipulations)) { $manipulations($this->manipulations); } return $this; } public function addAsFirstManipulations(Manipulations $manipulations): self { $manipulationSequence = $manipulations->getManipulationSequence()->toArray(); $this->manipulations ->getManipulationSequence() ->mergeArray($manipulationSequence); return $this; } public function performOnCollections(...$collectionNames): self { $this->performOnCollections = $collectionNames; return $this; } public function shouldBePerformedOn(string $collectionName): bool { //if no collections were specified, perform conversion on all collections if (! count($this->performOnCollections)) { return true; } if (in_array('*', $this->performOnCollections)) { return true; } return in_array($collectionName, $this->performOnCollections); } public function queued(): self { $this->performOnQueue = true; return $this; } public function nonQueued(): self { $this->performOnQueue = false; return $this; } public function nonOptimized(): self { $this->removeManipulation('optimize'); return $this; } public function withResponsiveImages(): self { $this->generateResponsiveImages = true; return $this; } public function shouldGenerateResponsiveImages(): bool { return $this->generateResponsiveImages; } public function shouldBeQueued(): bool { return $this->performOnQueue; } public function getResultExtension(string $originalFileExtension = ''): string { if ($this->shouldKeepOriginalImageFormat()) { if (in_array(strtolower($originalFileExtension), ['jpg', 'jpeg', 'pjpg', 'png', 'gif', 'webp', 'avif'])) { return $originalFileExtension; } } if ($manipulationArgument = $this->manipulations->getManipulationArgument('format')) { return $manipulationArgument; } return $originalFileExtension; } public function getConversionFile(Media $media): string { $fileName = $this->fileNamer->conversionFileName($media->file_name, $this); $fileExtension = $this->fileNamer->extensionFromBaseImage($media->file_name); $extension = $this->getResultExtension($fileExtension) ?: $fileExtension; return "{$fileName}.{$extension}"; } public function useLoadingAttributeValue(string $value): self { $this->loadingAttributeValue = $value; return $this; } public function getLoadingAttributeValue(): ?string { return $this->loadingAttributeValue; } public function pdfPageNumber(int $pageNumber): self { $this->pdfPageNumber = $pageNumber; return $this; } public function getPdfPageNumber(): int { return $this->pdfPageNumber; } } laravel-medialibrary/src/Downloaders/Downloader.php 0000644 00000000202 15021222773 0016506 0 ustar 00 <?php namespace Spatie\MediaLibrary\Downloaders; interface Downloader { public function getTempFile(string $url): string; } laravel-medialibrary/src/Downloaders/DefaultDownloader.php 0000644 00000001270 15021222773 0020021 0 ustar 00 <?php namespace Spatie\MediaLibrary\Downloaders; use Spatie\MediaLibrary\MediaCollections\Exceptions\UnreachableUrl; class DefaultDownloader implements Downloader { public function getTempFile(string $url): string { $context = stream_context_create([ "http" => [ "header" => "User-Agent: Spatie MediaLibrary", ], ]); if (! $stream = @fopen($url, 'r', false, $context)) { throw UnreachableUrl::create($url); } $temporaryFile = tempnam(sys_get_temp_dir(), 'media-library'); file_put_contents($temporaryFile, $stream); fclose($stream); return $temporaryFile; } } laravel-medialibrary/README.md 0000644 00000011020 15021222773 0012106 0 ustar 00 <p align="center"><img src="/art/socialcard.png" alt="Social Card of Laravel Media Library"></p> # Associate files with Eloquent models [](https://github.com/spatie/laravel-medialibrary/releases)  [](https://packagist.org/packages/spatie/laravel-medialibrary) This package can associate all sorts of files with Eloquent models. It provides a simple API to work with. To learn all about it, head over to [the extensive documentation](https://spatie.be/docs/laravel-medialibrary). Here are a few short examples of what you can do: ```php $newsItem = News::find(1); $newsItem->addMedia($pathToFile)->toMediaCollection('images'); ``` It can handle your uploads directly: ```php $newsItem->addMedia($request->file('image'))->toMediaCollection('images'); ``` Want to store some large files on another filesystem? No problem: ```php $newsItem->addMedia($smallFile)->toMediaCollection('downloads', 'local'); $newsItem->addMedia($bigFile)->toMediaCollection('downloads', 's3'); ``` The storage of the files is handled by [Laravel's Filesystem](https://laravel.com/docs/filesystem), so you can use any filesystem you like. Additionally the package can create image manipulations on images and pdfs that have been added in the media library. 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). ## Support us [<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) 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). 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). ## Documentation You'll find the documentation on [https://spatie.be/docs/laravel-medialibrary](https://spatie.be/docs/laravel-medialibrary/v10). 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. If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. ## Testing You can run the tests with: ```bash ./vendor/bin/pest ``` You can run the Github actions locally with [act](https://github.com/nektos/act). You have to use a [custom image](https://github.com/shivammathur/setup-php#local-testing-setup) for the ubuntu-latest platform to get PHP up and running properly. To run the tests locally, run: ```bash act -P ubuntu-latest=shivammathur/node:latest ``` To run a specific workflow, for example `run-tests.yml` run: ```bash act -P ubuntu-latest=shivammathur/node:latest -j run-tests ``` ## Upgrading Please see [UPGRADING](UPGRADING.md) for details. ### Changelog Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. ## Contributing Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. ## Security If you discover any security related issues, please email [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. ## Credits - [Freek Van der Herten](https://github.com/freekmurze) - [All Contributors](../../contributors) A big thank you to [Nicolas Beauvais](https://github.com/nicolasbeauvais) for helping out with the issues on this repo. And a special thanks to [Caneco](https://twitter.com/caneco) for the logo ✨ ## Alternatives - [laravel-mediable](https://github.com/plank/laravel-mediable) - [laravel-stapler](https://github.com/CodeSleeve/laravel-stapler) - [media-manager](https://github.com/talvbansal/media-manager) ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. laravel-medialibrary/database/migrations/create_media_table.php.stub 0000644 00000002016 15021222773 0022012 0 ustar 00 <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('media', function (Blueprint $table) { $table->id(); $table->morphs('model'); $table->uuid('uuid')->nullable()->unique(); $table->string('collection_name'); $table->string('name'); $table->string('file_name'); $table->string('mime_type')->nullable(); $table->string('disk'); $table->string('conversions_disk')->nullable(); $table->unsignedBigInteger('size'); $table->json('manipulations'); $table->json('custom_properties'); $table->json('generated_conversions'); $table->json('responsive_images'); $table->unsignedInteger('order_column')->nullable()->index(); $table->nullableTimestamps(); }); } }; laravel-medialibrary/resources/views/placeholderSvg.blade.php 0000644 00000000655 15021222773 0020533 0 ustar 00 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0" y="0" viewBox="0 0 {{ $originalImageWidth }} {{ $originalImageHeight }}"> <image width="{{ $originalImageWidth }}" height="{{ $originalImageHeight }}" xlink:href="{{ $tinyImageBase64 }}"> </image> </svg>