Compare commits

...

31 Commits

Author SHA1 Message Date
philipp lang 4a87b83b5a Add pathinfo information to default Properties 2024-10-30 15:25:43 +01:00
philipp lang 09919a1c29 Add heic support 2024-04-30 14:13:21 +02:00
philipp lang 820a725517 Lint 2024-01-02 01:28:49 +01:00
philipp lang 2d39816954 Lint 2024-01-02 01:28:16 +01:00
philipp lang 7e8f762885 Lint 2024-01-02 01:27:56 +01:00
philipp lang 9c33c8f128 Update tests 2024-01-02 00:41:22 +01:00
philipp lang cfb38ed792 Update README 2024-01-02 00:22:42 +01:00
Philipp Lang 2473c7c55b Add max width to upload 2023-05-02 14:23:25 +02:00
Philipp Lang fb7f70fb12 add fallback for nbo conversion 2023-03-14 11:35:24 +01:00
Philipp Lang 8a4dc77cfa Fixed order event 2023-03-13 11:05:05 +01:00
Philipp Lang fb15678d4e add after event 2023-03-13 10:34:52 +01:00
Philipp Lang f2218d83ad Add destroyed event 2023-03-13 10:17:14 +01:00
Philipp Lang ff3516ca4f add auth for reorder 2023-03-13 09:32:09 +01:00
philipp lang c24c32afd3 Add media ordering 2023-03-12 21:43:57 +01:00
Philipp Lang f269864194 ensure media is ordered 2023-03-10 14:52:17 +01:00
Philipp Lang 61ed6e0a3d Add icon to file 2023-03-10 12:47:14 +01:00
Philipp Lang 0c10472e2e Add mime types 2023-03-08 15:50:40 +01:00
Philipp Lang d429b8959b mod mediaController 2023-03-08 15:42:34 +01:00
Philipp Lang 00592aca0e mod middleware 2023-03-08 14:53:00 +01:00
Philipp Lang c385212f75 Add collection name to auth 2023-03-08 10:47:17 +01:00
philipp lang bcf708ecc3 Add fallback notice 2023-03-08 02:04:13 +01:00
philipp lang 63fb409951 update filename hook 2023-03-08 01:38:57 +01:00
philipp lang 47c734dfd4 Add default file 2023-03-08 00:58:15 +01:00
philipp lang 9d8ae685f6 Add destroy test 2023-03-08 00:04:00 +01:00
philipp lang f5e9aff0be abort if single media not found 2023-03-07 23:30:05 +01:00
philipp lang 278bf58551 Add index test 2023-03-07 23:28:11 +01:00
philipp lang 41dce6c2a7 lint 2023-03-07 23:14:27 +01:00
philipp lang 0f58e0f1eb fixed update missing values 2023-03-07 23:14:11 +01:00
philipp lang 87265191f2 add updater 2023-03-07 23:11:35 +01:00
Philipp Lang 58867e87e4 Add storing events 2023-03-07 21:53:36 +01:00
Philipp Lang 502bb41e97 Add tests 2023-03-07 15:31:24 +01:00
26 changed files with 10092 additions and 1048 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/vendor/
/.php-cs-fixer.cache
/.phpunit.result.cache

28
README.md Normal file
View File

@ -0,0 +1,28 @@
# Laravel Medialibrary Helper
This package creates routes for the popular Medialibrary Package from Spatie ().
## Available methods
In RegisterMediaCollections, you have the following methods available:
You can set a filename by default for the file. This accepts the associated Model, as well as the original filename. You should return the new name of the file with the extension (e.g. disc.jpg).
```
forceFileName(fn ($model, $path) => Str::slug($path))
```
You can set a max width (in Pixels) for images. This will resize the image BEFORE any normal Medialibrary conversions take place.
```
maxWidth(fn () => 2500)
```
You can call whatever you want after an image has been added, modified or deleted.
```
->after(function ($model) {
....
})
```

View File

@ -5,6 +5,7 @@
"license": "MIT",
"autoload": {
"psr-4": {
"Zoomyboy\\MedialibraryHelper\\Tests\\": "tests/",
"Zoomyboy\\MedialibraryHelper\\": "src/"
}
},
@ -15,6 +16,32 @@
}
],
"require": {
"spatie/laravel-medialibrary": "^10.7"
"ext-imagick": ">=3.6.0",
"spatie/laravel-medialibrary": "^10.7",
"laravel/framework": "^9.50",
"spatie/laravel-data": "^3.1",
"pestphp/pest": "^1.22"
},
"require-dev": {
"phpunit/phpunit": "^9.6",
"orchestra/testbench": "^7.0",
"illuminate/console": "^9.2"
},
"scripts": {
"post-autoload-dump": [
"@php vendor/bin/testbench package:discover --ansi"
]
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"extra": {
"laravel": {
"providers": [
"Zoomyboy\\MedialibraryHelper\\ServiceProvider"
]
}
}
}

9578
composer.lock generated

File diff suppressed because it is too large Load Diff

30
phpunit.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<env name="APP_ENV" value="testing"/>
<env name="APP_URL" value="http://localhost:8000"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>

View File

