updated to media management
This commit is contained in:
@@ -38,3 +38,20 @@ function arrayLimitKeys(array $arr, array $keys): array
|
|||||||
{
|
{
|
||||||
return array_intersect_key($arr, array_flip($keys));
|
return array_intersect_key($arr, array_flip($keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array value or default value if it does not exist
|
||||||
|
*
|
||||||
|
* @param string $key The key value to return if exists.
|
||||||
|
* @param array $arr The array to check.
|
||||||
|
* @param mixed $value The value to return if key does not exist.
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function arrayDefaultValue(string $key, array $arr, mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (array_key_exists($key, $arr) === true) {
|
||||||
|
return $arr[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|||||||
27
app/Helpers/TypeValue.php
Normal file
27
app/Helpers/TypeValue.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/* Type Value Helper Functions */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is value true
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check.
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function isTrue(mixed $value): bool
|
||||||
|
{
|
||||||
|
if (is_bool($value) === true && $value === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($value) === true && intval($value) === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value) === true && in_array(strtolower($value), ['true', '1'], true) === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -6,9 +6,10 @@ use App\Conductors\MediaConductor;
|
|||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Requests\MediaRequest;
|
use App\Http\Requests\MediaRequest;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Laravel\Sanctum\PersonalAccessToken;
|
use Laravel\Sanctum\PersonalAccessToken;
|
||||||
|
|
||||||
class MediaController extends ApiController
|
class MediaController extends ApiController
|
||||||
@@ -67,45 +68,62 @@ class MediaController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function store(MediaRequest $request)
|
public function store(MediaRequest $request)
|
||||||
{
|
{
|
||||||
if (MediaConductor::creatable() === true) {
|
if (MediaConductor::creatable() === false) {
|
||||||
$file = $request->file('file');
|
return $this->respondForbidden();
|
||||||
if ($file === null) {
|
}
|
||||||
return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
|
|
||||||
|
$file = $request->file('file');
|
||||||
|
if ($file === null) {
|
||||||
|
return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$jsonResult = $this->validateFileItem($file);
|
||||||
|
if ($jsonResult !== null) {
|
||||||
|
return $jsonResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->merge([
|
||||||
|
'title' => $request->get('title', ''),
|
||||||
|
'name' => '',
|
||||||
|
'size' => $file->getSize(),
|
||||||
|
'mime_type' => $file->getMimeType(),
|
||||||
|
'status' => '',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// We store images by default locally
|
||||||
|
if ($request->get('storage') === null) {
|
||||||
|
if (strpos($file->getMimeType(), 'image/') === 0) {
|
||||||
|
$request->merge([
|
||||||
|
'storage' => 'local',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$request->merge([
|
||||||
|
'storage' => 'cdn',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($file->isValid() !== true) {
|
$mediaItem = $request->user()->media()->create($request->except(['file','transform']));
|
||||||
switch ($file->getError()) {
|
|
||||||
case UPLOAD_ERR_INI_SIZE:
|
|
||||||
case UPLOAD_ERR_FORM_SIZE:
|
|
||||||
return $this->respondTooLarge();
|
|
||||||
case UPLOAD_ERR_PARTIAL:
|
|
||||||
return $this->respondWithErrors(['file' => 'The file upload was interrupted.']);
|
|
||||||
default:
|
|
||||||
return $this->respondWithErrors(['file' => 'An error occurred uploading the file to the server.']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($file->getSize() > Media::getMaxUploadSize()) {
|
$temporaryFilePath = generateTempFilePath();
|
||||||
return $this->respondTooLarge();
|
copy($file->path(), $temporaryFilePath);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
$transformData = ['file' => [
|
||||||
$media = Media::createFromUploadedFile($request, $file);
|
'path' => $temporaryFilePath,
|
||||||
} catch (\Exception $e) {
|
'size' => $file->getSize(),
|
||||||
if ($e->getCode() === Media::FILE_SIZE_EXCEEDED_ERROR) {
|
'mime_type' => $file->getMimeType(),
|
||||||
return $this->respondTooLarge();
|
]
|
||||||
} else {
|
];
|
||||||
return $this->respondWithErrors(['file' => $e->getMessage()]);
|
if ($request->has('transform') === true) {
|
||||||
}
|
$transformData = array_merge($transformData, array_map('trim', explode(',', $request->get('transform'))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondAsResource(
|
$mediaItem->transform($transformData);
|
||||||
MediaConductor::model($request, $media),
|
|
||||||
['respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
|
||||||
);
|
|
||||||
}//end if
|
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondAsResource(
|
||||||
|
MediaConductor::model($request, $mediaItem),
|
||||||
|
['respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,43 +135,42 @@ class MediaController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function update(MediaRequest $request, Media $medium)
|
public function update(MediaRequest $request, Media $medium)
|
||||||
{
|
{
|
||||||
if (MediaConductor::updatable($medium) === true) {
|
if (MediaConductor::updatable($medium) === false) {
|
||||||
$file = $request->file('file');
|
return $this->respondForbidden();
|
||||||
if ($file !== null) {
|
}
|
||||||
if ($file->isValid() !== true) {
|
|
||||||
switch ($file->getError()) {
|
|
||||||
case UPLOAD_ERR_INI_SIZE:
|
|
||||||
case UPLOAD_ERR_FORM_SIZE:
|
|
||||||
return $this->respondTooLarge();
|
|
||||||
case UPLOAD_ERR_PARTIAL:
|
|
||||||
return $this->respondWithErrors(['file' => 'The file upload was interrupted.']);
|
|
||||||
default:
|
|
||||||
return $this->respondWithErrors(['file' => 'An error occurred uploading the file to the server.']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($file->getSize() > Media::getMaxUploadSize()) {
|
$file = $request->file('file');
|
||||||
return $this->respondTooLarge();
|
if ($file !== null) {
|
||||||
}
|
$jsonResult = $this->validateFileItem($file);
|
||||||
|
if ($jsonResult !== null) {
|
||||||
|
return $jsonResult;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$medium->update($request->all());
|
$medium->update($request->except(['file','transform']));
|
||||||
|
|
||||||
if ($file !== null) {
|
$transformData = [];
|
||||||
try {
|
if ($file !== null) {
|
||||||
$medium->updateWithUploadedFile($file);
|
$temporaryFilePath = generateTempFilePath();
|
||||||
} catch (\Exception $e) {
|
copy($file->path(), $temporaryFilePath);
|
||||||
return $this->respondWithErrors(
|
|
||||||
['file' => $e->getMessage()],
|
|
||||||
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
$transformData = array_merge($transformData, ['file' => [
|
||||||
}//end if
|
'path' => $temporaryFilePath,
|
||||||
|
'size' => $file->getSize(),
|
||||||
|
'mime_type' => $file->getMimeType(),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->respondForbidden();
|
if ($request->has('transform') === true) {
|
||||||
|
$transformData = array_merge($transformData, array_map('trim', explode(',', $request->get('transform'))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($transformData) > 0) {
|
||||||
|
$medium->transform($transformData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -251,4 +268,32 @@ class MediaController extends ApiController
|
|||||||
|
|
||||||
return response()->file($path, $headers);
|
return response()->file($path, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a File item in a request is valid
|
||||||
|
*
|
||||||
|
* @param UploadedFile $file The file to validate.
|
||||||
|
* @param string $errorKey The error key to use.
|
||||||
|
* @return JsonResponse|null
|
||||||
|
*/
|
||||||
|
private function validateFileItem(UploadedFile $file, string $errorKey = 'file'): JsonResponse|null
|
||||||
|
{
|
||||||
|
if ($file->isValid() !== true) {
|
||||||
|
switch ($file->getError()) {
|
||||||
|
case UPLOAD_ERR_INI_SIZE:
|
||||||
|
case UPLOAD_ERR_FORM_SIZE:
|
||||||
|
return $this->respondTooLarge();
|
||||||
|
case UPLOAD_ERR_PARTIAL:
|
||||||
|
return $this->respondWithErrors([$errorKey => 'The file upload was interrupted.']);
|
||||||
|
default:
|
||||||
|
return $this->respondWithErrors([$errorKey => 'An error occurred uploading the file to the server.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||||
|
return $this->respondTooLarge();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
224
app/Jobs/MediaJob.php
Normal file
224
app/Jobs/MediaJob.php
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use FFMpeg;
|
||||||
|
use FFMpeg\Coordinate\Dimension;
|
||||||
|
use Intervention\Image\Facades\Image;
|
||||||
|
|
||||||
|
class MediaJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media item
|
||||||
|
*
|
||||||
|
* @var Media
|
||||||
|
*/
|
||||||
|
protected $media;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to make on the Media
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $actions;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param Media $media The media model.
|
||||||
|
* @param array $actions The media actions to make.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Media $media, array $actions)
|
||||||
|
{
|
||||||
|
$this->media = $media;
|
||||||
|
$this->actions = $actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// FILE
|
||||||
|
if (array_key_exists("file", $this->actions) === true) {
|
||||||
|
$uploadData = $this->actions["file"];
|
||||||
|
|
||||||
|
if (array_key_exists("path", $uploadData) === false || file_exists($uploadData["path"]) === false) {
|
||||||
|
$this->media->error("Upload file does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = $uploadData["path"];
|
||||||
|
|
||||||
|
// convert HEIC files to JPG
|
||||||
|
$fileExtension = File::extension($filePath);
|
||||||
|
if ($fileExtension === 'heic') {
|
||||||
|
// Get the path without the file name
|
||||||
|
$uploadedFileDirectory = dirname($filePath);
|
||||||
|
|
||||||
|
// Convert the HEIC file to JPG
|
||||||
|
$jpgFileName = pathinfo($filePath, PATHINFO_FILENAME) . '.jpg';
|
||||||
|
$jpgFilePath = $uploadedFileDirectory . '/' . $jpgFileName;
|
||||||
|
if (file_exists($jpgFilePath) === true) {
|
||||||
|
$this->media->error("File already exists in storage");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::make($filePath)->save($jpgFilePath);
|
||||||
|
|
||||||
|
// Update the uploaded file path and file name
|
||||||
|
unlink($filePath);
|
||||||
|
$filePath = $jpgFilePath;
|
||||||
|
$this->media->name = $jpgFileName;
|
||||||
|
$this->media->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file already exists
|
||||||
|
if (Storage::disk($this->media->storage)->exists($this->media->name) === true) {
|
||||||
|
if (array_key_exists('replace', $uploadData) === false || isTrue($uploadData['replace']) === false) {
|
||||||
|
$errorStr = "cannot upload file {$this->media->storage} " . // phpcs:ignore
|
||||||
|
"/ {$this->media->name} as it already exists";
|
||||||
|
Log::info($errorStr);
|
||||||
|
throw new \Exception($errorStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->media->setStagingFile($filePath);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
$this->media->createStagingFile();
|
||||||
|
$this->media->deleteFile();
|
||||||
|
|
||||||
|
// Modifications
|
||||||
|
if (strpos($this->media->mime_type, 'image/') === 0) {
|
||||||
|
$image = Image::make($filePath);
|
||||||
|
|
||||||
|
// ROTATE
|
||||||
|
if (array_key_exists("rotate", $this->actions) === true) {
|
||||||
|
$rotate = intval($this->actions["rotate"]);
|
||||||
|
if ($rotate !== 0) {
|
||||||
|
$image = $image->rotate($rotate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FLIP-H/V
|
||||||
|
if (array_key_exists('flip', $this->actions) === true) {
|
||||||
|
if (stripos($this->actions['flip'], 'h') !== false) {
|
||||||
|
$image = $image->flip('h');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stripos($this->actions['flip'], 'v') !== false) {
|
||||||
|
$image = $image->flip('v');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CROP
|
||||||
|
if (array_key_exists("crop", $this->actions) === true) {
|
||||||
|
$cropData = $this->actions["crop"];
|
||||||
|
$width = intval(arrayDefaultValue("width", $cropData, $image->getWidth()));
|
||||||
|
$height = intval(arrayDefaultValue("height", $cropData, $image->getHeight()));
|
||||||
|
$x = intval(arrayDefaultValue("x", $cropData, 0));
|
||||||
|
$y = intval(arrayDefaultValue("y", $cropData, 0));
|
||||||
|
|
||||||
|
$image = $image->crop($width, $height, $x, $y);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
$image->save($filePath);
|
||||||
|
} elseif (strpos($this->media->mime_type, 'video/') === 0) {
|
||||||
|
$ffmpeg = FFMpeg\FFMpeg::create();
|
||||||
|
$video = $ffmpeg->open($this->media->getStagingFilePath());
|
||||||
|
|
||||||
|
/** @var FFMpeg\Media\Video::filters */
|
||||||
|
$filters = $video->filters();
|
||||||
|
|
||||||
|
// ROTATE
|
||||||
|
if (array_key_exists("rotate", $this->actions) === true) {
|
||||||
|
$rotate = intval($this->actions["rotate"]);
|
||||||
|
$rotate = (($rotate % 360 + 360) % 360); // remove excess rotations
|
||||||
|
$rotate = (round($rotate / 90) * 90); // round to nearest 90%
|
||||||
|
|
||||||
|
if ($rotate > 0) {
|
||||||
|
if ($rotate === 90) {
|
||||||
|
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_90);
|
||||||
|
} elseif ($rotate === 190) {
|
||||||
|
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_180);
|
||||||
|
} elseif ($rotate === 270) {
|
||||||
|
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_270);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FLIP-H/V
|
||||||
|
if (array_key_exists('flip', $this->actions) === true) {
|
||||||
|
if (stripos($this->actions['flip'], 'h') !== false) {
|
||||||
|
$filters->hflip()->synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stripos($this->actions['flip'], 'v') !== false) {
|
||||||
|
$filters->vflip()->synchronize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CROP
|
||||||
|
if (array_key_exists("crop", $this->actions) === true) {
|
||||||
|
$cropData = $this->actions["crop"];
|
||||||
|
$videoStream = $video->getStreams()->videos()->first();
|
||||||
|
|
||||||
|
$width = intval(arrayDefaultValue("width", $cropData, $videoStream->get('width')));
|
||||||
|
$height = intval(arrayDefaultValue("height", $cropData, $videoStream->get('height')));
|
||||||
|
$x = intval(arrayDefaultValue("x", $cropData, 0));
|
||||||
|
$y = intval(arrayDefaultValue("y", $cropData, 0));
|
||||||
|
|
||||||
|
$cropDimension = new Dimension($width, $height);
|
||||||
|
|
||||||
|
$filters->crop($cropDimension, $x, $y)->synchronize();
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
$tempFilePath = tempnam(sys_get_temp_dir(), 'video-');
|
||||||
|
$video->save(null, $tempFilePath);
|
||||||
|
$this->media->changeStagingFile($tempFilePath);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
// Move file
|
||||||
|
if (array_key_exists("move", $this->actions) === true) {
|
||||||
|
if (array_key_exists("storage", $this->actions["move"]) === true) {
|
||||||
|
$newStorage = $this->actions["move"]["storage"];
|
||||||
|
if ($this->media->storage !== $newStorage) {
|
||||||
|
if (Storage::has($newStorage) === true) {
|
||||||
|
$this->media->storage = $newStorage;
|
||||||
|
} else {
|
||||||
|
$this->media->error("Cannot move file to '{$newStorage}' as it does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish media object
|
||||||
|
$this->media->saveStagingFile();
|
||||||
|
$this->media->ok();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
$this->media->error("Failed");
|
||||||
|
$this->fail($e);
|
||||||
|
}//end try
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,8 @@ class MoveMediaJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ class StoreUploadedFileJob implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
protected $replaceExisting;
|
protected $replaceExisting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifications to make on the Media
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $modifications;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
@@ -55,13 +62,15 @@ class StoreUploadedFileJob implements ShouldQueue
|
|||||||
* @param Media $media The media model.
|
* @param Media $media The media model.
|
||||||
* @param string $filePath The uploaded file.
|
* @param string $filePath The uploaded file.
|
||||||
* @param boolean $replaceExisting Replace existing files.
|
* @param boolean $replaceExisting Replace existing files.
|
||||||
|
* @param array $modifications The modifications to make on the media.
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(Media $media, string $filePath, bool $replaceExisting = true)
|
public function __construct(Media $media, string $filePath, bool $replaceExisting = true, array $modifications = [])
|
||||||
{
|
{
|
||||||
$this->media = $media;
|
$this->media = $media;
|
||||||
$this->uploadedFilePath = $filePath;
|
$this->uploadedFilePath = $filePath;
|
||||||
$this->replaceExisting = $replaceExisting;
|
$this->replaceExisting = $replaceExisting;
|
||||||
|
$this->modifications = $modifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Jobs\MediaJob;
|
||||||
use App\Jobs\MoveMediaJob;
|
use App\Jobs\MoveMediaJob;
|
||||||
use App\Jobs\StoreUploadedFileJob;
|
use App\Jobs\StoreUploadedFileJob;
|
||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
@@ -18,7 +21,13 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Intervention\Image\Exception\NotSupportedException;
|
||||||
|
use Intervention\Image\Exception\NotWritableException;
|
||||||
|
use ImagickException;
|
||||||
use Intervention\Image\Facades\Image;
|
use Intervention\Image\Facades\Image;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Psr\Container\NotFoundExceptionInterface;
|
||||||
|
use Psr\Container\ContainerExceptionInterface;
|
||||||
use SplFileInfo;
|
use SplFileInfo;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
@@ -82,13 +91,12 @@ class Media extends Model
|
|||||||
protected static $storageFileListCache = [];
|
protected static $storageFileListCache = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The variant types.
|
* Object variant details.
|
||||||
*
|
*
|
||||||
* @var int[][][]
|
* @var int[][][]
|
||||||
*/
|
*/
|
||||||
protected static $variantTypes = [
|
protected static $objectVariants = [
|
||||||
'image' => [
|
'image' => [
|
||||||
'thumb' => ['width' => 150, 'height' => 150],
|
|
||||||
'small' => ['width' => 300, 'height' => 225],
|
'small' => ['width' => 300, 'height' => 225],
|
||||||
'medium' => ['width' => 768, 'height' => 576],
|
'medium' => ['width' => 768, 'height' => 576],
|
||||||
'large' => ['width' => 1024, 'height' => 768],
|
'large' => ['width' => 1024, 'height' => 768],
|
||||||
@@ -98,6 +106,13 @@ class Media extends Model
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Staging file path of asset for processing.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $stagingFilePath = "";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model Boot
|
* Model Boot
|
||||||
@@ -138,15 +153,15 @@ class Media extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Type Variants.
|
* Get Object Variants.
|
||||||
*
|
*
|
||||||
* @param string $type The variant type to get.
|
* @param string $type The variant object to get.
|
||||||
* @return array The variant data.
|
* @return array The variant data.
|
||||||
*/
|
*/
|
||||||
public static function getTypeVariants(string $type): array
|
public static function getObjectVariants(string $type): array
|
||||||
{
|
{
|
||||||
if (isset(self::$variantTypes[$type]) === true) {
|
if (isset(self::$objectVariants[$type]) === true) {
|
||||||
return self::$variantTypes[$type];
|
return self::$objectVariants[$type];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
@@ -191,11 +206,11 @@ class Media extends Model
|
|||||||
*/
|
*/
|
||||||
public function getPreviousVariant(string $type, string $variant): string
|
public function getPreviousVariant(string $type, string $variant): string
|
||||||
{
|
{
|
||||||
if (isset(self::$variantTypes[$type]) === false) {
|
if (isset(self::$objectVariants[$type]) === false) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$variants = self::$variantTypes[$type];
|
$variants = self::$objectVariants[$type];
|
||||||
$keys = array_keys($variants);
|
$keys = array_keys($variants);
|
||||||
|
|
||||||
$currentIndex = array_search($variant, $keys);
|
$currentIndex = array_search($variant, $keys);
|
||||||
@@ -215,11 +230,11 @@ class Media extends Model
|
|||||||
*/
|
*/
|
||||||
public function getNextVariant(string $type, string $variant): string
|
public function getNextVariant(string $type, string $variant): string
|
||||||
{
|
{
|
||||||
if (isset(self::$variantTypes[$type]) === false) {
|
if (isset(self::$objectVariants[$type]) === false) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$variants = self::$variantTypes[$type];
|
$variants = self::$objectVariants[$type];
|
||||||
$keys = array_keys($variants);
|
$keys = array_keys($variants);
|
||||||
|
|
||||||
$currentIndex = array_search($variant, $keys);
|
$currentIndex = array_search($variant, $keys);
|
||||||
@@ -265,18 +280,12 @@ class Media extends Model
|
|||||||
*/
|
*/
|
||||||
public function deleteFile(): void
|
public function deleteFile(): void
|
||||||
{
|
{
|
||||||
$fileName = $this->name;
|
if (strlen($this->storage) > 0 && strlen($this->name) > 0 && Storage::disk($this->storage)->exists($this->name) === true) {
|
||||||
$baseName = pathinfo($fileName, PATHINFO_FILENAME);
|
Storage::disk($this->storage)->delete($this->name);
|
||||||
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
|
||||||
|
|
||||||
$files = Storage::disk($this->storage)->files();
|
|
||||||
|
|
||||||
foreach ($files as $file) {
|
|
||||||
if (preg_match("/{$baseName}(-[a-zA-Z0-9]+)?\.{$extension}/", $file) === 1) {
|
|
||||||
Storage::disk($this->storage)->delete($file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->deleteThumbnail();
|
||||||
|
$this->deleteVariants();
|
||||||
$this->invalidateCFCache();
|
$this->invalidateCFCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,96 +372,37 @@ class Media extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new Media from UploadedFile data.
|
* Transform the media through the Media Job Queue
|
||||||
*
|
*
|
||||||
* @param App\Models\Request $request The request data.
|
* @param array $transform The transform data.
|
||||||
* @param Illuminate\Http\UploadedFile $file The file.
|
* @return void
|
||||||
* @return null|Media The result or null if not successful.
|
|
||||||
*/
|
*/
|
||||||
public static function createFromUploadedFile(Request $request, UploadedFile $file): ?Media
|
public function transform(array $transform): void
|
||||||
{
|
{
|
||||||
$request->merge([
|
foreach ($transform as $key => $value) {
|
||||||
'title' => $request->get('title', ''),
|
if (is_string($value) === true) {
|
||||||
'name' => '',
|
if (preg_match('/^rotate-(-?\d+)$/', $value, $matches) !== false) {
|
||||||
'size' => 0,
|
unset($transform[$key]);
|
||||||
'mime_type' => '',
|
$transform['rotate'] = $matches[1];
|
||||||
'status' => '',
|
} elseif (preg_match('/^flip-([vh]|vh|hv)$/', $value, $matches) !== false) {
|
||||||
]);
|
unset($transform[$key]);
|
||||||
|
$transform['flip'] = $matches[1];
|
||||||
if ($request->get('storage') === null) {
|
} elseif (preg_match('/^crop-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||||
// We store images by default locally
|
unset($transform[$key]);
|
||||||
if (strpos($file->getMimeType(), 'image/') === 0) {
|
$transform['crop'] = ['width' => $matches[1], 'height' => $matches[2]];
|
||||||
$request->merge([
|
} elseif (preg_match('/^crop-(\d+)-(\d+)-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||||
'storage' => 'local',
|
unset($transform[$key]);
|
||||||
]);
|
$transform['crop'] = ['width' => $matches[1], 'height' => $matches[2], 'x' => $matches[3], 'y' => $matches[4]];
|
||||||
} else {
|
}
|
||||||
$request->merge([
|
|
||||||
'storage' => 'cdn',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$mediaItem = $request->user()->media()->create($request->all());
|
|
||||||
$mediaItem->updateWithUploadedFile($file);
|
|
||||||
|
|
||||||
return $mediaItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update Media with UploadedFile data.
|
|
||||||
*
|
|
||||||
* @param Illuminate\Http\UploadedFile $file The file.
|
|
||||||
* @return null|Media The media item.
|
|
||||||
*/
|
|
||||||
public function updateWithUploadedFile(UploadedFile $file): ?Media
|
|
||||||
{
|
|
||||||
if ($file === null || $file->isValid() !== true) {
|
|
||||||
throw new \Exception('The file is invalid.', self::INVALID_FILE_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($file->getSize() > static::getMaxUploadSize()) {
|
|
||||||
throw new \Exception('The file size is larger then permitted.', self::FILE_SIZE_EXCEEDED_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = static::generateUniqueFileName($file->getClientOriginalName());
|
|
||||||
if ($name === false) {
|
|
||||||
throw new \Exception('The file name already exists in storage.', self::FILE_NAME_EXISTS_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove file if there is an existing entry in this medium item
|
|
||||||
if (strlen($this->name) > 0 && strlen($this->storage) > 0) {
|
|
||||||
Storage::disk($this->storage)->delete($this->name);
|
|
||||||
foreach ($this->variants as $variantName => $fileName) {
|
|
||||||
Storage::disk($this->storage)->delete($fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->name = '';
|
|
||||||
$this->variants = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen($this->title) === 0) {
|
|
||||||
$this->title = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->name = $name;
|
|
||||||
$this->size = $file->getSize();
|
|
||||||
$this->mime_type = $file->getMimeType();
|
|
||||||
$this->status = 'Processing media';
|
|
||||||
$this->save();
|
|
||||||
|
|
||||||
$temporaryFilePath = generateTempFilePath();
|
|
||||||
copy($file->path(), $temporaryFilePath);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
StoreUploadedFileJob::dispatch($this, $temporaryFilePath)->onQueue('media');
|
MediaJob::dispatch($this, $transform)->onQueue('media');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->status = 'Error';
|
$this->error('Failed to transform media');
|
||||||
$this->save();
|
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}//end try
|
}//end try
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -625,7 +575,7 @@ class Media extends Model
|
|||||||
*/
|
*/
|
||||||
public static function fileNameHasSuffix(string $fileName): bool
|
public static function fileNameHasSuffix(string $fileName): bool
|
||||||
{
|
{
|
||||||
$suffix = '/(-\d+x\d+|-scaled)$/i';
|
$suffix = '/(-\d+x\d+|-scaled|-thumb)$/i';
|
||||||
$fileNameWithoutExtension = pathinfo($fileName, PATHINFO_FILENAME);
|
$fileNameWithoutExtension = pathinfo($fileName, PATHINFO_FILENAME);
|
||||||
|
|
||||||
return preg_match($suffix, $fileNameWithoutExtension) === 1;
|
return preg_match($suffix, $fileNameWithoutExtension) === 1;
|
||||||
@@ -700,31 +650,124 @@ class Media extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download temporary copy of the storage file.
|
* Get the Staging File path.
|
||||||
*
|
*
|
||||||
* @return string File path
|
* @param boolean $create Create staging file if doesn't exist.
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function downloadTempFile(): string
|
public function getStagingFilePath(bool $create = true): string
|
||||||
{
|
{
|
||||||
$readStream = Storage::disk($this->storageDisk)->readStream($this->name);
|
if ($this->stagingFilePath === "" && $create === true) {
|
||||||
$filePath = tempnam(sys_get_temp_dir(), 'download-');
|
$this->createStagingFile();
|
||||||
$writeStream = fopen($filePath, 'w');
|
|
||||||
while (feof($readStream) !== true) {
|
|
||||||
fwrite($writeStream, fread($readStream, 8192));
|
|
||||||
}
|
}
|
||||||
fclose($readStream);
|
|
||||||
fclose($writeStream);
|
|
||||||
|
|
||||||
return $filePath;
|
return $this->stagingFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Staging File for processing.
|
||||||
|
*
|
||||||
|
* @param string $path The path if the new staging file.
|
||||||
|
* @param boolean $overwrite Overwrite existing file.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setStagingFile(string $path, bool $overwrite = false): void
|
||||||
|
{
|
||||||
|
if ($this->stagingFilePath !== "") {
|
||||||
|
if ($overwrite === true) {
|
||||||
|
unlink($this->stagingFilePath);
|
||||||
|
} else {
|
||||||
|
// ignore request
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stagingFilePath = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download temporary copy of the storage file for staging.
|
||||||
|
*
|
||||||
|
* @return boolean If download was successful.
|
||||||
|
*/
|
||||||
|
public function createStagingFile(): bool
|
||||||
|
{
|
||||||
|
if ($this->stagingFilePath !== "") {
|
||||||
|
$readStream = Storage::disk($this->storageDisk)->readStream($this->name);
|
||||||
|
$filePath = tempnam(sys_get_temp_dir(), 'download-');
|
||||||
|
$writeStream = fopen($filePath, 'w');
|
||||||
|
while (feof($readStream) !== true) {
|
||||||
|
fwrite($writeStream, fread($readStream, 8192));
|
||||||
|
}
|
||||||
|
fclose($readStream);
|
||||||
|
fclose($writeStream);
|
||||||
|
|
||||||
|
$this->stagingFilePath = $filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stagingFilePath !== "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the Staging File to storage
|
||||||
|
*
|
||||||
|
* @param boolean $delete Delete the existing staging file.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function saveStagingFile(bool $delete = true): void
|
||||||
|
{
|
||||||
|
if (strlen($this->storage) > 0 && strlen($this->name) > 0) {
|
||||||
|
if (Storage::disk($this->storage)->exists($this->name) === true) {
|
||||||
|
Storage::disk($this->storage)->delete($this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Illuminate\Filesystem\FilesystemAdapter */
|
||||||
|
$fileSystem = Storage::disk($this->storage);
|
||||||
|
$fileSystem->putFileAs('/', $this->stagingFilePath, $this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->generateThumbnail();
|
||||||
|
$this->generateVariants();
|
||||||
|
|
||||||
|
if ($delete === true) {
|
||||||
|
$this->deleteStagingFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up temporary file.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteStagingFile(): void
|
||||||
|
{
|
||||||
|
if ($this->stagingFilePath !== "") {
|
||||||
|
unlink($this->stagingFilePath);
|
||||||
|
$this->stagingFilePath = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change staging file, removing the old file if present
|
||||||
|
*
|
||||||
|
* @param string $newFile The new staging file.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function changeStagingFile(string $newFile): void
|
||||||
|
{
|
||||||
|
if ($this->stagingFilePath !== "") {
|
||||||
|
unlink($this->stagingFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stagingFilePath = $newFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a Thumbnail for this media.
|
* Generate a Thumbnail for this media.
|
||||||
* @param string $uploadedFilePath The local file, if present (else download from storage).
|
|
||||||
*
|
*
|
||||||
* @return boolean If generation was successful.
|
* @return boolean If generation was successful.
|
||||||
*/
|
*/
|
||||||
public function generateThumbnail(string $uploadedFilePath = ""): bool
|
public function generateThumbnail(): bool
|
||||||
{
|
{
|
||||||
$thumbnailWidth = 200;
|
$thumbnailWidth = 200;
|
||||||
$thumbnailHeight = 200;
|
$thumbnailHeight = 200;
|
||||||
@@ -737,11 +780,7 @@ class Media extends Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// download original from CDN if no local file
|
$filePath = $this->createStagingFile();
|
||||||
$filePath = $uploadedFilePath;
|
|
||||||
if ($uploadedFilePath === "") {
|
|
||||||
$filePath = $this->downloadTempFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
$fileExtension = File::extension($this->name);
|
$fileExtension = File::extension($this->name);
|
||||||
$tempImagePath = tempnam(sys_get_temp_dir(), 'thumb');
|
$tempImagePath = tempnam(sys_get_temp_dir(), 'thumb');
|
||||||
@@ -832,22 +871,35 @@ class Media extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate variants for this media.
|
* Delete Media Thumbnail from storage.
|
||||||
* @param string $uploadedFilePath The local file, if present (else download from storage).
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function generateVariants(string $uploadedFilePath = ""): void
|
public function deleteThumbnail(): void
|
||||||
|
{
|
||||||
|
if (strlen($this->thumbnail) > 0) {
|
||||||
|
$path = substr($this->thumbnail, strlen($this->getUrlPath()));
|
||||||
|
|
||||||
|
if (strlen($path) > 0 && Storage::disk($this->storageDisk)->exists($path) === true) {
|
||||||
|
Storage::disk($this->storageDisk)->delete($path);
|
||||||
|
$this->thumbnail = ''; // Clear the thumbnail property
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate variants for this media.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function generateVariants(): void
|
||||||
{
|
{
|
||||||
if (strpos($this->media->mime_type, 'image/') === 0) {
|
if (strpos($this->media->mime_type, 'image/') === 0) {
|
||||||
// Generate additional image sizes
|
// Generate additional image sizes
|
||||||
$sizes = Media::getTypeVariants('image');
|
$sizes = Media::getObjectVariants('image');
|
||||||
|
|
||||||
// download original from CDN if no local file
|
// download original from CDN if no local file
|
||||||
$filePath = $uploadedFilePath;
|
$filePath = $this->createStagingFile();
|
||||||
if ($uploadedFilePath === "") {
|
|
||||||
$filePath = $this->downloadTempFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete existing variants
|
// delete existing variants
|
||||||
if (is_array($this->variants) === true) {
|
if (is_array($this->variants) === true) {
|
||||||
@@ -924,4 +976,53 @@ class Media extends Model
|
|||||||
$this->variants = $variants;
|
$this->variants = $variants;
|
||||||
}//end if
|
}//end if
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the Media variants from storage.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteVariants(): void
|
||||||
|
{
|
||||||
|
if (strlen($this->name) > 0 && strlen($this->storage) > 0) {
|
||||||
|
foreach ($this->variants as $variantName => $fileName) {
|
||||||
|
Storage::disk($this->storage)->delete($fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->variants = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Media status to OK
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function ok(): void
|
||||||
|
{
|
||||||
|
$this->status = "OK";
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Media status to an error
|
||||||
|
* @param string $error The error to set.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function error(string $error = ""): void
|
||||||
|
{
|
||||||
|
$this->status = "Error" . ($error !== "" ? ": {$error}" : "");
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Media status
|
||||||
|
* @param string $status The status to set.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function status(string $status = ""): void
|
||||||
|
{
|
||||||
|
$this->status = "Info: " . $status;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
"autoload": {
|
"autoload": {
|
||||||
"files": [
|
"files": [
|
||||||
"app/Helpers/Array.php",
|
"app/Helpers/Array.php",
|
||||||
"app/Helpers/Temp.php"
|
"app/Helpers/Temp.php",
|
||||||
|
"app/Helpers/TypeValue.php"
|
||||||
],
|
],
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
|
|||||||
@@ -234,7 +234,7 @@
|
|||||||
<img
|
<img
|
||||||
:src="mediaGetThumbnail(lastSelected)"
|
:src="mediaGetThumbnail(lastSelected)"
|
||||||
class="max-h-20 max-w-20 mr-2" />
|
class="max-h-20 max-w-20 mr-2" />
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col w-100">
|
||||||
<p class="m-0 text-bold">
|
<p class="m-0 text-bold">
|
||||||
{{ lastSelected.title }}
|
{{ lastSelected.title }}
|
||||||
</p>
|
</p>
|
||||||
@@ -260,13 +260,49 @@
|
|||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
v-if="allowEditSelected"
|
v-if="allowEditSelected"
|
||||||
class="m-0 italic text-red-6 small cursor-pointer hover:underline">
|
class="flex gap-1">
|
||||||
<span
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 cursor-pointer text-gray-6 hover:text-gray-4"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
@click="
|
||||||
|
handleRotateLeft(
|
||||||
|
lastSelected,
|
||||||
|
)
|
||||||
|
">
|
||||||
|
<title>Rotate Left</title>
|
||||||
|
<path
|
||||||
|
d="M4 11C4 6.58 7.58 3 12 3L13 3.06V5.08L12 5C8.69 5 6 7.69 6 11H9L5 15L1 11H4M17 7H13C11.9 7 11 7.9 11 9V18C11 19.11 11.9 20 13 20H19C20.11 20 21 19.11 21 18V11L17 7M19 18H13V9H16V12H19V18Z"
|
||||||
|
fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 cursor-pointer text-gray-6 hover:text-gray-4"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
@click="
|
||||||
|
handleRotateRight(
|
||||||
|
lastSelected,
|
||||||
|
)
|
||||||
|
">
|
||||||
|
<title>Rotate Right</title>
|
||||||
|
<path
|
||||||
|
d="M20 11H23L19 15L15 11H18C18 7.69 15.31 5 12 5L11 5.08V3.06L12 3C16.42 3 20 6.58 20 11M9 7H5C3.9 7 3 7.9 3 9V18C3 19.11 3.9 20 5 20H11C12.11 20 13 19.11 13 18V11L9 7M11 18H5V9H8V12H11V18Z"
|
||||||
|
fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 cursor-pointer text-red-6 hover:text-red-4 ml-auto"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
@click="
|
@click="
|
||||||
handleDelete(lastSelected)
|
handleDelete(lastSelected)
|
||||||
"
|
">
|
||||||
>Delete Permanently</span
|
<title>
|
||||||
>
|
Delete Permanently
|
||||||
|
</title>
|
||||||
|
<path
|
||||||
|
d="M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8,9H16V19H8V9M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z"
|
||||||
|
fill="currentColor" />
|
||||||
|
</svg>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1133,6 +1169,60 @@ const handleUpdate = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRotateLeft = async (item: Media) => {
|
||||||
|
api.put({
|
||||||
|
url: "/media/{id}",
|
||||||
|
params: {
|
||||||
|
id: item.id,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
transform: "rotate-270",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
if (result.data) {
|
||||||
|
const data = result.data as MediaResponse;
|
||||||
|
const index = mediaItems.value.findIndex(
|
||||||
|
(mediaItem) => mediaItem.id === item.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
mediaItems.value[index] = data.medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRotateRight = async (item: Media) => {
|
||||||
|
api.put({
|
||||||
|
url: "/media/{id}",
|
||||||
|
params: {
|
||||||
|
id: item.id,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
transform: "rotate-90",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
if (result.data) {
|
||||||
|
const data = result.data as MediaResponse;
|
||||||
|
const index = mediaItems.value.findIndex(
|
||||||
|
(mediaItem) => mediaItem.id === item.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
mediaItems.value[index] = data.medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = async (item: Media) => {
|
const handleDelete = async (item: Media) => {
|
||||||
let result = await openDialog(SMDialogConfirm, {
|
let result = await openDialog(SMDialogConfirm, {
|
||||||
title: "Delete File?",
|
title: "Delete File?",
|
||||||
|
|||||||
Reference in New Issue
Block a user