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
41 changed files with 4767 additions and 2174 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:
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))
@ -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",
"autoload": {
"psr-4": {
"Zoomyboy\\MedialibraryHelper\\Tests\\": "tests/",
"Zoomyboy\\MedialibraryHelper\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Zoomyboy\\MedialibraryHelper\\Tests\\": "tests/",
"Workbench\\App\\": "tests/workbench/app/",
"Database\\Factories\\": "tests/workbench/database/factories/"
}
},
"authors": [
{
"name": "Philipp Lang",
@ -16,19 +22,31 @@
}
],
"require": {
"spatie/laravel-medialibrary": "^10.7",
"laravel/framework": "^9.50",
"spatie/laravel-data": "^3.1",
"pestphp/pest": "^1.22"
"spatie/laravel-medialibrary": "^11.0",
"laravel/framework": "^11.0",
"spatie/laravel-data": "^4.0",
"pestphp/pest": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6",
"orchestra/testbench": "^7.0",
"illuminate/console": "^9.2"
"orchestra/testbench": "^9.0",
"illuminate/console": "^11.0"
},
"scripts": {
"post-autoload-dump": [
"@clear",
"@prepare",
"@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": {

5216
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"?>
<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"
colors="true"
>
@ -9,11 +9,6 @@
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<env name="APP_ENV" value="testing"/>
@ -27,4 +22,9 @@
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
<source>
<include>
<directory suffix=".php">./src</directory>
</include>
</source>
</phpunit>

View File

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

@ -5,15 +5,17 @@ namespace Zoomyboy\MedialibraryHelper;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Spatie\Image\Image;
use Spatie\LaravelData\DataCollection;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidBase64Data;
use Spatie\MediaLibrary\MediaCollections\FileAdder;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Zoomyboy\MedialibraryHelper\Rules\ModelRule;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\File\File;
class MediaController
{
@ -22,48 +24,83 @@ class MediaController
public function store(Request $request)
{
$request->validate([
'name' => 'string',
'model' => ['required', 'string', Rule::in(app('media-library-helpers')->keys())],
'parent' => ['required', new ModelRule()],
]);
$model = $this->validateModel($request);
$collection = $model->getMediaCollection($request->input('collection'));
if (is_null($request->input('parent.id'))) {
return $this->storeDeferred($request);
}
$model = ModelRule::getModel($request->input('parent'));
$collection = ModelRule::getCollection($request->input('parent'));
$isSingle = 1 === $collection->collectionSizeLimit;
$this->authorize('storeMedia', [$model, $collection->name]);
$request->validate($isSingle ? [
'payload' => 'array',
'payload.*' => '',
'payload.name' => 'required|string|max:255',
'payload.name' => 'required|string|regex:/\..*$/|max:255',
'payload.content' => 'required|string',
] : [
'payload' => 'required|array|min:1',
'payload.*' => 'array',
'payload.*.name' => 'string',
'payload.*.content' => 'string',
'payload.*.name' => 'required|string|regex:/\..*$/|max:255',
'payload.*.content' => 'required|string',
]);
$content = $isSingle ? [$request->input('payload')] : $request->input('payload');
$medias = collect($content)->map(function ($c) use ($collection, $model) {
$pathinfo = pathinfo($c['name']);
$basename = $collection->runCallback('forceFileName', $model, $pathinfo['filename']);
$path = $basename . '.' . $pathinfo['extension'];
$file = new MediaFile($c['name']);
$file->setBasename($collection->runCallback('forceFileName', $model, $file->getBasename()));
$file->setExtension(MediaFile::extensionFromData($c['content']));
$file->setExtension($collection->runCallback('convert', $file->getExtension()));
$adder = $this->fileAdderFromData($model, $c['content'], $collection)
->usingName($basename)
->usingFileName($path)
->withCustomProperties($collection->runCallback('withDefaultProperties', $path));
->usingName($file->getBasename())
->usingFileName($file->getFilename())
->withCustomProperties($collection->runCallback('withDefaultProperties', $file->getFilename()));
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)
);
});
$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
@ -81,7 +118,7 @@ class MediaController
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 = $model::find($parentId);
@ -97,7 +134,7 @@ class MediaController
return $isSingle
? 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
@ -117,27 +154,7 @@ class MediaController
return property_exists($collection, $callback);
}
protected function validateModel(Request $request): HasMedia
{
$model = app('media-library-helpers')->get($request->input('model'));
$request->validate([
'collection' => [
'required',
'string',
Rule::in((new $model())->getRegisteredMediaCollections()->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
private function storeTemporaryFile(string $data, MediaCollection $collection): string
{
$maxWidth = $collection->runCallback('maxWidth', 9);
if (str_contains($data, ';base64')) {
@ -157,13 +174,24 @@ class MediaController
throw InvalidBase64Data::create();
}
$tmpFile = tempnam(sys_get_temp_dir(), 'media-library');
file_put_contents($tmpFile, $binaryData);
$tmpFile = 'media-library/' . str()->uuid()->toString() . '.' . $collection->runCallback('convert', MediaFile::extensionFromData($data));
Storage::disk(config('media-library.temp_disk'))->put($tmpFile, $binaryData);
if (null !== $maxWidth && 'image/jpeg' === mime_content_type($tmpFile)) {
Image::load($tmpFile)->width($maxWidth)->save();
$imagePath = Storage::disk(config('media-library.temp_disk'))->path($tmpFile);
$image = Image::load($imagePath);
if (null !== $maxWidth && 'image/jpeg' === Storage::disk(config('media-library.temp_disk'))->mimeType($tmpFile)) {
$image->width($maxWidth);
}
return $model->addMedia($tmpFile);
$image->save();
return $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 $isDeferred = false;
public static function fromMedia(Media $media): self
{
$conversions = collect($media->getMediaConversionNames())->flip()->map(fn ($integer, $conversion) => $media->hasGeneratedConversion($conversion)
? ['original_url' => $media->getFullUrl($conversion)]
: null,
$conversions = collect($media->getMediaConversionNames())->flip()->map(
fn ($integer, $conversion) => $media->hasGeneratedConversion($conversion)
? ['original_url' => $media->getFullUrl($conversion)]
: null,
);
return self::withoutMagicalCreationFrom([
return self::factory()->withoutMagicalCreation()->from([
...$media->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) {
$media = Media::findOrFail($media);
return $media->model_id.'_'.$media->model_type;
return $media->model_id . '_' . $media->model_type;
})->unique()->count();
if (1 !== $mediaCount) {
@ -31,6 +31,6 @@ class OrderController
$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\Support\ServiceProvider as BaseServiceProvider;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
class ServiceProvider extends BaseServiceProvider
{
@ -11,6 +12,8 @@ class ServiceProvider extends BaseServiceProvider
{
app()->bind('media-library-helpers', fn () => collect([]));
app()->singleton(CollectionExtension::class, fn () => new CollectionExtension());
$this->mergeConfigFrom(__DIR__ . '/../config/media-library.php', 'media-library');
}
public function boot(): void
@ -23,7 +26,8 @@ class ServiceProvider extends BaseServiceProvider
$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;
use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaDestroyed;
use Workbench\App\Events\MediaChange;
use Workbench\App\Events\MediaDestroyed;
test('it deletes multiple media', function () {
$this->auth()->registerModel()->withoutExceptionHandling();

View File

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

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;
use Illuminate\Support\Facades\Event;
use Zoomyboy\MedialibraryHelper\Tests\Events\MediaChange;
use Workbench\App\Events\MediaChange;
test('it can reorder media', function () {
Event::fake();

View File

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

View File

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

View File

@ -1,7 +1,11 @@
<?php
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Workbench\App\Models\Post;
use Workbench\App\Policies\PostPolicy;
uses(Zoomyboy\MedialibraryHelper\Tests\TestCase::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\Support\Facades\Gate;
use Orchestra\Testbench\Concerns\WithWorkbench;
use Orchestra\Testbench\TestCase as BaseTestCase;
use Spatie\LaravelData\LaravelDataServiceProvider;
use Spatie\MediaLibrary\MediaLibraryServiceProvider;
use Zoomyboy\MedialibraryHelper\ServiceProvider;
use Zoomyboy\MedialibraryHelper\Tests\Models\Post;
use Workbench\App\Models\Post;
use Workbench\App\Models\User;
class TestCase extends BaseTestCase
{
/**
* Define database migrations.
*/
protected function defineDatabaseMigrations(): void
{
$this->loadMigrationsFrom(__DIR__ . '/migrations');
}
protected function getPackageProviders($app): array
{
return [
ServiceProvider::class,
MediaLibraryServiceProvider::class,
LaravelDataServiceProvider::class,
];
}
use WithWorkbench;
/**
* 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');
}
protected function pngFile(?string $filename = null): File
{
return $this->getFile('png.png', $filename ?: 'png.png');
}
protected function getFile(string $location, string $as): File
{
$path = __DIR__ . '/stubs/' . $location;
@ -65,23 +55,8 @@ class TestCase extends BaseTestCase
protected function auth(array $policies = []): self
{
$policies = [
'storeMedia' => true,
'updateMedia' => true,
'destroyMedia' => true,
'listMedia' => true,
...$policies,
];
foreach ($policies as $ability => $result) {
Gate::define($ability, fn (?string $user, string $collectionName) => $result);
}
$this->be(User::factory()->policies($policies)->create());
return $this;
}
protected function defineEnvironment($app)
{
$app['config']->set('media-library.middleware', ['web']);
}
}

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
namespace Zoomyboy\MedialibraryHelper\Tests\Events;
namespace Workbench\App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

View File

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

View File

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

View File

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

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();
});
}
};