vendor/contao/core-bundle/src/Resources/contao/library/Contao/Controller.php line 1447

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\Asset\ContaoContext;
  11. use Contao\CoreBundle\Exception\AccessDeniedException;
  12. use Contao\CoreBundle\Exception\AjaxRedirectResponseException;
  13. use Contao\CoreBundle\Exception\PageNotFoundException;
  14. use Contao\CoreBundle\Exception\RedirectResponseException;
  15. use Contao\Database\Result;
  16. use Contao\Image\PictureConfiguration;
  17. use Contao\Model\Collection;
  18. use Symfony\Component\Finder\Finder;
  19. use Symfony\Component\Finder\Glob;
  20. /**
  21.  * Abstract parent class for Controllers
  22.  *
  23.  * Some of the methods have been made static in Contao 3 and can be used in
  24.  * non-object context as well.
  25.  *
  26.  * Usage:
  27.  *
  28.  *     echo Controller::getTheme();
  29.  *
  30.  * Inside a controller:
  31.  *
  32.  *     public function generate()
  33.  *     {
  34.  *         return $this->getArticle(2);
  35.  *     }
  36.  *
  37.  * @author Leo Feyer <https://github.com/leofeyer>
  38.  */
  39. abstract class Controller extends System
  40. {
  41.     /**
  42.      * @var Template
  43.      *
  44.      * @todo: Add in Contao 5.0
  45.      */
  46.     //protected $Template;
  47.     /**
  48.      * @var array
  49.      */
  50.     protected static $arrQueryCache = array();
  51.     /**
  52.      * @var array
  53.      */
  54.     private static $arrOldBePathCache = array();
  55.     /**
  56.      * Find a particular template file and return its path
  57.      *
  58.      * @param string $strTemplate The name of the template
  59.      *
  60.      * @return string The path to the template file
  61.      *
  62.      * @throws \RuntimeException If the template group folder is insecure
  63.      */
  64.     public static function getTemplate($strTemplate)
  65.     {
  66.         $strTemplate basename($strTemplate);
  67.         // Check for a theme folder
  68.         if (\defined('TL_MODE') && TL_MODE == 'FE')
  69.         {
  70.             /** @var PageModel $objPage */
  71.             global $objPage;
  72.             if ($objPage->templateGroup)
  73.             {
  74.                 if (Validator::isInsecurePath($objPage->templateGroup))
  75.                 {
  76.                     throw new \RuntimeException('Invalid path ' $objPage->templateGroup);
  77.                 }
  78.                 return TemplateLoader::getPath($strTemplate'html5'$objPage->templateGroup);
  79.             }
  80.         }
  81.         return TemplateLoader::getPath($strTemplate'html5');
  82.     }
  83.     /**
  84.      * Return all template files of a particular group as array
  85.      *
  86.      * @param string $strPrefix           The template name prefix (e.g. "ce_")
  87.      * @param array  $arrAdditionalMapper An additional mapper array
  88.      * @param string $strDefaultTemplate  An optional default template
  89.      *
  90.      * @return array An array of template names
  91.      */
  92.     public static function getTemplateGroup($strPrefix, array $arrAdditionalMapper=array(), $strDefaultTemplate='')
  93.     {
  94.         $arrTemplates = array();
  95.         $arrBundleTemplates = array();
  96.         $arrMapper array_merge
  97.         (
  98.             $arrAdditionalMapper,
  99.             array
  100.             (
  101.                 'ce' => array_keys(array_merge(...array_values($GLOBALS['TL_CTE']))),
  102.                 'form' => array_keys($GLOBALS['TL_FFL']),
  103.                 'mod' => array_keys(array_merge(...array_values($GLOBALS['FE_MOD']))),
  104.             )
  105.         );
  106.         // Add templates that are not directly associated with a form field
  107.         $arrMapper['form'][] = 'row';
  108.         $arrMapper['form'][] = 'row_double';
  109.         $arrMapper['form'][] = 'xml';
  110.         $arrMapper['form'][] = 'wrapper';
  111.         $arrMapper['form'][] = 'message';
  112.         $arrMapper['form'][] = 'textfield'// TODO: remove in Contao 5.0
  113.         // Add templates that are not directly associated with a module
  114.         $arrMapper['mod'][] = 'article';
  115.         $arrMapper['mod'][] = 'message';
  116.         $arrMapper['mod'][] = 'password'// TODO: remove in Contao 5.0
  117.         $arrMapper['mod'][] = 'comment_form'// TODO: remove in Contao 5.0
  118.         $arrMapper['mod'][] = 'newsletter'// TODO: remove in Contao 5.0
  119.         // Get the default templates
  120.         foreach (TemplateLoader::getPrefixedFiles($strPrefix) as $strTemplate)
  121.         {
  122.             if ($strTemplate != $strPrefix)
  123.             {
  124.                 list($k$strKey) = explode('_'$strTemplate2);
  125.                 if (isset($arrMapper[$k]) && \in_array($strKey$arrMapper[$k]))
  126.                 {
  127.                     $arrBundleTemplates[] = $strTemplate;
  128.                     continue;
  129.                 }
  130.             }
  131.             $arrTemplates[$strTemplate][] = 'root';
  132.         }
  133.         $strGlobPrefix $strPrefix;
  134.         // Backwards compatibility (see #725)
  135.         if (substr($strGlobPrefix, -1) == '_')
  136.         {
  137.             $strGlobPrefix substr($strGlobPrefix0, -1) . '[_-]';
  138.         }
  139.         $projectDir System::getContainer()->getParameter('kernel.project_dir');
  140.         $arrCustomized self::braceGlob($projectDir '/templates/' $strGlobPrefix '*.html5');
  141.         // Add the customized templates
  142.         if (!empty($arrCustomized) && \is_array($arrCustomized))
  143.         {
  144.             $blnIsGroupPrefix preg_match('/^[a-z]+_$/'$strPrefix);
  145.             foreach ($arrCustomized as $strFile)
  146.             {
  147.                 $strTemplate basename($strFilestrrchr($strFile'.'));
  148.                 if (strpos($strTemplate'-') !== false)
  149.                 {
  150.                     @trigger_error('Using hyphens in the template name "' $strTemplate '.html5" has been deprecated and will no longer work in Contao 5.0. Use snake_case instead.'E_USER_DEPRECATED);
  151.                 }
  152.                 // Ignore bundle templates, e.g. mod_article and mod_article_list
  153.                 if (\in_array($strTemplate$arrBundleTemplates))
  154.                 {
  155.                     continue;
  156.                 }
  157.                 // Also ignore custom templates belonging to a different bundle template,
  158.                 // e.g. mod_article and mod_article_list_custom
  159.                 if (!$blnIsGroupPrefix)
  160.                 {
  161.                     foreach ($arrBundleTemplates as $strKey)
  162.                     {
  163.                         if (strpos($strTemplate$strKey '_') === 0)
  164.                         {
  165.                             continue 2;
  166.                         }
  167.                     }
  168.                 }
  169.                 $arrTemplates[$strTemplate][] = $GLOBALS['TL_LANG']['MSC']['global'];
  170.             }
  171.         }
  172.         $arrDefaultPlaces = array();
  173.         if ($strDefaultTemplate)
  174.         {
  175.             $arrDefaultPlaces[] = $GLOBALS['TL_LANG']['MSC']['default'];
  176.             if (file_exists($projectDir '/templates/' $strDefaultTemplate '.html5'))
  177.             {
  178.                 $arrDefaultPlaces[] = $GLOBALS['TL_LANG']['MSC']['global'];
  179.             }
  180.         }
  181.         // Do not look for back end templates in theme folders (see #5379)
  182.         if ($strPrefix != 'be_' && $strPrefix != 'mail_')
  183.         {
  184.             // Try to select the themes (see #5210)
  185.             try
  186.             {
  187.                 $objTheme ThemeModel::findAll(array('order'=>'name'));
  188.             }
  189.             catch (\Exception $e)
  190.             {
  191.                 $objTheme null;
  192.             }
  193.             // Add the theme templates
  194.             if ($objTheme !== null)
  195.             {
  196.                 while ($objTheme->next())
  197.                 {
  198.                     if (!$objTheme->templates)
  199.                     {
  200.                         continue;
  201.                     }
  202.                     if ($strDefaultTemplate && file_exists($projectDir '/' $objTheme->templates '/' $strDefaultTemplate '.html5'))
  203.                     {
  204.                         $arrDefaultPlaces[] = $objTheme->name;
  205.                     }
  206.                     $arrThemeTemplates self::braceGlob($projectDir '/' $objTheme->templates '/' $strGlobPrefix '*.html5');
  207.                     if (!empty($arrThemeTemplates) && \is_array($arrThemeTemplates))
  208.                     {
  209.                         foreach ($arrThemeTemplates as $strFile)
  210.                         {
  211.                             $strTemplate basename($strFilestrrchr($strFile'.'));
  212.                             $arrTemplates[$strTemplate][] = $objTheme->name;
  213.                         }
  214.                     }
  215.                 }
  216.             }
  217.         }
  218.         // Show the template sources (see #6875)
  219.         foreach ($arrTemplates as $k=>$v)
  220.         {
  221.             $v array_filter($v, static function ($a)
  222.             {
  223.                 return $a != 'root';
  224.             });
  225.             if (empty($v))
  226.             {
  227.                 $arrTemplates[$k] = $k;
  228.             }
  229.             else
  230.             {
  231.                 $arrTemplates[$k] = $k ' (' implode(', '$v) . ')';
  232.             }
  233.         }
  234.         // Sort the template names
  235.         ksort($arrTemplates);
  236.         if ($strDefaultTemplate)
  237.         {
  238.             if (!empty($arrDefaultPlaces))
  239.             {
  240.                 $strDefaultTemplate .= ' (' implode(', '$arrDefaultPlaces) . ')';
  241.             }
  242.             $arrTemplates = array('' => $strDefaultTemplate) + $arrTemplates;
  243.         }
  244.         return $arrTemplates;
  245.     }
  246.     /**
  247.      * Generate a front end module and return it as string
  248.      *
  249.      * @param mixed  $intId     A module ID or a Model object
  250.      * @param string $strColumn The name of the column
  251.      *
  252.      * @return string The module HTML markup
  253.      */
  254.     public static function getFrontendModule($intId$strColumn='main')
  255.     {
  256.         if (!\is_object($intId) && !\strlen($intId))
  257.         {
  258.             return '';
  259.         }
  260.         /** @var PageModel $objPage */
  261.         global $objPage;
  262.         // Articles
  263.         if (!\is_object($intId) && $intId == 0)
  264.         {
  265.             // Show a particular article only
  266.             if ($objPage->type == 'regular' && Input::get('articles'))
  267.             {
  268.                 list($strSection$strArticle) = explode(':'Input::get('articles'));
  269.                 if ($strArticle === null)
  270.                 {
  271.                     $strArticle $strSection;
  272.                     $strSection 'main';
  273.                 }
  274.                 if ($strSection == $strColumn)
  275.                 {
  276.                     $objArticle ArticleModel::findPublishedByIdOrAliasAndPid($strArticle$objPage->id);
  277.                     // Send a 404 header if there is no published article
  278.                     if (null === $objArticle)
  279.                     {
  280.                         throw new PageNotFoundException('Page not found: ' Environment::get('uri'));
  281.                     }
  282.                     // Send a 403 header if the article cannot be accessed
  283.                     if (!static::isVisibleElement($objArticle))
  284.                     {
  285.                         throw new AccessDeniedException('Access denied: ' Environment::get('uri'));
  286.                     }
  287.                     // Add the "first" and "last" classes (see #2583)
  288.                     $objArticle->classes = array('first''last');
  289.                     return static::getArticle($objArticle);
  290.                 }
  291.             }
  292.             // HOOK: add custom logic
  293.             if (isset($GLOBALS['TL_HOOKS']['getArticles']) && \is_array($GLOBALS['TL_HOOKS']['getArticles']))
  294.             {
  295.                 foreach ($GLOBALS['TL_HOOKS']['getArticles'] as $callback)
  296.                 {
  297.                     $return = static::importStatic($callback[0])->{$callback[1]}($objPage->id$strColumn);
  298.                     if (\is_string($return))
  299.                     {
  300.                         return $return;
  301.                     }
  302.                 }
  303.             }
  304.             // Show all articles (no else block here, see #4740)
  305.             $objArticles ArticleModel::findPublishedByPidAndColumn($objPage->id$strColumn);
  306.             if ($objArticles === null)
  307.             {
  308.                 return '';
  309.             }
  310.             $return '';
  311.             $intCount 0;
  312.             $blnMultiMode = ($objArticles->count() > 1);
  313.             $intLast $objArticles->count() - 1;
  314.             while ($objArticles->next())
  315.             {
  316.                 /** @var ArticleModel $objRow */
  317.                 $objRow $objArticles->current();
  318.                 // Add the "first" and "last" classes (see #2583)
  319.                 if ($intCount == || $intCount == $intLast)
  320.                 {
  321.                     $arrCss = array();
  322.                     if ($intCount == 0)
  323.                     {
  324.                         $arrCss[] = 'first';
  325.                     }
  326.                     if ($intCount == $intLast)
  327.                     {
  328.                         $arrCss[] = 'last';
  329.                     }
  330.                     $objRow->classes $arrCss;
  331.                 }
  332.                 $return .= static::getArticle($objRow$blnMultiModefalse$strColumn);
  333.                 ++$intCount;
  334.             }
  335.             return $return;
  336.         }
  337.         // Other modules
  338.         if (\is_object($intId))
  339.         {
  340.             $objRow $intId;
  341.         }
  342.         else
  343.         {
  344.             $objRow ModuleModel::findByPk($intId);
  345.             if ($objRow === null)
  346.             {
  347.                 return '';
  348.             }
  349.         }
  350.         // Check the visibility (see #6311)
  351.         if (!static::isVisibleElement($objRow))
  352.         {
  353.             return '';
  354.         }
  355.         $strClass Module::findClass($objRow->type);
  356.         // Return if the class does not exist
  357.         if (!class_exists($strClass))
  358.         {
  359.             static::log('Module class "' $strClass '" (module "' $objRow->type '") does not exist'__METHOD__TL_ERROR);
  360.             return '';
  361.         }
  362.         $strStopWatchId 'contao.frontend_module.' $objRow->type ' (ID ' $objRow->id ')';
  363.         if (System::getContainer()->getParameter('kernel.debug'))
  364.         {
  365.             $objStopwatch System::getContainer()->get('debug.stopwatch');
  366.             $objStopwatch->start($strStopWatchId'contao.layout');
  367.         }
  368.         $objRow->typePrefix 'mod_';
  369.         /** @var Module $objModule */
  370.         $objModule = new $strClass($objRow$strColumn);
  371.         $strBuffer $objModule->generate();
  372.         // HOOK: add custom logic
  373.         if (isset($GLOBALS['TL_HOOKS']['getFrontendModule']) && \is_array($GLOBALS['TL_HOOKS']['getFrontendModule']))
  374.         {
  375.             foreach ($GLOBALS['TL_HOOKS']['getFrontendModule'] as $callback)
  376.             {
  377.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($objRow$strBuffer$objModule);
  378.             }
  379.         }
  380.         // Disable indexing if protected
  381.         if ($objModule->protected && !preg_match('/^\s*<!-- indexer::stop/'$strBuffer))
  382.         {
  383.             $strBuffer "\n<!-- indexer::stop -->" $strBuffer "<!-- indexer::continue -->\n";
  384.         }
  385.         if (isset($objStopwatch) && $objStopwatch->isStarted($strStopWatchId))
  386.         {
  387.             $objStopwatch->stop($strStopWatchId);
  388.         }
  389.         return $strBuffer;
  390.     }
  391.     /**
  392.      * Generate an article and return it as string
  393.      *
  394.      * @param mixed   $varId          The article ID or a Model object
  395.      * @param boolean $blnMultiMode   If true, only teasers will be shown
  396.      * @param boolean $blnIsInsertTag If true, there will be no page relation
  397.      * @param string  $strColumn      The name of the column
  398.      *
  399.      * @return string|boolean The article HTML markup or false
  400.      */
  401.     public static function getArticle($varId$blnMultiMode=false$blnIsInsertTag=false$strColumn='main')
  402.     {
  403.         /** @var PageModel $objPage */
  404.         global $objPage;
  405.         if (\is_object($varId))
  406.         {
  407.             $objRow $varId;
  408.         }
  409.         else
  410.         {
  411.             if (!$varId)
  412.             {
  413.                 return '';
  414.             }
  415.             $objRow ArticleModel::findByIdOrAliasAndPid($varId, (!$blnIsInsertTag $objPage->id null));
  416.             if ($objRow === null)
  417.             {
  418.                 return false;
  419.             }
  420.         }
  421.         // Check the visibility (see #6311)
  422.         if (!static::isVisibleElement($objRow))
  423.         {
  424.             return '';
  425.         }
  426.         // Print the article as PDF
  427.         if (isset($_GET['pdf']) && Input::get('pdf') == $objRow->id)
  428.         {
  429.             // Deprecated since Contao 4.0, to be removed in Contao 5.0
  430.             if ($objRow->printable == 1)
  431.             {
  432.                 @trigger_error('Setting tl_article.printable to "1" has been deprecated and will no longer work in Contao 5.0.'E_USER_DEPRECATED);
  433.                 $objArticle = new ModuleArticle($objRow);
  434.                 $objArticle->generatePdf();
  435.             }
  436.             elseif ($objRow->printable)
  437.             {
  438.                 $options StringUtil::deserialize($objRow->printable);
  439.                 if (\is_array($options) && \in_array('pdf'$options))
  440.                 {
  441.                     $objArticle = new ModuleArticle($objRow);
  442.                     $objArticle->generatePdf();
  443.                 }
  444.             }
  445.         }
  446.         $objRow->headline $objRow->title;
  447.         $objRow->multiMode $blnMultiMode;
  448.         // HOOK: add custom logic
  449.         if (isset($GLOBALS['TL_HOOKS']['getArticle']) && \is_array($GLOBALS['TL_HOOKS']['getArticle']))
  450.         {
  451.             foreach ($GLOBALS['TL_HOOKS']['getArticle'] as $callback)
  452.             {
  453.                 static::importStatic($callback[0])->{$callback[1]}($objRow);
  454.             }
  455.         }
  456.         $strStopWatchId 'contao.article (ID ' $objRow->id ')';
  457.         if (System::getContainer()->getParameter('kernel.debug'))
  458.         {
  459.             $objStopwatch System::getContainer()->get('debug.stopwatch');
  460.             $objStopwatch->start($strStopWatchId'contao.layout');
  461.         }
  462.         $objArticle = new ModuleArticle($objRow$strColumn);
  463.         $strBuffer $objArticle->generate($blnIsInsertTag);
  464.         // Disable indexing if protected
  465.         if ($objArticle->protected && !preg_match('/^\s*<!-- indexer::stop/'$strBuffer))
  466.         {
  467.             $strBuffer "\n<!-- indexer::stop -->" $strBuffer "<!-- indexer::continue -->\n";
  468.         }
  469.         if (isset($objStopwatch) && $objStopwatch->isStarted($strStopWatchId))
  470.         {
  471.             $objStopwatch->stop($strStopWatchId);
  472.         }
  473.         return $strBuffer;
  474.     }
  475.     /**
  476.      * Generate a content element and return it as string
  477.      *
  478.      * @param mixed  $intId     A content element ID or a Model object
  479.      * @param string $strColumn The column the element is in
  480.      *
  481.      * @return string The content element HTML markup
  482.      */
  483.     public static function getContentElement($intId$strColumn='main')
  484.     {
  485.         if (\is_object($intId))
  486.         {
  487.             $objRow $intId;
  488.         }
  489.         else
  490.         {
  491.             if ($intId || !\strlen($intId))
  492.             {
  493.                 return '';
  494.             }
  495.             $objRow ContentModel::findByPk($intId);
  496.             if ($objRow === null)
  497.             {
  498.                 return '';
  499.             }
  500.         }
  501.         // Check the visibility (see #6311)
  502.         if (!static::isVisibleElement($objRow))
  503.         {
  504.             return '';
  505.         }
  506.         $strClass ContentElement::findClass($objRow->type);
  507.         // Return if the class does not exist
  508.         if (!class_exists($strClass))
  509.         {
  510.             static::log('Content element class "' $strClass '" (content element "' $objRow->type '") does not exist'__METHOD__TL_ERROR);
  511.             return '';
  512.         }
  513.         $objRow->typePrefix 'ce_';
  514.         $strStopWatchId 'contao.content_element.' $objRow->type ' (ID ' $objRow->id ')';
  515.         if ($objRow->type != 'module' && System::getContainer()->getParameter('kernel.debug'))
  516.         {
  517.             $objStopwatch System::getContainer()->get('debug.stopwatch');
  518.             $objStopwatch->start($strStopWatchId'contao.layout');
  519.         }
  520.         /** @var ContentElement $objElement */
  521.         $objElement = new $strClass($objRow$strColumn);
  522.         $strBuffer $objElement->generate();
  523.         // HOOK: add custom logic
  524.         if (isset($GLOBALS['TL_HOOKS']['getContentElement']) && \is_array($GLOBALS['TL_HOOKS']['getContentElement']))
  525.         {
  526.             foreach ($GLOBALS['TL_HOOKS']['getContentElement'] as $callback)
  527.             {
  528.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($objRow$strBuffer$objElement);
  529.             }
  530.         }
  531.         // Disable indexing if protected
  532.         if ($objElement->protected && !preg_match('/^\s*<!-- indexer::stop/'$strBuffer))
  533.         {
  534.             $strBuffer "\n<!-- indexer::stop -->" $strBuffer "<!-- indexer::continue -->\n";
  535.         }
  536.         if (isset($objStopwatch) && $objStopwatch->isStarted($strStopWatchId))
  537.         {
  538.             $objStopwatch->stop($strStopWatchId);
  539.         }
  540.         return $strBuffer;
  541.     }
  542.     /**
  543.      * Generate a form and return it as string
  544.      *
  545.      * @param mixed   $varId     A form ID or a Model object
  546.      * @param string  $strColumn The column the form is in
  547.      * @param boolean $blnModule Render the form as module
  548.      *
  549.      * @return string The form HTML markup
  550.      */
  551.     public static function getForm($varId$strColumn='main'$blnModule=false)
  552.     {
  553.         if (\is_object($varId))
  554.         {
  555.             $objRow $varId;
  556.         }
  557.         else
  558.         {
  559.             if (!$varId)
  560.             {
  561.                 return '';
  562.             }
  563.             $objRow FormModel::findByIdOrAlias($varId);
  564.             if ($objRow === null)
  565.             {
  566.                 return '';
  567.             }
  568.         }
  569.         $strClass $blnModule Module::findClass('form') : ContentElement::findClass('form');
  570.         if (!class_exists($strClass))
  571.         {
  572.             static::log('Form class "' $strClass '" does not exist'__METHOD__TL_ERROR);
  573.             return '';
  574.         }
  575.         $objRow->typePrefix $blnModule 'mod_' 'ce_';
  576.         $objRow->form $objRow->id;
  577.         /** @var Form $objElement */
  578.         $objElement = new $strClass($objRow$strColumn);
  579.         $strBuffer $objElement->generate();
  580.         // HOOK: add custom logic
  581.         if (isset($GLOBALS['TL_HOOKS']['getForm']) && \is_array($GLOBALS['TL_HOOKS']['getForm']))
  582.         {
  583.             foreach ($GLOBALS['TL_HOOKS']['getForm'] as $callback)
  584.             {
  585.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($objRow$strBuffer$objElement);
  586.             }
  587.         }
  588.         return $strBuffer;
  589.     }
  590.     /**
  591.      * Return the languages for the TinyMCE spellchecker
  592.      *
  593.      * @return string The TinyMCE spellchecker language string
  594.      */
  595.     protected function getSpellcheckerString()
  596.     {
  597.         System::loadLanguageFile('languages');
  598.         $return = array();
  599.         $langs scan(__DIR__ '/../../languages');
  600.         array_unshift($langs$GLOBALS['TL_LANGUAGE']);
  601.         foreach ($langs as $lang)
  602.         {
  603.             $lang substr($lang02);
  604.             if (isset($GLOBALS['TL_LANG']['LNG'][$lang]))
  605.             {
  606.                 $return[$lang] = $GLOBALS['TL_LANG']['LNG'][$lang] . '=' $lang;
  607.             }
  608.         }
  609.         return '+' implode(','array_unique($return));
  610.     }
  611.     /**
  612.      * Calculate the page status icon name based on the page parameters
  613.      *
  614.      * @param PageModel|Result|\stdClass $objPage The page object
  615.      *
  616.      * @return string The status icon name
  617.      */
  618.     public static function getPageStatusIcon($objPage)
  619.     {
  620.         $sub 0;
  621.         $image $objPage->type '.svg';
  622.         // Page not published or not active
  623.         if (!$objPage->published || ($objPage->start && $objPage->start time()) || ($objPage->stop && $objPage->stop <= time()))
  624.         {
  625.             ++$sub;
  626.         }
  627.         // Page hidden from menu
  628.         if ($objPage->hide && !\in_array($objPage->type, array('root''error_401''error_403''error_404')))
  629.         {
  630.             $sub += 2;
  631.         }
  632.         // Page protected
  633.         if ($objPage->protected && !\in_array($objPage->type, array('root''error_401''error_403''error_404')))
  634.         {
  635.             $sub += 4;
  636.         }
  637.         // Get the image name
  638.         if ($sub 0)
  639.         {
  640.             $image $objPage->type '_' $sub '.svg';
  641.         }
  642.         // HOOK: add custom logic
  643.         if (isset($GLOBALS['TL_HOOKS']['getPageStatusIcon']) && \is_array($GLOBALS['TL_HOOKS']['getPageStatusIcon']))
  644.         {
  645.             foreach ($GLOBALS['TL_HOOKS']['getPageStatusIcon'] as $callback)
  646.             {
  647.                 $image = static::importStatic($callback[0])->{$callback[1]}($objPage$image);
  648.             }
  649.         }
  650.         return $image;
  651.     }
  652.     /**
  653.      * Check whether an element is visible in the front end
  654.      *
  655.      * @param Model|ContentModel|ModuleModel $objElement The element model
  656.      *
  657.      * @return boolean True if the element is visible
  658.      */
  659.     public static function isVisibleElement(Model $objElement)
  660.     {
  661.         $blnReturn true;
  662.         // Only apply the restrictions in the front end
  663.         if (TL_MODE == 'FE')
  664.         {
  665.             $blnFeUserLoggedIn System::getContainer()->get('contao.security.token_checker')->hasFrontendUser();
  666.             // Protected element
  667.             if ($objElement->protected)
  668.             {
  669.                 if (!$blnFeUserLoggedIn)
  670.                 {
  671.                     $blnReturn false;
  672.                 }
  673.                 else
  674.                 {
  675.                     $objUser FrontendUser::getInstance();
  676.                     if (!\is_array($objUser->groups))
  677.                     {
  678.                         $blnReturn false;
  679.                     }
  680.                     else
  681.                     {
  682.                         $groups StringUtil::deserialize($objElement->groups);
  683.                         if (empty($groups) || !\is_array($groups) || !\count(array_intersect($groups$objUser->groups)))
  684.                         {
  685.                             $blnReturn false;
  686.                         }
  687.                     }
  688.                 }
  689.             }
  690.             // Show to guests only
  691.             elseif ($objElement->guests && $blnFeUserLoggedIn)
  692.             {
  693.                 $blnReturn false;
  694.             }
  695.         }
  696.         // HOOK: add custom logic
  697.         if (isset($GLOBALS['TL_HOOKS']['isVisibleElement']) && \is_array($GLOBALS['TL_HOOKS']['isVisibleElement']))
  698.         {
  699.             foreach ($GLOBALS['TL_HOOKS']['isVisibleElement'] as $callback)
  700.             {
  701.                 $blnReturn = static::importStatic($callback[0])->{$callback[1]}($objElement$blnReturn);
  702.             }
  703.         }
  704.         return $blnReturn;
  705.     }
  706.     /**
  707.      * Replace insert tags with their values
  708.      *
  709.      * @param string  $strBuffer The text with the tags to be replaced
  710.      * @param boolean $blnCache  If false, non-cacheable tags will be replaced
  711.      *
  712.      * @return string The text with the replaced tags
  713.      */
  714.     public static function replaceInsertTags($strBuffer$blnCache=true)
  715.     {
  716.         $objIt = new InsertTags();
  717.         return $objIt->replace($strBuffer$blnCache);
  718.     }
  719.     /**
  720.      * Replace the dynamic script tags (see #4203)
  721.      *
  722.      * @param string $strBuffer The string with the tags to be replaced
  723.      *
  724.      * @return string The string with the replaced tags
  725.      */
  726.     public static function replaceDynamicScriptTags($strBuffer)
  727.     {
  728.         // HOOK: add custom logic
  729.         if (isset($GLOBALS['TL_HOOKS']['replaceDynamicScriptTags']) && \is_array($GLOBALS['TL_HOOKS']['replaceDynamicScriptTags']))
  730.         {
  731.             foreach ($GLOBALS['TL_HOOKS']['replaceDynamicScriptTags'] as $callback)
  732.             {
  733.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($strBuffer);
  734.             }
  735.         }
  736.         $arrReplace = array();
  737.         $strScripts '';
  738.         // Add the internal jQuery scripts
  739.         if (!empty($GLOBALS['TL_JQUERY']) && \is_array($GLOBALS['TL_JQUERY']))
  740.         {
  741.             foreach (array_unique($GLOBALS['TL_JQUERY']) as $script)
  742.             {
  743.                 $strScripts .= $script;
  744.             }
  745.         }
  746.         $arrReplace['[[TL_JQUERY]]'] = $strScripts;
  747.         $strScripts '';
  748.         // Add the internal MooTools scripts
  749.         if (!empty($GLOBALS['TL_MOOTOOLS']) && \is_array($GLOBALS['TL_MOOTOOLS']))
  750.         {
  751.             foreach (array_unique($GLOBALS['TL_MOOTOOLS']) as $script)
  752.             {
  753.                 $strScripts .= $script;
  754.             }
  755.         }
  756.         $arrReplace['[[TL_MOOTOOLS]]'] = $strScripts;
  757.         $strScripts '';
  758.         // Add the internal <body> tags
  759.         if (!empty($GLOBALS['TL_BODY']) && \is_array($GLOBALS['TL_BODY']))
  760.         {
  761.             foreach (array_unique($GLOBALS['TL_BODY']) as $script)
  762.             {
  763.                 $strScripts .= $script;
  764.             }
  765.         }
  766.         global $objPage;
  767.         $objLayout LayoutModel::findByPk($objPage->layoutId);
  768.         $blnCombineScripts = ($objLayout === null) ? false $objLayout->combineScripts;
  769.         $arrReplace['[[TL_BODY]]'] = $strScripts;
  770.         $strScripts '';
  771.         $objCombiner = new Combiner();
  772.         // Add the CSS framework style sheets
  773.         if (!empty($GLOBALS['TL_FRAMEWORK_CSS']) && \is_array($GLOBALS['TL_FRAMEWORK_CSS']))
  774.         {
  775.             foreach (array_unique($GLOBALS['TL_FRAMEWORK_CSS']) as $stylesheet)
  776.             {
  777.                 $objCombiner->add($stylesheet);
  778.             }
  779.         }
  780.         // Add the internal style sheets
  781.         if (!empty($GLOBALS['TL_CSS']) && \is_array($GLOBALS['TL_CSS']))
  782.         {
  783.             foreach (array_unique($GLOBALS['TL_CSS']) as $stylesheet)
  784.             {
  785.                 $options StringUtil::resolveFlaggedUrl($stylesheet);
  786.                 if ($options->static)
  787.                 {
  788.                     $objCombiner->add($stylesheet$options->mtime$options->media);
  789.                 }
  790.                 else
  791.                 {
  792.                     $strScripts .= Template::generateStyleTag(static::addAssetsUrlTo($stylesheet), $options->media$options->mtime);
  793.                 }
  794.             }
  795.         }
  796.         // Add the user style sheets
  797.         if (!empty($GLOBALS['TL_USER_CSS']) && \is_array($GLOBALS['TL_USER_CSS']))
  798.         {
  799.             foreach (array_unique($GLOBALS['TL_USER_CSS']) as $stylesheet)
  800.             {
  801.                 $options StringUtil::resolveFlaggedUrl($stylesheet);
  802.                 if ($options->static)
  803.                 {
  804.                     $objCombiner->add($stylesheet$options->mtime$options->media);
  805.                 }
  806.                 else
  807.                 {
  808.                     $strScripts .= Template::generateStyleTag(static::addAssetsUrlTo($stylesheet), $options->media$options->mtime);
  809.                 }
  810.             }
  811.         }
  812.         // Create the aggregated style sheet
  813.         if ($objCombiner->hasEntries())
  814.         {
  815.             if ($blnCombineScripts)
  816.             {
  817.                 $strScripts .= Template::generateStyleTag($objCombiner->getCombinedFile(), 'all');
  818.             }
  819.             else
  820.             {
  821.                 foreach ($objCombiner->getFileUrls() as $strUrl)
  822.                 {
  823.                     $options StringUtil::resolveFlaggedUrl($strUrl);
  824.                     $strScripts .= Template::generateStyleTag($strUrl$options->media$options->mtime);
  825.                 }
  826.             }
  827.         }
  828.         $arrReplace['[[TL_CSS]]'] = $strScripts;
  829.         $strScripts '';
  830.         // Add the internal scripts
  831.         if (!empty($GLOBALS['TL_JAVASCRIPT']) && \is_array($GLOBALS['TL_JAVASCRIPT']))
  832.         {
  833.             $objCombiner = new Combiner();
  834.             $objCombinerAsync = new Combiner();
  835.             foreach (array_unique($GLOBALS['TL_JAVASCRIPT']) as $javascript)
  836.             {
  837.                 $options StringUtil::resolveFlaggedUrl($javascript);
  838.                 if ($options->static)
  839.                 {
  840.                     $options->async $objCombinerAsync->add($javascript$options->mtime) : $objCombiner->add($javascript$options->mtime);
  841.                 }
  842.                 else
  843.                 {
  844.                     $strScripts .= Template::generateScriptTag(static::addAssetsUrlTo($javascript), $options->async$options->mtime);
  845.                 }
  846.             }
  847.             // Create the aggregated script and add it before the non-static scripts (see #4890)
  848.             if ($objCombiner->hasEntries())
  849.             {
  850.                 if ($blnCombineScripts)
  851.                 {
  852.                     $strScripts Template::generateScriptTag($objCombiner->getCombinedFile()) . $strScripts;
  853.                 }
  854.                 else
  855.                 {
  856.                     $arrReversed array_reverse($objCombiner->getFileUrls());
  857.                     foreach ($arrReversed as $strUrl)
  858.                     {
  859.                         $options StringUtil::resolveFlaggedUrl($strUrl);
  860.                         $strScripts Template::generateScriptTag($strUrlfalse$options->mtime) . $strScripts;
  861.                     }
  862.                 }
  863.             }
  864.             if ($objCombinerAsync->hasEntries())
  865.             {
  866.                 if ($blnCombineScripts)
  867.                 {
  868.                     $strScripts Template::generateScriptTag($objCombinerAsync->getCombinedFile(), true) . $strScripts;
  869.                 }
  870.                 else
  871.                 {
  872.                     $arrReversed array_reverse($objCombinerAsync->getFileUrls());
  873.                     foreach ($arrReversed as $strUrl)
  874.                     {
  875.                         $options StringUtil::resolveFlaggedUrl($strUrl);
  876.                         $strScripts Template::generateScriptTag($strUrltrue$options->mtime) . $strScripts;
  877.                     }
  878.                 }
  879.             }
  880.         }
  881.         // Add the internal <head> tags
  882.         if (!empty($GLOBALS['TL_HEAD']) && \is_array($GLOBALS['TL_HEAD']))
  883.         {
  884.             foreach (array_unique($GLOBALS['TL_HEAD']) as $head)
  885.             {
  886.                 $strScripts .= $head;
  887.             }
  888.         }
  889.         $arrReplace['[[TL_HEAD]]'] = $strScripts;
  890.         return str_replace(array_keys($arrReplace), $arrReplace$strBuffer);
  891.     }
  892.     /**
  893.      * Compile the margin format definition based on an array of values
  894.      *
  895.      * @param array  $arrValues An array of four values and a unit
  896.      * @param string $strType   Either "margin" or "padding"
  897.      *
  898.      * @return string The CSS markup
  899.      */
  900.     public static function generateMargin($arrValues$strType='margin')
  901.     {
  902.         // Initialize an empty array (see #5217)
  903.         if (!\is_array($arrValues))
  904.         {
  905.             $arrValues = array('top'=>'''right'=>'''bottom'=>'''left'=>'''unit'=>'');
  906.         }
  907.         $top $arrValues['top'];
  908.         $right $arrValues['right'];
  909.         $bottom $arrValues['bottom'];
  910.         $left $arrValues['left'];
  911.         // Try to shorten the definition
  912.         if ($top && $right  && $bottom  && $left)
  913.         {
  914.             if ($top == $right && $top == $bottom && $top == $left)
  915.             {
  916.                 return $strType ':' $top $arrValues['unit'] . ';';
  917.             }
  918.             if ($top == $bottom && $right == $left)
  919.             {
  920.                 return $strType ':' $top $arrValues['unit'] . ' ' $left $arrValues['unit'] . ';';
  921.             }
  922.             if ($top != $bottom && $right == $left)
  923.             {
  924.                 return $strType ':' $top $arrValues['unit'] . ' ' $right $arrValues['unit'] . ' ' $bottom $arrValues['unit'] . ';';
  925.             }
  926.             return $strType ':' $top $arrValues['unit'] . ' ' $right $arrValues['unit'] . ' ' $bottom $arrValues['unit'] . ' ' $left $arrValues['unit'] . ';';
  927.         }
  928.         $return = array();
  929.         $arrDir compact('top''right''bottom''left');
  930.         foreach ($arrDir as $k=>$v)
  931.         {
  932.             if ($v)
  933.             {
  934.                 $return[] = $strType '-' $k ':' $v $arrValues['unit'] . ';';
  935.             }
  936.         }
  937.         return implode(''$return);
  938.     }
  939.     /**
  940.      * Add a request string to the current URL
  941.      *
  942.      * @param string  $strRequest The string to be added
  943.      * @param boolean $blnAddRef  Add the referer ID
  944.      * @param array   $arrUnset   An optional array of keys to unset
  945.      *
  946.      * @return string The new URL
  947.      */
  948.     public static function addToUrl($strRequest$blnAddRef=true$arrUnset=array())
  949.     {
  950.         $pairs = array();
  951.         $request System::getContainer()->get('request_stack')->getCurrentRequest();
  952.         if ($request->server->has('QUERY_STRING'))
  953.         {
  954.             $cacheKey md5($request->server->get('QUERY_STRING'));
  955.             if (!isset(static::$arrQueryCache[$cacheKey]))
  956.             {
  957.                 parse_str($request->server->get('QUERY_STRING'), $pairs);
  958.                 ksort($pairs);
  959.                 static::$arrQueryCache[$cacheKey] = $pairs;
  960.             }
  961.             $pairs = static::$arrQueryCache[$cacheKey];
  962.         }
  963.         // Remove the request token and referer ID
  964.         unset($pairs['rt'], $pairs['ref']);
  965.         foreach ($arrUnset as $key)
  966.         {
  967.             unset($pairs[$key]);
  968.         }
  969.         // Merge the request string to be added
  970.         if ($strRequest)
  971.         {
  972.             parse_str(str_replace('&amp;''&'$strRequest), $newPairs);
  973.             $pairs array_merge($pairs$newPairs);
  974.         }
  975.         // Add the referer ID
  976.         if ($request->query->has('ref') || ($strRequest && $blnAddRef))
  977.         {
  978.             $pairs['ref'] = $request->attributes->get('_contao_referer_id');
  979.         }
  980.         $uri '';
  981.         if (!empty($pairs))
  982.         {
  983.             $uri '?' http_build_query($pairs'''&amp;'PHP_QUERY_RFC3986);
  984.         }
  985.         return TL_SCRIPT $uri;
  986.     }
  987.     /**
  988.      * Reload the current page
  989.      */
  990.     public static function reload()
  991.     {
  992.         static::redirect(Environment::get('uri'));
  993.     }
  994.     /**
  995.      * Redirect to another page
  996.      *
  997.      * @param string  $strLocation The target URL
  998.      * @param integer $intStatus   The HTTP status code (defaults to 303)
  999.      */
  1000.     public static function redirect($strLocation$intStatus=303)
  1001.     {
  1002.         $strLocation str_replace('&amp;''&'$strLocation);
  1003.         $strLocation = static::replaceOldBePaths($strLocation);
  1004.         // Make the location an absolute URL
  1005.         if (!preg_match('@^https?://@i'$strLocation))
  1006.         {
  1007.             $strLocation Environment::get('base') . ltrim($strLocation'/');
  1008.         }
  1009.         // Ajax request
  1010.         if (Environment::get('isAjaxRequest'))
  1011.         {
  1012.             throw new AjaxRedirectResponseException($strLocation);
  1013.         }
  1014.         throw new RedirectResponseException($strLocation$intStatus);
  1015.     }
  1016.     /**
  1017.      * Replace the old back end paths
  1018.      *
  1019.      * @param string $strContext The context
  1020.      *
  1021.      * @return string The modified context
  1022.      */
  1023.     protected static function replaceOldBePaths($strContext)
  1024.     {
  1025.         $arrCache = &self::$arrOldBePathCache;
  1026.         $arrMapper = array
  1027.         (
  1028.             'contao/confirm.php'   => 'contao_backend_confirm',
  1029.             'contao/file.php'      => 'contao_backend_file',
  1030.             'contao/help.php'      => 'contao_backend_help',
  1031.             'contao/index.php'     => 'contao_backend_login',
  1032.             'contao/main.php'      => 'contao_backend',
  1033.             'contao/page.php'      => 'contao_backend_page',
  1034.             'contao/password.php'  => 'contao_backend_password',
  1035.             'contao/popup.php'     => 'contao_backend_popup',
  1036.             'contao/preview.php'   => 'contao_backend_preview',
  1037.         );
  1038.         $replace = static function ($matches) use ($arrMapper, &$arrCache)
  1039.         {
  1040.             $key $matches[0];
  1041.             if (!isset($arrCache[$key]))
  1042.             {
  1043.                 $router System::getContainer()->get('router');
  1044.                 $arrCache[$key] = substr($router->generate($arrMapper[$key]), \strlen(Environment::get('path')) + 1);
  1045.             }
  1046.             return $arrCache[$key];
  1047.         };
  1048.         $regex '(' implode('|'array_map('preg_quote'array_keys($arrMapper))) . ')';
  1049.         return preg_replace_callback($regex$replace$strContext);
  1050.     }
  1051.     /**
  1052.      * Generate a front end URL
  1053.      *
  1054.      * @param array   $arrRow       An array of page parameters
  1055.      * @param string  $strParams    An optional string of URL parameters
  1056.      * @param string  $strForceLang Force a certain language
  1057.      * @param boolean $blnFixDomain Check the domain of the target page and append it if necessary
  1058.      *
  1059.      * @return string An URL that can be used in the front end
  1060.      *
  1061.      * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.0.
  1062.      *             Use the contao.routing.url_generator service or PageModel::getFrontendUrl() instead.
  1063.      */
  1064.     public static function generateFrontendUrl(array $arrRow$strParams=null$strForceLang=null$blnFixDomain=false)
  1065.     {
  1066.         @trigger_error('Using Controller::generateFrontendUrl() has been deprecated and will no longer work in Contao 5.0. Use the contao.routing.url_generator service or PageModel::getFrontendUrl() instead.'E_USER_DEPRECATED);
  1067.         if (!isset($arrRow['rootId']))
  1068.         {
  1069.             $row PageModel::findWithDetails($arrRow['id']);
  1070.             $arrRow['rootId'] = $row->rootId;
  1071.             foreach (array('domain''rootLanguage''rootUseSSL') as $key)
  1072.             {
  1073.                 if (!isset($arrRow[$key]))
  1074.                 {
  1075.                     $arrRow[$key] = $row->$key;
  1076.                 }
  1077.             }
  1078.         }
  1079.         $arrParams = array();
  1080.         // Set the language
  1081.         if ($strForceLang)
  1082.         {
  1083.             $arrParams['_locale'] = $strForceLang;
  1084.         }
  1085.         elseif (isset($arrRow['rootLanguage']))
  1086.         {
  1087.             $arrParams['_locale'] = $arrRow['rootLanguage'];
  1088.         }
  1089.         elseif (isset($arrRow['language']) && $arrRow['type'] == 'root')
  1090.         {
  1091.             $arrParams['_locale'] = $arrRow['language'];
  1092.         }
  1093.         elseif (TL_MODE == 'FE')
  1094.         {
  1095.             /** @var PageModel $objPage */
  1096.             global $objPage;
  1097.             $arrParams['_locale'] = $objPage->rootLanguage;
  1098.         }
  1099.         // Add the domain if it differs from the current one (see #3765 and #6927)
  1100.         if ($blnFixDomain)
  1101.         {
  1102.             $arrParams['_domain'] = $arrRow['domain'];
  1103.             $arrParams['_ssl'] = (bool) $arrRow['rootUseSSL'];
  1104.         }
  1105.         $objUrlGenerator System::getContainer()->get('contao.routing.url_generator');
  1106.         $strUrl $objUrlGenerator->generate(($arrRow['alias'] ?: $arrRow['id']) . $strParams$arrParams);
  1107.         // Remove path from absolute URLs
  1108.         if (=== strncmp($strUrl'/'1))
  1109.         {
  1110.             $strUrl substr($strUrl, \strlen(Environment::get('path')) + 1);
  1111.         }
  1112.         // Decode sprintf placeholders
  1113.         if (strpos($strParams'%') !== false)
  1114.         {
  1115.             $arrMatches = array();
  1116.             preg_match_all('/%([sducoxXbgGeEfF])/'$strParams$arrMatches);
  1117.             foreach (array_unique($arrMatches[1]) as $v)
  1118.             {
  1119.                 $strUrl str_replace('%25' $v'%' $v$strUrl);
  1120.             }
  1121.         }
  1122.         // HOOK: add custom logic
  1123.         if (isset($GLOBALS['TL_HOOKS']['generateFrontendUrl']) && \is_array($GLOBALS['TL_HOOKS']['generateFrontendUrl']))
  1124.         {
  1125.             foreach ($GLOBALS['TL_HOOKS']['generateFrontendUrl'] as $callback)
  1126.             {
  1127.                 $strUrl = static::importStatic($callback[0])->{$callback[1]}($arrRow$strParams$strUrl);
  1128.             }
  1129.         }
  1130.         return $strUrl;
  1131.     }
  1132.     /**
  1133.      * Convert relative URLs in href and src attributes to absolute URLs
  1134.      *
  1135.      * @param string  $strContent  The text with the URLs to be converted
  1136.      * @param string  $strBase     An optional base URL
  1137.      * @param boolean $blnHrefOnly If true, only href attributes will be converted
  1138.      *
  1139.      * @return string The text with the replaced URLs
  1140.      */
  1141.     public static function convertRelativeUrls($strContent$strBase=''$blnHrefOnly=false)
  1142.     {
  1143.         if (!$strBase)
  1144.         {
  1145.             $strBase Environment::get('base');
  1146.         }
  1147.         $search $blnHrefOnly 'href' 'href|src';
  1148.         $arrUrls preg_split('/((' $search ')="([^"]+)")/i'$strContent, -1PREG_SPLIT_DELIM_CAPTURE);
  1149.         $strContent '';
  1150.         for ($i=0$c=\count($arrUrls); $i<$c$i+=4)
  1151.         {
  1152.             $strContent .= $arrUrls[$i];
  1153.             if (!isset($arrUrls[$i+2]))
  1154.             {
  1155.                 continue;
  1156.             }
  1157.             $strAttribute $arrUrls[$i+2];
  1158.             $strUrl $arrUrls[$i+3];
  1159.             if (!preg_match('@^(?:[a-z0-9]+:|#)@i'$strUrl))
  1160.             {
  1161.                 $strUrl $strBase . (($strUrl != '/') ? $strUrl '');
  1162.             }
  1163.             $strContent .= $strAttribute '="' $strUrl '"';
  1164.         }
  1165.         return $strContent;
  1166.     }
  1167.     /**
  1168.      * Send a file to the browser so the "save as â€¦" dialogue opens
  1169.      *
  1170.      * @param string  $strFile The file path
  1171.      * @param boolean $inline  Show the file in the browser instead of opening the download dialog
  1172.      *
  1173.      * @throws AccessDeniedException
  1174.      */
  1175.     public static function sendFileToBrowser($strFile$inline=false)
  1176.     {
  1177.         // Make sure there are no attempts to hack the file system
  1178.         if (preg_match('@^\.+@'$strFile) || preg_match('@\.+/@'$strFile) || preg_match('@(://)+@'$strFile))
  1179.         {
  1180.             throw new PageNotFoundException('Invalid file name');
  1181.         }
  1182.         // Limit downloads to the files directory
  1183.         if (!preg_match('@^' preg_quote(Config::get('uploadPath'), '@') . '@i'$strFile))
  1184.         {
  1185.             throw new PageNotFoundException('Invalid path');
  1186.         }
  1187.         $projectDir System::getContainer()->getParameter('kernel.project_dir');
  1188.         // Check whether the file exists
  1189.         if (!file_exists($projectDir '/' $strFile))
  1190.         {
  1191.             throw new PageNotFoundException('File not found');
  1192.         }
  1193.         $objFile = new File($strFile);
  1194.         $arrAllowedTypes StringUtil::trimsplit(','strtolower(Config::get('allowedDownload')));
  1195.         // Check whether the file type is allowed to be downloaded
  1196.         if (!\in_array($objFile->extension$arrAllowedTypes))
  1197.         {
  1198.             throw new AccessDeniedException(sprintf('File type "%s" is not allowed'$objFile->extension));
  1199.         }
  1200.         // HOOK: post download callback
  1201.         if (isset($GLOBALS['TL_HOOKS']['postDownload']) && \is_array($GLOBALS['TL_HOOKS']['postDownload']))
  1202.         {
  1203.             foreach ($GLOBALS['TL_HOOKS']['postDownload'] as $callback)
  1204.             {
  1205.                 static::importStatic($callback[0])->{$callback[1]}($strFile);
  1206.             }
  1207.         }
  1208.         // Send the file (will stop the script execution)
  1209.         $objFile->sendToBrowser(''$inline);
  1210.     }
  1211.     /**
  1212.      * Load a set of DCA files
  1213.      *
  1214.      * @param string  $strTable   The table name
  1215.      * @param boolean $blnNoCache If true, the cache will be bypassed
  1216.      */
  1217.     public static function loadDataContainer($strTable$blnNoCache=false)
  1218.     {
  1219.         $loader = new DcaLoader($strTable);
  1220.         $loader->load($blnNoCache);
  1221.     }
  1222.     /**
  1223.      * Do not name this "reset" because it might result in conflicts with child classes
  1224.      * @see https://github.com/contao/contao/issues/4257
  1225.      *
  1226.      * @internal
  1227.      */
  1228.     public static function resetControllerCache()
  1229.     {
  1230.         self::$arrQueryCache = array();
  1231.         self::$arrOldBePathCache = array();
  1232.     }
  1233.     /**
  1234.      * Redirect to a front end page
  1235.      *
  1236.      * @param integer $intPage    The page ID
  1237.      * @param string  $strArticle An optional article alias
  1238.      * @param boolean $blnReturn  If true, return the URL and don't redirect
  1239.      *
  1240.      * @return string The URL of the target page
  1241.      */
  1242.     protected function redirectToFrontendPage($intPage$strArticle=null$blnReturn=false)
  1243.     {
  1244.         if (($intPage = (int) $intPage) <= 0)
  1245.         {
  1246.             return '';
  1247.         }
  1248.         $objPage PageModel::findWithDetails($intPage);
  1249.         if ($objPage === null)
  1250.         {
  1251.             return '';
  1252.         }
  1253.         $strParams null;
  1254.         // Add the /article/ fragment (see #673)
  1255.         if ($strArticle !== null && ($objArticle ArticleModel::findByAlias($strArticle)) !== null)
  1256.         {
  1257.             $strParams '/articles/' . (($objArticle->inColumn != 'main') ? $objArticle->inColumn ':' '') . $strArticle;
  1258.         }
  1259.         $strUrl $objPage->getPreviewUrl($strParams);
  1260.         if (!$blnReturn)
  1261.         {
  1262.             $this->redirect($strUrl);
  1263.         }
  1264.         return $strUrl;
  1265.     }
  1266.     /**
  1267.      * Get the parent records of an entry and return them as string which can
  1268.      * be used in a log message
  1269.      *
  1270.      * @param string  $strTable The table name
  1271.      * @param integer $intId    The record ID
  1272.      *
  1273.      * @return string A string that can be used in a log message
  1274.      */
  1275.     protected function getParentEntries($strTable$intId)
  1276.     {
  1277.         // No parent table
  1278.         if (empty($GLOBALS['TL_DCA'][$strTable]['config']['ptable']))
  1279.         {
  1280.             return '';
  1281.         }
  1282.         $arrParent = array();
  1283.         do
  1284.         {
  1285.             // Get the pid
  1286.             $objParent $this->Database->prepare("SELECT pid FROM " $strTable " WHERE id=?")
  1287.                                         ->limit(1)
  1288.                                         ->execute($intId);
  1289.             if ($objParent->numRows 1)
  1290.             {
  1291.                 break;
  1292.             }
  1293.             // Store the parent table information
  1294.             $strTable $GLOBALS['TL_DCA'][$strTable]['config']['ptable'];
  1295.             $intId $objParent->pid;
  1296.             // Add the log entry
  1297.             $arrParent[] = $strTable '.id=' $intId;
  1298.             // Load the data container of the parent table
  1299.             $this->loadDataContainer($strTable);
  1300.         }
  1301.         while ($intId && isset($GLOBALS['TL_DCA'][$strTable]['config']['ptable']));
  1302.         if (empty($arrParent))
  1303.         {
  1304.             return '';
  1305.         }
  1306.         return ' (parent records: ' implode(', '$arrParent) . ')';
  1307.     }
  1308.     /**
  1309.      * Take an array of file paths and eliminate the nested ones
  1310.      *
  1311.      * @param array $arrPaths The array of file paths
  1312.      *
  1313.      * @return array The file paths array without the nested paths
  1314.      */
  1315.     protected function eliminateNestedPaths($arrPaths)
  1316.     {
  1317.         $arrPaths array_filter($arrPaths);
  1318.         if (empty($arrPaths) || !\is_array($arrPaths))
  1319.         {
  1320.             return array();
  1321.         }
  1322.         $nested = array();
  1323.         foreach ($arrPaths as $path)
  1324.         {
  1325.             $nested[] = preg_grep('/^' preg_quote($path'/') . '\/.+/'$arrPaths);
  1326.         }
  1327.         if (!empty($nested))
  1328.         {
  1329.             $nested array_merge(...$nested);
  1330.         }
  1331.         return array_values(array_diff($arrPaths$nested));
  1332.     }
  1333.     /**
  1334.      * Take an array of pages and eliminate the nested ones
  1335.      *
  1336.      * @param array   $arrPages   The array of page IDs
  1337.      * @param string  $strTable   The table name
  1338.      * @param boolean $blnSorting True if the table has a sorting field
  1339.      *
  1340.      * @return array The page IDs array without the nested IDs
  1341.      */
  1342.     protected function eliminateNestedPages($arrPages$strTable=null$blnSorting=false)
  1343.     {
  1344.         if (empty($arrPages) || !\is_array($arrPages))
  1345.         {
  1346.             return array();
  1347.         }
  1348.         if (!$strTable)
  1349.         {
  1350.             $strTable 'tl_page';
  1351.         }
  1352.         // Thanks to Andreas Schempp (see #2475 and #3423)
  1353.         $arrPages array_intersect($arrPages$this->Database->getChildRecords(0$strTable$blnSorting));
  1354.         $arrPages array_values(array_diff($arrPages$this->Database->getChildRecords($arrPages$strTable$blnSorting)));
  1355.         return $arrPages;
  1356.     }
  1357.     /**
  1358.      * Add an image to a template
  1359.      *
  1360.      * @param object     $objTemplate   The template object to add the image to
  1361.      * @param array      $arrItem       The element or module as array
  1362.      * @param integer    $intMaxWidth   An optional maximum width of the image
  1363.      * @param string     $strLightboxId An optional lightbox ID
  1364.      * @param FilesModel $objModel      An optional files model
  1365.      */
  1366.     public static function addImageToTemplate($objTemplate$arrItem$intMaxWidth=null$strLightboxId=nullFilesModel $objModel=null)
  1367.     {
  1368.         try
  1369.         {
  1370.             $objFile = new File($arrItem['singleSRC']);
  1371.         }
  1372.         catch (\Exception $e)
  1373.         {
  1374.             $objFile null;
  1375.         }
  1376.         $imgSize $objFile->imageSize ?? array();
  1377.         $size StringUtil::deserialize($arrItem['size']);
  1378.         if (is_numeric($size))
  1379.         {
  1380.             $size = array(00, (int) $size);
  1381.         }
  1382.         elseif (!$size instanceof PictureConfiguration)
  1383.         {
  1384.             if (!\is_array($size))
  1385.             {
  1386.                 $size = array();
  1387.             }
  1388.             $size += array(00'crop');
  1389.         }
  1390.         if ($intMaxWidth === null)
  1391.         {
  1392.             $intMaxWidth Config::get('maxImageWidth');
  1393.         }
  1394.         $request System::getContainer()->get('request_stack')->getCurrentRequest();
  1395.         if ($request && System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest($request))
  1396.         {
  1397.             $arrMargin = array();
  1398.         }
  1399.         else
  1400.         {
  1401.             $arrMargin StringUtil::deserialize($arrItem['imagemargin']);
  1402.         }
  1403.         // Store the original dimensions
  1404.         $objTemplate->width $imgSize[0];
  1405.         $objTemplate->height $imgSize[1];
  1406.         // Adjust the image size
  1407.         if ($intMaxWidth 0)
  1408.         {
  1409.             @trigger_error('Using a maximum front end width has been deprecated and will no longer work in Contao 5.0. Remove the "maxImageWidth" configuration and use responsive images instead.'E_USER_DEPRECATED);
  1410.             // Subtract the margins before deciding whether to resize (see #6018)
  1411.             if (\is_array($arrMargin) && $arrMargin['unit'] == 'px')
  1412.             {
  1413.                 $intMargin = (int) $arrMargin['left'] + (int) $arrMargin['right'];
  1414.                 // Reset the margin if it exceeds the maximum width (see #7245)
  1415.                 if ($intMaxWidth $intMargin 1)
  1416.                 {
  1417.                     $arrMargin['left'] = '';
  1418.                     $arrMargin['right'] = '';
  1419.                 }
  1420.                 else
  1421.                 {
  1422.                     $intMaxWidth -= $intMargin;
  1423.                 }
  1424.             }
  1425.             if (\is_array($size) && ($size[0] > $intMaxWidth || (!$size[0] && !$size[1] && (!$imgSize[0] || $imgSize[0] > $intMaxWidth))))
  1426.             {
  1427.                 // See #2268 (thanks to Thyon)
  1428.                 $ratio = ($size[0] && $size[1]) ? $size[1] / $size[0] : (($imgSize[0] && $imgSize[1]) ? $imgSize[1] / $imgSize[0] : 0);
  1429.                 $size[0] = $intMaxWidth;
  1430.                 $size[1] = floor($intMaxWidth $ratio);
  1431.             }
  1432.         }
  1433.         $container System::getContainer();
  1434.         try
  1435.         {
  1436.             $projectDir $container->getParameter('kernel.project_dir');
  1437.             $staticUrl $container->get('contao.assets.files_context')->getStaticUrl();
  1438.             $picture $container->get('contao.image.picture_factory')->create($projectDir '/' $arrItem['singleSRC'], $size);
  1439.             $picture = array
  1440.             (
  1441.                 'img' => $picture->getImg($projectDir$staticUrl),
  1442.                 'sources' => $picture->getSources($projectDir$staticUrl)
  1443.             );
  1444.             $src $picture['img']['src'];
  1445.             if ($src !== $arrItem['singleSRC'])
  1446.             {
  1447.                 $objFile = new File(rawurldecode($src));
  1448.             }
  1449.         }
  1450.         catch (\Exception $e)
  1451.         {
  1452.             System::log('Image "' $arrItem['singleSRC'] . '" could not be processed: ' $e->getMessage(), __METHOD__TL_ERROR);
  1453.             $src '';
  1454.             $picture = array('img'=>array('src'=>'''srcset'=>''), 'sources'=>array());
  1455.         }
  1456.         // Image dimensions
  1457.         if ($objFile && isset($objFile->imageSize[0], $objFile->imageSize[1]))
  1458.         {
  1459.             $objTemplate->arrSize $objFile->imageSize;
  1460.             $objTemplate->imgSize ' width="' $objFile->imageSize[0] . '" height="' $objFile->imageSize[1] . '"';
  1461.         }
  1462.         $arrMeta = array();
  1463.         // Load the meta data
  1464.         if ($objModel instanceof FilesModel)
  1465.         {
  1466.             if (TL_MODE == 'FE')
  1467.             {
  1468.                 global $objPage;
  1469.                 $arrMeta Frontend::getMetaData($objModel->meta$objPage->language);
  1470.                 if (empty($arrMeta) && $objPage->rootFallbackLanguage !== null)
  1471.                 {
  1472.                     $arrMeta Frontend::getMetaData($objModel->meta$objPage->rootFallbackLanguage);
  1473.                 }
  1474.             }
  1475.             else
  1476.             {
  1477.                 $arrMeta Frontend::getMetaData($objModel->meta$GLOBALS['TL_LANGUAGE']);
  1478.             }
  1479.             self::loadDataContainer('tl_files');
  1480.             // Add any missing fields
  1481.             foreach (array_keys($GLOBALS['TL_DCA']['tl_files']['fields']['meta']['eval']['metaFields']) as $k)
  1482.             {
  1483.                 if (!isset($arrMeta[$k]))
  1484.                 {
  1485.                     $arrMeta[$k] = '';
  1486.                 }
  1487.             }
  1488.             $arrMeta['imageTitle'] = $arrMeta['title'];
  1489.             $arrMeta['imageUrl'] = $arrMeta['link'];
  1490.             unset($arrMeta['title'], $arrMeta['link']);
  1491.             // Add the meta data to the item
  1492.             if (!$arrItem['overwriteMeta'])
  1493.             {
  1494.                 foreach ($arrMeta as $k=>$v)
  1495.                 {
  1496.                     switch ($k)
  1497.                     {
  1498.                         case 'alt':
  1499.                         case 'imageTitle':
  1500.                             $arrItem[$k] = StringUtil::specialchars($v);
  1501.                             break;
  1502.                         default:
  1503.                             $arrItem[$k] = $v;
  1504.                             break;
  1505.                     }
  1506.                 }
  1507.             }
  1508.         }
  1509.         $picture['alt'] = StringUtil::specialchars($arrItem['alt']);
  1510.         // Move the title to the link tag so it is shown in the lightbox
  1511.         if ($arrItem['imageTitle'] && !$arrItem['linkTitle'] && ($arrItem['fullsize'] || $arrItem['imageUrl']))
  1512.         {
  1513.             $arrItem['linkTitle'] = $arrItem['imageTitle'];
  1514.             unset($arrItem['imageTitle']);
  1515.         }
  1516.         if (isset($arrItem['imageTitle']))
  1517.         {
  1518.             $picture['title'] = StringUtil::specialchars($arrItem['imageTitle']);
  1519.         }
  1520.         $objTemplate->picture $picture;
  1521.         // Provide an ID for single lightbox images in HTML5 (see #3742)
  1522.         if ($strLightboxId === null && $arrItem['fullsize'] && $objTemplate instanceof Template && !empty($arrItem['id']))
  1523.         {
  1524.             $strLightboxId substr(md5($objTemplate->getName() . '_' $arrItem['id']), 06);
  1525.         }
  1526.         // Float image
  1527.         if ($arrItem['floating'])
  1528.         {
  1529.             $objTemplate->floatClass ' float_' $arrItem['floating'];
  1530.         }
  1531.         // Do not override the "href" key (see #6468)
  1532.         $strHrefKey $objTemplate->href 'imageHref' 'href';
  1533.         $lightboxSize StringUtil::deserialize($arrItem['lightboxSize'] ?? nulltrue);
  1534.         if (!$lightboxSize && $arrItem['fullsize'] && isset($GLOBALS['objPage']->layoutId))
  1535.         {
  1536.             $lightboxSize StringUtil::deserialize(LayoutModel::findByPk($GLOBALS['objPage']->layoutId)->lightboxSize ?? nulltrue);
  1537.         }
  1538.         // Image link
  1539.         if (TL_MODE == 'FE' && $arrItem['imageUrl'])
  1540.         {
  1541.             $objTemplate->$strHrefKey $arrItem['imageUrl'];
  1542.             $objTemplate->attributes '';
  1543.             if ($arrItem['fullsize'])
  1544.             {
  1545.                 // Always replace insert tags (see #2674)
  1546.                 $imageUrl self::replaceInsertTags($arrItem['imageUrl']);
  1547.                 $blnIsExternal strncmp($imageUrl'http://'7) === || strncmp($imageUrl'https://'8) === 0;
  1548.                 // Open images in the lightbox
  1549.                 if (preg_match('/\.(' strtr(preg_quote(Config::get('validImageTypes'), '/'), ',''|') . ')$/i'$imageUrl))
  1550.                 {
  1551.                     // Do not add the TL_FILES_URL to external URLs (see #4923)
  1552.                     if (!$blnIsExternal)
  1553.                     {
  1554.                         try
  1555.                         {
  1556.                             $projectDir $container->getParameter('kernel.project_dir');
  1557.                             $staticUrl $container->get('contao.assets.files_context')->getStaticUrl();
  1558.                             // The image url is always an url encoded string and must be decoded beforehand (see #2674)
  1559.                             $picture $container->get('contao.image.picture_factory')->create($projectDir '/' urldecode($imageUrl), $lightboxSize);
  1560.                             $objTemplate->lightboxPicture = array
  1561.                             (
  1562.                                 'img' => $picture->getImg($projectDir$staticUrl),
  1563.                                 'sources' => $picture->getSources($projectDir$staticUrl)
  1564.                             );
  1565.                             $objTemplate->$strHrefKey $objTemplate->lightboxPicture['img']['src'];
  1566.                         }
  1567.                         catch (\Exception $e)
  1568.                         {
  1569.                             $objTemplate->$strHrefKey = static::addFilesUrlTo($imageUrl);
  1570.                             $objTemplate->lightboxPicture = array('img'=>array('src'=>$objTemplate->$strHrefKey'srcset'=>$objTemplate->$strHrefKey), 'sources'=>array());
  1571.                         }
  1572.                     }
  1573.                     $objTemplate->attributes ' data-lightbox="' $strLightboxId '"';
  1574.                 }
  1575.                 else
  1576.                 {
  1577.                     $objTemplate->attributes ' target="_blank"';
  1578.                     if ($blnIsExternal)
  1579.                     {
  1580.                         $objTemplate->attributes .= ' rel="noreferrer noopener"';
  1581.                     }
  1582.                 }
  1583.             }
  1584.         }
  1585.         // Fullsize view
  1586.         elseif (TL_MODE == 'FE' && $arrItem['fullsize'])
  1587.         {
  1588.             try
  1589.             {
  1590.                 $projectDir $container->getParameter('kernel.project_dir');
  1591.                 $staticUrl $container->get('contao.assets.files_context')->getStaticUrl();
  1592.                 $picture $container->get('contao.image.picture_factory')->create($projectDir '/' $arrItem['singleSRC'], $lightboxSize);
  1593.                 $objTemplate->lightboxPicture = array
  1594.                 (
  1595.                     'img' => $picture->getImg($projectDir$staticUrl),
  1596.                     'sources' => $picture->getSources($projectDir$staticUrl)
  1597.                 );
  1598.                 $objTemplate->$strHrefKey $objTemplate->lightboxPicture['img']['src'];
  1599.             }
  1600.             catch (\Exception $e)
  1601.             {
  1602.                 $objTemplate->$strHrefKey = static::addFilesUrlTo(System::urlEncode($arrItem['singleSRC']));
  1603.                 $objTemplate->lightboxPicture = array('img'=>array('src'=>$objTemplate->$strHrefKey'srcset'=>$objTemplate->$strHrefKey), 'sources'=>array());
  1604.             }
  1605.             $objTemplate->attributes ' data-lightbox="' $strLightboxId '"';
  1606.         }
  1607.         // Add the meta data to the template
  1608.         foreach (array_keys($arrMeta) as $k)
  1609.         {
  1610.             $objTemplate->$k $arrItem[$k];
  1611.         }
  1612.         // Do not urlEncode() here because getImage() already does (see #3817)
  1613.         $objTemplate->src = static::addFilesUrlTo($src);
  1614.         $objTemplate->singleSRC $arrItem['singleSRC'];
  1615.         $objTemplate->linkTitle StringUtil::specialchars($arrItem['linkTitle'] ?: $arrItem['title']);
  1616.         $objTemplate->fullsize $arrItem['fullsize'] ? true false;
  1617.         $objTemplate->addBefore = ($arrItem['floating'] != 'below');
  1618.         $objTemplate->margin = static::generateMargin($arrMargin);
  1619.         $objTemplate->addImage true;
  1620.     }
  1621.     /**
  1622.      * Add enclosures to a template
  1623.      *
  1624.      * @param object $objTemplate The template object to add the enclosures to
  1625.      * @param array  $arrItem     The element or module as array
  1626.      * @param string $strKey      The name of the enclosures field in $arrItem
  1627.      */
  1628.     public static function addEnclosuresToTemplate($objTemplate$arrItem$strKey='enclosure')
  1629.     {
  1630.         $arrEnclosures StringUtil::deserialize($arrItem[$strKey]);
  1631.         if (empty($arrEnclosures) || !\is_array($arrEnclosures))
  1632.         {
  1633.             return;
  1634.         }
  1635.         $objFiles FilesModel::findMultipleByUuids($arrEnclosures);
  1636.         if ($objFiles === null)
  1637.         {
  1638.             return;
  1639.         }
  1640.         $file Input::get('file'true);
  1641.         // Send the file to the browser and do not send a 404 header (see #5178)
  1642.         if ($file)
  1643.         {
  1644.             while ($objFiles->next())
  1645.             {
  1646.                 if ($file == $objFiles->path)
  1647.                 {
  1648.                     static::sendFileToBrowser($file);
  1649.                 }
  1650.             }
  1651.             $objFiles->reset();
  1652.         }
  1653.         /** @var PageModel $objPage */
  1654.         global $objPage;
  1655.         $arrEnclosures = array();
  1656.         $allowedDownload StringUtil::trimsplit(','strtolower(Config::get('allowedDownload')));
  1657.         // Add download links
  1658.         while ($objFiles->next())
  1659.         {
  1660.             if ($objFiles->type == 'file')
  1661.             {
  1662.                 $projectDir System::getContainer()->getParameter('kernel.project_dir');
  1663.                 if (!\in_array($objFiles->extension$allowedDownload) || !is_file($projectDir '/' $objFiles->path))
  1664.                 {
  1665.                     continue;
  1666.                 }
  1667.                 $objFile = new File($objFiles->path);
  1668.                 $strHref Environment::get('request');
  1669.                 // Remove an existing file parameter (see #5683)
  1670.                 if (preg_match('/(&(amp;)?|\?)file=/'$strHref))
  1671.                 {
  1672.                     $strHref preg_replace('/(&(amp;)?|\?)file=[^&]+/'''$strHref);
  1673.                 }
  1674.                 $strHref .= ((strpos($strHref'?') !== false) ? '&amp;' '?') . 'file=' System::urlEncode($objFiles->path);
  1675.                 $arrMeta Frontend::getMetaData($objFiles->meta$objPage->language);
  1676.                 if (empty($arrMeta) && $objPage->rootFallbackLanguage !== null)
  1677.                 {
  1678.                     $arrMeta Frontend::getMetaData($objFiles->meta$objPage->rootFallbackLanguage);
  1679.                 }
  1680.                 // Use the file name as title if none is given
  1681.                 if (!$arrMeta['title'])
  1682.                 {
  1683.                     $arrMeta['title'] = StringUtil::specialchars($objFile->basename);
  1684.                 }
  1685.                 $arrEnclosures[] = array
  1686.                 (
  1687.                     'id'        => $objFiles->id,
  1688.                     'uuid'      => $objFiles->uuid,
  1689.                     'name'      => $objFile->basename,
  1690.                     'title'     => StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['download'], $objFile->basename)),
  1691.                     'link'      => $arrMeta['title'],
  1692.                     'caption'   => $arrMeta['caption'],
  1693.                     'href'      => $strHref,
  1694.                     'filesize'  => static::getReadableSize($objFile->filesize),
  1695.                     'icon'      => Image::getPath($objFile->icon),
  1696.                     'mime'      => $objFile->mime,
  1697.                     'meta'      => $arrMeta,
  1698.                     'extension' => $objFile->extension,
  1699.                     'path'      => $objFile->dirname,
  1700.                     'enclosure' => $objFiles->path // backwards compatibility
  1701.                 );
  1702.             }
  1703.         }
  1704.         // Order the enclosures
  1705.         if (!empty($arrItem['orderEnclosure']))
  1706.         {
  1707.             $tmp StringUtil::deserialize($arrItem['orderEnclosure']);
  1708.             if (!empty($tmp) && \is_array($tmp))
  1709.             {
  1710.                 // Remove all values
  1711.                 $arrOrder array_map(static function () {}, array_flip($tmp));
  1712.                 // Move the matching elements to their position in $arrOrder
  1713.                 foreach ($arrEnclosures as $k=>$v)
  1714.                 {
  1715.                     if (\array_key_exists($v['uuid'], $arrOrder))
  1716.                     {
  1717.                         $arrOrder[$v['uuid']] = $v;
  1718.                         unset($arrEnclosures[$k]);
  1719.                     }
  1720.                 }
  1721.                 // Append the left-over enclosures at the end
  1722.                 if (!empty($arrEnclosures))
  1723.                 {
  1724.                     $arrOrder array_merge($arrOrderarray_values($arrEnclosures));
  1725.                 }
  1726.                 // Remove empty (unreplaced) entries
  1727.                 $arrEnclosures array_values(array_filter($arrOrder));
  1728.                 unset($arrOrder);
  1729.             }
  1730.         }
  1731.         $objTemplate->enclosure $arrEnclosures;
  1732.     }
  1733.     /**
  1734.      * Set the static URL constants
  1735.      */
  1736.     public static function setStaticUrls()
  1737.     {
  1738.         if (\defined('TL_FILES_URL'))
  1739.         {
  1740.             return;
  1741.         }
  1742.         if (\func_num_args() > 0)
  1743.         {
  1744.             @trigger_error('Using Controller::setStaticUrls() has been deprecated and will no longer work in Contao 5.0. Use the asset contexts instead.'E_USER_DEPRECATED);
  1745.             if (!isset($GLOBALS['objPage']))
  1746.             {
  1747.                 $GLOBALS['objPage'] = func_get_arg(0);
  1748.             }
  1749.         }
  1750.         \define('TL_ASSETS_URL'System::getContainer()->get('contao.assets.assets_context')->getStaticUrl());
  1751.         \define('TL_FILES_URL'System::getContainer()->get('contao.assets.files_context')->getStaticUrl());
  1752.         // Deprecated since Contao 4.0, to be removed in Contao 5.0
  1753.         \define('TL_SCRIPT_URL'TL_ASSETS_URL);
  1754.         \define('TL_PLUGINS_URL'TL_ASSETS_URL);
  1755.     }
  1756.     /**
  1757.      * Add a static URL to a script
  1758.      *
  1759.      * @param string             $script  The script path
  1760.      * @param ContaoContext|null $context
  1761.      *
  1762.      * @return string The script path with the static URL
  1763.      */
  1764.     public static function addStaticUrlTo($scriptContaoContext $context null)
  1765.     {
  1766.         // Absolute URLs
  1767.         if (preg_match('@^https?://@'$script))
  1768.         {
  1769.             return $script;
  1770.         }
  1771.         if ($context === null)
  1772.         {
  1773.             $context System::getContainer()->get('contao.assets.assets_context');
  1774.         }
  1775.         if ($strStaticUrl $context->getStaticUrl())
  1776.         {
  1777.             return $strStaticUrl $script;
  1778.         }
  1779.         return $script;
  1780.     }
  1781.     /**
  1782.      * Add the assets URL to a script
  1783.      *
  1784.      * @param string $script The script path
  1785.      *
  1786.      * @return string The script path with the assets URL
  1787.      */
  1788.     public static function addAssetsUrlTo($script)
  1789.     {
  1790.         return static::addStaticUrlTo($scriptSystem::getContainer()->get('contao.assets.assets_context'));
  1791.     }
  1792.     /**
  1793.      * Add the files URL to a script
  1794.      *
  1795.      * @param string $script The script path
  1796.      *
  1797.      * @return string The script path with the files URL
  1798.      */
  1799.     public static function addFilesUrlTo($script)
  1800.     {
  1801.         return static::addStaticUrlTo($scriptSystem::getContainer()->get('contao.assets.files_context'));
  1802.     }
  1803.     /**
  1804.      * Return the current theme as string
  1805.      *
  1806.      * @return string The name of the theme
  1807.      *
  1808.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1809.      *             Use Backend::getTheme() instead.
  1810.      */
  1811.     public static function getTheme()
  1812.     {
  1813.         @trigger_error('Using Controller::getTheme() has been deprecated and will no longer work in Contao 5.0. Use Backend::getTheme() instead.'E_USER_DEPRECATED);
  1814.         return Backend::getTheme();
  1815.     }
  1816.     /**
  1817.      * Return the back end themes as array
  1818.      *
  1819.      * @return array An array of available back end themes
  1820.      *
  1821.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1822.      *             Use Backend::getThemes() instead.
  1823.      */
  1824.     public static function getBackendThemes()
  1825.     {
  1826.         @trigger_error('Using Controller::getBackendThemes() has been deprecated and will no longer work in Contao 5.0. Use Backend::getThemes() instead.'E_USER_DEPRECATED);
  1827.         return Backend::getThemes();
  1828.     }
  1829.     /**
  1830.      * Get the details of a page including inherited parameters
  1831.      *
  1832.      * @param mixed $intId A page ID or a Model object
  1833.      *
  1834.      * @return PageModel The page model or null
  1835.      *
  1836.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1837.      *             Use PageModel::findWithDetails() or PageModel->loadDetails() instead.
  1838.      */
  1839.     public static function getPageDetails($intId)
  1840.     {
  1841.         @trigger_error('Using Controller::getPageDetails() has been deprecated and will no longer work in Contao 5.0. Use PageModel::findWithDetails() or PageModel->loadDetails() instead.'E_USER_DEPRECATED);
  1842.         if ($intId instanceof PageModel)
  1843.         {
  1844.             return $intId->loadDetails();
  1845.         }
  1846.         if ($intId instanceof Collection)
  1847.         {
  1848.             /** @var PageModel $objPage */
  1849.             $objPage $intId->current();
  1850.             return $objPage->loadDetails();
  1851.         }
  1852.         if (\is_object($intId))
  1853.         {
  1854.             $strKey __METHOD__ '-' $intId->id;
  1855.             // Try to load from cache
  1856.             if (Cache::has($strKey))
  1857.             {
  1858.                 return Cache::get($strKey);
  1859.             }
  1860.             // Create a model from the database result
  1861.             $objPage = new PageModel();
  1862.             $objPage->setRow($intId->row());
  1863.             $objPage->loadDetails();
  1864.             Cache::set($strKey$objPage);
  1865.             return $objPage;
  1866.         }
  1867.         // Invalid ID
  1868.         if ($intId || !\strlen($intId))
  1869.         {
  1870.             return null;
  1871.         }
  1872.         $strKey __METHOD__ '-' $intId;
  1873.         // Try to load from cache
  1874.         if (Cache::has($strKey))
  1875.         {
  1876.             return Cache::get($strKey);
  1877.         }
  1878.         $objPage PageModel::findWithDetails($intId);
  1879.         Cache::set($strKey$objPage);
  1880.         return $objPage;
  1881.     }
  1882.     /**
  1883.      * Remove old XML files from the share directory
  1884.      *
  1885.      * @param boolean $blnReturn If true, only return the finds and don't delete
  1886.      *
  1887.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1888.      *             Use Automator::purgeXmlFiles() instead.
  1889.      */
  1890.     protected function removeOldFeeds($blnReturn=false)
  1891.     {
  1892.         @trigger_error('Using Controller::removeOldFeeds() has been deprecated and will no longer work in Contao 5.0. Use Automator::purgeXmlFiles() instead.'E_USER_DEPRECATED);
  1893.         $this->import(Automator::class, 'Automator');
  1894.         $this->Automator->purgeXmlFiles($blnReturn);
  1895.     }
  1896.     /**
  1897.      * Return true if a class exists (tries to autoload the class)
  1898.      *
  1899.      * @param string $strClass The class name
  1900.      *
  1901.      * @return boolean True if the class exists
  1902.      *
  1903.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1904.      *             Use the PHP function class_exists() instead.
  1905.      */
  1906.     protected function classFileExists($strClass)
  1907.     {
  1908.         @trigger_error('Using Controller::classFileExists() has been deprecated and will no longer work in Contao 5.0. Use the PHP function class_exists() instead.'E_USER_DEPRECATED);
  1909.         return class_exists($strClass);
  1910.     }
  1911.     /**
  1912.      * Restore basic entities
  1913.      *
  1914.      * @param string $strBuffer The string with the tags to be replaced
  1915.      *
  1916.      * @return string The string with the original entities
  1917.      *
  1918.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1919.      *             Use StringUtil::restoreBasicEntities() instead.
  1920.      */
  1921.     public static function restoreBasicEntities($strBuffer)
  1922.     {
  1923.         @trigger_error('Using Controller::restoreBasicEntities() has been deprecated and will no longer work in Contao 5.0. Use StringUtil::restoreBasicEntities() instead.'E_USER_DEPRECATED);
  1924.         return StringUtil::restoreBasicEntities($strBuffer);
  1925.     }
  1926.     /**
  1927.      * Resize an image and crop it if necessary
  1928.      *
  1929.      * @param string  $image  The image path
  1930.      * @param integer $width  The target width
  1931.      * @param integer $height The target height
  1932.      * @param string  $mode   An optional resize mode
  1933.      *
  1934.      * @return boolean True if the image has been resized correctly
  1935.      *
  1936.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1937.      *             Use Image::resize() instead.
  1938.      */
  1939.     protected function resizeImage($image$width$height$mode='')
  1940.     {
  1941.         @trigger_error('Using Controller::resizeImage() has been deprecated and will no longer work in Contao 5.0. Use Image::resize() instead.'E_USER_DEPRECATED);
  1942.         return Image::resize($image$width$height$mode);
  1943.     }
  1944.     /**
  1945.      * Resize an image and crop it if necessary
  1946.      *
  1947.      * @param string  $image  The image path
  1948.      * @param integer $width  The target width
  1949.      * @param integer $height The target height
  1950.      * @param string  $mode   An optional resize mode
  1951.      * @param string  $target An optional target to be replaced
  1952.      * @param boolean $force  Override existing target images
  1953.      *
  1954.      * @return string|null The image path or null
  1955.      *
  1956.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1957.      *             Use Image::get() instead.
  1958.      */
  1959.     protected function getImage($image$width$height$mode=''$target=null$force=false)
  1960.     {
  1961.         @trigger_error('Using Controller::getImage() has been deprecated and will no longer work in Contao 5.0. Use Image::get() instead.'E_USER_DEPRECATED);
  1962.         return Image::get($image$width$height$mode$target$force);
  1963.     }
  1964.     /**
  1965.      * Generate an image tag and return it as string
  1966.      *
  1967.      * @param string $src        The image path
  1968.      * @param string $alt        An optional alt attribute
  1969.      * @param string $attributes A string of other attributes
  1970.      *
  1971.      * @return string The image HTML tag
  1972.      *
  1973.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1974.      *             Use Image::getHtml() instead.
  1975.      */
  1976.     public static function generateImage($src$alt=''$attributes='')
  1977.     {
  1978.         @trigger_error('Using Controller::generateImage() has been deprecated and will no longer work in Contao 5.0. Use Image::getHtml() instead.'E_USER_DEPRECATED);
  1979.         return Image::getHtml($src$alt$attributes);
  1980.     }
  1981.     /**
  1982.      * Return the date picker string (see #3218)
  1983.      *
  1984.      * @return boolean
  1985.      *
  1986.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1987.      *             Specify "datepicker"=>true in your DCA file instead.
  1988.      */
  1989.     protected function getDatePickerString()
  1990.     {
  1991.         @trigger_error('Using Controller::getDatePickerString() has been deprecated and will no longer work in Contao 5.0. Specify "datepicker"=>true in your DCA file instead.'E_USER_DEPRECATED);
  1992.         return true;
  1993.     }
  1994.     /**
  1995.      * Return the installed back end languages as array
  1996.      *
  1997.      * @return array An array of available back end languages
  1998.      *
  1999.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2000.      *             Use System::getLanguages(true) instead.
  2001.      */
  2002.     protected function getBackendLanguages()
  2003.     {
  2004.         @trigger_error('Using Controller::getBackendLanguages() has been deprecated and will no longer work in Contao 5.0. Use System::getLanguages(true) instead.'E_USER_DEPRECATED);
  2005.         return $this->getLanguages(true);
  2006.     }
  2007.     /**
  2008.      * Parse simple tokens that can be used to personalize newsletters
  2009.      *
  2010.      * @param string $strBuffer The text with the tokens to be replaced
  2011.      * @param array  $arrData   The replacement data as array
  2012.      *
  2013.      * @return string The text with the replaced tokens
  2014.      *
  2015.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2016.      *             Use StringUtil::parseSimpleTokens() instead.
  2017.      */
  2018.     protected function parseSimpleTokens($strBuffer$arrData)
  2019.     {
  2020.         @trigger_error('Using Controller::parseSimpleTokens() has been deprecated and will no longer work in Contao 5.0. Use StringUtil::parseSimpleTokens() instead.'E_USER_DEPRECATED);
  2021.         return StringUtil::parseSimpleTokens($strBuffer$arrData);
  2022.     }
  2023.     /**
  2024.      * Convert a DCA file configuration to be used with widgets
  2025.      *
  2026.      * @param array  $arrData  The field configuration array
  2027.      * @param string $strName  The field name in the form
  2028.      * @param mixed  $varValue The field value
  2029.      * @param string $strField The field name in the database
  2030.      * @param string $strTable The table name
  2031.      *
  2032.      * @return array An array that can be passed to a widget
  2033.      *
  2034.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2035.      *             Use Widget::getAttributesFromDca() instead.
  2036.      */
  2037.     protected function prepareForWidget($arrData$strName$varValue=null$strField=''$strTable='')
  2038.     {
  2039.         @trigger_error('Using Controller::prepareForWidget() has been deprecated and will no longer work in Contao 5.0. Use Widget::getAttributesFromDca() instead.'E_USER_DEPRECATED);
  2040.         return Widget::getAttributesFromDca($arrData$strName$varValue$strField$strTable);
  2041.     }
  2042.     /**
  2043.      * Return the IDs of all child records of a particular record (see #2475)
  2044.      *
  2045.      * @author Andreas Schempp
  2046.      *
  2047.      * @param mixed   $arrParentIds An array of parent IDs
  2048.      * @param string  $strTable     The table name
  2049.      * @param boolean $blnSorting   True if the table has a sorting field
  2050.      * @param array   $arrReturn    The array to be returned
  2051.      * @param string  $strWhere     Additional WHERE condition
  2052.      *
  2053.      * @return array An array of child record IDs
  2054.      *
  2055.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2056.      *             Use Database::getChildRecords() instead.
  2057.      */
  2058.     protected function getChildRecords($arrParentIds$strTable$blnSorting=false$arrReturn=array(), $strWhere='')
  2059.     {
  2060.         @trigger_error('Using Controller::getChildRecords() has been deprecated and will no longer work in Contao 5.0. Use Database::getChildRecords() instead.'E_USER_DEPRECATED);
  2061.         return $this->Database->getChildRecords($arrParentIds$strTable$blnSorting$arrReturn$strWhere);
  2062.     }
  2063.     /**
  2064.      * Return the IDs of all parent records of a particular record
  2065.      *
  2066.      * @param integer $intId    The ID of the record
  2067.      * @param string  $strTable The table name
  2068.      *
  2069.      * @return array An array of parent record IDs
  2070.      *
  2071.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2072.      *             Use Database::getParentRecords() instead.
  2073.      */
  2074.     protected function getParentRecords($intId$strTable)
  2075.     {
  2076.         @trigger_error('Using Controller::getParentRecords() has been deprecated and will no longer work in Contao 5.0. Use Database::getParentRecords() instead.'E_USER_DEPRECATED);
  2077.         return $this->Database->getParentRecords($intId$strTable);
  2078.     }
  2079.     /**
  2080.      * Print an article as PDF and stream it to the browser
  2081.      *
  2082.      * @param ModuleModel $objArticle An article object
  2083.      *
  2084.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2085.      *             Use ModuleArticle->generatePdf() instead.
  2086.      */
  2087.     protected function printArticleAsPdf($objArticle)
  2088.     {
  2089.         @trigger_error('Using Controller::printArticleAsPdf() has been deprecated and will no longer work in Contao 5.0. Use ModuleArticle->generatePdf() instead.'E_USER_DEPRECATED);
  2090.         $objArticle = new ModuleArticle($objArticle);
  2091.         $objArticle->generatePdf();
  2092.     }
  2093.     /**
  2094.      * Return all page sections as array
  2095.      *
  2096.      * @return array An array of active page sections
  2097.      *
  2098.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2099.      *             See https://github.com/contao/core/issues/4693.
  2100.      */
  2101.     public static function getPageSections()
  2102.     {
  2103.         @trigger_error('Using Controller::getPageSections() has been deprecated and will no longer work in Contao 5.0.'E_USER_DEPRECATED);
  2104.         return array('header''left''right''main''footer');
  2105.     }
  2106.     /**
  2107.      * Return a "selected" attribute if the option is selected
  2108.      *
  2109.      * @param string $strOption The option to check
  2110.      * @param mixed  $varValues One or more values to check against
  2111.      *
  2112.      * @return string The attribute or an empty string
  2113.      *
  2114.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2115.      *             Use Widget::optionSelected() instead.
  2116.      */
  2117.     public static function optionSelected($strOption$varValues)
  2118.     {
  2119.         @trigger_error('Using Controller::optionSelected() has been deprecated and will no longer work in Contao 5.0. Use Widget::optionSelected() instead.'E_USER_DEPRECATED);
  2120.         return Widget::optionSelected($strOption$varValues);
  2121.     }
  2122.     /**
  2123.      * Return a "checked" attribute if the option is checked
  2124.      *
  2125.      * @param string $strOption The option to check
  2126.      * @param mixed  $varValues One or more values to check against
  2127.      *
  2128.      * @return string The attribute or an empty string
  2129.      *
  2130.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2131.      *             Use Widget::optionChecked() instead.
  2132.      */
  2133.     public static function optionChecked($strOption$varValues)
  2134.     {
  2135.         @trigger_error('Using Controller::optionChecked() has been deprecated and will no longer work in Contao 5.0. Use Widget::optionChecked() instead.'E_USER_DEPRECATED);
  2136.         return Widget::optionChecked($strOption$varValues);
  2137.     }
  2138.     /**
  2139.      * Find a content element in the TL_CTE array and return the class name
  2140.      *
  2141.      * @param string $strName The content element name
  2142.      *
  2143.      * @return string The class name
  2144.      *
  2145.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2146.      *             Use ContentElement::findClass() instead.
  2147.      */
  2148.     public static function findContentElement($strName)
  2149.     {
  2150.         @trigger_error('Using Controller::findContentElement() has been deprecated and will no longer work in Contao 5.0. Use ContentElement::findClass() instead.'E_USER_DEPRECATED);
  2151.         return ContentElement::findClass($strName);
  2152.     }
  2153.     /**
  2154.      * Find a front end module in the FE_MOD array and return the class name
  2155.      *
  2156.      * @param string $strName The front end module name
  2157.      *
  2158.      * @return string The class name
  2159.      *
  2160.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2161.      *             Use Module::findClass() instead.
  2162.      */
  2163.     public static function findFrontendModule($strName)
  2164.     {
  2165.         @trigger_error('Using Controller::findFrontendModule() has been deprecated and will no longer work in Contao 5.0. Use Module::findClass() instead.'E_USER_DEPRECATED);
  2166.         return Module::findClass($strName);
  2167.     }
  2168.     /**
  2169.      * Create an initial version of a record
  2170.      *
  2171.      * @param string  $strTable The table name
  2172.      * @param integer $intId    The ID of the element to be versioned
  2173.      *
  2174.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2175.      *             Use Versions->initialize() instead.
  2176.      */
  2177.     protected function createInitialVersion($strTable$intId)
  2178.     {
  2179.         @trigger_error('Using Controller::createInitialVersion() has been deprecated and will no longer work in Contao 5.0. Use Versions->initialize() instead.'E_USER_DEPRECATED);
  2180.         $objVersions = new Versions($strTable$intId);
  2181.         $objVersions->initialize();
  2182.     }
  2183.     /**
  2184.      * Create a new version of a record
  2185.      *
  2186.      * @param string  $strTable The table name
  2187.      * @param integer $intId    The ID of the element to be versioned
  2188.      *
  2189.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2190.      *             Use Versions->create() instead.
  2191.      */
  2192.     protected function createNewVersion($strTable$intId)
  2193.     {
  2194.         @trigger_error('Using Controller::createNewVersion() has been deprecated and will no longer work in Contao 5.0. Use Versions->create() instead.'E_USER_DEPRECATED);
  2195.         $objVersions = new Versions($strTable$intId);
  2196.         $objVersions->create();
  2197.     }
  2198.     /**
  2199.      * Return the files matching a GLOB pattern
  2200.      *
  2201.      * @param string $pattern
  2202.      *
  2203.      * @return array|false
  2204.      */
  2205.     protected static function braceGlob($pattern)
  2206.     {
  2207.         // Use glob() if possible
  2208.         if (false === strpos($pattern'/**/') && (\defined('GLOB_BRACE') || false === strpos($pattern'{')))
  2209.         {
  2210.             return glob($pattern, \defined('GLOB_BRACE') ? GLOB_BRACE 0);
  2211.         }
  2212.         $finder = new Finder();
  2213.         $regex Glob::toRegex($pattern);
  2214.         // All files in the given template folder
  2215.         $filesIterator $finder
  2216.             ->files()
  2217.             ->followLinks()
  2218.             ->sortByName()
  2219.             ->in(\dirname($pattern))
  2220.         ;
  2221.         // Match the actual regex and filter the files
  2222.         $filesIterator $filesIterator->filter(static function (\SplFileInfo $info) use ($regex)
  2223.         {
  2224.             $path $info->getPathname();
  2225.             return preg_match($regex$path) && $info->isFile();
  2226.         });
  2227.         $files iterator_to_array($filesIterator);
  2228.         return array_keys($files);
  2229.     }
  2230. }
  2231. class_alias(Controller::class, 'Controller');