src/Module/Cms/Twig/Functions/StoryblokExtension.php line 104

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Module\Cms\Twig\Functions;
  4. use App\Module\Catalog\Provider\CategoryTreeProvider;
  5. use App\Module\Cms\Model\Stories;
  6. use App\Module\Cms\Provider\CmsContentProvider;
  7. use App\Module\Cms\RichText\InternalLinkAwareRichTextSchema;
  8. use InvalidArgumentException;
  9. use Psr\Log\LoggerInterface;
  10. use Storyblok\RichtextRender\Resolver;
  11. use Twig\Environment;
  12. use Twig\Error\LoaderError;
  13. use Twig\Error\RuntimeError;
  14. use Twig\Error\SyntaxError;
  15. use Twig\Extension\AbstractExtension;
  16. use Twig\TwigFunction;
  17. use function sprintf;
  18. /**
  19. * Render extension for storyblok
  20. * Example: {{ render_storyblok(component, {'class': 'foo'}) }}
  21. * component are the Storyblock array and the array with class is optional
  22. *
  23. * @package App\Module\Cms\Twig\Functions
  24. */
  25. class StoryblokExtension extends AbstractExtension
  26. {
  27. /**
  28. * @var string
  29. */
  30. private const COMPONENT_PATH_PATTERN = 'components/%s/%s/%s.html.twig';
  31. private CmsContentProvider $provider;
  32. private CategoryTreeProvider $categoryTreeProvider;
  33. private InternalLinkAwareRichTextSchema $richTextSchema;
  34. private LoggerInterface $logger;
  35. /**
  36. * StoryblokExtension constructor.
  37. *
  38. * @param CmsContentProvider $provider
  39. * @param InternalLinkAwareRichTextSchema $internalLinkAwareSchema
  40. * @param CategoryTreeProvider $categoryTreeProvider
  41. */
  42. public function __construct(
  43. LoggerInterface $logger,
  44. CmsContentProvider $provider,
  45. InternalLinkAwareRichTextSchema $internalLinkAwareSchema,
  46. CategoryTreeProvider $categoryTreeProvider
  47. ) {
  48. $this->provider = $provider;
  49. $this->richTextSchema = $internalLinkAwareSchema;
  50. $this->categoryTreeProvider = $categoryTreeProvider;
  51. $this->logger = $logger;
  52. }
  53. /**
  54. * @param Environment $twig
  55. * @param array|null $componentData
  56. * @param array $options
  57. * @return string
  58. * @throws LoaderError
  59. * @throws RuntimeError
  60. * @throws SyntaxError
  61. */
  62. public function render(Environment $twig, array $componentData = null, array $options = []): string
  63. {
  64. // belongs to the DEN-844 ticket
  65. if (!is_array($componentData)) {
  66. $error = new InvalidArgumentException();
  67. $this->logger->error(
  68. 'Invalid Argument exception -> see DEN-844 ticket for more information',
  69. ['trace' => $error->getTrace()]
  70. );
  71. return '';
  72. }
  73. if (count($componentData) === 1) {
  74. $componentData = $componentData[0];
  75. }
  76. if (count($options) > 0) {
  77. $componentData = array_merge($componentData, $options);
  78. }
  79. if (isset($componentData['atomicType'], $componentData['component'])) {
  80. $template = sprintf(
  81. static::COMPONENT_PATH_PATTERN,
  82. $componentData['atomicType'],
  83. $componentData['component'],
  84. $componentData['component']
  85. );
  86. if (!$twig->getLoader()->exists($template)) {
  87. $this->logger->error(sprintf('Template "%s" not found', $template));
  88. return '';
  89. }
  90. $result = $twig->render($template, $componentData);
  91. return ($componentData['_editable'] ?? '') . $result;
  92. }
  93. return '';
  94. }
  95. /**
  96. * @param array $markup
  97. * @return string
  98. */
  99. public function renderRichText(array $markup): string
  100. {
  101. $resolver = new Resolver(
  102. ['marks' => $this->richTextSchema->getMarks(), 'nodes' => $this->richTextSchema->getNodes()]
  103. );
  104. return $resolver->render($markup);
  105. }
  106. /**
  107. * @param Stories $stories
  108. * @param string $filter
  109. * @return array
  110. */
  111. public function getContentsFilterOptions(Stories $stories, string $filter): array
  112. {
  113. $storiesArray = iterator_to_array($stories);
  114. // get 2D array of story->content->article_topics = [article_topicsA, article_topicsB, ...]
  115. $nestedFilterOptions = array_column(array_column($storiesArray, 'content'), $filter);
  116. // merge arrays into a single 1D one
  117. $filterOptions = [];
  118. array_walk_recursive($nestedFilterOptions, function($nestedItem) use (&$filterOptions) { $filterOptions[] = $nestedItem; });
  119. // array_values() to close array index gap
  120. return array_values(array_unique($filterOptions));
  121. }
  122. /**
  123. * @param string $contentId
  124. * @return string
  125. */
  126. public function getStoryLink(string $contentId): string
  127. {
  128. $content = $this->provider->getContentById($contentId);
  129. return $content !== null ? $content->slug : '';
  130. }
  131. /**
  132. * @return array
  133. */
  134. public function getCategoryChildrenFromTree(): array
  135. {
  136. $currentCategory = $this->provider->getContentBySlugForCurrentPage();
  137. $categories = [];
  138. if ($currentCategory !== null) {
  139. $categories = $this->categoryTreeProvider->getCategoryFromTreeById($currentCategory->uuid)['children'] ?? [];
  140. }
  141. return $categories;
  142. }
  143. /**
  144. * @param mixed $linkObject, Link in Storyblok format
  145. * @return string
  146. */
  147. public function getHrefFromStoryblokLink($linkObject): ?string
  148. {
  149. if (is_array($linkObject)) {
  150. $href = "";
  151. if ($linkObject['linktype'] == 'story') {
  152. $href = $this->getStoryLink($linkObject['id']);
  153. if (isset($linkObject['anchor'])) {
  154. $href = $href .'#'.$linkObject['anchor'];
  155. }
  156. } elseif ($linkObject['linktype'] == 'email') {
  157. $href = 'mailto:' . $linkObject['email'];
  158. } elseif (isset($linkObject['cached_url'])) {
  159. $href = $linkObject['cached_url'];
  160. }
  161. return $href;
  162. } else {
  163. return $linkObject;
  164. }
  165. }
  166. /**
  167. * Get twig functions
  168. *
  169. * @return array
  170. */
  171. public function getFunctions(): array
  172. {
  173. return [
  174. new TwigFunction(
  175. 'render_storyblok',
  176. [
  177. $this,
  178. 'render',
  179. ],
  180. [
  181. 'is_safe' => ['html'],
  182. 'needs_environment' => true,
  183. ]
  184. ),
  185. new TwigFunction(
  186. 'getContent',
  187. [
  188. $this->provider,
  189. 'getContentById',
  190. ],
  191. [
  192. 'is_safe' => ['html'],
  193. ]
  194. ),
  195. new TwigFunction(
  196. 'getContentByTopicsAndTypes',
  197. [
  198. $this->provider,
  199. 'getContentByTopicsAndTypes',
  200. ],
  201. [
  202. 'is_safe' => ['html'],
  203. ]
  204. ),
  205. new TwigFunction(
  206. 'getContentsFilterOptions',
  207. [
  208. $this,
  209. 'getContentsFilterOptions',
  210. ],
  211. [
  212. 'is_safe' => ['html'],
  213. ]
  214. ),
  215. new TwigFunction(
  216. 'getStoryLink',
  217. [
  218. $this,
  219. 'getStoryLink',
  220. ],
  221. [
  222. 'is_safe' => ['html'],
  223. ]
  224. ),
  225. new TwigFunction(
  226. 'getCategoryChildrenFromTree',
  227. [
  228. $this,
  229. 'getCategoryChildrenFromTree',
  230. ],
  231. [
  232. 'is_safe' => ['html'],
  233. ]
  234. ),
  235. new TwigFunction(
  236. 'renderRichText',
  237. [
  238. $this,
  239. 'renderRichText',
  240. ],
  241. [
  242. 'is_safe' => ['html'],
  243. ]
  244. ),
  245. new TwigFunction(
  246. 'getHrefFromStoryblokLink',
  247. [
  248. $this,
  249. 'getHrefFromStoryblokLink',
  250. ]
  251. ),
  252. ];
  253. }
  254. }