Compare commits

..

29 Commits

Author SHA1 Message Date
philipp lang 424ca30932 Fix response codes 2024-11-13 13:50:41 +01:00
philipp lang 9870ac8bab Fix response code of multiple upload 2024-11-13 13:28:03 +01:00
philipp lang e9975e9c5a Fix media data 2024-11-13 13:19:00 +01:00
philipp lang 77cfe9bbc7 Add Laravel 11 Support 2024-09-21 22:45:44 +02:00
philipp lang 6d8e9de3d7 Update Pest PHP 2024-09-21 17:13:38 +02:00
philipp lang b7a617aa62 Update media-library version 2024-09-21 16:54:51 +02:00
philipp lang 59d7647720 Change format for upload 2024-07-13 19:49:31 +02:00
philipp lang a6c11cdc14 Add image conversion 2024-07-13 19:38:51 +02:00
philipp lang d2f7f16e88 Add slot for multiple buttons 2024-04-18 22:14:20 +02:00
philipp lang 3a7f558755 Add multipleFiles vue component 2024-04-14 00:24:50 +02:00
philipp lang 35593ba37b Lint 2024-04-14 00:24:40 +02:00
philipp lang 46d28877d9 Add storing for multiple deferred uploads 2024-04-13 22:30:45 +02:00
philipp lang 91e5cc3e3b Add icon path to deferred upload 2024-01-13 21:49:17 +01:00
philipp lang 3b8a129fa4 Add Single file upload component 2024-01-13 21:43:45 +01:00
philipp lang c4eb67c09f Add deferred uploads 2024-01-12 00:16:42 +01:00
philipp lang 5c05ab7c80 Lint 2024-01-11 23:43:19 +01:00
philipp lang 7c33160b13 Add multiple files deferred 2024-01-03 16:45:02 +01:00
philipp lang 8bf17784cc Add deferred upload 2024-01-03 15:47:41 +01:00
philipp lang c00ee87082 Add authroization with actual user 2024-01-03 13:42:29 +01:00
philipp lang 1b2b148af5 Add auth 2024-01-03 13:07:03 +01:00
philipp lang 14ca7d312f Add config path 2024-01-03 12:19:15 +01:00
philipp lang 17afb48028 Add Workbench config 2024-01-03 11:10:59 +01:00
philipp lang 22d7841d5a Add Model rule 2024-01-03 02:12:26 +01:00
philipp lang 79cb5a8f58 update validation 2024-01-03 00:48:50 +01:00
philipp lang 2e90fde134 Add MediaFile Helper 2024-01-03 00:47:05 +01:00
philipp lang 38c40c48ac Fix Readme 2024-01-03 00:09:55 +01:00
philipp lang 1aeb520b06 Update validation 2024-01-03 00:09:48 +01:00
philipp lang 78143ec0b3 Set protected 2024-01-02 23:48:41 +01:00
philipp lang 194f1f9c14 Move CollectionExtension 2024-01-02 23:47:48 +01:00
42 changed files with 3717 additions and 1912 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 filename. You should return the new name of the file with the extension (e.g. disc.jpg). 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).
``` ```
forceFileName(fn ($model, $path) => Str::slug($path)) forceFileName(fn ($model, $path) => Str::slug($path))
@ -25,4 +25,3 @@ You can call whatever you want after an image has been added, modified or delete
.... ....
}) })
``` ```

View File

@ -0,0 +1,14 @@
<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

@ -0,0 +1,105 @@
<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

@ -0,0 +1,6 @@
<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

@ -0,0 +1,117 @@
<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

@ -0,0 +1,35 @@
<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

@ -0,0 +1,77 @@
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

