<?php
namespace App\Controller;
use App\Service\KmlStorageService;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
/**
* Serves live KML from outside the web root (no directory listing / direct static URLs).
*/
class TimelineKmlController extends BaseController
{
/**
* @Route(
* "/timelines/kml/{project}/{filepath}",
* name="timelines_kml",
* methods={"GET"},
* requirements={"project"="[a-z0-9_-]+", "filepath"="[a-zA-Z0-9_./-]+\.kml"}
* )
*/
public function serve(string $project, string $filepath, Request $request, KmlStorageService $storage): Response
{
$project = $storage->assertValidProject($project);
$relative = $storage->assertValidRelativeKmlPath($filepath);
$path = $storage->resolveLiveKmlPath($project, $relative);
if (!is_file($path)) {
throw new NotFoundHttpException('KML not found.');
}
$downloadName = basename($path);
$servePath = $path;
$useGzip = false;
$accept = (string) $request->headers->get('Accept-Encoding', '');
if (stripos($accept, 'gzip') !== false) {
$gzPath = $storage->ensureGzipCompanion($path);
if ($gzPath !== null && is_file($gzPath)) {
$servePath = $gzPath;
$useGzip = true;
}
}
$response = new BinaryFileResponse($servePath);
$response->headers->set('Content-Type', 'application/vnd.google-earth.kml+xml; charset=UTF-8');
$response->headers->set('X-Content-Type-Options', 'nosniff');
// Inline for map fetch — do not advertise as downloadable attachment.
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $downloadName);
if ($useGzip) {
$response->headers->set('Content-Encoding', 'gzip');
$response->headers->set('Vary', 'Accept-Encoding');
}
// Cheap validators (avoid hashing multi‑MB bodies on every request).
$mtime = (int) filemtime($path);
$response->setLastModified((new \DateTimeImmutable())->setTimestamp($mtime));
$response->setEtag(sprintf('"%d-%d-%s"', $mtime, (int) filesize($servePath), $useGzip ? 'gz' : 'raw'));
$response->setCache([
'public' => true,
'max_age' => 300,
's_maxage' => 300,
'must_revalidate' => true,
]);
$response->isNotModified($request);
return $response;
}
}