Completed
Push — master ( 4b8b44...5633c4 )
by Freek
06:41
created

HasMediaTrait::addMediaFromUrl()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 16
nc 5
nop 2
1
<?php
2
3
namespace Spatie\MediaLibrary\HasMedia;
4
5
use DateTimeInterface;
6
use Illuminate\Http\File;
7
use Illuminate\Support\Collection;
8
use Spatie\MediaLibrary\Models\Media;
9
use Spatie\MediaLibrary\MediaRepository;
10
use Illuminate\Support\Facades\Validator;
11
use Spatie\MediaLibrary\FileAdder\FileAdder;
12
use Illuminate\Database\Eloquent\SoftDeletes;
13
use Spatie\MediaLibrary\Conversion\Conversion;
14
use Spatie\MediaLibrary\FileAdder\FileAdderFactory;
15
use Spatie\MediaLibrary\Events\CollectionHasBeenCleared;
16
use Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted;
17
use Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated;
18
use Spatie\MediaLibrary\MediaCollection\MediaCollection;
19
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\UnreachableUrl;
20
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\InvalidBase64Data;
21
use Spatie\MediaLibrary\Exceptions\FileCannotBeAdded\MimeTypeNotAllowed;
22
23
trait HasMediaTrait
24
{
25
    /** @var array */
26
    public $mediaConversions = [];
27
28
    /** @var array */
29
    public $mediaCollections = [];
30
31
    /** @var bool */
32
    protected $deletePreservingMedia = false;
33
34
    /** @var array */
35
    protected $unAttachedMediaLibraryItems = [];
36
37
    public static function bootHasMediaTrait()
38
    {
39
        static::deleting(function (HasMedia $entity) {
40
            if ($entity->shouldDeletePreservingMedia()) {
41
                return;
42
            }
43
44
            if (in_array(SoftDeletes::class, trait_uses_recursive($entity))) {
0 ignored issues
show
Documentation introduced by
$entity is of type object<Spatie\MediaLibrary\HasMedia\HasMedia>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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