diff --git a/app/Helpers/Array.php b/app/Helpers/Array.php
index 17a169b..5c7ac8c 100644
--- a/app/Helpers/Array.php
+++ b/app/Helpers/Array.php
@@ -38,3 +38,20 @@ function arrayLimitKeys(array $arr, array $keys): array
{
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;
+}
diff --git a/app/Helpers/TypeValue.php b/app/Helpers/TypeValue.php
new file mode 100644
index 0000000..fac3573
--- /dev/null
+++ b/app/Helpers/TypeValue.php
@@ -0,0 +1,27 @@
+file('file');
- if ($file === null) {
- return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
+ if (MediaConductor::creatable() === false) {
+ return $this->respondForbidden();
+ }
+
+ $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) {
- 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.']);
- }
- }
+ $mediaItem = $request->user()->media()->create($request->except(['file','transform']));
- if ($file->getSize() > Media::getMaxUploadSize()) {
- return $this->respondTooLarge();
- }
+ $temporaryFilePath = generateTempFilePath();
+ copy($file->path(), $temporaryFilePath);
- try {
- $media = Media::createFromUploadedFile($request, $file);
- } catch (\Exception $e) {
- if ($e->getCode() === Media::FILE_SIZE_EXCEEDED_ERROR) {
- return $this->respondTooLarge();
- } else {
- return $this->respondWithErrors(['file' => $e->getMessage()]);
- }
- }
+ $transformData = ['file' => [
+ 'path' => $temporaryFilePath,
+ 'size' => $file->getSize(),
+ 'mime_type' => $file->getMimeType(),
+ ]
+ ];
+ if ($request->has('transform') === true) {
+ $transformData = array_merge($transformData, array_map('trim', explode(',', $request->get('transform'))));
+ }
- return $this->respondAsResource(
- MediaConductor::model($request, $media),
- ['respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
- );
- }//end if
+ $mediaItem->transform($transformData);
- 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)
{
- if (MediaConductor::updatable($medium) === true) {
- $file = $request->file('file');
- 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 (MediaConductor::updatable($medium) === false) {
+ return $this->respondForbidden();
+ }
- if ($file->getSize() > Media::getMaxUploadSize()) {
- return $this->respondTooLarge();
- }
+ $file = $request->file('file');
+ 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) {
- try {
- $medium->updateWithUploadedFile($file);
- } catch (\Exception $e) {
- return $this->respondWithErrors(
- ['file' => $e->getMessage()],
- HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
- );
- }
- }
+ $transformData = [];
+ if ($file !== null) {
+ $temporaryFilePath = generateTempFilePath();
+ copy($file->path(), $temporaryFilePath);
- return $this->respondAsResource(MediaConductor::model($request, $medium));
- }//end if
+ $transformData = array_merge($transformData, ['file' => [
+ '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);
}
+
+ /**
+ * 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;
+ }
}
diff --git a/app/Jobs/MediaJob.php b/app/Jobs/MediaJob.php
new file mode 100644
index 0000000..c3e2453
--- /dev/null
+++ b/app/Jobs/MediaJob.php
@@ -0,0 +1,224 @@
+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
+ }
+}
diff --git a/app/Jobs/MoveMediaJob.php b/app/Jobs/MoveMediaJob.php
index 9c33b57..9221cd7 100644
--- a/app/Jobs/MoveMediaJob.php
+++ b/app/Jobs/MoveMediaJob.php
@@ -48,6 +48,8 @@ class MoveMediaJob implements ShouldQueue
/**
* Execute the job.
+ *
+ * @return void
*/
public function handle(): void
{
diff --git a/app/Jobs/StoreUploadedFileJob.php b/app/Jobs/StoreUploadedFileJob.php
index 3796133..ef114a6 100644
--- a/app/Jobs/StoreUploadedFileJob.php
+++ b/app/Jobs/StoreUploadedFileJob.php
@@ -48,6 +48,13 @@ class StoreUploadedFileJob implements ShouldQueue
*/
protected $replaceExisting;
+ /**
+ * Modifications to make on the Media
+ *
+ * @var array
+ */
+ protected $modifications;
+
/**
* Create a new job instance.
@@ -55,13 +62,15 @@ class StoreUploadedFileJob implements ShouldQueue
* @param Media $media The media model.
* @param string $filePath The uploaded file.
* @param boolean $replaceExisting Replace existing files.
+ * @param array $modifications The modifications to make on the media.
* @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->uploadedFilePath = $filePath;
$this->replaceExisting = $replaceExisting;
+ $this->modifications = $modifications;
}
/**
diff --git a/app/Models/Media.php b/app/Models/Media.php
index 7bbed9d..3df33c8 100644
--- a/app/Models/Media.php
+++ b/app/Models/Media.php
@@ -3,11 +3,14 @@
namespace App\Models;
use App\Enum\HttpResponseCodes;
+use App\Jobs\MediaJob;
use App\Jobs\MoveMediaJob;
use App\Jobs\StoreUploadedFileJob;
use App\Traits\Uuids;
+use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\InvalidCastException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Bus\DispatchesJobs;
@@ -18,7 +21,13 @@ use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
+use Intervention\Image\Exception\NotSupportedException;
+use Intervention\Image\Exception\NotWritableException;
+use ImagickException;
use Intervention\Image\Facades\Image;
+use InvalidArgumentException;
+use Psr\Container\NotFoundExceptionInterface;
+use Psr\Container\ContainerExceptionInterface;
use SplFileInfo;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -82,13 +91,12 @@ class Media extends Model
protected static $storageFileListCache = [];
/**
- * The variant types.
+ * Object variant details.
*
* @var int[][][]
*/
- protected static $variantTypes = [
+ protected static $objectVariants = [
'image' => [
- 'thumb' => ['width' => 150, 'height' => 150],
'small' => ['width' => 300, 'height' => 225],
'medium' => ['width' => 768, 'height' => 576],
'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
@@ -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.
*/
- public static function getTypeVariants(string $type): array
+ public static function getObjectVariants(string $type): array
{
- if (isset(self::$variantTypes[$type]) === true) {
- return self::$variantTypes[$type];
+ if (isset(self::$objectVariants[$type]) === true) {
+ return self::$objectVariants[$type];
}
return [];
@@ -191,11 +206,11 @@ class Media extends Model
*/
public function getPreviousVariant(string $type, string $variant): string
{
- if (isset(self::$variantTypes[$type]) === false) {
+ if (isset(self::$objectVariants[$type]) === false) {
return '';
}
- $variants = self::$variantTypes[$type];
+ $variants = self::$objectVariants[$type];
$keys = array_keys($variants);
$currentIndex = array_search($variant, $keys);
@@ -215,11 +230,11 @@ class Media extends Model
*/
public function getNextVariant(string $type, string $variant): string
{
- if (isset(self::$variantTypes[$type]) === false) {
+ if (isset(self::$objectVariants[$type]) === false) {
return '';
}
- $variants = self::$variantTypes[$type];
+ $variants = self::$objectVariants[$type];
$keys = array_keys($variants);
$currentIndex = array_search($variant, $keys);
@@ -265,18 +280,12 @@ class Media extends Model
*/
public function deleteFile(): void
{
- $fileName = $this->name;
- $baseName = pathinfo($fileName, PATHINFO_FILENAME);
- $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);
- }
+ if (strlen($this->storage) > 0 && strlen($this->name) > 0 && Storage::disk($this->storage)->exists($this->name) === true) {
+ Storage::disk($this->storage)->delete($this->name);
}
+ $this->deleteThumbnail();
+ $this->deleteVariants();
$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 Illuminate\Http\UploadedFile $file The file.
- * @return null|Media The result or null if not successful.
+ * @param array $transform The transform data.
+ * @return void
*/
- public static function createFromUploadedFile(Request $request, UploadedFile $file): ?Media
+ public function transform(array $transform): void
{
- $request->merge([
- 'title' => $request->get('title', ''),
- 'name' => '',
- 'size' => 0,
- 'mime_type' => '',
- 'status' => '',
- ]);
-
- if ($request->get('storage') === null) {
- // We store images by default locally
- if (strpos($file->getMimeType(), 'image/') === 0) {
- $request->merge([
- 'storage' => 'local',
- ]);
- } else {
- $request->merge([
- 'storage' => 'cdn',
- ]);
+ foreach ($transform as $key => $value) {
+ if (is_string($value) === true) {
+ if (preg_match('/^rotate-(-?\d+)$/', $value, $matches) !== false) {
+ unset($transform[$key]);
+ $transform['rotate'] = $matches[1];
+ } elseif (preg_match('/^flip-([vh]|vh|hv)$/', $value, $matches) !== false) {
+ unset($transform[$key]);
+ $transform['flip'] = $matches[1];
+ } elseif (preg_match('/^crop-(\d+)-(\d+)$/', $value, $matches) !== false) {
+ unset($transform[$key]);
+ $transform['crop'] = ['width' => $matches[1], 'height' => $matches[2]];
+ } elseif (preg_match('/^crop-(\d+)-(\d+)-(\d+)-(\d+)$/', $value, $matches) !== false) {
+ unset($transform[$key]);
+ $transform['crop'] = ['width' => $matches[1], 'height' => $matches[2], 'x' => $matches[3], 'y' => $matches[4]];
+ }
}
}
- $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 {
- StoreUploadedFileJob::dispatch($this, $temporaryFilePath)->onQueue('media');
+ MediaJob::dispatch($this, $transform)->onQueue('media');
} catch (\Exception $e) {
- $this->status = 'Error';
- $this->save();
-
+ $this->error('Failed to transform media');
throw $e;
}//end try
-
- return $this;
}
/**
@@ -625,7 +575,7 @@ class Media extends Model
*/
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);
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);
- $filePath = tempnam(sys_get_temp_dir(), 'download-');
- $writeStream = fopen($filePath, 'w');
- while (feof($readStream) !== true) {
- fwrite($writeStream, fread($readStream, 8192));
+ if ($this->stagingFilePath === "" && $create === true) {
+ $this->createStagingFile();
}
- 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.
- * @param string $uploadedFilePath The local file, if present (else download from storage).
*
* @return boolean If generation was successful.
*/
- public function generateThumbnail(string $uploadedFilePath = ""): bool
+ public function generateThumbnail(): bool
{
$thumbnailWidth = 200;
$thumbnailHeight = 200;
@@ -737,11 +780,7 @@ class Media extends Model
}
}
- // download original from CDN if no local file
- $filePath = $uploadedFilePath;
- if ($uploadedFilePath === "") {
- $filePath = $this->downloadTempFile();
- }
+ $filePath = $this->createStagingFile();
$fileExtension = File::extension($this->name);
$tempImagePath = tempnam(sys_get_temp_dir(), 'thumb');
@@ -832,22 +871,35 @@ class Media extends Model
}
/**
- * Generate variants for this media.
- * @param string $uploadedFilePath The local file, if present (else download from storage).
+ * Delete Media Thumbnail from storage.
*
* @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) {
// Generate additional image sizes
- $sizes = Media::getTypeVariants('image');
+ $sizes = Media::getObjectVariants('image');
// download original from CDN if no local file
- $filePath = $uploadedFilePath;
- if ($uploadedFilePath === "") {
- $filePath = $this->downloadTempFile();
- }
+ $filePath = $this->createStagingFile();
// delete existing variants
if (is_array($this->variants) === true) {
@@ -924,4 +976,53 @@ class Media extends Model
$this->variants = $variants;
}//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();
+ }
}
diff --git a/composer.json b/composer.json
index db59d18..3d59874 100644
--- a/composer.json
+++ b/composer.json
@@ -36,7 +36,8 @@
"autoload": {
"files": [
"app/Helpers/Array.php",
- "app/Helpers/Temp.php"
+ "app/Helpers/Temp.php",
+ "app/Helpers/TypeValue.php"
],
"psr-4": {
"App\\": "app/",
diff --git a/resources/js/components/dialogs/SMDialogMedia.vue b/resources/js/components/dialogs/SMDialogMedia.vue
index ffe6af9..16bc8e5 100644
--- a/resources/js/components/dialogs/SMDialogMedia.vue
+++ b/resources/js/components/dialogs/SMDialogMedia.vue
@@ -234,7 +234,7 @@
-
{{ lastSelected.title }}
@@ -260,13 +260,49 @@- + + + + "> +