src/Cms/CoreBundle/Service/Twig/ContextExtension.php line 1843

Open in your IDE?
  1. <?php
  2. namespace Cms\CoreBundle\Service\Twig;
  3. use Cms\AssetsBundle\Model\ElementSettings;
  4. use Cms\AssetsBundle\Model\Structure\MetaStructure;
  5. use Cms\AssetsBundle\Model\Structure\ScriptStructure\LinkedScriptStructure;
  6. use Cms\AssetsBundle\Model\Structure\StyleStructure\LinkedStyleStructure;
  7. use Cms\AssetsBundle\Model\Structure\TitleStructure;
  8. use Cms\ContainerBundle\Entity\Container;
  9. use Cms\ContainerBundle\Entity\Containers\GenericContainer;
  10. use Cms\ContainerBundle\Entity\Containers\IntranetContainer;
  11. use Cms\ContainerBundle\Entity\Containers\PersonalContainer;
  12. use Cms\ContainerBundle\Entity\Containers\StorageContainer;
  13. use Cms\ContainerBundle\Service\ContainerService;
  14. use Cms\CoreBundle\Model\Interfaces\Blameable\BlameableInterface;
  15. use Cms\CoreBundle\Model\Interfaces\Identifiable\IdentifiableInterface;
  16. use Cms\CoreBundle\Model\Interfaces\Lockable\LockableInterface;
  17. use Cms\CoreBundle\Model\Interfaces\Nestable\NestableInterface;
  18. use Cms\CoreBundle\Model\Interfaces\Timestampable\TimestampableInterface;
  19. use Cms\CoreBundle\Service\Aws\S3Wrapper;
  20. use Cms\CoreBundle\Service\Locksmith;
  21. use Cms\CoreBundle\Service\SceneRenderer;
  22. use Cms\CoreBundle\Util\DateTimeUtils;
  23. use Cms\CoreBundle\Util\Doctrine\EntityManager;
  24. use Cms\CoreBundle\Util\Twig\TokenParser\InlineHtmlTokenParser;
  25. use Cms\CoreBundle\Util\Twig\TokenParser\InlineScriptTokenParser;
  26. use Cms\CoreBundle\Util\Twig\TokenParser\InlineStyleTokenParser;
  27. use Cms\DomainBundle\Entity\SslCertificate;
  28. use Cms\DomainBundle\Entity\SslCertificates\CustomSslCertificate;
  29. use Cms\DomainBundle\Entity\SslCertificates\LetsEncryptSslCertificate;
  30. use Cms\FileBundle\Entity\Nodes\File;
  31. use Cms\FileBundle\Entity\Nodes\Files\ImageFile;
  32. use Cms\FileBundle\Entity\Optimizations\ImageOptimization;
  33. use Cms\FileBundle\Service\FileManager;
  34. use Cms\FrontendBundle\Model\FrontendGlobals;
  35. use Cms\ModuleBundle\Entity\Draft;
  36. use Cms\ModuleBundle\Entity\Proxy;
  37. use Cms\ModuleBundle\Model\Interfaces\Shareable\ShareableInterface;
  38. use Cms\ModuleBundle\Model\ModuleConfig;
  39. use Cms\ModuleBundle\Service\DraftManager;
  40. use Cms\SyncBundle\Model\Interfaces\Syncable\SyncableInterface;
  41. use Cms\TenantBundle\Entity\TenantedEntity;
  42. use Cms\ThemeBundle\Entity\Template;
  43. use Cms\ThemeBundle\Service\Twig\Loader\DatabaseLoader;
  44. use Common\Util\Base64;
  45. use Common\Util\Strings;
  46. use Common\Util\Tokens;
  47. use Doctrine\Common\Util\ClassUtils;
  48. use Exception;
  49. use Platform\SecurityBundle\Entity\Identity\Account;
  50. use Symfony\Bridge\Twig\AppVariable;
  51. use Symfony\Component\HttpFoundation\Request;
  52. use Symfony\Component\HttpFoundation\RequestStack;
  53. use Symfony\Contracts\Translation\TranslatorInterface;
  54. use Twig\Environment;
  55. use Twig\Extension\AbstractExtension;
  56. use Twig\Extension\GlobalsInterface;
  57. use Twig\TwigFilter;
  58. use Twig\TwigFunction;
  59. /**
  60.  * Class ContextExtension
  61.  *
  62.  * @package Cms\CoreBundle\Service\Twig
  63.  */
  64. class ContextExtension extends AbstractExtension implements GlobalsInterface
  65. {
  66.     /**
  67.      * @var TranslatorInterface
  68.      */
  69.     private TranslatorInterface $translator;
  70.     /**
  71.      * @var S3Wrapper
  72.      */
  73.     private S3Wrapper $s3Wrapper;
  74.     /**
  75.      * @var ContainerService
  76.      */
  77.     private ContainerService $containerService;
  78.     /**
  79.      * @var EntityManager
  80.      */
  81.     private EntityManager $em;
  82.     /**
  83.      * @var FileManager
  84.      */
  85.     private FileManager $fileManager;
  86.     /**
  87.      * @var DatabaseLoader
  88.      */
  89.     private DatabaseLoader $databaseLoader;
  90.     /**
  91.      * @var RequestStack
  92.      */
  93.     private RequestStack $requestStack;
  94.     /**
  95.      * @var SceneRenderer
  96.      */
  97.     private SceneRenderer $sceneRenderer;
  98.     /**
  99.      * @var DraftManager
  100.      */
  101.     private DraftManager $draftManager;
  102.     /**
  103.      * @var Locksmith
  104.      */
  105.     private Locksmith $locksmith;
  106.     /**
  107.      * @var GlobalVariables
  108.      */
  109.     private GlobalVariables $globalVariables;
  110.     /**
  111.      * @param TranslatorInterface $translator
  112.      * @param S3Wrapper $s3Wrapper
  113.      * @param ContainerService $containerService
  114.      * @param EntityManager $em
  115.      * @param FileManager $fileManager
  116.      * @param DatabaseLoader $databaseLoader
  117.      * @param RequestStack $requestStack
  118.      * @param SceneRenderer $sceneRenderer
  119.      * @param DraftManager $draftManager
  120.      * @param Locksmith $locksmith
  121.      * @param GlobalVariables $globalVariables
  122.      */
  123.     public function __construct(
  124.         TranslatorInterface $translator,
  125.         S3Wrapper $s3Wrapper,
  126.         ContainerService $containerService,
  127.         EntityManager $em,
  128.         FileManager $fileManager,
  129.         DatabaseLoader $databaseLoader,
  130.         RequestStack $requestStack,
  131.         SceneRenderer $sceneRenderer,
  132.         DraftManager $draftManager,
  133.         Locksmith $locksmith,
  134.         GlobalVariables $globalVariables
  135.     )
  136.     {
  137.         $this->translator $translator;
  138.         $this->s3Wrapper $s3Wrapper;
  139.         $this->containerService $containerService;
  140.         $this->em $em;
  141.         $this->fileManager $fileManager;
  142.         $this->databaseLoader $databaseLoader;
  143.         $this->requestStack $requestStack;
  144.         $this->sceneRenderer $sceneRenderer;
  145.         $this->draftManager $draftManager;
  146.         $this->locksmith $locksmith;
  147.         $this->globalVariables $globalVariables;
  148.     }
  149.     /**
  150.      * {@inheritdoc}
  151.      */
  152.     public function getTokenParsers(): array
  153.     {
  154.         return array(
  155.             new InlineScriptTokenParser(),
  156.             new InlineStyleTokenParser(),
  157.             new InlineHtmlTokenParser(),
  158.             new \Cms\CoreBundle\Twig\TokenParser\ScopedIncludeTokenParser(),
  159.             new \Cms\CoreBundle\Twig\TokenParser\ScopedEmbedTokenParser(),
  160.         );
  161.     }
  162.     /**
  163.      * {@inheritdoc}
  164.      */
  165.     public function getFilters(): array
  166.     {
  167.         return array(
  168.             new TwigFilter(
  169.                 'base64_encode',
  170.                 array($this'base64_encode')
  171.             ),
  172.             new TwigFilter(
  173.                 'bytes',
  174.                 array($this'bytesFilter')
  175.             ),
  176.             new TwigFilter(
  177.                 'call',
  178.                 array($this'filterCall')
  179.             ),
  180.             new TwigFilter(
  181.                 'call_array',
  182.                 array($this'filterCallArray')
  183.             ),
  184.             new TwigFilter(
  185.                 'regex',
  186.                 array($this'regex')
  187.             ),
  188.             new TwigFilter(
  189.                 'flatten',
  190.                 array($this'filterFlatten')
  191.             ),
  192.             new TwigFilter(
  193.                 'arrayify',
  194.                 array($this'filterArrayify')
  195.             ),
  196.             new TwigFilter(
  197.                 'rot13',
  198.                 array($this'rot13')
  199.             ),
  200.             new TwigFilter(
  201.                 'scoped',
  202.                 array($this'scoped')
  203.             ),
  204.             new TwigFilter(
  205.                 'vformat',
  206.                 'vsprintf'
  207.             ),
  208.             new TwigFilter(
  209.                 'extract',
  210.                 array($this'extract')
  211.             ),
  212.         );
  213.     }
  214.     /**
  215.      * @param mixed $value
  216.      * @param string $property
  217.      * @return array
  218.      */
  219.     public function extract($value$property)
  220.     {
  221.         $results = [];
  222.         foreach ($value as $v) {
  223.             if (is_array($v)) {
  224.                 $results[] = $v[$property];
  225.             } else if (is_object($v)) {
  226.                 $refl = new \ReflectionObject($v);
  227.                 if ($refl->hasProperty($property) && $refl->getProperty($property)->isPublic()) {
  228.                     $results[] = $refl->getProperty($property)->getValue($v);
  229.                 } else if (method_exists($v$property)) {
  230.                     $results[] = $v->$property();
  231.                 } else if (method_exists($vsprintf('get%s'ucfirst($property)))) {
  232.                     $results[] = $v->{'get'.ucfirst($property)}();
  233.                 } else {
  234.                     throw new \Exception();
  235.                 }
  236.             } else {
  237.                 throw new \Exception();
  238.             }
  239.         }
  240.         return $results;
  241.     }
  242.     /**
  243.      * @param string $value
  244.      * @return string
  245.      */
  246.     public function base64_encode($value)
  247.     {
  248.         return Base64::encode((string) $value);
  249.     }
  250.     /**
  251.      * @param mixed $value
  252.      * @param string $glue
  253.      * @return string
  254.      */
  255.     public function filterFlatten($value$glue ' ')
  256.     {
  257.         if (is_array($value)) {
  258.             return implode($glue$value);
  259.         }
  260.         return trim((string) $value);
  261.     }
  262.     /**
  263.      * @param mixed $value
  264.      * @return array
  265.      */
  266.     public function filterArrayify($value)
  267.     {
  268.         if (is_array($value)) {
  269.             return $value;
  270.         }
  271.         return array($value);
  272.     }
  273.     /**
  274.      * @param string $value
  275.      * @return string
  276.      */
  277.     public function rot13($value)
  278.     {
  279.         return str_rot13($value);
  280.     }
  281.     /**
  282.      * @param callable $closure
  283.      * @return mixed
  284.      */
  285.     public function filterCall(callable $closure)
  286.     {
  287.         // get the remaining arguments, these are passed to the callable
  288.         $arguments array_slice(func_get_args(), 1);
  289.         // run and get the result
  290.         return call_user_func_array($closure$arguments);
  291.     }
  292.     /**
  293.      * @param callable $closure
  294.      * @param array $args
  295.      * @return mixed
  296.      */
  297.     public function filterCallArray(callable $closure, array $args)
  298.     {
  299.         return call_user_func_array($closure$args);
  300.     }
  301.     /**
  302.      * {@inheritdoc}
  303.      */
  304.     public function getFunctions(): array
  305.     {
  306.         return array(
  307.             new TwigFunction(
  308.                 'blame',
  309.                 array($this'blame'),
  310.                 array(
  311.                     'needs_environment' => true,
  312.                     'is_safe' => array(
  313.                         'all',
  314.                     ),
  315.                 )
  316.             ),
  317.             new TwigFunction(
  318.                 's3_entity',
  319.                 array($this'functionS3Entity'),
  320.                 []
  321.             ),
  322.             new TwigFunction(
  323.                 'instanceof',
  324.                 array($this'functionInstanceof'),
  325.                 []
  326.             ),
  327.             new TwigFunction(
  328.                 'embed_script',
  329.                 array($this'embedScript'),
  330.                 array(
  331.                     'is_safe' => array(
  332.                         'html',
  333.                     ),
  334.                 )
  335.             ),
  336.             new TwigFunction(
  337.                 'embed_script_unique',
  338.                 array($this'embedScriptUnique'),
  339.                 array(
  340.                     'is_safe' => array(
  341.                         'html',
  342.                     ),
  343.                 )
  344.             ),
  345.             new TwigFunction(
  346.                 'embed_style',
  347.                 array($this'embedStyle'),
  348.                 array(
  349.                     'is_safe' => array(
  350.                         'html',
  351.                     ),
  352.                 )
  353.             ),
  354.             new TwigFunction(
  355.                 'embed_meta',
  356.                 array($this'embedMeta'),
  357.                 array(
  358.                     'is_safe' => array(
  359.                         'html',
  360.                     ),
  361.                 )
  362.             ),
  363.             new TwigFunction(
  364.                 'embed_title',
  365.                 array($this'embedTitle'),
  366.                 array(
  367.                     'is_safe' => array(
  368.                         'html',
  369.                     ),
  370.                 )
  371.             ),
  372.             new TwigFunction(
  373.                 'html_attrs',
  374.                 array($this'htmlAttrs'),
  375.                 array(
  376.                     'is_safe' => array(
  377.                         'html',
  378.                     ),
  379.                 )
  380.             ),
  381.             new TwigFunction(
  382.                 'body_attrs',
  383.                 array($this'bodyAttrs'),
  384.                 array(
  385.                     'is_safe' => array(
  386.                         'html',
  387.                     ),
  388.                 )
  389.             ),
  390.             new TwigFunction(
  391.                 'curpath',
  392.                 array($this'curpathFunction'),
  393.                 array(
  394.                     'needs_environment' => true,
  395.                     'needs_context' => true,
  396.                 )
  397.             ),
  398.             new TwigFunction(
  399.                 'redirpath',
  400.                 array($this'redirpathFunction'),
  401.                 array(
  402.                     'needs_environment' => true,
  403.                     'needs_context' => true,
  404.                 )
  405.             ),
  406.             new TwigFunction(
  407.                 'is_type',
  408.                 array($this'functionIsType'),
  409.                 []
  410.             ),
  411.             new TwigFunction(
  412.                 'base64_encode',
  413.                 array($this'functionBase64Encode'),
  414.                 []
  415.             ),
  416.             new TwigFunction(
  417.                 'region',
  418.                 array($this'functionRegion'),
  419.                 array(
  420.                     'needs_context' => true,
  421.                     'is_safe' => array(
  422.                         'all',
  423.                     ),
  424.                 )
  425.             ),
  426.             new TwigFunction(
  427.                 'token',
  428.                 array($this'functionToken'),
  429.                 []
  430.             ),
  431.             new TwigFunction(
  432.                 'headjs_script',
  433.                 array($this'functionHeadJsScript'),
  434.                 array(
  435.                     'is_safe' => array(
  436.                         'all',
  437.                     ),
  438.                 )
  439.             ),
  440.             new TwigFunction(
  441.                 'nbsp',
  442.                 array($this'functionNbsp'),
  443.                 []
  444.             ),
  445.             new TwigFunction(
  446.                 'regex',
  447.                 array($this'regex'),
  448.                 []
  449.             ),
  450.             new TwigFunction(
  451.                 'cms_path_prefix',
  452.                 array($this'functionCmsPathPrefix'),
  453.                 []
  454.             ),
  455.             new TwigFunction(
  456.                 'cms_path_host',
  457.                 array($this'functionCmsPathHost'),
  458.                 []
  459.             ),
  460.             new TwigFunction(
  461.                 'ui_container_indent',
  462.                 array($this'ui_container_indent'),
  463.                 array(
  464.                     'is_safe' => ['html'],
  465.                 )
  466.             ),
  467.             new TwigFunction(
  468.                 'cms_modules_listUrl',
  469.                 array($this'cms_modules_listUrl'),
  470.                 []
  471.             ),
  472.             new TwigFunction(
  473.                 'cms_modules_viewUrl',
  474.                 array($this'cms_modules_viewUrl'),
  475.                 []
  476.             ),
  477.             new TwigFunction(
  478.                 'cms_files_imageopt',
  479.                 array($this'cms_files_imageopt'),
  480.                 []
  481.             ),
  482.             new TwigFunction(
  483.                 'cms_files_drive',
  484.                 array($this'cms_files_drive'),
  485.                 []
  486.             ),
  487.             new TwigFunction(
  488.                 'ui_tree_estimation',
  489.                 array($this'ui_tree_estimation'),
  490.                 []
  491.             ),
  492.             new TwigFunction(
  493.                 'ui_is_locked',
  494.                 array($this'ui_is_locked'),
  495.                 []
  496.             ),
  497.             new TwigFunction(
  498.                 'ui_is_placeholder',
  499.                 array($this'ui_is_placeholder'),
  500.                 []
  501.             ),
  502.             new TwigFunction(
  503.                 'ui_has_draft',
  504.                 array($this'ui_has_draft'),
  505.                 []
  506.             ),
  507.             new TwigFunction(
  508.                 'ui_is_active_draft',
  509.                 array($this'ui_is_active_draft'),
  510.                 []
  511.             ),
  512.             new TwigFunction(
  513.                 'ui_is_synced',
  514.                 array($this'ui_is_synced'),
  515.                 []
  516.             ),
  517.             new TwigFunction(
  518.                 'ui_is_shared',
  519.                 array($this'ui_is_shared'),
  520.                 []
  521.             ),
  522.             new TwigFunction(
  523.                 'ui_is_shareable',
  524.                 array($this'ui_is_shareable'),
  525.                 []
  526.             ),
  527.             new TwigFunction(
  528.                 'cms_abstract',
  529.                 array($this'cms_abstract'),
  530.                 []
  531.             ),
  532.             new TwigFunction(
  533.                 'cms_base_override',
  534.                 array($this'cms_base_override'),
  535.                 []
  536.             ),
  537.             new TwigFunction(
  538.                 'cms_package_override',
  539.                 array($this'cms_package_override'),
  540.                 array(
  541.                     'needs_context' => true,
  542.                 )
  543.             ),
  544.             new TwigFunction(
  545.                 'cms_theme_override',
  546.                 array($this'cms_theme_override'),
  547.                 array(
  548.                     'needs_context' => true,
  549.                 )
  550.             ),
  551.             new TwigFunction(
  552.                 'cms_class',
  553.                 array($this'cms_class'),
  554.                 []
  555.             ),
  556.             new TwigFunction(
  557.                 'infscroll',
  558.                 array($this'infscroll'),
  559.                 []
  560.             ),
  561.             new TwigFunction(
  562.                 'ifgzip',
  563.                 array($this'ifgzip'),
  564.                 array(
  565.                     'needs_environment' => false,
  566.                     'needs_context' => true,
  567.                 )
  568.             ),
  569.             new TwigFunction(
  570.                 'scoped',
  571.                 array($this'scoped'),
  572.                 []
  573.             ),
  574.             new TwigFunction(
  575.                 'get_class',
  576.                 array($this'get_class'),
  577.                 []
  578.             ),
  579.             new TwigFunction(
  580.                 'ui_ssl_expiration_helpers',
  581.                 array($this'ui_ssl_expiration_helpers'),
  582.                 []
  583.             ),
  584.             new TwigFunction(
  585.                 'is_route',
  586.                 array($this'is_route'),
  587.                 []
  588.             ),
  589.             new TwigFunction(
  590.                 'first_of',
  591.                 array($this'first_of'),
  592.                 []
  593.             ),
  594.             new TwigFunction(
  595.                 'btstamp',
  596.                 array($this'btstamp'),
  597.                 array(
  598.                     'needs_environment' => true,
  599.                     'is_safe' => 'html',
  600.                 )
  601.             ),
  602.         );
  603.     }
  604.     /**
  605.      * @param Environment $twig
  606.      * @param mixed $thing
  607.      * @param bool $avatar
  608.      * @return string
  609.      */
  610.     public function btstamp(Environment $twig$thingbool $avatar true): string
  611.     {
  612.         $image null;
  613.         $texts = [];
  614.         if ($avatar === true && $thing instanceof BlameableInterface) {
  615.             if ($thing->getBlamedBy() instanceof Account) {
  616.                 if ($thing->getBlamedBy()->getSystemProfile()->hasAvatar()) {
  617.                     $url $twig->getFunction('s3_entity')->getCallable()($thing->getBlamedBy(), '/avatar/thumb');
  618.                 } else {
  619.                     $url '/ui/images/avatars/avatar-na-36x36.png';
  620.                 }
  621.                 $title $thing->getBlamedBy()->getDisplayName();
  622.             } else {
  623.                 $url '/ui/images/avatars/avatar-na-36x36.png';
  624.                 $title 'SYSTEM';
  625.             }
  626.             $image sprintf(
  627.                 '<img class="user-avatar round" src="%s" alt="%s" title="%s" />',
  628.                 $url,
  629.                 $title,
  630.                 $title
  631.             );
  632.         }
  633.         if ($thing instanceof TimestampableInterface) {
  634.             $texts[] = ($thing->isUpdated()) ? 'Updated' 'Created';
  635.         } else if ($thing instanceof BlameableInterface) {
  636.             $texts[] = ( ! empty($thing->getUpdatedBy())) ? 'Updated' 'Created';
  637.         } else {
  638.             throw new \Exception();
  639.         }
  640.         if ($thing instanceof BlameableInterface) {
  641.             $texts[] = sprintf(
  642.                 'by %s',
  643.                 ( ! empty($thing->getBlamedBy()))
  644.                     ? $thing->getBlamedBy()->getDisplayName()
  645.                     : 'SYSTEM'
  646.             );
  647.         }
  648.         if ($thing instanceof TimestampableInterface) {
  649.             $texts[] = $twig->getFilter('ui_datetime')->getCallable()($thing->getTimestampedAt());
  650.         }
  651.         return trim(sprintf(
  652.             '%s %s',
  653.             $image,
  654.             implode(' 'array_map(
  655.                 function (string $text) use ($twig) {
  656.                     return $twig->getFilter('escape')->getCallable()($twig$text);
  657.                 },
  658.                 $texts
  659.             ))
  660.         ));
  661.     }
  662.     /**
  663.      * @param array $tests
  664.      * @param mixed  $default
  665.      * @return mixed
  666.      */
  667.     public function first_of(array $tests$default null)
  668.     {
  669.         foreach ($tests as $result => $test) {
  670.             if (is_array($test)) {
  671.                 if ($test[0] == true) {
  672.                     return $test[1];
  673.                 }
  674.             }
  675.             if ($test == true) {
  676.                 return $result;
  677.             }
  678.         }
  679.         return $default;
  680.     }
  681.     /**
  682.      * @param string $route
  683.      * @param array $params
  684.      * @param Request|null $request
  685.      * @return bool
  686.      */
  687.     public function is_route($route, array $params = [], Request $request null)
  688.     {
  689.         // default the request if not given
  690.         if (empty($request)) {
  691.             $request $this->requestStack->getMasterRequest();
  692.         }
  693.         // if route does not match, fail
  694.         if ($request->get('_route') !== $route) {
  695.             return false;
  696.         }
  697.         // now see if we have params to check
  698.         foreach ($params as $key => $value) {
  699.             // must be set in the route params
  700.             if ( ! array_key_exists($key$request->get('_route_params'))) {
  701.                 return false;
  702.             }
  703.             // it does exist, so it should match
  704.             if ($request->get('_route_params')[$key] !== $value) {
  705.                 return false;
  706.             }
  707.         }
  708.         // everything matches
  709.         return true;
  710.     }
  711.     public function ui_ssl_expiration_helpers(SslCertificate $certificate)
  712.     {
  713.         static $lookup = array(
  714.             CustomSslCertificate::class => array(
  715.                 => 'danger',
  716.                 30 => 'warning',
  717.             ),
  718.             LetsEncryptSslCertificate::class => array(
  719.                 => 'danger',
  720.                 15 => 'warning',
  721.             ),
  722.         );
  723.         $diff intval(DateTimeUtils::current()->diff($certificate->getExpiration())->format('%r%a'));
  724.         foreach ($lookup[ClassUtils::getClass($certificate)] as $limit => $helper) {
  725.             if ($diff <= $limit) {
  726.                 return $helper;
  727.             }
  728.         }
  729.         return 'success';
  730.     }
  731.     public function get_class($object)
  732.     {
  733.         return ClassUtils::getClass($object);
  734.     }
  735.     public function scoped(array $variables)
  736.     {
  737.         return array(
  738.             '_args' => $variables,
  739.         );
  740.     }
  741.     public function ifgzip($context$if$else)
  742.     {
  743.         // make sure we have valid arguments
  744.         if (empty($if) || empty($else)) {
  745.             throw new \Exception();
  746.         }
  747.         // make sure we have app and can get request
  748.         if ( ! array_key_exists('app'$context) || ! $context['app'] instanceof AppVariable) {
  749.             // assume gzip not supported
  750.             return (string) $else;
  751.         }
  752.         // code completion
  753.         /** @var AppVariable $app */
  754.         $app $context['app'];
  755.         // make sure we have a request
  756.         if (empty($app->getRequest())) {
  757.             // assume gzip not supported
  758.             return (string) $else;
  759.         }
  760.         // get the headers and check if an encoding header is given
  761.         $headers $app->getRequest()->headers;
  762.         if ($headers->has('Accept-Encoding')) {
  763.             // see if we mention anything about gzip
  764.             if (strpos($headers->get('Accept-Encoding'), 'gzip') !== false) {
  765.                 // should be able to use gzip
  766.                 return (string) $if;
  767.             }
  768.         }
  769.         // assume gzip not supported
  770.         return (string) $else;
  771.     }
  772.     public function infscroll($url)
  773.     {
  774.         // reusable regex
  775.         $regex '/(.+\\/)0$/';
  776.         // make sure we match
  777.         if (preg_match($regex$url) !== 1) {
  778.             throw new \Exception();
  779.         }
  780.         return preg_replace($regex'$1{{#}}'$url);
  781.     }
  782.     /**
  783.      * @param object $thing
  784.      * @return string|null
  785.      */
  786.     public function cms_class($thing)
  787.     {
  788.         if (is_object($thing)) {
  789.             return ClassUtils::getClass($thing);
  790.         }
  791.         return null;
  792.     }
  793.     /**
  794.      * @param string $path
  795.      * @return string
  796.      */
  797.     public function cms_base_override($path)
  798.     {
  799.         return sprintf(
  800.             '@package$/%s',
  801.             ltrim($path'/')
  802.         );
  803.     }
  804.     /**
  805.      * @param array $context
  806.      * @param string $path
  807.      * @param Template $theme
  808.      * @return string
  809.      */
  810.     public function cms_package_override(array $context$pathTemplate $theme null)
  811.     {
  812.         // if no theme, try to get globals from context; can get theme from that
  813.         if (empty($theme) && array_key_exists('_globals'$context)  && ! empty($context['_globals'])) {
  814.             $globals $context['_globals'];
  815.             if ($globals instanceof FrontendGlobals) {
  816.                 $theme $globals->getTheme();
  817.             }
  818.         }
  819.         // return package template
  820.         return sprintf(
  821.             '@package%s/%s',
  822.             ($theme !== null) ? $theme->getPackage() : '$',
  823.             ltrim($path'/')
  824.         );
  825.     }
  826.     /**
  827.      * @param array $context
  828.      * @param string $path
  829.      * @param Template $theme
  830.      * @return string
  831.      */
  832.     public function cms_theme_override(array $context$pathTemplate $theme null)
  833.     {
  834.         // if no theme, try to get globals from context; can get theme from that
  835.         if (empty($theme) && array_key_exists('_globals'$context)  && ! empty($context['_globals'])) {
  836.             $globals $context['_globals'];
  837.             if ($globals instanceof FrontendGlobals) {
  838.                 $theme $globals->getTheme();
  839.             }
  840.         }
  841.         return $this->databaseLoader->determine(
  842.             $theme,
  843.             $path
  844.         );
  845.     }
  846.     /**
  847.      * @param Environment $environment
  848.      * @param mixed $thing
  849.      * @return string
  850.      */
  851.     public function blame(Environment $environment$thing)
  852.     {
  853.         $timestamp '';
  854.         if ($thing instanceof TimestampableInterface) {
  855.             if ($thing->getTimestampedAt() !== null) {
  856.                 if ($thing->isUpdated()) {
  857.                     $timestamp 'Updated';
  858.                 } else {
  859.                     $timestamp 'Created';
  860.                 }
  861.                 $timestamp sprintf(
  862.                     '%s %s',
  863.                     $timestamp,
  864.                     call_user_func(
  865.                         $environment->getFilter('ui_relativeDate')->getCallable(),
  866.                         $thing->getTimestampedAt()
  867.                     )
  868.                 );
  869.             }
  870.         }
  871.         $blamestamp '';
  872.         if ($thing instanceof BlameableInterface) {
  873.             if ($thing->getBlamedBy() !== null) {
  874.                 $blamestamp sprintf(
  875.                     'by %s',
  876.                     $thing->getBlamedBy()->getDisplayName()
  877.                 );
  878.             }
  879.         }
  880.         return trim(sprintf(
  881.             '%s %s',
  882.             $timestamp,
  883.             $blamestamp
  884.         ));
  885.     }
  886.     /**
  887.      * @param mixed|LockableInterface $thing
  888.      * @param Account|null $account
  889.      * @return bool
  890.      */
  891.     public function ui_is_locked($thingAccount $account null): bool
  892.     {
  893.         // SCHOOLNOW: skip if we are passing a sn data type
  894.         return $thing instanceof LockableInterface && $this->locksmith->isLocked($thing$account);
  895.     }
  896.     /**
  897.      * @param mixed|Proxy $thing
  898.      * @return bool
  899.      */
  900.     public function ui_is_placeholder($thing): bool
  901.     {
  902.         // SCHOOLNOW: skip if we are passing a sn data type
  903.         return $thing instanceof Proxy && $thing->isPlaceholder();
  904.     }
  905.     /**
  906.      * @param mixed|Proxy $thing
  907.      * @return bool
  908.      */
  909.     public function ui_has_draft($thing): bool
  910.     {
  911.         // SCHOOLNOW: skip if we are passing a sn data type
  912.         return $thing instanceof Proxy && $this->draftManager->hasActiveDraft($thing);
  913.     }
  914.     /**
  915.      * @param mixed|Draft $thing
  916.      * @return bool
  917.      */
  918.     public function ui_is_active_draft($thing): bool
  919.     {
  920.         // SCHOOLNOW: skip if we are passing a sn data type
  921.         return $thing instanceof Draft && $this->draftManager->isActiveDraft($thing);
  922.     }
  923.     /**
  924.      * @param mixed|SyncableInterface $thing
  925.      * @return bool
  926.      */
  927.     public function ui_is_synced($thing): bool
  928.     {
  929.         // SCHOOLNOW: skip if we are passing a sn data type
  930.         return $thing instanceof SyncableInterface && $thing->isSynced();
  931.     }
  932.     /**
  933.      * @param mixed|ShareableInterface $thing
  934.      * @return bool
  935.      */
  936.     public function ui_is_shared($thing): bool
  937.     {
  938.         // SCHOOLNOW: skip if we are passing a sn data type
  939.         return $thing instanceof ShareableInterface && $thing->isShared();
  940.     }
  941.     /**
  942.      * @param mixed|ShareableInterface $thing
  943.      * @return bool
  944.      */
  945.     public function ui_is_shareable($thing): bool
  946.     {
  947.         // SCHOOLNOW: skip if we are passing a sn data type
  948.         return $thing instanceof ShareableInterface && $thing->isMaster();
  949.     }
  950.     /**
  951.      * @param NestableInterface $node
  952.      * @return float
  953.      */
  954.     public function ui_tree_estimation(NestableInterface $node)
  955.     {
  956.         return ((($node->getRight() - $node->getLeft()) - 1) / 2);
  957.     }
  958.     /**
  959.      * @param null $url
  960.      * @return string
  961.      */
  962.     public function cms_image_fallback($url null)
  963.     {
  964.         // if no fallback url given, use our generic one
  965.         if ($url === null) {
  966.             $url 'https://via.placeholder.com/1600x1200';
  967.         }
  968.         // render out the html attribute
  969.         return sprintf(
  970.             'onerror="this.src = \'%s\';this.onerror = null;"',
  971.             $url
  972.         );
  973.     }
  974.     /**
  975.      * @param null $optimization
  976.      * @return string
  977.      * @throws Exception
  978.      */
  979.     public function cms_image_placeholder($optimization null)
  980.     {
  981.         // if no mask given, assume full
  982.         if ($optimization === null) {
  983.             $optimization ImageOptimization::MASKS__GENERIC__FULL;
  984.         }
  985.         if (is_string($optimization)) {
  986.             $constant ImageOptimization::class.'::'.$optimization;
  987.             if ( ! defined($constant)) {
  988.                 throw new \Exception();
  989.             }
  990.             $optimization constant($constant);
  991.         }
  992.         // get the dimensions based on the mask
  993.         $size ImageOptimization::$sizes[$optimization];
  994.         [$width$height] = array_map(
  995.             function($value) {
  996.                 return intval($value);
  997.             },
  998.             explode('x'$size)
  999.         );
  1000.         // default sizes if zeroed
  1001.         if ($width === 0) {
  1002.             $width 1600;
  1003.         }
  1004.         if ($height === 0) {
  1005.             $height 1200;
  1006.         }
  1007.         // return a placeholdit url
  1008.         return sprintf(
  1009.             'https://via.placeholder.com/%sx%s',
  1010.             $width,
  1011.             $height
  1012.         );
  1013.     }
  1014.     /**
  1015.      * @param $file
  1016.      * @param null $optimization
  1017.      * @param null $expires
  1018.      * @param array $args
  1019.      * @return string
  1020.      * @throws Exception
  1021.      */
  1022.     public function cms_files_imageopt($file$optimization null$expires null, array $args = [])
  1023.     {
  1024.         if (is_int($file)) {
  1025.             $file $this->em->getRepository(File::class)->findExact($file);
  1026.         }
  1027.         if (is_string($file)) {
  1028.             $file $this->fileManager->identLoad($file);
  1029.         }
  1030.         if ( ! $file instanceof ImageFile) {
  1031.             //throw new \Exception();
  1032.             return '/ui/images/placeholders/news/2048.png';
  1033.         }
  1034.         if ($optimization === null) {
  1035.             $optimization ImageOptimization::MASKS__GENERIC__FULL;
  1036.         }
  1037.         if (is_string($optimization)) {
  1038.             $constant ImageOptimization::class.'::'.$optimization;
  1039.             if ( ! defined($constant)) {
  1040.                 throw new \Exception();
  1041.             }
  1042.             $optimization constant($constant);
  1043.         }
  1044.         return $this->s3Wrapper->entityUrl(
  1045.             S3Wrapper::BUCKETS__STORAGE,
  1046.             $file,
  1047.             '/optimizations/' $optimization,
  1048.             $expires,
  1049.             $args
  1050.         );
  1051.     }
  1052.     /**
  1053.      * @param mixed $container
  1054.      * @param string $path
  1055.      * @param null $optimization
  1056.      * @param null $expires
  1057.      * @param array $args
  1058.      * @return string|null
  1059.      */
  1060.     public function cms_files_drive($container$path$optimization null$expires null, array $args = [])
  1061.     {
  1062.         // load up container, should be a drive storage
  1063.         if ( ! $container instanceof Container) {
  1064.             $container $this->em->getRepository(StorageContainer::class)->findOneBy(array(
  1065.                 'slug' => $container,
  1066.             ));
  1067.         }
  1068.         if ( ! $container instanceof StorageContainer) {
  1069.             return null;
  1070.         }
  1071.         // value could be percent encoded, decode it
  1072.         if (strpos($path'%') !== false) {
  1073.             $path rawurldecode($path);
  1074.         }
  1075.         // file is a path, need to resolve it
  1076.         try {
  1077.             $file $this->fileManager->resolveFile($container$path);
  1078.         } catch (\Exception $e) {
  1079.             $file null;
  1080.         }
  1081.         if ( ! $file instanceof ImageFile) {
  1082.             return null;
  1083.         }
  1084.         // determine what kind of optimization we want
  1085.         if ($optimization === null) {
  1086.             $optimization ImageOptimization::MASKS__GENERIC__FULL;
  1087.         }
  1088.         if (is_string($optimization)) {
  1089.             $constant ImageOptimization::class.'::'.$optimization;
  1090.             if ( ! defined($constant)) {
  1091.                 $optimization ImageOptimization::MASKS__GENERIC__FULL;
  1092.             } else {
  1093.                 $optimization constant($constant);
  1094.             }
  1095.         }
  1096.         // get the s3 url
  1097.         return $this->s3Wrapper->entityUrl(
  1098.             S3Wrapper::BUCKETS__STORAGE,
  1099.             $file,
  1100.             '/optimizations/' $optimization,
  1101.             $expires,
  1102.             $args
  1103.         );
  1104.     }
  1105.     /**
  1106.      * @param array $containers
  1107.      * @param $module
  1108.      * @param null $extra
  1109.      * @return string
  1110.      */
  1111.     public function cms_modules_url(array $containers$module$extra null)
  1112.     {
  1113.         if ($module instanceof ModuleConfig) {
  1114.             $module $module->key();
  1115.         }
  1116.         return sprintf(
  1117.             '%s/%s%s',
  1118.             $this->functionCmsPathPrefix($containers),
  1119.             $module,
  1120.             ( ! empty($extra)) ? ('/' ltrim($extra'/')) : ''
  1121.         );
  1122.     }
  1123.     /**
  1124.      * @param array $containers
  1125.      * @param $module
  1126.      * @param $item
  1127.      * @param $extra
  1128.      * @return string
  1129.      */
  1130.     public function cms_modules_itemUrl(array $containers$module$item$extra)
  1131.     {
  1132.         if ($module instanceof ModuleConfig) {
  1133.             $module $module->key();
  1134.         }
  1135.         if ($item instanceof IdentifiableInterface) {
  1136.             $item $item->getId();
  1137.         }
  1138.         return sprintf(
  1139.             '%s/%s/%s/%s',
  1140.             $this->functionCmsPathPrefix($containers),
  1141.             $module,
  1142.             $item,
  1143.             ltrim($extra'/')
  1144.         );
  1145.     }
  1146.     /**
  1147.      * @param Container $container
  1148.      * @param int $multiplier
  1149.      * @return string
  1150.      */
  1151.     public function ui_container_indent(Container $container$icons false)
  1152.     {
  1153.         $icon null;
  1154.         if ($icons === true) {
  1155.             switch (true) {
  1156.                 case empty($container->getParent()) && $container instanceof GenericContainer:
  1157.                     $icon '&#xF0AC;';
  1158.                     break;
  1159.                 case empty($container->getParent()) && $container instanceof IntranetContainer:
  1160.                     $icon '&#xF233;';
  1161.                     break;
  1162.                 case empty($container->getParent()) && $container instanceof PersonalContainer:
  1163.                     $icon '&#xF0C0;';
  1164.                     break;
  1165.                 case empty($container->getParent()) && $container instanceof StorageContainer:
  1166.                     $icon '&#xF0A0;';
  1167.                     break;
  1168.                 default:
  1169.                     $icon '&#xF114;';
  1170.             }
  1171.         }
  1172.         return str_repeat(Strings::nbsp(), ($container->getLevel() * 4)) . (( ! empty($icon)) ? ($icon ' ') : ' ') . htmlspecialchars($container->getName());
  1173.     }
  1174.     /**
  1175.      * @param array|Container[] $containers
  1176.      * @param string $scheme
  1177.      * @return string
  1178.      */
  1179.     public function functionCmsPathPrefix($containers$scheme null)
  1180.     {
  1181.         if ( ! is_array($containers)) {
  1182.             $containers = array($containers);
  1183.         }
  1184.         return $this->containerService->getFrontLink(
  1185.             $containers[0],
  1186.             $scheme
  1187.         );
  1188.     }
  1189.     /**
  1190.      * @param Container|Container[] $containers
  1191.      * @return string
  1192.      * @throws \Exception
  1193.      */
  1194.     public function functionCmsPathHost($containers)
  1195.     {
  1196.         if ( ! is_array($containers)) {
  1197.             $containers = array($containers);
  1198.         }
  1199.         /** @var array|Container[] $containers */
  1200.         $container $containers[count($containers) - 1];
  1201.         if ($container->getDomain() === null) {
  1202.             throw new \Exception();
  1203.         }
  1204.         return 'http://'.$container->getDomain()->getHost();
  1205.     }
  1206.     /**
  1207.      * @param int $count
  1208.      * @return string
  1209.      */
  1210.     public function functionNbsp($count 1)
  1211.     {
  1212.         // NOTE: this is the utf8 non-breaking space character
  1213.         return str_repeat(' '$count);
  1214.     }
  1215.     /**
  1216.      * @param string $var
  1217.      * @param string $find
  1218.      * @param string $replace
  1219.      * @return string
  1220.      */
  1221.     public function regex($var$find ''$replace '')
  1222.     {
  1223.         if (is_string($var) && strlen($var)) {
  1224.             $var preg_replace($find$replace$var);
  1225.         }
  1226.         return $var;
  1227.     }
  1228.     /**
  1229.      * @param string $url
  1230.      * @param string $callback
  1231.      * @return string
  1232.      * @throws Exception
  1233.      */
  1234.     public function functionHeadJsScript($url$callback '')
  1235.     {
  1236.         return sprintf(
  1237.             '
  1238.                 ;(function(window, document, $, undefined) {
  1239.                     if (window.head !== undefined) {
  1240.                         window.head.load(\'%s\', function() {
  1241.                             %s
  1242.                         });
  1243.                     }
  1244.                 })(window, document, jQuery);
  1245.             ',
  1246.             $url,
  1247.             $callback
  1248.         );
  1249.     }
  1250.     /**
  1251.      * @param TenantedEntity $entity
  1252.      * @param string $subKey
  1253.      * @param mixed $expires
  1254.      * @param array $args
  1255.      * @return string
  1256.      * @throws Exception
  1257.      */
  1258.     public function functionS3Entity(TenantedEntity $entity$subKey$expires null, array $args = [])
  1259.     {
  1260.         $key $this->s3Wrapper->entityKey($entity$subKey);
  1261.         $url $this->s3Wrapper->url(S3Wrapper::BUCKETS__STORAGE$key$expires$args);
  1262.         return $url;
  1263.     }
  1264.     /**
  1265.      * @param mixed $thing
  1266.      * @param string $class
  1267.      * @return bool
  1268.      */
  1269.     public function functionInstanceof($thing$class)
  1270.     {
  1271.         if ( ! is_object($thing)) {
  1272.             return false;
  1273.         }
  1274.         return (ClassUtils::getClass($thing) === $class || is_subclass_of($thing$class));
  1275.     }
  1276.     /**
  1277.      * @param int|null $length
  1278.      * @return string
  1279.      */
  1280.     public function functionToken($length null)
  1281.     {
  1282.         return Tokens::generate($length);
  1283.     }
  1284.     /**
  1285.      * @param array $context
  1286.      * @param string $key
  1287.      * @return string
  1288.      * @throws Exception
  1289.      */
  1290.     public function functionRegion(array $context$key)
  1291.     {
  1292.         if (array_key_exists('_regions'$context) === false) {
  1293.             throw new Exception();
  1294.         }
  1295.         return $context['_regions'][$key];
  1296.     }
  1297.     /**
  1298.      * @param string $var
  1299.      * @return string
  1300.      */
  1301.     public function functionBase64Encode($var)
  1302.     {
  1303.         return Base64::encode($var);
  1304.     }
  1305.     /**
  1306.      * @param mixed $var
  1307.      * @param string $type
  1308.      * @return bool
  1309.      * @throws Exception
  1310.      */
  1311.     public function functionIsType($var$type)
  1312.     {
  1313.         switch ($type) {
  1314.             case 'array':
  1315.                 return is_array($var);
  1316.                 break;
  1317.             case 'boolean':
  1318.             case 'bool':
  1319.                 return is_bool($var);
  1320.                 break;
  1321.             case 'float':
  1322.                 return is_float($var);
  1323.                 break;
  1324.             case 'integer':
  1325.             case 'int':
  1326.                 return is_int($var);
  1327.                 break;
  1328.             case 'numeric':
  1329.                 return is_numeric($var);
  1330.                 break;
  1331.             case 'object':
  1332.                 return is_object($var);
  1333.                 break;
  1334.             case 'scalar':
  1335.                 return is_scalar($var);
  1336.                 break;
  1337.             case 'string':
  1338.                 return is_string($var);
  1339.                 break;
  1340.             case 'null':
  1341.                 return is_null($var);
  1342.                 break;
  1343.             default:
  1344.                 throw new Exception();
  1345.         }
  1346.     }
  1347.     /**
  1348.      * @param string $url
  1349.      * @param string|null $type
  1350.      * @param int|null $rank
  1351.      * @param string|null $group
  1352.      * @return string
  1353.      */
  1354.     public function embedScriptUnique($url$type null$rank null$group null)
  1355.     {
  1356.         // check title
  1357.         if ($url === null) {
  1358.             throw new Exception();
  1359.         }
  1360.         // obtain the document tool from current
  1361.         $documentSettings $this->sceneRenderer->currentScene()->getAssetsOrganizer();
  1362.         // create the title
  1363.         $structure = new LinkedScriptStructure($url$type);
  1364.         // check if it exists
  1365.         if ( ! $documentSettings->getScripts()->check($structure$group)) {
  1366.             // register
  1367.             $documentSettings->getScripts()->add($structure$rank$group);
  1368.         }
  1369.         /*
  1370.         return sprintf(
  1371.             '<!-- SCRIPT | LINKED | %s | %s | %s | %s -->',
  1372.             ($group !== null) ? $group : ScriptStructureBag::DEFAULTS__GROUP,
  1373.             ($rank !== null) ? $rank : ScriptStructureBag::DEFAULTS__PRIORITY,
  1374.             ($type !== null) ? $type : ScriptStructure::DEFAULTS__TYPE,
  1375.             $url
  1376.         );
  1377.         */
  1378.         return '';
  1379.     }
  1380.     /**
  1381.      * @param string $url
  1382.      * @param string|null $type
  1383.      * @param int|null $rank
  1384.      * @param string|null $group
  1385.      * @return string
  1386.      */
  1387.     public function embedScript($url$type null$rank null$group null)
  1388.     {
  1389.         // check title
  1390.         if ($url === null) {
  1391.             throw new Exception();
  1392.         }
  1393.         // obtain the document tool from current
  1394.         $documentSettings $this->sceneRenderer->currentScene()->getAssetsOrganizer();
  1395.         // create the title
  1396.         $structure = new LinkedScriptStructure($url$type);
  1397.         // register
  1398.         $documentSettings->getScripts()->add($structure$rank$group);
  1399.         /*
  1400.         return sprintf(
  1401.             '<!-- SCRIPT | LINKED | %s | %s | %s | %s -->',
  1402.             ($group !== null) ? $group : ScriptStructureBag::DEFAULTS__GROUP,
  1403.             ($rank !== null) ? $rank : ScriptStructureBag::DEFAULTS__PRIORITY,
  1404.             ($type !== null) ? $type : ScriptStructure::DEFAULTS__TYPE,
  1405.             $url
  1406.         );
  1407.         */
  1408.         return '';
  1409.     }
  1410.     /**
  1411.      * @param string $url
  1412.      * @param string|null $type
  1413.      * @param int|null $rank
  1414.      * @param string|null $group
  1415.      * @return string
  1416.      * @throws Exception
  1417.      */
  1418.     public function embedStyle($url$type null$rank null$group null)
  1419.     {
  1420.         // check title
  1421.         if ($url === null) {
  1422.             throw new Exception();
  1423.         }
  1424.         // obtain the document tool from current
  1425.         $documentSettings $this->sceneRenderer->currentScene()->getAssetsOrganizer();
  1426.         // create the title
  1427.         $structure = new LinkedStyleStructure($url$type);
  1428.         // register
  1429.         $documentSettings->getStyles()->add($structure$rank$group);
  1430.         /*
  1431.         return sprintf(
  1432.             '<!-- STYLE | LINKED | %s | %s | %s | %s -->',
  1433.             ($group !== null) ? $group : StyleStructureBag::DEFAULTS__GROUP,
  1434.             ($rank !== null) ? $rank : StyleStructureBag::DEFAULTS__PRIORITY,
  1435.             ($type !== null) ? $type : 'text/css',
  1436.             $url
  1437.         );
  1438.         */
  1439.         return '';
  1440.     }
  1441.     /**
  1442.      * @param string $name
  1443.      * @param string|null $value
  1444.      * @param int|null $rank
  1445.      * @param string|null $group
  1446.      * @return string
  1447.      * @throws Exception
  1448.      */
  1449.     public function embedMeta($name$value null$rank null$group null)
  1450.     {
  1451.         // check title
  1452.         if ($name === null) {
  1453.             throw new Exception();
  1454.         }
  1455.         // obtain the document tool from current
  1456.         $documentSettings $this->sceneRenderer->currentScene()->getAssetsOrganizer();
  1457.         // create the title
  1458.         $structure = new MetaStructure($name$value$rank);
  1459.         // register
  1460.         $documentSettings->getMetas()->add($structure$group);
  1461.         /*
  1462.         return sprintf(
  1463.             '<!-- META | %s | %s | %s | %s -->',
  1464.             ($group !== null) ? $group : StyleStructureBag::DEFAULTS__GROUP,
  1465.             ($rank !== null) ? $rank : StyleStructureBag::DEFAULTS__PRIORITY,
  1466.             $name,
  1467.             ($value !== null) ? $value : ''
  1468.         );
  1469.         */
  1470.         return '';
  1471.     }
  1472.     /**
  1473.      * @param string $title
  1474.      * @param int $rank
  1475.      * @return string
  1476.      * @throws Exception
  1477.      */
  1478.     public function embedTitle($title$rank null)
  1479.     {
  1480.         // check title
  1481.         if ($title === null) {
  1482.             throw new Exception();
  1483.         }
  1484.         $title $this->translator->trans($title);
  1485.         // obtain the document tool from current
  1486.         $documentSettings $this->sceneRenderer->currentScene()->getAssetsOrganizer();
  1487.         // create the title
  1488.         $structure = new TitleStructure($title$rank);
  1489.         // register
  1490.         $documentSettings->getTitles()->add($structure);
  1491.         /*
  1492.         return sprintf(
  1493.             '<!-- TITLE | %s | %s -->',
  1494.             ($rank !== null) ? $rank : StyleStructureBag::DEFAULTS__PRIORITY,
  1495.             $title
  1496.         );
  1497.         */
  1498.         return '';
  1499.     }
  1500.     /**
  1501.      * @param string $action
  1502.      * @param mixed $arg1
  1503.      * @param mixed $arg2
  1504.      * @param mixed $arg3
  1505.      * @return string
  1506.      * @throws Exception
  1507.      */
  1508.     public function htmlAttrs($action$arg1 null$arg2 null$arg3 null)
  1509.     {
  1510.         // obtain the document tool from current
  1511.         $documentSettings $this->sceneRenderer->currentScene()->getAssetsOrganizer();
  1512.         // call helper
  1513.         return $this->attrs($documentSettings->getHtmlTag(), $action$arg1$arg2$arg3);
  1514.     }
  1515.     /**
  1516.      * @param string $action
  1517.      * @param mixed $arg1
  1518.      * @param mixed $arg2
  1519.      * @param mixed $arg3
  1520.      * @return string
  1521.      * @throws Exception
  1522.      */
  1523.     public function bodyAttrs($action$arg1 null$arg2 null$arg3 null)
  1524.     {
  1525.         // obtain the document tool from current
  1526.         $documentSettings $this->sceneRenderer->currentScene()->getAssetsOrganizer();
  1527.         // call helper
  1528.         return $this->attrs($documentSettings->getBodyTag(), $action$arg1$arg2$arg3);
  1529.     }
  1530.     /**
  1531.      * @param ElementSettings $settings
  1532.      * @param string $action
  1533.      * @param mixed $arg1
  1534.      * @param mixed $arg2
  1535.      * @param mixed $arg3
  1536.      * @return string
  1537.      * @throws Exception
  1538.      */
  1539.     private function attrs(ElementSettings $settings$action$arg1 null$arg2 null$arg3 null)
  1540.     {
  1541.         // action is required
  1542.         if ($action === null) {
  1543.             throw new Exception();
  1544.         }
  1545.         // action must be a function on the class
  1546.         if (method_exists($settings$action) === false) {
  1547.             throw new Exception();
  1548.         }
  1549.         // just do the call
  1550.         $settings->$action($arg1$arg2$arg3);
  1551.         /*
  1552.         return sprintf(
  1553.             '<!-- ATTR | %s | %s | %s | %s | %s -->',
  1554.             get_class($settings),
  1555.             $action,
  1556.             ($arg1 !== null) ? $arg1 : '',
  1557.             ($arg2 !== null) ? $arg2 : '',
  1558.             ($arg3 !== null) ? $arg3 : ''
  1559.         );
  1560.         */
  1561.         return '';
  1562.     }
  1563.     /**
  1564.      * @param Environment $environment
  1565.      * @param array $context
  1566.      * @param null $route
  1567.      * @param array $merges
  1568.      * @param array $removals
  1569.      * @return mixed
  1570.      */
  1571.     public function curpathFunction(
  1572.         Environment $environment,
  1573.         array $context,
  1574.         $route null,
  1575.         array $merges = [],
  1576.         array $removals = []
  1577.     ) {
  1578.         // get env globals
  1579.         /** @var AppVariable $globals */
  1580.         $globals $context['app'];
  1581.         // get current url params
  1582.         $params $globals->getRequest()->get('_route_params');
  1583.         // merge in what we need
  1584.         $params array_merge($params$merges);
  1585.         // remove what we don't
  1586.         foreach ($removals as $removal) {
  1587.             unset($params[$removal]);
  1588.         }
  1589.         // handle nulls
  1590.         foreach (array_keys($params) as $key) {
  1591.             if ($params[$key] === null) {
  1592.                 unset($params[$key]);
  1593.             }
  1594.         }
  1595.         if (empty($params['return'])) {
  1596.             // enforce route
  1597.             if ($route === null) {
  1598.                 $route $globals->getRequest()->get('_route');
  1599.             }
  1600.             // call the path function
  1601.             $call $environment->getFunction('path')->getCallable();
  1602.             $result $call($route$params);
  1603.         } else {
  1604.             $result $params['return'];
  1605.         }
  1606.         // return the result
  1607.         return $result;
  1608.     }
  1609.     /**
  1610.      * @param Environment $environment
  1611.      * @param array $context
  1612.      * @param null $route
  1613.      * @param array $merges
  1614.      * @param array $removals
  1615.      * @return mixed
  1616.      */
  1617.     public function redirpathFunction(
  1618.         Environment $environment,
  1619.         array $context,
  1620.         $route null,
  1621.         array $merges = [],
  1622.         array $removals = []
  1623.     ) {
  1624.         // get env globals
  1625.         /** @var AppVariable $globals */
  1626.         $globals $context['app'];
  1627.         // see if we have a redirectTo field
  1628.         $request $globals->getRequest();
  1629.         if ($request->query->has('redirectTo')) {
  1630.             return $request->query->get('redirectTo');
  1631.         }
  1632.         // get current url params
  1633.         $params $globals->getRequest()->get('_route_params');
  1634.         // merge in what we need
  1635.         $params array_merge($params$merges);
  1636.         // remove what we don't
  1637.         foreach ($removals as $removal) {
  1638.             unset($params[$removal]);
  1639.         }
  1640.         // handle nulls
  1641.         foreach (array_keys($params) as $key) {
  1642.             if ($params[$key] === null) {
  1643.                 unset($params[$key]);
  1644.             }
  1645.         }
  1646.         if (empty($params['return'])) {
  1647.             // enforce route
  1648.             if ($route === null) {
  1649.                 $route $globals->getRequest()->get('_route');
  1650.             }
  1651.             // call the path function
  1652.             $call $environment->getFunction('path')->getCallable();
  1653.             $result $call($route$params);
  1654.         } else {
  1655.             $result $params['return'];
  1656.         }
  1657.         // return the result
  1658.         return $result;
  1659.     }
  1660.     /**
  1661.      * @param int $value
  1662.      * @param string $unit Supports null, 'B', 'KB', 'MB', 'GB', 'TB', 'PB',
  1663.      *                  'EB', 'ZB', 'YB'. Default null.
  1664.      * @param int $precision
  1665.      * @param string $separate
  1666.      * @return string
  1667.      * @throws Exception
  1668.      */
  1669.     public function bytesFilter($value$unit 'B'$precision 2$separate ' ')
  1670.     {
  1671.         static $suffixes = array('B''KB''MB''GB''TB''PB''EB''ZB''YB');
  1672.         if (empty($value)) {
  1673.             $value 0;
  1674.         }
  1675.         if ($value === 0) {
  1676.             $base array_search($unit$suffixes);
  1677.             if ($base === false) {
  1678.                 $base 0;
  1679.             }
  1680.             return '0' $separate $suffixes[$base];
  1681.         }
  1682.         $value abs($value);
  1683.         $place intval(floor(log($value1024)));
  1684.         $num round($value pow(1024$place), $precision);
  1685.         return (min(1max(-1$value)) * $num) . $separate $suffixes[$place];
  1686.     }
  1687.     /**
  1688.      * @param string|null $abstract
  1689.      * @param string $content
  1690.      * @param int $length
  1691.      * @return string
  1692.      */
  1693.     public function cms_abstract($abstract$content$length 90)
  1694.     {
  1695.         if (empty($abstract)) {
  1696.             $abstract '';
  1697.         }
  1698.         $abstract trim($abstract);
  1699.         return trim(html_entity_decode(
  1700.             ( ! empty($abstract))
  1701.                 ? $abstract
  1702.                 mb_strimwidth(strip_tags($content), 0$length'...')
  1703.             ,
  1704.             ENT_QUOTES
  1705.         ));
  1706.     }
  1707.     /**
  1708.      * {@inheritdoc}
  1709.      */
  1710.     public function getGlobals(): array
  1711.     {
  1712.         return array(
  1713.             'cms' => $this->globalVariables,
  1714.         );
  1715.     }
  1716. }