Completed
Pull Request — master (#1705)
by Voyula
01:24
created

HasMedia::getFirstMediaUrl()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
namespace Spatie\MediaLibrary\HasMedia;
4
5
use DateTimeInterface;
6
use Illuminate\Database\Eloquent\SoftDeletes;
7
use Illuminate\Http\File;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\Facades\Validator;
11
use Illuminate\Support\Str;
12
use Spatie\MediaLibrary\Conversion\Conversion;
13
use Spatie\MediaLibrary\Events\CollectionHasBeenCleared;
14
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\InvalidBase64Data;
15
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\MimeTypeNotAllowed;
16
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\UnreachableUrl;
17
use Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted;
18
use Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated;
19
use Spatie\MediaLibrary\FileAdder\FileAdder;
20
use Spatie\MediaLibrary\FileAdder\FileAdderFactory;
21
use Spatie\MediaLibrary\MediaCollection\MediaCollection;
22
use Spatie\MediaLibrary\MediaRepository;
23
use Spatie\MediaLibrary\Models\Media;
24
use Spatie\MediaLibrary\HasMedia\Contracts\HasMedia;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Spatie\MediaLibrary\HasMedia\HasMedia.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
25
26
trait HasMedia
27
{
28
    /** @var Conversion[] */
29
    public $mediaConversions = [];
30
31
    /** @var MediaCollection[] */
32
    public $mediaCollections = [];
33
34
    /** @var bool */
35
    protected $deletePreservingMedia = false;
36
37
    /** @var array */
38
    protected $unAttachedMediaLibraryItems = [];
39
40
    public static function bootHasMediaTrait()
41
    {
42
        static::deleting(function (HasMedia $entity) {
43
            if ($entity->shouldDeletePreservingMedia()) {
44
                return;
45
            }
46
47
            if (in_array(SoftDeletes::class, class_uses_recursive($entity))) {
48
                if (! $entity->forceDeleting) {
0 ignored issues
show
Bug introduced by
Accessing forceDeleting on the interface Spatie\MediaLibrary\HasMedia\Contracts\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...
49
                    return;
50
                }
51
            }
52
53
            $entity->media()->get()->each->delete();
54
        });
55
    }
56
57
    /**
58
     * Set the polymorphic relation.
59
     *
60
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
61
     */
62
    public function media()
63
    {
64
        return $this->morphMany(config('medialibrary.media_model'), 'model');
65
    }
66
67
    /**
68
     * Add a file to the medialibrary.
69
     *
70
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
71
     *
72
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
73
     */
74
    public function addMedia($file)
75
    {
76
        return app(FileAdderFactory::class)->create($this, $file);
77
    }
78
79
    /**
80
     * Add a file from the given disk.
81
     *
82
     * @param string $key
83
     * @param string $disk
84
     *
85
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
86
     */
87
    public function addMediaFromDisk(string $key, string $disk = null)
88
    {
89
        return app(FileAdderFactory::class)->createFromDisk($this, $key, $disk ?: config('filesystems.default'));
90
    }
91
92
    /**
93
     * Add a file from a request.
94
     *
95
     * @param string $key
96
     *
97
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
98
     */
99
    public function addMediaFromRequest(string $key)
100
    {
101
        return app(FileAdderFactory::class)->createFromRequest($this, $key);
102
    }
103
104
    /**
105
     * Add multiple files from a request by keys.
106
     *
107
     * @param string[] $keys
108
     *
109
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
110
     */
111
    public function addMultipleMediaFromRequest(array $keys)
112
    {
113
        return app(FileAdderFactory::class)->createMultipleFromRequest($this, $keys);
114
    }
115
116
    /**
117
     * Add all files from a request.
118
     *
119
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
120
     */
121
    public function addAllMediaFromRequest()
122
    {
123
        return app(FileAdderFactory::class)->createAllFromRequest($this);
124
    }
125
126
    /**
127
     * Add a remote file to the medialibrary.
128
     *
129
     * @param string $url
130
     * @param string|array ...$allowedMimeTypes
131
     *
132
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
133
     *
134
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
135
     */
136
    public function addMediaFromUrl(string $url, ...$allowedMimeTypes)
137
    {
138
        if (! $stream = @fopen($url, 'r')) {
139
            throw UnreachableUrl::create($url);
140
        }
141
142
        $temporaryFile = tempnam(sys_get_temp_dir(), 'media-library');
143
        file_put_contents($temporaryFile, $stream);
144
145
        $this->guardAgainstInvalidMimeType($temporaryFile, $allowedMimeTypes);
146
147
        $filename = basename(parse_url($url, PHP_URL_PATH));
148
        $filename = str_replace('%20', ' ', $filename);
149
150
        if ($filename === '') {
151
            $filename = 'file';
152
        }
153
154
        $mediaExtension = explode('/', mime_content_type($temporaryFile));
155
156
        if (! Str::contains($filename, '.')) {
157
            $filename = "{$filename}.{$mediaExtension[1]}";
158
        }
159
160
        return app(FileAdderFactory::class)
161
            ->create($this, $temporaryFile)
162
            ->usingName(pathinfo($filename, PATHINFO_FILENAME))
163
            ->usingFileName($filename);
164
    }
165
166
    /**
167
     * Add a base64 encoded file to the medialibrary.
168
     *
169
     * @param string $base64data
170
     * @param string|array ...$allowedMimeTypes
171
     *
172
     * @throws InvalidBase64Data
173
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
174
     *
175
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
176
     */
177
    public function addMediaFromBase64(string $base64data, ...$allowedMimeTypes): FileAdder
178
    {
179
        // strip out data uri scheme information (see RFC 2397)
180
        if (strpos($base64data, ';base64') !== false) {
181
            [$_, $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...
182
            [$_, $base64data] = explode(',', $base64data);
183
        }
184
185
        // strict mode filters for non-base64 alphabet characters
186
        if (base64_decode($base64data, true) === false) {
187
            throw InvalidBase64Data::create();
188
        }
189
190
        // decoding and then reencoding should not change the data
191
        if (base64_encode(base64_decode($base64data)) !== $base64data) {
192
            throw InvalidBase64Data::create();
193
        }
194
195
        $binaryData = base64_decode($base64data);
196
197
        // temporarily store the decoded data on the filesystem to be able to pass it to the fileAdder
198
        $tmpFile = tempnam(sys_get_temp_dir(), 'medialibrary');
199
        file_put_contents($tmpFile, $binaryData);
200
201
        $this->guardAgainstInvalidMimeType($tmpFile, $allowedMimeTypes);
202
203
        $file = app(FileAdderFactory::class)->create($this, $tmpFile);
204
205
        return $file;
206
    }
207
208
    /**
209
     * Copy a file to the medialibrary.
210
     *
211
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
212
     *
213
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
214
     */
215
    public function copyMedia($file)
216
    {
217
        return $this->addMedia($file)->preservingOriginal();
218
    }
219
220
    /*
221
     * Determine if there is media in the given collection.
222
     */
223
    public function hasMedia(string $collectionName = 'default'): bool
224
    {
225
        return count($this->getMedia($collectionName)) ? true : false;
226
    }
227
228
    /**
229
     * Get media collection by its collectionName.
230
     *
231
     * @param string $collectionName
232
     * @param array|callable $filters
233
     *
234
     * @return \Illuminate\Support\Collection
235
     */
236
    public function getMedia(string $collectionName = 'default', $filters = []): Collection
237
    {
238
        return app(MediaRepository::class)->getCollection($this, $collectionName, $filters);
239
    }
240
241
    public function getFirstMedia(string $collectionName = 'default', array $filters = []): ?Media
242
    {
243
        $media = $this->getMedia($collectionName, $filters);
244
245
        return $media->first();
246
    }
247
248
    /*
249
     * Get the url of the image for the given conversionName
250
     * for first media for the given collectionName.
251
     * If no profile is given, return the source's url.
252
     */
253
    public function getFirstMediaUrl(string $collectionName = 'default', string $conversionName = ''): string
254
    {
255
        $media = $this->getFirstMedia($collectionName);
256
257
        if (! $media) {
258
            return $this->getFallbackMediaUrl($collectionName) ?: '';
259
        }
260
261
        return $media->getUrl($conversionName);
262
    }
263
264
    /*
265
     * Get the url of the image for the given conversionName
266
     * for first media for the given collectionName.
267
     *
268
     * If no profile is given, return the source's url.
269
     */
270
    public function getFirstTemporaryUrl(DateTimeInterface $expiration, string $collectionName = 'default', string $conversionName = ''): string
271
    {
272
        $media = $this->getFirstMedia($collectionName);
273
274
        if (! $media) {
275
            return $this->getFallbackMediaUrl($collectionName) ?: '';
276
        }
277
278
        return $media->getTemporaryUrl($expiration, $conversionName);
279
    }
280
281
    public function getMediaCollection(string $collectionName = 'default'): ?MediaCollection
282
    {
283
        $this->registerMediaCollections();
284
285
        return collect($this->mediaCollections)
286
            ->first(function (MediaCollection $collection) use ($collectionName) {
287
                return $collection->name === $collectionName;
288
            });
289
    }
290
291
    public function getFallbackMediaUrl(string $collectionName = 'default'): string
292
    {
293
        return optional($this->getMediaCollection($collectionName))->fallbackUrl ?? '';
294
    }
295
296
    public function getFallbackMediaPath(string $collectionName = 'default'): string
297
    {
298
        return optional($this->getMediaCollection($collectionName))->fallbackPath ?? '';
299
    }
300
301
    /*
302
     * Get the url of the image for the given conversionName
303
     * for first media for the given collectionName.
304
     * If no profile is given, return the source's url.
305
     */
306
    public function getFirstMediaPath(string $collectionName = 'default', string $conversionName = ''): string
307
    {
308
        $media = $this->getFirstMedia($collectionName);
309
310
        if (! $media) {
311
            return $this->getFallbackMediaPath($collectionName) ?: '';
312
        }
313
314
        return $media->getPath($conversionName);
315
    }
316
317
    /**
318
     * Update a media collection by deleting and inserting again with new values.
319
     *
320
     * @param array $newMediaArray
321
     * @param string $collectionName
322
     *
323
     * @return \Illuminate\Support\Collection
324
     *
325
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated
326
     */
327
    public function updateMedia(array $newMediaArray, string $collectionName = 'default'): Collection
328
    {
329
        $this->removeMediaItemsNotPresentInArray($newMediaArray, $collectionName);
330
331
        $mediaClass = config('medialibrary.media_model');
332
        $mediaInstance = new $mediaClass();
333
        $keyName = $mediaInstance->getKeyName();
334
335
        return collect($newMediaArray)
336
            ->map(function (array $newMediaItem) use ($collectionName, $mediaClass, $keyName) {
337
                static $orderColumn = 1;
338
339
                $currentMedia = $mediaClass::findOrFail($newMediaItem[$keyName]);
340
341
                if ($currentMedia->collection_name !== $collectionName) {
342
                    throw MediaCannotBeUpdated::doesNotBelongToCollection($collectionName, $currentMedia);
343
                }
344
345
                if (array_key_exists('name', $newMediaItem)) {
346
                    $currentMedia->name = $newMediaItem['name'];
347
                }
348
349
                if (array_key_exists('custom_properties', $newMediaItem)) {
350
                    $currentMedia->custom_properties = $newMediaItem['custom_properties'];
351
                }
352
353
                $currentMedia->order_column = $orderColumn++;
354
355
                $currentMedia->save();
356
357
                return $currentMedia;
358
            });
359
    }
360
361
    protected function removeMediaItemsNotPresentInArray(array $newMediaArray, string $collectionName = 'default')
362
    {
363
        $this->getMedia($collectionName)
364
            ->reject(function (Media $currentMediaItem) use ($newMediaArray) {
365
                return in_array($currentMediaItem->getKey(), array_column($newMediaArray, $currentMediaItem->getKeyName()));
366
            })
367
            ->each->delete();
368
    }
369
370
    /**
371
     * Remove all media in the given collection.
372
     *
373
     * @param string $collectionName
374
     *
375
     * @return $this
376
     */
377
    public function clearMediaCollection(string $collectionName = 'default'): self
378
    {
379
        $this->getMedia($collectionName)
380
            ->each->delete();
381
382
        event(new CollectionHasBeenCleared($this, $collectionName));
383
384
        if ($this->mediaIsPreloaded()) {
385
            unset($this->media);
386
        }
387
388
        return $this;
389
    }
390
391
    /**
392
     * Remove all media in the given collection except some.
393
     *
394
     * @param string $collectionName
395
     * @param \Spatie\MediaLibrary\Models\Media[]|\Illuminate\Support\Collection $excludedMedia
396
     *
397
     * @return $this
398
     */
399
    public function clearMediaCollectionExcept(string $collectionName = 'default', $excludedMedia = [])
400
    {
401
        if ($excludedMedia instanceof Media) {
402
            $excludedMedia = collect()->push($excludedMedia);
403
        }
404
405
        $excludedMedia = collect($excludedMedia);
406
407
        if ($excludedMedia->isEmpty()) {
408
            return $this->clearMediaCollection($collectionName);
409
        }
410
411
        $this->getMedia($collectionName)
412
            ->reject(function (Media $media) use ($excludedMedia) {
413
                return $excludedMedia->where($media->getKeyName(), $media->getKey())->count();
414
            })
415
            ->each->delete();
416
417
        if ($this->mediaIsPreloaded()) {
418
            unset($this->media);
419
        }
420
421
        if ($this->getMedia($collectionName)->isEmpty()) {
422
            event(new CollectionHasBeenCleared($this, $collectionName));
423
        }
424
425
        return $this;
426
    }
427
428
    /**
429
     * Delete the associated media with the given id.
430
     * You may also pass a media object.
431
     *
432
     * @param int|\Spatie\MediaLibrary\Models\Media $mediaId
433
     *
434
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted
435
     */
436
    public function deleteMedia($mediaId)
437
    {
438
        if ($mediaId instanceof Media) {
439
            $mediaId = $mediaId->getKey();
440
        }
441
442
        $media = $this->media->find($mediaId);
443
444
        if (! $media) {
445
            throw MediaCannotBeDeleted::doesNotBelongToModel($mediaId, $this);
446
        }
447
448
        $media->delete();
449
    }
450
451
    /*
452
     * Add a conversion.
453
     */
454
    public function addMediaConversion(string $name): Conversion
455
    {
456
        $conversion = Conversion::create($name);
457
458
        $this->mediaConversions[] = $conversion;
459
460
        return $conversion;
461
    }
462
463
    public function addMediaCollection(string $name): MediaCollection
464
    {
465
        $mediaCollection = MediaCollection::create($name);
466
467
        $this->mediaCollections[] = $mediaCollection;
468
469
        return $mediaCollection;
470
    }
471
472
    /**
473
     * Delete the model, but preserve all the associated media.
474
     *
475
     * @return bool
476
     */
477
    public function deletePreservingMedia(): bool
478
    {
479
        $this->deletePreservingMedia = true;
480
481
        return $this->delete();
0 ignored issues
show
Bug introduced by
The method delete() does not exist on Spatie\MediaLibrary\HasMedia\HasMedia. 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...
482
    }
483
484
    /**
485
     * Determines if the media files should be preserved when the media object gets deleted.
486
     *
487
     * @return bool
488
     */
489
    public function shouldDeletePreservingMedia()
490
    {
491
        return $this->deletePreservingMedia ?? false;
492
    }
493
494
    protected function mediaIsPreloaded(): bool
495
    {
496
        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...
497
    }
498
499
    /**
500
     * Cache the media on the object.
501
     *
502
     * @param string $collectionName
503
     *
504
     * @return mixed
505
     */
506
    public function loadMedia(string $collectionName)
507
    {
508
        $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...
509
            ? $this->media
510
            : collect($this->unAttachedMediaLibraryItems)->pluck('media');
511
512
        return $collection
513
            ->filter(function (Media $mediaItem) use ($collectionName) {
514
                if ($collectionName == '') {
515
                    return true;
516
                }
517
518
                return $mediaItem->collection_name === $collectionName;
519
            })
520
            ->sortBy('order_column')
521
            ->values();
522
    }
523
524
    public function prepareToAttachMedia(Media $media, FileAdder $fileAdder)
525
    {
526
        $this->unAttachedMediaLibraryItems[] = compact('media', 'fileAdder');
527
    }
528
529
    public function processUnattachedMedia(callable $callable)
530
    {
531
        foreach ($this->unAttachedMediaLibraryItems as $item) {
532
            $callable($item['media'], $item['fileAdder']);
533
        }
534
535
        $this->unAttachedMediaLibraryItems = [];
536
    }
537
538
    protected function guardAgainstInvalidMimeType(string $file, ...$allowedMimeTypes)
539
    {
540
        $allowedMimeTypes = Arr::flatten($allowedMimeTypes);
541
542
        if (empty($allowedMimeTypes)) {
543
            return;
544
        }
545
546
        $validation = Validator::make(
547
            ['file' => new File($file)],
548
            ['file' => 'mimetypes:'.implode(',', $allowedMimeTypes)]
549
        );
550
551
        if ($validation->fails()) {
552
            throw MimeTypeNotAllowed::create($file, $allowedMimeTypes);
553
        }
554
    }
555
556
    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...
557
    {
558
    }
559
560
    public function registerMediaCollections()
561
    {
562
    }
563
564
    public function registerAllMediaConversions(Media $media = null)
565
    {
566
        $this->registerMediaCollections();
567
568
        collect($this->mediaCollections)->each(function (MediaCollection $mediaCollection) use ($media) {
569
            $actualMediaConversions = $this->mediaConversions;
570
571
            $this->mediaConversions = [];
572
573
            ($mediaCollection->mediaConversionRegistrations)($media);
574
575
            $preparedMediaConversions = collect($this->mediaConversions)
576
                ->each(function (Conversion $conversion) use ($mediaCollection) {
577
                    $conversion->performOnCollections($mediaCollection->name);
578
                })
579
                ->values()
580
                ->toArray();
581
582
            $this->mediaConversions = array_merge($actualMediaConversions, $preparedMediaConversions);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($actualMedia...eparedMediaConversions) of type array is incompatible with the declared type array<integer,object<Spa...Conversion\Conversion>> of property $mediaConversions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
583
        });
584
585
        $this->registerMediaConversions($media);
586
    }
587
}
588