Completed
Push — master ( a6dd06...dfef89 )
by Freek
01:14
created

HasMediaTrait::getFallbackMediaUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Spatie\MediaLibrary\HasMedia;
4
5
use DateTimeInterface;
6
use Illuminate\Http\File;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Str;
9
use Illuminate\Support\Collection;
10
use Spatie\MediaLibrary\Models\Media;
11
use Spatie\MediaLibrary\MediaRepository;
12
use Illuminate\Support\Facades\Validator;
13
use Spatie\MediaLibrary\FileAdder\FileAdder;
14
use Illuminate\Database\Eloquent\SoftDeletes;
15
use Spatie\MediaLibrary\Conversion\Conversion;
16
use Spatie\MediaLibrary\FileAdder\FileAdderFactory;
17
use Spatie\MediaLibrary\Events\CollectionHasBeenCleared;
18
use Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted;
19
use Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated;
20
use Spatie\MediaLibrary\MediaCollection\MediaCollection;
21
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\UnreachableUrl;
22
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\InvalidBase64Data;
23
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\MimeTypeNotAllowed;
24
25
trait HasMediaTrait
26
{
27
    /** @var Conversion[] */
28
    public $mediaConversions = [];
29
30
    /** @var MediaCollection[] */
31
    public $mediaCollections = [];
32
33
    /** @var bool */
34
    protected $deletePreservingMedia = false;
35
36
    /** @var array */
37
    protected $unAttachedMediaLibraryItems = [];
38
39
    public static function bootHasMediaTrait()
40
    {
41
        static::deleting(function (HasMedia $entity) {
42
            if ($entity->shouldDeletePreservingMedia()) {
43
                return;
44
            }
45
46
            if (in_array(SoftDeletes::class, class_uses_recursive($entity))) {
47
                if (! $entity->forceDeleting) {
0 ignored issues
show
Bug introduced by
Accessing forceDeleting on the interface Spatie\MediaLibrary\HasMedia\HasMedia suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
48
                    return;
49
                }
50
            }
51
52
            $entity->media()->get()->each->delete();
53
        });
54
    }
55
56
    /**
57
     * Set the polymorphic relation.
58
     *
59
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
60
     */
61
    public function media()
62
    {
63
        return $this->morphMany(config('medialibrary.media_model'), 'model');
64
    }
65
66
    /**
67
     * Add a file to the medialibrary.
68
     *
69
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
70
     *
71
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
72
     */
73
    public function addMedia($file)
74
    {
75
        return app(FileAdderFactory::class)->create($this, $file);
76
    }
77
78
    /**
79
     * Add a file from a request.
80
     *
81
     * @param string $key
82
     *
83
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
84
     */
85
    public function addMediaFromRequest(string $key)
86
    {
87
        return app(FileAdderFactory::class)->createFromRequest($this, $key);
88
    }
89
90
    /**
91
     * Add multiple files from a request by keys.
92
     *
93
     * @param string[] $keys
94
     *
95
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
96
     */
97
    public function addMultipleMediaFromRequest(array $keys)
98
    {
99
        return app(FileAdderFactory::class)->createMultipleFromRequest($this, $keys);
100
    }
101
102
    /**
103
     * Add all files from a request.
104
     *
105
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
106
     */
107
    public function addAllMediaFromRequest()
108
    {
109
        return app(FileAdderFactory::class)->createAllFromRequest($this);
110
    }
111
112
    /**
113
     * Add a remote file to the medialibrary.
114
     *
115
     * @param string $url
116
     * @param string|array ...$allowedMimeTypes
117
     *
118
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
119
     *
120
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
121
     */
122
    public function addMediaFromUrl(string $url, ...$allowedMimeTypes)
123
    {
124
        if (! $stream = @fopen($url, 'r')) {
125
            throw UnreachableUrl::create($url);
126
        }
127
128
        $temporaryFile = tempnam(sys_get_temp_dir(), 'media-library');
129
        file_put_contents($temporaryFile, $stream);
130
131
        $this->guardAgainstInvalidMimeType($temporaryFile, $allowedMimeTypes);
132
133
        $filename = basename(parse_url($url, PHP_URL_PATH));
134
        $filename = str_replace('%20', ' ', $filename);
135
136
        if ($filename === '') {
137
            $filename = 'file';
138
        }
139
140
        $mediaExtension = explode('/', mime_content_type($temporaryFile));
141
142
        if (! Str::contains($filename, '.')) {
143
            $filename = "{$filename}.{$mediaExtension[1]}";
144
        }
145
146
        return app(FileAdderFactory::class)
147
            ->create($this, $temporaryFile)
148
            ->usingName(pathinfo($filename, PATHINFO_FILENAME))
149
            ->usingFileName($filename);
150
    }
151
152
    /**
153
     * Add a base64 encoded file to the medialibrary.
154
     *
155
     * @param string $base64data
156
     * @param string|array ...$allowedMimeTypes
157
     *
158
     * @throws InvalidBase64Data
159
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
160
     *
161
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
162
     */
163
    public function addMediaFromBase64(string $base64data, ...$allowedMimeTypes): FileAdder
164
    {
165
        // strip out data uri scheme information (see RFC 2397)
166
        if (strpos($base64data, ';base64') !== false) {
167
            [$_, $base64data] = explode(';', $base64data);
0 ignored issues
show
Bug introduced by
The variable $_ does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
168
            [$_, $base64data] = explode(',', $base64data);
169
        }
170
171
        // strict mode filters for non-base64 alphabet characters
172
        if (base64_decode($base64data, true) === false) {
173
            throw InvalidBase64Data::create();
174
        }
175
176
        // decoding and then reencoding should not change the data
177
        if (base64_encode(base64_decode($base64data)) !== $base64data) {
178
            throw InvalidBase64Data::create();
179
        }
180
181
        $binaryData = base64_decode($base64data);
182
183
        // temporarily store the decoded data on the filesystem to be able to pass it to the fileAdder
184
        $tmpFile = tempnam(sys_get_temp_dir(), 'medialibrary');
185
        file_put_contents($tmpFile, $binaryData);
186
187
        $this->guardAgainstInvalidMimeType($tmpFile, $allowedMimeTypes);
188
189
        $file = app(FileAdderFactory::class)->create($this, $tmpFile);
190
191
        return $file;
192
    }
193
194
    /**
195
     * Copy a file to the medialibrary.
196
     *
197
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
198
     *
199
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
200
     */
201
    public function copyMedia($file)
202
    {
203
        return $this->addMedia($file)->preservingOriginal();
204
    }
205
206
    /*
207
     * Determine if there is media in the given collection.
208
     */
209
    public function hasMedia(string $collectionName = 'default'): bool
210
    {
211
        return count($this->getMedia($collectionName)) ? true : false;
212
    }
213
214
    /**
215
     * Get media collection by its collectionName.
216
     *
217
     * @param string $collectionName
218
     * @param array|callable $filters
219
     *
220
     * @return \Illuminate\Support\Collection
221
     */
222
    public function getMedia(string $collectionName = 'default', $filters = []): Collection
223
    {
224
        return app(MediaRepository::class)->getCollection($this, $collectionName, $filters);
225
    }
226
227
    public function getFirstMedia(string $collectionName = 'default', array $filters = []): ?Media
228
    {
229
        $media = $this->getMedia($collectionName, $filters);
230
231
        return $media->first();
232
    }
233
234
    /*
235
     * Get the url of the image for the given conversionName
236
     * for first media for the given collectionName.
237
     * If no profile is given, return the source's url.
238
     */
239
    public function getFirstMediaUrl(string $collectionName = 'default', string $conversionName = ''): string
240
    {
241
        $media = $this->getFirstMedia($collectionName);
242
243
        if (! $media) {
244
            return $this->getFallbackMediaUrl($collectionName) ?: '';
245
        }
246
247
        return $media->getUrl($conversionName);
248
    }
249
250
    /*
251
     * Get the url of the image for the given conversionName
252
     * for first media for the given collectionName.
253
     *
254
     * If no profile is given, return the source's url.
255
     */
256
    public function getFirstTemporaryUrl(DateTimeInterface $expiration, string $collectionName = 'default', string $conversionName = ''): string
257
    {
258
        $media = $this->getFirstMedia($collectionName);
259
260
        if (! $media) {
261
            return $this->getFallbackMediaUrl($collectionName) ?: '';
262
        }
263
264
        return $media->getTemporaryUrl($expiration, $conversionName);
265
    }
266
267
    public function getMediaCollection(string $collectionName = 'default'): ?MediaCollection
268
    {
269
        $this->registerMediaCollections();
270
271
        return collect($this->mediaCollections)
272
            ->first(function (MediaCollection $collection) use ($collectionName) {
273
                return $collection->name === $collectionName;
274
            });
275
    }
276
277
    public function getFallbackMediaUrl(string $collectionName = 'default')
278
    {
279
        return optional($this->getMediaCollection($collectionName))->fallbackUrl;
280
    }
281
282
    public function getFallbackMediaPath(string $collectionName = 'default'): string
283
    {
284
        return optional($this->getMediaCollection($collectionName))->fallbackPath;
285
    }
286
287
    /*
288
     * Get the url of the image for the given conversionName
289
     * for first media for the given collectionName.
290
     * If no profile is given, return the source's url.
291
     */
292
    public function getFirstMediaPath(string $collectionName = 'default', string $conversionName = ''): string
293
    {
294
        $media = $this->getFirstMedia($collectionName);
295
296
        if (! $media) {
297
            return $this->getFallbackMediaPath($collectionName) ?: '';
298
        }
299
300
        return $media->getPath($conversionName);
301
    }
302
303
    /**
304
     * Update a media collection by deleting and inserting again with new values.
305
     *
306
     * @param array $newMediaArray
307
     * @param string $collectionName
308
     *
309
     * @return \Illuminate\Support\Collection
310
     *
311
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated
312
     */
313
    public function updateMedia(array $newMediaArray, string $collectionName = 'default'): Collection
314
    {
315
        $this->removeMediaItemsNotPresentInArray($newMediaArray, $collectionName);
316
317
        $mediaClass = config('medialibrary.media_model');
318
        $mediaInstance = new $mediaClass();
319
        $keyName = $mediaInstance->getKeyName();
320
321
        return collect($newMediaArray)
322
            ->map(function (array $newMediaItem) use ($collectionName, $mediaClass, $keyName) {
323
                static $orderColumn = 1;
324
325
                $currentMedia = $mediaClass::findOrFail($newMediaItem[$keyName]);
326
327
                if ($currentMedia->collection_name !== $collectionName) {
328
                    throw MediaCannotBeUpdated::doesNotBelongToCollection($collectionName, $currentMedia);
329
                }
330
331
                if (array_key_exists('name', $newMediaItem)) {
332
                    $currentMedia->name = $newMediaItem['name'];
333
                }
334
335
                if (array_key_exists('custom_properties', $newMediaItem)) {
336
                    $currentMedia->custom_properties = $newMediaItem['custom_properties'];
337
                }
338
339
                $currentMedia->order_column = $orderColumn++;
340
341
                $currentMedia->save();
342
343
                return $currentMedia;
344
            });
345
    }
346
347
    protected function removeMediaItemsNotPresentInArray(array $newMediaArray, string $collectionName = 'default')
348
    {
349
        $this->getMedia($collectionName)
350
            ->reject(function (Media $currentMediaItem) use ($newMediaArray) {
351
                return in_array($currentMediaItem->getKey(), array_column($newMediaArray, $currentMediaItem->getKeyName()));
352
            })
353
            ->each->delete();
354
    }
355
356
    /**
357
     * Remove all media in the given collection.
358
     *
359
     * @param string $collectionName
360
     *
361
     * @return $this
362
     */
363
    public function clearMediaCollection(string $collectionName = 'default'): self
364
    {
365
        $this->getMedia($collectionName)
366
            ->each->delete();
367
368
        event(new CollectionHasBeenCleared($this, $collectionName));
369
370
        if ($this->mediaIsPreloaded()) {
371
            unset($this->media);
372
        }
373
374
        return $this;
375
    }
376
377
    /**
378
     * Remove all media in the given collection except some.
379
     *
380
     * @param string $collectionName
381
     * @param \Spatie\MediaLibrary\Models\Media[]|\Illuminate\Support\Collection $excludedMedia
382
     *
383
     * @return $this
384
     */
385
    public function clearMediaCollectionExcept(string $collectionName = 'default', $excludedMedia = [])
386
    {
387
        if ($excludedMedia instanceof Media) {
388
            $excludedMedia = collect()->push($excludedMedia);
389
        }
390
391
        $excludedMedia = collect($excludedMedia);
392
393
        if ($excludedMedia->isEmpty()) {
394
            return $this->clearMediaCollection($collectionName);
395
        }
396
397
        $this->getMedia($collectionName)
398
            ->reject(function (Media $media) use ($excludedMedia) {
399
                return $excludedMedia->where($media->getKeyName(), $media->getKey())->count();
400
            })
401
            ->each->delete();
402
403
        if ($this->mediaIsPreloaded()) {
404
            unset($this->media);
405
        }
406
407
        return $this;
408
    }
409
410
    /**
411
     * Delete the associated media with the given id.
412
     * You may also pass a media object.
413
     *
414
     * @param int|\Spatie\MediaLibrary\Models\Media $mediaId
415
     *
416
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted
417
     */
418
    public function deleteMedia($mediaId)
419
    {
420
        if ($mediaId instanceof Media) {
421
            $mediaId = $mediaId->getKey();
422
        }
423
424
        $media = $this->media->find($mediaId);
425
426
        if (! $media) {
427
            throw MediaCannotBeDeleted::doesNotBelongToModel($mediaId, $this);
428
        }
429
430
        $media->delete();
431
    }
432
433
    /*
434
     * Add a conversion.
435
     */
436
    public function addMediaConversion(string $name): Conversion
437
    {
438
        $conversion = Conversion::create($name);
439
440
        $this->mediaConversions[] = $conversion;
441
442
        return $conversion;
443
    }
444
445
    public function addMediaCollection(string $name): MediaCollection
446
    {
447
        $mediaCollection = MediaCollection::create($name);
448
449
        $this->mediaCollections[] = $mediaCollection;
450
451
        return $mediaCollection;
452
    }
453
454
    /**
455
     * Delete the model, but preserve all the associated media.
456
     *
457
     * @return bool
458
     */
459
    public function deletePreservingMedia(): bool
460
    {
461
        $this->deletePreservingMedia = true;
462
463
        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...
464
    }
465
466
    /**
467
     * Determines if the media files should be preserved when the media object gets deleted.
468
     *
469
     * @return bool
470
     */
471
    public function shouldDeletePreservingMedia()
472
    {
473
        return $this->deletePreservingMedia ?? false;
474
    }
475
476
    protected function mediaIsPreloaded(): bool
477
    {
478
        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...
479
    }
480
481
    /**
482
     * Cache the media on the object.
483
     *
484
     * @param string $collectionName
485
     *
486
     * @return mixed
487
     */
488
    public function loadMedia(string $collectionName)
489
    {
490
        $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...
491
            ? $this->media
492
            : collect($this->unAttachedMediaLibraryItems)->pluck('media');
493
494
        return $collection
495
            ->filter(function (Media $mediaItem) use ($collectionName) {
496
                if ($collectionName == '') {
497
                    return true;
498
                }
499
500
                return $mediaItem->collection_name === $collectionName;
501
            })
502
            ->sortBy('order_column')
503
            ->values();
504
    }
505
506
    public function prepareToAttachMedia(Media $media, FileAdder $fileAdder)
507
    {
508
        $this->unAttachedMediaLibraryItems[] = compact('media', 'fileAdder');
509
    }
510
511
    public function processUnattachedMedia(callable $callable)
512
    {
513
        foreach ($this->unAttachedMediaLibraryItems as $item) {
514
            $callable($item['media'], $item['fileAdder']);
515
        }
516
517
        $this->unAttachedMediaLibraryItems = [];
518
    }
519
520
    protected function guardAgainstInvalidMimeType(string $file, ...$allowedMimeTypes)
521
    {
522
        $allowedMimeTypes = Arr::flatten($allowedMimeTypes);
523
524
        if (empty($allowedMimeTypes)) {
525
            return;
526
        }
527
528
        $validation = Validator::make(
529
            ['file' => new File($file)],
530
            ['file' => 'mimetypes:'.implode(',', $allowedMimeTypes)]
531
        );
532
533
        if ($validation->fails()) {
534
            throw MimeTypeNotAllowed::create($file, $allowedMimeTypes);
535
        }
536
    }
537
538
    public function registerMediaConversions(Media $media = null)
0 ignored issues
show
Unused Code introduced by
The parameter $media is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
539
    {
540
    }
541
542
    public function registerMediaCollections()
543
    {
544
    }
545
546
    public function registerAllMediaConversions(Media $media = null)
547
    {
548
        $this->registerMediaCollections();
549
550
        collect($this->mediaCollections)->each(function (MediaCollection $mediaCollection) use ($media) {
551
            $actualMediaConversions = $this->mediaConversions;
552
553
            $this->mediaConversions = [];
554
555
            ($mediaCollection->mediaConversionRegistrations)($media);
556
557
            $preparedMediaConversions = collect($this->mediaConversions)
558
                ->each(function (Conversion $conversion) use ($mediaCollection) {
559
                    $conversion->performOnCollections($mediaCollection->name);
560
                })
561
                ->values()
562
                ->toArray();
563
564
            $this->mediaConversions = array_merge($actualMediaConversions, $preparedMediaConversions);
565
        });
566
567
        $this->registerMediaConversions($media);
568
    }
569
}
570