src/Twig/TwigExtension.php line 164

Open in your IDE?
  1. <?php
  2. namespace App\Twig;
  3. use App\Entity\HtmlBlocks;
  4. use App\Entity\Locales;
  5. use App\Entity\Setting;
  6. use App\Service\SimpleCache;
  7. use Doctrine\Persistence\ManagerRegistry;
  8. use Twig\Environment;
  9. use Twig\Extension\AbstractExtension;
  10. use Twig\TwigFilter;
  11. use Twig\TwigFunction;
  12. class TwigExtension extends AbstractExtension
  13. {
  14.     private ?\Symfony\Component\DependencyInjection\ContainerInterface $container null;
  15.     public function __construct(private readonly \App\Service\ImageCacheService $imageCacheService, private readonly \Symfony\Component\HttpKernel\KernelInterface $kernel, private readonly \Symfony\Component\Routing\RouterInterface $router, private readonly ManagerRegistry $doctrine, private readonly \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $authorizationChecker, private readonly \Symfony\Component\HttpFoundation\RequestStack $requestStack, private readonly \App\Service\ServiceController $serviceController, private readonly SimpleCache $cache, private $projectRoot)
  16.     {
  17.         $this->container $this->kernel->getContainer();
  18.     }
  19.     public function getName()
  20.     {
  21.         return 'twig_extension';
  22.     }
  23.     #[\Override]
  24.     public function getFilters()
  25.     {
  26.         return [
  27.             new TwigFilter('cacheBust', fn ($asset) => $this->cacheBust($asset)),
  28.             new TwigFilter('html_entity_decode', fn ($string) => html_entity_decode($string)),
  29.         ];
  30.     }
  31.     #[\Override]
  32.     public function getFunctions()
  33.     {
  34.         return [
  35.             new TwigFunction('renderComponents', fn ($positionId$pageComponents) => $this->renderComponents($positionId$pageComponents)),
  36.             new TwigFunction('removeBracesFromSlug', fn ($string) => $this->removeBracesFromSlug($string)),
  37.             new TwigFunction('generatePath', fn ($request$pageID$parametersArray = []) => $this->generatePath($request$pageID$parametersArray)),
  38.             new TwigFunction('imageCache', fn ($pathtofile$filter$width 0$height 0$background 'transparent') => $this->imageCache($pathtofile$filter$width$height$background)),
  39.             new TwigFunction('fetchLocales', fn () => $this->fetchLocales()),
  40.             new TwigFunction('breadcrumbs', function ($pageEntity null$currentUrl null) {
  41.                 $this->breadcrumbs($pageEntity$currentUrl);
  42.             }),
  43.             new TwigFunction('renderHtmlBlock'$this->renderHtmlBlock(...), ['needs_environment' => true]),
  44.             new TwigFunction('allowInlineEditor', fn ($entity$field) => $this->allowInlineEditor($entity$field)),
  45.             new TwigFunction('showAdminControlLinks', fn ($entity$route) => $this->showAdminControlLinks($entity$route)),
  46.             new TwigFunction('moneyFormat', fn ($number) => $this->moneyFormat($number)),
  47.             new TwigFunction('domCheckIgnore', fn ($value) => $this->domCheckIgnore($value)),
  48.             new TwigFunction('componentEntities', fn ($pageComponents$entityName null$field null) => $this->componentEntities($pageComponents$entityName$field)),
  49.             new TwigFunction('componentEntity', fn ($pageComponents$field null) => $this->componentEntity($pageComponents$field)),
  50.             new TwigFunction('replaceIfComponentDataExists', fn ($pageComponents$field null$fallback null) => $this->replaceIfComponentDataExists($pageComponents$field$fallback)),
  51.             new TwigFunction('forceRenderHtmlBlock', fn ($identifier) => $this->forceRenderHtmlBlock($identifier)),
  52.             new TwigFunction('renderSetting', fn ($id$field) => $this->renderSetting($id$field), ['is_safe' => ['html']]),
  53.         ];
  54.     }
  55.     public function renderSetting($id$field)
  56.     {
  57.         /** @var array $setting */
  58.         $setting $this->doctrine->getRepository(Setting::class)->findActiveSetting($id);
  59.         if (!$setting) {
  60.             return '';
  61.         }
  62.         if (!array_key_exists($field$setting)) {
  63.             return '';
  64.         }
  65.         return nl2br((string) $setting[$field]);
  66.     }
  67.     // fetch field from selected entity from a page component - use if you have multiple components on the same page
  68.     public function componentEntities($pageComponents$entityName null$field null)
  69.     {
  70.         $component = [];
  71.         $checkfield substr((string) $field03);
  72.         if ('get' != $checkfield) {
  73.             $field 'get'.ucwords((string) $field);
  74.         }
  75.         if (isset($component['urlKey'])) {
  76.             foreach ($pageComponents as $component) {
  77.                 if (strtolower((string) $component['urlKey']) === strtolower((string) $entityName) && method_exists($component['entity'], $field)) {
  78.                     return call_user_func([$component['entity'], $field]);
  79.                 }
  80.             }
  81.         }
  82.     }
  83.     // fetch field from url component - only if using a url based component
  84.     public function componentEntity($pageComponents$field null)
  85.     {
  86.         $checkfield substr((string) $field03);
  87.         if ('get' != $checkfield) {
  88.             $field 'get'.ucwords((string) $field);
  89.         }
  90.         // ADDED AS VAR WAS MISSING _ CW
  91.         $entityName '';
  92.         foreach ($pageComponents as $component) {
  93.             if (isset($component['urlKey']) && strtolower((string) $component['urlKey']) === strtolower($entityName) && method_exists($component['entity'], $field)) {
  94.                 return call_user_func([$component['entity'], $field]);
  95.             }
  96.         }
  97.     }
  98.     // Simlar to the above 'componentEntity' function except you can use a fallback string
  99.     // This was intented to be used as an alternative to an if statement
  100.     public function replaceIfComponentDataExists($pageComponents$field null$fallback null)
  101.     {
  102.         $data null;
  103.         $checkfield substr((string) $field03);
  104.         if ('get' != $checkfield) {
  105.             $field 'get'.ucwords((string) $field);
  106.         }
  107.         foreach ($pageComponents as $component) {
  108.             if ((isset($component['urlKey']) && null != $component['urlKey']) && null != $component['data'] && method_exists($component['entity'], $field)) {
  109.                 $data call_user_func([$component['entity'], $field]);
  110.             }
  111.         }
  112.         if (null == $data) {
  113.             return $fallback;
  114.         }
  115.         return $data;
  116.     }
  117.     public function moneyFormat($number)
  118.     {
  119.         return number_format($number2',''.');
  120.     }
  121.     // fix for the domcrawler (which gathers component positions on the add/edit page controller )
  122.     // used to ignore app.request or app.user twig functions - wasn't an issue on my testing machine but did effect TREV
  123.     public function domCheckIgnore($value)
  124.     {
  125.         if (is_array($value)) {
  126.             return null;
  127.         }
  128.         return $value;
  129.     }
  130.     // fetch image or generate new depending on parameter provided - this function may get overriden by the App/Twig/AdminTwigExtension
  131.     public function imageCache($pathtofile$filter$width 0$height 0$background 'transparent')
  132.     {
  133.         echo $this->imageCacheService->imageCache($pathtofile$filter$width$height$background);
  134.     }
  135.     // generate page url which translates to the current locale
  136.     // get all slugs stored in the array cache to generate the path
  137.     // example : <a href="{{generatePath( app.request, 7, {'blog_category': post.category.slug, 'blog_post_slug': post.slug}  )}}">
  138.     public function generatePath($request$pageID$parametersArray = [])
  139.     {
  140.         $locale $request->getLocale();
  141.         $session $request->getSession();
  142.         $localePages $session->get('localePages');
  143.         $not_found = [];
  144.         $slugCache $this->cache->get('slugCache');
  145.         if (null == $slugCache) { // prevents error in page admin ( dom crawer issue with app.request )
  146.             return false;
  147.         }
  148.         foreach (unserialize($slugCache) as $page) {
  149.             if ($page['id'] == $pageID) {
  150.                 $finalUrl $page['slug'];
  151.                 $confirmedPagePieces explode('/', (string) $page['slug']);
  152.                 foreach ($parametersArray as $key => $parameter) {
  153.                     $slugCheck str_replace(' ''', (string) $key);
  154.                     $slugKey array_search('{'.$slugCheck.'}'$confirmedPagePieces);
  155.                     if (!is_numeric($slugKey)) {
  156.                         $not_found[$key] = $parameter;
  157.                     } else {
  158.                         $finalUrl str_replace('{'.$slugCheck.'}'$parameter, (string) $finalUrl);
  159.                     }
  160.                 }
  161.                 $getparams '';
  162.                 if (count($not_found) > 0) {
  163.                     $getparams '?';
  164.                     foreach ($not_found as $extraParam => $extraParamValue) {
  165.                         $getparams .= $extraParam.'='.$extraParamValue.'&';
  166.                     }
  167.                     $getparams rtrim($getparams'&');
  168.                 }
  169.                 return '/'.str_replace('//''/'$finalUrl.$getparams);
  170.             }
  171.         }
  172.         $getparams '';
  173.         if (count($not_found) > 0) {
  174.             $getparams '?path=notfound&';
  175.             foreach ($not_found as $extraParam => $extraParamValue) {
  176.                 $getparams .= $extraParam.'='.$extraParamValue.'&';
  177.             }
  178.             $getparams rtrim($getparams'&');
  179.         }
  180.         return '#'.$getparams;
  181.     }
  182.     // used to tidy page {slugs}
  183.     public function removeBracesFromSlug($string)
  184.     {
  185.         return preg_replace('#{[\s\S]+?}#''', (string) $string);
  186.     }
  187.     // Inserts the relevant component into the page template
  188.     // Also assists the new/edit page component selector (domcrawler picks up on the domcheck attribute)
  189.     public function renderComponents($positionId$pageComponents)
  190.     {
  191.         if ($pageComponents) {
  192.             if ('domcheck' == $pageComponents[0]['position']) {
  193.                 echo "<div data-cms='domcheck'>".$positionId.'</div>';
  194.             }
  195.             foreach ($pageComponents as $component) {
  196.                 if ($component['position'] == $positionId && array_key_exists('data'$component)) {
  197.                     return $component['data'];
  198.                 }
  199.             }
  200.         }
  201.     }
  202.     // Inserts the relevant htmlblock into the page template
  203.     // Also assists the new/edit page component selector (domcrawler picks up on the domcheck attribute)
  204.     public function renderHtmlBlock(Environment $environment$positionId$pageHtmlblocks$page null)
  205.     {
  206.         if ($pageHtmlblocks) {
  207.             if ('domcheck' == $pageHtmlblocks[0]['position']) {
  208.                 echo "<div data-cms='domcheckhtml'>".$positionId.'</div>';
  209.             }
  210.             foreach ($pageHtmlblocks as $block) {
  211.                 $block['page'] = $page ?? null;
  212.                 if ($block['position'] == $positionId && array_key_exists('data'$block)) {
  213.                     if ($this->authorizationChecker->isGranted('ROLE_PAGE_EDITOR')) {
  214.                         return $this->getInlineEditorHTML(HtmlBlocks::class, 'html'$block['data'], $block['blockId'], 'HtmlBlock');
  215.                     }
  216.                     return $environment->render('@theme/common/htmlblock.html.twig', ['block' => $block]);
  217.                 }
  218.             }
  219.         }
  220.     }
  221.     // Inserts the relevant htmlblock into the page template
  222.     // This function is not used tied to the CMS - renders same block on every page
  223.     public function forceRenderHtmlBlock($identifier)
  224.     {
  225.         if ($identifier) {
  226.             $block $this->doctrine->getRepository(HtmlBlocks::class)->findOneBy(['title' => $identifier'deleted' => false'active' => true]);
  227.             if (null !== $block) {
  228.                 if ($this->authorizationChecker->isGranted('ROLE_PAGE_EDITOR')) {
  229.                     return $this->getInlineEditorHTML(HtmlBlocks::class, 'html'$block->getHtml(), $block->getId(), 'HtmlBlock');
  230.                 }
  231.                 return $block->getHtml();
  232.             }
  233.         }
  234.     }
  235.     public function allowInlineEditor($entity$field)
  236.     {
  237.         $namespaceMeta $this->serviceController->getBundleNameFromEntity($entity$field);
  238.         $getterMethod 'get'.ucwords((string) $field);
  239.         $editText $entity->{$getterMethod}();
  240.         if ('' == $editText) {
  241.             $editText '&nbsp;';
  242.             // return null;
  243.         }
  244.         $request $this->requestStack->getCurrentRequest();
  245.         // $request = $this->container->get('request');
  246.         if ($request->query->has('preview')) {
  247.             return $editText;
  248.         }
  249.         if ($this->authorizationChecker->isGranted('ROLE_PAGE_EDITOR')) {
  250.             return $this->getInlineEditorHTML($namespaceMeta['full'], $field$editText$entity->getId(), $namespaceMeta['short'], $namespaceMeta['fieldmeta']);
  251.         }
  252.         return $editText;
  253.     }
  254.     public function showAdminControlLinks($entity$route)
  255.     {
  256.         $namespaceMeta $this->serviceController->getBundleNameFromEntity($entity);
  257.         $url $this->router->generate($route, ['id' => $entity->getId()]);
  258.         if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
  259.             $buttons "<div class='adminControlButtons'>";
  260.             $buttons .= "    <div class='inlineEditorToolboxContainer'>Admin Control (".$namespaceMeta['short'].')</div>';
  261.             $buttons .= "    <div class='inlineEditorToolboxLink'><a href='".$url."' data-toggle='tooltip' title='View/Edit' ><span class='glyphicon glyphicon-pencil'></span>&nbsp;View/Edit</a></div>";
  262.             $buttons .= '</div>';
  263.             return $buttons;
  264.         }
  265.     }
  266.     public function getInlineEditorHTML($namespace$field$content$id$entityname null$fieldmeta null)
  267.     {
  268.         // show inline editor
  269.         $request $this->requestStack->getCurrentRequest();
  270.         $locale $request->getLocale();
  271.         $showFullEditor 1;
  272.         if (null != $fieldmeta && 'string' == $fieldmeta['type']) {
  273.             $showFullEditor 0;
  274.         }
  275.         // Redactor required uniqueIDs - classes conflicted if multiple editors were used
  276.         $uniqueID substr(md5(random_int(19999)), 07);
  277.         $editor "<div class='inlineEditorContainer'>";
  278.         $editor .= "    <div id='inlineEditor-message-".$uniqueID."' class='inlineEditorToolboxContainer'>Editable (".$entityname.':'.$field.')</div>';
  279.         $editor .= "    <div class='inlineEditorToolboxSave'><a data-toggle='tooltip' title='Save Text' id='btn-save-".$uniqueID."' style='display:none'><span class='glyphicon glyphicon-floppy-disk'></span>&nbsp;Save</a></div>";
  280.         // $editor .= "    <div class='inlineEditorToolboxClose'><a data-toggle='tooltip' title='Close Editor' id='btn-cancel-".$uniqueID."' style='display:none'><span class='glyphicon glyphicon-remove-sign'></span></a></div>";
  281.         $editor .= "    <div class='inlineEditor' data-fulleditor='".$showFullEditor."' id='".$uniqueID."' data-entitynamespace='".$namespace."'  data-entityfield='".$field."' data-id='".$id."' data-locale='".$locale."' >";
  282.         if (== $showFullEditor) {
  283.             $editor .= '<p>';
  284.         }
  285.         $editor .= $content;
  286.         if (== $showFullEditor) {
  287.             $editor .= '</p>';
  288.         }
  289.         $editor .= '    </div>';
  290.         // if($showFullEditor ==1){
  291.         //     $editor .= "    <textarea class='inlineEditor' data-fulleditor='".$showFullEditor."' id='".$uniqueID."' data-entitynamespace='".$namespace."'  data-entityfield='".$field."' data-id='".$id."' data-locale='".$locale."' >";
  292.         //     $editor .= $content;
  293.         //     $editor .= "    </textarea>";
  294.         // }else{
  295.         //     $editor .= "    <input type='text' class='inlineEditor' data-fulleditor='".$showFullEditor."' value='".$content."' id='".$uniqueID."' data-entitynamespace='".$namespace."'  data-entityfield='".$field."' data-id='".$id."' data-locale='".$locale."' />";
  296.         // }
  297.         $editor .= '</div>';
  298.         return $editor;
  299.     }
  300.     // simple function to fetch all locales
  301.     // done this way to ensure locale switch buttons work on non cms pages
  302.     public function fetchLocales()
  303.     {
  304.         return $this->doctrine->getRepository(Locales::class)->findBy(['active' => true]);
  305.     }
  306.     public function breadcrumbs($pageEntity null$currentUrl null)
  307.     {
  308.         // // this function generates a full url category path
  309.         // $currentUrl = $this->container->get('request')->getUri();
  310.         if (null != $pageEntity && null != $currentUrl) {
  311.             $exploded explode('/', (string) $currentUrl);
  312.             unset($exploded[0]);
  313.             $exploded array_values($exploded);
  314.             $structure = [];
  315.             $explodedCount count($exploded);
  316.             for ($i 0$i $explodedCount; ++$i) {
  317.                 if (array_key_exists($i 1$structure)) {
  318.                     $structure[$i] = $structure[$i 1]['url'].'/'.$exploded[$i];
  319.                 } else {
  320.                     $structure[$i] = '/'.$exploded[$i];
  321.                 }
  322.                 $url array_key_exists($i 1$structure) ? $structure[$i 1]['url'].'/'.$exploded[$i] : '/'.$exploded[$i];
  323.                 $structure[$i] = ['url' => $url'title' => $exploded[$i]];
  324.             }
  325.             // print_r($structure);
  326.             $seperater ' > ';
  327.             $html '<div class="breadcrumb">';
  328.             $html .= '<span><a href="/">Home</a>'.$seperater.'</span>';
  329.             $count 0;
  330.             foreach ($structure as $item) {
  331.                 ++$count;
  332.                 if (count($structure) == $count) {
  333.                     $seperater '';
  334.                 }
  335.                 $html .= '<span><a href="'.$item['url'].'">'.str_replace('-'' 'ucfirst($item['title'])).'</a>'.$seperater.'</span>';
  336.             }
  337.             $html .= '</div>';
  338.             echo $html;
  339.         }
  340.     }
  341.     // simple function to pluralise text string (adds 's' if array count >1 )
  342.     public function pluralize($text$array$plural_version null)
  343.     {
  344.         return (is_countable($array) ? count($array) : 0) > ? ($plural_version ?: $text.'s') : $text;
  345.     }
  346.     // simple word limiter function
  347.     public function wordLimiter($str$limit 30)
  348.     {
  349.         $words explode(' 'strip_tags((string) $str));
  350.         if ($words $limit) {
  351.             return implode(' 'array_splice($words0$limit)).'...';
  352.         }
  353.         return $str;
  354.     }
  355.     /**
  356.      * Cache bust specified asset.
  357.      */
  358.     public function cacheBust(mixed $asset)
  359.     {
  360.         $asset '/'.ltrim((string) $asset'/');
  361.         $assetPath sprintf('%s/../public/%s'$this->projectRoot$asset);
  362.         // If we are assuming a CSS or JS file exists when it doesn't,
  363.         // we probably need to know about it.
  364.         if (!file_exists($assetPath)) {
  365.             throw new \RuntimeException(sprintf('Asset: %s is missing!'$asset));
  366.         }
  367.         $modified filemtime($assetPath);
  368.         return $asset.sprintf('?version=%d'$modified);
  369.     }
  370. }