Compare commits

..

2 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
42 changed files with 1908 additions and 3713 deletions

View File

@ -6,7 +6,7 @@ This package creates routes for the popular Medialibrary Package from Spatie ().
In RegisterMediaCollections, you have the following methods available: 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 basename (without extension). You should return the new name of the file without the extension (e.g. disc). 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)) forceFileName(fn ($model, $path) => Str::slug($path))
@ -25,3 +25,4 @@ You can call whatever you want after an image has been added, modified or delete
.... ....
}) })
``` ```

View File

@ -1,14 +0,0 @@
<template>
<svg x="0px" y="0px" viewBox="0 0 511.626 511.627" xml:space="preserve" fill="currentColor">
<g>
<g>
<path
d="M392.857,292.354h-18.274c-2.669,0-4.859,0.855-6.563,2.573c-1.718,1.708-2.573,3.897-2.573,6.563v91.361 c0,12.563-4.47,23.315-13.415,32.262c-8.945,8.945-19.701,13.414-32.264,13.414H82.224c-12.562,0-23.317-4.469-32.264-13.414 c-8.945-8.946-13.417-19.698-13.417-32.262V155.31c0-12.562,4.471-23.313,13.417-32.259c8.947-8.947,19.702-13.418,32.264-13.418 h200.994c2.669,0,4.859-0.859,6.57-2.57c1.711-1.713,2.566-3.9,2.566-6.567V82.221c0-2.662-0.855-4.853-2.566-6.563 c-1.711-1.713-3.901-2.568-6.57-2.568H82.224c-22.648,0-42.016,8.042-58.102,24.125C8.042,113.297,0,132.665,0,155.313v237.542 c0,22.647,8.042,42.018,24.123,58.095c16.086,16.084,35.454,24.13,58.102,24.13h237.543c22.647,0,42.017-8.046,58.101-24.13 c16.085-16.077,24.127-35.447,24.127-58.095v-91.358c0-2.669-0.856-4.859-2.574-6.57 C397.709,293.209,395.519,292.354,392.857,292.354z"
/>
<path
d="M506.199,41.971c-3.617-3.617-7.905-5.424-12.85-5.424H347.171c-4.948,0-9.233,1.807-12.847,5.424 c-3.617,3.615-5.428,7.898-5.428,12.847s1.811,9.233,5.428,12.85l50.247,50.248L198.424,304.067 c-1.906,1.903-2.856,4.093-2.856,6.563c0,2.479,0.953,4.668,2.856,6.571l32.548,32.544c1.903,1.903,4.093,2.852,6.567,2.852 s4.665-0.948,6.567-2.852l186.148-186.148l50.251,50.248c3.614,3.617,7.898,5.426,12.847,5.426s9.233-1.809,12.851-5.426 c3.617-3.616,5.424-7.898,5.424-12.847V54.818C511.626,49.866,509.813,45.586,506.199,41.971z"
/>
</g>
</g>
</svg>
</template>

View File

@ -1,105 +0,0 @@
<template>
<div class="space-y-2">
<div v-if="label" class="text-sm font-semibold text-gray-400" v-text="label"></div>
<label class="flex items-center justify-center h-[35px] border-2 border-solid border-gray-600 rounded-lg text-sm text-gray-300 bg-gray-700 relative" :for="id">
<div class="relative">Klicken oder Datei hierhin ziehen zum hochladen</div>
<input :id="id" ref="uploader" class="hidden" type="file" :name="name" multiple @change="upload($event.target.files)" />
<div class="absolute w-full h-full top-0 left-0 cursor-pointer" @drop="onDropping" @dragenter="onDragEnter" @dragover="onDragOver" @dragleave="onDragLeave"></div>
</label>
<div v-for="file in inner" :key="file.id" class="flex h-[35px] justify-between items-center space-x-2 px-2 border-2 border-solid border-gray-600 rounded-lg text-gray-300 bg-gray-700">
<img :src="file.icon" class="w-6 h-6" />
<div class="text-sm text-gray-300 leading-none grow" v-text="file.file_name"></div>
<slot name="buttons" button-class="flex justify-center items-center w-6 h-6 rounded-full" icon-class="w-3 h-3" :file="file"></slot>
<a v-tooltip="`Löschen`" href="#" class="flex justify-center items-center w-6 h-6 rounded-full bg-red-200" @click.prevent="onDelete(file)">
<trash-icon class="text-red-800 w-3 h-3"></trash-icon>
</a>
<a v-tooltip="`Öffnen`" :href="file.original_url" class="flex justify-center items-center w-6 h-6 rounded-full bg-primary-700" target="_BLANK">
<external-icon class="text-primary-200 w-3 h-3"></external-icon>
</a>
</div>
</div>
</template>
<script setup>
import {ref, inject} from 'vue';
import TrashIcon from './TrashIcon.vue';
import ExternalIcon from './ExternalIcon.vue';
import useReadFile from '../composables/useReadFile.js';
import {useToast} from 'vue-toastification';
const emit = defineEmits(['update:modelValue']);
const axios = inject('axios');
const toast = useToast();
const queue = ref([]);
const inner = ref([]);
const {onDragEnter, onDragOver, onDragLeave, processDrop, read} = useReadFile();
const props = defineProps({
collection: {
required: true,
type: String,
},
parentName: {
required: true,
type: String,
},
parentId: {
required: true,
validator: (value) => typeof value === 'number' || value === null,
},
name: {
type: String,
required: true,
},
id: {
required: true,
type: String,
},
label: {
required: false,
default: () => null,
validator: (value) => value === null || typeof value === 'string',
},
});
async function onDropping(e) {
await upload(await processDrop(e, 1));
}
async function upload(files) {
[...files].forEach((f) => realUpload(f));
}
async function realUpload(file) {
var payload = await read(file);
var identifier = Math.random().toString(36) + Date.now() + payload.content.substring(2, 15);
queue.value = [...queue.value, {file_name: payload.name, size: payload.size, identifier: identifier}];
var response = await axios.post('/mediaupload', {
parent: {model: props.parentName, collection_name: props.collection, id: props.parentId ? props.parentId : null},
payload: [payload],
});
queue.value = queue.value.filter((f) => f.identifier !== identifier);
inner.value.push(response.data[0]);
emit('update:modelValue', inner.value);
toast.success('Dateien hochgeladen');
}
async function onDelete(file) {
await axios.delete(`/mediaupload/${file.id}`);
inner.value = inner.value.filter((f) => f.id !== file.id);
emit('update:modelValue', inner.value);
toast.success('Datei entfernt');
}
async function reload() {
var response = await axios.get(`/mediaupload/${props.parentName}/${props.parentId}/${props.collection}`);
inner.value = response.data;
emit('update:modelValue', response.data);
}
if (props.parentId !== null) {
reload();
}
</script>

View File

@ -1,6 +0,0 @@
<template>
<svg height="426.66667pt" viewBox="0 0 426.66667 426.66667" width="426.66667pt" xmlns="http://www.w3.org/2000/svg">
<path
d="m405.332031 192h-170.664062v-170.667969c0-11.773437-9.558594-21.332031-21.335938-21.332031-11.773437 0-21.332031 9.558594-21.332031 21.332031v170.667969h-170.667969c-11.773437 0-21.332031 9.558594-21.332031 21.332031 0 11.777344 9.558594 21.335938 21.332031 21.335938h170.667969v170.664062c0 11.777344 9.558594 21.335938 21.332031 21.335938 11.777344 0 21.335938-9.558594 21.335938-21.335938v-170.664062h170.664062c11.777344 0 21.335938-9.558594 21.335938-21.335938 0-11.773437-9.558594-21.332031-21.335938-21.332031zm0 0" />
</svg>
</template>

View File