@ -0,0 +1,98 @@
<?php
namespace Zoomyboy\MedialibraryHelper;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
class CollectionExtension
{
public function boot(): void
{
MediaCollection::mixin(new class()
{
public function forceFileName()
{
return fn ($callback) => $this->registerCustomCallback('forceFileName', $callback);
}
public function storing()
{
return fn ($callback) => $this->registerCustomCallback('storing', $callback);
}
public function destroyed()
{
return fn ($callback) => $this->registerCustomCallback('destroyed', $callback);
}
public function after()
{
return fn ($callback) => $this->registerCustomCallback('after', $callback);
}
public function withDefaultProperties()
{
return fn ($callback) => $this->registerCustomCallback('withDefaultProperties', $callback);
}
public function stored()
{
return fn ($callback) => $this->registerCustomCallback('stored', $callback);
}
public function withPropertyValidation()
{
return fn ($callback) => $this->registerCustomCallback('withPropertyValidation', $callback);
}
public function withFallback()
{
return fn ($callback) => $this->registerCustomCallback('withFallback', $callback);
}
public function maxWidth()
{
return fn ($callback) => $this->registerCustomCallback('maxWidth', $callback);
}
public function runCallback()
{
return function (string $callback, ...$parameters) {
$this->setDefaultCustomCallbacks();
return call_user_func($this->customCallbacks->get($callback), ...$parameters);
};
}
public function registerCustomCallback()
{
return function (string $name, callable $callback) {
$this->setDefaultCustomCallbacks();
$this->customCallbacks->put($name, $callback);
return $this;
};
}
public function setDefaultCustomCallbacks()
{
return function () {
if (property_exists($this, 'customCallbacks')) {
return;
}
$this->customCallbacks = collect([
'forceFileName' => fn ($model, $name) => $name,
'maxWidth' => fn ($size) => null,
'stored' => fn ($event) => true,
'after' => fn ($event) => true,
'destroyed' => fn ($event) => true,
'storing' => fn ($adder, $name) => $adder,
'withDefaultProperties' => fn ($path, $pathinfo) => [],
'withPropertyValidation' => fn ($path) => [],
'withFallback' => fn ($parent) => null,
]);
};
}
});
}
}

View File

