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

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 Contao\CoreBundle\Routing\ResponseContext\JsonLd\JsonLdManager;
  11. use Contao\Image\ImageInterface;
  12. use Contao\Image\PictureConfiguration;
  13. use MatthiasMullie\Minify\CSS;
  14. use MatthiasMullie\Minify\JS;
  15. use Spatie\SchemaOrg\Graph;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\VarDumper\VarDumper;
  18. /**
  19.  * Parses and outputs template files
  20.  *
  21.  * The class supports loading template files, adding variables to them and then
  22.  * printing them to the screen. It functions as abstract parent class for the
  23.  * two core classes "BackendTemplate" and "FrontendTemplate".
  24.  *
  25.  * Usage:
  26.  *
  27.  *     $template = new BackendTemplate();
  28.  *     $template->name = 'Leo Feyer';
  29.  *     $template->output();
  30.  *
  31.  * @property string       $style
  32.  * @property array|string $cssID
  33.  * @property string       $class
  34.  * @property string       $inColumn
  35.  * @property string       $headline
  36.  * @property array        $hl
  37.  * @property string       $content
  38.  * @property string       $action
  39.  * @property string       $enforceTwoFactor
  40.  * @property string       $targetPath
  41.  * @property string       $message
  42.  * @property string       $href
  43.  * @property string       $twoFactor
  44.  * @property string       $explain
  45.  * @property string       $active
  46.  * @property string       $enableButton
  47.  * @property string       $disableButton
  48.  * @property boolean      $enable
  49.  * @property boolean      $isEnabled
  50.  * @property string       $secret
  51.  * @property string       $textCode
  52.  * @property string       $qrCode
  53.  * @property string       $scan
  54.  * @property string       $verify
  55.  * @property string       $verifyHelp
  56.  * @property boolean      $showBackupCodes
  57.  * @property array        $backupCodes
  58.  * @property boolean      $trustedDevicesEnabled
  59.  * @property array        $trustedDevices
  60.  * @property string       $currentDevice
  61.  */
  62. abstract class Template extends Controller
  63. {
  64.     use TemplateInheritance;
  65.     /**
  66.      * Output buffer
  67.      * @var string
  68.      */
  69.     protected $strBuffer;
  70.     /**
  71.      * Content type
  72.      * @var string
  73.      */
  74.     protected $strContentType;
  75.     /**
  76.      * Template data
  77.      * @var array
  78.      */
  79.     protected $arrData = array();
  80.     /**
  81.      * Valid JavaScipt types
  82.      * @var array
  83.      * @see http://www.w3.org/TR/html5/scripting-1.html#scriptingLanguages
  84.      */
  85.     protected static $validJavaScriptTypes = array
  86.     (
  87.         'application/ecmascript',
  88.         'application/javascript',
  89.         'application/x-ecmascript',
  90.         'application/x-javascript',
  91.         'text/ecmascript',
  92.         'text/javascript',
  93.         'text/javascript1.0',
  94.         'text/javascript1.1',
  95.         'text/javascript1.2',
  96.         'text/javascript1.3',
  97.         'text/javascript1.4',
  98.         'text/javascript1.5',
  99.         'text/jscript',
  100.         'text/livescript',
  101.         'text/x-ecmascript',
  102.         'text/x-javascript',
  103.     );
  104.     /**
  105.      * Create a new template object
  106.      *
  107.      * @param string $strTemplate    The template name
  108.      * @param string $strContentType The content type (defaults to "text/html")
  109.      */
  110.     public function __construct($strTemplate=''$strContentType='text/html')
  111.     {
  112.         parent::__construct();
  113.         $this->strTemplate $strTemplate;
  114.         $this->strContentType $strContentType;
  115.     }
  116.     /**
  117.      * Set an object property
  118.      *
  119.      * @param string $strKey   The property name
  120.      * @param mixed  $varValue The property value
  121.      */
  122.     public function __set($strKey$varValue)
  123.     {
  124.         $this->arrData[$strKey] = $varValue;
  125.     }
  126.     /**
  127.      * Return an object property
  128.      *
  129.      * @param string $strKey The property name
  130.      *
  131.      * @return mixed The property value
  132.      */
  133.     public function __get($strKey)
  134.     {
  135.         if (isset($this->arrData[$strKey]))
  136.         {
  137.             if (\is_object($this->arrData[$strKey]) && \is_callable($this->arrData[$strKey]))
  138.             {
  139.                 return $this->arrData[$strKey]();
  140.             }
  141.             return $this->arrData[$strKey];
  142.         }
  143.         return parent::__get($strKey);
  144.     }
  145.     /**
  146.      * Execute a callable and return the result
  147.      *
  148.      * @param string $strKey    The name of the key
  149.      * @param array  $arrParams The parameters array
  150.      *
  151.      * @return mixed The callable return value
  152.      *
  153.      * @throws \InvalidArgumentException If the callable does not exist
  154.      */
  155.     public function __call($strKey$arrParams)
  156.     {
  157.         if (!isset($this->arrData[$strKey]) || !\is_callable($this->arrData[$strKey]))
  158.         {
  159.             throw new \InvalidArgumentException("$strKey is not set or not a callable");
  160.         }
  161.         return ($this->arrData[$strKey])(...$arrParams);
  162.     }
  163.     /**
  164.      * Check whether a property is set
  165.      *
  166.      * @param string $strKey The property name
  167.      *
  168.      * @return boolean True if the property is set
  169.      */
  170.     public function __isset($strKey)
  171.     {
  172.         return isset($this->arrData[$strKey]);
  173.     }
  174.     /**
  175.      * Adds a function to a template which is evaluated only once. This is helpful for
  176.      * lazy-evaluating data where we can use functions without arguments. Let's say
  177.      * you wanted to lazy-evaluate a variable like this:
  178.      *
  179.      *     $template->hasText = function () use ($article) {
  180.      *         return ContentModel::countPublishedByPidAndTable($article->id, 'tl_news') > 0;
  181.      *     };
  182.      *
  183.      * This would cause a query everytime $template->hasText is accessed in the
  184.      * template. You can improve this by turning it into this:
  185.      *
  186.      *     $template->hasText = Template::once(function () use ($article) {
  187.      *         return ContentModel::countPublishedByPidAndTable($article->id, 'tl_news') > 0;
  188.      *     });
  189.      */
  190.     public static function once(callable $callback)
  191.     {
  192.         return static function () use (&$callback)
  193.         {
  194.             if (\is_callable($callback))
  195.             {
  196.                 $callback $callback();
  197.             }
  198.             return $callback;
  199.         };
  200.     }
  201.     /**
  202.      * Set the template data from an array
  203.      *
  204.      * @param array $arrData The data array
  205.      */
  206.     public function setData($arrData)
  207.     {
  208.         $this->arrData $arrData;
  209.     }
  210.     /**
  211.      * Return the template data as array
  212.      *
  213.      * @return array The data array
  214.      */
  215.     public function getData()
  216.     {
  217.         return $this->arrData;
  218.     }
  219.     /**
  220.      * Set the template name
  221.      *
  222.      * @param string $strTemplate The template name
  223.      */
  224.     public function setName($strTemplate)
  225.     {
  226.         $this->strTemplate $strTemplate;
  227.     }
  228.     /**
  229.      * Return the template name
  230.      *
  231.      * @return string The template name
  232.      */
  233.     public function getName()
  234.     {
  235.         return $this->strTemplate;
  236.     }
  237.     /**
  238.      * Set the output format
  239.      *
  240.      * @param string $strFormat The output format
  241.      */
  242.     public function setFormat($strFormat)
  243.     {
  244.         $this->strFormat $strFormat;
  245.     }
  246.     /**
  247.      * Return the output format
  248.      *
  249.      * @return string The output format
  250.      */
  251.     public function getFormat()
  252.     {
  253.         return $this->strFormat;
  254.     }
  255.     /**
  256.      * Print all template variables to the screen using print_r
  257.      *
  258.      * @deprecated Deprecated since Contao 4.3, to be removed in Contao 5.
  259.      *             Use Template::dumpTemplateVars() instead.
  260.      */
  261.     public function showTemplateVars()
  262.     {
  263.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Template::showTemplateVars()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Template::dumpTemplateVars()" instead.');
  264.         $this->dumpTemplateVars();
  265.     }
  266.     /**
  267.      * Print all template variables to the screen using the Symfony VarDumper component
  268.      */
  269.     public function dumpTemplateVars()
  270.     {
  271.         VarDumper::dump($this->arrData);
  272.     }
  273.     /**
  274.      * Parse the template file and return it as string
  275.      *
  276.      * @return string The template markup
  277.      */
  278.     public function parse()
  279.     {
  280.         if (!$this->strTemplate)
  281.         {
  282.             return '';
  283.         }
  284.         // HOOK: add custom parse filters
  285.         if (isset($GLOBALS['TL_HOOKS']['parseTemplate']) && \is_array($GLOBALS['TL_HOOKS']['parseTemplate']))
  286.         {
  287.             foreach ($GLOBALS['TL_HOOKS']['parseTemplate'] as $callback)
  288.             {
  289.                 $this->import($callback[0]);
  290.                 $this->{$callback[0]}->{$callback[1]}($this);
  291.             }
  292.         }
  293.         return $this->inherit();
  294.     }
  295.     /**
  296.      * Parse the template file and print it to the screen
  297.      *
  298.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  299.      *             Use Template::getResponse() instead.
  300.      */
  301.     public function output()
  302.     {
  303.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Template::output()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Template::getResponse()" instead.');
  304.         $this->compile();
  305.         header('Content-Type: ' $this->strContentType '; charset=' System::getContainer()->getParameter('kernel.charset'));
  306.         echo $this->strBuffer;
  307.     }
  308.     /**
  309.      * Return a response object
  310.      *
  311.      * @return Response The response object
  312.      */
  313.     public function getResponse()
  314.     {
  315.         $this->compile();
  316.         $response = new Response($this->strBuffer);
  317.         $response->headers->set('Content-Type'$this->strContentType);
  318.         $response->setCharset(System::getContainer()->getParameter('kernel.charset'));
  319.         return $response;
  320.     }
  321.     /**
  322.      * Return a route relative to the base URL
  323.      *
  324.      * @param string $strName   The route name
  325.      * @param array  $arrParams The route parameters
  326.      *
  327.      * @return string The route
  328.      */
  329.     public function route($strName$arrParams=array())
  330.     {
  331.         $strUrl System::getContainer()->get('router')->generate($strName$arrParams);
  332.         $strUrl substr($strUrl\strlen(Environment::get('path')) + 1);
  333.         return StringUtil::ampersand($strUrl);
  334.     }
  335.     /**
  336.      * Return the preview route
  337.      *
  338.      * @param string $strName   The route name
  339.      * @param array  $arrParams The route parameters
  340.      *
  341.      * @return string The route
  342.      */
  343.     public function previewRoute($strName$arrParams=array())
  344.     {
  345.         $container System::getContainer();
  346.         if (!$previewScript $container->getParameter('contao.preview_script'))
  347.         {
  348.             return $this->route($strName$arrParams);
  349.         }
  350.         $router $container->get('router');
  351.         $context $router->getContext();
  352.         $context->setBaseUrl($previewScript);
  353.         $strUrl $router->generate($strName$arrParams);
  354.         $strUrl substr($strUrl\strlen(Environment::get('path')) + 1);
  355.         $context->setBaseUrl('');
  356.         return StringUtil::ampersand($strUrl);
  357.     }
  358.     /**
  359.      * Returns a translated message
  360.      *
  361.      * @param string $strId
  362.      * @param array  $arrParams
  363.      * @param string $strDomain
  364.      *
  365.      * @return string
  366.      */
  367.     public function trans($strId, array $arrParams=array(), $strDomain='contao_default')
  368.     {
  369.         return System::getContainer()->get('translator')->trans($strId$arrParams$strDomain);
  370.     }
  371.     /**
  372.      * Helper method to allow quick access in the Contao templates for safe raw (unencoded) output.
  373.      * It replaces (or optionally removes) Contao insert tags and removes all HTML.
  374.      *
  375.      * Be careful when using this. It must NOT be used within regular HTML when $value
  376.      * is uncontrolled user input. It's useful to ensure raw values within e.g. <code> examples
  377.      * or JSON-LD arrays.
  378.      */
  379.     public function rawPlainText(string $valuebool $removeInsertTags false): string
  380.     {
  381.         return System::getContainer()->get('contao.string.html_decoder')->inputEncodedToPlainText($value$removeInsertTags);
  382.     }
  383.     /**
  384.      * Helper method to allow quick access in the Contao templates for safe raw (unencoded) output.
  385.      *
  386.      * Compared to $this->rawPlainText() it adds new lines before and after block level HTML elements
  387.      * and only then removes the rest of the HTML tags.
  388.      *
  389.      * Be careful when using this. It must NOT be used within regular HTML when $value
  390.      * is uncontrolled user input. It's useful to ensure raw values within e.g. <code> examples
  391.      * or JSON-LD arrays.
  392.      */
  393.     public function rawHtmlToPlainText(string $valuebool $removeInsertTags false): string
  394.     {
  395.         return System::getContainer()->get('contao.string.html_decoder')->htmlToPlainText($value$removeInsertTags);
  396.     }
  397.     /**
  398.      * Adds schema.org JSON-LD data to the current response context
  399.      */
  400.     public function addSchemaOrg(array $jsonLd): void
  401.     {
  402.         $responseContext System::getContainer()->get('contao.routing.response_context_accessor')->getResponseContext();
  403.         if (!$responseContext || !$responseContext->has(JsonLdManager::class))
  404.         {
  405.             return;
  406.         }
  407.         /** @var JsonLdManager $jsonLdManager */
  408.         $jsonLdManager $responseContext->get(JsonLdManager::class);
  409.         $type $jsonLdManager->createSchemaOrgTypeFromArray($jsonLd);
  410.         $jsonLdManager
  411.             ->getGraphForSchema(JsonLdManager::SCHEMA_ORG)
  412.             ->set($type$jsonLd['identifier'] ?? Graph::IDENTIFIER_DEFAULT)
  413.         ;
  414.     }
  415.     /**
  416.      * Render a figure
  417.      *
  418.      * The provided configuration array is used to configure a FigureBuilder.
  419.      * If not explicitly set, the default template "image.html5" will be used
  420.      * to render the results. To use the core's default Twig template, pass
  421.      * "@ContaoCore/Image/Studio/figure.html.twig" as $template argument.
  422.      *
  423.      * @param int|string|FilesModel|ImageInterface       $from          Can be a FilesModel, an ImageInterface, a tl_files UUID/ID/path or a file system path
  424.      * @param int|string|array|PictureConfiguration|null $size          A picture size configuration or reference
  425.      * @param array<string, mixed>                       $configuration Configuration for the FigureBuilder
  426.      * @param string                                     $template      A Contao or Twig template
  427.      *
  428.      * @return string|null Returns null if the resource is invalid
  429.      */
  430.     public function figure($from$size$configuration = array(), $template 'image')
  431.     {
  432.         return System::getContainer()->get('contao.image.studio.figure_renderer')->render($from$size$configuration$template);
  433.     }
  434.     /**
  435.      * Returns an asset path
  436.      *
  437.      * @param string      $path
  438.      * @param string|null $packageName
  439.      *
  440.      * @return string
  441.      */
  442.     public function asset($path$packageName null)
  443.     {
  444.         $url System::getContainer()->get('assets.packages')->getUrl($path$packageName);
  445.         $basePath '/';
  446.         $request System::getContainer()->get('request_stack')->getMainRequest();
  447.         if ($request !== null)
  448.         {
  449.             $basePath $request->getBasePath() . '/';
  450.         }
  451.         if (=== strncmp($url$basePath\strlen($basePath)))
  452.         {
  453.             return substr($url\strlen($basePath));
  454.         }
  455.         // Contao paths are relative to the <base> tag, so remove leading slashes
  456.         return $url;
  457.     }
  458.     /**
  459.      * Returns an asset version
  460.      *
  461.      * @param string      $path
  462.      * @param string|null $packageName
  463.      *
  464.      * @return string
  465.      */
  466.     public function assetVersion($path$packageName null)
  467.     {
  468.         return System::getContainer()->get('assets.packages')->getVersion($path$packageName);
  469.     }
  470.     /**
  471.      * Returns a container parameter
  472.      *
  473.      * @param string $strKey
  474.      *
  475.      * @return mixed
  476.      */
  477.     public function param($strKey)
  478.     {
  479.         return System::getContainer()->getParameter($strKey);
  480.     }
  481.     /**
  482.      * Compile the template
  483.      *
  484.      * @internal Do not call this method in your code. It will be made private in Contao 5.0.
  485.      */
  486.     protected function compile()
  487.     {
  488.         if (!$this->strBuffer)
  489.         {
  490.             $this->strBuffer $this->parse();
  491.         }
  492.     }
  493.     /**
  494.      * Return the debug bar string
  495.      *
  496.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  497.      */
  498.     protected function getDebugBar()
  499.     {
  500.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Template::getDebugBar()" has been deprecated and will no longer work in Contao 5.0.');
  501.     }
  502.     /**
  503.      * Minify the HTML markup preserving pre, script, style and textarea tags
  504.      *
  505.      * @param string $strHtml The HTML markup
  506.      *
  507.      * @return string The minified HTML markup
  508.      */
  509.     public function minifyHtml($strHtml)
  510.     {
  511.         if (System::getContainer()->getParameter('kernel.debug'))
  512.         {
  513.             return $strHtml;
  514.         }
  515.         // Split the markup based on the tags that shall be preserved
  516.         $arrChunks preg_split('@(</?pre[^>]*>)|(</?script[^>]*>)|(</?style[^>]*>)|( ?</?textarea[^>]*>)@i'$strHtml, -1PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  517.         $strHtml '';
  518.         $blnPreserveNext false;
  519.         $blnOptimizeNext false;
  520.         $strType null;
  521.         // Check for valid JavaScript types (see #7927)
  522.         $isJavaScript = static function ($strChunk)
  523.         {
  524.             $typeMatch = array();
  525.             if (preg_match('/\stype\s*=\s*(?:(?J)(["\'])\s*(?<type>.*?)\s*\1|(?<type>[^\s>]+))/i'$strChunk$typeMatch) && !\in_array(strtolower($typeMatch['type']), static::$validJavaScriptTypes))
  526.             {
  527.                 return false;
  528.             }
  529.             if (preg_match('/\slanguage\s*=\s*(?:(?J)(["\'])\s*(?<type>.*?)\s*\1|(?<type>[^\s>]+))/i'$strChunk$typeMatch) && !\in_array('text/' strtolower($typeMatch['type']), static::$validJavaScriptTypes))
  530.             {
  531.                 return false;
  532.             }
  533.             return true;
  534.         };
  535.         // Recombine the markup
  536.         foreach ($arrChunks as $strChunk)
  537.         {
  538.             if (strncasecmp($strChunk'<pre'4) === || strncasecmp(ltrim($strChunk), '<textarea'9) === 0)
  539.             {
  540.                 $blnPreserveNext true;
  541.             }
  542.             elseif (strncasecmp($strChunk'<script'7) === 0)
  543.             {
  544.                 if ($isJavaScript($strChunk))
  545.                 {
  546.                     $blnOptimizeNext true;
  547.                     $strType 'js';
  548.                 }
  549.                 else
  550.                 {
  551.                     $blnPreserveNext true;
  552.                 }
  553.             }
  554.             elseif (strncasecmp($strChunk'<style'6) === 0)
  555.             {
  556.                 $blnOptimizeNext true;
  557.                 $strType 'css';
  558.             }
  559.             elseif ($blnPreserveNext)
  560.             {
  561.                 $blnPreserveNext false;
  562.             }
  563.             elseif ($blnOptimizeNext)
  564.             {
  565.                 $blnOptimizeNext false;
  566.                 // Minify inline scripts
  567.                 if ($strType == 'js')
  568.                 {
  569.                     $objMinify = new JS();
  570.                     $objMinify->add($strChunk);
  571.                     $strChunk $objMinify->minify();
  572.                 }
  573.                 elseif ($strType == 'css')
  574.                 {
  575.                     $objMinify = new CSS();
  576.                     $objMinify->add($strChunk);
  577.                     $strChunk $objMinify->minify();
  578.                 }
  579.             }
  580.             else
  581.             {
  582.                 // Remove line indentations and trailing spaces
  583.                 $strChunk str_replace("\r"''$strChunk);
  584.                 $strChunk preg_replace(array('/^[\t ]+/m''/[\t ]+$/m''/\n\n+/'), array(''''"\n"), $strChunk);
  585.             }
  586.             $strHtml .= $strChunk;
  587.         }
  588.         return trim($strHtml);
  589.     }
  590.     /**
  591.      * Generate the markup for a style sheet tag
  592.      *
  593.      * @param string $href  The script path
  594.      * @param string $media The media type string
  595.      * @param mixed  $mtime The file mtime
  596.      *
  597.      * @return string The markup string
  598.      */
  599.     public static function generateStyleTag($href$media=null$mtime=false)
  600.     {
  601.         // Add the filemtime if not given and not an external file
  602.         if ($mtime === null && !preg_match('@^https?://@'$href))
  603.         {
  604.             $container System::getContainer();
  605.             $projectDir $container->getParameter('kernel.project_dir');
  606.             if (file_exists($projectDir '/' $href))
  607.             {
  608.                 $mtime filemtime($projectDir '/' $href);
  609.             }
  610.             else
  611.             {
  612.                 $webDir StringUtil::stripRootDir($container->getParameter('contao.web_dir'));
  613.                 // Handle public bundle resources in the contao.web_dir folder
  614.                 if (file_exists($projectDir '/' $webDir '/' $href))
  615.                 {
  616.                     $mtime filemtime($projectDir '/' $webDir '/' $href);
  617.                 }
  618.             }
  619.         }
  620.         if ($mtime)
  621.         {
  622.             $href .= '?v=' substr(md5($mtime), 08);
  623.         }
  624.         return '<link rel="stylesheet" href="' $href '"' . (($media && $media != 'all') ? ' media="' $media '"' '') . '>';
  625.     }
  626.     /**
  627.      * Generate the markup for inline CSS code
  628.      *
  629.      * @param string $script The CSS code
  630.      *
  631.      * @return string The markup string
  632.      */
  633.     public static function generateInlineStyle($script)
  634.     {
  635.         return '<style>' $script '</style>';
  636.     }
  637.     /**
  638.      * Generate the markup for a JavaScript tag
  639.      *
  640.      * @param string      $src            The script path
  641.      * @param boolean     $async          True to add the async attribute
  642.      * @param mixed       $mtime          The file mtime
  643.      * @param string|null $hash           An optional integrity hash
  644.      * @param string|null $crossorigin    An optional crossorigin attribute
  645.      * @param string|null $referrerpolicy An optional referrerpolicy attribute
  646.      *
  647.      * @return string The markup string
  648.      */
  649.     public static function generateScriptTag($src$async=false$mtime=false$hash=null$crossorigin=null$referrerpolicy=null)
  650.     {
  651.         // Add the filemtime if not given and not an external file
  652.         if ($mtime === null && !preg_match('@^https?://@'$src))
  653.         {
  654.             $container System::getContainer();
  655.             $projectDir $container->getParameter('kernel.project_dir');
  656.             if (file_exists($projectDir '/' $src))
  657.             {
  658.                 $mtime filemtime($projectDir '/' $src);
  659.             }
  660.             else
  661.             {
  662.                 $webDir StringUtil::stripRootDir($container->getParameter('contao.web_dir'));
  663.                 // Handle public bundle resources in the contao.web_dir folder
  664.                 if (file_exists($projectDir '/' $webDir '/' $src))
  665.                 {
  666.                     $mtime filemtime($projectDir '/' $webDir '/' $src);
  667.                 }
  668.             }
  669.         }
  670.         if ($mtime)
  671.         {
  672.             $src .= '?v=' substr(md5($mtime), 08);
  673.         }
  674.         return '<script src="' $src '"' . ($async ' async' '') . ($hash ' integrity="' $hash '"' '') . ($crossorigin ' crossorigin="' $crossorigin '"' '') . ($referrerpolicy ' referrerpolicy="' $referrerpolicy '"' '') . '></script>';
  675.     }
  676.     /**
  677.      * Generate the markup for an inline JavaScript
  678.      *
  679.      * @param string $script The JavaScript code
  680.      *
  681.      * @return string The markup string
  682.      */
  683.     public static function generateInlineScript($script)
  684.     {
  685.         return '<script>' $script '</script>';
  686.     }
  687.     /**
  688.      * Generate the markup for an RSS feed tag
  689.      *
  690.      * @param string $href   The script path
  691.      * @param string $format The feed format
  692.      * @param string $title  The feed title
  693.      *
  694.      * @return string The markup string
  695.      */
  696.     public static function generateFeedTag($href$format$title)
  697.     {
  698.         return '<link type="application/' $format '+xml" rel="alternate" href="' $href '" title="' StringUtil::specialchars($title) . '">';
  699.     }
  700.     /**
  701.      * Flush the output buffers
  702.      *
  703.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  704.      */
  705.     public function flushAllData()
  706.     {
  707.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Template::flushAllData()" has been deprecated and will no longer work in Contao 5.0.');
  708.         if (\function_exists('fastcgi_finish_request'))
  709.         {
  710.             fastcgi_finish_request();
  711.         }
  712.         elseif (\PHP_SAPI !== 'cli')
  713.         {
  714.             $status ob_get_status(true);
  715.             $level \count($status);
  716.             while ($level-- > && (!empty($status[$level]['del']) || (isset($status[$level]['flags']) && ($status[$level]['flags'] & PHP_OUTPUT_HANDLER_REMOVABLE) && ($status[$level]['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE))))
  717.             {
  718.                 ob_end_flush();
  719.             }
  720.             flush();
  721.         }
  722.     }
  723. }
  724. class_alias(Template::class, 'Template');