Completed
Push — master ( a50e59...22377b )
by Freek
10:42
created

src/HasMedia/HasMediaTrait.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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, class_uses_recursive($entity))) {
45
                if (! $entity->forceDeleting) {
46
                    return;
47
                }
48
            }
49
50
            $entity->media()->get()->each->delete();
51
        });
52
    }
53
54
    /**
55
     * Set the polymorphic relation.
56
     *
57
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
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
        $filename = str_replace('%20', ' ', $filename);
133
134
        if ($filename === '') {
135
            $filename = 'file';
136
        }
137
138
        $mediaExtension = explode('/', mime_content_type($temporaryFile));
139
140
        if (! str_contains($filename, '.')) {
0 ignored issues
show
Deprecated Code introduced by
The function str_contains() has been deprecated with message: Str::contains() should be used directly instead. Will be removed in Laravel 5.9.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

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

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
497
498
        if (empty($allowedMimeTypes)) {
499
            return;
500
        }
501
502
        $validation = Validator::make(
503
            ['file' => new File($file)],
504
            ['file' => 'mimetypes:'.implode(',', $allowedMimeTypes)]
505
        );
506
507
        if ($validation->fails()) {
508
            throw MimeTypeNotAllowed::create($file, $allowedMimeTypes);
509
        }
510
    }
511
512
    public function registerMediaConversions(Media $media = null)
513
    {
514
    }
515
516
    public function registerMediaCollections()
517
    {
518
    }
519
520
    public function registerAllMediaConversions(Media $media = null)
521
    {
522
        $this->registerMediaCollections();
523
524
        collect($this->mediaCollections)->each(function (MediaCollection $mediaCollection) use ($media) {
525
            $actualMediaConversions = $this->mediaConversions;
526
527
            $this->mediaConversions = [];
528
529
            ($mediaCollection->mediaConversionRegistrations)($media);
530
531
            $preparedMediaConversions = collect($this->mediaConversions)
532
                ->each(function (Conversion $conversion) use ($mediaCollection) {
533
                    $conversion->performOnCollections($mediaCollection->name);
534
                })
535
                ->values()
536
                ->toArray();
537
538
            $this->mediaConversions = array_merge($actualMediaConversions, $preparedMediaConversions);
539
        });
540
541
        $this->registerMediaConversions($media);
542
    }
543
}
544