src/Twig/TwigExtension.php line 166

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