src/Twig/Runtime/PlatformComponentRuntime.php line 413

Open in your IDE?
  1. <?php
  2.     namespace App\Twig\Runtime;
  3.     use App\Entity\User;
  4.     use App\Services\Back\Settings\FrontService;
  5.     use Exception;
  6.     use JsonException;
  7.     use Psr\Log\LoggerInterface;
  8.     use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  9.     use Symfony\Component\HttpKernel\KernelInterface;
  10.     use Symfony\Component\Security\Core\Security;
  11.     use Twig\Environment;
  12.     use Twig\Error\LoaderError;
  13.     use Twig\Error\RuntimeError;
  14.     use Twig\Error\SyntaxError;
  15.     use Twig\Extension\RuntimeExtensionInterface;
  16.     class PlatformComponentRuntime implements RuntimeExtensionInterface
  17.     {
  18.         private ParameterBagInterface $params;
  19.         private Environment           $twig;
  20.         private Security              $security;
  21.         private LoggerInterface       $logger;
  22.         private FrontService          $frontService;
  23.         private KernelInterface       $kernel;
  24.         private string                $projectDir;
  25.         /**
  26.          * @param ParameterBagInterface $params
  27.          * @param Environment           $twig
  28.          * @param Security              $security
  29.          * @param LoggerInterface       $logger
  30.          * @param FrontService          $frontService
  31.          * @param KernelInterface       $kernel
  32.          * @param string                $projectDir
  33.          */
  34.         public function __construct(
  35.                 ParameterBagInterface $params,
  36.                 Environment $twig,
  37.                 Security $security,
  38.                 LoggerInterface $logger,
  39.                 FrontService $frontService,
  40.                 KernelInterface $kernel,
  41.                 string $projectDir
  42.         ) {
  43.             $this->params       $params;
  44.             $this->twig         $twig;
  45.             $this->security     $security;
  46.             $this->logger       $logger;
  47.             $this->frontService $frontService;
  48.             $this->projectDir   $projectDir;
  49.             $this->kernel       $kernel;
  50.         }
  51.         /**
  52.          * Retourne le contenu d'un component
  53.          *
  54.          * @TODO Attention,si on passe le $component comme un tableau avec les valeurs du yaml !
  55.          * On ne peut pas utiliser le système d'ACL dynamique
  56.          *
  57.          * @param string|array $component
  58.          * @param string       $componentKey
  59.          * @param null         $data
  60.          * @param null         $index
  61.          * @param bool         $debug
  62.          *
  63.          * @return string
  64.          *
  65.          * @throws JsonException
  66.          */
  67.         public function component($componentstring $componentKey ''$data NULL$index NULLbool $debug FALSE): string
  68.         {
  69.             $keys $componentKey;
  70.             if (is_array($component) && isset($component'type' ])) {
  71.                 $value $component;
  72.             } else {
  73.                 $keyArr explode('.'$keys);
  74.                 if (count($keyArr) === 2) {
  75.                     $data  $this->getContentData($keyArr], $keyArr]);
  76.                     $value $data'pageArray' ];
  77.                 } else {
  78.                     $value $this->checkComponent($component$keys$index);
  79.                 }
  80.             }
  81.             if (!isset($value'type' ])) {
  82.                 return '<div class="text-danger">The component ' $component ' is typeless!</div>';
  83.             }
  84. //            try {
  85.                 return $this->buildComponent($value$keys$data$debug);
  86. //            }
  87. //            catch (LoaderError|RuntimeError|SyntaxError $e)
  88. //            {
  89. //                if($this->kernel->getEnvironment() == 'dev') throw $e;
  90. //
  91. //                $message = 'Le component ' . (is_array($keys) ? implode('.', $keys) : $value[ 'type' ]) . ' ne peut pas être généré.';
  92. //                $this->logger->critical($message . $e->getMessage());
  93. //
  94. //                $error = '<div class="text-danger">' . $message;
  95. //                /** @var User $currentUser */
  96. //                $currentUser = $this->security->getUser();
  97. //
  98. //                if (($currentUser !== NULL && $currentUser->isDeveloper()) || $this->kernel->getEnvironment() === 'dev') {
  99. //                    $error .= '<pre>' . $e->getMessage() . '</pre>';
  100. //                }
  101. //                $error .= '</div>';
  102. //                return $error;
  103. //            }
  104.         }
  105.         /**
  106.          * @param string $page
  107.          * @param        $data
  108.          * @param bool   $isSecurity
  109.          *
  110.          * @return string
  111.          *
  112.          * @throws JsonException
  113.          */
  114.         public function content(string $page$data NULLbool $isSecurity FALSEstring $key null): string
  115.         {
  116.             $frontType   $isSecurity 'security' 'content';
  117.             $contentData $this->getContentData($page$frontType);
  118.             $pageArray   $contentData'pageArray' ] ?? [];
  119.             $frontCat    $contentData'frontCat' ];
  120.             $divs $this->getContentStartDivs($pageArray);
  121.             $content '';
  122.             if($key) {
  123.                 $content .= $this->component($pageArray[$key], $contentData'contentKey' ] . '.sections.' $key$dataNULLTRUE);
  124.                 return implode(''$divs) . $content str_repeat('</div>'count($divs));
  125.             }
  126.             $items $pageArray'sections' ];
  127.             foreach ($items as $key => $item)
  128.             {
  129. //                try {
  130.                     $content .= $this->component($item$contentData'contentKey' ] . '.sections.' $key$dataNULLTRUE);
  131.                     // $content .= $this->component( $frontCat . '.' . $page . '.sections.' . $key, $data );
  132. //                } catch (Exception $e) {
  133. //                    /** @var User $currentUser */
  134. //                    $currentUser = $this->security->getUser();
  135. //                    if ($currentUser !== NULL && $currentUser->isDeveloper()) {
  136. //                        echo '<div style="border: 1px red solid; padding:8px; color:red; text-align:center">' .
  137. //                             '<strong>' . $frontCat . '.' . $page . '.sections.' . $key . '</strong><br>' .
  138. //                             $e->getMessage() .
  139. //                             '</div>';
  140. //                    }
  141. //                }
  142.             }
  143.             return implode(''$divs) . $content str_repeat('</div>'count($divs));
  144.         }
  145.         /**
  146.          * @param $item
  147.          *
  148.          * @return array
  149.          */
  150.         public function getItemData($item): array
  151.         {
  152.             $result = [];
  153.             if (isset($item'data' ]) && count($item'data' ]) > 0) {
  154.                 foreach ($item'data' ] as $k => $v) {
  155.                     $result'data-' str_replace('_''-'$k) ] = $v;
  156.                 }
  157.             }
  158.             return $result;
  159.         }
  160.         /**
  161.          * Retourne le tableau permettant la génération dynamique d'un élément en twig (wrapper, item, container)
  162.          *
  163.          * Les components doivent être configuré avec les éléments suivants :
  164.          *
  165.          * mon_component:
  166.          *     type: mon_type_de_component
  167.          *     wrapper:  <== va gérer une div qui engloble le component
  168.          *         class: ""
  169.          *     class: "" <== va gérer la class du component
  170.          *      container:  <== va gérer une div interne au component qui va contenir les sous-éléments du component
  171.          *          class: ""
  172.          *
  173.          *  <div class="ma-classe-wrapper" + autres éléments dans wrapper>
  174.          *      <div class="ma-classe" + autres élément>
  175.          *           <div class="ma-classe-container" + autres éléments dans container>
  176.          *
  177.          * Cette configuration permet une plus grande souplesse pour organiser les éléments via les class bootstrap
  178.          *
  179.          * @param array|string $item tableau contenant les données de l'élément, si c'est une string, c'est pour maintenir l'ancien système
  180.          * @param string       $key  clé du data-component-acl pour son identification
  181.          * @param bool         $debug
  182.          *
  183.          * @return array tableau contenant les informations
  184.          *
  185.          *               id => si le component doit avoir un id, '' par défaut
  186.          *               class => class de l'élément, '' par défaut
  187.          *               data => tableau qui contient tous les éléments data de l'élément et leur valeur (data-foo="bla"), [] par défaut
  188.          *               tag => le tag de l'élément si c'est précisé, div par défaut
  189.          *               style => tableau si des éléments doivent être passé dans style (style="background:red"), défaut []
  190.          *               enabled => Bool pour savoir si l'élément s'affiche ou non, défaut TRUE
  191.          *               display => tableau qui gère l'affichage par addition ou soustraction sur des pages, défaut []
  192.          *               univers => uniquement si des datas sont passée dans l'item
  193.          */
  194.         public function generateDomOption($itemstring $key ''bool $debug false): array
  195.         {
  196.             $result = [
  197.                     'id'    => '',
  198.                     'data'  => [],
  199.                     'tag'   => 'div',
  200.                     'style' => [],
  201.                     'enabled' => true,
  202.                     'display' => []
  203.             ];
  204.             // $item n'est pas un array (ancien système → wrapper correspond à la class)
  205.             if (!is_array($item)) {
  206.                 $result'class' ] = $item;
  207.                 return $result;
  208.             }
  209.             $result'class' ] = $this->getClassForItem($item);
  210.             $result'id' ]    = $item'id' ] ?? $result'id' ];
  211.             $result'tag' ]   = $item'tag' ] ?? $result'tag' ];
  212.             if (isset($item'data' ]) && $item'data' ] !== []) {
  213.                 $result'data' ] = $this->getItemData($item);
  214.             }
  215.             if ($key !== '') {
  216.                 $result'data' ][ 'data-component-acl' ] = $key;
  217.                 $result'enabled' ]                      = $item'enabled' ] ?? TRUE;
  218.                 $result'display' ]                      = $item'display' ] ?? [];
  219.                 if (isset($item'univers' ]) && $item'univers' ] !== []) {
  220.                     $result'univers' ] = $item'univers' ];
  221.                 }
  222.             }
  223.             if (isset($item'style' ]) && $item'style' ] !== []) {
  224.                 foreach ($item'style' ] as $rule => $value) {
  225.                     $result'style' ][ str_replace('_''-'$rule) ] = $value;
  226.                 }
  227.             }
  228.             return $result;
  229.         }
  230.         /**
  231.          * Génère le tableau permettant la création dynamique d'un atom dans le twig
  232.          *
  233.          * Pour un atom, c'est la clef wrapper qui va prendre le data-acl-component
  234.          *
  235.          * @param array|null  $atom tableau contenant les data de l'atom TODO gerer un toArray si on passe un objet
  236.          * @param string|null $key  clé identifiant l'atom pour les ACL
  237.          * @param bool        $debug
  238.          *
  239.          * @return array
  240.          */
  241.         public function generateAtomOptions(?array $atom, ?string $key ''bool $debug FALSE): array
  242.         {
  243.             // wrapper n'existe pas, ou est null, ou n'est pas un tableau
  244.             switch (TRUE) {
  245.                 case !isset($atom'wrapper' ]):
  246.                     $wrapper = [];
  247.                     break;
  248.                 case is_string($atom'wrapper' ]):
  249.                     $wrapper = [
  250.                             'class' => $atom'wrapper' ],
  251.                     ];
  252.                     break;
  253.                 default:
  254.                     $wrapper $atom'wrapper' ];
  255.                     break;
  256.             }
  257.             $result array_merge(
  258.                     [
  259.                             'enabled' => $atom'enabled' ] ?? TRUE,
  260.                     ],
  261.                     $wrapper,
  262.             );
  263.             return $this->generateDomOption($result$key);
  264.         }
  265.         /**
  266.          * @param             $component
  267.          * @param string|null $key
  268.          *
  269.          * @return array
  270.          */
  271.         public function generateComponentOptions($component, ?string $key ''): array
  272.         {
  273.             $key $key ?? '';
  274.             // WRAPPER
  275.             $wrapperKey 'wrapper';
  276.             $wrapper    = isset($component$wrapperKey ]) ? $this->generateDomOption($component$wrapperKey ]) : $this->generateDomOption([]);
  277.             // ITEM
  278.             $item $this->generateDomOption($component$key);
  279.             // CONTAINER
  280.             $containerKey 'container';
  281.             $container    = isset($component$containerKey ]) ? $this->generateDomOption($component$containerKey ]) : $this->generateDomOption([]);
  282.             return [
  283.                     'wrapper'   => $wrapper,
  284.                     'item'      => $item,
  285.                     'container' => $container,
  286.             ];
  287.         }
  288.         /**
  289.          * @param string      $keys
  290.          * @param array|null  $platform
  291.          * @param string|null $lastKeyPlatform
  292.          *
  293.          * @return array|mixed
  294.          *
  295.          * @throws JsonException
  296.          */
  297.         public function getFrontDataFromSettingOrYaml(string $keys, ?array $platform, ?string $lastKeyPlatform NULL)
  298.         {
  299.             return $this->frontService->getFrontDataFromSettingOrYaml($keys$platform$lastKeyPlatform);
  300.         }
  301.         /**
  302.          * TODO Vérifier son utilisation, pour le moment uniquement sur default_progression_status_step.html.twig
  303.          *
  304.          * @param $atomic_component
  305.          *
  306.          * @return string
  307.          *
  308.          * @throws LoaderError
  309.          * @throws RuntimeError
  310.          * @throws SyntaxError
  311.          */
  312.         public function customAtomicContent($atomic_component): string
  313.         {
  314.             $folder '/templates/platform/component';
  315.             if (file_exists($this->projectDir $folder '/atom/' $atomic_component '.html.twig')) {
  316.                 $view 'platform/component/atom/' $atomic_component '.html.twig';
  317.             } elseif (file_exists($this->projectDir $folder '/molecule/' $atomic_component '.html.twig')) {
  318.                 $view 'platform/component/molecule/' $atomic_component '.html.twig';
  319.             } elseif (file_exists($this->projectDir $folder '/organism/' $atomic_component '.html.twig')) {
  320.                 $view 'platform/component/organism/' $atomic_component '.html.twig';
  321.             } else {
  322.                 return $atomic_component ' not found !';
  323.             }
  324.             return $this->twig->render($view);
  325.         }
  326.         /**
  327.          * @param $component
  328.          * @param $keys
  329.          * @param $index
  330.          *
  331.          * @return mixed
  332.          */
  333.         private function checkComponent($component, &$keys$index)
  334.         {
  335.             $platform $this->params->get('platform');
  336.             $value $platform;
  337.             $keys explode('.'$component);
  338.             $i 1;
  339.             foreach ($keys as $key) {
  340.                 if (!isset($value$key ]) &&
  341.                     !isset($value'global' ][ $key ]) &&
  342.                     !isset($value'front' ][ $key ]) &&
  343.                     !isset($value'back_office' ][ $key ])) {
  344.                     // On affiche l'erreur de key non trouvée que pour les développeurs.
  345.                     // En prod et pour les autres utilisateurs, on n'affiche rien (une erreur log est générée néanmoins).
  346.                     /** @var User $currentUser */
  347.                     $currentUser $this->security->getUser();
  348.                     if ($currentUser !== NULL && $currentUser->isDeveloper()) {
  349.                         return "key '$key' of '$component' does not exist";
  350.                     }
  351.                     $this->logger->error("key '$key' of '$component' does not exist");
  352.                     return '';
  353.                 }
  354.                 if (isset($value'global' ][ $key ])) {
  355.                     $value $value'global' ][ $key ];
  356.                 } elseif (isset($value'front' ][ $key ])) {
  357.                     $value $value'front' ][ $key ];
  358.                 } elseif (isset($value'back_office' ][ $key ])) {
  359.                     $value $value'back_office' ][ $key ];
  360.                 } else {
  361.                     $value $value$key ];
  362.                 }
  363.                 // si un index est passé et qu'on est à la dernière clé, on va chercher l'objet à l'index donné.
  364.                 if (NULL !== $index && $i === count($keys)) {
  365.                     $value $value$index ];
  366.                 }
  367.                 $i++;
  368.             }
  369.             return $value;
  370.         }
  371.         /**
  372.          * @throws SyntaxError
  373.          * @throws RuntimeError
  374.          * @throws LoaderError
  375.          */
  376.         private function buildComponent($value$keys$data$debug FALSE): string
  377.         {
  378.             $response '<div class="text-danger">' $value'type' ] . ' not found in components !</div>';
  379.             /** @var User $currentUser */
  380.             $currentUser $this->security->getUser();
  381.             $componentAclFullKey is_array($keys) ? implode('.'$keys) : $keys;
  382.             $folder '/templates/platform/component';
  383.             if (!(isset($value'disabled' ]) && $value'disabled' ] === TRUE)) {
  384.                 if (file_exists($this->projectDir $folder '/atom/' $value'type' ] . '.html.twig')) {
  385.                     $view 'platform/component/atom/' $value'type' ] . '.html.twig';
  386.                 } elseif (file_exists($this->projectDir $folder '/molecule/' $value'type' ] . '.html.twig')) {
  387.                     $view 'platform/component/molecule/' $value'type' ] . '.html.twig';
  388.                 } elseif (file_exists($this->projectDir $folder '/organism/' $value'type' ] . '.html.twig')) {
  389.                     $view 'platform/component/organism/' $value'type' ] . '.html.twig';
  390.                 }
  391.                 if (isset($view)) {
  392.                     $response $this->twig->render($view, [
  393.                             'value'        => $value,
  394.                             'data'         => $data,
  395.                             'componentKey' => $componentAclFullKey,
  396.                     ]);
  397.                 }
  398.             }
  399.             if (isset($view) && $currentUser !== NULL && $currentUser->isDeveloper()) {
  400.                 $response "\n<!-- ***** START component " $value'type' ] . " : " $view " ***** -->\n" .
  401.                             $response .
  402.                             "\n<!-- ***** END component " $value'type' ] . " ***** -->\n";
  403.             }
  404.             return $response;
  405.         }
  406.         /**
  407.          * @param string $page
  408.          * @param string $frontType
  409.          *
  410.          * @return array
  411.          * @throws JsonException
  412.          */
  413.         private function getContentData(string $pagestring $frontType): array
  414.         {
  415.             if (!in_array($frontType, ['security''common''content'])) {
  416.                 $frontType 'content';
  417.             }
  418.             // on regarde si on a des données en BDD pour cette page
  419.             $fromBdd $this->frontService->getArrayDataFromSetting('front.' $frontType '.' $page);
  420.             if ($fromBdd !== []) {
  421.                 $pageArray $fromBdd;
  422.             } else {
  423.                 $platform  $this->params->get('platform');
  424.                 $pageArray $platform'front' ][ $frontType ];
  425.             }
  426.             $testPage explode('.'$page);
  427.             if (count($testPage) > 1) {
  428.                 foreach ($testPage as $item) {
  429.                     $pageArray $pageArray$item ];
  430.                 }
  431.             } else {
  432.                 $pageArray $pageArray$page ];
  433.             }
  434.             return [
  435.                     'pageArray'  => $pageArray,
  436.                     'frontCat'   => $frontType,
  437.                     'contentKey' => $frontType '.' $page,
  438.             ];
  439.         }
  440.         /**
  441.          * Génère les div d'ouverture lorsque content() est appelé
  442.          *
  443.          * TODO à revoir pour verrouiller
  444.          *
  445.          * container peut avoir plusieurs valeurs
  446.          * - TRUE => on ajoute une div class="container" au debut
  447.          * - container => on ajoute une div class="container" au debut
  448.          * - container-fluid => on ajoute une div class="container-fluid" au debut
  449.          * - fluid => on ajoute une div class="container-fluid" au debut
  450.          *
  451.          * si la clef row existe et n'est pas FALSE => on rajoute une div class="row" après le container
  452.          * si la clef row existe et n'est pas FALSE et que la clef row_justify existe => on rajoute une div class="row [valeur de row_justify]" après le container
  453.          *
  454.          * @param array $pageArray
  455.          *
  456.          * @return array
  457.          */
  458.         private function getContentStartDivs(array $pageArray): array
  459.         {
  460.             $divs = [];
  461.             // 3 cas possibles
  462.             // la clef n'existe pas ou est à false → pas de container
  463.             if (isset($pageArray'container' ])) {
  464.                 // si la clef est à true ou "container" → container
  465.                 if (in_array(
  466.                         $pageArray'container' ],
  467.                         [TRUE'container'],
  468.                         TRUE
  469.                 )) {
  470.                     $divs[] = '<div class="container">';
  471.                     // si la clef est à "fluid" ou "container-fluid" => container-fluid
  472.                 } elseif (in_array(
  473.                         $pageArray'container' ],
  474.                         ['container-fluid''fluid']
  475.                 )) {
  476.                     $divs[] = '<div class="container-fluid">';
  477.                 }
  478.             }
  479.             if (
  480.                     isset($pageArray'row' ])
  481.                     && $pageArray'row' ] !== FALSE
  482.             ) {
  483.                 $row_justify $pageArray'row_justify' ] ?? '';
  484.                 $divs[]      = '<div class="row ' $row_justify '">';
  485.             }
  486.             return $divs;
  487.         }
  488.         private function getClassForItem($item)
  489.         {
  490.             $allClass $item'class' ] ?? '';
  491.             $allClassArray explode(' '$allClass);
  492.             $classCatArr = [];
  493.             if (isset($item'class_category' ])) {
  494.                 foreach ($item'class_category' ] as $key => $value) {
  495.                     $classCatArr$key ] = !is_array($value) ? explode(' '$value) : $value;
  496.                 }
  497.             }
  498.             $merged   array_merge($allClassArray, ...array_values($classCatArr));
  499.             $allClass array_unique($merged);
  500.             return implode(' '$allClass);
  501.         }
  502.         public function urlExist($url): bool
  503.         {
  504.             stream_context_set_default( [
  505.                     'ssl' => [
  506.                             'verify_peer' => false,
  507.                             'verify_peer_name' => false,
  508.                     ],
  509.             ]);
  510.             $headers get_headers($url);
  511.             return (bool)stripos($headers], "200 OK");
  512.         }
  513.     }