vendor/contao/core-bundle/src/Resources/contao/library/Contao/Template.php line 316

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use MatthiasMullie\Minify;
  11. use Symfony\Component\HttpFoundation\Response;
  12. use Symfony\Component\VarDumper\VarDumper;
  13. /**
  14.  * Parses and outputs template files
  15.  *
  16.  * The class supports loading template files, adding variables to them and then
  17.  * printing them to the screen. It functions as abstract parent class for the
  18.  * two core classes "BackendTemplate" and "FrontendTemplate".
  19.  *
  20.  * Usage:
  21.  *
  22.  *     $template = new BackendTemplate();
  23.  *     $template->name = 'Leo Feyer';
  24.  *     $template->output();
  25.  *
  26.  * @property string       $style
  27.  * @property array|string $cssID
  28.  * @property string       $class
  29.  * @property string       $inColumn
  30.  * @property string       $headline
  31.  * @property array        $hl
  32.  * @property string       $action
  33.  * @property string       $enforceTwoFactor
  34.  * @property string       $targetPath
  35.  * @property string       $message
  36.  * @property string       $href
  37.  * @property string       $twoFactor
  38.  * @property string       $explain
  39.  * @property string       $active
  40.  * @property string       $enableButton
  41.  * @property string       $disableButton
  42.  * @property boolean      $enable
  43.  * @property boolean      $isEnabled
  44.  * @property string       $secret
  45.  * @property string       $textCode
  46.  * @property string       $qrCode
  47.  * @property string       $scan
  48.  * @property string       $verify
  49.  * @property string       $verifyHelp
  50.  * @property boolean      $showBackupCodes
  51.  * @property array        $backupCodes
  52.  * @property boolean      $trustedDevicesEnabled
  53.  * @property array        $trustedDevices
  54.  * @property string       $currentDevice
  55.  *
  56.  * @author Leo Feyer <https://github.com/leofeyer>
  57.  */
  58. abstract class Template extends Controller
  59. {
  60.     use TemplateInheritance;
  61.     /**
  62.      * Output buffer
  63.      * @var string
  64.      */
  65.     protected $strBuffer;
  66.     /**
  67.      * Content type
  68.      * @var string
  69.      */
  70.     protected $strContentType;
  71.     /**
  72.      * Template data
  73.      * @var array
  74.      */
  75.     protected $arrData = array();
  76.     /**
  77.      * Valid JavaScipt types
  78.      * @var array
  79.      * @see http://www.w3.org/TR/html5/scripting-1.html#scriptingLanguages
  80.      */
  81.     protected static $validJavaScriptTypes = array
  82.     (
  83.         'application/ecmascript',
  84.         'application/javascript',
  85.         'application/x-ecmascript',
  86.         'application/x-javascript',
  87.         'text/ecmascript',
  88.         'text/javascript',
  89.         'text/javascript1.0',
  90.         'text/javascript1.1',
  91.         'text/javascript1.2',
  92.         'text/javascript1.3',
  93.         'text/javascript1.4',
  94.         'text/javascript1.5',
  95.         'text/jscript',
  96.         'text/livescript',
  97.         'text/x-ecmascript',
  98.         'text/x-javascript',
  99.     );
  100.     /**
  101.      * Create a new template object
  102.      *
  103.      * @param string $strTemplate    The template name
  104.      * @param string $strContentType The content type (defaults to "text/html")
  105.      */
  106.     public function __construct($strTemplate=''$strContentType='text/html')
  107.     {
  108.         parent::__construct();
  109.         $this->strTemplate $strTemplate;
  110.         $this->strContentType $strContentType;
  111.     }
  112.     /**
  113.      * Set an object property
  114.      *
  115.      * @param string $strKey   The property name
  116.      * @param mixed  $varValue The property value
  117.      */
  118.     public function __set($strKey$varValue)
  119.     {
  120.         $this->arrData[$strKey] = $varValue;
  121.     }
  122.     /**
  123.      * Return an object property
  124.      *
  125.      * @param string $strKey The property name
  126.      *
  127.      * @return mixed The property value
  128.      */
  129.     public function __get($strKey)
  130.     {
  131.         if (isset($this->arrData[$strKey]))
  132.         {
  133.             if (\is_object($this->arrData[$strKey]) && \is_callable($this->arrData[$strKey]))
  134.             {
  135.                 return $this->arrData[$strKey]();
  136.             }
  137.             return $this->arrData[$strKey];
  138.         }
  139.         return parent::__get($strKey);
  140.     }
  141.     /**
  142.      * Execute a callable and return the result
  143.      *
  144.      * @param string $strKey    The name of the key
  145.      * @param array  $arrParams The parameters array
  146.      *
  147.      * @return mixed The callable return value
  148.      *
  149.      * @throws \InvalidArgumentException If the callable does not exist
  150.      */
  151.     public function __call($strKey$arrParams)
  152.     {
  153.         if (!isset($this->arrData[$strKey]) || !\is_callable($this->arrData[$strKey]))
  154.         {
  155.             throw new \InvalidArgumentException("$strKey is not set or not a callable");
  156.         }
  157.         return \call_user_func_array($this->arrData[$strKey], $arrParams);
  158.     }
  159.     /**
  160.      * Check whether a property is set
  161.      *
  162.      * @param string $strKey The property name
  163.      *
  164.      * @return boolean True if the property is set
  165.      */
  166.     public function __isset($strKey)
  167.     {
  168.         return isset($this->arrData[$strKey]);
  169.     }
  170.     /**
  171.      * Set the template data from an array
  172.      *
  173.      * @param array $arrData The data array
  174.      */
  175.     public function setData($arrData)
  176.     {
  177.         $this->arrData $arrData;
  178.     }
  179.     /**
  180.      * Return the template data as array
  181.      *
  182.      * @return array The data array
  183.      */
  184.     public function getData()
  185.     {
  186.         return $this->arrData;
  187.     }
  188.     /**
  189.      * Set the template name
  190.      *
  191.      * @param string $strTemplate The template name
  192.      */
  193.     public function setName($strTemplate)
  194.     {
  195.         $this->strTemplate $strTemplate;
  196.     }
  197.     /**
  198.      * Return the template name
  199.      *
  200.      * @return string The template name
  201.      */
  202.     public function getName()
  203.     {
  204.         return $this->strTemplate;
  205.     }
  206.     /**
  207.      * Set the output format
  208.      *
  209.      * @param string $strFormat The output format
  210.      */
  211.     public function setFormat($strFormat)
  212.     {
  213.         $this->strFormat $strFormat;
  214.     }
  215.     /**
  216.      * Return the output format
  217.      *
  218.      * @return string The output format
  219.      */
  220.     public function getFormat()
  221.     {
  222.         return $this->strFormat;
  223.     }
  224.     /**
  225.      * Print all template variables to the screen using print_r
  226.      *
  227.      * @deprecated Deprecated since Contao 4.3, to be removed in Contao 5.
  228.      *             Use Template::dumpTemplateVars() instead.
  229.      */
  230.     public function showTemplateVars()
  231.     {
  232.         @trigger_error('Using Template::showTemplateVars() has been deprecated and will no longer work in Contao 5.0. Use Template::dumpTemplateVars() instead.'E_USER_DEPRECATED);
  233.         $this->dumpTemplateVars();
  234.     }
  235.     /**
  236.      * Print all template variables to the screen using the Symfony VarDumper component
  237.      */
  238.     public function dumpTemplateVars()
  239.     {
  240.         VarDumper::dump($this->arrData);
  241.     }
  242.     /**
  243.      * Parse the template file and return it as string
  244.      *
  245.      * @return string The template markup
  246.      */
  247.     public function parse()
  248.     {
  249.         if (!$this->strTemplate)
  250.         {
  251.             return '';
  252.         }
  253.         // HOOK: add custom parse filters
  254.         if (isset($GLOBALS['TL_HOOKS']['parseTemplate']) && \is_array($GLOBALS['TL_HOOKS']['parseTemplate']))
  255.         {
  256.             foreach ($GLOBALS['TL_HOOKS']['parseTemplate'] as $callback)
  257.             {
  258.                 $this->import($callback[0]);
  259.                 $this->{$callback[0]}->{$callback[1]}($this);
  260.             }
  261.         }
  262.         return $this->inherit();
  263.     }
  264.     /**
  265.      * Parse the template file and print it to the screen
  266.      *
  267.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  268.      *             Use Template::getResponse() instead.
  269.      */
  270.     public function output()
  271.     {
  272.         @trigger_error('Using Template::output() has been deprecated and will no longer work in Contao 5.0. Use Template::getResponse() instead.'E_USER_DEPRECATED);
  273.         $this->compile();
  274.         header('Content-Type: ' $this->strContentType '; charset=' Config::get('characterSet'));
  275.         echo $this->strBuffer;
  276.     }
  277.     /**
  278.      * Return a response object
  279.      *
  280.      * @return Response The response object
  281.      */
  282.     public function getResponse()
  283.     {
  284.         $this->compile();
  285.         $response = new Response($this->strBuffer);
  286.         $response->headers->set('Content-Type'$this->strContentType '; charset=' Config::get('characterSet'));
  287.         return $response;
  288.     }
  289.     /**
  290.      * Return a route relative to the base URL
  291.      *
  292.      * @param string $strName   The route name
  293.      * @param array  $arrParams The route parameters
  294.      *
  295.      * @return string The route
  296.      */
  297.     public function route($strName$arrParams=array())
  298.     {
  299.         $strUrl System::getContainer()->get('router')->generate($strName$arrParams);
  300.         $strUrl substr($strUrl, \strlen(Environment::get('path')) + 1);
  301.         return ampersand($strUrl);
  302.     }
  303.     /**
  304.      * Return the preview route
  305.      *
  306.      * @param string $strName   The route name
  307.      * @param array  $arrParams The route parameters
  308.      *
  309.      * @return string The route
  310.      */
  311.     public function previewRoute($strName$arrParams=array())
  312.     {
  313.         $container System::getContainer();
  314.         if (!$previewScript $container->getParameter('contao.preview_script'))
  315.         {
  316.             return $this->route($strName$arrParams);
  317.         }
  318.         $router $container->get('router');
  319.         $context $router->getContext();
  320.         $context->setBaseUrl($previewScript);
  321.         $strUrl $router->generate($strName$arrParams);
  322.         $strUrl substr($strUrl, \strlen(Environment::get('path')) + 1);
  323.         $context->setBaseUrl('');
  324.         return ampersand($strUrl);
  325.     }
  326.     /**
  327.      * Returns a translated message
  328.      *
  329.      * @param string $strId
  330.      * @param array  $arrParams
  331.      * @param string $strDomain
  332.      *
  333.      * @return string
  334.      */
  335.     public function trans($strId, array $arrParams=array(), $strDomain='contao_default')
  336.     {
  337.         return System::getContainer()->get('translator')->trans($strId$arrParams$strDomain);
  338.     }
  339.     /**
  340.      * Returns an asset path
  341.      *
  342.      * @param string      $path
  343.      * @param string|null $packageName
  344.      *
  345.      * @return string
  346.      */
  347.     public function asset($path$packageName null)
  348.     {
  349.         $url System::getContainer()->get('assets.packages')->getUrl($path$packageName);
  350.         $basePath '/';
  351.         $request System::getContainer()->get('request_stack')->getMasterRequest();
  352.         if ($request !== null)
  353.         {
  354.             $basePath $request->getBasePath() . '/';
  355.         }
  356.         if (=== strncmp($url$basePath, \strlen($basePath)))
  357.         {
  358.             return substr($url, \strlen($basePath));
  359.         }
  360.         // Contao paths are relative to the <base> tag, so remove leading slashes
  361.         return $url;
  362.     }
  363.     /**
  364.      * Returns a container parameter
  365.      *
  366.      * @param string $strKey
  367.      *
  368.      * @return mixed
  369.      */
  370.     public function param($strKey)
  371.     {
  372.         return System::getContainer()->getParameter($strKey);
  373.     }
  374.     /**
  375.      * Compile the template
  376.      *
  377.      * @internal Do not call this method in your code. It will be made private in Contao 5.0.
  378.      */
  379.     protected function compile()
  380.     {
  381.         if (!$this->strBuffer)
  382.         {
  383.             $this->strBuffer $this->parse();
  384.         }
  385.     }
  386.     /**
  387.      * Return the debug bar string
  388.      *
  389.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  390.      */
  391.     protected function getDebugBar()
  392.     {
  393.         @trigger_error('Using Template::getDebugBar() has been deprecated and will no longer work in Contao 5.0.'E_USER_DEPRECATED);
  394.     }
  395.     /**
  396.      * Minify the HTML markup preserving pre, script, style and textarea tags
  397.      *
  398.      * @param string $strHtml The HTML markup
  399.      *
  400.      * @return string The minified HTML markup
  401.      */
  402.     public function minifyHtml($strHtml)
  403.     {
  404.         if (Config::get('debugMode'))
  405.         {
  406.             return $strHtml;
  407.         }
  408.         // Split the markup based on the tags that shall be preserved
  409.         $arrChunks preg_split('@(</?pre[^>]*>)|(</?script[^>]*>)|(</?style[^>]*>)|( ?</?textarea[^>]*>)@i'$strHtml, -1PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  410.         $strHtml '';
  411.         $blnPreserveNext false;
  412.         $blnOptimizeNext false;
  413.         $strType null;
  414.         // Check for valid JavaScript types (see #7927)
  415.         $isJavaScript = static function ($strChunk)
  416.         {
  417.             $typeMatch = array();
  418.             if (preg_match('/\stype\s*=\s*(?:(?J)(["\'])\s*(?<type>.*?)\s*\1|(?<type>[^\s>]+))/i'$strChunk$typeMatch) && !\in_array(strtolower($typeMatch['type']), static::$validJavaScriptTypes))
  419.             {
  420.                 return false;
  421.             }
  422.             if (preg_match('/\slanguage\s*=\s*(?:(?J)(["\'])\s*(?<type>.*?)\s*\1|(?<type>[^\s>]+))/i'$strChunk$typeMatch) && !\in_array('text/' strtolower($typeMatch['type']), static::$validJavaScriptTypes))
  423.             {
  424.                 return false;
  425.             }
  426.             return true;
  427.         };
  428.         // Recombine the markup
  429.         foreach ($arrChunks as $strChunk)
  430.         {
  431.             if (strncasecmp($strChunk'<pre'4) === || strncasecmp(ltrim($strChunk), '<textarea'9) === 0)
  432.             {
  433.                 $blnPreserveNext true;
  434.             }
  435.             elseif (strncasecmp($strChunk'<script'7) === 0)
  436.             {
  437.                 if ($isJavaScript($strChunk))
  438.                 {
  439.                     $blnOptimizeNext true;
  440.                     $strType 'js';
  441.                 }
  442.                 else
  443.                 {
  444.                     $blnPreserveNext true;
  445.                 }
  446.             }
  447.             elseif (strncasecmp($strChunk'<style'6) === 0)
  448.             {
  449.                 $blnOptimizeNext true;
  450.                 $strType 'css';
  451.             }
  452.             elseif ($blnPreserveNext)
  453.             {
  454.                 $blnPreserveNext false;
  455.             }
  456.             elseif ($blnOptimizeNext)
  457.             {
  458.                 $blnOptimizeNext false;
  459.                 // Minify inline scripts
  460.                 if ($strType == 'js')
  461.                 {
  462.                     $objMinify = new Minify\JS();
  463.                     $objMinify->add($strChunk);
  464.                     $strChunk $objMinify->minify();
  465.                 }
  466.                 elseif ($strType == 'css')
  467.                 {
  468.                     $objMinify = new Minify\CSS();
  469.                     $objMinify->add($strChunk);
  470.                     $strChunk $objMinify->minify();
  471.                 }
  472.             }
  473.             else
  474.             {
  475.                 // Remove line indentations and trailing spaces
  476.                 $strChunk str_replace("\r"''$strChunk);
  477.                 $strChunk preg_replace(array('/^[\t ]+/m''/[\t ]+$/m''/\n\n+/'), array(''''"\n"), $strChunk);
  478.             }
  479.             $strHtml .= $strChunk;
  480.         }
  481.         return trim($strHtml);
  482.     }
  483.     /**
  484.      * Generate the markup for a style sheet tag
  485.      *
  486.      * @param string $href  The script path
  487.      * @param string $media The media type string
  488.      * @param mixed  $mtime The file mtime
  489.      *
  490.      * @return string The markup string
  491.      */
  492.     public static function generateStyleTag($href$media=null$mtime=false)
  493.     {
  494.         // Add the filemtime if not given and not an external file
  495.         if ($mtime === null && !preg_match('@^https?://@'$href))
  496.         {
  497.             $container System::getContainer();
  498.             $projectDir $container->getParameter('kernel.project_dir');
  499.             if (file_exists($projectDir '/' $href))
  500.             {
  501.                 $mtime filemtime($projectDir '/' $href);
  502.             }
  503.             else
  504.             {
  505.                 $webDir StringUtil::stripRootDir($container->getParameter('contao.web_dir'));
  506.                 // Handle public bundle resources in web/
  507.                 if (file_exists($projectDir '/' $webDir '/' $href))
  508.                 {
  509.                     $mtime filemtime($projectDir '/' $webDir '/' $href);
  510.                 }
  511.             }
  512.         }
  513.         if ($mtime)
  514.         {
  515.             $href .= '?v=' substr(md5($mtime), 08);
  516.         }
  517.         return '<link rel="stylesheet" href="' $href '"' . (($media && $media != 'all') ? ' media="' $media '"' '') . '>';
  518.     }
  519.     /**
  520.      * Generate the markup for inline CSS code
  521.      *
  522.      * @param string $script The CSS code
  523.      *
  524.      * @return string The markup string
  525.      */
  526.     public static function generateInlineStyle($script)
  527.     {
  528.         return '<style>' $script '</style>';
  529.     }
  530.     /**
  531.      * Generate the markup for a JavaScript tag
  532.      *
  533.      * @param string      $src            The script path
  534.      * @param boolean     $async          True to add the async attribute
  535.      * @param mixed       $mtime          The file mtime
  536.      * @param string|null $hash           An optional integrity hash
  537.      * @param string|null $crossorigin    An optional crossorigin attribute
  538.      * @param string|null $referrerpolicy An optional referrerpolicy attribute
  539.      *
  540.      * @return string The markup string
  541.      */
  542.     public static function generateScriptTag($src$async=false$mtime=false$hash=null$crossorigin=null$referrerpolicy=null)
  543.     {
  544.         // Add the filemtime if not given and not an external file
  545.         if ($mtime === null && !preg_match('@^https?://@'$src))
  546.         {
  547.             $container System::getContainer();
  548.             $projectDir $container->getParameter('kernel.project_dir');
  549.             if (file_exists($projectDir '/' $src))
  550.             {
  551.                 $mtime filemtime($projectDir '/' $src);
  552.             }
  553.             else
  554.             {
  555.                 $webDir StringUtil::stripRootDir($container->getParameter('contao.web_dir'));
  556.                 // Handle public bundle resources in web/
  557.                 if (file_exists($projectDir '/' $webDir '/' $src))
  558.                 {
  559.                     $mtime filemtime($projectDir '/' $webDir '/' $src);
  560.                 }
  561.             }
  562.         }
  563.         if ($mtime)
  564.         {
  565.             $src .= '?v=' substr(md5($mtime), 08);
  566.         }
  567.         return '<script src="' $src '"' . ($async ' async' '') . ($hash ' integrity="' $hash '"' '') . ($crossorigin ' crossorigin="' $crossorigin '"' '') . ($referrerpolicy ' referrerpolicy="' $referrerpolicy '"' '') . '></script>';
  568.     }
  569.     /**
  570.      * Generate the markup for an inline JavaScript
  571.      *
  572.      * @param string $script The JavaScript code
  573.      *
  574.      * @return string The markup string
  575.      */
  576.     public static function generateInlineScript($script)
  577.     {
  578.         return '<script>' $script '</script>';
  579.     }
  580.     /**
  581.      * Generate the markup for an RSS feed tag
  582.      *
  583.      * @param string $href   The script path
  584.      * @param string $format The feed format
  585.      * @param string $title  The feed title
  586.      *
  587.      * @return string The markup string
  588.      */
  589.     public static function generateFeedTag($href$format$title)
  590.     {
  591.         return '<link type="application/' $format '+xml" rel="alternate" href="' $href '" title="' StringUtil::specialchars($title) . '">';
  592.     }
  593.     /**
  594.      * Flush the output buffers
  595.      *
  596.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  597.      */
  598.     public function flushAllData()
  599.     {
  600.         @trigger_error('Using Template::flushAllData() has been deprecated and will no longer work in Contao 5.0.'E_USER_DEPRECATED);
  601.         if (\function_exists('fastcgi_finish_request'))
  602.         {
  603.             fastcgi_finish_request();
  604.         }
  605.         elseif (\PHP_SAPI !== 'cli')
  606.         {
  607.             $status ob_get_status(true);
  608.             $level = \count($status);
  609.             while ($level-- > && (!empty($status[$level]['del']) || (isset($status[$level]['flags']) && ($status[$level]['flags'] & PHP_OUTPUT_HANDLER_REMOVABLE) && ($status[$level]['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE))))
  610.             {
  611.                 ob_end_flush();
  612.             }
  613.             flush();
  614.         }
  615.     }
  616. }
  617. class_alias(Template::class, 'Template');