vendor/boldr/cms-bundle/src/Twig/TwigExtension.php line 429

Open in your IDE?
  1. <?php
  2. namespace Boldr\Cms\CmsBundle\Twig;
  3. use Boldr\Cms\CmsBundle\SiteConfiguration;
  4. use Boldr\Cms\CmsBundle\Upload\UploadStorageManager;
  5. use Boldr\Cms\CmsBundle\Presenter\Menu\{ MenuPresenter, MenuView };
  6. use Boldr\Cms\CmsBundle\Entity\{ Upload, Menu };
  7. use Boldr\Cms\CmsBundle\Controller\PermalinkController;
  8. use Boldr\Cms\CmsBundle\Presenter\Attachment\AttachmentPresenterInterface;
  9. use Boldr\Cms\CmsBundle\Search\SearchConfigurationPermalinkable;
  10. use Boldr\Cms\CmsBundle\Link\{ LinkInterface };
  11. use Boldr\Cms\CmsBundle\Permalink\PermalinkableInterface;
  12. use Boldr\Cms\CmsBundle\Content\{ ContentManager, Assets };
  13. use Boldr\Cms\CmsBundle\Renderer\{ HtmlRenderer };
  14. use Boldr\Cms\CmsBundle\Cookies\CookieManager;
  15. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  16. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  17. use Symfony\Component\HttpFoundation\RequestStack;
  18. use Symfony\Contracts\Cache\TagAwareCacheInterface;
  19. use Symfony\Component\Serializer\Serializer;
  20. use Symfony\Component\Serializer\Encoder\JsonEncoder;
  21. use Twig\{ TwigFunction, Environment };
  22. use Twig\Extension\AbstractExtension;
  23. use Doctrine\ORM\EntityManagerInterface;
  24. use Psr\Container\ContainerInterface;
  25. use Psr\Http\Client\ClientInterface;
  26. use Psr\Http\Message\{ RequestFactoryInterface, UriFactoryInterface };
  27. use Embed\Embed;
  28. use Embed\Http\Crawler;
  29. use Exception;
  30. use Throwable;
  31. use MatthiasMullie\Minify;
  32. class TwigExtension extends AbstractExtension
  33. {
  34. public function __construct(
  35. private readonly ParameterBagInterface $parameterBag,
  36. private readonly EntityManagerInterface $em,
  37. private readonly RequestStack $requestStack,
  38. private readonly MenuPresenter $menuPresenter,
  39. private readonly PermalinkController $permalinkController,
  40. private readonly UrlGeneratorInterface $urlGenerator,
  41. private readonly TagAwareCacheInterface $cache,
  42. private readonly UploadStorageManager $uploadStorageManager,
  43. private readonly SiteConfiguration $siteConfiguration,
  44. private readonly AttachmentPresenterInterface $attachmentPresenter,
  45. private readonly ClientInterface $httpClient,
  46. private readonly RequestFactoryInterface $requestFactory,
  47. private readonly UriFactoryInterface $uriFactory,
  48. private readonly HtmlRenderer $htmlRenderer,
  49. private readonly ContentManager $contentManager,
  50. private readonly CookieManager $cookieManager,
  51. /** @var iterable<AssetProviderInterface> */
  52. private readonly iterable $assetProviders,
  53. private readonly ContainerInterface $linkGenerators,
  54. ) {}
  55. public function getFunctions()
  56. {
  57. $functions = [];
  58. $functions[] = new TwigFunction('boldr_cms_content_render', [$this, 'renderContent'], ['is_safe' => ['html'], 'needs_context' => true]);
  59. $functions[] = new TwigFunction('boldr_cms_get_assets', [$this, 'getAssets'], ['needs_context' => true]);
  60. $functions[] = new TwigFunction('boldr_cms_render_css', [$this, 'renderCss'], ['is_safe' => ['html']]);
  61. $functions[] = new TwigFunction('boldr_cms_render_js', [$this, 'renderJs'], ['is_safe' => ['html']]);
  62. $functions[] = new TwigFunction('boldr_cms_get_available_locales', [$this, 'getAvailableLocales']);
  63. $functions[] = new TwigFunction('boldr_cms_get_enabled_locales', [$this, 'getEnabledLocales']);
  64. $functions[] = new TwigFunction('boldr_cms_get_cookie_state', [$this, 'getCookieState']);
  65. $functions[] = new TwigFunction('boldr_cms_get_site_name', [$this, 'getSiteName']);
  66. $functions[] = new TwigFunction('boldr_cms_get_menus_at_location', [$this, 'getMenusAtLocation']);
  67. $functions[] = new TwigFunction('boldr_cms_get_search_url', [$this, 'getSearchUrl']);
  68. $functions[] = new TwigFunction('boldr_cms_get_permalink', [$this, 'getPermalink']);
  69. $functions[] = new TwigFunction('boldr_cms_get_translation_url', [$this, 'getTranslationUrl']);
  70. $functions[] = new TwigFunction('boldr_cms_get_attachment_url', [$this, 'getUploadUrl']);
  71. $functions[] = new TwigFunction('boldr_cms_render_attachment', [$this, 'renderAttachment']);
  72. $functions[] = new TwigFunction('boldr_cms_get_heading_style_class', [$this, 'getHeadingStyleClass']);
  73. $functions[] = new TwigFunction('boldr_cms_get_default_heading_style', [$this, 'getDefaultHeadingStyle']);
  74. $functions[] = new TwigFunction('boldr_cms_get_button_style_class', [$this, 'getButtonStyleClass']);
  75. $functions[] = new TwigFunction('boldr_cms_get_primary_button_style', [$this, 'getPrimaryButtonStyle']);
  76. $functions[] = new TwigFunction('boldr_cms_get_secondary_button_style', [$this, 'getSecondaryButtonStyle']);
  77. $functions[] = new TwigFunction('boldr_cms_get_section_style_class', [$this, 'getSectionStyleClass']);
  78. $functions[] = new TwigFunction('boldr_cms_get_defaut_section_style', [$this, 'getDefaultSectionStyle']);
  79. $functions[] = new TwigFunction('boldr_cms_get_default_section_style', [$this, 'getDefaultSectionStyle']);
  80. $functions[] = new TwigFunction('boldr_cms_generate_link', [$this, 'generateLink']);
  81. $functions[] = new TwigFunction('boldr_cms_get_oembed_code', [$this, 'getOembedCode'], ['is_safe' => ['html']]);
  82. $functions[] = new TwigFunction('boldr_cms_serialize_view', [$this, 'serializeView'], ['is_safe' => ['js', 'html']]);
  83. $functions[] = new TwigFunction('boldr_cms_render', [$this, 'render'], ['needs_context' => true, 'is_safe' => ['html']]);
  84. if (!class_exists('Symfony\\WebpackEncoreBundle\\Twig\\EntryFilesTwigExtension'))
  85. {
  86. $functions[] = new TwigFunction('encore_entry_link_tags', [$this, 'encoreNotInstalled'], ['is_safe' => ['html'], 'needs_environment' => true]);
  87. $functions[] = new TwigFunction('encore_entry_script_tags', [$this, 'encoreNotInstalled'], ['is_safe' => ['html'], 'needs_environment' => true]);
  88. }
  89. return $functions;
  90. }
  91. public function getCookieState(): array
  92. {
  93. return [
  94. 'marketing' => $this->cookieManager->getAcceptsMarketingCookies(),
  95. 'analysis' => $this->cookieManager->getAcceptsAnalysisCookies(),
  96. 'preference' => $this->cookieManager->getAcceptsPreferenceCookies(),
  97. ];
  98. }
  99. public function render(array &$context, $renderable): string
  100. {
  101. $render = $this->htmlRenderer->render($renderable);
  102. if ($render->getAssets() !== null)
  103. {
  104. if (isset($context['assets']))
  105. {
  106. $context['assets']->addAll($render->getAssets());
  107. }
  108. else
  109. {
  110. $context['assets'] = $render->getAssets();
  111. }
  112. }
  113. return $render->getHtml();
  114. }
  115. public function serializeView($view): string
  116. {
  117. $serializer = new Serializer([new DynamicPropertyNormalizer], [new JsonEncoder]);
  118. return $serializer->serialize($view, 'json');
  119. }
  120. public function getOembedCode(string $url): string
  121. {
  122. return $this->cache->get('oembed.'. sha1($url), function() use ($url) {
  123. try
  124. {
  125. $embed = new Embed(new Crawler($this->httpClient, $this->requestFactory, $this->uriFactory));
  126. $info = $embed->get($url);
  127. $code = $info->code;
  128. return $code ? $code->html : '[could not embed]';
  129. }
  130. catch (Throwable $ex)
  131. {
  132. return '[could not embed]';
  133. }
  134. });
  135. }
  136. public function getDefaultSectionStyle(): string
  137. {
  138. return $this->parameterBag->get('boldr_cms.default_section_style');
  139. }
  140. public function getSectionStyleClass(string $sectionStyleName): string
  141. {
  142. /** @var array */
  143. $sectionStyles = $this->parameterBag->get('boldr_cms.section_styles');
  144. return $sectionStyles[$sectionStyleName]['css_class'];
  145. }
  146. public function renderCss(iterable $files)
  147. {
  148. $files = is_array($files) ? $files : iterator_to_array($files);
  149. if (count($files) === 0)
  150. {
  151. return '';
  152. }
  153. if ($this->parameterBag->get('boldr_cms.minify_assets') && !$this->parameterBag->get('kernel.debug')) {
  154. $publicPath = $this->parameterBag->get('kernel.project_dir').'/public';
  155. $output = '';
  156. $filesToMinify = [];
  157. foreach ($files as $file)
  158. {
  159. if (strpos($file, ':') !== false || !file_exists($publicPath .'/'. $file))
  160. {
  161. $output .= '<link rel="stylesheet" href="'.$file.'" />'.PHP_EOL;
  162. }
  163. else
  164. {
  165. $filesToMinify[] = $file;
  166. }
  167. }
  168. if (count($filesToMinify))
  169. {
  170. $hash = sha1(implode(',', $filesToMinify));
  171. $dirPath = $publicPath.'/assets.min';
  172. $minifiedPath = $dirPath.'/'.$hash.'.css';
  173. $minifiedUrl = '/assets.min/'.$hash.'.css';
  174. if (!file_exists($minifiedPath)) {
  175. if (!file_exists($dirPath)) {
  176. mkdir($dirPath);
  177. }
  178. $minify = new Minify\CSS;
  179. foreach ($filesToMinify as $file) {
  180. $minify->add($publicPath.$file);
  181. }
  182. $minify->minify($minifiedPath);
  183. }
  184. $output .= '<link rel="stylesheet" href="'.$minifiedUrl.'?v='.filemtime($minifiedPath).'" />'.PHP_EOL;
  185. }
  186. return $output;
  187. }
  188. else
  189. {
  190. $output = '';
  191. foreach ($files as $file) {
  192. $output .= '<link rel="stylesheet" href="'.$file.'" />'.PHP_EOL;
  193. }
  194. return $output;
  195. }
  196. }
  197. public function renderJs(iterable $files)
  198. {
  199. $files = is_array($files) ? $files : iterator_to_array($files);
  200. if (count($files) === 0)
  201. {
  202. return '';
  203. }
  204. if ($this->parameterBag->get('boldr_cms.minify_assets') && !$this->parameterBag->get('kernel.debug')) {
  205. $output = '';
  206. $filesToMinify = [];
  207. $publicPath = $this->parameterBag->get('kernel.project_dir').'/public';
  208. foreach ($files as $file)
  209. {
  210. if (strpos($file, ':') !== false || !file_exists($publicPath .'/'. $file))
  211. {
  212. $output .= '<script type="application/javascript" src="'.$file.'"></script>'.PHP_EOL;
  213. }
  214. else
  215. {
  216. $filesToMinify[] = $file;
  217. }
  218. }
  219. if (count($filesToMinify))
  220. {
  221. $hash = sha1(implode(',', $filesToMinify));
  222. $dirPath = $publicPath.'/assets.min';
  223. $minifiedPath = $dirPath.'/'.$hash.'.js';
  224. $minifiedUrl = '/assets.min/'.$hash.'.js';
  225. if (!file_exists($minifiedPath)) {
  226. if (!file_exists($dirPath)) {
  227. mkdir($dirPath);
  228. }
  229. $minify = new Minify\JS;
  230. foreach ($filesToMinify as $file) {
  231. $minify->add($publicPath.$file);
  232. }
  233. $minify->minify($minifiedPath);
  234. }
  235. $output .= '<script type="application/javascript" src="'.$minifiedUrl.'?v='.filemtime($minifiedPath).'"></script>'.PHP_EOL;
  236. }
  237. return $output;
  238. }
  239. else
  240. {
  241. $output = '';
  242. foreach ($files as $file) {
  243. $output .= '<script type="application/javascript" src="'.$file.'"></script>'.PHP_EOL;
  244. }
  245. return $output;
  246. }
  247. }
  248. public function renderAttachment($upload, array $sizes = ['small', 'medium', 'large'])
  249. {
  250. $upload = is_int($upload) ? $this->em->getReference(Upload::class, $upload) : $upload;
  251. return $this->attachmentPresenter->createAttachmentView($upload, $sizes, $this->getCurrentLocale());
  252. }
  253. public function getPermalink(PermalinkableInterface $permalinkable, ?string $locale = null)
  254. {
  255. return $this->permalinkController->generate($permalinkable, $locale ?? $this->requestStack->getCurrentRequest()->getLocale());
  256. }
  257. public function generateLink(?LinkInterface $link)
  258. {
  259. if ($link === null)
  260. return '';
  261. return $this->linkGenerators->get(get_class($link))->generateLinkUrl($link);
  262. }
  263. public function getHeadingStyleClass(string $headingStyle)
  264. {
  265. /** @var array */
  266. $styles = $this->parameterBag->get('boldr_cms.heading_styles');
  267. $style = $styles[$headingStyle] ?? $styles['default'] ?? [];
  268. return $style['css_class'] ?? '';
  269. }
  270. public function getDefaultHeadingStyle(): string
  271. {
  272. return $this->parameterBag->get('boldr_cms.default_heading_style');
  273. }
  274. public function getPrimaryButtonStyle(): string
  275. {
  276. return $this->parameterBag->get('boldr_cms.primary_button_style');
  277. }
  278. public function getSecondaryButtonStyle(): string
  279. {
  280. return $this->parameterBag->get('boldr_cms.secondary_button_style');
  281. }
  282. public function getButtonStyleClass(string $buttonStyle)
  283. {
  284. /** @var array */
  285. $styles = $this->parameterBag->get('boldr_cms.button_styles');
  286. $style = $styles[$buttonStyle] ?? $styles[$this->getPrimaryButtonStyle()] ?? null;
  287. if ($style === null)
  288. {
  289. return '';
  290. }
  291. return $style['css_class'] ?? '';
  292. }
  293. public function getSearchUrl(string $searchConfigurationName)
  294. {
  295. /** @var array */
  296. $searchConfigurations = $this->parameterBag->get('boldr_cms.search_configurations');
  297. $searchConfiguration = $searchConfigurations[$searchConfigurationName] ?? null;
  298. if ($searchConfiguration === null)
  299. {
  300. throw new Exception('No search configuration with id "'. $searchConfigurationName .'" exists. Verify your search configuration in config/packages/boldr_cms.yaml');
  301. }
  302. return $this->permalinkController->generate(new SearchConfigurationPermalinkable($searchConfigurationName), $this->getCurrentLocale());
  303. }
  304. public function getAssets(array $context): Assets
  305. {
  306. $assets = $context['assets'] ?? new Assets;
  307. foreach ($this->assetProviders as $assetProvider)
  308. {
  309. $assetProvider->addAssets($assets);
  310. }
  311. return $assets;
  312. }
  313. public function getUploadUrl($upload, ?string $size = null): string
  314. {
  315. if (is_int($upload))
  316. {
  317. $upload = $this->em->getRepository(Upload::class)->find($upload);
  318. }
  319. $request = $this->requestStack->getCurrentRequest();
  320. $baseUrl = $request->getSchemeAndHttpHost().$request->getBasePath();
  321. return $this->uploadStorageManager->getImageUrl($upload, $baseUrl, $size);
  322. }
  323. /**
  324. * @return string[]
  325. */
  326. public function getAvailableLocales(): array
  327. {
  328. return $this->siteConfiguration->getAvailableLocales();
  329. }
  330. /**
  331. * @return string[]
  332. */
  333. public function getEnabledLocales(): array
  334. {
  335. return $this->siteConfiguration->getEnabledLocales();
  336. }
  337. public function getTranslationUrl(string $locale): ?string
  338. {
  339. $request = $this->requestStack->getCurrentRequest();
  340. /** @var array<string,string> */
  341. $localeBaseUrls = $this->parameterBag->get('boldr_cms.locale_base_urls');
  342. $localeBaseUrl = $localeBaseUrls[$locale] ?? '';
  343. if (is_array($localeBaseUrl))
  344. {
  345. $localeBaseUrl = $localeBaseUrl[0];
  346. }
  347. if ($request->attributes->has('_permalinkable'))
  348. {
  349. return $localeBaseUrl . $this->permalinkController->generate($request->attributes->get('_permalinkable'), $locale);
  350. }
  351. $url = !$request->attributes->has('_route') ? '' : $localeBaseUrl . $this->urlGenerator->generate(
  352. $request->attributes->get('_route'),
  353. array_merge(
  354. $request->attributes->get('_route_params') ?: [],
  355. $request->query->all(),
  356. ['_locale' => $locale]
  357. )
  358. );
  359. if (strpos($url, '?_locale=') === false && strpos($url, '&_locale=') === false)
  360. {
  361. return $url;
  362. }
  363. return null;
  364. }
  365. private function getCurrentLocale(): string
  366. {
  367. $request = $this->requestStack->getCurrentRequest();
  368. $locale = $request === null ? $this->siteConfiguration->getDefaultLocale() : $request->getLocale();
  369. return $locale;
  370. }
  371. public function getSiteName(): string {
  372. $locale = $this->getCurrentLocale();
  373. return $this->siteConfiguration->getSiteName($locale);
  374. }
  375. /**
  376. * @return MenuView[]
  377. */
  378. public function getMenusAtLocation(string $locationId): array
  379. {
  380. $locale = $this->requestStack->getCurrentRequest()->getLocale();
  381. return $this->cache->get('menus_at_location.'.$locationId.'.'.$locale, function($cacheItem) use ($locationId, $locale) {
  382. $cacheItem->tag('boldr_cms.menus_at_location');
  383. $repo = $this->em->getRepository(Menu::class);
  384. /** @phpstan-ignore-next-line */
  385. $menus = $repo->createQueryBuilder('m')
  386. ->leftJoin('m.locations', 'l')
  387. ->where('l.location = ?1')
  388. ->setParameter(1, $locationId)
  389. ->getQuery()
  390. ->getResult();
  391. $menuViews = [];
  392. foreach ($menus as $menu)
  393. {
  394. $menuViews[] = $this->menuPresenter->createMenuView($menu, $locale);
  395. }
  396. return $menuViews;
  397. });
  398. }
  399. public function encoreNotInstalled(Environment $twig)
  400. {
  401. if ($twig->isDebug())
  402. return '<!-- Webpack Encore is not installed -->';
  403. return '';
  404. }
  405. public function renderContent($context, array $contentElements): string
  406. {
  407. $assets = $context['assets'];
  408. return $this->contentManager->renderAsHtml($contentElements, $assets);
  409. }
  410. }