@ -5,10 +5,16 @@
"license": "MIT", "license": "MIT",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Zoomyboy\\MedialibraryHelper\\Tests\\": "tests/",
"Zoomyboy\\MedialibraryHelper\\": "src/" "Zoomyboy\\MedialibraryHelper\\": "src/"
} }
}, },
"autoload-dev": {
"psr-4": {
"Zoomyboy\\MedialibraryHelper\\Tests\\": "tests/",
"Workbench\\App\\": "tests/workbench/app/",
"Database\\Factories\\": "tests/workbench/database/factories/"
}
},
"authors": [ "authors": [
{ {
"name": "Philipp Lang", "name": "Philipp Lang",
@ -16,20 +22,31 @@
} }
], ],
"require": { "require": {
"ext-imagick": ">=3.6.0", "spatie/laravel-medialibrary": "^11.0",
"spatie/laravel-medialibrary": "^10.7", "laravel/framework": "^11.0",
"laravel/framework": "^9.50", "spatie/laravel-data": "^4.0",
"spatie/laravel-data": "^3.1", "pestphp/pest": "^3.0"
"pestphp/pest": "^1.22"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.6", "orchestra/testbench": "^9.0",
"orchestra/testbench": "^7.0", "illuminate/console": "^11.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": {

3891
composer.lock generated

File diff suppressed because it is too large Load Diff

6
config/media-library.php Normal file
View File

@ -0,0 +1,6 @@
<?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="./vendor/phpunit/phpunit/phpunit.xsd" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
colors="true" colors="true"
> >
@ -9,11 +9,6 @@
<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"/>
@ -27,4 +22,9 @@
<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,14 +2,8 @@
namespace Zoomyboy\MedialibraryHelper; namespace Zoomyboy\MedialibraryHelper;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
class CollectionExtension class CollectionExtension
{ {
public function boot(): void
{
MediaCollection::mixin(new class()
{
public function forceFileName() public function forceFileName()
{ {
return fn ($callback) => $this->registerCustomCallback('forceFileName', $callback); return fn ($callback) => $this->registerCustomCallback('forceFileName', $callback);
@ -25,6 +19,11 @@ class CollectionExtension
return fn ($callback) => $this->registerCustomCallback('destroyed', $callback); return fn ($callback) => $this->registerCustomCallback('destroyed', $callback);
} }
public function convert()
{
return fn ($callback) => $this->registerCustomCallback('convert', $callback);
}
public function after() public function after()
{ {
return fn ($callback) => $this->registerCustomCallback('after', $callback); return fn ($callback) => $this->registerCustomCallback('after', $callback);
@ -74,25 +73,25 @@ class CollectionExtension
}; };
} }
public function setDefaultCustomCallbacks() protected function setDefaultCustomCallbacks()
{ {
return function () { return function () {
if (property_exists($this, 'customCallbacks')) { if (property_exists($this, 'customCallbacks')) {
return; return;
} }
$this->convertTo = null;
$this->customCallbacks = collect([ $this->customCallbacks = collect([
'forceFileName' => fn ($model, $name) => $name, 'forceFileName' => fn ($model, $name) => $name,
'convert' => fn ($extension) => $extension,
'maxWidth' => fn ($size) => null, 'maxWidth' => fn ($size) => null,
'stored' => fn ($event) => true, 'stored' => fn ($event) => true,
'after' => fn ($event) => true, 'after' => fn ($event) => true,
'destroyed' => fn ($event) => true, 'destroyed' => fn ($event) => true,
'storing' => fn ($adder, $name) => $adder, 'storing' => fn ($adder, $name) => $adder,
'withDefaultProperties' => fn ($path, $pathinfo) => [], 'withDefaultProperties' => fn ($path) => [],
'withPropertyValidation' => fn ($path) => [], 'withPropertyValidation' => fn ($path) => [],
'withFallback' => fn ($parent) => null, 'withFallback' => fn ($parent) => null,
]); ]);
}; };
} }
});
}
} }

56
src/DeferredMediaData.php Normal file
View File

@ -0,0 +1,56 @@
<?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'));
}
}

45
src/DefersUploads.php Normal file
View File

