Completed
Push — master ( a50e59...22377b )
by Freek
10:42
created

src/Commands/CleanCommand.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Spatie\MediaLibrary\Commands;
4
5
use Illuminate\Console\Command;
6
use Spatie\MediaLibrary\Models\Media;
7
use Illuminate\Console\ConfirmableTrait;
8
use Spatie\MediaLibrary\FileManipulator;
9
use Spatie\MediaLibrary\MediaRepository;
10
use Illuminate\Contracts\Filesystem\Factory;
11
use Illuminate\Database\Eloquent\Collection;
12
use Spatie\MediaLibrary\Conversion\Conversion;
13
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded;
14
use Spatie\MediaLibrary\Conversion\ConversionCollection;
15
use Spatie\MediaLibrary\PathGenerator\BasePathGenerator;
16
use Spatie\MediaLibrary\ResponsiveImages\RegisteredResponsiveImages;
17
18
class CleanCommand extends Command
19
{
20
    use ConfirmableTrait;
21
22
    protected $signature = 'medialibrary:clean {modelType?} {collectionName?} {disk?}
23
    {--dry-run : List files that will be removed without removing them},
24
    {--force : Force the operation to run when in production},
25
    {--rate-limit= : Limit the number of request per second }';
26
27
    protected $description = 'Clean deprecated conversions and files without related model.';
28
29
    /** @var \Spatie\MediaLibrary\MediaRepository */
30
    protected $mediaRepository;
31
32
    /** @var \Spatie\MediaLibrary\FileManipulator */
33
    protected $fileManipulator;
34
35
    /** @var \Illuminate\Contracts\Filesystem\Factory */
36
    protected $fileSystem;
37
38
    /** @var \Spatie\MediaLibrary\PathGenerator\BasePathGenerator */
39
    protected $basePathGenerator;
40
41
    /** @var bool */
42
    protected $isDryRun = false;
43
44
    /** @var int */
45
    protected $rateLimit = 0;
46
47
    /**
48
     * @param \Spatie\MediaLibrary\MediaRepository                 $mediaRepository
49
     * @param \Spatie\MediaLibrary\FileManipulator                 $fileManipulator
50
     * @param \Illuminate\Contracts\Filesystem\Factory             $fileSystem
51
     * @param \Spatie\MediaLibrary\PathGenerator\BasePathGenerator $basePathGenerator
52
     */
53
    public function __construct(
54
        MediaRepository $mediaRepository,
55
        FileManipulator $fileManipulator,
56
        Factory $fileSystem,
57
        BasePathGenerator $basePathGenerator
58
    ) {
59
        parent::__construct();
60
61
        $this->mediaRepository = $mediaRepository;
62
        $this->fileManipulator = $fileManipulator;
63
        $this->fileSystem = $fileSystem;
64
        $this->basePathGenerator = $basePathGenerator;
65
    }
66
67
    public function handle()
68
    {
69
        if (! $this->confirmToProceed()) {
70
            return;
71
        }
72
73
        $this->isDryRun = $this->option('dry-run');
74
        $this->rateLimit = (int) $this->option('rate-limit');
75
76
        $this->deleteFilesGeneratedForDeprecatedConversions();
77
78
        $this->deleteOrphanedDirectories();
79
80
        $this->info('All done!');
81
    }
82
83
    public function getMediaItems(): Collection
84
    {
85
        $modelType = $this->argument('modelType');
86
        $collectionName = $this->argument('collectionName');
87
88
        if (! is_null($modelType) && ! is_null($collectionName)) {
89
            return $this->mediaRepository->getByModelTypeAndCollectionName(
90
                $modelType,
91
                $collectionName
92
            );
93
        }
94
95
        if (! is_null($modelType)) {
96
            return $this->mediaRepository->getByModelType($modelType);
97
        }
98
99
        if (! is_null($collectionName)) {
100
            return $this->mediaRepository->getByCollectionName($collectionName);
101
        }
102
103
        return $this->mediaRepository->all();
104
    }
105
106
    protected function deleteFilesGeneratedForDeprecatedConversions()
107
    {
108
        $this->getMediaItems()->each(function (Media $media) {
109
            $this->deleteConversionFilesForDeprecatedConversions($media);
110
111
            if ($media->responsive_images) {
112
                $this->deleteResponsiveImagesForDeprecatedConversions($media);
113
            }
114
115
            if ($this->rateLimit) {
116
                usleep((1 / $this->rateLimit) * 1000000 * 2);
117
            }
118
        });
119
    }
120
121
    protected function deleteConversionFilesForDeprecatedConversions(Media $media)
122
    {
123
        $conversionFilePaths = ConversionCollection::createForMedia($media)->getConversionsFiles($media->collection_name);
124
125
        $conversionPath = $this->basePathGenerator->getPathForConversions($media);
126
        $currentFilePaths = $this->fileSystem->disk($media->disk)->files($conversionPath);
127
128
        collect($currentFilePaths)
129
            ->reject(function (string $currentFilePath) use ($conversionFilePaths) {
130
                return $conversionFilePaths->contains(basename($currentFilePath));
131
            })
132
            ->each(function (string $currentFilePath) use ($media) {
133
                if (! $this->isDryRun) {
134
                    $this->fileSystem->disk($media->disk)->delete($currentFilePath);
135
136
                    $this->markConversionAsRemoved($media, $currentFilePath);
137
                }
138
139
                $this->info("Deprecated conversion file `{$currentFilePath}` ".($this->isDryRun ? 'found' : 'has been removed'));
140
            });
141
    }
142
143
    protected function deleteResponsiveImagesForDeprecatedConversions(Media $media)
144
    {
145
        $conversionNames = ConversionCollection::createForMedia($media)
146
            ->map(function (Conversion $conversion) {
147
                return $conversion->getName();
148
            })
149
            ->push('medialibrary_original');
150
151
        $responsiveImagesGeneratedFor = array_keys($media->responsive_images);
152
153
        collect($responsiveImagesGeneratedFor)
154
            ->map(function (string $generatedFor) use ($media) {
155
                return $media->responsiveImages($generatedFor);
156
            })
157
            ->reject(function (RegisteredResponsiveImages $responsiveImages) use ($conversionNames) {
158
                return $conversionNames->contains($responsiveImages->generatedFor);
159
            })
160
            ->each(function (RegisteredResponsiveImages $responsiveImages) {
161
                if (! $this->isDryRun) {
162
                    $responsiveImages->delete();
163
                }
164
            });
165
    }
166
167
    protected function deleteOrphanedDirectories()
168
    {
169
        $diskName = $this->argument('disk') ?: config('medialibrary.disk_name');
170
171
        if (is_null(config("filesystems.disks.{$diskName}"))) {
172
            throw FileCannotBeAdded::diskDoesNotExist($diskName);
173
        }
174
175
        $mediaIds = collect($this->mediaRepository->all()->pluck('id')->toArray());
176
177
        collect($this->fileSystem->disk($diskName)->directories())
178
            ->filter(function (string $directory) {
179
                return is_numeric($directory);
180
            })
181
            ->reject(function (string $directory) use ($mediaIds) {
182
                return $mediaIds->contains((int) $directory);
183
            })->each(function (string $directory) use ($diskName) {
184
                if (! $this->isDryRun) {
185
                    $this->fileSystem->disk($diskName)->deleteDirectory($directory);
186
                }
187
188
                if ($this->rateLimit) {
189
                    usleep((1 / $this->rateLimit) * 1000000);
190
                }
191
192
                $this->info("Orphaned media directory `{$directory}` ".($this->isDryRun ? 'found' : 'has been removed'));
193
            });
194
    }
195
196
    protected function markConversionAsRemoved(Media $media, string $conversionPath)
197
    {
198
        $conversionFile = pathinfo($conversionPath, PATHINFO_FILENAME);
199
200
        $generatedConversionName = null;
201
202
        $media->getGeneratedConversions()
203
            ->filter(function (bool $isGenerated, string $generatedConversionName) use ($conversionFile) {
204
                return str_contains($conversionFile, $generatedConversionName);
0 ignored issues
show
Deprecated Code introduced by
The function str_contains() has been deprecated with message: Str::contains() should be used directly instead. Will be removed in Laravel 5.9.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
205
            })
206
            ->each(function (bool $isGenerated, string $generatedConversionName) use ($media) {
207
                $media->markAsConversionGenerated($generatedConversionName, false);
208
            });
209
210
        $media->save();
211
    }
212
}
213