src/Controller/Website/ClassicHomeController.php line 171

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Website;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\Routing\Annotation\Route;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\JsonResponse;
  8. use Symfony\Component\HttpFoundation\File\UploadedFile;
  9. final class ClassicHomeController extends AbstractController
  10. {
  11.     private function isAllowedCmsImageUpload(UploadedFile $file): bool
  12.     {
  13.         $allowedMimes = ['image/png''image/jpeg''image/webp'];
  14.         $allowedExts = ['png''jpg''jpeg''webp'];
  15.         $mimeCandidates array_filter([
  16.             $file->getClientMimeType(),
  17.         ]);
  18.         foreach ($mimeCandidates as $mime) {
  19.             if (in_array(strtolower((string) $mime), $allowedMimestrue)) {
  20.                 return true;
  21.             }
  22.         }
  23.         $extCandidates array_filter([
  24.             $file->getClientOriginalExtension(),
  25.         ]);
  26.         foreach ($extCandidates as $ext) {
  27.             if (in_array(strtolower((string) $ext), $allowedExtstrue)) {
  28.                 return true;
  29.             }
  30.         }
  31.         return false;
  32.     }
  33.     private function safeUploadExtension(UploadedFile $filestring $default 'bin'): string
  34.     {
  35.         $allowedExts = ['png''jpg''jpeg''webp'];
  36.         $clientExt strtolower((string) $file->getClientOriginalExtension());
  37.         if (in_array($clientExt$allowedExtstrue)) {
  38.             return $clientExt;
  39.         }
  40.         return $default;
  41.     }
  42.        private function latestByPrefix(string $prefixstring $fallback): string
  43.         {
  44.             $publicDir $this->getParameter('kernel.project_dir') . '/public';
  45.             $dir       $publicDir '/uploads/cms/misc';
  46.             if (!is_dir($dir)) {
  47.                 return $fallback;
  48.             }
  49.             $files glob($dir '/' $prefix '*.{png,jpg,jpeg,webp}'GLOB_BRACE);
  50.             if (!$files) {
  51.                 return $fallback;
  52.             }
  53.             usort($files, static fn($a$b) => filemtime($b) <=> filemtime($a));
  54.             $abs $files[0];
  55.             $rel ltrim(str_replace($publicDir''$abs), '/');
  56.             return $rel ?: $fallback;
  57.         }
  58.     private function publicAssetExists(string $path): bool
  59.     {
  60.         $path trim($path);
  61.         if ($path === '') {
  62.             return false;
  63.         }
  64.         if (preg_match('#^https?://#i'$path)) {
  65.             return true;
  66.         }
  67.         $parsedPath parse_url($pathPHP_URL_PATH);
  68.         if (!is_string($parsedPath) || $parsedPath === '') {
  69.             return false;
  70.         }
  71.         $relative ltrim($parsedPath'/');
  72.         if ($relative === '') {
  73.             return false;
  74.         }
  75.         return is_file($this->getParameter('kernel.project_dir') . '/public/' $relative);
  76.     }
  77.     private function resolveKpiImage(string $keystring $prefixstring $fallback): string
  78.     {
  79.         $stored $this->readCms($key'');
  80.         if ($this->publicAssetExists($stored)) {
  81.             return ltrim((string) parse_url($storedPHP_URL_PATH), '/');
  82.         }
  83.         $latest $this->latestByPrefix($prefix'');
  84.         if ($this->publicAssetExists($latest)) {
  85.             return ltrim((string) parse_url($latestPHP_URL_PATH), '/');
  86.         }
  87.         return $fallback;
  88.     }
  89.     private function latestHeroBg(string $keystring $prefixstring $fallback): string
  90.     {
  91.         $stored $this->readCms($key'');
  92.         if ($this->publicAssetExists($stored)) {
  93.             return ltrim((string) parse_url($storedPHP_URL_PATH), '/');
  94.         }
  95.         $publicDir $this->getParameter('kernel.project_dir') . '/public';
  96.         $dir       $publicDir '/uploads/cms/misc';
  97.         if (!is_dir($dir)) {
  98.             return $fallback;
  99.         }
  100.         $pattern $dir '/' $prefix '*.{png,jpg,jpeg,webp}';
  101.         $files   glob($patternGLOB_BRACE);
  102.         if (!$files) {
  103.             return $fallback;
  104.         }
  105.         usort($files, static fn($a$b) => filemtime($b) <=> filemtime($a));
  106.         $abs $files[0];
  107.         $rel ltrim(str_replace($publicDir''$abs), '/');
  108.         return $rel ?: $fallback;
  109.     }
  110.     private function readCms(string $keystring $default ''): string
  111.     {
  112.         $base $this->getParameter('kernel.project_dir') . '/public/cms';
  113.         $file $base '/' $key '.txt';
  114.         if (is_file($file) && is_readable($file)) {
  115.             $c = @file_get_contents($file);
  116.             $c $c === false '' trim($c);
  117.             if ($c !== '') {
  118.                 return $c;
  119.             }
  120.         }
  121.         return $default;
  122.     }
  123.     private function writeCms(string $keystring $text): bool
  124.     {
  125.         $base $this->getParameter('kernel.project_dir') . '/public/cms';
  126.         if (!is_dir($base) && !@mkdir($base0775true) && !is_dir($base)) {
  127.             return false;
  128.         }
  129.         $file $base '/' $key '.txt';
  130.         $ok = @file_put_contents($file$text) !== false;
  131.         if ($ok) { @chmod($file0664); }
  132.         return $ok;
  133.     }
  134.     #[Route(path'/'name'homepage'methods: ['GET'])]
  135.     public function index(): Response
  136.     {
  137.         // ------------------ Defaults ------------------
  138.         $defaultCatalogueText 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  139.           incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
  140.           exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  141.           Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  142.           incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
  143.           exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  144.           incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
  145.           exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.';
  146.         // ------------------ Read dynamic texts ------------------
  147.         $catalogue_text_left $this->readCms('catalogue_text_left'$defaultCatalogueText);
  148.         $text_action_formation_card1 $this->readCms('text_action_formation_card1''Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  149.                                 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
  150.                                 exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.');
  151.         $text_action_formation_card2 $this->readCms('text_action_formation_card2''Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  152.                                 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
  153.                                 exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.');
  154.         $text_action_formation_card3 $this->readCms('text_action_formation_card3''Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  155.                                 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
  156.                                 exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.');
  157.         $text_actualite_du_moment_p1 $this->readCms('text_actualite_du_moment_p1''Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  158.                         incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
  159.                         exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.');
  160.         // ------------------ HERO------------------
  161.     $defaultRibbonText 'JEUDI 5 ET VENDREDI 6 DÉCEMBRE 2024 · HÔTEL ARAWAK · GOSIER, GUADELOUPE';
  162. //dd($this->readCms('hero_slide1_link', '#'));
  163. $hero = [
  164.     'slide1' => [
  165.         'bg'     => $this->latestHeroBg('hero_slide1_bg''hero1-''default'),
  166.         'kicker' => $this->readCms('hero_slide1_kicker''Colloque professionnel'),
  167.         'title'  => $this->readCms('hero_slide1_title'"Prendre en compte les besoins du patient<br/>ou de la personne accompagnée"),
  168.         'link'   => $this->readCms('hero_slide1_link''#'),
  169.         'cta'    => $this->readCms('hero_slide1_cta'"Je m'inscris"),
  170.         'ribbon' => $this->readCms('hero_slide1_ribbon'$defaultRibbonText),
  171.     ],
  172.     'slide2' => [
  173.         'bg'     => $this->latestHeroBg('hero_slide2_bg''hero2-''default'),
  174.         'kicker' => $this->readCms('hero_slide2_kicker''Nouvelles sessions'),
  175.         'title'  => $this->readCms('hero_slide2_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  176.         'cta'    => $this->readCms('hero_slide2_cta'"Je m'inscris"),
  177.         'link'   => $this->readCms('hero_slide2_link''#'),
  178.         'ribbon' => $this->readCms('hero_slide2_ribbon'$defaultRibbonText),
  179.     ],
  180.     'slide3' => [
  181.         'bg'     => $this->latestHeroBg('hero_slide3_bg''hero3-''default'),
  182.         'kicker' => $this->readCms('hero_slide3_kicker''Nouvelles sessions'),
  183.         'title'  => $this->readCms('hero_slide3_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  184.         'cta'    => $this->readCms('hero_slide3_cta'"Je m'inscris"),
  185.         'ribbon' => $this->readCms('hero_slide3_ribbon'$defaultRibbonText),
  186.         'link'   => $this->readCms('hero_slide3_link''#'),
  187.     ],
  188.     'slide4' => [
  189.         'bg'     => $this->latestHeroBg('hero_slide4_bg''hero4-''default'),
  190.         'kicker' => $this->readCms('hero_slide4_kicker''Nouvelles sessions'),
  191.         'title'  => $this->readCms('hero_slide4_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  192.         'cta'    => $this->readCms('hero_slide4_cta'"Je m'inscris"),
  193.         'ribbon' => $this->readCms('hero_slide4_ribbon'$defaultRibbonText),
  194.         'link'   => $this->readCms('hero_slide4_link''#'),
  195.     ],
  196.     'slide5' => [
  197.         'bg'     => $this->latestHeroBg('hero_slide5_bg''hero5-''default'),
  198.         'kicker' => $this->readCms('hero_slide5_kicker''Nouvelles sessions'),
  199.         'title'  => $this->readCms('hero_slide5_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  200.         'cta'    => $this->readCms('hero_slide5_cta'"Je m'inscris"),
  201.         'ribbon' => $this->readCms('hero_slide5_ribbon'$defaultRibbonText),
  202.         'link'   => $this->readCms('hero_slide5_link''#'),
  203.     ],
  204.     'slide6' => [
  205.         'bg'     => $this->latestHeroBg('hero_slide6_bg''hero6-''default'),
  206.         'kicker' => $this->readCms('hero_slide6_kicker''Nouvelles sessions'),
  207.         'title'  => $this->readCms('hero_slide6_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  208.         'cta'    => $this->readCms('hero_slide6_cta'"Je m'inscris"),
  209.         'ribbon' => $this->readCms('hero_slide6_ribbon'$defaultRibbonText),
  210.         'link'   => $this->readCms('hero_slide6_link''#'),
  211.     ],
  212.     'slide7' => [
  213.         'bg'     => $this->latestHeroBg('hero_slide7_bg''hero7-''default'),
  214.         'kicker' => $this->readCms('hero_slide7_kicker''Nouvelles sessions'),
  215.         'title'  => $this->readCms('hero_slide7_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  216.         'cta'    => $this->readCms('hero_slide7_cta'"Je m'inscris"),
  217.         'ribbon' => $this->readCms('hero_slide7_ribbon'$defaultRibbonText),
  218.         'link'   => $this->readCms('hero_slide7_link''#'),
  219.     ],
  220.     'slide8' => [
  221.         'bg'     => $this->latestHeroBg('hero_slide8_bg''hero8-''default'),
  222.         'kicker' => $this->readCms('hero_slide8_kicker''Nouvelles sessions'),
  223.         'title'  => $this->readCms('hero_slide8_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  224.         'cta'    => $this->readCms('hero_slide8_cta'"Je m'inscris"),
  225.         'ribbon' => $this->readCms('hero_slide8_ribbon'$defaultRibbonText),
  226.         'link'   => $this->readCms('hero_slide8_link''#'),
  227.     ],
  228.     'slide9' => [
  229.         'bg'     => $this->latestHeroBg('hero_slide9_bg''hero9-''default'),
  230.         'kicker' => $this->readCms('hero_slide9_kicker''Nouvelles sessions'),
  231.         'title'  => $this->readCms('hero_slide9_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  232.         'cta'    => $this->readCms('hero_slide9_cta'"Je m'inscris"),
  233.         'ribbon' => $this->readCms('hero_slide9_ribbon'$defaultRibbonText),
  234.         'link'   => $this->readCms('hero_slide9_link''#'),
  235.     ],
  236.     'slide10' => [
  237.         'bg'     => $this->latestHeroBg('hero_slide10_bg''hero10-''default'),
  238.         'kicker' => $this->readCms('hero_slide10_kicker''Nouvelles sessions'),
  239.         'title'  => $this->readCms('hero_slide10_title'"Perfectionnez vos compétences<br/>avec nos modules 2025"),
  240.         'cta'    => $this->readCms('hero_slide10_cta'"Je m'inscris"),
  241.         'ribbon' => $this->readCms('hero_slide10_ribbon'$defaultRibbonText),
  242.         'link'   => $this->readCms('hero_slide10_link''#'),
  243.     ],
  244. ];
  245.         // ------------------ CATALOGUE ------------------
  246.         $catalogue = [
  247.             'title'     => $this->readCms('catalogue_title''DÉCOUVREZ NOTRE NOUVEAU CATALOGUE DE CONSEIL ET DE FORMATION 2025'),
  248.             'text'      => $catalogue_text_left,
  249.             'btn_more'  => $this->readCms('catalogue_btn_more''Voir plus'),
  250.             'btn_cta'   => $this->readCms('catalogue_btn_cta'"Je m'inscris"),
  251.             'image'     => 'assets/img/pexels-thirdman-5684445.png',
  252.             'vtitle'    => $this->readCms('catalogue_vtitle''CATALOGUE'),
  253.             'vsubtitle' => $this->readCms('catalogue_vsubtitle''DE CONSEIL ET DE CONSULTATION'),
  254.             'year'      => $this->readCms('catalogue_year''2025'),
  255.             'badge'     => $this->readCms('catalogue_badge''VOTRE PARTENAIRE CONSEIL ET PRESTATION EN<br>GUADELOUPE, MARTINIQUE &amp; GUYANE'),
  256.         ];
  257.         // ------------------ COMPÉTENCES ------------------
  258.         $competences = [
  259.             'title'    => $this->readCms('competences_title''Nos domaines de compétences'),
  260.             'explorer' => $this->readCms('competences_cta''Explorer'),
  261.             'items'    => [
  262.                 ['label' => 'Productivité et éfficacité professionnelle''icon' => 'assets/img/icons/stonks.png'],
  263.                 ['label' => 'Élaboration et conduite de projet',         'icon' => 'assets/img/icons/eos-icons_content-lifecycle-management.png'],
  264.                 ['label' => 'Équipe de cadres et managers',              'icon' => 'assets/img/icons/ri_team-fill.png'],
  265.                 ['label' => 'Santé au travail',                          'icon' => 'assets/img/icons/mage_heart-health-fill.png'],
  266.                 ['label' => 'Gestion globale des risques, des crises & développement durable''icon' => 'assets/img/icons/graph.png'],
  267.                 ['label' => 'Management des ressources humaines',        'icon' => 'assets/img/icons/carbon_id-management.png'],
  268.                 ['label' => "Mobilisation & cohésion d'équipes",         'icon' => 'assets/img/icons/hugeicons_agreement-01.png'],
  269.                 ['label' => 'Organisation sociale & médico-sociale',     'icon' => 'assets/img/icons/ion_share-social-sharp.png'],
  270.                 ['label' => 'Évolution et transition professionnelle',   'icon' => 'assets/img/icons/ph_plant-bold.png'],
  271.             ],
  272.         ];
  273.         // ------------------ PRESTATIONS (3 cards) ------------------
  274.         $prestations = [
  275.             [
  276.                 'img'   => $this->readCms('presta_card1_img''assets/img/pexels-thirdman-5684445.png'),
  277.                 'title' => $this->readCms('presta_card1_title''Action de formation inter'),
  278.                 'text'  => $text_action_formation_card1,
  279.                 'cta'   => $this->readCms('presta_card1_cta''Voir plus'),
  280.                 'tone'  => 'tone-a',
  281.             ],
  282.             [
  283.                 'img'   => $this->readCms('presta_card2_img''assets/img/pexels-kindelmedia-7979405.png'),
  284.                 'title' => $this->readCms('presta_card2_title''Action de formation intra'),
  285.                 'text'  => $text_action_formation_card2,
  286.                 'cta'   => $this->readCms('presta_card2_cta''Voir plus'),
  287.                 'tone'  => 'tone-b',
  288.             ],
  289.             [
  290.                 'img'   => $this->readCms('presta_card3_img''assets/img/pexels-kindelmedia-7979435.png'),
  291.                 'title' => $this->readCms('presta_card3_title''Conseil & accompagnement'),
  292.                 'text'  => $text_action_formation_card3,
  293.                 'cta'   => $this->readCms('presta_card3_cta''Voir plus'),
  294.                 'tone'  => 'tone-c',
  295.             ],
  296.         ];
  297.         // ------------------ ACTU — TEXTS + IMAGES ------------------
  298.         $actu = [
  299.             'kicker'    => $this->readCms('actu_kicker''Actualités du moment'),
  300.             'title'     => $this->readCms('actu_title''Titre de l’actualité'),
  301.             'p1'        => $this->readCms('text_actualite_du_moment_p1''Lorem ipsum dolor sit amet, consectetur adipiscing elit.'),
  302.             'p2'        => $this->readCms('text_actualite_du_moment_p2''Lorem ipsum dolor sit amet, consectetur adipiscing elit.'),
  303.             'cta'       => $this->readCms('actu_cta''Découvrir'),
  304.             'pill1'     => $this->readCms('actu_pill1_label''Mieux Gérer'),
  305.             'bg_img'    => $this->readCms('actu_bg_img',    'assets/img/pexels-shkrabaanthony-5816297.png'),
  306.             'pill1_img' => $this->readCms('actu_pill1_img''assets/img/pexels-thirdman-5684445.png'),
  307.             'pill2_img' => $this->readCms('actu_pill2_img''assets/img/pexels-kindelmedia-7979405.png'),
  308.             'pill3_img' => $this->readCms('actu_pill3_img''assets/img/img conseiller.png'),
  309.         ];
  310.         // ------------------ TRUST (logos) ------------------
  311.         $trust = [
  312.             'kicker' => $this->readCms('trust_kicker''ILS NOUS FONT CONFIANCE !'),
  313.             'sub'    => $this->readCms('trust_sub''Labels & certification'),
  314.             'logos'  => [
  315.                 'assets/img/icons/2560px-La_Poste_logo.png',
  316.                 'assets/img/icons/2560px-La_Poste_logo.png',
  317.                 'assets/img/icons/2560px-La_Poste_logo.png',
  318.                 'assets/img/icons/2560px-La_Poste_logo.png',
  319.                 'assets/img/icons/2560px-La_Poste_logo.png',
  320.             ],
  321.         ];
  322.         // ------------------ CHIFFRES CLÉS ------------------
  323.         $kpis = [
  324.             'title' => $this->readCms('kpis_title''Nos chiffres clés !'),
  325.             'items' => [
  326.                 [
  327.                     'img'   => $this->resolveKpiImage('kpis_img1''c1-''assets/img/pexels-fauxels-3182831.png'),
  328.                     'value' => $this->readCms('chiffre_cles_card1_value''95%'),
  329.                     'label' => $this->readCms('chiffre_cles_card1_label''Taux de satisfaction pour l’année en cours'),
  330.                 ],
  331.                 [
  332.                     'img'   => $this->resolveKpiImage('kpis_img2''c2-''assets/img/pexels-growthgal-3719037.png'),
  333.                     'value' => $this->readCms('chiffre_cles_card2_value''95%'),
  334.                     'label' => $this->readCms('chiffre_cles_card2_label''Personnes certifiées de la formation'),
  335.                 ],
  336.                 [
  337.                     'img'   => $this->resolveKpiImage('kpis_img3''c3-''assets/img/pexels-shkrabaanthony-5292192.png'),
  338.                     'value' => $this->readCms('chiffre_cles_card3_value''67%'),
  339.                     'label' => $this->readCms('chiffre_cles_card3_label''Des demandes de contacts traitées'),
  340.                 ],
  341.                 [
  342.                     'img'   => $this->resolveKpiImage('kpis_img4''c4-''assets/img/pexels-shkrabaanthony-5292192.png'),
  343.                     'value' => $this->readCms('chiffre_cles_card4_value''27%'),
  344.                     'label' => $this->readCms('chiffre_cles_card4_label''Des demandes de contacts traitées'),
  345.                 ],
  346.                 [
  347.                     'img'   => $this->resolveKpiImage('kpis_img5''c5-''assets/img/pexels-shkrabaanthony-5292192.png'),
  348.                     'value' => $this->readCms('chiffre_cles_card5_value''27%'),
  349.                     'label' => $this->readCms('chiffre_cles_card5_label''Des demandes de contacts traitées'),
  350.                 ],
  351.             ],
  352.         ];
  353.         // ------------------ Render ------------------
  354.         return $this->render('home/index.html.twig', [
  355.             'title' => 'Accueil - ACOA',
  356.             // legacy vars 
  357.             'catalogue_text_left'         => $catalogue_text_left,
  358.             'text_action_formation_card1' => $text_action_formation_card1,
  359.             'text_action_formation_card2' => $text_action_formation_card2,
  360.             'text_action_formation_card3' => $text_action_formation_card3,
  361.             'text_actualite_du_moment_p1' => $text_actualite_du_moment_p1
  362.             'text_actualite_du_moment_p2' => $actu['p2'],
  363.             'chiffre_cles_card1_value'    => $kpis['items'][0]['value'],
  364.             'chiffre_cles_card1_label'    => $kpis['items'][0]['label'],
  365.             'chiffre_cles_card2_value'    => $kpis['items'][1]['value'],
  366.             'chiffre_cles_card2_label'    => $kpis['items'][1]['label'],
  367.             'chiffre_cles_card3_value'    => $kpis['items'][2]['value'],
  368.             'chiffre_cles_card3_label'    => $kpis['items'][2]['label'],
  369.             // structured vars
  370.             'hero'        => $hero,
  371.             'catalogue'   => $catalogue,
  372.             'competences' => $competences,
  373.             'prestations' => $prestations,
  374.             'actu'        => $actu,
  375.             'trust'       => $trust,
  376.             'kpis'        => $kpis,
  377.         ]);
  378.     }
  379.     // ------------------ Text & image endpoints ------------------
  380.     #[Route('/cms/text'name'cms_text_update'methods: ['POST'])]
  381.     public function updateText(Request $request): JsonResponse
  382.     {
  383.         $data json_decode($request->getContent(), true) ?? [];
  384.         $key  preg_replace('/[^a-z0-9_]/i''', (string)($data['key'] ?? ''));
  385.         $text trim((string)($data['text'] ?? ''));
  386.         if ($key === '' || $text === '') {
  387.             return new JsonResponse(['ok' => false'error' => 'Paramètres invalides'], 422);
  388.         }
  389.       $allowed = [
  390.     'hero_slide1_kicker','hero_slide1_title','hero_slide1_cta','hero_slide1_bg','hero_slide1_ribbon','hero_slide1_link',
  391.     'hero_slide2_kicker','hero_slide2_title','hero_slide2_cta','hero_slide2_bg','hero_slide2_ribbon','hero_slide2_link',
  392.     'hero_slide3_kicker','hero_slide3_title','hero_slide3_cta','hero_slide3_bg','hero_slide3_ribbon','hero_slide3_link',
  393.     'hero_slide4_kicker','hero_slide4_title','hero_slide4_cta','hero_slide4_bg','hero_slide4_ribbon','hero_slide4_link',
  394.     'hero_slide5_kicker','hero_slide5_title','hero_slide5_cta','hero_slide5_bg','hero_slide5_ribbon','hero_slide5_link',
  395.     'hero_slide6_kicker','hero_slide6_title','hero_slide6_cta','hero_slide6_bg','hero_slide6_ribbon','hero_slide6_link',
  396.     'hero_slide7_kicker','hero_slide7_title','hero_slide7_cta','hero_slide7_bg','hero_slide7_ribbon','hero_slide7_link',
  397.     'hero_slide8_kicker','hero_slide8_title','hero_slide8_cta','hero_slide8_bg','hero_slide8_ribbon','hero_slide8_link',
  398.     'hero_slide9_kicker','hero_slide9_title','hero_slide9_cta','hero_slide9_bg','hero_slide9_ribbon','hero_slide9_link',
  399.     'hero_slide10_kicker','hero_slide10_title','hero_slide10_cta','hero_slide10_bg','hero_slide10_ribbon','hero_slide10_link',
  400.     'catalogue_title','catalogue_text_left','catalogue_btn_more','catalogue_btn_cta',
  401.     'catalogue_vtitle','catalogue_vsubtitle','catalogue_year','catalogue_badge',
  402.     'presta_card1_title','presta_card1_cta','presta_card1_img',
  403.     'presta_card2_title','presta_card2_cta','presta_card2_img',
  404.     'presta_card3_title','presta_card3_cta','presta_card3_img',
  405.     'text_action_formation_card1','text_action_formation_card2','text_action_formation_card3',
  406.     'actu_kicker','actu_title','text_actualite_du_moment_p1','text_actualite_du_moment_p2',
  407.     'actu_cta','actu_pill1_label',
  408.     'actu_bg_img','actu_pill1_img','actu_pill2_img','actu_pill3_img',
  409.     'trust_kicker','trust_sub',
  410.     'kpis_title',
  411.     'kpis_img1','kpis_img2','kpis_img3','kpis_img4','kpis_img5',
  412.     'chiffre_cles_card1_value','chiffre_cles_card1_label',
  413.     'chiffre_cles_card2_value','chiffre_cles_card2_label',
  414.     'chiffre_cles_card3_value','chiffre_cles_card3_label',
  415.     'chiffre_cles_card4_value','chiffre_cles_card4_label',
  416.     'chiffre_cles_card5_value','chiffre_cles_card5_label',
  417. ];
  418.         if (!in_array($key$allowedtrue)) {
  419.             return new JsonResponse(['ok' => false'error' => 'Clé non autorisée'], 403);
  420.         }
  421.         if (!$this->writeCms($key$text)) {
  422.             return new JsonResponse(['ok' => false'error' => "Écriture impossible"], 500);
  423.         }
  424.         return new JsonResponse(['ok' => true'key' => $key'text' => $text]);
  425.     }
  426.     #[Route('/cms/media'name'cms_page_media_upload'methods: ['POST'])]
  427.     public function uploadMedia(Request $request): JsonResponse
  428.     {
  429.         /** @var UploadedFile|null $file */
  430.         $file $request->files->get('file');
  431.         $key  preg_replace('/[^a-z0-9_]/i''', (string)$request->request->get('key'''));
  432.         if (!$file instanceof UploadedFile || $key === '') {
  433.             return new JsonResponse(['ok' => false'error' => 'Fichier ou clé manquants'], 422);
  434.         }
  435.         $allowedKeys = [
  436.             'hero_slide1_bg','hero_slide2_bg','hero_slide3_bg','hero_slide4_bg','hero_slide5_bg',
  437.             'hero_slide6_bg','hero_slide7_bg','hero_slide8_bg','hero_slide9_bg','hero_slide10_bg',
  438.             'presta_card1_img','presta_card2_img','presta_card3_img',
  439.             'actu_bg_img','actu_pill1_img','actu_pill2_img','actu_pill3_img',
  440.             'kpis_img1','kpis_img2','kpis_img3','kpis_img4','kpis_img5',
  441.         ];
  442.         if (!in_array($key$allowedKeystrue)) {
  443.             return new JsonResponse(['ok' => false'error' => 'Clé non autorisée'], 403);
  444.         }
  445.         if (!$this->isAllowedCmsImageUpload($file)) {
  446.             return new JsonResponse(['ok' => false'error' => 'Format non supporté (png, jpg, webp)'], 415);
  447.         }
  448.         $projectDir $this->getParameter('kernel.project_dir');
  449.         $subdir 'uploads/cms/' date('Y/m');
  450.         $targetDir $projectDir '/public/' $subdir;
  451.         if (!is_dir($targetDir) && !@mkdir($targetDir0775true) && !is_dir($targetDir)) {
  452.             return new JsonResponse(['ok' => false'error' => 'Impossible de créer le dossier'], 500);
  453.         }
  454.         $ext $this->safeUploadExtension($file);
  455.         $originalName pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
  456.         $sanitizedName preg_replace('/[^a-z0-9]+/i''-', (string) $originalName);
  457.         $sanitizedName trim((string) $sanitizedName'-');
  458.         $basename $sanitizedName !== ''
  459.             strtolower($sanitizedName)
  460.             : 'img-' date('Ymd-His') . '-' substr(sha1(uniqid(''true)), 08);
  461.         $filename $basename '.' $ext;
  462.         try {
  463.             $file->move($targetDir$filename);
  464.             @chmod($targetDir '/' $filename0664);
  465.         } catch (\Throwable $e) {
  466.             return new JsonResponse([
  467.                 'ok' => false,
  468.                 'error' => 'Upload échoué',
  469.                 'detail' => $e->getMessage(),
  470.                 'targetDir' => $targetDir,
  471.             ], 500);
  472.         }
  473.         $relativePath $subdir '/' $filename;
  474.         if (!$this->writeCms($key$relativePath)) {
  475.             return new JsonResponse(['ok' => false'error' => 'Upload enregistré mais liaison CMS impossible'], 500);
  476.         }
  477.         return new JsonResponse(['ok' => true'path' => $relativePath'key' => $key]);
  478.     }
  479.     // ------------------ (Optional) legacy route kept for backward compatibility ------------------
  480.     #[Route('/cms/catalogue-text'name'catalogue_text_update'methods: ['POST'])]
  481.     public function updateCatalogueTextLegacy(Request $request): JsonResponse
  482.     {
  483.         $data json_decode($request->getContent(), true);
  484.         $text trim((string)($data['text'] ?? ''));
  485.         if ($text === '') {
  486.             return new JsonResponse(['ok' => false'error' => 'Texte vide'], 422);
  487.         }
  488.         if (!$this->writeCms('catalogue_text_left'$text)) {
  489.             return new JsonResponse(['ok' => false'error' => "Impossible d'écrire le fichier"], 500);
  490.         }
  491.         return new JsonResponse(['ok' => true'text' => $text]);
  492.     }
  493. }