@ -1,117 +0,0 @@
<template>
<label class="flex flex-col" :for="id">
<span v-if="label" class="text-sm font-semibold text-gray-400">
{{ label }}
<span v-show="required" class="text-red-800">&nbsp;*</span>
</span>
<div class="h-[35px] border-2 border-solid relative rounded-lg cursor-pointer flex-none border-gray-600 text-gray-300 bg-gray-700">
<div v-if="inner === null" class="flex items-center justify-center h-full">
<div class="relative text-sm text-gray-300 leading-none">Klicken oder Datei hierhin ziehen zum hochladen</div>
<input :id="id" class="hidden" type="file" :name="name" :multiple="false" @change="upload($event.target.files)" />
<div class="absolute w-full h-full top-0 left-0 cursor-pointer" @drop="onDropping" @dragenter="onDragEnter" @dragover="onDragOver" @dragleave="onDragLeave"></div>
</div>
<div v-else class="flex justify-between items-center h-full space-x-2 px-2">
<img :src="inner.icon" class="w-6 h-6" />
<div class="text-sm text-gray-300 leading-none grow" v-text="inner.file_name"></div>
<a v-tooltip="`Löschen`" href="#" class="flex justify-center items-center w-6 h-6 rounded-full bg-red-200" @click.prevent="onDelete">
<trash-icon class="text-red-800 w-3 h-3"></trash-icon>
</a>
<a v-tooltip="`Öffnen`" :href="inner.original_url" class="flex justify-center items-center w-6 h-6 rounded-full bg-primary-700" target="_BLANK">
<external-icon class="text-primary-200 w-3 h-3"></external-icon>
</a>
</div>
</div>
</label>
</template>
<script setup>
import {watch, ref, inject} from 'vue';
import TrashIcon from './TrashIcon.vue';
import ExternalIcon from './ExternalIcon.vue';
import useReadFile from '../composables/useReadFile.js';
import {useToast} from 'vue-toastification';
const emit = defineEmits(['update:modelValue']);
const axios = inject('axios');
const toast = useToast();
const inner = ref(null);
const {dropping, onDragEnter, onDragOver, onDragLeave, processDrop, read} = useReadFile();
const props = defineProps({
required: {
type: Boolean,
default: () => false,
},
collection: {
required: true,
type: String,
},
parentName: {
required: true,
type: String,
},
parentId: {
required: true,
validator: (value) => typeof value === 'number' || value === null,
},
name: {
type: String,
required: true,
},
id: {
required: true,
type: String,
},
label: {
required: false,
default: () => null,
validator: (value) => value === null || typeof value === 'string',
},
});
function parseValue(v) {
if (v === null) {
return null;
}
if (v.fallback) {
return null;
}
return v;
}
async function reload() {
var response = await axios.get(`/mediaupload/${props.parentName}/${props.parentId}/${props.collection}`);
inner.value = parseValue(response.data);
emit('update:modelValue', inner.value.id);
}
async function onDropping(e) {
await upload(await processDrop(e, 1));
}
async function upload(files) {
await realUpload(await read(files[0]));
}
async function realUpload(file) {
inner.value = (
await axios.post('/mediaupload', {
parent: {model: props.parentName, collection_name: props.collection, id: props.parentId ? props.parentId : null},
payload: file,
})
).data;
emit('update:modelValue', inner.value);
toast.success('Datei hochgeladen');
}
async function onDelete() {
await axios.delete(`/mediaupload/${inner.value.id}`);
inner.value = null;
emit('update:modelValue', null);
toast.success('Datei entfernt');
}
if (props.parentId !== null) {
reload();
}
</script>

View File

@ -1,35 +0,0 @@
<template>
<svg
version="1.1"
id="Capa_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 512 512"
style="enable-background: new 0 0 512 512"
xml:space="preserve"
fill="currentColor"
>
<g>
<g>
<path
d="M486.4,102.4h-128V25.6c0-15.36-10.24-25.6-25.6-25.6H179.2c-15.36,0-25.6,10.24-25.6,25.6v76.8h-128
C10.24,102.4,0,112.64,0,128s10.24,25.6,25.6,25.6h460.8c15.36,0,25.6-10.24,25.6-25.6S501.76,102.4,486.4,102.4z M307.2,102.4
H204.8V51.2h102.4V102.4z"
/>
</g>
</g>
<g>
<g>
<path
d="M25.6,204.8l48.64,284.16c2.56,12.8,12.8,23.04,25.6,23.04h312.32c12.8,0,23.04-10.24,25.6-23.04L486.4,204.8H25.6z
M153.6,460.8c-15.36,0-25.6-10.24-25.6-25.6l-25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6l25.6,153.6
C179.2,450.56,168.96,460.8,153.6,460.8z M281.6,435.2c0,15.36-10.24,25.6-25.6,25.6s-25.6-10.24-25.6-25.6V281.6
c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6V435.2z M384,435.2c0,15.36-10.24,25.6-25.6,25.6
c-15.36,0-25.6-10.24-25.6-25.6l25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6L384,435.2z"
/>
</g>
</g>
</svg>
</template>

View File

@ -1,77 +0,0 @@
import accounting from 'accounting';
import {ref} from 'vue';
accounting.settings.currency = {
...accounting.settings.currency,
precision: 2,
format: '%v %s',
decimal: ',',
thousand: '.',
};
export default function () {
const dropping = ref(false);
async function read(file) {
return new Promise((resolve) => {
var reader = new FileReader();
reader.onload = function () {
var r = reader.result;
resolve({
content: r.substr(r.search(',') + 1),
name: file.name,
type: file.type,
size: file.size,
});
};
reader.readAsDataURL(file);
});
}
function onDragEnter(e) {
e.preventDefault();
e.stopPropagation();
dropping.value = true;
}
function onDragOver(e) {
e.preventDefault();
e.stopPropagation();
}
function onDragLeave(e) {
e.preventDefault();
e.stopPropagation();
dropping.value = false;
}
function processDrop(e, maxFiles) {
e.preventDefault();
e.stopPropagation();
let dt = e.dataTransfer;
let files = [...dt.files].slice(0, maxFiles ? maxFiles : dt.files.length);
let result = [];
for (const f in files) {
if (typeof files[f] === 'object') {
result.push(files[f]);
}
}
dropping.value = false;
return result;
}
function size(file) {
if (file.size < 1000) {
return accounting.formatMoney(file.size, {symbol: 'B'});
}
if (file.size < 1000000) {
return accounting.formatMoney(file.size / 1000, {symbol: 'KB'});
}
if (file.size < 1000000000) {
return accounting.formatMoney(file.size / 1000000, {symbol: 'MB'});
}
if (file.size < 1000000000000) {
return accounting.formatMoney(file.size / 1000000000, {symbol: 'GB'});
}
return '';
}
return {dropping, onDragLeave, onDragOver, onDragEnter, processDrop, read};
}

View File

