feat(documents) : bouton reload explorateur + liaison d'un fichier du partage SMB à un ticket
This commit is contained in:
@@ -5,24 +5,33 @@ declare(strict_types=1);
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\TaskDocument;
|
||||
use App\Service\Share\Exception\ShareConnectionException;
|
||||
use App\Service\Share\Exception\ShareNotConfiguredException;
|
||||
use App\Service\Share\FileSource;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
use function is_resource;
|
||||
|
||||
class TaskDocumentDownloadController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly FileSource $fileSource,
|
||||
private readonly string $uploadDir,
|
||||
) {}
|
||||
|
||||
#[Route('/api/task_documents/{id}/download', name: 'task_document_download', methods: ['GET'], priority: 1)]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
public function __invoke(int $id): BinaryFileResponse
|
||||
public function __invoke(int $id): Response
|
||||
{
|
||||
$document = $this->entityManager->getRepository(TaskDocument::class)->find($id);
|
||||
|
||||
@@ -30,6 +39,20 @@ class TaskDocumentDownloadController extends AbstractController
|
||||
throw new NotFoundHttpException('Document not found.');
|
||||
}
|
||||
|
||||
$mimeType = $document->getMimeType() ?? 'application/octet-stream';
|
||||
|
||||
// Inline for images (except SVG) and PDFs, attachment for everything else.
|
||||
// SVG is always attachment to prevent XSS via embedded JavaScript.
|
||||
$inline = 'image/svg+xml' !== $mimeType && (str_starts_with($mimeType, 'image/') || 'application/pdf' === $mimeType);
|
||||
$disposition = $inline ? ResponseHeaderBag::DISPOSITION_INLINE : ResponseHeaderBag::DISPOSITION_ATTACHMENT;
|
||||
|
||||
return $document->isShareLink()
|
||||
? $this->streamFromShare($document, $mimeType, $disposition)
|
||||
: $this->streamFromDisk($document, $mimeType, $disposition);
|
||||
}
|
||||
|
||||
private function streamFromDisk(TaskDocument $document, string $mimeType, string $disposition): BinaryFileResponse
|
||||
{
|
||||
$filePath = $this->uploadDir.'/'.$document->getFileName();
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
@@ -37,18 +60,32 @@ class TaskDocumentDownloadController extends AbstractController
|
||||
}
|
||||
|
||||
$response = new BinaryFileResponse($filePath);
|
||||
$mimeType = $document->getMimeType() ?? 'application/octet-stream';
|
||||
|
||||
// Inline for images and PDFs, attachment for everything else
|
||||
// SVG files are always served as attachment to prevent XSS via embedded JavaScript
|
||||
$disposition = 'image/svg+xml' === $mimeType
|
||||
? ResponseHeaderBag::DISPOSITION_ATTACHMENT
|
||||
: (str_starts_with($mimeType, 'image/') || 'application/pdf' === $mimeType
|
||||
? ResponseHeaderBag::DISPOSITION_INLINE
|
||||
: ResponseHeaderBag::DISPOSITION_ATTACHMENT);
|
||||
|
||||
$response->setContentDisposition($disposition, $document->getOriginalName());
|
||||
$response->setContentDisposition($disposition, (string) $document->getOriginalName());
|
||||
$response->headers->set('Content-Type', $mimeType);
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function streamFromShare(TaskDocument $document, string $mimeType, string $disposition): StreamedResponse
|
||||
{
|
||||
try {
|
||||
$stream = $this->fileSource->read((string) $document->getSharePath());
|
||||
} catch (ShareNotConfiguredException) {
|
||||
throw new NotFoundHttpException('Share not configured.');
|
||||
} catch (ShareConnectionException) {
|
||||
throw new NotFoundHttpException('File not found on the share.');
|
||||
}
|
||||
|
||||
$response = new StreamedResponse(function () use ($stream): void {
|
||||
if (is_resource($stream)) {
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}
|
||||
});
|
||||
$response->headers->set('Content-Type', $mimeType);
|
||||
$response->headers->set('Content-Disposition', HeaderUtils::makeDisposition($disposition, (string) $document->getOriginalName()));
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user