@ -0,0 +1,45 @@
<?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,19 +2,20 @@
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\Validation\Rule; use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException; use Illuminate\Support\Collection;
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
{ {
@ -23,48 +24,83 @@ class MediaController
public function store(Request $request) public function store(Request $request)
{ {
$request->validate([ $request->validate([
'name' => 'string', 'parent' => ['required', new ModelRule()],
'model' => ['required', 'string', Rule::in(app('media-library-helpers')->keys())],
]); ]);
$model = $this->validateModel($request); if (is_null($request->input('parent.id'))) {
$collection = $model->getMediaCollection($request->input('collection')); return $this->storeDeferred($request);
}
$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.*' => '', 'payload.name' => 'required|string|regex:/\..*$/|max:255',
'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' => 'string', 'payload.*.name' => 'required|string|regex:/\..*$/|max:255',
'payload.*.content' => 'string', 'payload.*.content' => 'required|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) {
$pathinfo = pathinfo($c['name']); $file = new MediaFile($c['name']);
$basename = $collection->runCallback('forceFileName', $model, $pathinfo['filename']); $file->setBasename($collection->runCallback('forceFileName', $model, $file->getBasename()));
$path = $basename . '.' . $pathinfo['extension']; $file->setExtension(MediaFile::extensionFromData($c['content']));
$file->setExtension($collection->runCallback('convert', $file->getExtension()));
$adder = $this->fileAdderFromData($model, $c['content'], $collection) $adder = $this->fileAdderFromData($model, $c['content'], $collection)
->usingName($basename) ->usingName($file->getBasename())
->usingFileName($path) ->usingFileName($file->getFilename())
->withCustomProperties($collection->runCallback('withDefaultProperties', $path, $pathinfo)); ->withCustomProperties($collection->runCallback('withDefaultProperties', $file->getFilename()));
return tap( return tap(
$collection->runCallback('storing', $adder, $path)->toMediaCollection($collection->name), $collection->runCallback('storing', $adder, $file->getFilename())->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()) : MediaData::collection($medias); return $isSingle ? MediaData::from($medias->first()) : response(MediaData::collect($medias), 201);
}
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
@ -82,7 +118,7 @@ class MediaController
return MediaData::from($media); return MediaData::from($media);
} }
public function index(Request $request, $parentModel, int $parentId, string $collectionName): MediaData|DataCollection public function index(Request $request, $parentModel, int $parentId, string $collectionName): MediaData|JsonResponse
{ {
$model = app('media-library-helpers')->get($parentModel); $model = app('media-library-helpers')->get($parentModel);
$model = $model::find($parentId); $model = $model::find($parentId);
@ -98,7 +134,7 @@ class MediaController
return $isSingle return $isSingle
? MediaData::from($model->getFirstMedia($collectionName)) ? MediaData::from($model->getFirstMedia($collectionName))
: MediaData::collection($model->getMedia($collectionName)); : response()->json(MediaData::collect($model->getMedia($collectionName))->toArray());
} }
public function destroy(Media $media, Request $request): JsonResponse public function destroy(Media $media, Request $request): JsonResponse
@ -118,27 +154,7 @@ class MediaController
return property_exists($collection, $callback); return property_exists($collection, $callback);
} }
protected function validateModel(Request $request): HasMedia private function storeTemporaryFile(string $data, MediaCollection $collection): string
{
$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')) {
@ -158,21 +174,24 @@ class MediaController
throw InvalidBase64Data::create(); throw InvalidBase64Data::create();
} }
$tmpFile = tempnam(sys_get_temp_dir(), 'media-library'); $tmpFile = 'media-library/' . str()->uuid()->toString() . '.' . $collection->runCallback('convert', MediaFile::extensionFromData($data));
file_put_contents($tmpFile, $binaryData); Storage::disk(config('media-library.temp_disk'))->put($tmpFile, $binaryData);
$i = (new Imagick()); $imagePath = Storage::disk(config('media-library.temp_disk'))->path($tmpFile);
$i->readImage($tmpFile); $image = Image::load($imagePath);
if (null !== $maxWidth && 'image/jpeg' === Storage::disk(config('media-library.temp_disk'))->mimeType($tmpFile)) {
if ($i->getImageFormat() === 'HEIC') { $image->width($maxWidth);
$i->setFormat('jpg');
$i->writeImage($tmpFile);
} }
if (null !== $maxWidth && 'image/jpeg' === mime_content_type($tmpFile)) { $image->save();
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,14 +37,17 @@ 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(fn ($integer, $conversion) => $media->hasGeneratedConversion($conversion) $conversions = collect($media->getMediaConversionNames())->flip()->map(
fn ($integer, $conversion) => $media->hasGeneratedConversion($conversion)
? ['original_url' => $media->getFullUrl($conversion)] ? ['original_url' => $media->getFullUrl($conversion)]
: null, : null,
); );
return self::withoutMagicalCreationFrom([ return self::factory()->withoutMagicalCreation()->from([
...$media->toArray(), ...$media->toArray(),
'conversions' => $conversions->toArray(), 'conversions' => $conversions->toArray(),
]); ]);

51
src/MediaFile.php Normal file
View File

@ -0,0 +1,51 @@
<?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::collection($model->getMedia($collectionName)); return MediaData::collect($model->getMedia($collectionName))->toArray();
} }
} }

