Completed
Push — master ( e281b9...5e7f10 )
by Freek
01:38
created

HasMediaTrait::getFirstTemporyUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 3
1
<?php
2
3
namespace Spatie\MediaLibrary\HasMedia;
4
5
use DateTimeInterface;
6
use Illuminate\Http\File;
7
use Spatie\MediaLibrary\Media;
8
use Illuminate\Support\Collection;
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\HasMedia\Interfaces\HasMedia;
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 bool */
29
    protected $deletePreservingMedia = false;
30
31
    /** @var array */
32
    protected $unAttachedMediaLibraryItems = [];
33
34
    public static function bootHasMediaTrait()
35
    {
36
        static::deleting(function (HasMedia $entity) {
37
            if ($entity->shouldDeletePreservingMedia()) {
38
                return;
39
            }
40
41
            if (in_array(SoftDeletes::class, trait_uses_recursive($entity))) {
0 ignored issues
show
Documentation introduced by
$entity is of type object<Spatie\MediaLibra...ia\Interfaces\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...
42
                if (! $entity->forceDeleting) {
0 ignored issues
show
Bug introduced by
Accessing forceDeleting on the interface Spatie\MediaLibrary\HasMedia\Interfaces\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...
43
                    return;
44
                }
45
            }
46
47
            $entity->media()->get()->each->delete();
48
        });
49
    }
50
51
    /**
52
     * Set the polymorphic relation.
53
     *
54
     * @return mixed
55
     */
56
    public function media()
57
    {
58
        return $this->morphMany(config('medialibrary.media_model'), 'model');
59
    }
60
61
    /**
62
     * Add a file to the medialibrary.
63
     *
64
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
65
     *
66
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
67
     */
68
    public function addMedia($file)
69
    {
70
        return app(FileAdderFactory::class)->create($this, $file);
71
    }
72
73
    /**
74
     * Add a file from a request.
75
     *
76
     * @param string $key
77
     *
78
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
79
     */
80
    public function addMediaFromRequest(string $key)
81
    {
82
        return app(FileAdderFactory::class)->createFromRequest($this, $key);
83
    }
84
85
    /**
86
     * Add multiple files from a request by keys.
87
     *
88
     * @param string[] $keys
89
     *
90
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
91
     */
92
    public function addMultipleMediaFromRequest(array $keys)
93
    {
94
        return app(FileAdderFactory::class)->createMultipleFromRequest($this, $keys);
95
    }
96
97
    /**
98
     * Add all files from a request.
99
     *
100
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder[]
101
     */
102
    public function addAllMediaFromRequest()
103
    {
104
        return app(FileAdderFactory::class)->createAllFromRequest($this);
105
    }
106
107
    /**
108
     * Add a remote file to the medialibrary.
109
     *
110
     * @param string $url
111
     * @param string|array ...$allowedMimeTypes
112
     *
113
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
114
     *
115
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
116
     */
117
    public function addMediaFromUrl(string $url, ...$allowedMimeTypes)
118
    {
119
        if (! $stream = @fopen($url, 'r')) {
120
            throw UnreachableUrl::create($url);
121
        }
122
123
        $tmpFile = tempnam(sys_get_temp_dir(), 'media-library');
124
        file_put_contents($tmpFile, $stream);
125
126
        $this->guardAgainstInvalidMimeType($tmpFile, $allowedMimeTypes);
127
128
        $filename = basename(parse_url($url, PHP_URL_PATH));
129
130
        return app(FileAdderFactory::class)
131
            ->create($this, $tmpFile)
132
            ->usingName(pathinfo($filename, PATHINFO_FILENAME))
133
            ->usingFileName($filename);
134
    }
135
136
    /**
137
     * Add a base64 encoded file to the medialibrary.
138
     *
139
     * @param string $base64data
140
     * @param string|array ...$allowedMimeTypes
141
     *
142
     * @throws InvalidBase64Data
143
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
144
     *
145
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
146
     */
147
    public function addMediaFromBase64(string $base64data, ...$allowedMimeTypes)
