Completed
Push — master ( c2f76d...03586b )
by Freek
11:35
created

HasMediaTrait::addMedia()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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