<?php
namespace App\Controller;
use App\Constant\UtilsConstant;
use App\Entity\BandMember;
use App\Entity\Project;
use App\Entity\User;
use App\Exception\BadRequestException;
use App\Services\StorageManager;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use FOS\RestBundle\Controller\Annotations as Rest;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Constant\FileActionConstant;
use App\Constant\FileTypeConstant;
use App\Entity\File;
use App\Entity\Folder;
use App\Entity\Playable;
use App\Entity\Picture;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
const PROFILE = 'profile';
const COVER = 'cover';
const PICTURE = 'picture';
const CLASS_KEY = 'class';
const CLASSNAME = 'className';
const MESSAGE = 'message';
class UploadController extends WhiplayController
{
const EDITABLE_PICTURES = [
'userProfile' => [
'type' => PROFILE,
PICTURE => null,
'path' => 'users/',
CLASS_KEY => 'User',
CLASSNAME => 'user-profile-picture-img'
],
'userCover' => [
'type' => COVER,
PICTURE => null,
'path' => 'users/',
CLASS_KEY => 'User',
CLASSNAME => 'user-cover-picture-img'
],
'bandProfile' => [
'type' => PROFILE,
PICTURE => null,
'path' => 'bands/',
CLASS_KEY => 'Band',
CLASSNAME => 'band-profile-picture-img'
],
'bandCover' => [
'type' => COVER,
PICTURE => null,
'path' => 'bands/',
CLASS_KEY => 'Band',
CLASSNAME => 'band-cover-picture-img'
],
'file' => [
'type' => 'image',
PICTURE => null,
'path' => 'files/',
CLASS_KEY => 'File',
CLASSNAME => 'file-picture-img'
],
];
const ALLOWED_MUSIC_TYPES = [
'audio/mpeg' => 'mp3',
'application/octet-stream' => 'mp3',
'audio/x-wav' => 'wav',
'audio/wav' => 'wav',
];
const ALLOWED_PICTURE_TYPES = [
'image/jpeg' => 'jpg',
'image/jpg' => 'jpg',
'image/png' => 'png',
];
/**
* Upload a picture to replace one of:
* - The band's cover or profile picture
* - The user's cover or profile picture
* - The band's cover or profile picture
*
* @Rest\View()
* @Rest\Post("/upload/picture")
*
* @param Request $request
* @param TranslatorInterface $translator
* @return JsonResponse
*/
public function picture(Request $request, TranslatorInterface $translator): JsonResponse
{
try {
// Check if current request come from AJAX
if (!$request->isXmlHttpRequest()) {
throw new BadRequestException('upload.forbidden');
}
// Check if user is logged
$user = $this->getUser();
if (!($user instanceof User)) {
throw new BadRequestException('upload.need_login');
}
// check if data64 and name get param are valid
$base64Content = $request->get('data64');
$pictureName = $request->get('name');
$entityId = $request->get('entityId');
if (!$base64Content || !isset(self::EDITABLE_PICTURES[$pictureName]) || !$entityId) {
throw new BadRequestException('upload.invalid_parameters');
}
$data = self::EDITABLE_PICTURES[$pictureName];
// Check if entity class is found
$entityClass = $data[CLASS_KEY];
if (!$entityClass) {
throw new BadRequestException('upload.picture.class_not_found');
}
// Check if entity is found
$entity = $this->getDoctrine()
->getRepository('App:' . $entityClass)
->find($entityId);
if (!isset($entity)) {
throw new BadRequestException('upload.picture.entity_not_found');
}
if ($entityClass === 'User' && $user->getId() !== $entity->getId()) {
throw new BadRequestException('upload.picture.user_upload_as_external');
}
if ($entityClass === 'Band' && !$user->isMemberButNotGuest($entity)) {
throw new BadRequestException('upload.picture.upload_as_guest');
}
if ($entityClass === 'File' && !$user->isMemberButNotGuest($entity->getFolder()->getOwner())) {
throw new BadRequestException('upload.picture.upload_as_guest');
}
// Check if extension is valid
$extension = $this->extensionIsValid($base64Content);
if (!$extension) {
throw new BadRequestException('upload.picture.invalid_format');
}
} catch (BadRequestException $exception) {
return new JsonResponse([MESSAGE => $translator->trans($exception->getMessage())], Response::HTTP_BAD_REQUEST);
}
$picture = new Picture();
$picture->setBase64Content($base64Content);
$picture->setExtension($extension);
$picture->setSubPath($data['path']);
$em = $this->getDoctrine()->getManager();
$em->persist($picture);
$postData = [];
$postData['type'] = $data['type'];
$postData[CLASSNAME] = $data[CLASSNAME];
if (self::EDITABLE_PICTURES[$pictureName]['type'] === COVER) {
$oldPicture = $entity->getCommonInformation()->getCoverPicture();
$entity->getCommonInformation()->setCoverPicture($picture);
} else if (self::EDITABLE_PICTURES[$pictureName]['type'] === PROFILE) {
$oldPicture = $entity->getCommonInformation()->getProfilePicture();
$entity->getCommonInformation()->setProfilePicture($picture);
} else {
$oldPicture = $entity->getPicture();
$entity->setPicture($picture);
}
if ($oldPicture instanceof Picture) {
$em->remove($oldPicture);
}
$em->persist($entity);
$em->flush();
$postData['src'] = $this->getParameter(UtilsConstant::GLOBAL_URL) . $picture->getWebPath();
return new JsonResponse($postData);
}
/**
* Check if extension is valid from base64 content
* return extension or null if it's invalid
*
* @param string $content
* @return string|null
*/
private function extensionIsValid(string $content): ?string
{
$validExtension = false;
$fileType = substr($content, 5, strrpos($content, ';') - 5);
foreach (array_keys(self::ALLOWED_PICTURE_TYPES) as $currentExt) {
$end = strtolower(substr($fileType, -strlen($currentExt)));
if ($end === $currentExt) {
$validExtension = true;
break;
}
}
$explodeExtension = explode('/', $fileType);
if ($validExtension && count($explodeExtension) === 2) {
return $explodeExtension[1];
}
return null;
}
/**
* Upload a file in $folderId in user file repository
*
* @Rest\View()
* @Rest\Post("/upload/file")
*
* @param Request $request
* @param StorageManager $storageManager
* @param TranslatorInterface $translator
* @param HubInterface $hub
* @return JsonResponse
*/
public function uploadFile(
Request $request,
StorageManager $storageManager,
TranslatorInterface $translator,
HubInterface $hub
): JsonResponse
{
try {
if (!$request->isXmlHttpRequest()) {
throw new BadRequestException('upload.forbidden');
}
/** @var User $user */
$user = $this->getUser();
if (!($user instanceof User)) {
throw new BadRequestException('upload.need_login');
}
$folderId = $request->get('folderId');
if (!$folderId) {
throw new BadRequestException('upload.invalid_parameters');
}
/** @var Folder $folder */
$folder = $this->getDoctrine()
->getRepository(Folder::class)
->find($folderId);
if (!$folder) {
throw new BadRequestException('upload.file.folder_not_found');
}
$band = $folder->getOwner();
if (!$user->isInBand($band)) {
throw new BadRequestException('upload.file.invalid_band');
}
if (!$user->isMemberButNotGuest($band)) {
throw new BadRequestException('upload.file.upload_as_guest');
}
$fileInputName = 'add-file';
/* @var UploadedFile $file */
$file = $request->files->get($fileInputName);
if ($file->getError() > 0) {
throw new BadRequestException('upload.file.failed');
}
$fullUsedSize = $storageManager->getStorageUsage($band);
$fullAvailableSize = $storageManager->getAvailableStorage($band);
$remainingSize = $fullAvailableSize - ($fullUsedSize + ($file->getSize() / (1024 * 1024)));
if ($remainingSize < 0) {
throw new BadRequestException('upload.file.too_big');
}
} catch (BadRequestException $exception) {
return new JsonResponse([MESSAGE => $translator->trans($exception->getMessage())]);
}
$em = $this->getDoctrine()->getManager();
$postData = [];
$newFile = new File();
$newFile->setName(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME));
$newFile->setFile($file);
$newFileType = $file->getMimeType();
if (array_key_exists($newFileType, self::ALLOWED_MUSIC_TYPES)) {
$newFile->setType(FileTypeConstant:: MUSIC);
$postData['fileType'] = FileTypeConstant::MUSIC;
$playable = new Playable();
$em->persist($playable);
$newFile->setPlayable($playable);
} elseif (array_key_exists($newFileType, self::ALLOWED_PICTURE_TYPES)) {
$newFile->setType(FileTypeConstant::PICTURE);
$postData['fileType'] = FileTypeConstant::PICTURE;
} else {
return new JsonResponse([MESSAGE => $translator->trans('upload.file.invalid_type')], Response::HTTP_BAD_REQUEST);
}
if ($folder->getFilesType() == $newFile->getType()) {
$newFile->setFolder($folder);
} else {
$newFile->setFolder($folder->getOwner()->getBaseFolder($newFile->getType()));
}
$projectId = $request->get('projectId');
if ($projectId) {
/** @var Project|null $project */
$project = $this->getDoctrine()
->getRepository(Project::class)
->find($projectId);
if ($project && $project->getBand() === $band) {
$newFile->setProject($project);
}
}
$em->persist($newFile);
$em->flush();
$this->toggleFileAction($newFile, FileActionConstant::ADD, false);
/** @var BandMember $bandMember */
foreach ($band->getMembers() as $bandMember) {
$update = new Update(
'messaging/' . $bandMember->getUser()->getId(),
json_encode([
'type' => 'new-file',
'file' => [
'id' => $newFile->getId(),
'name' => $newFile->getName(),
'type' => $newFile->getType(),
'status' => $newFile->getStatus(),
'show_route' => $this->generateUrl('whiplay_file_show', [
'id' => $newFile->getId(),
]),
'download_route' => $this->generateUrl('whiplay_download_music', [
'id' => $newFile->getId(),
]),
]
])
);
$hub->publish($update);
}
$postData['url'] = $this->get('router')->generate('whiplay_file_edit', array('id' => $newFile->getId()));
return new JsonResponse($postData);
}
}