350 lines
13 KiB
PHP
350 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\Database\Eloquent\Builder;
|
|
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::query()
|
|
->where('id', $request->input('media_id'))
|
|
->whereHas('sujet', fn(Builder $q) => $q->where('codeSujet', $sujet->codeSujet))
|
|
->whereHas('dossier', fn(Builder $q) => $q->where('id', $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();
|
|
|
|
Media::flushQueryCache();
|
|
|
|
// * Get props inserted by DB.
|
|
return response()->json($media->refresh());
|
|
});
|
|
}
|
|
}
|