Completed
Push — master ( aacab2...e5ee50 )
by Freek
04:08 queued 01:40
created

HasMediaTrait::guardAgainstInvalidMimeType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 2
1
<?php
2
3
namespace Spatie\MediaLibrary\HasMedia;
4
5
use Illuminate\Http\File;
6
use Spatie\MediaLibrary\Media;
7
use Illuminate\Support\Collection;
8
use Spatie\MediaLibrary\MediaRepository;
9
use Illuminate\Support\Facades\Validator;
10
use Spatie\MediaLibrary\FileAdder\FileAdder;
11
use Spatie\MediaLibrary\Conversion\Conversion;
12
use Spatie\MediaLibrary\FileAdder\FileAdderFactory;
13
use Spatie\MediaLibrary\HasMedia\Interfaces\HasMedia;
14
use Spatie\MediaLibrary\Events\CollectionHasBeenCleared;
15
use Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted;
16
use Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated;
17
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\UnreachableUrl;
18
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\InvalidBase64Data;
19
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\MimeTypeNotAllowed;
20
21
trait HasMediaTrait
22
{
23
    /** @var array */
24
    public $mediaConversions = [];
25
26
    /** @var bool */
27
    protected $deletePreservingMedia = false;
28
29
    /** @var array */
30
    protected $unAttachedMediaLibraryItems = [];
31
32
    public static function bootHasMediaTrait()
33
    {
34
        static::deleted(function (HasMedia $entity) {
35
            if ($entity->shouldDeletePreservingMedia()) {
36
                return;
37
            }
38
39
            $entity->media()->get()->each->delete();
40
        });
41
    }
42
43
    /**
44
     * Set the polymorphic relation.
45
     *
46
     * @return mixed
47
     */
48
    public function media()
49
    {
50
        return $this->morphMany(config('medialibrary.media_model'), 'model');
51
    }
52
53
    /**
54
     * Add a file to the medialibrary.
55
     *
56
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
57
     *
58
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
59
     */
60
    public function addMedia($file)
61
    {
62
        return app(FileAdderFactory::class)->create($this, $file);
63
    }
64
65
    /**
66
     * Add a file from a request.
67
     *
68
     * @param string $key
69
     *
70
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
71
     */
72
    public function addMediaFromRequest(string $key)
73
    {
74
        return app(FileAdderFactory::class)->createFromRequest($this, $key);
75
    }
76
77
    /**
78
     * Add multiple files from a request by keys.
79
     *
80
     * @param string[] $keys
81
     *
82
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
83
     */
84
    public function addMultipleMediaFromRequest(array $keys)
85
    {
86
        return app(FileAdderFactory::class)->createMultipleFromRequest($this, $keys);
87
    }
88
89
    /**
90
     * Add all files from a request.
91
     *
92
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
93
     */
94
    public function addAllMediaFromRequest()
95
    {
96
        return app(FileAdderFactory::class)->createAllFromRequest($this);
97
    }
98
99
    /**
100
     * Add a remote file to the medialibrary.
101
     *
102
     * @param string $url
103
     * @param array $allowedMimeTypes
104
     *
105
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
106
     *
107
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
108
     */
109
    public function addMediaFromUrl(string $url, array $allowedMimeTypes = [])
110
    {
111
        if (! $stream = @fopen($url, 'r')) {
112
            throw UnreachableUrl::create($url);
113
        }
114
115
        $tmpFile = tempnam(sys_get_temp_dir(), 'media-library');
116
        file_put_contents($tmpFile, $stream);
117
118
        $this->guardAgainstInvalidMimeType($tmpFile, $allowedMimeTypes);
119
120
        $filename = basename(parse_url($url, PHP_URL_PATH));
121
122
        return app(FileAdderFactory::class)
123
            ->create($this, $tmpFile)
124
            ->usingName(pathinfo($filename, PATHINFO_FILENAME))
125
            ->usingFileName($filename);
126
    }
127
128
    /**
129
     * Add a base64 encoded file to the medialibrary.
130
     *
131
     * @param string $base64data
132
     * @param array $allowedMimeTypes
133
     *
134
     * @throws InvalidBase64Data
135
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
136
     *
137
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
138
     */
139
    public function addMediaFromBase64(string $base64data, array $allowedMimeTypes = [])
140
    {
141
        // strip out data uri scheme information (see RFC 2397)
142
        if (strpos($base64data, ';base64') !== false) {
143
            list(, $base64data) = explode(';', $base64data);
144
            list(, $base64data) = explode(',', $base64data);
145
        }
146
147
        // strict mode filters for non-base64 alphabet characters
148
        if (base64_decode($base64data, true) === false) {
149
            throw InvalidBase64Data::create();
150
        }
151
152
        // decoding and then reeconding should not change the data
153
        if (base64_encode(base64_decode($base64data)) !== $base64data) {
154
            throw InvalidBase64Data::create();
155
        }
156
157
        $binaryData = base64_decode($base64data);
158
159
        // temporarily store the decoded data on the filesystem to be able to pass it to the fileAdder
160
        $tmpFile = tempnam(sys_get_temp_dir(), 'medialibrary');
161
        file_put_contents($tmpFile, $binaryData);
162
163
        $this->guardAgainstInvalidMimeType($tmpFile, $allowedMimeTypes);
164
165
        $file = app(FileAdderFactory::class)
166
            ->create($this, $tmpFile);
167
168
        return $file;
169
    }
170
171
    /**
172
     * Copy a file to the medialibrary.
173
     *
174
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
175
     *
176
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
177
     */
178
    public function copyMedia($file)
179
    {
180
        return $this->addMedia($file)->preservingOriginal();
181
    }
182
183
    /*
184
     * Determine if there is media in the given collection.
185
     */
186
    public function hasMedia(string $collectionName = 'default'): bool
187
    {
188
        return count($this->getMedia($collectionName)) ? true : false;
189
    }
190
191
    /**
192
     * Get media collection by its collectionName.
193
     *
194
     * @param string $collectionName
195
     * @param array|callable $filters
196
     *
197
     * @return \Illuminate\Support\Collection
198
     */
199
    public function getMedia(string $collectionName = 'default', $filters = []): Collection
200
    {
201
        return app(MediaRepository::class)->getCollection($this, $collectionName, $filters);
202
    }
203
204
    /**
205
     * Get the first media item of a media collection.
206
     *
207
     * @param string $collectionName
208
     * @param array $filters
209
     *
210
     * @return Media|null
211
     */
212
    public function getFirstMedia(string $collectionName = 'default', array $filters = [])
213
    {
214
        $media = $this->getMedia($collectionName, $filters);
215
216
        return $media->first();
217
    }
218
219
    /*
220
     * Get the url of the image for the given conversionName
221
     * for first media for the given collectionName.
222
     * If no profile is given, return the source's url.
223
     */
224
    public function getFirstMediaUrl(string $collectionName = 'default', string $conversionName = ''): string
225
    {
226
        $media = $this->getFirstMedia($collectionName);
227
228
        if (! $media) {
229
            return '';
230
        }
231
232
        return $media->getUrl($conversionName);
233
    }
234
235
    /*
236
     * Get the url of the image for the given conversionName
237
     * for first media for the given collectionName.
238
     * If no profile is given, return the source's url.
239
     */
240
    public function getFirstMediaPath(string $collectionName = 'default', string $conversionName = ''): string
241
    {
242
        $media = $this->getFirstMedia($collectionName);
243
244
        if (! $media) {
245
            return '';
246
        }
247
248
        return $media->getPath($conversionName);
249
    }
250
251
    /**
252
     * Update a media collection by deleting and inserting again with new values.
253
     *
254
     * @param array $newMediaArray
255
     * @param string $collectionName
256
     *
257
     * @return \Illuminate\Support\Collection
258
     *
259
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated
260
     */
261
    public function updateMedia(array $newMediaArray, string $collectionName = 'default'): Collection
262
    {
263
        $this->removeMediaItemsNotPresentInArray($newMediaArray, $collectionName);
264
265
        return collect($newMediaArray)
266
            ->map(function (array $newMediaItem) use ($collectionName) {
267
                static $orderColumn = 1;
268
269
                $mediaClass = config('medialibrary.media_model');
270
                $currentMedia = $mediaClass::findOrFail($newMediaItem['id']);
271
272
                if ($currentMedia->collection_name != $collectionName) {
273
                    throw MediaCannotBeUpdated::doesNotBelongToCollection($collectionName, $currentMedia);
274
                }
275
276
                if (array_key_exists('name', $newMediaItem)) {
277
                    $currentMedia->name = $newMediaItem['name'];
278
                }
279
280
                if (array_key_exists('custom_properties', $newMediaItem)) {
281
                    $currentMedia->custom_properties = $newMediaItem['custom_properties'];
282
                }
283
284
                $currentMedia->order_column = $orderColumn++;
285
286
                $currentMedia->save();
287
288
                return $currentMedia;
289
            });
290
    }
291
292
    /**
293
     * @param array $newMediaArray
294
     * @param string $collectionName
295
     */
296
    protected function removeMediaItemsNotPresentInArray(array $newMediaArray, string $collectionName = 'default')
297
    {
298
        $this->getMedia($collectionName)
299
            ->reject(function (Media $currentMediaItem) use ($newMediaArray) {
300
                return in_array($currentMediaItem->id, array_column($newMediaArray, 'id'));
301
            })
302
            ->each->delete();
303
    }
304
305
    /**
306
     * Remove all media in the given collection.
307
     *
308
     * @param string $collectionName
309
     *
310
     * @return $this
311
     */
312
    public function clearMediaCollection(string $collectionName = 'default')
313
    {
314
        $this->getMedia($collectionName)
315
            ->each->delete();
316
317
        event(new CollectionHasBeenCleared($this, $collectionName));
318
319
        if ($this->mediaIsPreloaded()) {
320
            unset($this->media);
321
        }
322
323
        return $this;
324
    }
325
326
    /**
327
     * Remove all media in the given collection except some.
328
     *
329
     * @param string $collectionName
330
     * @param \Spatie\MediaLibrary\Media[]|\Illuminate\Support\Collection $excludedMedia
331
     *
332
     * @return $this
333
     */
334
    public function clearMediaCollectionExcept(string $collectionName = 'default', $excludedMedia = [])
335
    {
336
        $excludedMedia = collect($excludedMedia);
337
338
        if ($excludedMedia->isEmpty()) {
339
            return $this->clearMediaCollection($collectionName);
340
        }
341
342
        $this->getMedia($collectionName)
343
            ->reject(function (Media $media) use ($excludedMedia) {
344
                return $excludedMedia->where('id', $media->id)->count();
345
            })
346
            ->each->delete();
347
348
        if ($this->mediaIsPreloaded()) {
349
            unset($this->media);
350
        }
351
352
        return $this;
353
    }
354
355
    /**
356
     * Delete the associated media with the given id.
357
     * You may also pass a media object.
358
     *
359
     * @param int|\Spatie\MediaLibrary\Media $mediaId
360
     *
361
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted
362
     */
363
    public function deleteMedia($mediaId)
364
    {
365
        if ($mediaId instanceof Media) {
366
            $mediaId = $mediaId->id;
367
        }
368
369
        $media = $this->media->find($mediaId);
370
371
        if (! $media) {
372
            throw MediaCannotBeDeleted::doesNotBelongToModel($media, $this);
373
        }
374
375
        $media->delete();
376
    }
377
378
    /*
379
     * Add a conversion.
380
     */
381
    public function addMediaConversion(string $name): Conversion
382
    {
383
        $conversion = Conversion::create($name);
384
385
        $this->mediaConversions[] = $conversion;
386
387
        return $conversion;
388
    }
389
390
    /**
391
     * Delete the model, but preserve all the associated media.
392
     *
393
     * @return bool
394
     */
395
    public function deletePreservingMedia(): bool
396
    {
397
        $this->deletePreservingMedia = true;
398
399
        return $this->delete();
0 ignored issues
show
Bug introduced by
The method delete() does not exist on Spatie\MediaLibrary\HasMedia\HasMediaTrait. Did you maybe mean deleteMedia()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
400
    }
401
402
    /**
403
     * Determines if the media files should be preserved when the media object gets deleted.
404
     *
405
     * @return \Spatie\MediaLibrary\Media
406
     */
407
    public function shouldDeletePreservingMedia()
408
    {
409
        return $this->deletePreservingMedia ?? false;
410
    }
411
412
    protected function mediaIsPreloaded(): bool
413
    {
414
        return $this->relationLoaded('media');
0 ignored issues
show
Bug introduced by
It seems like relationLoaded() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
415
    }
416
417
    /**
418
     * Cache the media on the object.
419
     *
420
     * @param string $collectionName
421
     *
422
     * @return mixed
423
     */
424
    public function loadMedia(string $collectionName)
425
    {
426
        $collection = $this->exists
0 ignored issues
show
Bug introduced by
The property exists does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
427
            ? $this->media
428
            : collect($this->unAttachedMediaLibraryItems)->pluck('media');
429
430
        return $collection
431
            ->filter(function (Media $mediaItem) use ($collectionName) {
432
                if ($collectionName == '') {
433
                    return true;
434
                }
435
436
                return $mediaItem->collection_name === $collectionName;
437
            })
438
            ->sortBy('order_column')
439
            ->values();
440
    }
441
442
    public function prepareToAttachMedia(Media $media, FileAdder $fileAdder)
443
    {
444
        $this->unAttachedMediaLibraryItems[] = compact('media', 'fileAdder');
445
    }
446
447
    public function processUnattachedMedia(callable $callable)
448
    {
449
        foreach ($this->unAttachedMediaLibraryItems as $item) {
450
            $callable($item['media'], $item['fileAdder']);
451
        }
452
453
        $this->unAttachedMediaLibraryItems = [];
454
    }
455
456
    protected function guardAgainstInvalidMimeType(string $file, array $allowedMimeTypes)
457
    {
458
        if (empty($allowedMimeTypes)) {
459
            return;
460
        }
461
462
        $validation = Validator::make(['file' => new File($file)], ['file' => 'mimetypes:'.implode(',', $allowedMimeTypes)]);
463
464
        if ($validation->fails()) {
465
            throw MimeTypeNotAllowed::create(mime_content_type($file), $allowedMimeTypes);
466
        }
467
    }
468
}
469