vendor/contao/core-bundle/src/Resources/contao/classes/FrontendTemplate.php line 85

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 Symfony\Component\HttpFoundation\Response;
  11. /**
  12.  * Class FrontendTemplate
  13.  *
  14.  * @property integer $id
  15.  * @property string  $keywords
  16.  * @property string  $content
  17.  * @property array   $sections
  18.  * @property array   $positions
  19.  * @property array   $matches
  20.  * @property string  $tag
  21.  *
  22.  * @author Leo Feyer <https://github.com/leofeyer>
  23.  */
  24. class FrontendTemplate extends Template
  25. {
  26.     /**
  27.      * Unsued $_GET check
  28.      * @var boolean
  29.      */
  30.     protected $blnCheckRequest false;
  31.     /**
  32.      * Add a hook to modify the template output
  33.      *
  34.      * @return string The template markup
  35.      */
  36.     public function parse()
  37.     {
  38.         $strBuffer parent::parse();
  39.         // HOOK: add custom parse filters
  40.         if (isset($GLOBALS['TL_HOOKS']['parseFrontendTemplate']) && \is_array($GLOBALS['TL_HOOKS']['parseFrontendTemplate']))
  41.         {
  42.             foreach ($GLOBALS['TL_HOOKS']['parseFrontendTemplate'] as $callback)
  43.             {
  44.                 $this->import($callback[0]);
  45.                 $strBuffer $this->{$callback[0]}->{$callback[1]}($strBuffer$this->strTemplate$this);
  46.             }
  47.         }
  48.         return $strBuffer;
  49.     }
  50.     /**
  51.      * Send the response to the client
  52.      *
  53.      * @param bool $blnCheckRequest If true, check for unused $_GET parameters
  54.      *
  55.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  56.      *             Use FrontendTemplate::getResponse() instead.
  57.      */
  58.     public function output($blnCheckRequest=false)
  59.     {
  60.         $this->blnCheckRequest $blnCheckRequest;
  61.         parent::output();
  62.     }
  63.     /**
  64.      * Return a response object
  65.      *
  66.      * @param bool $blnCheckRequest      If true, check for unused $_GET parameters
  67.      * @param bool $blnForceCacheHeaders
  68.      *
  69.      * @return Response The response object
  70.      */
  71.     public function getResponse($blnCheckRequest=false$blnForceCacheHeaders=false)
  72.     {
  73.         $this->blnCheckRequest $blnCheckRequest;
  74.         $response parent::getResponse();
  75.         if ($blnForceCacheHeaders || === strncmp('fe_'$this->strTemplate3))
  76.         {
  77.             return $this->setCacheHeaders($response);
  78.         }
  79.         return $response;
  80.     }
  81.     /**
  82.      * Compile the template
  83.      *
  84.      * @throws \UnusedArgumentsException If there are unused $_GET parameters
  85.      *
  86.      * @internal Do not call this method in your code. It will be made private in Contao 5.0.
  87.      */
  88.     protected function compile()
  89.     {
  90.         $this->keywords '';
  91.         $arrKeywords StringUtil::trimsplit(','$GLOBALS['TL_KEYWORDS']);
  92.         // Add the meta keywords
  93.         if (isset($arrKeywords[0]))
  94.         {
  95.             $this->keywords str_replace(array("\n""\r"'"'), array(' '''''), implode(', 'array_unique($arrKeywords)));
  96.         }
  97.         // Parse the template
  98.         $this->strBuffer $this->parse();
  99.         // HOOK: add custom output filters
  100.         if (isset($GLOBALS['TL_HOOKS']['outputFrontendTemplate']) && \is_array($GLOBALS['TL_HOOKS']['outputFrontendTemplate']))
  101.         {
  102.             foreach ($GLOBALS['TL_HOOKS']['outputFrontendTemplate'] as $callback)
  103.             {
  104.                 $this->import($callback[0]);
  105.                 $this->strBuffer $this->{$callback[0]}->{$callback[1]}($this->strBuffer$this->strTemplate);
  106.             }
  107.         }
  108.         // Replace insert tags
  109.         $this->strBuffer $this->replaceInsertTags($this->strBuffer);
  110.         $this->strBuffer $this->replaceDynamicScriptTags($this->strBuffer); // see #4203
  111.         // HOOK: allow to modify the compiled markup (see #4291)
  112.         if (isset($GLOBALS['TL_HOOKS']['modifyFrontendPage']) && \is_array($GLOBALS['TL_HOOKS']['modifyFrontendPage']))
  113.         {
  114.             foreach ($GLOBALS['TL_HOOKS']['modifyFrontendPage'] as $callback)
  115.             {
  116.                 $this->import($callback[0]);
  117.                 $this->strBuffer $this->{$callback[0]}->{$callback[1]}($this->strBuffer$this->strTemplate);
  118.             }
  119.         }
  120.         // Check whether all $_GET parameters have been used (see #4277)
  121.         if ($this->blnCheckRequest && Input::hasUnusedGet())
  122.         {
  123.             throw new \UnusedArgumentsException('Unused arguments: ' implode(', 'Input::getUnusedGet()));
  124.         }
  125.         /** @var PageModel $objPage */
  126.         global $objPage;
  127.         // Minify the markup
  128.         if ($objPage->minifyMarkup)
  129.         {
  130.             $this->strBuffer $this->minifyHtml($this->strBuffer);
  131.         }
  132.         // Replace literal insert tags (see #670, #3249)
  133.         $this->strBuffer preg_replace_callback(
  134.             '/<script[^>]*>.*?<\/script[^>]*>|\[[{}]]/is',
  135.             static function ($matches)
  136.             {
  137.                 return $matches[0][0] === '<' $matches[0] : '&#' . \ord($matches[0][1]) . ';&#' . \ord($matches[0][1]) . ';';
  138.             },
  139.             $this->strBuffer
  140.         );
  141.         parent::compile();
  142.     }
  143.     /**
  144.      * Return a custom layout section
  145.      *
  146.      * @param string $key      The section name
  147.      * @param string $template An optional template name
  148.      */
  149.     public function section($key$template=null)
  150.     {
  151.         if (empty($this->sections[$key]))
  152.         {
  153.             return;
  154.         }
  155.         $this->id $key;
  156.         $this->content $this->sections[$key];
  157.         if ($template === null)
  158.         {
  159.             foreach ($this->positions as $position)
  160.             {
  161.                 if (isset($position[$key]['template']))
  162.                 {
  163.                     $template $position[$key]['template'];
  164.                 }
  165.             }
  166.         }
  167.         if ($template === null)
  168.         {
  169.             $template 'block_section';
  170.         }
  171.         include $this->getTemplate($template);
  172.     }
  173.     /**
  174.      * Return the custom layout sections
  175.      *
  176.      * @param string $key      An optional section name
  177.      * @param string $template An optional template name
  178.      */
  179.     public function sections($key=null$template=null)
  180.     {
  181.         if (!array_filter($this->sections))
  182.         {
  183.             return;
  184.         }
  185.         // The key does not match
  186.         if ($key && !isset($this->positions[$key]))
  187.         {
  188.             return;
  189.         }
  190.         $matches = array();
  191.         foreach ($this->positions[$key] as $id=>$section)
  192.         {
  193.             if (!empty($this->sections[$id]))
  194.             {
  195.                 if (!isset($section['template']))
  196.                 {
  197.                     $section['template'] = 'block_section';
  198.                 }
  199.                 $section['content'] = $this->sections[$id];
  200.                 $matches[$id] = $section;
  201.             }
  202.         }
  203.         // Return if the section is empty (see #1115)
  204.         if (empty($matches))
  205.         {
  206.             return;
  207.         }
  208.         $this->matches $matches;
  209.         if ($template === null)
  210.         {
  211.             $template 'block_sections';
  212.         }
  213.         include $this->getTemplate($template);
  214.     }
  215.     /**
  216.      * Point to `Frontend::addToUrl()` in front end templates (see #6736)
  217.      *
  218.      * @param string  $strRequest      The request string to be added
  219.      * @param boolean $blnIgnoreParams If true, the $_GET parameters will be ignored
  220.      * @param array   $arrUnset        An optional array of keys to unset
  221.      *
  222.      * @return string The new URI string
  223.      */
  224.     public static function addToUrl($strRequest$blnIgnoreParams=false$arrUnset=array())
  225.     {
  226.         return Frontend::addToUrl($strRequest$blnIgnoreParams$arrUnset);
  227.     }
  228.     /**
  229.      * Check whether there is an authenticated back end user
  230.      *
  231.      * @return boolean True if there is an authenticated back end user
  232.      */
  233.     public function hasAuthenticatedBackendUser()
  234.     {
  235.         return System::getContainer()->get('contao.security.token_checker')->hasBackendUser();
  236.     }
  237.     /**
  238.      * Add the template output to the cache and add the cache headers
  239.      *
  240.      * @deprecated Deprecated since Contao 4.3, to be removed in Contao 5.0.
  241.      *             Use proper response caching headers instead.
  242.      */
  243.     protected function addToCache()
  244.     {
  245.         @trigger_error('Using FrontendTemplate::addToCache() has been deprecated and will no longer work in Contao 5.0. Use proper response caching headers instead.'E_USER_DEPRECATED);
  246.     }
  247.     /**
  248.      * Add the template output to the search index
  249.      *
  250.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  251.      *             Use the kernel.terminate event instead.
  252.      */
  253.     protected function addToSearchIndex()
  254.     {
  255.         @trigger_error('Using FrontendTemplate::addToSearchIndex() has been deprecated and will no longer work in Contao 5.0. Use the kernel.terminate event instead.'E_USER_DEPRECATED);
  256.     }
  257.     /**
  258.      * Return a custom layout section
  259.      *
  260.      * @param string $strKey The section name
  261.      *
  262.      * @return string The section markup
  263.      *
  264.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  265.      *             Use FrontendTemplate::section() instead.
  266.      */
  267.     public function getCustomSection($strKey)
  268.     {
  269.         @trigger_error('Using FrontendTemplate::getCustomSection() has been deprecated and will no longer work in Contao 5.0. Use FrontendTemplate::section() instead.'E_USER_DEPRECATED);
  270.         return '<div id="' $strKey '">' $this->sections[$strKey] . '</div>' "\n";
  271.     }
  272.     /**
  273.      * Return all custom layout sections
  274.      *
  275.      * @param string $strKey An optional section name
  276.      *
  277.      * @return string The section markup
  278.      *
  279.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  280.      *             Use FrontendTemplate::sections() instead.
  281.      */
  282.     public function getCustomSections($strKey=null)
  283.     {
  284.         @trigger_error('Using FrontendTemplate::getCustomSections() has been deprecated and will no longer work in Contao 5.0. Use FrontendTemplate::sections() instead.'E_USER_DEPRECATED);
  285.         if ($strKey && !isset($this->positions[$strKey]))
  286.         {
  287.             return '';
  288.         }
  289.         $tag 'div';
  290.         // Use the section tag for the main column
  291.         if ($strKey == 'main')
  292.         {
  293.             $tag 'section';
  294.         }
  295.         $sections '';
  296.         // Standardize the IDs (thanks to Tsarma) (see #4251)
  297.         foreach ($this->positions[$strKey] as $sect)
  298.         {
  299.             if (isset($this->sections[$sect['id']]))
  300.             {
  301.                 $sections .= "\n" '<' $tag ' id="' StringUtil::standardize($sect['id'], true) . '">' "\n" '<div class="inside">' "\n" $this->sections[$sect['id']] . "\n" '</div>' "\n" '</' $tag '>' "\n";
  302.             }
  303.         }
  304.         if (!$sections)
  305.         {
  306.             return '';
  307.         }
  308.         return '<div class="custom">' "\n" $sections "\n" '</div>' "\n";
  309.     }
  310.     /**
  311.      * Set the cache headers according to the page settings.
  312.      *
  313.      * @param Response $response The response object
  314.      *
  315.      * @return Response The response object
  316.      */
  317.     private function setCacheHeaders(Response $response)
  318.     {
  319.         /** @var PageModel $objPage */
  320.         global $objPage;
  321.         // Do not cache the response if caching was not configured at all or disabled explicitly
  322.         if (($objPage->cache === false || $objPage->cache 1) && ($objPage->clientCache === false || $objPage->clientCache 1))
  323.         {
  324.             $response->headers->set('Cache-Control''no-cache, no-store');
  325.             return $response->setPrivate(); // Make sure the response is private
  326.         }
  327.         // Private cache
  328.         if ($objPage->clientCache 0)
  329.         {
  330.             $response->setMaxAge($objPage->clientCache);
  331.             $response->setPrivate(); // Make sure the response is private
  332.         }
  333.         // Shared cache
  334.         if ($objPage->cache 0)
  335.         {
  336.             $response->setSharedMaxAge($objPage->cache); // Automatically sets the response to public
  337.             // We vary on cookies if a response is cacheable by the shared
  338.             // cache, so a reverse proxy does not load a response from cache if
  339.             // the _request_ contains a cookie.
  340.             //
  341.             // This DOES NOT mean that we generate a cache entry for every
  342.             // response containing a cookie! Responses with cookies will always
  343.             // be private (@see Contao\CoreBundle\EventListener\MakeResponsePrivateListener).
  344.             //
  345.             // However, we want to be able to force the reverse proxy to load a
  346.             // response from cache, even if the request contains a cookie – in
  347.             // case the admin has configured to do so. A typical use case would
  348.             // be serving public pages from cache to logged in members.
  349.             if (!$objPage->alwaysLoadFromCache)
  350.             {
  351.                 $response->setVary(array('Cookie'));
  352.             }
  353.             // Tag the page (see #2137)
  354.             if (System::getContainer()->has('fos_http_cache.http.symfony_response_tagger'))
  355.             {
  356.                 $responseTagger System::getContainer()->get('fos_http_cache.http.symfony_response_tagger');
  357.                 $responseTagger->addTags(array('contao.db.tl_page.' $objPage->id));
  358.             }
  359.         }
  360.         return $response;
  361.     }
  362. }
  363. class_alias(FrontendTemplate::class, 'FrontendTemplate');