@ -4,15 +4,9 @@
"type": "library", "type": "library",
"license": "MIT", "license": "MIT",
"autoload": { "autoload": {
"psr-4": {
"Zoomyboy\\MedialibraryHelper\\": "src/"
}
},
"autoload-dev": {
"psr-4": { "psr-4": {
"Zoomyboy\\MedialibraryHelper\\Tests\\": "tests/", "Zoomyboy\\MedialibraryHelper\\Tests\\": "tests/",
"Workbench\\App\\": "tests/workbench/app/", "Zoomyboy\\MedialibraryHelper\\": "src/"
"Database\\Factories\\": "tests/workbench/database/factories/"
} }
}, },
"authors": [ "authors": [
@ -22,31 +16,20 @@
} }
], ],
"require": { "require": {
"spatie/laravel-medialibrary": "^11.0", "ext-imagick": ">=3.6.0",
"laravel/framework": "^11.0", "spatie/laravel-medialibrary": "^10.7",
"spatie/laravel-data": "^4.0", "laravel/framework": "^9.50",
"pestphp/pest": "^3.0" "spatie/laravel-data": "^3.1",
"pestphp/pest": "^1.22"
}, },
"require-dev": { "require-dev": {
"orchestra/testbench": "^9.0", "phpunit/phpunit": "^9.6",
"illuminate/console": "^11.0" "orchestra/testbench": "^7.0",
"illuminate/console": "^9.2"
}, },
"scripts": { "scripts": {
"post-autoload-dump": [ "post-autoload-dump": [
"@clear",
"@prepare",
"@php vendor/bin/testbench package:discover --ansi" "@php vendor/bin/testbench package:discover --ansi"
],
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"build": "@php vendor/bin/testbench workbench:build --ansi",
"serve": [
"Composer\\Config::disableProcessTimeout",
"@build",
"@php vendor/bin/testbench serve"
],
"test": [
"@php vendor/bin/pest"
] ]
}, },
"config": { "config": {

3883
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
<?php
return [
'temp_disk' => 'temp',
'middleware' => ['web', 'auth:web'],
];

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
colors="true" colors="true"
> >
@ -9,6 +9,11 @@
<directory suffix="Test.php">./tests/Feature</directory> <directory suffix="Test.php">./tests/Feature</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php> <php>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/> <env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing"/>
@ -22,9 +27,4 @@
<env name="SESSION_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/> <env name="TELESCOPE_ENABLED" value="false"/>
</php> </php>
<source>
<include>
<directory suffix=".php">./src</directory>
</include>
</source>
</phpunit> </phpunit>

View File

@ -2,96 +2,97 @@
namespace Zoomyboy\MedialibraryHelper; namespace Zoomyboy\MedialibraryHelper;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
class CollectionExtension class CollectionExtension
{ {
public function forceFileName() public function boot(): void
{ {
return fn ($callback) => $this->registerCustomCallback('forceFileName', $callback); MediaCollection::mixin(new class()
} {
public function forceFileName()
public function storing() {
{ return fn ($callback) => $this->registerCustomCallback('forceFileName', $callback);
return fn ($callback) => $this->registerCustomCallback('storing', $callback);
}
public function destroyed()
{
return fn ($callback) => $this->registerCustomCallback('destroyed', $callback);
}
public function convert()
{
return fn ($callback) => $this->registerCustomCallback('convert', $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;
};
}
protected function setDefaultCustomCallbacks()
{
return function () {
if (property_exists($this, 'customCallbacks')) {
return;
} }
$this->convertTo = null;
$this->customCallbacks = collect([ public function storing()
'forceFileName' => fn ($model, $name) => $name, {
'convert' => fn ($extension) => $extension, return fn ($callback) => $this->registerCustomCallback('storing', $callback);
'maxWidth' => fn ($size) => null, }
'stored' => fn ($event) => true,
'after' => fn ($event) => true, public function destroyed()
'destroyed' => fn ($event) => true, {
'storing' => fn ($adder, $name) => $adder, return fn ($callback) => $this->registerCustomCallback('destroyed', $callback);
'withDefaultProperties' => fn ($path) => [], }
'withPropertyValidation' => fn ($path) => [],
'withFallback' => fn ($parent) => null, 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

@ -1,56 +0,0 @@
<?php
namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use Illuminate\Support\Collection;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\Lazy;
#[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)]
class DeferredMediaData extends Data
{
public string $collectionName;
public Lazy|string $path;
public static function fromPath(string $path, MediaCollection $collection): self
{
return self::factory()->withoutMagicalCreation()->from([
'path' => Lazy::create(fn () => $path),
'collection_name' => $collection->name,
]);
}
public static function collectionFromPaths(Collection $paths, MediaCollection $collection): Collection
{
return static::collect($paths->map(fn ($path) => static::fromPath($path, $collection)));
}
public function with(): array
{
$file = new MediaFile(Storage::disk(config('media-library.temp_disk'))->path($this->path->resolve()));
return [
'is_deferred' => true,
'original_url' => $this->storage()->url($this->path->resolve()),
'name' => $file->getBasename(),
'size' => $file->getSize(),
'file_name' => $file->getFilename(),
'mime_type' => $file->getMimeType(),
'icon' => Storage::disk('public')->url('filetypes/' . str()->slug($file->getMimeType()) . '.svg'),
];
}
protected function storage(): FilesystemAdapter
{
return Storage::disk(config('media-library.temp_disk'));
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
trait DefersUploads
{
public function setDeferredUploads(array $uploads): void
{
if (array_key_exists('collection_name', $uploads)) {
$uploads = [$uploads];
}
foreach ($uploads as $upload) {
$this->runMediaUploadForSingleFile($upload);
}
}
protected function runMediaUploadForSingleFile(array $fileData): void
{
$collection = $this->getMediaCollection($fileData['collection_name']);
$file = new MediaFile($this->storage()->path($fileData['file_name']));
$file->setBasename($collection->runCallback('forceFileName', $this, $file->getBasename()));
$adder = $this->addMediaFromDisk('media-library/' . $fileData['file_name'], config('media-library.temp_disk'))
->usingName($file->getBasename())
->usingFileName($file->getFilename())
->withCustomProperties($collection->runCallback('withDefaultProperties', $file->getFilename()));
tap(
$collection->runCallback('storing', $adder, $file->getFilename())->toMediaCollection($collection->name),
fn ($media) => $collection->runCallback('stored', $media)
);
$collection->runCallback('after', $this);
}
protected function storage(): FilesystemAdapter
{
return Storage::disk(config('media-library.temp_disk'));
}
}

View File

@ -2,20 +2,19 @@
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\Http\Response; use Illuminate\Validation\Rule;
use Illuminate\Support\Collection; use Illuminate\Validation\ValidationException;
use Spatie\Image\Image; use Spatie\Image\Image;
use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\DataCollection;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidBase64Data; use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidBase64Data;
use Spatie\MediaLibrary\MediaCollections\FileAdder; 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;
use Zoomyboy\MedialibraryHelper\Rules\ModelRule;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\File\File;
class MediaController class MediaController
{ {
@ -24,83 +23,48 @@ class MediaController
public function store(Request $request) public function store(Request $request)
{ {
$request->validate([ $request->validate([
'parent' => ['required', new ModelRule()], 'name' => 'string',
'model' => ['required', 'string', Rule::in(app('media-library-helpers')->keys())],
]); ]);
if (is_null($request->input('parent.id'))) { $model = $this->validateModel($request);
return $this->storeDeferred($request); $collection = $model->getMediaCollection($request->input('collection'));
}
$model = ModelRule::getModel($request->input('parent'));
$collection = ModelRule::getCollection($request->input('parent'));
$isSingle = 1 === $collection->collectionSizeLimit; $isSingle = 1 === $collection->collectionSizeLimit;
$this->authorize('storeMedia', [$model, $collection->name]); $this->authorize('storeMedia', [$model, $collection->name]);
$request->validate($isSingle ? [ $request->validate($isSingle ? [
'payload' => 'array', 'payload' => 'array',
'payload.name' => 'required|string|regex:/\..*$/|max:255', 'payload.*' => '',
'payload.name' => 'required|string|max:255',
'payload.content' => 'required|string', 'payload.content' => 'required|string',
] : [ ] : [
'payload' => 'required|array|min:1', 'payload' => 'required|array|min:1',
'payload.*' => 'array', 'payload.*' => 'array',
'payload.*.name' => 'required|string|regex:/\..*$/|max:255', 'payload.*.name' => 'string',
'payload.*.content' => 'required|string', 'payload.*.content' => 'string',
]); ]);
$content = $isSingle ? [$request->input('payload')] : $request->input('payload'); $content = $isSingle ? [$request->input('payload')] : $request->input('payload');
$medias = collect($content)->map(function ($c) use ($collection, $model) { $medias = collect($content)->map(function ($c) use ($collection, $model) {
$file = new MediaFile($c['name']); $pathinfo = pathinfo($c['name']);
$file->setBasename($collection->runCallback('forceFileName', $model, $file->getBasename())); $basename = $collection->runCallback('forceFileName', $model, $pathinfo['filename']);
$file->setExtension(MediaFile::extensionFromData($c['content'])); $path = $basename . '.' . $pathinfo['extension'];
$file->setExtension($collection->runCallback('convert', $file->getExtension()));
$adder = $this->fileAdderFromData($model, $c['content'], $collection) $adder = $this->fileAdderFromData($model, $c['content'], $collection)
->usingName($file->getBasename()) ->usingName($basename)
->usingFileName($file->getFilename()) ->usingFileName($path)
->withCustomProperties($collection->runCallback('withDefaultProperties', $file->getFilename())); ->withCustomProperties($collection->runCallback('withDefaultProperties', $path, $pathinfo));
return tap( return tap(
$collection->runCallback('storing', $adder, $file->getFilename())->toMediaCollection($collection->name), $collection->runCallback('storing', $adder, $path)->toMediaCollection($collection->name),
fn ($media) => $collection->runCallback('stored', $media) fn ($media) => $collection->runCallback('stored', $media)
); );
}); });
$collection->runCallback('after', $model->fresh()); $collection->runCallback('after', $model->fresh());
return $isSingle ? MediaData::from($medias->first()) : response(MediaData::collect($medias), 201); return $isSingle ? MediaData::from($medias->first()) : MediaData::collection($medias);
}
protected function storeDeferred(Request $request)
{
$modelName = ModelRule::getModelClassName($request->input('parent'));
$collection = ModelRule::getCollection($request->input('parent'));
$isSingle = 1 === $collection->collectionSizeLimit;
$request->validate($isSingle ? [
'payload' => 'array',
'payload.name' => 'required|string|regex:/\..*$/|max:255',
'payload.content' => 'required|string',
] : [
'payload' => 'required|array|min:1',
'payload.*' => 'array',
'payload.*.name' => 'required|string|regex:/\..*$/|max:255',
'payload.*.content' => 'required|string',
]);
$content = $isSingle ? [$request->input('payload')] : $request->input('payload');
$tempPaths = collect($content)->map(function ($c) use ($collection) {
$file = new MediaFile($c['name']);
$file->setExtension(MediaFile::extensionFromData($c['content']));
$file->setExtension($collection->runCallback('convert', $file->getExtension()));
$tmpFile = $this->storeTemporaryFile($c['content'], $collection);
Storage::disk(config('media-library.temp_disk'))->move($tmpFile, 'media-library/' . $file->getFilename());
return 'media-library/' . $file->getFilename();
});
$this->authorize('storeMedia', [$modelName, null, $collection->name]);
return $isSingle ? DeferredMediaData::fromPath($tempPaths->first(), $collection) : response(DeferredMediaData::collectionFromPaths($tempPaths, $collection), 201);
} }
public function update(Request $request, Media $media): MediaData public function update(Request $request, Media $media): MediaData
@ -118,7 +82,7 @@ class MediaController
return MediaData::from($media); return MediaData::from($media);
} }
public function index(Request $request, $parentModel, int $parentId, string $collectionName): MediaData|JsonResponse 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);
@ -134,7 +98,7 @@ class MediaController
return $isSingle return $isSingle
? MediaData::from($model->getFirstMedia($collectionName)) ? MediaData::from($model->getFirstMedia($collectionName))
: response()->json(MediaData::collect($model->getMedia($collectionName))->toArray()); : MediaData::collection($model->getMedia($collectionName));
} }
public function destroy(Media $media, Request $request): JsonResponse public function destroy(Media $media, Request $request): JsonResponse
@ -154,7 +118,27 @@ class MediaController
return property_exists($collection, $callback); return property_exists($collection, $callback);
} }
private function storeTemporaryFile(string $data, MediaCollection $collection): string 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()->pluck('name')),
],
]);
$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); $maxWidth = $collection->runCallback('maxWidth', 9);
if (str_contains($data, ';base64')) { if (str_contains($data, ';base64')) {
@ -174,24 +158,21 @@ class MediaController
throw InvalidBase64Data::create(); throw InvalidBase64Data::create();
} }
$tmpFile = 'media-library/' . str()->uuid()->toString() . '.' . $collection->runCallback('convert', MediaFile::extensionFromData($data)); $tmpFile = tempnam(sys_get_temp_dir(), 'media-library');
Storage::disk(config('media-library.temp_disk'))->put($tmpFile, $binaryData); file_put_contents($tmpFile, $binaryData);
$imagePath = Storage::disk(config('media-library.temp_disk'))->path($tmpFile); $i = (new Imagick());
$image = Image::load($imagePath); $i->readImage($tmpFile);
if (null !== $maxWidth && 'image/jpeg' === Storage::disk(config('media-library.temp_disk'))->mimeType($tmpFile)) {
$image->width($maxWidth); if ($i->getImageFormat() === 'HEIC') {
$i->setFormat('jpg');
$i->writeImage($tmpFile);
} }
$image->save(); if (null !== $maxWidth && 'image/jpeg' === mime_content_type($tmpFile)) {
Image::load($tmpFile)->width($maxWidth)->save();
}
return $tmpFile; return $model->addMedia($tmpFile);
}
protected function fileAdderFromData($model, $data, $collection): FileAdder
{
$tmpFile = $this->storeTemporaryFile($data, $collection);
return $model->addMediaFromDisk($tmpFile, config('media-library.temp_disk'));
} }
} }