148
    {
149
        // strip out data uri scheme information (see RFC 2397)
150
        if (strpos($base64data, ';base64') !== false) {
151
            list(, $base64data) = explode(';', $base64data);
152
            list(, $base64data) = explode(',', $base64data);
153
        }
154
155
        // strict mode filters for non-base64 alphabet characters
156
        if (base64_decode($base64data, true) === false) {
157
            throw InvalidBase64Data::create();
158
        }
159
160
        // decoding and then reeconding should not change the data
161
        if (base64_encode(base64_decode($base64data)) !== $base64data) {
162
            throw InvalidBase64Data::create();
163
        }
164
165
        $binaryData = base64_decode($base64data);
166
167
        // temporarily store the decoded data on the filesystem to be able to pass it to the fileAdder
168
        $tmpFile = tempnam(sys_get_temp_dir(), 'medialibrary');
169
        file_put_contents($tmpFile, $binaryData);
170
171
        $this->guardAgainstInvalidMimeType($tmpFile, $allowedMimeTypes);
172
173
        $file = app(FileAdderFactory::class)
174
            ->create($this, $tmpFile);
175
176
        return $file;
177
    }
178
179
    /**
180
     * Copy a file to the medialibrary.
181
     *
182
     * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file
183
     *
184
     * @return \Spatie\MediaLibrary\FileAdder\FileAdder
185
     */
186
    public function copyMedia($file)
187
    {
188
        return $this->addMedia($file)->preservingOriginal();
189
    }
190
191
    /*
192
     * Determine if there is media in the given collection.
193
     */
194
    public function hasMedia(string $collectionName = 'default'): bool
195
    {
196
        return count($this->getMedia($collectionName)) ? true : false;
197
    }
198
199
    /**
200
     * Get media collection by its collectionName.
201
     *
202
     * @param string $collectionName
203
     * @param array|callable $filters
204
     *
205
     * @return \Illuminate\Support\Collection
206
     */
207
    public function getMedia(string $collectionName = 'default', $filters = []): Collection
208
    {
209
        return app(MediaRepository::class)->getCollection($this, $collectionName, $filters);
210
    }
211
212
    /**
213
     * Get the first media item of a media collection.
214
     *
215
     * @param string $collectionName
216
     * @param array $filters
217
     *
218
     * @return Media|null
219
     */
220
    public function getFirstMedia(string $collectionName = 'default', array $filters = [])
221
    {
222
        $media = $this->getMedia($collectionName, $filters);
223
224
        return $media->first();
225
    }
226
227
    /*
228
     * Get the url of the image for the given conversionName
229
     * for first media for the given collectionName.
230
     * If no profile is given, return the source's url.
231
     */
232
    public function getFirstMediaUrl(string $collectionName = 'default', string $conversionName = ''): string
233
    {
234
        $media = $this->getFirstMedia($collectionName);
235
236
        if (! $media) {
237
            return '';
238
        }
239
240
        return $media->getUrl($conversionName);
241
    }
242
243
    /*
244
     * Get the url of the image for the given conversionName
245
     * for first media for the given collectionName.
246
     * If no profile is given, return the source's url.
247
     */
248
    public function getFirstTemporyUrl(DateTimeInterface $expiration, string $collectionName = 'default', string $conversionName = ''): string
249
    {
250
        $media = $this->getFirstMedia($collectionName);
251
252
        if (! $media) {
253
            return '';
254
        }
255
256
        return $media->getTemporaryUrl($expiration, $conversionName);
257
    }
258
259
    /*
260
     * Get the url of the image for the given conversionName
261
     * for first media for the given collectionName.
262
     * If no profile is given, return the source's url.
263
     */
264
    public function getFirstMediaPath(string $collectionName = 'default', string $conversionName = ''): string
265
    {
266
        $media = $this->getFirstMedia($collectionName);
267
268
        if (! $media) {
269
            return '';
270
        }
271
272
        return $media->getPath($conversionName);
273
    }
274
275
    /**
276
     * Update a media collection by deleting and inserting again with new values.
277
     *
278
     * @param array $newMediaArray
279
     * @param string $collectionName
280
     *
281
     * @return \Illuminate\Support\Collection
282
     *
283
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeUpdated
284
     */
285
    public function updateMedia(array $newMediaArray, string $collectionName = 'default'): Collection
286
    {
287
        $this->removeMediaItemsNotPresentInArray($newMediaArray, $collectionName);
288
289
        return collect($newMediaArray)
290
            ->map(function (array $newMediaItem) use ($collectionName) {
291
                static $orderColumn = 1;
292
293
                $mediaClass = config('medialibrary.media_model');
294
                $currentMedia = $mediaClass::findOrFail($newMediaItem['id']);
295
296
                if ($currentMedia->collection_name != $collectionName) {
297
                    throw MediaCannotBeUpdated::doesNotBelongToCollection($collectionName, $currentMedia);
298
                }
299
300
                if (array_key_exists('name', $newMediaItem)) {
301
                    $currentMedia->name = $newMediaItem['name'];
302
                }
303
304
                if (array_key_exists('custom_properties', $newMediaItem)) {
305
                    $currentMedia->custom_properties = $newMediaItem['custom_properties'];
306
                }
307
308
                $currentMedia->order_column = $orderColumn++;
309
310
                $currentMedia->save();
311
312
                return $currentMedia;
313
            });
314
    }
