From 9bc16826513143b632bfafc297f93c72ca9505e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Novotn=C3=BD?= Date: Thu, 30 Jan 2025 09:22:47 +0100 Subject: [PATCH] fix /image route --- .../Controller/Front/SearchFormController.php | 110 ++++++++++-------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/htdocs/src/Controller/Front/SearchFormController.php b/htdocs/src/Controller/Front/SearchFormController.php index 28d9dd8..ca40125 100644 --- a/htdocs/src/Controller/Front/SearchFormController.php +++ b/htdocs/src/Controller/Front/SearchFormController.php @@ -2,29 +2,20 @@ namespace App\Controller\Front; -use App\Entity\User; -use App\Enum\CoreObjectsEnum; -use App\Enum\TimeIntervalEnum; use App\Facade\SearchFormFacade; use App\Service\CollectionService; -use App\Service\DjatokaService; use App\Service\ExcelService; use App\Service\ImageService; use App\Service\InstitutionService; -use App\Service\KmlService; -use App\Service\Rest\DevelopersService; -use App\Service\Rest\StatisticsService; use App\Service\SearchFormSessionService; use App\Service\SpecimenService; use PhpOffice\PhpSpreadsheet\Writer\Csv; use PhpOffice\PhpSpreadsheet\Writer\Ods; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; use Symfony\Component\Routing\Attribute\Route; @@ -35,9 +26,9 @@ class SearchFormController extends AbstractController public const array RECORDS_PER_PAGE = array(10, 30, 50, 100); //TODO the name of taxon is not part of the query now, hard to sort - public const array SORT = ["taxon"=> '', 'collector'=>'s.collector']; + public const array SORT = ["taxon" => '', 'collector' => 's.collector']; - public function __construct( protected readonly CollectionService $collectionService, protected readonly InstitutionService $herbariumService, protected readonly SearchFormFacade $searchFormFacade, protected readonly SearchFormSessionService $sessionService, protected readonly ImageService $imageService, protected readonly SpecimenService $specimenService, protected readonly ExcelService $excelService) + public function __construct(protected readonly CollectionService $collectionService, protected readonly InstitutionService $herbariumService, protected readonly SearchFormFacade $searchFormFacade, protected readonly SearchFormSessionService $sessionService, protected readonly ImageService $imageService, protected readonly SpecimenService $specimenService, protected readonly ExcelService $excelService) { } @@ -103,52 +94,43 @@ public function collectionsSelectOptions(#[MapQueryParameter] string $herbariumI } #[Route('/image', name: 'app_front_image_endpoint', methods: ['GET'])] - public function showImage(#[MapQueryParameter] string $filename,#[MapQueryParameter] ?string $sid,#[MapQueryParameter] string $method,#[MapQueryParameter] ?string $format): Response + public function showImage(#[MapQueryParameter] string $filename, #[MapQueryParameter] ?string $sid, #[MapQueryParameter] string $method, #[MapQueryParameter] ?string $format): Response { if ($_SERVER['REMOTE_ADDR'] == '94.177.9.139' && !empty($sid) && $method == 'download' && strrpos($filename, '_') == strpos($filename, '_')) { // kulturpool is calling... // Redirect to new location - $this->redirectToRoute("services_rest_images_europeana", ["specimenID"=>$sid], 303); + $this->redirectToRoute("services_rest_images_europeana", ["specimenID" => $sid], 303); } + //TODO only due Djatoka in the getPicDetails() is needed to have this format, otherwise could be deriobed from the streamed response headers, see bellow switch ($format) { case 'jpeg2000': - $mime = 'image/jp2'; + $contentType = 'image/jp2'; break; case'tiff': - $mime = 'image/tiff'; + $contentType = 'image/tiff'; break; default: - $mime = 'image/jpeg'; + $contentType = 'image/jpeg'; break; } - $picDetails = $this->imageService->getPicDetails($filename,$mime, $sid); + $picDetails = $this->imageService->getPicDetails($filename, $contentType, $sid); if (!empty($picDetails['url'])) { switch ($method) { default: - $this->imageService->getSourceUrl($picDetails,$mime, 0); - exit; + $url = $this->imageService->getSourceUrl($picDetails, $contentType, 0); + break; case 'download': // detail - - return new StreamedResponse(function () use ($picDetails, $mime) { - // ignore broken certificates - $context = stream_context_create(array("ssl"=>array("verify_peer" => false, - "verify_peer_name" => false), - "http"=>array('timeout' => 60))); - readfile($url, false, $context); - $this->imageService->getSourceUrl($picDetails,$mime, 0); - }); + $url = $this->imageService->getSourceUrl($picDetails, $contentType, 0); + break; case 'thumb': // detail - return new JsonResponse(['dd' => $this->imageService->getSourceUrl($picDetails,$mime, 1)], 202); - - return new StreamedResponse(function () use ($picDetails, $mime) { - $this->imageService->getSourceUrl($picDetails,$mime, 1); - }); + $url = $this->imageService->getSourceUrl($picDetails, $contentType, 1); + break; case 'resized': // create_xml.php - $this->imageService->getSourceUrl($picDetails,$mime, 2); - exit; + $url = $this->imageService->getSourceUrl($picDetails, $contentType, 2); + break; case 'europeana': // NOTE: not supported on non-djatoka servers (yet) if (strtolower(substr($picDetails['requestFileName'], 0, 3)) == 'wu_' && $this->imageService->checkPhaidra((int)$picDetails['specimenID'])) { // Phaidra (only WU) @@ -156,35 +138,60 @@ public function showImage(#[MapQueryParameter] string $filename,#[MapQueryParame } else { // Djatoka $picinfo = $this->imageService->getPicInfo($picDetails); - if (!empty($picinfo['pics'][0]) && !in_array($picDetails['originalFilename'], $picinfo['pics'])) { + if (!empty($picinfo['pics'][0]) && !in_array($picDetails['originalFilename'], $picinfo['pics'])) { $picDetails['originalFilename'] = $picinfo['pics'][0]; } } - $this->imageService->getSourceUrl($picDetails,$mime, 3); - exit; + $url = $this->imageService->getSourceUrl($picDetails, $contentType, 3); + break; case 'nhmwthumb': // NOTE: not supported on legacy image server scripts - $this->imageService->getSourceUrl($picDetails,$mime, 4); - exit; + $url = $this->imageService->getSourceUrl($picDetails, $contentType, 4); + break; case 'thumbs': // unused return $this->json($this->imageService->getPicInfo($picDetails)); case 'show': // detail, ajax/results.php - return $this->redirect($this->imageService->doRedirectShowPic($picDetails)); + $url = $this->imageService->doRedirectShowPic($picDetails); + break; } + $streamContext = stream_context_create([ + 'http' => ['follow_location' => true, + 'timeout' => 60], + 'ssl' => ["verify_peer" => false, + "verify_peer_name" => false] + ]); + $imageStream = @fopen($url, 'rb', false, $streamContext); + + $headers = get_headers($url, true); + +// $contentType = $headers['Content-Type'] ?? 'image/jpeg'; // Fallback na JPEG + $contentLength = $headers['Content-Length'] ?? null; + $response = new StreamedResponse(function () use ($imageStream) { + fpassthru($imageStream); + fclose($imageStream); + }); + + $response->headers->set('Content-Type', $contentType); + if ($contentLength) { + $response->headers->set('Content-Length', $contentLength); + } + + return $response; + } else { switch ($method) { case 'download': case 'thumb': - $filePath = $this->getParameter('kernel.project_dir') . '/public/recordIcons/404.png'; + $filePath = $this->getParameter('kernel.project_dir') . '/public/recordIcons/404.png'; - if (!file_exists($filePath) || mime_content_type($filePath) !== 'image/png') { - throw $this->createNotFoundException('Sorry, this image does not exist.'); - } + if (!file_exists($filePath) || mime_content_type($filePath) !== 'image/png') { + throw $this->createNotFoundException('Sorry, this image does not exist.'); + } - return new Response(file_get_contents($filePath), 200, [ - 'Content-Type' => 'image/png', - 'Content-Length' => filesize($filePath), - ]); + return new Response(file_get_contents($filePath), 200, [ + 'Content-Type' => 'image/png', + 'Content-Length' => filesize($filePath), + ]); case 'thumbs': return new JsonResponse(['error' => 'not found'], 404); default: @@ -197,7 +204,7 @@ public function showImage(#[MapQueryParameter] string $filename,#[MapQueryParame #[Route('/detail/{specimenId}', name: 'app_front_specimenDetail', methods: ['GET'])] public function detail(int $specimenId): Response { - return $this->render('front/home/detail.html.twig', ['specimen'=> $this->specimenService->findAccessibleForPublic($specimenId)]); + return $this->render('front/home/detail.html.twig', ['specimen' => $this->specimenService->findAccessibleForPublic($specimenId)]); } #[Route('/exportKml', name: 'app_front_exportKml', methods: ['GET'])] @@ -210,6 +217,7 @@ public function exportKml(): Response return $response; } + #[Route('/exportExcel', name: 'app_front_exportExcel', methods: ['GET'])] public function exportExcel(): Response { @@ -227,6 +235,7 @@ public function exportExcel(): Response return $response; } + #[Route('/exportCsv', name: 'app_front_exportCsv', methods: ['GET'])] public function exportCsv(): Response { @@ -244,6 +253,7 @@ public function exportCsv(): Response return $response; } + #[Route('/exportOds', name: 'app_front_exportOds', methods: ['GET'])] public function exportOds(): Response {