View File

@ -37,17 +37,14 @@ class MediaData extends Data
public bool $fallback = false; public bool $fallback = false;
public bool $isDeferred = false;
public static function fromMedia(Media $media): self public static function fromMedia(Media $media): self
{ {
$conversions = collect($media->getMediaConversionNames())->flip()->map( $conversions = collect($media->getMediaConversionNames())->flip()->map(fn ($integer, $conversion) => $media->hasGeneratedConversion($conversion)
fn ($integer, $conversion) => $media->hasGeneratedConversion($conversion) ? ['original_url' => $media->getFullUrl($conversion)]
? ['original_url' => $media->getFullUrl($conversion)] : null,
: null,
); );
return self::factory()->withoutMagicalCreation()->from([ return self::withoutMagicalCreationFrom([
...$media->toArray(), ...$media->toArray(),
'conversions' => $conversions->toArray(), 'conversions' => $conversions->toArray(),
]); ]);

View File

@ -1,51 +0,0 @@
<?php
namespace Zoomyboy\MedialibraryHelper;
use Symfony\Component\HttpFoundation\File\File;
class MediaFile
{
public File $file;
public function __construct(string $path)
{
$this->file = new File($path, false);
}
public function getBasename(): string
{
return $this->file->getBasename('.' . $this->file->getExtension());
}
public function setBasename(string $basename): void
{
$newInstance = new self(($this->getPath() ? $this->getPath() . '/' : '') . $basename . '.' . $this->getExtension());
$this->file = $newInstance->file;
}
public function setExtension(string $extension): void
{
$newInstance = new self(($this->getPath() ? $this->getPath() . '/' : '') . $this->getBasename() . '.' . $extension);
$this->file = $newInstance->file;
}
public function __call($method, $arguments)
{
return $this->file->{$method}(...$arguments);
}
public static function extensionFromData(string $data): string
{
$tempFile = sys_get_temp_dir() . '/' . str()->uuid()->toString();
file_put_contents($tempFile, base64_decode($data));
$extension = (new self($tempFile))->guessExtension();
unlink($tempFile);
return $extension;
}
}

View File

@ -16,7 +16,7 @@ class OrderController
$mediaCount = collect($request->order)->map(function ($media) { $mediaCount = collect($request->order)->map(function ($media) {
$media = Media::findOrFail($media); $media = Media::findOrFail($media);
return $media->model_id . '_' . $media->model_type; return $media->model_id.'_'.$media->model_type;
})->unique()->count(); })->unique()->count();
if (1 !== $mediaCount) { if (1 !== $mediaCount) {
@ -31,6 +31,6 @@ class OrderController
$model->getMediaCollection($collectionName)->runCallback('after', $model->fresh()); $model->getMediaCollection($collectionName)->runCallback('after', $model->fresh());
return MediaData::collect($model->getMedia($collectionName))->toArray(); return MediaData::collection($model->getMedia($collectionName));
} }
} }

View File

@ -1,89 +0,0 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Rules;
use Illuminate\Contracts\Validation\InvokableRule;
use Spatie\MediaLibrary\HasMedia;
use Illuminate\Validation\Factory;
use Illuminate\Validation\Rule;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
class ModelRule implements InvokableRule
{
public ?string $collection;
public ?int $id;
public ?string $model;
/**
* @param array{?id: int, ?collection: string, ?model: string} $attribute
*/
public function __invoke($attribute, $value, $fail)
{
app(Factory::class)->make([$attribute => $value], [
"{$attribute}.id" => 'nullable|integer|gt:0',
"{$attribute}.model" => ['required', 'string', Rule::in(app('media-library-helpers')->keys())],
"{$attribute}.collection_name" => 'required|string',
])->validate();
$this->model = data_get($value, 'model');
$this->id = data_get($value, 'id');
$this->collection = data_get($value, 'collection_name');
if (is_null($this->id)) {
$this->validateDeferred($attribute, $value);
return;
}
$model = app('media-library-helpers')->get($this->model);
app(Factory::class)->make([$attribute => $value], [
"{$attribute}.collection_name" => ['required', Rule::in((new $model())->getRegisteredMediaCollections()->pluck('name'))],
"{$attribute}.id" => ['required', 'exists:' . (new $model)->getTable() . ',id'],
])->validate();
}
public function validateDeferred($attribute, $value): void
{
app(Factory::class)->make([$attribute => $value], [
"{$attribute}.model" => ['required', 'string', Rule::in(app('media-library-helpers')->keys())],
"{$attribute}.collection_name" => 'required|string',
])->validate();
$model = app('media-library-helpers')->get($this->model);
app(Factory::class)->make([$attribute => $value], [
"{$attribute}.collection_name" => ['required', Rule::in((new $model())->getRegisteredMediaCollections()->pluck('name'))],
])->validate();
}
/**
* @param array{?id: int, ?collection_name: string, ?model: string} $modelParam
*/
public static function getModel($modelParam): HasMedia
{
$model = static::getModelClassName($modelParam);
return $model::find($modelParam['id']);
}
/**
* @param array{?id: int, ?collection_name: string, ?model: string} $modelParam
* @return class-string<HasMedia>
*/
public static function getModelClassName($modelParam): string
{
return app('media-library-helpers')->get($modelParam['model']);
}
/**
* @param array{?id: int, ?collection_name: string, ?model: string} $modelParam
*/
public static function getCollection($modelParam): MediaCollection
{
$className = static::getModelClassName($modelParam);
return (new $className)->getMediaCollection($modelParam['collection_name']);
}
}