315
316
    /**
317
     * @param array $newMediaArray
318
     * @param string $collectionName
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')
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\Media[]|\Illuminate\Support\Collection $excludedMedia
355
     *
356
     * @return $this
357
     */
358
    public function clearMediaCollectionExcept(string $collectionName = 'default', $excludedMedia = [])
359
    {
360
        $excludedMedia = collect($excludedMedia);
361
362
        if ($excludedMedia->isEmpty()) {
363
            return $this->clearMediaCollection($collectionName);
364
        }
365
366
        $this->getMedia($collectionName)
367
            ->reject(function (Media $media) use ($excludedMedia) {
368
                return $excludedMedia->where('id', $media->id)->count();
369
            })
370
            ->each->delete();
371
372
        if ($this->mediaIsPreloaded()) {
373
            unset($this->media);
374
        }
375
376
        return $this;
377
    }
378
379
    /**
380
     * Delete the associated media with the given id.
381
     * You may also pass a media object.
382
     *
383
     * @param int|\Spatie\MediaLibrary\Media $mediaId
384
     *
385
     * @throws \Spatie\MediaLibrary\Exceptions\MediaCannotBeDeleted
386
     */
387
    public function deleteMedia($mediaId)
388
    {
389
        if ($mediaId instanceof Media) {
390
            $mediaId = $mediaId->id;
391
        }
392
393
        $media = $this->media->find($mediaId);
394
395
        if (! $media) {
396
            throw MediaCannotBeDeleted::doesNotBelongToModel($mediaId, $this);
397
        }
398
399
        $media->delete();
400
    }
401
402
    /*
403
     * Add a conversion.
404
     */
405
    public function addMediaConversion(string $name): Conversion
406
    {
407
        $conversion = Conversion::create($name);
408
409
        $this->mediaConversions[] = $conversion;
410
411
        return $conversion;
412
    }
413
414
    /**
415
     * Delete the model, but preserve all the associated media.
416
     *
417
     * @return bool
418
     */
419
    public function deletePreservingMedia(): bool
420
    {
421
        $this->deletePreservingMedia = true;
422
423
        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...
424
    }
425
426
    /**
427
     * Determines if the media files should be preserved when the media object gets deleted.
428
     *
429
     * @return \Spatie\MediaLibrary\Media
430
     */
431
    public function shouldDeletePreservingMedia()
432
    {
433
        return $this->deletePreservingMedia ?? false;
434
    }
435
436
    protected function mediaIsPreloaded(): bool
437
    {
438
        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...
439
    }
440
441
    /**
442
     * Cache the media on the object.
443
     *
444
     * @param string $collectionName
445
     *
446
     * @return mixed
447
     */
448
    public function loadMedia(string $collectionName)
449
    {
450
        $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...
451
            ? $this->media
452
            : collect($this->unAttachedMediaLibraryItems)->pluck('media');
453
454
        return $collection
455
            ->filter(function (Media $mediaItem) use ($collectionName) {
456
                if ($collectionName == '') {
457
                    return true;
458
                }
459
460
                return $mediaItem->collection_name === $collectionName;
461
            })
462
            ->sortBy('order_column')
463
            ->values();
464
    }
465
466
    public function prepareToAttachMedia(Media $media, FileAdder $fileAdder)
467
    {
468
        $this->unAttachedMediaLibraryItems[] = compact('media', 'fileAdder');
469
    }
470
471
    public function processUnattachedMedia(callable $callable)
472
    {
473
        foreach ($this->unAttachedMediaLibraryItems as $item) {
474
            $callable($item['media'], $item['fileAdder']);
475
        }
476
477
        $this->unAttachedMediaLibraryItems = [];
478
    }
479
480
    protected function guardAgainstInvalidMimeType(string $file, ...$allowedMimeTypes)
481
    {
482
        $allowedMimeTypes = array_flatten($allowedMimeTypes);
483
484
        if (empty($allowedMimeTypes)) {
485
            return;
486
        }
487
488
        $validation = Validator::make(
489
            ['file' => new File($file)],
490
            ['file' => 'mimetypes:'.implode(',', $allowedMimeTypes)]
491
        );
492
493
        if ($validation->fails()) {
494
            throw MimeTypeNotAllowed::create($file, $allowedMimeTypes);
495
        }
496
    }
497
}
498