feat(sitemap): Améliore le sitemap avec images, vidéos et pages statiques
```
This commit is contained in:
Serreau Jovann
2026-01-20 18:18:23 +01:00
parent b1f6a0cfa2
commit d2250f0aeb
3 changed files with 176 additions and 33 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Process\Process;
#[AsCommand(
name: 'app:generate-video-thumbs',
description: 'Génère une miniature JPG à partir dune liste de vidéos.',
)]
class GenerateVideoThumbsCommand extends Command
{
// Liste des vidéos (relatif à /public)
private const VIDEOS_TO_THUMB = [
'provider/video/video.mp4',
];
public function __construct(
private readonly ParameterBagInterface $parameterBag
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$publicPath = $this->parameterBag->get('kernel.project_dir') . '/public/';
$io->title('Extraction des miniatures Vidéo');
foreach (self::VIDEOS_TO_THUMB as $relativeVideoPath) {
$videoFile = $publicPath . $relativeVideoPath;
if (!file_exists($videoFile)) {
$io->warning("Vidéo introuvable : $videoFile");
continue;
}
// Définition du nom de sortie (ex: video.mp4 -> video.jpg)
$pathInfo = pathinfo($videoFile);
$thumbName = $pathInfo['filename'] . '.jpg';
$thumbPath = $pathInfo['dirname'] . '/' . $thumbName;
$io->writeln("Traitement de : " . $pathInfo['basename']);
// Commande FFmpeg :
// -i : fichier d'entrée
// -ss : position (00:00:01 = 1ère seconde pour éviter l'écran noir du début)
// -vframes 1 : extraire une seule image
// -q:v 2 : qualité de l'image (2 à 5 est bon)
$command = [
'ffmpeg',
'-y', // Écraser si le fichier existe
'-i', $videoFile,
'-ss', '00:00:01.000', // Capture à 1 seconde
'-vframes', '1',
$thumbPath
];
$process = new Process($command);
$process->run();
if ($process->isSuccessful()) {
$io->writeln("<info>[OK]</info> Miniature générée : $thumbName");
} else {
$io->error("Erreur FFmpeg : " . $process->getErrorOutput());
}
}
$io->success('Toutes les vidéos ont été traitées.');
return Command::SUCCESS;
}
}

View File

@@ -2,31 +2,29 @@
namespace App\Security;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;
use Presta\SitemapBundle\Event\SitemapPopulateEvent;
use Presta\SitemapBundle\Sitemap\Url\GoogleImage;
use Presta\SitemapBundle\Sitemap\Url\GoogleImageUrlDecorator;
use Presta\SitemapBundle\Sitemap\Url\GoogleVideo;
use Presta\SitemapBundle\Sitemap\Url\GoogleVideoUrlDecorator;
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
class SiteMapListener implements EventSubscriberInterface
{
public function __construct(private readonly ProductRepository $productRepository)
{
public function __construct(
private readonly UploaderHelper $uploaderHelper,
private readonly ProductRepository $productRepository,
private readonly EntityManagerInterface $entityManager
) {
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return [
SitemapPopulateEvent::class => 'populate',
@@ -38,26 +36,91 @@ class SiteMapListener implements EventSubscriberInterface
$urlGenerator = $event->getUrlGenerator();
$urlContainer = $event->getUrlContainer();
$t = new \DateTime();
$rv = new UrlConcrete($urlGenerator->generate('reservation',[], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_DAILY,1);
$urlContainer->addUrl($rv,'reservation');
$rv = new UrlConcrete($urlGenerator->generate('reservation_contact',[], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_DAILY,1);
$urlContainer->addUrl($rv,'reservation');
$rv = new UrlConcrete($urlGenerator->generate('reservation_cookies',[], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_WEEKLY,0.5);
$urlContainer->addUrl($rv,'reservation');
$rv = new UrlConcrete($urlGenerator->generate('reservation_mentions-legal',[], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_WEEKLY,0.5);
$urlContainer->addUrl($rv,'reservation');
$rv = new UrlConcrete($urlGenerator->generate('reservation_rgpd',[], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_WEEKLY,0.5);
$urlContainer->addUrl($rv,'reservation');
$rv = new UrlConcrete($urlGenerator->generate('reservation_hosting',[], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_WEEKLY,0.5);
$urlContainer->addUrl($rv,'reservation');
$rv = new UrlConcrete($urlGenerator->generate('reservation_cgv',[], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_WEEKLY,0.5);
$urlContainer->addUrl($rv,'reservation');
// Base URL pour les médias
$baseUrl = $urlGenerator->generate('reservation', [], UrlGeneratorInterface::ABSOLUTE_URL);
$rootUrl = rtrim($baseUrl, '/');
$t = new \DateTime();
// --- 1. PAGES STATIQUES & VIDÉO ---
$reservationUrl = $urlGenerator->generate('reservation', [], UrlGeneratorInterface::ABSOLUTE_URL);
$homeUrl = new UrlConcrete($reservationUrl, $t, UrlConcrete::CHANGEFREQ_DAILY, 1);
// Décorateur Vidéo pour la page d'accueil
$videoUrl = $rootUrl . '/provider/video/video.mp4';
$videoTrumbs = $rootUrl . '/provider/video/video.jpg';
$homeWithVideo = new GoogleVideoUrlDecorator($homeUrl);
$video = new GoogleVideo(
$videoTrumbs,
"Découvrez nos produits en vidéo",
"Découvrez nos produits en vidéo",
['content_location' => $videoUrl]
);
$homeWithVideo->addVideo($video);
$urlContainer->addUrl($homeWithVideo, 'reservation');
// Autres routes statiques
$staticRoutes = [
'reservation_contact' => UrlConcrete::CHANGEFREQ_DAILY,
'reservation_cookies' => UrlConcrete::CHANGEFREQ_WEEKLY,
'reservation_mentions-legal' => UrlConcrete::CHANGEFREQ_WEEKLY,
'reservation_rgpd' => UrlConcrete::CHANGEFREQ_WEEKLY,
'reservation_hosting' => UrlConcrete::CHANGEFREQ_WEEKLY,
'reservation_cgv' => UrlConcrete::CHANGEFREQ_WEEKLY,
];
foreach ($staticRoutes as $route => $freq) {
$url = $urlGenerator->generate($route, [], UrlGeneratorInterface::ABSOLUTE_URL);
$urlContainer->addUrl(new UrlConcrete($url, $t, $freq, 0.5), 'reservation');
}
// --- 2. PRODUITS & IMAGES ---
foreach ($this->productRepository->findAll() as $product) {
$rv = new UrlConcrete($urlGenerator->generate('reservation_product_show',['id'=>$product->slug()], UrlGeneratorInterface::ABSOLUTE_URL),$t,UrlConcrete::CHANGEFREQ_WEEKLY,0.5);
$urlContainer->addUrl($rv,'reservation_product');
$productUrl = $urlGenerator->generate(
'reservation_product_show',
['id' => $product->slug()],
UrlGeneratorInterface::ABSOLUTE_URL
);
$decoratedUrl = new GoogleImageUrlDecorator(
new UrlConcrete($productUrl, $t, UrlConcrete::CHANGEFREQ_WEEKLY, 0.5)
);
$imagePath = $this->uploaderHelper->asset($product, 'imageFile');
if ($imagePath) {
$usedList = [];
// Appel de votre fonction avec l'objet product
$this->addImageToSitemap($decoratedUrl, $usedList, $rootUrl, $imagePath, $product);
}
$urlContainer->addUrl($decoratedUrl, 'reservation_product');
}
}
/**
* Votre fonction personnalisée avec gestion Base64 et Titre dynamique
*/
private function addImageToSitemap(GoogleImageUrlDecorator $pathMenu, array &$usedList, string $rootUrl, string $image, object $object): void
{
// 1. Ignorer les images en base64
if (str_starts_with($image, 'data:image/')) {
return;
}
// 2. Éviter les doublons
if (!isset($usedList[$image])) {
$imageUrl = str_starts_with($image, 'http') ? $image : $rootUrl . $image;
$gImage = new GoogleImage($imageUrl);
if ($object instanceof Product) {
// Utilisation du nom de l'objet pour le SEO de l'image
$gImage->setTitle("Image du produit - " . $object->getName());
}
$pathMenu->addImage($gImage);
$usedList[$image] = 1;
}
}
}