@ -2,79 +2,177 @@
namespace Zoomyboy\MedialibraryHelper;
use Imagick;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Spatie\Image\Image;
use Spatie\LaravelData\DataCollection;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidBase64Data;
use Spatie\MediaLibrary\MediaCollections\FileAdder;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class MediaController
{
use AuthorizesRequests;
public function store(Request $request): JsonResponse
public function store(Request $request)
{
$request->validate([
'name' => 'nullable|string',
'content' => 'required|string',
'name' => 'string',
'model' => ['required', 'string', Rule::in(app('media-library-helpers')->keys())],
]);
$model = $this->validateModel($request);
$this->authorize('storeMedia', $model);
$collection = $model->getMediaCollection($request->input('collection'));
$isSingle = 1 === $collection->collectionSizeLimit;
$this->authorize('storeMedia', [$model, $collection->name]);
if ($collection->forceFileRenamer) {
$fileRenamer = $collection->forceFileRenamer;
$path = $fileRenamer($model);
} else {
$path = $request->input('name', Str::random(32));
}
$request->validate($isSingle ? [
'payload' => 'array',
'payload.*' => '',
'payload.name' => 'required|string|max:255',
'payload.content' => 'required|string',
] : [
'payload' => 'required|array|min:1',
'payload.*' => 'array',
'payload.*.name' => 'string',
'payload.*.content' => 'string',
]);
$content = $isSingle ? [$request->input('content')] : $request->input('content');
$content = $isSingle ? [$request->input('payload')] : $request->input('payload');
$medias = collect([]);
foreach ($content as $c) {
Storage::disk('public')->put($path, base64_decode($c));
$medias = collect($content)->map(function ($c) use ($collection, $model) {
$pathinfo = pathinfo($c['name']);
$basename = $collection->runCallback('forceFileName', $model, $pathinfo['filename']);
$path = $basename . '.' . $pathinfo['extension'];
$medias->push($model->addMedia(Storage::disk('public')->path($path))->toMediaCollection($collection->name));
}
$adder = $this->fileAdderFromData($model, $c['content'], $collection)
->usingName($basename)
->usingFileName($path)
->withCustomProperties($collection->runCallback('withDefaultProperties', $path, $pathinfo));
return response()->json($isSingle ? $medias->first()->toArray() : $medias->map(fn ($media) => $media->toArray()));
return tap(
$collection->runCallback('storing', $adder, $path)->toMediaCollection($collection->name),
fn ($media) => $collection->runCallback('stored', $media)
);
});
$collection->runCallback('after', $model->fresh());
return $isSingle ? MediaData::from($medias->first()) : MediaData::collection($medias);
}
public function index(Request $request, $parentModel, int $parentId, string $collection): JsonResponse
public function update(Request $request, Media $media): MediaData
{
$this->authorize('updateMedia', [$media->model, $media->collection_name]);
$rules = collect($media->model->getMediaCollection($media->collection_name)->runCallback('withPropertyValidation', $media->file_name))
->mapWithKeys(fn ($rule, $key) => ["properties.{$key}" => $rule])->toArray();
$validated = $request->validate($rules);
$media->update(['custom_properties' => data_get($validated, 'properties', [])]);
$media->model->getMediaCollection($media->collection_name)->runCallback('after', $media->model->fresh());
return MediaData::from($media);
}
public function index(Request $request, $parentModel, int $parentId, string $collectionName): MediaData|DataCollection
{
$model = app('media-library-helpers')->get($parentModel);
$model = $model::findOrFail($parentId);
$isSingle = $model->getMediaCollection($collection)->collectionSizeLimit;
$model = $model::find($parentId);
$this->authorize('listMedia', [$model, $collectionName]);
$collection = $model->getMediaCollection($collectionName);
$isSingle = 1 === $collection->collectionSizeLimit;
return response()->json([
'data' => $isSingle ? $model->getFirstMedia($collection) : $model->getMedia($collection)->map(fn ($c) => $c->toArray()),
]);
abort_if($isSingle && !$model->getFirstMedia($collectionName) && !MediaData::defaultFromCollection($model, $collection), 404);
if ($isSingle && !$model->getFirstMedia($collectionName)) {
return MediaData::defaultFromCollection($model, $collection);
}
return $isSingle
? MediaData::from($model->getFirstMedia($collectionName))
: MediaData::collection($model->getMedia($collectionName));
}
public function destroy(Media $media, Request $request): JsonResponse
{
$this->authorize('destroyMedia', $media->model);
$this->authorize('destroyMedia', [$media->model, $media->collection_name]);
$model = $media->model->fresh();
$collection = $model->getMediaCollection($media->collection_name);
$media->delete();
$collection->runCallback('destroyed', $media->model->fresh());
$collection->runCallback('after', $media->model->fresh());
return response()->json([]);
}
private function validateModel(Request $request): HasMedia
protected function hasCallback(MediaCollection $collection, string $callback): bool
{
return property_exists($collection, $callback);
}
protected function validateModel(Request $request): HasMedia
{
$model = app('media-library-helpers')->get($request->input('model'));
$request->validate([
'collection' => ['required', 'string', Rule::in((new $model())->getRegisteredMediaCollections()->map(fn ($collection) => $collection->name)->toArray())],
'collection' => [
'required',
'string',
Rule::in((new $model())->getRegisteredMediaCollections()->pluck('name')),
],
]);
return $model::findOrFail($request->input('id'));
$model = $model::find($request->input('id'));
if (!$model) {
throw ValidationException::withMessages(['model' => 'nicht gefunden']);
}
return $model;
}
protected function fileAdderFromData($model, $data, $collection): FileAdder
{
$maxWidth = $collection->runCallback('maxWidth', 9);
if (str_contains($data, ';base64')) {
[$_, $data] = explode(';', $data);
[$_, $data] = explode(',', $data);
}
// strict mode filters for non-base64 alphabet characters
$binaryData = base64_decode($data, true);
if (false === $binaryData) {
throw InvalidBase64Data::create();
}
// decoding and then reencoding should not change the data
if (base64_encode($binaryData) !== $data) {
throw InvalidBase64Data::create();
}
$tmpFile = tempnam(sys_get_temp_dir(), 'media-library');
file_put_contents($tmpFile, $binaryData);
$i = (new Imagick());
$i->readImage($tmpFile);
if ($i->getImageFormat() === 'HEIC') {
$i->setFormat('jpg');
$i->writeImage($tmpFile);
}
if (null !== $maxWidth && 'image/jpeg' === mime_content_type($tmpFile)) {
Image::load($tmpFile)->width($maxWidth)->save();
}
return $model->addMedia($tmpFile);
}
}

82
src/MediaData.php Normal file
View File

@ -0,0 +1,82 @@
<?php
namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
#[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)]
class MediaData extends Data
{
public ?int $id;
public string $originalUrl;
public int $size;
public string $name;
public string $collectionName;
public string $fileName;
public string $mimeType;
#[MapInputName('custom_properties')]
public array $properties;
public array $conversions = [];
public bool $fallback = false;
public static function fromMedia(Media $media): self
{
$conversions = collect($media->getMediaConversionNames())->flip()->map(fn ($integer, $conversion) => $media->hasGeneratedConversion($conversion)
? ['original_url' => $media->getFullUrl($conversion)]
: null,
);
return self::withoutMagicalCreationFrom([
...$media->toArray(),
'conversions' => $conversions->toArray(),
]);
}
public function with(): array
{
$mime = Str::slug($this->mimeType);
return [
'icon' => Storage::disk('public')->url("filetypes/{$mime}.svg"),
];
}
public static function defaultFromCollection(HasMedia $parent, MediaCollection $collection): ?self
{
$default = $collection->runCallback('withFallback', $parent);
if (is_null($default)) {
return null;
}
return static::from([
'id' => null,
'originalUrl' => Storage::disk($default[1])->url($default[0]),
'size' => -1,
'collection_name' => $collection->name,
'name' => pathinfo($default[0], PATHINFO_FILENAME),
'file_name' => pathinfo($default[0], PATHINFO_BASENAME),
'properties' => [],
'fallback' => true,
'mime_type' => Storage::disk($default[1])->mimeType($default[0]),
]);
}
}

