Compare commits

...

23 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
21 changed files with 3472 additions and 1051 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/vendor/ /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

@ -16,6 +16,7 @@
} }
], ],
"require": { "require": {
"ext-imagick": ">=3.6.0",
"spatie/laravel-medialibrary": "^10.7", "spatie/laravel-medialibrary": "^10.7",
"laravel/framework": "^9.50", "laravel/framework": "^9.50",
"spatie/laravel-data": "^3.1", "spatie/laravel-data": "^3.1",
@ -35,5 +36,12 @@
"allow-plugins": { "allow-plugins": {
"pestphp/pest-plugin": true "pestphp/pest-plugin": true
} }
},
"extra": {
"laravel": {
"providers": [
"Zoomyboy\\MedialibraryHelper\\ServiceProvider"
]
}
} }
} }

3957
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,8 @@ class CollectionExtension
{ {
public function boot(): void public function boot(): void
{ {
MediaCollection::mixin(new class() { MediaCollection::mixin(new class()
{
public function forceFileName() public function forceFileName()
{ {
return fn ($callback) => $this->registerCustomCallback('forceFileName', $callback); return fn ($callback) => $this->registerCustomCallback('forceFileName', $callback);
@ -19,6 +20,16 @@ class CollectionExtension
return fn ($callback) => $this->registerCustomCallback('storing', $callback); 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() public function withDefaultProperties()
{ {
return fn ($callback) => $this->registerCustomCallback('withDefaultProperties', $callback); return fn ($callback) => $this->registerCustomCallback('withDefaultProperties', $callback);
@ -34,6 +45,16 @@ class CollectionExtension
return fn ($callback) => $this->registerCustomCallback('withPropertyValidation', $callback); 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() public function runCallback()
{ {
return function (string $callback, ...$parameters) { return function (string $callback, ...$parameters) {
@ -60,11 +81,15 @@ class CollectionExtension
return; return;
} }
$this->customCallbacks = collect([ $this->customCallbacks = collect([
'forceFileName' => fn ($name) => $name, 'forceFileName' => fn ($model, $name) => $name,
'maxWidth' => fn ($size) => null,
'stored' => fn ($event) => true, 'stored' => fn ($event) => true,
'after' => fn ($event) => true,
'destroyed' => fn ($event) => true,
'storing' => fn ($adder, $name) => $adder, 'storing' => fn ($adder, $name) => $adder,
'withDefaultProperties' => fn ($path) => [], 'withDefaultProperties' => fn ($path, $pathinfo) => [],
'withPropertyValidation' => fn ($path) => [], 'withPropertyValidation' => fn ($path) => [],
'withFallback' => fn ($parent) => null,
]); ]);
}; };
} }

View File

@ -2,14 +2,17 @@
namespace Zoomyboy\MedialibraryHelper; namespace Zoomyboy\MedialibraryHelper;
use Imagick;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Spatie\Image\Image;
use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\DataCollection;
use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidBase64Data;
use Spatie\MediaLibrary\MediaCollections\FileAdder;
use Spatie\MediaLibrary\MediaCollections\MediaCollection; use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\MediaCollections\Models\Media;
@ -25,9 +28,9 @@ class MediaController
]); ]);
$model = $this->validateModel($request); $model = $this->validateModel($request);
$this->authorize('storeMedia', $model);
$collection = $model->getMediaCollection($request->input('collection')); $collection = $model->getMediaCollection($request->input('collection'));
$isSingle = 1 === $collection->collectionSizeLimit; $isSingle = 1 === $collection->collectionSizeLimit;
$this->authorize('storeMedia', [$model, $collection->name]);
$request->validate($isSingle ? [ $request->validate($isSingle ? [
'payload' => 'array', 'payload' => 'array',
@ -45,11 +48,13 @@ class MediaController
$medias = collect($content)->map(function ($c) use ($collection, $model) { $medias = collect($content)->map(function ($c) use ($collection, $model) {
$pathinfo = pathinfo($c['name']); $pathinfo = pathinfo($c['name']);
$path = $collection->runCallback('forceFileName', $pathinfo['filename']).'.'.$pathinfo['extension']; $basename = $collection->runCallback('forceFileName', $model, $pathinfo['filename']);
Storage::disk('public')->put($path, base64_decode($c['content'])); $path = $basename . '.' . $pathinfo['extension'];
$adder = $model
->addMedia(Storage::disk('public')->path($path)) $adder = $this->fileAdderFromData($model, $c['content'], $collection)
->withCustomProperties($collection->runCallback('withDefaultProperties', $path)); ->usingName($basename)
->usingFileName($path)
->withCustomProperties($collection->runCallback('withDefaultProperties', $path, $pathinfo));
return tap( return tap(
$collection->runCallback('storing', $adder, $path)->toMediaCollection($collection->name), $collection->runCallback('storing', $adder, $path)->toMediaCollection($collection->name),
@ -57,12 +62,14 @@ class MediaController
); );
}); });
$collection->runCallback('after', $model->fresh());
return $isSingle ? MediaData::from($medias->first()) : MediaData::collection($medias); return $isSingle ? MediaData::from($medias->first()) : MediaData::collection($medias);
} }
public function update(Request $request, Media $media): MediaData public function update(Request $request, Media $media): MediaData
{ {
$this->authorize('updateMedia', $media->model); $this->authorize('updateMedia', [$media->model, $media->collection_name]);
$rules = collect($media->model->getMediaCollection($media->collection_name)->runCallback('withPropertyValidation', $media->file_name)) $rules = collect($media->model->getMediaCollection($media->collection_name)->runCallback('withPropertyValidation', $media->file_name))
->mapWithKeys(fn ($rule, $key) => ["properties.{$key}" => $rule])->toArray(); ->mapWithKeys(fn ($rule, $key) => ["properties.{$key}" => $rule])->toArray();
@ -70,28 +77,38 @@ class MediaController
$validated = $request->validate($rules); $validated = $request->validate($rules);
$media->update(['custom_properties' => data_get($validated, 'properties', [])]); $media->update(['custom_properties' => data_get($validated, 'properties', [])]);
$media->model->getMediaCollection($media->collection_name)->runCallback('after', $media->model->fresh());
return MediaData::from($media); return MediaData::from($media);
} }
public function index(Request $request, $parentModel, int $parentId, string $collection): MediaData|DataCollection public function index(Request $request, $parentModel, int $parentId, string $collectionName): MediaData|DataCollection
{ {
$model = app('media-library-helpers')->get($parentModel); $model = app('media-library-helpers')->get($parentModel);
$model = $model::find($parentId); $model = $model::find($parentId);
$this->authorize('listMedia', $model); $this->authorize('listMedia', [$model, $collectionName]);
$isSingle = 1 === $model->getMediaCollection($collection)->collectionSizeLimit; $collection = $model->getMediaCollection($collectionName);
$isSingle = 1 === $collection->collectionSizeLimit;
abort_if($isSingle && !$model->getFirstMedia($collection), 404); abort_if($isSingle && !$model->getFirstMedia($collectionName) && !MediaData::defaultFromCollection($model, $collection), 404);
if ($isSingle && !$model->getFirstMedia($collectionName)) {
return MediaData::defaultFromCollection($model, $collection);
}
return $isSingle return $isSingle
? MediaData::from($model->getFirstMedia($collection)) ? MediaData::from($model->getFirstMedia($collectionName))
: MediaData::collection($model->getMedia($collection)); : MediaData::collection($model->getMedia($collectionName));
} }
public function destroy(Media $media, Request $request): JsonResponse 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(); $media->delete();
$collection->runCallback('destroyed', $media->model->fresh());
$collection->runCallback('after', $media->model->fresh());
return response()->json([]); return response()->json([]);
} }
@ -120,4 +137,42 @@ class MediaController
return $model; 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);
}
} }

View File

@ -2,17 +2,21 @@
namespace Zoomyboy\MedialibraryHelper; namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Data; use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper; use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\MediaCollections\Models\Media;
#[MapInputName(SnakeCaseMapper::class)] #[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)] #[MapOutputName(SnakeCaseMapper::class)]
class MediaData extends Data class MediaData extends Data
{ {
public int $id; public ?int $id;
public string $originalUrl; public string $originalUrl;
@ -24,11 +28,55 @@ class MediaData extends Data
public string $fileName; public string $fileName;
public string $mimeType;
#[MapInputName('custom_properties')] #[MapInputName('custom_properties')]
public array $properties; public array $properties;
public array $conversions = [];
public bool $fallback = false;
public static function fromMedia(Media $media): self public static function fromMedia(Media $media): self
{ {
return self::withoutMagicalCreationFrom($media); $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

@ -18,7 +18,8 @@ class ServiceProvider extends BaseServiceProvider
app(Router::class)->group($this->routeGroup(), function ($router) { app(Router::class)->group($this->routeGroup(), function ($router) {
$router->post('mediaupload', [MediaController::class, 'store'])->name('media.store'); $router->post('mediaupload', [MediaController::class, 'store'])->name('media.store');
$router->delete('mediaupload/{media}', [MediaController::class, 'destroy'])->name('media.destroy'); $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'); $router->patch('mediaupload/{media}', [MediaController::class, 'update'])->name('media.update');
}); });
@ -31,7 +32,7 @@ class ServiceProvider extends BaseServiceProvider
protected function routeGroup(): array protected function routeGroup(): array
{ {
return [ return [
'middleware' => config('medialibrary-helper.middleware'), '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

@ -2,8 +2,12 @@
namespace Zoomyboy\MedialibraryHelper\Tests\Feature; 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 () { test('it deletes multiple media', function () {
$this->auth()->registerModel(); $this->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost(); $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');
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleForced'); $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleForced');
@ -33,3 +37,16 @@ test('it needs authorization', function () {
$this->deleteJson("/mediaupload/{$media->id}")->assertStatus(403); $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

@ -2,6 +2,9 @@
namespace Zoomyboy\MedialibraryHelper\Tests\Feature; namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Storage;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
test('it gets all medias', function () { test('it gets all medias', function () {
$this->auth()->registerModel(); $this->auth()->registerModel();
$post = $this->newPost(); $post = $this->newPost();
@ -14,6 +17,25 @@ test('it gets all medias', function () {
$response->assertJsonPath('0.id', $firstMedia->id); $response->assertJsonPath('0.id', $firstMedia->id);
$response->assertJsonPath('1.id', $secondMedia->id); $response->assertJsonPath('1.id', $secondMedia->id);
$response->assertJsonPath('1.properties.test', 'old'); $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 () { test('it gets media for single', function () {
@ -26,6 +48,8 @@ test('it gets media for single', function () {
$response->assertStatus(200); $response->assertStatus(200);
$response->assertJsonPath('id', $media->id); $response->assertJsonPath('id', $media->id);
$response->assertJsonPath('properties.test', 'old'); $response->assertJsonPath('properties.test', 'old');
$response->assertJsonPath('fallback', false);
$response->assertJsonPath('mime_type', 'application/pdf');
}); });
test('it checks for authorization', function () { test('it checks for authorization', function () {
@ -46,3 +70,30 @@ test('it returns 404 when media not found', function () {
$response->assertStatus(404); $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

@ -1,44 +0,0 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Zoomyboy\MedialibraryHelper\Tests\TestCase;
class MiddlewareTest extends TestCase
{
use RefreshDatabase;
public function testItReturns401WhenNotLoggedIn(): void
{
$this->registerModel();
$post = $this->newPost();
$response = $this->postJson('/mediaupload', [
'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'content' => base64_encode($this->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
]);
$response->assertStatus(401);
}
public function testItReturns401WhenDestroying(): void
{
$this->registerModel();
$post = $this->newPost();
$media = $post->addMedia($this->pdfFile()->getPathname())->toMediaCollection('defaultSingleFile');
$response = $this->deleteJson("/mediaupload/{$media->id}");
$response->assertStatus(401);
}
protected function defineEnvironment($app)
{
$app['config']->set('medialibrary-helper.middleware', ['web', 'auth:web']);
}
}

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

@ -2,7 +2,11 @@
namespace Zoomyboy\MedialibraryHelper\Tests\Feature; namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
test('it updates a single files properties', function () { test('it updates a single files properties', function () {
Event::fake();
$this->auth()->registerModel(); $this->auth()->registerModel();
$post = $this->newPost(); $post = $this->newPost();
$post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleProperties'); $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('multipleProperties');
@ -20,6 +24,7 @@ test('it updates a single files properties', function () {
$this->assertEquals(null, $media->fresh()->getCustomProperty('missing')); $this->assertEquals(null, $media->fresh()->getCustomProperty('missing'));
$response->assertJsonPath('properties.test', 'new'); $response->assertJsonPath('properties.test', 'new');
$response->assertJsonMissingPath('properties.missing'); $response->assertJsonMissingPath('properties.missing');
Event::assertDispatched(MediaChange::class, fn ($event) => $event->model->is($post));
}); });
test('it validates a single files properties', function () { test('it validates a single files properties', function () {

View File

@ -4,6 +4,7 @@ namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored;
test('it uploads a single file to a single file collection', function () { test('it uploads a single file to a single file collection', function () {
@ -34,6 +35,41 @@ test('it uploads a single file to a single file collection', function () {
$response->assertJsonMissingPath('model_id'); $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 () { test('it forces a filename for a single collection', function () {
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00')); Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
$this->auth()->registerModel(); $this->auth()->registerModel();
@ -140,6 +176,7 @@ test('it throws event when file has been uploaded', function () {
$response->assertStatus(201); $response->assertStatus(201);
Event::assertDispatched(MediaStored::class, fn ($event) => $event->media->id === $response->json('id')); 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 () { test('it throws event when multiple files uploaded', function () {

View File

@ -8,6 +8,8 @@ use Illuminate\Support\Str;
use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia; use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored;
class Post extends Model implements HasMedia class Post extends Model implements HasMedia
@ -18,16 +20,23 @@ class Post extends Model implements HasMedia
public function registerMediaCollections(): void public function registerMediaCollections(): void
{ {
$this->addMediaCollection('defaultSingleFile')->singleFile(); $this->addMediaCollection('defaultSingleFile')->maxWidth(fn () => 250)->singleFile();
$this->addMediaCollection('images'); $this->addMediaCollection('conversionsWithDefault')
->singleFile()
$this->addMediaCollection('singleForced')->singleFile()->forceFileName(function ($name) { ->withFallback(fn ($parent) => ['default.jpg', 'public'])
return $name.' '.now()->format('Y-m-d'); ->registerMediaConversions(function () {
$this->addMediaConversion('tiny')->width(200)->height(200);
}); });
$this->addMediaCollection('multipleForced')->forceFileName(function ($name) { $this->addMediaCollection('images')->after(fn ($model) => Event::dispatch(new MediaChange($model)));
return $name.' '.now()->format('Y-m-d');
$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) { $this->addMediaCollection('singleStoringHook')->singleFile()->storing(function ($adder, $fileName) {
@ -39,16 +48,19 @@ class Post extends Model implements HasMedia
$this->addMediaCollection('singleWithEvent')->singleFile()->stored(function (Media $media) { $this->addMediaCollection('singleWithEvent')->singleFile()->stored(function (Media $media) {
Event::dispatch(new MediaStored($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) { $this->addMediaCollection('multipleFilesWithEvent')->stored(function (Media $media) {
Event::dispatch(new MediaStored($media)); Event::dispatch(new MediaStored($media));
}); });
$this->addMediaCollection('multipleProperties')->singleFile()->withDefaultProperties(fn ($path) => [ $this->addMediaCollection('multipleProperties')->singleFile()->withDefaultProperties(fn ($path, $pathinfo) => [
'test' => Str::camel($path), 'test' => Str::camel($path),
])->withPropertyValidation(fn ($path) => [ ])->withPropertyValidation(fn ($path) => [
'test' => 'string|max:10', 'test' => 'string|max:10',
]); ])
->after(fn ($model) => Event::dispatch(new MediaChange($model)));
} }
} }

View File

@ -17,7 +17,7 @@ class TestCase extends BaseTestCase
*/ */
protected function defineDatabaseMigrations(): void protected function defineDatabaseMigrations(): void
{ {
$this->loadMigrationsFrom(__DIR__.'/migrations'); $this->loadMigrationsFrom(__DIR__ . '/migrations');
} }
protected function getPackageProviders($app): array protected function getPackageProviders($app): array
@ -37,10 +37,15 @@ class TestCase extends BaseTestCase
return $this->getFile('pdf.pdf', $filename ?: 'pdf.pdf'); 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 protected function getFile(string $location, string $as): File
{ {
$path = __DIR__.'/stubs/'.$location; $path = __DIR__ . '/stubs/' . $location;
$to = sys_get_temp_dir().'/'.$as; $to = sys_get_temp_dir() . '/' . $as;
copy($path, $to); copy($path, $to);
return new File($to); return new File($to);
@ -69,7 +74,7 @@ class TestCase extends BaseTestCase
]; ];
foreach ($policies as $ability => $result) { foreach ($policies as $ability => $result) {
Gate::define($ability, fn (?string $whatever) => $result); Gate::define($ability, fn (?string $user, string $collectionName) => $result);
} }
return $this; return $this;
@ -77,6 +82,6 @@ class TestCase extends BaseTestCase
protected function defineEnvironment($app) protected function defineEnvironment($app)
{ {
$app['config']->set('medialibrary-helper.middleware', ['web']); $app['config']->set('media-library.middleware', ['web']);
} }
} }

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