src/Twig/Runtime/PlatformComponentRuntime.php line 434

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($component, string $componentKey = '', $data = NULL, $index = NULL, bool $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[ 1 ], $keyArr[ 0 ]);
  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 = NULL, bool $isSecurity = FALSE, string $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, $data, NULL, TRUE);
  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, $data, NULL, TRUE);
  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($item, string $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 $page, string $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[ 0 ], "200 OK");
  512. }
  513. }