36
src/OrderController.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class OrderController
{
use AuthorizesRequests;
public function __invoke(Request $request, $parentModel, int $parentId, string $collectionName)
{
$mediaCount = collect($request->order)->map(function ($media) {
$media = Media::findOrFail($media);
return $media->model_id.'_'.$media->model_type;
})->unique()->count();
if (1 !== $mediaCount) {
throw ValidationException::withMessages(['order' => 'Sortierung von verschiedenen Medien nicht möglich.']);
}
$model = app('media-library-helpers')->get($parentModel);
$model = $model::find($parentId);
$this->authorize('listMedia', [$model, $collectionName]);
Media::setNewOrder($request->order);
$model->getMediaCollection($collectionName)->runCallback('after', $model->fresh());
return MediaData::collection($model->getMedia($collectionName));
}
}

View File

@ -4,27 +4,35 @@ namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
class ServiceProvider extends BaseServiceProvider
{
public function register(): void
{
app()->bind('media-library-helpers', fn () => collect([]));
app()->singleton(CollectionExtension::class, fn () => new CollectionExtension());
}
public function boot(): void
{
app(Router::class)->group(['middleware' => ['web', 'auth:web']], function ($router) {
app(Router::class)->group($this->routeGroup(), function ($router) {
$router->post('mediaupload', [MediaController::class, 'store'])->name('media.store');
$router->delete('mediaupload/{media}', [MediaController::class, 'destroy'])->name('media.destroy');
$router->get('mediaupload/{parent_model}/{parent_id}/{collection}', [MediaController::class, 'index'])->name('media.index');
$router->get('mediaupload/{parent_model}/{parent_id}/{collection_name}', [MediaController::class, 'index'])->name('media.index');
$router->patch('mediaupload/{parent_model}/{parent_id}/{collection_name}', OrderController::class)->name('media.order');
$router->patch('mediaupload/{media}', [MediaController::class, 'update'])->name('media.update');
});
MediaCollection::macro('forceFileName', function ($callback) {
$this->forceFileRenamer = $callback;
app(CollectionExtension::class)->boot();
}
return $this;
});
/**
* @return array{middleware: array<int, string>}
*/
protected function routeGroup(): array
{
return [
'middleware' => config('media-library.middleware'),
];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Spatie\MediaLibrary\HasMedia;
class MediaChange
{
use Dispatchable;
use SerializesModels;
public function __construct(public HasMedia $model)
{
}
public function broadcastOn()
{
return [];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Spatie\MediaLibrary\HasMedia;
class MediaDestroyed
{
use Dispatchable;
use SerializesModels;
public function __construct(public HasMedia $model)
{
}
public function broadcastOn()
{
return [];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class MediaStored
{
use Dispatchable;
use SerializesModels;
public function __construct(public Media $media)
{
}
public function broadcastOn()
{
return [];
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed;
test('it deletes multiple media', function () {
$this->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleForced');
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleForced');
$media = $post->getFirstMedia('multipleForced');
$this->deleteJson("/mediaupload/{$media->id}")->assertStatus(200);
$this->assertCount(1, $post->fresh()->getMedia('multipleForced'));
});
test('it deletes single media', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('defaultSingleFile');
$media = $post->getFirstMedia('defaultSingleFile');
$this->deleteJson("/mediaupload/{$media->id}")->assertStatus(200);
$this->assertCount(0, $post->fresh()->getMedia('defaultSingleFile'));
});
test('it needs authorization', function () {
$this->auth(['destroyMedia' => false])->registerModel();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('defaultSingleFile');
$media = $post->getFirstMedia('defaultSingleFile');
$this->deleteJson("/mediaupload/{$media->id}")->assertStatus(403);
});
test('it fires event', function () {
Event::fake();
$this->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('singleWithEvent');
$media = $post->getFirstMedia('singleWithEvent');
$this->deleteJson("/mediaupload/{$media->id}")->assertStatus(200);
Event::assertDispatched(MediaDestroyed::class, fn ($event) => $event->model->is($post));
Event::assertDispatched(MediaChange::class, fn ($event) => $event->model->is($post));
});

View File

@ -0,0 +1,99 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Storage;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
test('it gets all medias', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$firstMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');
$secondMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');
$response = $this->getJson("/mediaupload/post/{$post->id}/images");
$response->assertStatus(200);
$response->assertJsonPath('0.id', $firstMedia->id);
$response->assertJsonPath('1.id', $secondMedia->id);
$response->assertJsonPath('1.properties.test', 'old');
$response->assertJsonPath('1.icon', url('storage/filetypes/applicationpdf.svg'));
});
test('it gets media in order', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$firstMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');
$secondMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');
$thirdMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');
$order = $post->getMedia('images')->pluck('id');
$order->prepend($order->pop());
Media::setNewOrder($order->toArray());
$response = $this->getJson("/mediaupload/post/{$post->id}/images");
$response->assertStatus(200);
$response->assertJsonPath('0.id', $thirdMedia->id);
$response->assertJsonPath('1.id', $firstMedia->id);
$response->assertJsonPath('2.id', $secondMedia->id);
});
test('it gets media for single', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$media = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('defaultSingleFile');
$response = $this->getJson("/mediaupload/post/{$post->id}/defaultSingleFile");
$response->assertStatus(200);
$response->assertJsonPath('id', $media->id);
$response->assertJsonPath('properties.test', 'old');
$response->assertJsonPath('fallback', false);
$response->assertJsonPath('mime_type', 'application/pdf');
});
test('it checks for authorization', function () {
$this->auth(['listMedia' => false])->registerModel();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');
$response = $this->getJson("/mediaupload/post/{$post->id}/images");
$response->assertStatus(403);
});
test('it returns 404 when media not found', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$response = $this->getJson("/mediaupload/post/{$post->id}/defaultSingleFile");
$response->assertStatus(404);
});
test('test it gets conversions for single media', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$media = $post->addMedia($this->jpgFile()->getPathname())->preservingOriginal()->toMediaCollection('conversionsWithDefault');
$response = $this->getJson("/mediaupload/post/{$post->id}/conversionsWithDefault");
$response->assertStatus(200);
$response->assertJsonPath('conversions.tiny.original_url', $media->getFullUrl('tiny'));
});
test('test it gets default single media', function () {
$this->auth()->registerModel();
$post = $this->newPost();
Storage::disk('public')->put('default.jpg', $this->jpgFile()->getContent());
$response = $this->getJson("/mediaupload/post/{$post->id}/conversionsWithDefault");
$response->assertStatus(200);
$response->assertJsonPath('id', null);
$response->assertJsonPath('original_url', Storage::disk('public')->url('default.jpg'));
$response->assertJsonPath('name', 'default');
$response->assertJsonPath('file_name', 'default.jpg');
$response->assertJsonPath('fallback', true);
$response->assertJsonPath('mime_type', 'image/jpeg');
});

View File

@ -0,0 +1,58 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
test('it can reorder media', function () {
Event::fake();
$this->auth()->registerModel();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('images');
$post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('images');
$post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('images');
$order = $post->getMedia('images')->pluck('id');
$order->prepend($order->pop());
$response = $this->patchJson("/mediaupload/post/{$post->id}/images", [
'order' => $order,
]);
$response->assertStatus(200);
$response->assertJsonPath('0.id', $order->get(0));
$response->assertJsonPath('1.id', $order->get(1));
$response->assertJsonPath('2.id', $order->get(2));
$this->assertEquals($order, $post->fresh()->getMedia('images')->pluck('id'));
Event::assertDispatched(MediaChange::class, fn ($event) => $event->model->is($post));
});
test('images should belong to same model', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$firstMedia = $post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('images');
$post = $this->newPost();
$secondMedia = $post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('images');
$thirdMedia = $post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('images');
$response = $this->patchJson("/mediaupload/post/{$post->id}/images", [
'order' => [$firstMedia->id, $secondMedia->id, $thirdMedia->id],
]);
$response->assertJsonValidationErrors('order');
});
test('it should authorize', function () {
$this->auth(['listMedia' => false])->registerModel();
$post = $this->newPost();
$media = $post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('images');
$response = $this->patchJson("/mediaupload/post/{$post->id}/images", [
'order' => [$media->id],
]);
$response->assertStatus(403);
});

View File

@ -0,0 +1,60 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
test('it updates a single files properties', function () {
Event::fake();
$this->auth()->registerModel();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleProperties');
$media = $post->getFirstMedia('multipleProperties');
$response = $this->patchJson("/mediaupload/{$media->id}", [
'properties' => [
'test' => 'new',
'missing' => 'value',
],
]);
$response->assertStatus(200);
$this->assertEquals('new', $media->fresh()->getCustomProperty('test'));
$this->assertEquals(null, $media->fresh()->getCustomProperty('missing'));
$response->assertJsonPath('properties.test', 'new');
$response->assertJsonMissingPath('properties.missing');
Event::assertDispatched(MediaChange::class, fn ($event) => $event->model->is($post));
});
test('it validates a single files properties', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleProperties');
$media = $post->getFirstMedia('multipleProperties');
$response = $this->patchJson("/mediaupload/{$media->id}", [
'properties' => [
'test' => 'new feswfewfwewefew wewe ew ewewf wefwfwefwefwefwewefewwedw sad fd',
],
]);
$response->assertStatus(422);
$response->assertJsonValidationErrors('properties.test');
});
test('it checks for authorization', function () {
$this->auth(['updateMedia' => false])->registerModel();
$post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->preservingOriginal()->toMediaCollection('multipleProperties');
$media = $post->getFirstMedia('multipleProperties');
$response = $this->patchJson("/mediaupload/{$media->id}", [
'properties' => [
'test' => 'new',
'missing' => 'value',
],
]);
$response->assertStatus(403);
});

View File

@ -0,0 +1,335 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Carbon\Carbon;
use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored;
test('it uploads a single file to a single file collection', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$content = base64_encode($this->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
]);
$response->assertStatus(201);
$this->assertCount(1, $post->getMedia('defaultSingleFile'));
$media = $post->getFirstMedia('defaultSingleFile');
$response->assertJsonPath('id', $media->id);
$response->assertJsonPath('original_url', $media->getFullUrl());
$response->assertJsonPath('size', 3028);
$response->assertJsonPath('name', 'beispiel bild');
$response->assertJsonPath('collection_name', 'defaultSingleFile');
$response->assertJsonPath('file_name', 'beispiel-bild.jpg');
$response->assertJsonMissingPath('model_type');
$response->assertJsonMissingPath('model_id');
});
test('it uploads heig image', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$content = base64_encode($this->getFile('heic.jpg', 'heic.jpg')->getContent());
$this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'conversionsWithDefault',
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
])->assertStatus(201);
});
test('it uploads a single image to a single file collection', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$content = base64_encode($this->jpgFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
]);
$response->assertStatus(201);
$this->assertCount(1, $post->getMedia('defaultSingleFile'));
});
test('it forces a filename for a single collection', function () {
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
$this->auth()->registerModel();
$post = $this->newPost();
$content = base64_encode($this->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'singleForced',
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
]);
$response->assertStatus(201);
$this->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('singleForced')->file_name);
$response->assertJsonPath('name', 'beispiel bild 2023-04-04');
$response->assertJsonPath('file_name', 'beispiel-bild-2023-04-04.jpg');
});
test('it sets custom title when storing', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$content = base64_encode($this->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'singleStoringHook',
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
]);
$response->assertStatus(201);
$media = $post->getFirstMedia('singleStoringHook');
$this->assertEquals('AAA', $media->getCustomProperty('use'));
$this->assertEquals('beispiel bild', $media->getCustomProperty('ttt'));
});
test('it sets custom properties from properties method', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$content = base64_encode($this->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'multipleProperties',
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
]);
$response->assertStatus(201);
$media = $post->getFirstMedia('multipleProperties');
$this->assertEquals('beispielBild.jpg', $media->getCustomProperty('test'));
});
test('it forces a filename for multiple collections', function () {
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
$this->auth()->registerModel();
$post = $this->newPost();
$content = base64_encode($this->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'multipleForced',
'payload' => [
[
'content' => $content,
'name' => 'beispiel bild.jpg',
],
],
]);
$response->assertStatus(201);
$this->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('multipleForced')->file_name);
});
test('it throws event when file has been uploaded', function () {
Event::fake();
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
$this->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost();
$content = base64_encode($this->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'singleWithEvent',
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
]);
$response->assertStatus(201);
Event::assertDispatched(MediaStored::class, fn ($event) => $event->media->id === $response->json('id'));
Event::assertDispatched(MediaChange::class, fn ($event) => $event->model->is($post));
});
test('it throws event when multiple files uploaded', function () {
Event::fake();
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
$this->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost();
$content = base64_encode($this->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'multipleFilesWithEvent',
'payload' => [
[
'content' => $content,
'name' => 'beispiel bild.jpg',
],
[
'content' => $content,
'name' => 'beispiel bild 1.jpg',
],
],
]);
$response->assertStatus(201);
Event::assertDispatched(MediaStored::class, fn ($event) => $event->media->id === $response->json('0.id'));
Event::assertDispatched(MediaStored::class, fn ($event) => $event->media->id === $response->json('1.id'));
});
test('it uploads multiple files', function () {
$this->auth()->registerModel();
$post = $this->newPost();
$file = $this->pdfFile();
$post->addMedia($file->getPathname())->preservingOriginal()->toMediaCollection('images');
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'images',
'payload' => [
[
'content' => base64_encode($file->getContent()),
'name' => 'aaaa.jpg',
],
[
'content' => base64_encode($file->getContent()),
'name' => 'beispiel bild.jpg',
],
],
]);
$response->assertStatus(201);
$this->assertCount(3, $post->getMedia('images'));
$media = $post->getMedia('images')->skip(1)->values();
$this->assertCount(2, $response->json());
$response->assertJsonPath('0.id', $media->get(0)->id);
$response->assertJsonPath('1.id', $media->get(1)->id);
$response->assertJsonPath('0.original_url', $media->first()->getFullUrl());
$response->assertJsonPath('0.size', 3028);
$response->assertJsonPath('0.name', 'aaaa');
$response->assertJsonPath('0.collection_name', 'images');
$response->assertJsonPath('0.file_name', 'aaaa.jpg');
$response->assertJsonMissingPath('0.model_type');
$response->assertJsonMissingPath('0.model_id');
});
test('it returns 403 when not authorized', function () {
$this->auth(['storeMedia' => false])->registerModel();
$post = $this->newPost();
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [
'content' => base64_encode($this->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
]);
$response->assertStatus(403);
});
test('it needs validation for single files', function (array $payload, string $invalidFieldName) {
$this->auth()->registerModel();
$post = $this->newPost();
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [
'content' => base64_encode($this->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
...$payload,
]);
$response->assertStatus(422);
$response->assertJsonValidationErrors($invalidFieldName);
})->with(function () {
yield [['model' => 'missingmodel'], 'model'];
yield [['id' => -1], 'model'];
yield [['collection' => 'missingcollection'], 'collection'];
yield [['payload' => ['name' => 'AAA', 'content' => []]], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => ['UU']]], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => null]], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => '']], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => 1]], 'payload.content'];
yield [['payload' => ['name' => '', 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload' => ['name' => ['U'], 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload' => ['name' => 1, 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload' => ['name' => null, 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload' => 'lalal'], 'payload'];
yield [['payload' => 55], 'payload'];
});
test('it needs validation for multiple files', function (array $payload, string $invalidFieldName) {
$this->auth()->registerModel();
$post = $this->newPost();
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'images',
'payload' => [
[
'content' => base64_encode($this->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
],
...$payload,
]);
$response->assertStatus(422);
$response->assertJsonValidationErrors($invalidFieldName);
})->with(function () {
yield [['model' => 'missingmodel'], 'model'];
yield [['id' => -1], 'model'];
yield [['collection' => 'missingcollection'], 'collection'];
yield [['payload' => 'lalal'], 'payload'];
yield [['payload' => []], 'payload'];
yield [['payload' => 1], 'payload'];
yield [['payload' => [['name' => 'AAA', 'content' => []]]], 'payload.0.content'];
yield [['payload' => [['name' => 'AAA', 'content' => ['UU']]]], 'payload.0.content'];
yield [['payload' => [['name' => 'AAA', 'content' => null]]], 'payload.0.content'];
yield [['payload' => [['name' => 'AAA', 'content' => '']]], 'payload.0.content'];
yield [['payload' => [['name' => 'AAA', 'content' => 1]]], 'payload.0.content'];
yield [['payload' => [['name' => '', 'content' => 'aaadfdf']]], 'payload.0.name'];
yield [['payload' => [['name' => ['U'], 'content' => 'aaadfdf']]], 'payload.0.name'];
yield [['payload' => [['name' => 1, 'content' => 'aaadfdf']]], 'payload.0.name'];
yield [['payload' => [['name' => null, 'content' => 'aaadfdf']]], 'payload.0.name'];
yield [['payload' => ['RRR']], 'payload.0'];
});