View File

@ -4,7 +4,6 @@ namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Routing\Router; use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider as BaseServiceProvider; use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
class ServiceProvider extends BaseServiceProvider class ServiceProvider extends BaseServiceProvider
{ {
@ -12,8 +11,6 @@ class ServiceProvider extends BaseServiceProvider
{ {
app()->bind('media-library-helpers', fn () => collect([])); app()->bind('media-library-helpers', fn () => collect([]));
app()->singleton(CollectionExtension::class, fn () => new CollectionExtension()); app()->singleton(CollectionExtension::class, fn () => new CollectionExtension());
$this->mergeConfigFrom(__DIR__ . '/../config/media-library.php', 'media-library');
} }
public function boot(): void public function boot(): void
@ -26,8 +23,7 @@ class ServiceProvider extends BaseServiceProvider
$router->patch('mediaupload/{media}', [MediaController::class, 'update'])->name('media.update'); $router->patch('mediaupload/{media}', [MediaController::class, 'update'])->name('media.update');
}); });
app(CollectionExtension::class)->boot();
MediaCollection::mixin(app(CollectionExtension::class));
} }
/** /**

View File

@ -1,20 +0,0 @@
providers:
- Spatie\MediaLibrary\MediaLibraryServiceProvider
- Spatie\LaravelData\LaravelDataServiceProvider
- Zoomyboy\MedialibraryHelper\ServiceProvider
migrations:
- tests/workbench/database/migrations
workbench:
start: '/'
install: false
discovers:
web: false
api: false
commands: false
components: false
views: false
build: []
assets: []
sync: []

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Workbench\App\Events; namespace Zoomyboy\MedialibraryHelper\Tests\Events;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Workbench\App\Events; namespace Zoomyboy\MedialibraryHelper\Tests\Events;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Workbench\App\Events; namespace Zoomyboy\MedialibraryHelper\Tests\Events;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;

View File

@ -1,196 +0,0 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;
test('it uploads a deferred file to a collection', function () {
$this->auth()->registerModel()->withoutExceptionHandling();
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => null],
'payload' => ['content' => base64_encode(test()->pdfFile()->getContent()), 'name' => 'beispiel.pdf'],
])
->assertStatus(201)
->assertExactJson([
'is_deferred' => true,
'original_url' => Storage::disk('temp')->url('media-library/beispiel.pdf'),
'name' => 'beispiel',
'collection_name' => 'defaultSingleFile',
'size' => 64576,
'file_name' => 'beispiel.pdf',
'mime_type' => 'application/pdf',
'icon' => url('storage/filetypes/applicationpdf.svg'),
]);
Storage::disk('temp')->assertExists('media-library/beispiel.pdf');
});
test('it forces filename when uploading', function () {
test()->auth()->registerModel()->withoutExceptionHandling();
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'singleJpegFile', 'id' => null],
'payload' => ['content' => base64_encode(test()->pngFile()->getContent()), 'name' => 'beispiel.png'],
])
->assertStatus(201)
->assertExactJson([
'is_deferred' => true,
'original_url' => Storage::disk('temp')->url('media-library/beispiel.jpg'),
'name' => 'beispiel',
'collection_name' => 'singleJpegFile',
'size' => 490278,
'file_name' => 'beispiel.jpg',
'mime_type' => 'image/jpeg',
'icon' => url('storage/filetypes/imagejpeg.svg'),
]);
Storage::disk('temp')->assertExists('media-library/beispiel.jpg');
Storage::disk('temp')->assertMissing('media-library/beispiel.png');
});
test('it stores a file to media library after deferred upload', function () {
Carbon::setTestNow(Carbon::parse('2023-05-06 06:00:00'));
test()->auth()->registerModel()->withoutExceptionHandling();
Storage::disk('temp')->put('media-library/beispiel.pdf', test()->pdfFile()->getContent());
$post = test()->newPost();
$post->setDeferredUploads([
'file_name' => 'beispiel.pdf',
'collection_name' => 'singleForced',
]);
$media = $post->getMedia('singleForced')->first();
test()->assertNotNull($media);
test()->assertEquals('beispiel 2023-05-06', $media->name);
Storage::disk('temp')->assertMissing('media-library/beispiel.pdf');
});
test('it stores multiple files to media library after deferred upload', function () {
Carbon::setTestNow(Carbon::parse('2023-05-06 06:00:00'));
test()->auth()->registerModel()->withoutExceptionHandling();
Storage::disk('temp')->put('media-library/beispiel.pdf', test()->pdfFile()->getContent());
Storage::disk('temp')->put('media-library/beispiel2.pdf', test()->pdfFile()->getContent());
$post = test()->newPost();
$post->setDeferredUploads([
[
'file_name' => 'beispiel.pdf',
'collection_name' => 'multipleForced',
],
[
'file_name' => 'beispiel2.pdf',
'collection_name' => 'multipleForced',
]
]);
$medias = $post->getMedia('multipleForced');
test()->assertCount(2, $medias);
test()->assertEquals('beispiel 2023-05-06', $medias->get(0)->name);
test()->assertEquals('beispiel2 2023-05-06', $medias->get(1)->name);
Storage::disk('temp')->assertMissing('media-library/beispiel.pdf');
Storage::disk('temp')->assertMissing('media-library/beispiel2.pdf');
});
test('it uploads multiple files', function () {
test()->auth()->registerModel()->withoutExceptionHandling();
$content = base64_encode(test()->pdfFile()->getContent());
$payload = [
'parent' => ['model' => 'post', 'collection_name' => 'multipleForced', 'id' => null],
'payload' => [
['content' => $content, 'name' => 'beispiel.pdf'],
['content' => $content, 'name' => 'beispiel2.pdf'],
]
];
test()->postJson('/mediaupload', $payload)
->assertStatus(201)
->assertJson([
[
'is_deferred' => true,
'original_url' => Storage::disk('temp')->url('media-library/beispiel.pdf'),
'name' => 'beispiel',
'collection_name' => 'multipleForced',
'size' => 64576,
'file_name' => 'beispiel.pdf',
'mime_type' => 'application/pdf',
],
[
'is_deferred' => true,
'original_url' => Storage::disk('temp')->url('media-library/beispiel2.pdf'),
'name' => 'beispiel2',
'collection_name' => 'multipleForced',
'size' => 64576,
'file_name' => 'beispiel2.pdf',
'mime_type' => 'application/pdf',
]
]);
Storage::disk('temp')->assertExists('media-library/beispiel.pdf');
Storage::disk('temp')->assertExists('media-library/beispiel2.pdf');
});
test('it reduces file size', function () {
test()->auth()->registerModel()->withoutExceptionHandling();
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => null],
'payload' => [
'content' => base64_encode(test()->jpgFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
]);
$size = getimagesize(Storage::disk('temp')->path('media-library/beispiel bild.jpg'));
test()->assertEquals(250, $size[0]);
});
test('it handles authorization with collection', function () {
test()->auth(['storeMedia' => ['collection' => 'rtrt']])->registerModel();
$content = base64_encode(test()->pdfFile()->getContent());
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => null],
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
])->assertStatus(403);
});
test('it handles authorization with collection correctly', function () {
test()->auth(['storeMedia' => ['collection' => 'defaultSingleFile']])->registerModel();
$content = base64_encode(test()->pdfFile()->getContent());
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => null],
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
])->assertStatus(201);
});
test('it needs a collection', function ($key, $value) {
test()->auth()->registerModel();
$content = base64_encode(test()->pdfFile()->getContent());
$payload = [
'parent' => ['model' => 'post', 'collection' => '', 'id' => null],
'payload' => [
'content' => $content,
'name' => 'beispiel bild.jpg',
],
];
data_set($payload, $key, $value);
test()->postJson('/mediaupload', $payload)->assertJsonValidationErrors($key);
})->with(function () {
yield ['parent.collection_name', ''];
yield ['parent.collection_name', -1];
yield ['parent.collection_name', 'missingcollection'];
yield ['parent.model', 'lalala'];
yield ['parent.model', -1];
yield ['parent.model', ''];
});

View File

@ -3,8 +3,8 @@
namespace Zoomyboy\MedialibraryHelper\Tests\Feature; namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Workbench\App\Events\MediaChange; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Workbench\App\Events\MediaDestroyed; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed;
test('it deletes multiple media', function () { test('it deletes multiple media', function () {
$this->auth()->registerModel()->withoutExceptionHandling(); $this->auth()->registerModel()->withoutExceptionHandling();

View File

@ -7,7 +7,6 @@ use Spatie\MediaLibrary\MediaCollections\Models\Media;
test('it gets all medias', function () { test('it gets all medias', function () {
$this->auth()->registerModel(); $this->auth()->registerModel();
$this->withoutExceptionHandling();
$post = $this->newPost(); $post = $this->newPost();
$firstMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images'); $firstMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');
$secondMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images'); $secondMedia = $post->addMedia($this->pdfFile()->getPathname())->withCustomProperties(['test' => 'old'])->preservingOriginal()->toMediaCollection('images');

View File

@ -1,24 +0,0 @@
<?php
namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Foundation\Console\VendorPublishCommand;
use Illuminate\Support\Facades\Artisan;
use Spatie\MediaLibrary\MediaLibraryServiceProvider;
afterEach(function () {
@unlink(config_path('media-library.php'));
});
test('modifies config file', function () {
Artisan::call(VendorPublishCommand::class, ['--provider' => MediaLibraryServiceProvider::class, '--tag' => 'config']);
$configContents = file_get_contents(config_path('media-library.php'));
$configContents = preg_replace('/\'image_driver\' => env.*/', '\'image_driver\' => "lala",', $configContents);
file_put_contents(config_path('media-library.php'), $configContents);
$this->tearDownTheTestEnvironment();
$this->setUpTheTestEnvironment();
$this->assertEquals('lala', config('media-library.image_driver'));
$this->assertEquals('temp', config('media-library.temp_disk'));
});