89
src/Rules/ModelRule.php Normal file
View File

@ -0,0 +1,89 @@
<?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,6 +4,7 @@ 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
{ {
@ -11,6 +12,8 @@ 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
@ -23,7 +26,8 @@ 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));
} }
/** /**

20
testbench.yaml Normal file
View File

@ -0,0 +1,20 @@
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

@ -0,0 +1,196 @@
<?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 Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange; use Workbench\App\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed; use Workbench\App\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,6 +7,7 @@ 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

@ -0,0 +1,24 @@
<?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 Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange; use Workbench\App\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 Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange; use Workbench\App\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,62 +4,65 @@ 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 Spatie\MediaLibrary\MediaCollections\Models\Media;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored; use Symfony\Component\Mime\MimeTypes;
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 () {
$this->auth()->registerModel(); test()->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->pdfFile()->getContent()); $content = base64_encode(test()->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id],
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.pdf',
], ],
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
$this->assertCount(1, $post->getMedia('defaultSingleFile')); test()->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.jpg'); $response->assertJsonPath('file_name', 'beispiel-bild.pdf');
$response->assertJsonPath('is_deferred', false);
$response->assertJsonMissingPath('model_type'); $response->assertJsonMissingPath('model_type');
$response->assertJsonMissingPath('model_id'); $response->assertJsonMissingPath('model_id');
}); });
test('it uploads heig image', function () { test('it changes format', function () {
$this->auth()->registerModel(); test()->auth()->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->getFile('heic.jpg', 'heic.jpg')->getContent()); $content = base64_encode(test()->pngFile()->getContent());
$this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'singleJpegFile', 'id' => $post->id],
'id' => $post->id,
'collection' => 'conversionsWithDefault',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.png',
], ],
])->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 () {
$this->auth()->registerModel(); test()->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->jpgFile()->getContent()); $content = base64_encode(test()->jpgFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id],
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -67,19 +70,17 @@ test('it uploads a single image to a single file collection', function () {
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
$this->assertCount(1, $post->getMedia('defaultSingleFile')); test()->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(); test()->auth()->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->pdfFile()->getContent()); $content = base64_encode(test()->jpgFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'singleForced', 'id' => $post->id],
'id' => $post->id,
'collection' => 'singleForced',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -87,20 +88,18 @@ test('it forces a filename for a single collection', function () {
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
$this->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('singleForced')->file_name); test()->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 () {
$this->auth()->registerModel(); test()->auth()->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->pdfFile()->getContent()); $content = base64_encode(test()->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'singleStoringHook', 'id' => $post->id],
'id' => $post->id,
'collection' => 'singleStoringHook',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -110,41 +109,37 @@ test('it sets custom title when storing', function () {
$response->assertStatus(201); $response->assertStatus(201);
$media = $post->getFirstMedia('singleStoringHook'); $media = $post->getFirstMedia('singleStoringHook');
$this->assertEquals('AAA', $media->getCustomProperty('use')); test()->assertEquals('AAA', $media->getCustomProperty('use'));
$this->assertEquals('beispiel bild', $media->getCustomProperty('ttt')); test()->assertEquals('beispiel bild', $media->getCustomProperty('ttt'));
}); });
test('it sets custom properties from properties method', function () { test('it sets custom properties from properties method', function () {
$this->auth()->registerModel(); test()->auth()->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->pdfFile()->getContent()); $content = base64_encode(test()->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'multipleProperties', 'id' => $post->id],
'id' => $post->id,
'collection' => 'multipleProperties',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.pdf',
], ],
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
$media = $post->getFirstMedia('multipleProperties'); $media = $post->getFirstMedia('multipleProperties');
$this->assertEquals('beispielBild.jpg', $media->getCustomProperty('test')); test()->assertEquals('beispielBild.pdf', $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'));
$this->auth()->registerModel(); test()->auth()->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->pdfFile()->getContent()); $content = base64_encode(test()->jpgFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'multipleForced', 'id' => $post->id],
'id' => $post->id,
'collection' => 'multipleForced',
'payload' => [ 'payload' => [
[ [
'content' => $content, 'content' => $content,
@ -154,20 +149,18 @@ test('it forces a filename for multiple collections', function () {
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
$this->assertEquals('beispiel-bild-2023-04-04.jpg', $post->getFirstMedia('multipleForced')->file_name); test()->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'));
$this->auth()->registerModel()->withoutExceptionHandling(); test()->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->pdfFile()->getContent()); $content = base64_encode(test()->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'singleWithEvent', 'id' => $post->id],
'id' => $post->id,
'collection' => 'singleWithEvent',
'payload' => [ 'payload' => [
'content' => $content, 'content' => $content,
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
@ -182,14 +175,12 @@ 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'));
$this->auth()->registerModel()->withoutExceptionHandling(); test()->auth()->registerModel()->withoutExceptionHandling();
$post = $this->newPost(); $post = test()->newPost();
$content = base64_encode($this->pdfFile()->getContent()); $content = base64_encode(test()->pdfFile()->getContent());
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'multipleFilesWithEvent', 'id' => $post->id],
'id' => $post->id,
'collection' => 'multipleFilesWithEvent',
'payload' => [ 'payload' => [
[ [
'content' => $content, 'content' => $content,
@ -208,120 +199,164 @@ test('it throws event when multiple files uploaded', function () {
}); });
test('it uploads multiple files', function () { test('it uploads multiple files', function () {
$this->auth()->registerModel(); test()->auth()->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$file = $this->pdfFile(); $file = test()->pdfFile();
$post->addMedia($file->getPathname())->preservingOriginal()->toMediaCollection('images'); $post->addMedia($file->getPathname())->preservingOriginal()->toMediaCollection('images');
$response = $this->postJson('/mediaupload', [ $response = test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'images', 'id' => $post->id],
'id' => $post->id,
'collection' => 'images',
'payload' => [ 'payload' => [
[ [
'content' => base64_encode($file->getContent()), 'content' => base64_encode($file->getContent()),
'name' => 'aaaa.jpg', 'name' => 'aaaa.pdf',
], ],
[ [
'content' => base64_encode($file->getContent()), 'content' => base64_encode($file->getContent()),
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.pdf',
], ],
], ],
]); ]);
$response->assertStatus(201); $response->assertStatus(201);
$this->assertCount(3, $post->getMedia('images')); test()->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();
$this->assertCount(2, $response->json()); test()->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', 3028); $response->assertJsonPath('0.size', 64576);
$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.jpg'); $response->assertJsonPath('0.file_name', 'aaaa.pdf');
$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 () {
$this->auth(['storeMedia' => false])->registerModel(); test()->auth(['storeMedia' => false])->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$response = $this->postJson('/mediaupload', [ test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id],
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => base64_encode($this->pdfFile()->getContent()), 'content' => base64_encode(test()->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
], ],
]); ])->assertStatus(403);
$response->assertStatus(403);
}); });
test('it needs validation for single files', function (array $payload, string $invalidFieldName) { test('it checks for model when running authorization', function () {
$this->auth()->registerModel(); $otherPost = test()->newPost();
$post = $this->newPost(); $post = test()->newPost();
test()->auth(['storeMedia' => ['id' => $post->id, 'collection' => 'defaultSingleFile']])->registerModel();
$response = $this->postJson('/mediaupload', [ test()->postJson('/mediaupload', [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'defaultSingleFile', 'id' => $post->id],
'id' => $post->id,
'collection' => 'defaultSingleFile',
'payload' => [ 'payload' => [
'content' => base64_encode($this->pdfFile()->getContent()), 'content' => base64_encode(test()->pdfFile()->getContent()),
'name' => 'beispiel bild.jpg', 'name' => 'beispiel bild.jpg',
], ],
...$payload, ])->assertStatus(201);
]);
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 [['model' => 'missingmodel'], 'model']; yield [['parent.model' => 'missingmodel'], 'parent.model'];
yield [['id' => -1], 'model']; yield [['parent.id' => -1], 'parent.id'];
yield [['collection' => 'missingcollection'], 'collection']; yield [['parent.collection_name' => 'missingcollection'], 'parent.collection_name'];
yield [['payload' => ['name' => 'AAA', 'content' => []]], 'payload.content']; yield [['payload.content' => []], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => ['UU']]], 'payload.content']; yield [['payload.content' => ['UU']], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => null]], 'payload.content']; yield [['payload.content' => null], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => '']], 'payload.content']; yield [['payload.content' => ''], 'payload.content'];
yield [['payload' => ['name' => 'AAA', 'content' => 1]], 'payload.content']; yield [['payload.content' => 1], 'payload.content'];
yield [['payload' => ['name' => '', 'content' => 'aaadfdf']], 'payload.name']; yield [['payload.name' => ''], 'payload.name'];
yield [['payload' => ['name' => ['U'], 'content' => 'aaadfdf']], 'payload.name']; yield [['payload.name' => ['U']], 'payload.name'];
yield [['payload' => ['name' => 1, 'content' => 'aaadfdf']], 'payload.name']; yield [['payload.name' => 1], 'payload.name'];
yield [['payload' => ['name' => null, 'content' => 'aaadfdf']], 'payload.name']; yield [['payload.name' => null], '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 $payload, string $invalidFieldName) { test('it needs validation for multiple files', function (array $payloadOverwrites, string $invalidFieldName) {
$this->auth()->registerModel(); test()->auth()->registerModel();
$post = $this->newPost(); $post = test()->newPost();
$response = $this->postJson('/mediaupload', [ $payload = [
'model' => 'post', 'parent' => ['model' => 'post', 'collection_name' => 'images', 'id' => $post->id],
'id' => $post->id,
'collection' => 'images',
'payload' => [ 'payload' => [
[ [
'content' => base64_encode($this->pdfFile()->getContent()), 'content' => base64_encode(test()->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 [['model' => 'missingmodel'], 'model']; yield [['parent.model' => 'missingmodel'], 'parent.model'];
yield [['id' => -1], 'model']; yield [['parent.model' => 'post.missingcollection'], 'parent.model'];
yield [['collection' => 'missingcollection'], 'collection']; yield [['parent.id' => -1], 'parent.id'];
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,7 +1,11 @@
<?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'))->in('Feature'); uses()->beforeEach(fn () => Storage::fake('media') && Storage::fake('temp'))->in('Feature');
uses()->beforeEach(fn () => Gate::policy(Post::class, PostPolicy::class))->in('Feature');

View File

@ -4,30 +4,15 @@ 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 Spatie\LaravelData\LaravelDataServiceProvider; use Workbench\App\Models\Post;
use Spatie\MediaLibrary\MediaLibraryServiceProvider; use Workbench\App\Models\User;
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');
}
protected function getPackageProviders($app): array use WithWorkbench;
{
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.
@ -42,6 +27,11 @@ 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;
@ -65,23 +55,8 @@ class TestCase extends BaseTestCase
protected function auth(array $policies = []): self protected function auth(array $policies = []): self
{ {
$policies = [ $this->be(User::factory()->policies($policies)->create());
'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']);
}
} }

Binary file not shown.

BIN
tests/stubs/png.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Zoomyboy\MedialibraryHelper\Tests\Events; namespace Workbench\App\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 Zoomyboy\MedialibraryHelper\Tests\Events; namespace Workbench\App\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 Zoomyboy\MedialibraryHelper\Tests\Events; namespace Workbench\App\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 Zoomyboy\MedialibraryHelper\Tests\Models; namespace Workbench\App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
@ -8,19 +8,22 @@ 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 Workbench\App\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed; use Workbench\App\Events\MediaDestroyed;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaStored; use Workbench\App\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()
@ -56,7 +59,7 @@ class Post extends Model implements HasMedia
Event::dispatch(new MediaStored($media)); Event::dispatch(new MediaStored($media));
}); });
$this->addMediaCollection('multipleProperties')->singleFile()->withDefaultProperties(fn ($path, $pathinfo) => [ $this->addMediaCollection('multipleProperties')->singleFile()->withDefaultProperties(fn ($path) => [
'test' => Str::camel($path), 'test' => Str::camel($path),
])->withPropertyValidation(fn ($path) => [ ])->withPropertyValidation(fn ($path) => [
'test' => 'string|max:10', 'test' => 'string|max:10',

View File

@ -0,0 +1,17 @@
<?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

@ -0,0 +1,56 @@
<?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

@ -0,0 +1,41 @@
<?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

@ -0,0 +1,20 @@
<?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();
});
}
};