66
tests/Models/Post.php Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored;
class Post extends Model implements HasMedia
{
use InteractsWithMedia;
public $guarded = [];
public function registerMediaCollections(): void
{
$this->addMediaCollection('defaultSingleFile')->maxWidth(fn () => 250)->singleFile();
$this->addMediaCollection('conversionsWithDefault')
->singleFile()
->withFallback(fn ($parent) => ['default.jpg', 'public'])
->registerMediaConversions(function () {
$this->addMediaConversion('tiny')->width(200)->height(200);
});
$this->addMediaCollection('images')->after(fn ($model) => Event::dispatch(new MediaChange($model)));
$this->addMediaCollection('singleForced')->singleFile()->forceFileName(function ($model, $name) {
return $name . ' ' . now()->format('Y-m-d');
});
$this->addMediaCollection('multipleForced')->forceFileName(function ($model, $name) {
return $name . ' ' . now()->format('Y-m-d');
});
$this->addMediaCollection('singleStoringHook')->singleFile()->storing(function ($adder, $fileName) {
return $adder->withCustomProperties([
'use' => 'AAA',
'ttt' => pathinfo($fileName, PATHINFO_FILENAME),
]);
});
$this->addMediaCollection('singleWithEvent')->singleFile()->stored(function (Media $media) {
Event::dispatch(new MediaStored($media));
})
->destroyed(fn ($model) => Event::dispatch(new MediaDestroyed($model)))
->after(fn ($model) => Event::dispatch(new MediaChange($model)));
$this->addMediaCollection('multipleFilesWithEvent')->stored(function (Media $media) {
Event::dispatch(new MediaStored($media));
});
$this->addMediaCollection('multipleProperties')->singleFile()->withDefaultProperties(fn ($path, $pathinfo) => [
'test' => Str::camel($path),
])->withPropertyValidation(fn ($path) => [
'test' => 'string|max:10',
])
->after(fn ($model) => Event::dispatch(new MediaChange($model)));
}
}