View File

@ -3,7 +3,7 @@
namespace Zoomyboy\MedialibraryHelper\Tests\Feature; namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Workbench\App\Events\MediaChange; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
test('it can reorder media', function () { test('it can reorder media', function () {
Event::fake(); Event::fake();

View File

@ -3,7 +3,7 @@
namespace Zoomyboy\MedialibraryHelper\Tests\Feature; namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Workbench\App\Events\MediaChange; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
test('it updates a single files properties', function () { test('it updates a single files properties', function () {
Event::fake(); Event::fake();

View File

@ -4,65 +4,62 @@ namespace Zoomyboy\MedialibraryHelper\Tests\Feature;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Spatie\MediaLibrary\MediaCollections\Models\Media; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Symfony\Component\Mime\MimeTypes; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored;
use Workbench\App\Events\MediaChange;
use Workbench\App\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 () {
test()->auth()->registerModel()->withoutExceptionHandling(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->pdfFile()->getContent()); $content = base64_encode($this->pdfFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.pdf', 'name' => 'beispiel bild.jpg',
], ],
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
test()->assertCount(1, $post->getMedia('defaultSingleFile')); $this->assertCount(1, $post->getMedia('defaultSingleFile'));
$media = $post->getFirstMedia('defaultSingleFile'); $media = $post->getFirstMedia('defaultSingleFile');
$response->assertJsonPath('id', $media->id); $response->assertJsonPath('id', $media->id);
$response->assertJsonPath('original_url', $media->getFullUrl()); $response->assertJsonPath('original_url', $media->getFullUrl());
$response->assertJsonPath('size', 3028); $response->assertJsonPath('size', 3028);
$response->assertJsonPath('name', 'beispiel bild'); $response->assertJsonPath('name', 'beispiel bild');
$response->assertJsonPath('collection_name', 'defaultSingleFile'); $response->assertJsonPath('collection_name', 'defaultSingleFile');
$response->assertJsonPath('file_name', 'beispiel-bild.pdf'); $response->assertJsonPath('file_name', 'beispiel-bild.jpg');
$response->assertJsonPath('is_deferred', false);
$response->assertJsonMissingPath('model_type'); $response->assertJsonMissingPath('model_type');
$response->assertJsonMissingPath('model_id'); $response->assertJsonMissingPath('model_id');
}); });
test('it changes format', function () { test('it uploads heig image', function () {
test()->auth()->registerModel(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->pngFile()->getContent()); $content = base64_encode($this->getFile('heic.jpg', 'heic.jpg')->getContent());
$response = test()->postJson('/mediaupload', [ $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'singleJpegFile', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'conversionsWithDefault',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.png', 'name' => 'beispiel bild.jpg',
], ],
]); ])->assertStatus(201);
$response->assertStatus(201)
->assertJsonPath('size', 490278)
->assertJsonPath('file_name', 'beispiel-bild.jpg')
->assertJsonPath('mime_type', 'image/jpeg');
$this->assertEquals('image/jpeg', MimeTypes::getDefault()->guessMimeType($post->getFirstMedia('singleJpegFile')->getPath()));
}); });
test('it uploads a single image to a single file collection', function () { test('it uploads a single image to a single file collection', function () {
test()->auth()->registerModel()->withoutExceptionHandling(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->jpgFile()->getContent()); $content = base64_encode($this->jpgFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -70,17 +67,19 @@ test('it uploads a single image to a single file collection', function () {
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
test()->assertCount(1, $post->getMedia('defaultSingleFile')); $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'));
test()->auth()->registerModel(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->jpgFile()->getContent()); $content = base64_encode($this->pdfFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'singleForced', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'singleForced',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -88,18 +87,20 @@ test('it forces a filename for a single collection', function () {
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
test()->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('singleForced')->file_name); $this->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('singleForced')->file_name);
$response->assertJsonPath('name', 'beispiel bild 2023-04-04'); $response->assertJsonPath('name', 'beispiel bild 2023-04-04');
$response->assertJsonPath('file_name', 'beispiel-bild-2023-04-04.jpg'); $response->assertJsonPath('file_name', 'beispiel-bild-2023-04-04.jpg');
}); });
test('it sets custom title when storing', function () { test('it sets custom title when storing', function () {
test()->auth()->registerModel(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->pdfFile()->getContent()); $content = base64_encode($this->pdfFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'singleStoringHook', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'singleStoringHook',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -109,37 +110,41 @@ test('it sets custom title when storing', function () {
$response->assertStatus(201); $response->assertStatus(201);
$media = $post->getFirstMedia('singleStoringHook'); $media = $post->getFirstMedia('singleStoringHook');
test()->assertEquals('AAA', $media->getCustomProperty('use')); $this->assertEquals('AAA', $media->getCustomProperty('use'));
test()->assertEquals('beispiel bild', $media->getCustomProperty('ttt')); $this->assertEquals('beispiel bild', $media->getCustomProperty('ttt'));
}); });
test('it sets custom properties from properties method', function () { test('it sets custom properties from properties method', function () {
test()->auth()->registerModel(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->pdfFile()->getContent()); $content = base64_encode($this->pdfFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'multipleProperties', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'multipleProperties',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.pdf', 'name' => 'beispiel bild.jpg',
], ],
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
$media = $post->getFirstMedia('multipleProperties'); $media = $post->getFirstMedia('multipleProperties');
test()->assertEquals('beispielBild.pdf', $media->getCustomProperty('test')); $this->assertEquals('beispielBild.jpg', $media->getCustomProperty('test'));
}); });
test('it forces a filename for multiple collections', function () { test('it forces a filename for multiple collections', function () {
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00')); Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
test()->auth()->registerModel(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->jpgFile()->getContent()); $content = base64_encode($this->pdfFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'multipleForced', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'multipleForced',
'payload' => [ 'payload' => [
[ [
'content' => $content, 'content' => $content,
@ -149,18 +154,20 @@ test('it forces a filename for multiple collections', function () {
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
test()->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('multipleForced')->file_name); $this->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('multipleForced')->file_name);
}); });
test('it throws event when file has been uploaded', function () { test('it throws event when file has been uploaded', function () {
Event::fake(); Event::fake();
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00')); Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
test()->auth()->registerModel()->withoutExceptionHandling(); $this->auth()->registerModel()->withoutExceptionHandling();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->pdfFile()->getContent()); $content = base64_encode($this->pdfFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'singleWithEvent', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'singleWithEvent',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -175,12 +182,14 @@ test('it throws event when file has been uploaded', function () {
test('it throws event when multiple files uploaded', function () { test('it throws event when multiple files uploaded', function () {
Event::fake(); Event::fake();
Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00')); Carbon::setTestNow(Carbon::parse('2023-04-04 00:00:00'));
test()->auth()->registerModel()->withoutExceptionHandling(); $this->auth()->registerModel()->withoutExceptionHandling();
$post = test()->newPost(); $post = $this->newPost();
$content = base64_encode(test()->pdfFile()->getContent()); $content = base64_encode($this->pdfFile()->getContent());
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'multipleFilesWithEvent', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'multipleFilesWithEvent',
'payload' => [ 'payload' => [
[ [
'content' => $content, 'content' => $content,
@ -199,164 +208,120 @@ test('it throws event when multiple files uploaded', function () {
}); });
test('it uploads multiple files', function () { test('it uploads multiple files', function () {
test()->auth()->registerModel(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$file = test()->pdfFile(); $file = $this->pdfFile();
$post->addMedia($file->getPathname())->preservingOriginal()->toMediaCollection('images'); $post->addMedia($file->getPathname())->preservingOriginal()->toMediaCollection('images');
$response = test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'images', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'images',
'payload' => [ 'payload' => [
[ [
'content' => base64_encode($file->getContent()), 'content' => base64_encode($file->getContent()),
'name' => 'aaaa.pdf', 'name' => 'aaaa.jpg',
], ],
[ [
'content' => base64_encode($file->getContent()), 'content' => base64_encode($file->getContent()),
'name' => 'beispiel bild.pdf', 'name' => 'beispiel bild.jpg',
], ],
], ],
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
test()->assertCount(3, $post->getMedia('images')); $this->assertCount(3, $post->getMedia('images'));
$this->assertEquals('application/pdf', MimeTypes::getDefault()->guessMimeType($post->getFirstMedia('images')->getPath()));
$media = $post->getMedia('images')->skip(1)->values(); $media = $post->getMedia('images')->skip(1)->values();
test()->assertCount(2, $response->json()); $this->assertCount(2, $response->json());
$response->assertJsonPath('0.id', $media->get(0)->id); $response->assertJsonPath('0.id', $media->get(0)->id);
$response->assertJsonPath('1.id', $media->get(1)->id); $response->assertJsonPath('1.id', $media->get(1)->id);
$response->assertJsonPath('0.original_url', $media->first()->getFullUrl()); $response->assertJsonPath('0.original_url', $media->first()->getFullUrl());
$response->assertJsonPath('0.size', 64576); $response->assertJsonPath('0.size', 3028);
$response->assertJsonPath('0.name', 'aaaa'); $response->assertJsonPath('0.name', 'aaaa');
$response->assertJsonPath('0.collection_name', 'images'); $response->assertJsonPath('0.collection_name', 'images');
$response->assertJsonPath('0.file_name', 'aaaa.pdf'); $response->assertJsonPath('0.file_name', 'aaaa.jpg');
$response->assertJsonMissingPath('0.model_type'); $response->assertJsonMissingPath('0.model_type');
$response->assertJsonMissingPath('0.model_id'); $response->assertJsonMissingPath('0.model_id');
}); });
test('it returns 403 when not authorized', function () { test('it returns 403 when not authorized', function () {
test()->auth(['storeMedia' => false])->registerModel(); $this->auth(['storeMedia' => false])->registerModel();
$post = test()->newPost(); $post = $this->newPost();
test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => base64_encode(test()->pdfFile()->getContent()), 'content' => base64_encode($this->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
], ],
])->assertStatus(403); ]);
$response->assertStatus(403);
}); });
test('it checks for model when running authorization', function () { test('it needs validation for single files', function (array $payload, string $invalidFieldName) {
$otherPost = test()->newPost(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
test()->auth(['storeMedia' => ['id' => $post->id, 'collection' => 'defaultSingleFile']])->registerModel();
test()->postJson('/mediaupload', [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => base64_encode(test()->pdfFile()->getContent()), 'content' => base64_encode($this->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
], ],
])->assertStatus(201); ...$payload,
]);
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $otherPost->id],
'payload' => [
'content' => base64_encode(test()->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
])->assertStatus(403);
});
test('it checks for collection when running authorization', function () {
$post = test()->newPost();
test()->auth(['storeMedia' => ['id' => $post->id, 'collection' => 'defaultSingleFile']])->registerModel();
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id],
'payload' => [
'content' => base64_encode(test()->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
])->assertStatus(201);
test()->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'singleWithEvent', 'id' => $post->id],
'payload' => [
'content' => base64_encode(test()->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
])->assertStatus(403);
});
test('it needs validation for single files', function (array $payloadOverwrites, string $invalidFieldName) {
test()->auth()->registerModel();
$post = test()->newPost();
$payload = [
'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id],
'payload' => [
'content' => base64_encode(test()->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg',
],
];
foreach ($payloadOverwrites as $key => $value) {
data_set($payload, $key, $value);
}
$response = test()->postJson('/mediaupload', $payload);
$response->assertStatus(422); $response->assertStatus(422);
$response->assertJsonValidationErrors($invalidFieldName); $response->assertJsonValidationErrors($invalidFieldName);
})->with(function () { })->with(function () {
yield [['parent.model' => 'missingmodel'], 'parent.model']; yield [['model' => 'missingmodel'], 'model'];
yield [['parent.id' => -1], 'parent.id']; yield [['id' => -1], 'model'];
yield [['parent.collection_name' => 'missingcollection'], 'parent.collection_name']; yield [['collection' => 'missingcollection'], 'collection'];
yield [['payload.content' => []], 'payload.content']; yield [['payload' => ['name' => 'AAA', 'content' => []]], 'payload.content'];
yield [['payload.content' => ['UU']], 'payload.content']; yield [['payload' => ['name' => 'AAA', 'content' => ['UU']]], 'payload.content'];
yield [['payload.content' => null], 'payload.content']; yield [['payload' => ['name' => 'AAA', 'content' => null]], 'payload.content'];
yield [['payload.content' => ''], 'payload.content']; yield [['payload' => ['name' => 'AAA', 'content' => '']], 'payload.content'];
yield [['payload.content' => 1], 'payload.content']; yield [['payload' => ['name' => 'AAA', 'content' => 1]], 'payload.content'];
yield [['payload.name' => ''], 'payload.name']; yield [['payload' => ['name' => '', 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload.name' => ['U']], 'payload.name']; yield [['payload' => ['name' => ['U'], 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload.name' => 1], 'payload.name']; yield [['payload' => ['name' => 1, 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload.name' => null], 'payload.name']; yield [['payload' => ['name' => null, 'content' => 'aaadfdf']], 'payload.name'];
yield [['payload' => 'lalal'], 'payload']; yield [['payload' => 'lalal'], 'payload'];
yield [['payload' => 55], 'payload']; yield [['payload' => 55], 'payload'];
}); });
test('it needs validation for multiple files', function (array $payloadOverwrites, string $invalidFieldName) { test('it needs validation for multiple files', function (array $payload, string $invalidFieldName) {
test()->auth()->registerModel(); $this->auth()->registerModel();
$post = test()->newPost(); $post = $this->newPost();
$payload = [ $response = $this->postJson('/mediaupload', [
'parent' => ['model' => 'post', 'collection_name' => 'images', 'id' => $post->id], 'model' => 'post',
'id' => $post->id,
'collection' => 'images',
'payload' => [ 'payload' => [
[ [
'content' => base64_encode(test()->pdfFile()->getContent()), 'content' => base64_encode($this->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
], ],
], ],
]; ...$payload,
]);
foreach ($payloadOverwrites as $key => $value) {
data_set($payload, $key, $value);
}
$response = test()->postJson('/mediaupload', $payload);
$response->assertStatus(422); $response->assertStatus(422);
$response->assertJsonValidationErrors($invalidFieldName); $response->assertJsonValidationErrors($invalidFieldName);
})->with(function () { })->with(function () {
yield [['parent.model' => 'missingmodel'], 'parent.model']; yield [['model' => 'missingmodel'], 'model'];
yield [['parent.model' => 'post.missingcollection'], 'parent.model']; yield [['id' => -1], 'model'];
yield [['parent.id' => -1], 'parent.id']; yield [['collection' => 'missingcollection'], 'collection'];
yield [['payload' => 'lalal'], 'payload']; yield [['payload' => 'lalal'], 'payload'];
yield [['payload' => []], 'payload']; yield [['payload' => []], 'payload'];
yield [['payload' => 1], 'payload']; yield [['payload' => 1], 'payload'];
yield [['payload' => [['name' => 'AAA', 'content' => []]]], 'payload.0.content']; yield [['payload' => [['name' => 'AAA', 'content' => []]]], 'payload.0.content'];
yield [['payload' => [['name' => 'AAA', 'content' => ['UU']]]], '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' => null]]], 'payload.0.content'];

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Workbench\App\Models; namespace Zoomyboy\MedialibraryHelper\Tests\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
@ -8,22 +8,19 @@ 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 Workbench\App\Events\MediaChange; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Workbench\App\Events\MediaDestroyed; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed;
use Workbench\App\Events\MediaStored; use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored;
use Zoomyboy\MedialibraryHelper\DefersUploads;
class Post extends Model implements HasMedia class Post extends Model implements HasMedia
{ {
use InteractsWithMedia; use InteractsWithMedia;
use DefersUploads;
public $guarded = []; public $guarded = [];
public function registerMediaCollections(): void public function registerMediaCollections(): void
{ {
$this->addMediaCollection('defaultSingleFile')->maxWidth(fn () => 250)->singleFile(); $this->addMediaCollection('defaultSingleFile')->maxWidth(fn () => 250)->singleFile();
$this->addMediaCollection('singleJpegFile')->convert(fn ($extension) => 'jpg')->singleFile();
$this->addMediaCollection('conversionsWithDefault') $this->addMediaCollection('conversionsWithDefault')
->singleFile() ->singleFile()
@ -59,7 +56,7 @@ class Post extends Model implements HasMedia
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',

View File

@ -1,11 +1,7 @@
<?php <?php
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Workbench\App\Models\Post;
use Workbench\App\Policies\PostPolicy;
uses(Zoomyboy\MedialibraryHelper\Tests\TestCase::class)->in('Feature'); uses(Zoomyboy\MedialibraryHelper\Tests\TestCase::class)->in('Feature');
uses(Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature'); uses(Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
uses()->beforeEach(fn () => Storage::fake('media') && Storage::fake('temp'))->in('Feature'); uses()->beforeEach(fn () => Storage::fake('media'))->in('Feature');
uses()->beforeEach(fn () => Gate::policy(Post::class, PostPolicy::class))->in('Feature');

View File

@ -4,15 +4,30 @@ namespace Zoomyboy\MedialibraryHelper\Tests;
use Illuminate\Http\File; use Illuminate\Http\File;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Orchestra\Testbench\Concerns\WithWorkbench;
use Orchestra\Testbench\TestCase as BaseTestCase; use Orchestra\Testbench\TestCase as BaseTestCase;
use Workbench\App\Models\Post; use Spatie\LaravelData\LaravelDataServiceProvider;
use Workbench\App\Models\User; use Spatie\MediaLibrary\MediaLibraryServiceProvider;
use Zoomyboy\MedialibraryHelper\ServiceProvider;
use Zoomyboy\MedialibraryHelper\Tests\Models\Post;
class TestCase extends BaseTestCase class TestCase extends BaseTestCase
{ {
/**
* Define database migrations.
*/
protected function defineDatabaseMigrations(): void
{
$this->loadMigrationsFrom(__DIR__ . '/migrations');
}
use WithWorkbench; protected function getPackageProviders($app): array
{
return [
ServiceProvider::class,
MediaLibraryServiceProvider::class,
LaravelDataServiceProvider::class,
];
}
/** /**
* Generate a pdf file with a filename and get path. * Generate a pdf file with a filename and get path.
@ -27,11 +42,6 @@ class TestCase extends BaseTestCase
return $this->getFile('jpg.jpg', $filename ?: 'jpg.jpg'); return $this->getFile('jpg.jpg', $filename ?: 'jpg.jpg');
} }
protected function pngFile(?string $filename = null): File
{
return $this->getFile('png.png', $filename ?: 'png.png');
}
protected function getFile(string $location, string $as): File protected function getFile(string $location, string $as): File
{ {
$path = __DIR__ . '/stubs/' . $location; $path = __DIR__ . '/stubs/' . $location;
@ -55,8 +65,23 @@ class TestCase extends BaseTestCase
protected function auth(array $policies = []): self protected function auth(array $policies = []): self
{ {
$this->be(User::factory()->policies($policies)->create()); $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; return $this;
} }
protected function defineEnvironment($app)
{
$app['config']->set('media-library.middleware', ['web']);
}
} }

BIN
tests/stubs/heic.jpg Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View File

@ -1,17 +0,0 @@
<?php
namespace Workbench\App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasFactory;
public $guarded = [];
public $casts = [
'policies' => 'json',
];
}

View File

@ -1,56 +0,0 @@
<?php
namespace Workbench\App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use Spatie\MediaLibrary\HasMedia;
use Workbench\App\Models\User;
class PostPolicy
{
use HandlesAuthorization;
public function listMedia(User $user, HasMedia $model): bool
{
if (is_bool($user->policies['listMedia'])) {
return $user->policies['listMedia'] === true;
}
return data_get($user->policies, 'listMedia.id') === $model->id
&& data_get($user->policies, 'listMedia.collection') === $collection;
}
public function storeMedia(User $user, ?HasMedia $model, ?string $collection = null): bool
{
if (is_bool($user->policies['storeMedia'])) {
return $user->policies['storeMedia'] === true;
}
if (is_null($model)) {
return data_get($user->policies, 'storeMedia.collection') === $collection;
}
return data_get($user->policies, 'storeMedia.id') === $model->id
&& data_get($user->policies, 'storeMedia.collection') === $collection;
}
public function updateMedia(User $user, HasMedia $model, string $collection): bool
{
if (is_bool($user->policies['updateMedia'])) {
return $user->policies['updateMedia'] === true;
}
return data_get($user->policies, 'updateMedia.id') === $model->id
&& data_get($user->policies, 'updateMedia.collection') === $collection;
}
public function destroyMedia(User $user, HasMedia $model, string $collection): bool
{
if (is_bool($user->policies['destroyMedia'])) {
return $user->policies['destroyMedia'] === true;
}
return data_get($user->policies, 'destroyMedia.id') === $model->id
&& data_get($user->policies, 'destroyMedia.collection') === $collection;
}
}

View File

@ -1,41 +0,0 @@
<?php
namespace Database\Factories\Models;
use Workbench\App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
/**
* @extends Factory<User>
*/
class UserFactory extends Factory
{
protected $model = User::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'email' => $this->faker->safeEmail,
'password' => Hash::make('password'),
'name' => $this->faker->firstName,
'policies' => [],
];
}
public function policies(array $policies): self
{
return $this->state(['policies' => [
'storeMedia' => true,
'updateMedia' => true,
'destroyMedia' => true,
'listMedia' => true,
...$policies,
]]);
}
}

View File

@ -1,20 +0,0 @@
<?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('users', function (Blueprint $table) {
$table->id();
$table->string('email');
$table->string('name');
$table->string('password');
$table->json('policies');
$table->timestamps();
});
}
};