1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spatie\MediaLibrary\FileAdder; |
4
|
|
|
|
5
|
|
|
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\FileUnacceptableForCollection; |
6
|
|
|
use Spatie\MediaLibrary\File as PendingFile; |
7
|
|
|
use Spatie\MediaLibrary\Models\Media; |
8
|
|
|
use Spatie\MediaLibrary\Helpers\File; |
9
|
|
|
use Illuminate\Database\Eloquent\Model; |
10
|
|
|
use Spatie\MediaLibrary\Filesystem\Filesystem; |
11
|
|
|
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded; |
12
|
|
|
use Spatie\MediaLibrary\HasMedia\HasMedia; |
13
|
|
|
use Spatie\MediaLibrary\MediaCollection\MediaCollection; |
14
|
|
|
use Symfony\Component\HttpFoundation\File\UploadedFile; |
15
|
|
|
use Symfony\Component\HttpFoundation\File\File as SymfonyFile; |
16
|
|
|
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\UnknownType; |
17
|
|
|
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\FileIsTooBig; |
18
|
|
|
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\DiskDoesNotExist; |
19
|
|
|
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\FileDoesNotExist; |
20
|
|
|
use Spatie\MediaLibrary\Jobs\GenerateResponsiveImages; |
21
|
|
|
use Spatie\MediaLibrary\ImageGenerators\FileTypes\Image as ImageGenerator; |
22
|
|
|
|
23
|
|
|
class FileAdder |
24
|
|
|
{ |
25
|
|
|
/** @var \Illuminate\Database\Eloquent\Model subject */ |
26
|
|
|
protected $subject; |
27
|
|
|
|
28
|
|
|
/** @var \Spatie\MediaLibrary\Filesystem\Filesystem */ |
29
|
|
|
protected $filesystem; |
30
|
|
|
|
31
|
|
|
/** @var bool */ |
32
|
|
|
protected $preserveOriginal = false; |
33
|
|
|
|
34
|
|
|
/** @var string|\Symfony\Component\HttpFoundation\File\UploadedFile */ |
35
|
|
|
protected $file; |
36
|
|
|
|
37
|
|
|
/** @var array */ |
38
|
|
|
protected $properties = []; |
39
|
|
|
|
40
|
|
|
/** @var array */ |
41
|
|
|
protected $customProperties = []; |
42
|
|
|
|
43
|
|
|
/** @var array */ |
44
|
|
|
protected $manipulations = []; |
45
|
|
|
|
46
|
|
|
/** @var string */ |
47
|
|
|
protected $pathToFile; |
48
|
|
|
|
49
|
|
|
/** @var string */ |
50
|
|
|
protected $fileName; |
51
|
|
|
|
52
|
|
|
/** @var string */ |
53
|
|
|
protected $mediaName; |
54
|
|
|
|
55
|
|
|
/** @var string */ |
56
|
|
|
protected $diskName = ''; |
57
|
|
|
|
58
|
|
|
/** @var null|callable */ |
59
|
|
|
protected $fileNameSanitizer; |
60
|
|
|
|
61
|
|
|
/** @var bool */ |
62
|
|
|
protected $generateResponsiveImages = false; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @param Filesystem $fileSystem |
66
|
|
|
*/ |
67
|
|
|
public function __construct(Filesystem $fileSystem) |
68
|
|
|
{ |
69
|
|
|
$this->filesystem = $fileSystem; |
70
|
|
|
|
71
|
|
|
$this->fileNameSanitizer = function ($fileName) { |
72
|
|
|
return $this->defaultSanitizer($fileName); |
73
|
|
|
}; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @param \Illuminate\Database\Eloquent\Model $subject |
78
|
|
|
* |
79
|
|
|
* @return FileAdder |
80
|
|
|
*/ |
81
|
|
|
public function setSubject(Model $subject) |
82
|
|
|
{ |
83
|
|
|
$this->subject = $subject; |
84
|
|
|
|
85
|
|
|
return $this; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/* |
89
|
|
|
* Set the file that needs to be imported. |
90
|
|
|
* |
91
|
|
|
* @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file |
92
|
|
|
* |
93
|
|
|
* @return $this |
94
|
|
|
*/ |
95
|
|
|
public function setFile($file): self |
96
|
|
|
{ |
97
|
|
|
$this->file = $file; |
98
|
|
|
|
99
|
|
|
if (is_string($file)) { |
100
|
|
|
$this->pathToFile = $file; |
101
|
|
|
$this->setFileName(pathinfo($file, PATHINFO_BASENAME)); |
102
|
|
|
$this->mediaName = pathinfo($file, PATHINFO_FILENAME); |
103
|
|
|
|
104
|
|
|
return $this; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
if ($file instanceof UploadedFile) { |
108
|
|
|
$this->pathToFile = $file->getPath() . '/' . $file->getFilename(); |
109
|
|
|
$this->setFileName($file->getClientOriginalName()); |
110
|
|
|
$this->mediaName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); |
111
|
|
|
|
112
|
|
|
return $this; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
if ($file instanceof SymfonyFile) { |
116
|
|
|
$this->pathToFile = $file->getPath() . '/' . $file->getFilename(); |
117
|
|
|
$this->setFileName(pathinfo($file->getFilename(), PATHINFO_BASENAME)); |
118
|
|
|
$this->mediaName = pathinfo($file->getFilename(), PATHINFO_FILENAME); |
119
|
|
|
|
120
|
|
|
return $this; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
throw UnknownType::create(); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
public function preservingOriginal(): self |
127
|
|
|
{ |
128
|
|
|
$this->preserveOriginal = true; |
129
|
|
|
|
130
|
|
|
return $this; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
public function usingName(string $name): self |
134
|
|
|
{ |
135
|
|
|
return $this->setName($name); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
public function setName(string $name): self |
139
|
|
|
{ |
140
|
|
|
$this->mediaName = $name; |
141
|
|
|
|
142
|
|
|
return $this; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
public function usingFileName(string $fileName): self |
146
|
|
|
{ |
147
|
|
|
return $this->setFileName($fileName); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
public function setFileName(string $fileName): self |
151
|
|
|
{ |
152
|
|
|
$this->fileName = $fileName; |
153
|
|
|
|
154
|
|
|
return $this; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
public function withCustomProperties(array $customProperties): self |
158
|
|
|
{ |
159
|
|
|
$this->customProperties = $customProperties; |
160
|
|
|
|
161
|
|
|
return $this; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
public function withManipulations(array $manipulations): self |
165
|
|
|
{ |
166
|
|
|
$this->manipulations = $manipulations; |
167
|
|
|
|
168
|
|
|
return $this; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function withProperties(array $properties): self |
172
|
|
|
{ |
173
|
|
|
$this->properties = $properties; |
174
|
|
|
|
175
|
|
|
return $this; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
public function withAttributes(array $properties): self |
179
|
|
|
{ |
180
|
|
|
return $this->withProperties($properties); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
public function withResponsiveImages(): self |
184
|
|
|
{ |
185
|
|
|
$this->generateResponsiveImages = true; |
186
|
|
|
|
187
|
|
|
return $this; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
public function addCustomHeaders(array $customRemoteHeaders): self |
191
|
|
|
{ |
192
|
|
|
$this->filesystem->addCustomRemoteHeaders($customRemoteHeaders); |
193
|
|
|
|
194
|
|
|
return $this; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
public function toMediaCollectionOnCloudDisk(string $collectionName = 'default'): Media |
198
|
|
|
{ |
199
|
|
|
return $this->toMediaCollection($collectionName, config('filesystems.cloud')); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
public function toMediaCollection(string $collectionName = 'default', string $diskName = ''): Media |
203
|
|
|
{ |
204
|
|
|
if (!is_file($this->pathToFile)) { |
205
|
|
|
throw FileDoesNotExist::create($this->pathToFile); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
if (filesize($this->pathToFile) > config('medialibrary.max_file_size')) { |
209
|
|
|
throw FileIsTooBig::create($this->pathToFile); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
$mediaClass = config('medialibrary.media_model'); |
213
|
|
|
$media = new $mediaClass(); |
214
|
|
|
|
215
|
|
|
$media->name = $this->mediaName; |
216
|
|
|
|
217
|
|
|
$this->fileName = ($this->fileNameSanitizer)($this->fileName); |
218
|
|
|
|
219
|
|
|
$media->file_name = $this->fileName; |
220
|
|
|
|
221
|
|
|
$media->disk = $this->determineDiskName($diskName, $collectionName); |
222
|
|
|
|
223
|
|
|
if (is_null(config("filesystems.disks.{$media->disk}"))) { |
224
|
|
|
throw DiskDoesNotExist::create($media->disk); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
$media->collection_name = $collectionName; |
228
|
|
|
|
229
|
|
|
$media->mime_type = File::getMimetype($this->pathToFile); |
230
|
|
|
$media->size = filesize($this->pathToFile); |
231
|
|
|
$media->custom_properties = $this->customProperties; |
232
|
|
|
|
233
|
|
|
$media->responsive_images = []; |
234
|
|
|
|
235
|
|
|
$media->manipulations = $this->manipulations; |
236
|
|
|
|
237
|
|
|
$media->fill($this->properties); |
238
|
|
|
|
239
|
|
|
$this->attachMedia($media); |
240
|
|
|
|
241
|
|
|
return $media; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
protected function determineDiskName(string $diskName, string $collectionName): string |
245
|
|
|
{ |
246
|
|
|
if ($diskName !== '') { |
247
|
|
|
return $diskName; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
if ($collection = $this->getMediaCollection($collectionName)) { |
251
|
|
|
$collectionDiskName = $collection->diskName; |
252
|
|
|
|
253
|
|
|
if ($collectionDiskName !== '') { |
254
|
|
|
return $collectionDiskName; |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return config('medialibrary.default_filesystem'); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
public function defaultSanitizer(string $fileName): string |
262
|
|
|
{ |
263
|
|
|
return str_replace(['#', '/', '\\', ' '], '-', $fileName); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
public function sanitizingFileName(callable $fileNameSanitizer): self |
267
|
|
|
{ |
268
|
|
|
$this->fileNameSanitizer = $fileNameSanitizer; |
269
|
|
|
|
270
|
|
|
return $this; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
protected function attachMedia(Media $media) |
274
|
|
|
{ |
275
|
|
|
if (!$this->subject->exists) { |
276
|
|
|
$this->subject->prepareToAttachMedia($media, $this); |
277
|
|
|
|
278
|
|
|
$class = get_class($this->subject); |
279
|
|
|
|
280
|
|
|
$class::created(function ($model) { |
281
|
|
|
$model->processUnattachedMedia(function (Media $media, FileAdder $fileAdder) use ($model) { |
282
|
|
|
$this->processMediaItem($model, $media, $fileAdder); |
283
|
|
|
}); |
284
|
|
|
}); |
285
|
|
|
|
286
|
|
|
return; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
$this->processMediaItem($this->subject, $media, $this); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
protected function processMediaItem(HasMedia $model, Media $media, FileAdder $fileAdder) |
293
|
|
|
{ |
294
|
|
|
$this->guardAgainstDisallowedFileAdditions($media, $model); |
|
|
|
|
295
|
|
|
|
296
|
|
|
$model->media()->save($media); |
297
|
|
|
|
298
|
|
|
$this->filesystem->add($fileAdder->pathToFile, $media, $fileAdder->fileName); |
299
|
|
|
|
300
|
|
|
if (!$fileAdder->preserveOriginal) { |
301
|
|
|
unlink($fileAdder->pathToFile); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
if ($this->generateResponsiveImages && (new ImageGenerator())->canConvert($media)) { |
305
|
|
|
$job = new GenerateResponsiveImages($media); |
306
|
|
|
|
307
|
|
|
if ($customQueue = config('medialibrary.queue_name')) { |
308
|
|
|
$job->onQueue($customQueue); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
dispatch($job); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
if (optional($this->getMediaCollection($media->collection_name))->singleFile) { |
315
|
|
|
$model->clearMediaCollectionExcept($media->collection_name, $media); |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
protected function getMediaCollection(string $collectionName): ?MediaCollection |
320
|
|
|
{ |
321
|
|
|
$this->subject->registerMediaCollections(); |
322
|
|
|
|
323
|
|
|
return collect($this->subject->mediaCollections) |
324
|
|
|
->first(function (MediaCollection $collection) use ($collectionName) { |
325
|
|
|
return $collection->name === $collectionName; |
326
|
|
|
}); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
protected function guardAgainstDisallowedFileAdditions(Media $media) |
330
|
|
|
{ |
331
|
|
|
$file = PendingFile::createFromMedia($media); |
332
|
|
|
|
333
|
|
|
if (! $collection = $this->getMediaCollection($media->collection_name)) { |
334
|
|
|
return; |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
if (! ($collection->acceptsFile)($file, $this->subject)) { |
338
|
|
|
throw FileUnacceptableForCollection::create($file, $collection, $this->subject); |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.