ldc-dam/app/Http/Controllers/UploadController.php
2022-07-29 13:12:34 +02:00

348 lines
13 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Enums\MediaExtension;
use App\Http\Requests\Bo\Ajax\UploadMediaFileRequest;
use App\Lib\ToolboxHelper;
use App\Models\Copyright;
use App\Models\Dossier;
use App\Models\Etiquette;
use App\Models\Langue;
use App\Models\Media;
use App\Models\Site;
use App\Models\Sujet;
use App\Models\TypeMedia;
use App\Models\User;
use App\Traits\Http\HandlesMedia;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
use Symfony\Component\Mime\MimeTypes;
class UploadController extends Controller
{
use HandlesMedia;
/**
* Show upload form
*
* @param Request $request
* @return \Illuminate\Contracts\Validation\Validator|\Illuminate\Contracts\Validation\Factory
*/
public function showUploadForm(Request $request)
{
// TODO: Quand le DAM sera ouvert au public, Enforce security with dynamic tokens.
// ---- Vérification du token de sécurité .
$token = $request->get('token');
if ($token != env('TOKEN_DEPOT', "c8c4613a-236c-4b06-95f6-a9c9c6c18861")) {
return abort(404);
}
$sites = Site::orderBy('libelle')->get();
return view('upload.show')->with([
'sites' => $sites,
]);
}
/**
* Asynchronous chunked upload file.
*
* @param \App\Http\Requests\Bo\Ajax\UploadMediaFileRequest $request
* @param \Pion\Laravel\ChunkUpload\Receiver\FileReceiver $receiver
* @return \Illuminate\Http\JsonResponse
* @throws \Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException If failed upload.
*/
public function uploadMediaFileIntoDepot(
UploadMediaFileRequest $request,
FileReceiver $receiver,
): JsonResponse {
/** @var \App\Models\Dossier */
$dossier = Dossier::where('id', intval($request->dossier_id))->firstOrFail();
// Check if the upload is success, throw exception or return response you need.
if ($receiver->isUploaded() === false) {
throw new UploadMissingFileException();
}
// Receive the file.
$save = $receiver->receive();
// Check if the upload has finished (in chunk mode it will send smaller files).
if ($save->isFinished()) {
/** @var \App\Models\User */
$user = auth()->user();
$uplodedFile = $save->getFile();
try {
/** @throws \Illuminate\Validation\ValidationException */
$this->validateUploadedFile($uplodedFile);
/** @var \App\Models\Media */
$media = Media::whereId($request->media_id)
->whereHas('dossier', function ($q) use ($dossier) {
$q->whereId($dossier->id);
})->firstOrNew();
// Save the file and return any response you need.
return $this->createOrUpdateMedia($user, $uplodedFile, $media, $dossier);
} catch (ValidationException $e) {
// 400 instead of 422 is for resumablejs to handle properly.
return \response()->json($e->errors(), 400);
}
}//end if
// We are in chunk mode, lets send the current progress.
/** @var AbstractHandler $handler */
$handler = $save->handler();
return response()->json([
'done' => $handler->getPercentageDone()
]);
}
/**
* Asynchronous chunked upload file.
*
* @param \App\Http\Requests\Bo\Ajax\UploadMediaFileRequest $request
* @param \Pion\Laravel\ChunkUpload\Receiver\FileReceiver $receiver
* @param \App\Models\Sujet $sujet
* @param \App\Models\Dossier $dossier
* @return \Illuminate\Http\JsonResponse
* @throws \Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException If failed upload.
*/
public function uploadMediaFile(
UploadMediaFileRequest $request,
FileReceiver $receiver,
Sujet $sujet,
Dossier $dossier
): JsonResponse {
// Check if the upload is success, throw exception or return response you need.
if ($receiver->isUploaded() === false) {
throw new UploadMissingFileException();
}
// Receive the file.
$save = $receiver->receive();
// Check if the upload has finished (in chunk mode it will send smaller files).
if ($save->isFinished()) {
/** @var \App\Models\User */
$user = auth()->user();
$uplodedFile = $save->getFile();
try {
/** @throws \Illuminate\Validation\ValidationException */
$this->validateUploadedFile($uplodedFile);
/** @var \App\Models\Media */
$media = Media::whereId($request->media_id)
->whereHas('sujet', function ($q) use ($sujet) {
$q->whereId($sujet->id);
})->whereHas('dossier', function ($q) use ($dossier) {
$q->whereId($dossier->id);
})->firstOrNew();
// Save the file and return any response you need.
return $this->createOrUpdateMedia($user, $uplodedFile, $media, $dossier, $sujet);
} catch (ValidationException $e) {
// 400 instead of 422 is for resumablejs to handle properly.
return \response()->json($e->errors(), 400);
}
}//end if
// We are in chunk mode, lets send the current progress.
/** @var AbstractHandler $handler */
$handler = $save->handler();
return response()->json([
'done' => $handler->getPercentageDone()
]);
}
/**
* Validate uploaded file
*
* @param \Illuminate\Http\UploadedFile $file
* @return void
* @throws \Illuminate\Validation\ValidationException If uploaded file is not valid.
*/
private function validateUploadedFile(UploadedFile $file)
{
Validator::make([
'file' => $file
], [
'file' => 'required|file|mimes:gif,jpeg,jpg,png,mp4,mp3,psd,ai,svg,pdf'
], [
'files.mimes' => trans('Ce type de fichier n\'est pas accepté.')
])->validate();
$this->validateExtension($file);
}
/**
* Validate file extension matches allowed extensions enum
*
* @param \Illuminate\Http\UploadedFile $file
* @return void
* @throws \Illuminate\Validation\ValidationException If file extension could not be confirmed.
*/
private function validateExtension(UploadedFile $file)
{
if (\is_null(MediaExtension::tryFrom($file->getClientOriginalExtension()))) {
throw ValidationException::withMessages([
'file' => trans('Ce type de fichier n\'est pas accepté.')
]);
}
/** @throws \ValueError If value is not defined in enum */
$mediaExtension = MediaExtension::from($file->getClientOriginalExtension());
if ($file->guessExtension()) {
$mt = new MimeTypes();
$guessedExts = $mt->getExtensions($mt->guessMimeType($file->path()));
if (!\in_array($mediaExtension->value, $guessedExts)) {
$guessedExts = \implode(',', $guessedExts);
throw ValidationException::withMessages([
'file' => trans(
'File guessed extensions :guessedExts does not match file name extension :extension',
[
'guessedExt' => $guessedExts,
'extension' => $mediaExtension,
]
)
]);
}
}
}
/**
* Create or update media
*
* @param \App\Models\User $user
* @param \Illuminate\Http\UploadedFile $file
* @param \App\Models\Media $media
* @param \App\Models\Dossier $dossier
* @param \App\Models\Sujet|null $sujet
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException If $subject is missing and folder
* is not a system folder (Dossier dépot).
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
* (switch) .
*/
private function createOrUpdateMedia(
User $user,
UploadedFile $file,
Media $media,
Dossier $dossier,
Sujet|null $sujet = null
): JsonResponse {
// phpcs:enable
return DB::transaction(function () use ($user, $file, $media, $dossier, $sujet) {
if (!$dossier->system and !$sujet) {
throw ValidationException::withMessages([
'file' => 'Cannot put media inside a non system folder (depot) without a subject'
]);
}
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
$isNewMedia = !$media->exists;
$mediaFolderName = $this->getCurentMediaFolderName();
// * File size
$fileSize = $file->getSize();
$sanitizedFileName = ToolboxHelper::sanitizeFileName($file->getClientOriginalName());
/** @var string */
$sanitizedFileBasename = \pathinfo($sanitizedFileName, \PATHINFO_BASENAME);
/** @var string */
$sanitizedFileExtension = \pathinfo($sanitizedFileName, \PATHINFO_EXTENSION);
/** @throws \ValueError If value is not defined in enum */
$mediaExtension = MediaExtension::from($sanitizedFileExtension);
if (!$isNewMedia) {
// Supprimer les vielles images.
$media->deleteFilesOnAllPath();
}
// * New folder attribution
$media->folder = $mediaFolderName;
// * New Image, New uuid.
$media->uid = Str::uuid();
// * New Image extension
$media->extension = $mediaExtension;
// * New Image size
$media->taille = $fileSize ? $fileSize : 0;
// * Le media n'est pas une image par défaut.
$media->isDefault = false;
// * On déplace le fichier dans le répertoire des médias.
/** @throws \FileException */
$file->move($storage->path($media->folder), $media->basename);
// * Si on upload un média pour remplacer un fichier par un autre
if ($isNewMedia) {
$media->libelle = Str::limit($sanitizedFileBasename, 50, '');
$media->owner()->associate($user);
$media->dossier()->associate($dossier);
$media->sujet()->associate($sujet);
// * Par défaut FR
$media->langue()->associate(Langue::getDefaultLang());
// * Par défaut propriété de la marque
$media->copyright()->associate(Copyright::getBrandCopyright());
$media->etiquette()->associate(Etiquette::getDefaultEtiquette());
}
switch ($media->extension) {
case MediaExtension::gif:
case MediaExtension::jpeg:
case MediaExtension::jpg:
case MediaExtension::png:
case MediaExtension::ai:
case MediaExtension::pdf:
case MediaExtension::psd:
$dimensions = ToolboxHelper::getImageDimensions($media->full_path);
if ($dimensions) {
$media->resolution = $dimensions['resolutionX'];
$media->largeur = $dimensions['width'];
$media->hauteur = $dimensions['height'];
$media->largeur_mm = !empty($media->resolution) ? ceil($media->largeur * 10) : 0;
$media->hauteur_mm = !empty($media->resolution) ? ceil($media->hauteur * 10) : 0;
}
// ---- Creation des vignettes de base
$media->createThumbnail();
// ---- Creation des vignettes en lien avec le sujet du média
$media->createSubjectThumbnails();
break;
case MediaExtension::mp4:
if ($dimensions = ToolboxHelper::getId3VideoDimensions($media->full_path)) {
$media->largeur = $dimensions['width'];
$media->hauteur = $dimensions['height'];
}
// Fall-through .
case MediaExtension::mp3:
$media->duree = ToolboxHelper::getId3Playtime($media->full_path);
break;
} //end switch
$media->typeMedia()->associate(TypeMedia::getMediaType($media->extension));
$media->saveOrFail();
// * Get props inserted by DB.
return response()->json($media->refresh());
});
}
}