7
tests/Pest.php Normal file
View File

@ -0,0 +1,7 @@
<?php
use Illuminate\Support\Facades\Storage;
uses(Zoomyboy\MedialibraryHelper\Tests\TestCase::class)->in('Feature');
uses(Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
uses()->beforeEach(fn () => Storage::fake('media'))->in('Feature');

87
tests/TestCase.php Normal file
View File

@ -0,0 +1,87 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests;
use Illuminate\Http\File;
use Illuminate\Support\Facades\Gate;
use Orchestra\Testbench\TestCase as BaseTestCase;
use Spatie\LaravelData\LaravelDataServiceProvider;
use Spatie\MediaLibrary\MediaLibraryServiceProvider;
use Zoomyboy\MedialibraryHelper\ServiceProvider;
use Zoomyboy\MedialibraryHelper\Tests\Models\Post;
class TestCase extends BaseTestCase
{
/**
* Define database migrations.
*/
protected function defineDatabaseMigrations(): void
{
$this->loadMigrationsFrom(__DIR__ . '/migrations');
}
protected function getPackageProviders($app): array
{
return [
ServiceProvider::class,
MediaLibraryServiceProvider::class,
LaravelDataServiceProvider::class,
];
}
/**
* Generate a pdf file with a filename and get path.
*/
protected function pdfFile(?string $filename = null): File
{
return $this->getFile('pdf.pdf', $filename ?: 'pdf.pdf');
}
protected function jpgFile(?string $filename = null): File
{
return $this->getFile('jpg.jpg', $filename ?: 'jpg.jpg');
}
protected function getFile(string $location, string $as): File
{
$path = __DIR__ . '/stubs/' . $location;
$to = sys_get_temp_dir() . '/' . $as;
copy($path, $to);
return new File($to);
}
protected function registerModel(): static
{
app()->extend('media-library-helpers', fn ($p) => $p->put('post', Post::class));
return $this;
}
protected function newPost(): Post
{
return Post::create(['title' => 'Lorem', 'content' => 'aafff']);
}
protected function auth(array $policies = []): self
{
$policies = [
'storeMedia' => true,
'updateMedia' => true,
'destroyMedia' => true,
'listMedia' => true,
...$policies,
];
foreach ($policies as $ability => $result) {
Gate::define($ability, fn (?string $user, string $collectionName) => $result);
}
return $this;
}
protected function defineEnvironment($app)
{
$app['config']->set('media-library.middleware', ['web']);
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('media', function (Blueprint $table) {
$table->id();
$table->morphs('model');
$table->uuid('uuid')->nullable()->unique();
$table->string('collection_name');
$table->string('name');
$table->string('file_name');
$table->string('mime_type')->nullable();
$table->string('disk');
$table->string('conversions_disk')->nullable();
$table->unsignedBigInteger('size');
$table->json('manipulations');
$table->json('custom_properties');
$table->json('generated_conversions');
$table->json('responsive_images');
$table->unsignedInteger('order_column')->nullable()->index();
$table->nullableTimestamps();
});
}
};

View File

@ -0,0 +1,17 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
class InitialMigration extends Migration {
public function up(): void
{
Schema::create('posts', function($table) {
$table->id();
$table->string('title');
$table->string('content');
$table->timestamps();
});
}
}

BIN
tests/stubs/heic.jpg Normal file

Binary file not shown.

BIN
tests/stubs/jpg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

198
tests/stubs/pdf.pdf Normal file
View File

@ -0,0 +1,198 @@
%PDF-1.3
%âãÏÓ
1 0 obj
<<
/Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R
>>
endobj
2 0 obj
<<
/Type /Outlines
/Count 0
>>
endobj
3 0 obj
<<
/Type /Pages
/Count 2
/Kids [ 4 0 R 6 0 R ]
>>
endobj
4 0 obj
<<
/Type /Page
/Parent 3 0 R
/Resources <<
/Font <<
/F1 9 0 R
>>
/ProcSet 8 0 R
>>
/MediaBox [0 0 612.0000 792.0000]
/Contents 5 0 R
>>
endobj
5 0 obj
<< /Length 1074 >>
stream
2 J
BT
0 0 0 rg
/F1 0027 Tf
57.3750 722.2800 Td
( A Simple PDF File ) Tj
ET
BT
/F1 0010 Tf
69.2500 688.6080 Td
( This is a small demonstration .pdf file - ) Tj
ET
BT
/F1 0010 Tf
69.2500 664.7040 Td
( just for use in the Virtual Mechanics tutorials. More text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 652.7520 Td
( text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 628.8480 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 616.8960 Td
( text. And more text. Boring, zzzzz. And more text. And more text. And ) Tj
ET
BT
/F1 0010 Tf
69.2500 604.9440 Td
( more text. And more text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 592.9920 Td
( And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 569.0880 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 557.1360 Td
( text. And more text. And more text. Even more. Continued on page 2 ...) Tj
ET
endstream
endobj
6 0 obj
<<
/Type /Page
/Parent 3 0 R
/Resources <<
/Font <<
/F1 9 0 R
>>
/ProcSet 8 0 R
>>
/MediaBox [0 0 612.0000 792.0000]
/Contents 7 0 R
>>
endobj
7 0 obj
<< /Length 676 >>
stream
2 J
BT
0 0 0 rg
/F1 0027 Tf
57.3750 722.2800 Td
( Simple PDF File 2 ) Tj
ET
BT
/F1 0010 Tf
69.2500 688.6080 Td
( ...continued from page 1. Yet more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 676.6560 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 664.7040 Td
( text. Oh, how boring typing this stuff. But not as boring as watching ) Tj
ET
BT
/F1 0010 Tf
69.2500 652.7520 Td
( paint dry. And more text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 640.8000 Td
( Boring. More, a little more text. The end, and just as well. ) Tj
ET
endstream
endobj
8 0 obj
[/PDF /Text]
endobj
9 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
>>
endobj
10 0 obj
<<
/Creator (Rave \(http://www.nevrona.com/rave\))
/Producer (Nevrona Designs)
/CreationDate (D:20060301072826)
>>
endobj
xref
0 11
0000000000 65535 f
0000000019 00000 n
0000000093 00000 n
0000000147 00000 n
0000000222 00000 n
0000000390 00000 n
0000001522 00000 n
0000001690 00000 n
0000002423 00000 n
0000002456 00000 n
0000002574 00000 n
trailer
<<
/Size 11
/Root 1 0 R
/Info 10 0 R
>>
startxref
2714
%%EOF