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

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\CoreBundle\File\Metadata;
  16. use Contao\CoreBundle\Image\Studio\FigureBuilder;
  17. use Contao\CoreBundle\Image\Studio\Studio;
  18. use Contao\CoreBundle\Monolog\ContaoContext as ContaoMonologContext;
  19. use Contao\CoreBundle\Security\ContaoCorePermissions;
  20. use Contao\CoreBundle\Util\LocaleUtil;
  21. use Contao\CoreBundle\Util\SimpleTokenParser;
  22. use Contao\Database\Result;
  23. use Contao\Image\PictureConfiguration;
  24. use Contao\Model\Collection;
  25. use Imagine\Image\BoxInterface;
  26. use Psr\Log\LogLevel;
  27. use Symfony\Cmf\Component\Routing\RouteObjectInterface;
  28. use Symfony\Component\Finder\Finder;
  29. use Symfony\Component\Finder\Glob;
  30. /**
  31.  * Abstract parent class for Controllers
  32.  *
  33.  * Some of the methods have been made static in Contao 3 and can be used in
  34.  * non-object context as well.
  35.  *
  36.  * Usage:
  37.  *
  38.  *     echo Controller::getTheme();
  39.  *
  40.  * Inside a controller:
  41.  *
  42.  *     public function generate()
  43.  *     {
  44.  *         return $this->getArticle(2);
  45.  *     }
  46.  *
  47.  * @author Leo Feyer <https://github.com/leofeyer>
  48.  */
  49. abstract class Controller extends System
  50. {
  51.     /**
  52.      * @var array
  53.      */
  54.     protected static $arrQueryCache = 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_deprecation('contao/core-bundle''4.9''Using hyphens in the template name "' $strTemplate '.html5" has been deprecated and will no longer work in Contao 5.0. Use snake_case instead.');
  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.                     return static::getArticle($objArticle);
  288.                 }
  289.             }
  290.             // HOOK: add custom logic
  291.             if (isset($GLOBALS['TL_HOOKS']['getArticles']) && \is_array($GLOBALS['TL_HOOKS']['getArticles']))
  292.             {
  293.                 foreach ($GLOBALS['TL_HOOKS']['getArticles'] as $callback)
  294.                 {
  295.                     $return = static::importStatic($callback[0])->{$callback[1]}($objPage->id$strColumn);
  296.                     if (\is_string($return))
  297.                     {
  298.                         return $return;
  299.                     }
  300.                 }
  301.             }
  302.             // Show all articles (no else block here, see #4740)
  303.             $objArticles ArticleModel::findPublishedByPidAndColumn($objPage->id$strColumn);
  304.             if ($objArticles === null)
  305.             {
  306.                 return '';
  307.             }
  308.             $return '';
  309.             $blnMultiMode = ($objArticles->count() > 1);
  310.             while ($objArticles->next())
  311.             {
  312.                 $return .= static::getArticle($objArticles->current(), $blnMultiModefalse$strColumn);
  313.             }
  314.             return $return;
  315.         }
  316.         // Other modules
  317.         if (\is_object($intId))
  318.         {
  319.             $objRow $intId;
  320.         }
  321.         else
  322.         {
  323.             $objRow ModuleModel::findByPk($intId);
  324.             if ($objRow === null)
  325.             {
  326.                 return '';
  327.             }
  328.         }
  329.         // Check the visibility (see #6311)
  330.         if (!static::isVisibleElement($objRow))
  331.         {
  332.             return '';
  333.         }
  334.         $strClass Module::findClass($objRow->type);
  335.         // Return if the class does not exist
  336.         if (!class_exists($strClass))
  337.         {
  338.             static::log('Module class "' $strClass '" (module "' $objRow->type '") does not exist'__METHOD__TL_ERROR);
  339.             return '';
  340.         }
  341.         $strStopWatchId 'contao.frontend_module.' $objRow->type ' (ID ' $objRow->id ')';
  342.         if (System::getContainer()->getParameter('kernel.debug'))
  343.         {
  344.             $objStopwatch System::getContainer()->get('debug.stopwatch');
  345.             $objStopwatch->start($strStopWatchId'contao.layout');
  346.         }
  347.         $objRow->typePrefix 'mod_';
  348.         /** @var Module $objModule */
  349.         $objModule = new $strClass($objRow$strColumn);
  350.         $strBuffer $objModule->generate();
  351.         // HOOK: add custom logic
  352.         if (isset($GLOBALS['TL_HOOKS']['getFrontendModule']) && \is_array($GLOBALS['TL_HOOKS']['getFrontendModule']))
  353.         {
  354.             foreach ($GLOBALS['TL_HOOKS']['getFrontendModule'] as $callback)
  355.             {
  356.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($objRow$strBuffer$objModule);
  357.             }
  358.         }
  359.         // Disable indexing if protected
  360.         if ($objModule->protected && !preg_match('/^\s*<!-- indexer::stop/'$strBuffer))
  361.         {
  362.             $strBuffer "\n<!-- indexer::stop -->" $strBuffer "<!-- indexer::continue -->\n";
  363.         }
  364.         if (isset($objStopwatch) && $objStopwatch->isStarted($strStopWatchId))
  365.         {
  366.             $objStopwatch->stop($strStopWatchId);
  367.         }
  368.         return $strBuffer;
  369.     }
  370.     /**
  371.      * Generate an article and return it as string
  372.      *
  373.      * @param mixed   $varId          The article ID or a Model object
  374.      * @param boolean $blnMultiMode   If true, only teasers will be shown
  375.      * @param boolean $blnIsInsertTag If true, there will be no page relation
  376.      * @param string  $strColumn      The name of the column
  377.      *
  378.      * @return string|boolean The article HTML markup or false
  379.      */
  380.     public static function getArticle($varId$blnMultiMode=false$blnIsInsertTag=false$strColumn='main')
  381.     {
  382.         /** @var PageModel $objPage */
  383.         global $objPage;
  384.         if (\is_object($varId))
  385.         {
  386.             $objRow $varId;
  387.         }
  388.         else
  389.         {
  390.             if (!$varId)
  391.             {
  392.                 return '';
  393.             }
  394.             $objRow ArticleModel::findByIdOrAliasAndPid($varId, (!$blnIsInsertTag $objPage->id null));
  395.             if ($objRow === null)
  396.             {
  397.                 return false;
  398.             }
  399.         }
  400.         // Check the visibility (see #6311)
  401.         if (!static::isVisibleElement($objRow))
  402.         {
  403.             return '';
  404.         }
  405.         // Print the article as PDF
  406.         if (isset($_GET['pdf']) && Input::get('pdf') == $objRow->id)
  407.         {
  408.             // Deprecated since Contao 4.0, to be removed in Contao 5.0
  409.             if ($objRow->printable == 1)
  410.             {
  411.                 trigger_deprecation('contao/core-bundle''4.0''Setting tl_article.printable to "1" has been deprecated and will no longer work in Contao 5.0.');
  412.                 $objArticle = new ModuleArticle($objRow);
  413.                 $objArticle->generatePdf();
  414.             }
  415.             elseif ($objRow->printable)
  416.             {
  417.                 $options StringUtil::deserialize($objRow->printable);
  418.                 if (\is_array($options) && \in_array('pdf'$options))
  419.                 {
  420.                     $objArticle = new ModuleArticle($objRow);
  421.                     $objArticle->generatePdf();
  422.                 }
  423.             }
  424.         }
  425.         $objRow->headline $objRow->title;
  426.         $objRow->multiMode $blnMultiMode;
  427.         // HOOK: add custom logic
  428.         if (isset($GLOBALS['TL_HOOKS']['getArticle']) && \is_array($GLOBALS['TL_HOOKS']['getArticle']))
  429.         {
  430.             foreach ($GLOBALS['TL_HOOKS']['getArticle'] as $callback)
  431.             {
  432.                 static::importStatic($callback[0])->{$callback[1]}($objRow);
  433.             }
  434.         }
  435.         $strStopWatchId 'contao.article (ID ' $objRow->id ')';
  436.         if (System::getContainer()->getParameter('kernel.debug'))
  437.         {
  438.             $objStopwatch System::getContainer()->get('debug.stopwatch');
  439.             $objStopwatch->start($strStopWatchId'contao.layout');
  440.         }
  441.         $objArticle = new ModuleArticle($objRow$strColumn);
  442.         $strBuffer $objArticle->generate($blnIsInsertTag);
  443.         // Disable indexing if protected
  444.         if ($objArticle->protected && !preg_match('/^\s*<!-- indexer::stop/'$strBuffer))
  445.         {
  446.             $strBuffer "\n<!-- indexer::stop -->" $strBuffer "<!-- indexer::continue -->\n";
  447.         }
  448.         if (isset($objStopwatch) && $objStopwatch->isStarted($strStopWatchId))
  449.         {
  450.             $objStopwatch->stop($strStopWatchId);
  451.         }
  452.         return $strBuffer;
  453.     }
  454.     /**
  455.      * Generate a content element and return it as string
  456.      *
  457.      * @param mixed  $intId     A content element ID or a Model object
  458.      * @param string $strColumn The column the element is in
  459.      *
  460.      * @return string The content element HTML markup
  461.      */
  462.     public static function getContentElement($intId$strColumn='main')
  463.     {
  464.         if (\is_object($intId))
  465.         {
  466.             $objRow $intId;
  467.         }
  468.         else
  469.         {
  470.             if ($intId || !\strlen($intId))
  471.             {
  472.                 return '';
  473.             }
  474.             $objRow ContentModel::findByPk($intId);
  475.             if ($objRow === null)
  476.             {
  477.                 return '';
  478.             }
  479.         }
  480.         // Check the visibility (see #6311)
  481.         if (!static::isVisibleElement($objRow))
  482.         {
  483.             return '';
  484.         }
  485.         $strClass ContentElement::findClass($objRow->type);
  486.         // Return if the class does not exist
  487.         if (!class_exists($strClass))
  488.         {
  489.             static::log('Content element class "' $strClass '" (content element "' $objRow->type '") does not exist'__METHOD__TL_ERROR);
  490.             return '';
  491.         }
  492.         $objRow->typePrefix 'ce_';
  493.         $strStopWatchId 'contao.content_element.' $objRow->type ' (ID ' $objRow->id ')';
  494.         if ($objRow->type != 'module' && System::getContainer()->getParameter('kernel.debug'))
  495.         {
  496.             $objStopwatch System::getContainer()->get('debug.stopwatch');
  497.             $objStopwatch->start($strStopWatchId'contao.layout');
  498.         }
  499.         /** @var ContentElement $objElement */
  500.         $objElement = new $strClass($objRow$strColumn);
  501.         $strBuffer $objElement->generate();
  502.         // HOOK: add custom logic
  503.         if (isset($GLOBALS['TL_HOOKS']['getContentElement']) && \is_array($GLOBALS['TL_HOOKS']['getContentElement']))
  504.         {
  505.             foreach ($GLOBALS['TL_HOOKS']['getContentElement'] as $callback)
  506.             {
  507.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($objRow$strBuffer$objElement);
  508.             }
  509.         }
  510.         // Disable indexing if protected
  511.         if ($objElement->protected && !preg_match('/^\s*<!-- indexer::stop/'$strBuffer))
  512.         {
  513.             $strBuffer "\n<!-- indexer::stop -->" $strBuffer "<!-- indexer::continue -->\n";
  514.         }
  515.         if (isset($objStopwatch) && $objStopwatch->isStarted($strStopWatchId))
  516.         {
  517.             $objStopwatch->stop($strStopWatchId);
  518.         }
  519.         return $strBuffer;
  520.     }
  521.     /**
  522.      * Generate a form and return it as string
  523.      *
  524.      * @param mixed   $varId     A form ID or a Model object
  525.      * @param string  $strColumn The column the form is in
  526.      * @param boolean $blnModule Render the form as module
  527.      *
  528.      * @return string The form HTML markup
  529.      */
  530.     public static function getForm($varId$strColumn='main'$blnModule=false)
  531.     {
  532.         if (\is_object($varId))
  533.         {
  534.             $objRow $varId;
  535.         }
  536.         else
  537.         {
  538.             if (!$varId)
  539.             {
  540.                 return '';
  541.             }
  542.             $objRow FormModel::findByIdOrAlias($varId);
  543.             if ($objRow === null)
  544.             {
  545.                 return '';
  546.             }
  547.         }
  548.         $strClass $blnModule Module::findClass('form') : ContentElement::findClass('form');
  549.         if (!class_exists($strClass))
  550.         {
  551.             static::log('Form class "' $strClass '" does not exist'__METHOD__TL_ERROR);
  552.             return '';
  553.         }
  554.         $objRow->typePrefix $blnModule 'mod_' 'ce_';
  555.         $objRow->form $objRow->id;
  556.         /** @var Form $objElement */
  557.         $objElement = new $strClass($objRow$strColumn);
  558.         $strBuffer $objElement->generate();
  559.         // HOOK: add custom logic
  560.         if (isset($GLOBALS['TL_HOOKS']['getForm']) && \is_array($GLOBALS['TL_HOOKS']['getForm']))
  561.         {
  562.             foreach ($GLOBALS['TL_HOOKS']['getForm'] as $callback)
  563.             {
  564.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($objRow$strBuffer$objElement);
  565.             }
  566.         }
  567.         return $strBuffer;
  568.     }
  569.     /**
  570.      * Return the languages for the TinyMCE spellchecker
  571.      *
  572.      * @return string The TinyMCE spellchecker language string
  573.      */
  574.     protected function getSpellcheckerString()
  575.     {
  576.         System::loadLanguageFile('languages');
  577.         $return = array();
  578.         $langs Folder::scan(__DIR__ '/../../languages');
  579.         array_unshift($langs$GLOBALS['TL_LANGUAGE']);
  580.         foreach ($langs as $lang)
  581.         {
  582.             $lang substr($lang02);
  583.             if (isset($GLOBALS['TL_LANG']['LNG'][$lang]))
  584.             {
  585.                 $return[$lang] = $GLOBALS['TL_LANG']['LNG'][$lang] . '=' $lang;
  586.             }
  587.         }
  588.         return '+' implode(','array_unique($return));
  589.     }
  590.     /**
  591.      * Calculate the page status icon name based on the page parameters
  592.      *
  593.      * @param PageModel|Result|\stdClass $objPage The page object
  594.      *
  595.      * @return string The status icon name
  596.      */
  597.     public static function getPageStatusIcon($objPage)
  598.     {
  599.         $sub 0;
  600.         $type = \in_array($objPage->type, array('regular''root''forward''redirect''error_401''error_403''error_404'), true) ? $objPage->type 'regular';
  601.         $image $type '.svg';
  602.         // Page not published or not active
  603.         if (!$objPage->published || ($objPage->start && $objPage->start time()) || ($objPage->stop && $objPage->stop <= time()))
  604.         {
  605.             ++$sub;
  606.         }
  607.         // Page hidden from menu
  608.         if ($objPage->hide && !\in_array($type, array('root''error_401''error_403''error_404')))
  609.         {
  610.             $sub += 2;
  611.         }
  612.         // Page protected
  613.         if ($objPage->protected && !\in_array($type, array('root''error_401''error_403''error_404')))
  614.         {
  615.             $sub += 4;
  616.         }
  617.         // Get the image name
  618.         if ($sub 0)
  619.         {
  620.             $image $type '_' $sub '.svg';
  621.         }
  622.         // HOOK: add custom logic
  623.         if (isset($GLOBALS['TL_HOOKS']['getPageStatusIcon']) && \is_array($GLOBALS['TL_HOOKS']['getPageStatusIcon']))
  624.         {
  625.             foreach ($GLOBALS['TL_HOOKS']['getPageStatusIcon'] as $callback)
  626.             {
  627.                 $image = static::importStatic($callback[0])->{$callback[1]}($objPage$image);
  628.             }
  629.         }
  630.         return $image;
  631.     }
  632.     /**
  633.      * Check whether an element is visible in the front end
  634.      *
  635.      * @param Model|ContentModel|ModuleModel $objElement The element model
  636.      *
  637.      * @return boolean True if the element is visible
  638.      */
  639.     public static function isVisibleElement(Model $objElement)
  640.     {
  641.         $blnReturn true;
  642.         // Only apply the restrictions in the front end
  643.         if (TL_MODE == 'FE')
  644.         {
  645.             $security System::getContainer()->get('security.helper');
  646.             if ($objElement->protected)
  647.             {
  648.                 $groups StringUtil::deserialize($objElement->groupstrue);
  649.                 $blnReturn $security->isGranted(ContaoCorePermissions::MEMBER_IN_GROUPS$groups);
  650.             }
  651.             elseif ($objElement->guests)
  652.             {
  653.                 trigger_deprecation('contao/core-bundle''4.12''Using the "show to guests only" feature has been deprecated an will no longer work in Contao 5.0. Use the "protect page" function instead.');
  654.                 $blnReturn = !$security->isGranted('ROLE_MEMBER'); // backwards compatibility
  655.             }
  656.         }
  657.         // HOOK: add custom logic
  658.         if (isset($GLOBALS['TL_HOOKS']['isVisibleElement']) && \is_array($GLOBALS['TL_HOOKS']['isVisibleElement']))
  659.         {
  660.             foreach ($GLOBALS['TL_HOOKS']['isVisibleElement'] as $callback)
  661.             {
  662.                 $blnReturn = static::importStatic($callback[0])->{$callback[1]}($objElement$blnReturn);
  663.             }
  664.         }
  665.         return $blnReturn;
  666.     }
  667.     /**
  668.      * Replace insert tags with their values
  669.      *
  670.      * @param string  $strBuffer The text with the tags to be replaced
  671.      * @param boolean $blnCache  If false, non-cacheable tags will be replaced
  672.      *
  673.      * @return string The text with the replaced tags
  674.      */
  675.     public static function replaceInsertTags($strBuffer$blnCache=true)
  676.     {
  677.         $objIt = new InsertTags();
  678.         return $objIt->replace($strBuffer$blnCache);
  679.     }
  680.     /**
  681.      * Replace the dynamic script tags (see #4203)
  682.      *
  683.      * @param string $strBuffer The string with the tags to be replaced
  684.      *
  685.      * @return string The string with the replaced tags
  686.      */
  687.     public static function replaceDynamicScriptTags($strBuffer)
  688.     {
  689.         // HOOK: add custom logic
  690.         if (isset($GLOBALS['TL_HOOKS']['replaceDynamicScriptTags']) && \is_array($GLOBALS['TL_HOOKS']['replaceDynamicScriptTags']))
  691.         {
  692.             foreach ($GLOBALS['TL_HOOKS']['replaceDynamicScriptTags'] as $callback)
  693.             {
  694.                 $strBuffer = static::importStatic($callback[0])->{$callback[1]}($strBuffer);
  695.             }
  696.         }
  697.         $arrReplace = array();
  698.         $strScripts '';
  699.         // Add the internal jQuery scripts
  700.         if (!empty($GLOBALS['TL_JQUERY']) && \is_array($GLOBALS['TL_JQUERY']))
  701.         {
  702.             foreach (array_unique($GLOBALS['TL_JQUERY']) as $script)
  703.             {
  704.                 $strScripts .= $script;
  705.             }
  706.         }
  707.         $arrReplace['[[TL_JQUERY]]'] = $strScripts;
  708.         $strScripts '';
  709.         // Add the internal MooTools scripts
  710.         if (!empty($GLOBALS['TL_MOOTOOLS']) && \is_array($GLOBALS['TL_MOOTOOLS']))
  711.         {
  712.             foreach (array_unique($GLOBALS['TL_MOOTOOLS']) as $script)
  713.             {
  714.                 $strScripts .= $script;
  715.             }
  716.         }
  717.         $arrReplace['[[TL_MOOTOOLS]]'] = $strScripts;
  718.         $strScripts '';
  719.         // Add the internal <body> tags
  720.         if (!empty($GLOBALS['TL_BODY']) && \is_array($GLOBALS['TL_BODY']))
  721.         {
  722.             foreach (array_unique($GLOBALS['TL_BODY']) as $script)
  723.             {
  724.                 $strScripts .= $script;
  725.             }
  726.         }
  727.         /** @var PageModel|null $objPage */
  728.         global $objPage;
  729.         $objLayout = ($objPage !== null) ? LayoutModel::findByPk($objPage->layoutId) : null;
  730.         $blnCombineScripts = ($objLayout === null) ? false $objLayout->combineScripts;
  731.         $arrReplace['[[TL_BODY]]'] = $strScripts;
  732.         $strScripts '';
  733.         $objCombiner = new Combiner();
  734.         // Add the CSS framework style sheets
  735.         if (!empty($GLOBALS['TL_FRAMEWORK_CSS']) && \is_array($GLOBALS['TL_FRAMEWORK_CSS']))
  736.         {
  737.             foreach (array_unique($GLOBALS['TL_FRAMEWORK_CSS']) as $stylesheet)
  738.             {
  739.                 $objCombiner->add($stylesheet);
  740.             }
  741.         }
  742.         // Add the internal style sheets
  743.         if (!empty($GLOBALS['TL_CSS']) && \is_array($GLOBALS['TL_CSS']))
  744.         {
  745.             foreach (array_unique($GLOBALS['TL_CSS']) as $stylesheet)
  746.             {
  747.                 $options StringUtil::resolveFlaggedUrl($stylesheet);
  748.                 if ($options->static)
  749.                 {
  750.                     $objCombiner->add($stylesheet$options->mtime$options->media);
  751.                 }
  752.                 else
  753.                 {
  754.                     $strScripts .= Template::generateStyleTag(static::addAssetsUrlTo($stylesheet), $options->media$options->mtime);
  755.                 }
  756.             }
  757.         }
  758.         // Add the user style sheets
  759.         if (!empty($GLOBALS['TL_USER_CSS']) && \is_array($GLOBALS['TL_USER_CSS']))
  760.         {
  761.             foreach (array_unique($GLOBALS['TL_USER_CSS']) as $stylesheet)
  762.             {
  763.                 $options StringUtil::resolveFlaggedUrl($stylesheet);
  764.                 if ($options->static)
  765.                 {
  766.                     $objCombiner->add($stylesheet$options->mtime$options->media);
  767.                 }
  768.                 else
  769.                 {
  770.                     $strScripts .= Template::generateStyleTag(static::addAssetsUrlTo($stylesheet), $options->media$options->mtime);
  771.                 }
  772.             }
  773.         }
  774.         // Create the aggregated style sheet
  775.         if ($objCombiner->hasEntries())
  776.         {
  777.             if ($blnCombineScripts)
  778.             {
  779.                 $strScripts .= Template::generateStyleTag($objCombiner->getCombinedFile(), 'all');
  780.             }
  781.             else
  782.             {
  783.                 foreach ($objCombiner->getFileUrls() as $strUrl)
  784.                 {
  785.                     $options StringUtil::resolveFlaggedUrl($strUrl);
  786.                     $strScripts .= Template::generateStyleTag($strUrl$options->media$options->mtime);
  787.                 }
  788.             }
  789.         }
  790.         $arrReplace['[[TL_CSS]]'] = $strScripts;
  791.         $strScripts '';
  792.         // Add the internal scripts
  793.         if (!empty($GLOBALS['TL_JAVASCRIPT']) && \is_array($GLOBALS['TL_JAVASCRIPT']))
  794.         {
  795.             $objCombiner = new Combiner();
  796.             $objCombinerAsync = new Combiner();
  797.             foreach (array_unique($GLOBALS['TL_JAVASCRIPT']) as $javascript)
  798.             {
  799.                 $options StringUtil::resolveFlaggedUrl($javascript);
  800.                 if ($options->static)
  801.                 {
  802.                     $options->async $objCombinerAsync->add($javascript$options->mtime) : $objCombiner->add($javascript$options->mtime);
  803.                 }
  804.                 else
  805.                 {
  806.                     $strScripts .= Template::generateScriptTag(static::addAssetsUrlTo($javascript), $options->async$options->mtime);
  807.                 }
  808.             }
  809.             // Create the aggregated script and add it before the non-static scripts (see #4890)
  810.             if ($objCombiner->hasEntries())
  811.             {
  812.                 if ($blnCombineScripts)
  813.                 {
  814.                     $strScripts Template::generateScriptTag($objCombiner->getCombinedFile()) . $strScripts;
  815.                 }
  816.                 else
  817.                 {
  818.                     $arrReversed array_reverse($objCombiner->getFileUrls());
  819.                     foreach ($arrReversed as $strUrl)
  820.                     {
  821.                         $options StringUtil::resolveFlaggedUrl($strUrl);
  822.                         $strScripts Template::generateScriptTag($strUrlfalse$options->mtime) . $strScripts;
  823.                     }
  824.                 }
  825.             }
  826.             if ($objCombinerAsync->hasEntries())
  827.             {
  828.                 if ($blnCombineScripts)
  829.                 {
  830.                     $strScripts Template::generateScriptTag($objCombinerAsync->getCombinedFile(), true) . $strScripts;
  831.                 }
  832.                 else
  833.                 {
  834.                     $arrReversed array_reverse($objCombinerAsync->getFileUrls());
  835.                     foreach ($arrReversed as $strUrl)
  836.                     {
  837.                         $options StringUtil::resolveFlaggedUrl($strUrl);
  838.                         $strScripts Template::generateScriptTag($strUrltrue$options->mtime) . $strScripts;
  839.                     }
  840.                 }
  841.             }
  842.         }
  843.         // Add the internal <head> tags
  844.         if (!empty($GLOBALS['TL_HEAD']) && \is_array($GLOBALS['TL_HEAD']))
  845.         {
  846.             foreach (array_unique($GLOBALS['TL_HEAD']) as $head)
  847.             {
  848.                 $strScripts .= $head;
  849.             }
  850.         }
  851.         $arrReplace['[[TL_HEAD]]'] = $strScripts;
  852.         return str_replace(array_keys($arrReplace), $arrReplace$strBuffer);
  853.     }
  854.     /**
  855.      * Compile the margin format definition based on an array of values
  856.      *
  857.      * @param array  $arrValues An array of four values and a unit
  858.      * @param string $strType   Either "margin" or "padding"
  859.      *
  860.      * @return string The CSS markup
  861.      */
  862.     public static function generateMargin($arrValues$strType='margin')
  863.     {
  864.         // Initialize an empty array (see #5217)
  865.         if (!\is_array($arrValues))
  866.         {
  867.             $arrValues = array('top'=>'''right'=>'''bottom'=>'''left'=>'''unit'=>'');
  868.         }
  869.         $top $arrValues['top'];
  870.         $right $arrValues['right'];
  871.         $bottom $arrValues['bottom'];
  872.         $left $arrValues['left'];
  873.         // Try to shorten the definition
  874.         if ($top && $right  && $bottom  && $left)
  875.         {
  876.             if ($top == $right && $top == $bottom && $top == $left)
  877.             {
  878.                 return $strType ':' $top $arrValues['unit'] . ';';
  879.             }
  880.             if ($top == $bottom && $right == $left)
  881.             {
  882.                 return $strType ':' $top $arrValues['unit'] . ' ' $left $arrValues['unit'] . ';';
  883.             }
  884.             if ($top != $bottom && $right == $left)
  885.             {
  886.                 return $strType ':' $top $arrValues['unit'] . ' ' $right $arrValues['unit'] . ' ' $bottom $arrValues['unit'] . ';';
  887.             }
  888.             return $strType ':' $top $arrValues['unit'] . ' ' $right $arrValues['unit'] . ' ' $bottom $arrValues['unit'] . ' ' $left $arrValues['unit'] . ';';
  889.         }
  890.         $return = array();
  891.         $arrDir compact('top''right''bottom''left');
  892.         foreach ($arrDir as $k=>$v)
  893.         {
  894.             if ($v)
  895.             {
  896.                 $return[] = $strType '-' $k ':' $v $arrValues['unit'] . ';';
  897.             }
  898.         }
  899.         return implode(''$return);
  900.     }
  901.     /**
  902.      * Add a request string to the current URL
  903.      *
  904.      * @param string  $strRequest The string to be added
  905.      * @param boolean $blnAddRef  Add the referer ID
  906.      * @param array   $arrUnset   An optional array of keys to unset
  907.      *
  908.      * @return string The new URL
  909.      */
  910.     public static function addToUrl($strRequest$blnAddRef=true$arrUnset=array())
  911.     {
  912.         $pairs = array();
  913.         $request System::getContainer()->get('request_stack')->getCurrentRequest();
  914.         if ($request->server->has('QUERY_STRING'))
  915.         {
  916.             $cacheKey md5($request->server->get('QUERY_STRING'));
  917.             if (!isset(static::$arrQueryCache[$cacheKey]))
  918.             {
  919.                 parse_str($request->server->get('QUERY_STRING'), $pairs);
  920.                 ksort($pairs);
  921.                 static::$arrQueryCache[$cacheKey] = $pairs;
  922.             }
  923.             $pairs = static::$arrQueryCache[$cacheKey];
  924.         }
  925.         // Remove the request token and referer ID
  926.         unset($pairs['rt'], $pairs['ref']);
  927.         foreach ($arrUnset as $key)
  928.         {
  929.             unset($pairs[$key]);
  930.         }
  931.         // Merge the request string to be added
  932.         if ($strRequest)
  933.         {
  934.             parse_str(str_replace('&amp;''&'$strRequest), $newPairs);
  935.             $pairs array_merge($pairs$newPairs);
  936.         }
  937.         // Add the referer ID
  938.         if ($request->query->has('ref') || ($strRequest && $blnAddRef))
  939.         {
  940.             $pairs['ref'] = $request->attributes->get('_contao_referer_id');
  941.         }
  942.         $uri '';
  943.         if (!empty($pairs))
  944.         {
  945.             $uri '?' http_build_query($pairs'''&amp;'PHP_QUERY_RFC3986);
  946.         }
  947.         return TL_SCRIPT $uri;
  948.     }
  949.     /**
  950.      * Reload the current page
  951.      */
  952.     public static function reload()
  953.     {
  954.         static::redirect(Environment::get('uri'));
  955.     }
  956.     /**
  957.      * Redirect to another page
  958.      *
  959.      * @param string  $strLocation The target URL
  960.      * @param integer $intStatus   The HTTP status code (defaults to 303)
  961.      */
  962.     public static function redirect($strLocation$intStatus=303)
  963.     {
  964.         $strLocation str_replace('&amp;''&'$strLocation);
  965.         $strLocation = static::replaceOldBePaths($strLocation);
  966.         // Make the location an absolute URL
  967.         if (!preg_match('@^https?://@i'$strLocation))
  968.         {
  969.             $strLocation Environment::get('base') . ltrim($strLocation'/');
  970.         }
  971.         // Ajax request
  972.         if (Environment::get('isAjaxRequest'))
  973.         {
  974.             throw new AjaxRedirectResponseException($strLocation);
  975.         }
  976.         throw new RedirectResponseException($strLocation$intStatus);
  977.     }
  978.     /**
  979.      * Replace the old back end paths
  980.      *
  981.      * @param string $strContext The context
  982.      *
  983.      * @return string The modified context
  984.      */
  985.     protected static function replaceOldBePaths($strContext)
  986.     {
  987.         $router System::getContainer()->get('router');
  988.         $generate = static function ($route) use ($router)
  989.         {
  990.             return substr($router->generate($route), \strlen(Environment::get('path')) + 1);
  991.         };
  992.         $arrMapper = array
  993.         (
  994.             'contao/confirm.php'   => $generate('contao_backend_confirm'),
  995.             'contao/file.php'      => $generate('contao_backend_file'),
  996.             'contao/help.php'      => $generate('contao_backend_help'),
  997.             'contao/index.php'     => $generate('contao_backend_login'),
  998.             'contao/main.php'      => $generate('contao_backend'),
  999.             'contao/page.php'      => $generate('contao_backend_page'),
  1000.             'contao/password.php'  => $generate('contao_backend_password'),
  1001.             'contao/popup.php'     => $generate('contao_backend_popup'),
  1002.             'contao/preview.php'   => $generate('contao_backend_preview'),
  1003.         );
  1004.         return str_replace(array_keys($arrMapper), $arrMapper$strContext);
  1005.     }
  1006.     /**
  1007.      * Generate a front end URL
  1008.      *
  1009.      * @param array   $arrRow       An array of page parameters
  1010.      * @param string  $strParams    An optional string of URL parameters
  1011.      * @param string  $strForceLang Force a certain language
  1012.      * @param boolean $blnFixDomain Check the domain of the target page and append it if necessary
  1013.      *
  1014.      * @return string An URL that can be used in the front end
  1015.      *
  1016.      * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.0.
  1017.      *             Use PageModel::getFrontendUrl() instead.
  1018.      */
  1019.     public static function generateFrontendUrl(array $arrRow$strParams=null$strForceLang=null$blnFixDomain=false)
  1020.     {
  1021.         trigger_deprecation('contao/core-bundle''4.2''Using "Contao\Controller::generateFrontendUrl()" has been deprecated and will no longer work in Contao 5.0. Use PageModel::getFrontendUrl() instead.');
  1022.         $page = new PageModel();
  1023.         $page->preventSaving(false);
  1024.         $page->setRow($arrRow);
  1025.         if (!isset($arrRow['rootId']))
  1026.         {
  1027.             $page->loadDetails();
  1028.             foreach (array('domain''rootLanguage''rootUseSSL') as $key)
  1029.             {
  1030.                 if (isset($arrRow[$key]))
  1031.                 {
  1032.                     $page->$key $arrRow[$key];
  1033.                 }
  1034.                 else
  1035.                 {
  1036.                     $arrRow[$key] = $page->$key;
  1037.                 }
  1038.             }
  1039.         }
  1040.         // Set the language
  1041.         if ($strForceLang !== null)
  1042.         {
  1043.             $strForceLang LocaleUtil::formatAsLocale($strForceLang);
  1044.             $page->language $strForceLang;
  1045.             $page->rootLanguage $strForceLang;
  1046.             if (System::getContainer()->getParameter('contao.legacy_routing'))
  1047.             {
  1048.                 $page->urlPrefix System::getContainer()->getParameter('contao.prepend_locale') ? $strForceLang '';
  1049.             }
  1050.         }
  1051.         // Add the domain if it differs from the current one (see #3765 and #6927)
  1052.         if ($blnFixDomain)
  1053.         {
  1054.             $page->domain $arrRow['domain'];
  1055.             $page->rootUseSSL = (bool) $arrRow['rootUseSSL'];
  1056.         }
  1057.         $objRouter System::getContainer()->get('router');
  1058.         $strUrl $objRouter->generate(RouteObjectInterface::OBJECT_BASED_ROUTE_NAME, array(RouteObjectInterface::CONTENT_OBJECT => $page'parameters' => $strParams));
  1059.         // Remove path from absolute URLs
  1060.         if (=== strncmp($strUrl'/'1) && !== strncmp($strUrl'//'2))
  1061.         {
  1062.             $strUrl substr($strUrl, \strlen(Environment::get('path')) + 1);
  1063.         }
  1064.         // Decode sprintf placeholders
  1065.         if (strpos($strParams'%') !== false)
  1066.         {
  1067.             $arrMatches = array();
  1068.             preg_match_all('/%([sducoxXbgGeEfF])/'$strParams$arrMatches);
  1069.             foreach (array_unique($arrMatches[1]) as $v)
  1070.             {
  1071.                 $strUrl str_replace('%25' $v'%' $v$strUrl);
  1072.             }
  1073.         }
  1074.         // HOOK: add custom logic
  1075.         if (isset($GLOBALS['TL_HOOKS']['generateFrontendUrl']) && \is_array($GLOBALS['TL_HOOKS']['generateFrontendUrl']))
  1076.         {
  1077.             foreach ($GLOBALS['TL_HOOKS']['generateFrontendUrl'] as $callback)
  1078.             {
  1079.                 $strUrl = static::importStatic($callback[0])->{$callback[1]}($arrRow$strParams$strUrl);
  1080.             }
  1081.         }
  1082.         return $strUrl;
  1083.     }
  1084.     /**
  1085.      * Convert relative URLs in href and src attributes to absolute URLs
  1086.      *
  1087.      * @param string  $strContent  The text with the URLs to be converted
  1088.      * @param string  $strBase     An optional base URL
  1089.      * @param boolean $blnHrefOnly If true, only href attributes will be converted
  1090.      *
  1091.      * @return string The text with the replaced URLs
  1092.      */
  1093.     public static function convertRelativeUrls($strContent$strBase=''$blnHrefOnly=false)
  1094.     {
  1095.         if (!$strBase)
  1096.         {
  1097.             $strBase Environment::get('base');
  1098.         }
  1099.         $search $blnHrefOnly 'href' 'href|src';
  1100.         $arrUrls preg_split('/((' $search ')="([^"]+)")/i'$strContent, -1PREG_SPLIT_DELIM_CAPTURE);
  1101.         $strContent '';
  1102.         for ($i=0$c=\count($arrUrls); $i<$c$i+=4)
  1103.         {
  1104.             $strContent .= $arrUrls[$i];
  1105.             if (!isset($arrUrls[$i+2]))
  1106.             {
  1107.                 continue;
  1108.             }
  1109.             $strAttribute $arrUrls[$i+2];
  1110.             $strUrl $arrUrls[$i+3];
  1111.             if (!preg_match('@^(?:[a-z0-9]+:|#)@i'$strUrl))
  1112.             {
  1113.                 $strUrl $strBase . (($strUrl != '/') ? $strUrl '');
  1114.             }
  1115.             $strContent .= $strAttribute '="' $strUrl '"';
  1116.         }
  1117.         return $strContent;
  1118.     }
  1119.     /**
  1120.      * Send a file to the browser so the "save as â€¦" dialogue opens
  1121.      *
  1122.      * @param string  $strFile The file path
  1123.      * @param boolean $inline  Show the file in the browser instead of opening the download dialog
  1124.      *
  1125.      * @throws AccessDeniedException
  1126.      */
  1127.     public static function sendFileToBrowser($strFile$inline=false)
  1128.     {
  1129.         // Make sure there are no attempts to hack the file system
  1130.         if (preg_match('@^\.+@'$strFile) || preg_match('@\.+/@'$strFile) || preg_match('@(://)+@'$strFile))
  1131.         {
  1132.             throw new PageNotFoundException('Invalid file name');
  1133.         }
  1134.         // Limit downloads to the files directory
  1135.         if (!preg_match('@^' preg_quote(System::getContainer()->getParameter('contao.upload_path'), '@') . '@i'$strFile))
  1136.         {
  1137.             throw new PageNotFoundException('Invalid path');
  1138.         }
  1139.         $projectDir System::getContainer()->getParameter('kernel.project_dir');
  1140.         // Check whether the file exists
  1141.         if (!file_exists($projectDir '/' $strFile))
  1142.         {
  1143.             throw new PageNotFoundException('File not found');
  1144.         }
  1145.         $objFile = new File($strFile);
  1146.         $arrAllowedTypes StringUtil::trimsplit(','strtolower(Config::get('allowedDownload')));
  1147.         // Check whether the file type is allowed to be downloaded
  1148.         if (!\in_array($objFile->extension$arrAllowedTypes))
  1149.         {
  1150.             throw new AccessDeniedException(sprintf('File type "%s" is not allowed'$objFile->extension));
  1151.         }
  1152.         // HOOK: post download callback
  1153.         if (isset($GLOBALS['TL_HOOKS']['postDownload']) && \is_array($GLOBALS['TL_HOOKS']['postDownload']))
  1154.         {
  1155.             foreach ($GLOBALS['TL_HOOKS']['postDownload'] as $callback)
  1156.             {
  1157.                 static::importStatic($callback[0])->{$callback[1]}($strFile);
  1158.             }
  1159.         }
  1160.         // Send the file (will stop the script execution)
  1161.         $objFile->sendToBrowser(''$inline);
  1162.     }
  1163.     /**
  1164.      * Load a set of DCA files
  1165.      *
  1166.      * @param string  $strTable   The table name
  1167.      * @param boolean $blnNoCache If true, the cache will be bypassed
  1168.      */
  1169.     public static function loadDataContainer($strTable$blnNoCache=false)
  1170.     {
  1171.         $loader = new DcaLoader($strTable);
  1172.         $loader->load($blnNoCache);
  1173.     }
  1174.     /**
  1175.      * Redirect to a front end page
  1176.      *
  1177.      * @param integer $intPage    The page ID
  1178.      * @param string  $strArticle An optional article alias
  1179.      * @param boolean $blnReturn  If true, return the URL and don't redirect
  1180.      *
  1181.      * @return string The URL of the target page
  1182.      */
  1183.     protected function redirectToFrontendPage($intPage$strArticle=null$blnReturn=false)
  1184.     {
  1185.         if (($intPage = (int) $intPage) <= 0)
  1186.         {
  1187.             return '';
  1188.         }
  1189.         $objPage PageModel::findWithDetails($intPage);
  1190.         if ($objPage === null)
  1191.         {
  1192.             return '';
  1193.         }
  1194.         $strParams null;
  1195.         // Add the /article/ fragment (see #673)
  1196.         if ($strArticle !== null && ($objArticle ArticleModel::findByAlias($strArticle)) !== null)
  1197.         {
  1198.             $strParams '/articles/' . (($objArticle->inColumn != 'main') ? $objArticle->inColumn ':' '') . $strArticle;
  1199.         }
  1200.         $strUrl $objPage->getPreviewUrl($strParams);
  1201.         if (!$blnReturn)
  1202.         {
  1203.             $this->redirect($strUrl);
  1204.         }
  1205.         return $strUrl;
  1206.     }
  1207.     /**
  1208.      * Get the parent records of an entry and return them as string which can
  1209.      * be used in a log message
  1210.      *
  1211.      * @param string  $strTable The table name
  1212.      * @param integer $intId    The record ID
  1213.      *
  1214.      * @return string A string that can be used in a log message
  1215.      */
  1216.     protected function getParentEntries($strTable$intId)
  1217.     {
  1218.         // No parent table
  1219.         if (empty($GLOBALS['TL_DCA'][$strTable]['config']['ptable']))
  1220.         {
  1221.             return '';
  1222.         }
  1223.         $arrParent = array();
  1224.         do
  1225.         {
  1226.             // Get the pid
  1227.             $objParent $this->Database->prepare("SELECT pid FROM " $strTable " WHERE id=?")
  1228.                                         ->limit(1)
  1229.                                         ->execute($intId);
  1230.             if ($objParent->numRows 1)
  1231.             {
  1232.                 break;
  1233.             }
  1234.             // Store the parent table information
  1235.             $strTable $GLOBALS['TL_DCA'][$strTable]['config']['ptable'];
  1236.             $intId $objParent->pid;
  1237.             // Add the log entry
  1238.             $arrParent[] = $strTable '.id=' $intId;
  1239.             // Load the data container of the parent table
  1240.             $this->loadDataContainer($strTable);
  1241.         } while ($intId && isset($GLOBALS['TL_DCA'][$strTable]['config']['ptable']));
  1242.         if (empty($arrParent))
  1243.         {
  1244.             return '';
  1245.         }
  1246.         return ' (parent records: ' implode(', '$arrParent) . ')';
  1247.     }
  1248.     /**
  1249.      * Take an array of file paths and eliminate the nested ones
  1250.      *
  1251.      * @param array $arrPaths The array of file paths
  1252.      *
  1253.      * @return array The file paths array without the nested paths
  1254.      */
  1255.     protected function eliminateNestedPaths($arrPaths)
  1256.     {
  1257.         $arrPaths array_filter($arrPaths);
  1258.         if (empty($arrPaths) || !\is_array($arrPaths))
  1259.         {
  1260.             return array();
  1261.         }
  1262.         $nested = array();
  1263.         foreach ($arrPaths as $path)
  1264.         {
  1265.             $nested[] = preg_grep('/^' preg_quote($path'/') . '\/.+/'$arrPaths);
  1266.         }
  1267.         if (!empty($nested))
  1268.         {
  1269.             $nested array_merge(...$nested);
  1270.         }
  1271.         return array_values(array_diff($arrPaths$nested));
  1272.     }
  1273.     /**
  1274.      * Take an array of pages and eliminate the nested ones
  1275.      *
  1276.      * @param array   $arrPages   The array of page IDs
  1277.      * @param string  $strTable   The table name
  1278.      * @param boolean $blnSorting True if the table has a sorting field
  1279.      *
  1280.      * @return array The page IDs array without the nested IDs
  1281.      */
  1282.     protected function eliminateNestedPages($arrPages$strTable=null$blnSorting=false)
  1283.     {
  1284.         if (empty($arrPages) || !\is_array($arrPages))
  1285.         {
  1286.             return array();
  1287.         }
  1288.         if (!$strTable)
  1289.         {
  1290.             $strTable 'tl_page';
  1291.         }
  1292.         // Thanks to Andreas Schempp (see #2475 and #3423)
  1293.         $arrPages array_intersect($arrPages$this->Database->getChildRecords(0$strTable$blnSorting));
  1294.         $arrPages array_values(array_diff($arrPages$this->Database->getChildRecords($arrPages$strTable$blnSorting)));
  1295.         return $arrPages;
  1296.     }
  1297.     /**
  1298.      * Add an image to a template
  1299.      *
  1300.      * @param object          $template                The template object to add the image to
  1301.      * @param array           $rowData                 The element or module as array
  1302.      * @param integer|null    $maxWidth                An optional maximum width of the image
  1303.      * @param string|null     $lightboxGroupIdentifier An optional lightbox group identifier
  1304.      * @param FilesModel|null $filesModel              An optional files model
  1305.      *
  1306.      * @deprecated Deprecated since Contao 4.11, to be removed in Contao 5.0;
  1307.      *             use the Contao\CoreBundle\Image\Studio\FigureBuilder instead.
  1308.      */
  1309.     public static function addImageToTemplate($template, array $rowData$maxWidth null$lightboxGroupIdentifier nullFilesModel $filesModel null): void
  1310.     {
  1311.         trigger_deprecation('contao/core-bundle''4.11''Using Controller::addImageToTemplate() is deprecated and will no longer work in Contao 5.0. Use the "Contao\CoreBundle\Image\Studio\FigureBuilder" class instead.');
  1312.         // Helper: Create metadata from the specified row data
  1313.         $createMetadataOverwriteFromRowData = static function (bool $interpretAsContentModel) use ($rowData)
  1314.         {
  1315.             if ($interpretAsContentModel)
  1316.             {
  1317.                 // This will be null if "overwriteMeta" is not set
  1318.                 return (new ContentModel())->setRow($rowData)->getOverwriteMetadata();
  1319.             }
  1320.             // Manually create metadata that always contains certain properties (BC)
  1321.             return new Metadata(array(
  1322.                 Metadata::VALUE_ALT => $rowData['alt'] ?? '',
  1323.                 Metadata::VALUE_TITLE => $rowData['imageTitle'] ?? '',
  1324.                 Metadata::VALUE_URL => self::replaceInsertTags($rowData['imageUrl'] ?? ''),
  1325.                 'linkTitle' => (string) ($rowData['linkTitle'] ?? ''),
  1326.             ));
  1327.         };
  1328.         // Helper: Create fallback template data with (mostly) empty fields (used if resource acquisition fails)
  1329.         $createFallBackTemplateData = static function () use ($filesModel$rowData)
  1330.         {
  1331.             $templateData = array(
  1332.                 'width' => null,
  1333.                 'height' => null,
  1334.                 'picture' => array(
  1335.                     'img' => array(
  1336.                         'src' => '',
  1337.                         'srcset' => '',
  1338.                     ),
  1339.                     'sources' => array(),
  1340.                     'alt' => '',
  1341.                     'title' => '',
  1342.                 ),
  1343.                 'singleSRC' => $rowData['singleSRC'],
  1344.                 'src' => '',
  1345.                 'linkTitle' => '',
  1346.                 'margin' => '',
  1347.                 'addImage' => true,
  1348.                 'addBefore' => true,
  1349.                 'fullsize' => false,
  1350.             );
  1351.             if (null !== $filesModel)
  1352.             {
  1353.                 // Set empty metadata
  1354.                 $templateData array_replace_recursive(
  1355.                     $templateData,
  1356.                     array(
  1357.                         'alt' => '',
  1358.                         'caption' => '',
  1359.                         'imageTitle' => '',
  1360.                         'imageUrl' => '',
  1361.                     )
  1362.                 );
  1363.             }
  1364.             return $templateData;
  1365.         };
  1366.         // Helper: Get size and margins and handle legacy $maxWidth option
  1367.         $getSizeAndMargin = static function () use ($rowData$maxWidth)
  1368.         {
  1369.             $size $rowData['size'] ?? null;
  1370.             $margin StringUtil::deserialize($rowData['imagemargin'] ?? null);
  1371.             $maxWidth = (int) ($maxWidth ?? Config::get('maxImageWidth'));
  1372.             if (=== $maxWidth)
  1373.             {
  1374.                 return array($size$margin);
  1375.             }
  1376.             trigger_deprecation('contao/core-bundle''4.10''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.');
  1377.             // Adjust margins if needed
  1378.             if ('px' === ($margin['unit'] ?? null))
  1379.             {
  1380.                 $horizontalMargin = (int) ($margin['left'] ?? 0) + (int) ($margin['right'] ?? 0);
  1381.                 if ($maxWidth $horizontalMargin 1)
  1382.                 {
  1383.                     $margin['left'] = '';
  1384.                     $margin['right'] = '';
  1385.                 }
  1386.                 else
  1387.                 {
  1388.                     $maxWidth -= $horizontalMargin;
  1389.                 }
  1390.             }
  1391.             // Normalize size
  1392.             if ($size instanceof PictureConfiguration)
  1393.             {
  1394.                 return array($size$margin);
  1395.             }
  1396.             $size StringUtil::deserialize($size);
  1397.             if (is_numeric($size))
  1398.             {
  1399.                 $size = array(00, (int) $size);
  1400.             }
  1401.             else
  1402.             {
  1403.                 $size = (\is_array($size) ? $size : array()) + array(00'crop');
  1404.                 $size[0] = (int) $size[0];
  1405.                 $size[1] = (int) $size[1];
  1406.             }
  1407.             // Adjust image size configuration if it exceeds the max width
  1408.             if ($size[0] > && $size[1] > 0)
  1409.             {
  1410.                 list($width$height) = $size;
  1411.             }
  1412.             else
  1413.             {
  1414.                 $container System::getContainer();
  1415.                 /** @var BoxInterface $originalSize */
  1416.                 $originalSize $container
  1417.                     ->get('contao.image.image_factory')
  1418.                     ->create($container->getParameter('kernel.project_dir') . '/' $rowData['singleSRC'])
  1419.                     ->getDimensions()
  1420.                     ->getSize();
  1421.                 $width $originalSize->getWidth();
  1422.                 $height $originalSize->getHeight();
  1423.             }
  1424.             if ($width <= $maxWidth)
  1425.             {
  1426.                 return array($size$margin);
  1427.             }
  1428.             $size[0] = $maxWidth;
  1429.             $size[1] = (int) floor($maxWidth * ($height $width));
  1430.             return array($size$margin);
  1431.         };
  1432.         /** @var FigureBuilder $figureBuilder */
  1433.         $figureBuilder System::getContainer()->get(Studio::class)->createFigureBuilder();
  1434.         // Set image resource
  1435.         if (null !== $filesModel)
  1436.         {
  1437.             // Make sure model points to the same resource (BC)
  1438.             $filesModel = clone $filesModel;
  1439.             $filesModel->path $rowData['singleSRC'];
  1440.             // Use source + metadata from files model (if not overwritten)
  1441.             $figureBuilder
  1442.                 ->fromFilesModel($filesModel)
  1443.                 ->setMetadata($createMetadataOverwriteFromRowData(true));
  1444.             $includeFullMetadata true;
  1445.         }
  1446.         else
  1447.         {
  1448.             // Always ignore file metadata when building from path (BC)
  1449.             $figureBuilder
  1450.                 ->fromPath($rowData['singleSRC'], false)
  1451.                 ->setMetadata($createMetadataOverwriteFromRowData(false));
  1452.             $includeFullMetadata false;
  1453.         }
  1454.         // Set size and lightbox configuration
  1455.         list($size$margin) = $getSizeAndMargin();
  1456.         $lightboxSize StringUtil::deserialize($rowData['lightboxSize'] ?? null) ?: null;
  1457.         $figure $figureBuilder
  1458.             ->setSize($size)
  1459.             ->setLightboxGroupIdentifier($lightboxGroupIdentifier)
  1460.             ->setLightboxSize($lightboxSize)
  1461.             ->enableLightbox((bool) ($rowData['fullsize'] ?? false))
  1462.             ->buildIfResourceExists();
  1463.         if (null === $figure)
  1464.         {
  1465.             System::getContainer()
  1466.                 ->get('monolog.logger.contao')
  1467.                 ->log(
  1468.                     LogLevel::ERROR,
  1469.                     sprintf('Image "%s" could not be processed: %s'$rowData['singleSRC'], $figureBuilder->getLastException()->getMessage()),
  1470.                     array('contao' => new ContaoMonologContext(__METHOD__'ERROR'))
  1471.                 );
  1472.             // Fall back to apply a sparse data set instead of failing (BC)
  1473.             foreach ($createFallBackTemplateData() as $key => $value)
  1474.             {
  1475.                 $template->$key $value;
  1476.             }
  1477.             return;
  1478.         }
  1479.         // Build result and apply it to the template
  1480.         $figure->applyLegacyTemplateData($template$margin$rowData['floating'] ?? null$includeFullMetadata);
  1481.         // Fall back to manually specified link title or empty string if not set (backwards compatibility)
  1482.         $template->linkTitle $template->linkTitle ?? StringUtil::specialchars($rowData['title'] ?? '');
  1483.     }
  1484.     /**
  1485.      * Add enclosures to a template
  1486.      *
  1487.      * @param object $objTemplate The template object to add the enclosures to
  1488.      * @param array  $arrItem     The element or module as array
  1489.      * @param string $strKey      The name of the enclosures field in $arrItem
  1490.      */
  1491.     public static function addEnclosuresToTemplate($objTemplate$arrItem$strKey='enclosure')
  1492.     {
  1493.         $arrEnclosures StringUtil::deserialize($arrItem[$strKey]);
  1494.         if (empty($arrEnclosures) || !\is_array($arrEnclosures))
  1495.         {
  1496.             return;
  1497.         }
  1498.         $objFiles FilesModel::findMultipleByUuids($arrEnclosures);
  1499.         if ($objFiles === null)
  1500.         {
  1501.             return;
  1502.         }
  1503.         $file Input::get('file'true);
  1504.         // Send the file to the browser and do not send a 404 header (see #5178)
  1505.         if ($file)
  1506.         {
  1507.             while ($objFiles->next())
  1508.             {
  1509.                 if ($file == $objFiles->path)
  1510.                 {
  1511.                     static::sendFileToBrowser($file);
  1512.                 }
  1513.             }
  1514.             $objFiles->reset();
  1515.         }
  1516.         /** @var PageModel $objPage */
  1517.         global $objPage;
  1518.         $arrEnclosures = array();
  1519.         $allowedDownload StringUtil::trimsplit(','strtolower(Config::get('allowedDownload')));
  1520.         // Add download links
  1521.         while ($objFiles->next())
  1522.         {
  1523.             if ($objFiles->type == 'file')
  1524.             {
  1525.                 $projectDir System::getContainer()->getParameter('kernel.project_dir');
  1526.                 if (!\in_array($objFiles->extension$allowedDownload) || !is_file($projectDir '/' $objFiles->path))
  1527.                 {
  1528.                     continue;
  1529.                 }
  1530.                 $objFile = new File($objFiles->path);
  1531.                 $strHref Environment::get('request');
  1532.                 // Remove an existing file parameter (see #5683)
  1533.                 if (preg_match('/(&(amp;)?|\?)file=/'$strHref))
  1534.                 {
  1535.                     $strHref preg_replace('/(&(amp;)?|\?)file=[^&]+/'''$strHref);
  1536.                 }
  1537.                 $strHref .= ((strpos($strHref'?') !== false) ? '&amp;' '?') . 'file=' System::urlEncode($objFiles->path);
  1538.                 $arrMeta Frontend::getMetaData($objFiles->meta$objPage->language);
  1539.                 if (empty($arrMeta) && $objPage->rootFallbackLanguage !== null)
  1540.                 {
  1541.                     $arrMeta Frontend::getMetaData($objFiles->meta$objPage->rootFallbackLanguage);
  1542.                 }
  1543.                 // Use the file name as title if none is given
  1544.                 if (empty($arrMeta['title']))
  1545.                 {
  1546.                     $arrMeta['title'] = StringUtil::specialchars($objFile->basename);
  1547.                 }
  1548.                 $arrEnclosures[] = array
  1549.                 (
  1550.                     'id'        => $objFiles->id,
  1551.                     'uuid'      => $objFiles->uuid,
  1552.                     'name'      => $objFile->basename,
  1553.                     'title'     => StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['download'], $objFile->basename)),
  1554.                     'link'      => $arrMeta['title'],
  1555.                     'caption'   => $arrMeta['caption'] ?? null,
  1556.                     'href'      => $strHref,
  1557.                     'filesize'  => static::getReadableSize($objFile->filesize),
  1558.                     'icon'      => Image::getPath($objFile->icon),
  1559.                     'mime'      => $objFile->mime,
  1560.                     'meta'      => $arrMeta,
  1561.                     'extension' => $objFile->extension,
  1562.                     'path'      => $objFile->dirname,
  1563.                     'enclosure' => $objFiles->path // backwards compatibility
  1564.                 );
  1565.             }
  1566.         }
  1567.         // Order the enclosures
  1568.         if (!empty($arrItem['orderEnclosure']))
  1569.         {
  1570.             trigger_deprecation('contao/core-bundle''4.10''Using "orderEnclosure" has been deprecated and will no longer work in Contao 5.0. Use a file tree with "isSortable" instead.');
  1571.             $arrEnclosures ArrayUtil::sortByOrderField($arrEnclosures$arrItem['orderEnclosure']);
  1572.         }
  1573.         $objTemplate->enclosure $arrEnclosures;
  1574.     }
  1575.     /**
  1576.      * Set the static URL constants
  1577.      */
  1578.     public static function setStaticUrls()
  1579.     {
  1580.         if (\defined('TL_FILES_URL'))
  1581.         {
  1582.             return;
  1583.         }
  1584.         if (\func_num_args() > 0)
  1585.         {
  1586.             trigger_deprecation('contao/core-bundle''4.9''Using "Contao\Controller::setStaticUrls()" has been deprecated and will no longer work in Contao 5.0. Use the asset contexts instead.');
  1587.             if (!isset($GLOBALS['objPage']))
  1588.             {
  1589.                 $GLOBALS['objPage'] = func_get_arg(0);
  1590.             }
  1591.         }
  1592.         \define('TL_ASSETS_URL'System::getContainer()->get('contao.assets.assets_context')->getStaticUrl());
  1593.         \define('TL_FILES_URL'System::getContainer()->get('contao.assets.files_context')->getStaticUrl());
  1594.         // Deprecated since Contao 4.0, to be removed in Contao 5.0
  1595.         \define('TL_SCRIPT_URL'TL_ASSETS_URL);
  1596.         \define('TL_PLUGINS_URL'TL_ASSETS_URL);
  1597.     }
  1598.     /**
  1599.      * Add a static URL to a script
  1600.      *
  1601.      * @param string             $script  The script path
  1602.      * @param ContaoContext|null $context
  1603.      *
  1604.      * @return string The script path with the static URL
  1605.      */
  1606.     public static function addStaticUrlTo($scriptContaoContext $context null)
  1607.     {
  1608.         // Absolute URLs
  1609.         if (preg_match('@^https?://@'$script))
  1610.         {
  1611.             return $script;
  1612.         }
  1613.         if ($context === null)
  1614.         {
  1615.             $context System::getContainer()->get('contao.assets.assets_context');
  1616.         }
  1617.         if ($strStaticUrl $context->getStaticUrl())
  1618.         {
  1619.             return $strStaticUrl $script;
  1620.         }
  1621.         return $script;
  1622.     }
  1623.     /**
  1624.      * Add the assets URL to a script
  1625.      *
  1626.      * @param string $script The script path
  1627.      *
  1628.      * @return string The script path with the assets URL
  1629.      */
  1630.     public static function addAssetsUrlTo($script)
  1631.     {
  1632.         return static::addStaticUrlTo($scriptSystem::getContainer()->get('contao.assets.assets_context'));
  1633.     }
  1634.     /**
  1635.      * Add the files URL to a script
  1636.      *
  1637.      * @param string $script The script path
  1638.      *
  1639.      * @return string The script path with the files URL
  1640.      */
  1641.     public static function addFilesUrlTo($script)
  1642.     {
  1643.         return static::addStaticUrlTo($scriptSystem::getContainer()->get('contao.assets.files_context'));
  1644.     }
  1645.     /**
  1646.      * Return the current theme as string
  1647.      *
  1648.      * @return string The name of the theme
  1649.      *
  1650.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1651.      *             Use Backend::getTheme() instead.
  1652.      */
  1653.     public static function getTheme()
  1654.     {
  1655.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getTheme()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Backend::getTheme()" instead.');
  1656.         return Backend::getTheme();
  1657.     }
  1658.     /**
  1659.      * Return the back end themes as array
  1660.      *
  1661.      * @return array An array of available back end themes
  1662.      *
  1663.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1664.      *             Use Backend::getThemes() instead.
  1665.      */
  1666.     public static function getBackendThemes()
  1667.     {
  1668.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getBackendThemes()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Backend::getThemes()" instead.');
  1669.         return Backend::getThemes();
  1670.     }
  1671.     /**
  1672.      * Get the details of a page including inherited parameters
  1673.      *
  1674.      * @param mixed $intId A page ID or a Model object
  1675.      *
  1676.      * @return PageModel The page model or null
  1677.      *
  1678.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1679.      *             Use PageModel::findWithDetails() or PageModel->loadDetails() instead.
  1680.      */
  1681.     public static function getPageDetails($intId)
  1682.     {
  1683.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getPageDetails()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\PageModel::findWithDetails()" or "Contao\PageModel->loadDetails()" instead.');
  1684.         if ($intId instanceof PageModel)
  1685.         {
  1686.             return $intId->loadDetails();
  1687.         }
  1688.         if ($intId instanceof Collection)
  1689.         {
  1690.             /** @var PageModel $objPage */
  1691.             $objPage $intId->current();
  1692.             return $objPage->loadDetails();
  1693.         }
  1694.         if (\is_object($intId))
  1695.         {
  1696.             $strKey __METHOD__ '-' $intId->id;
  1697.             // Try to load from cache
  1698.             if (Cache::has($strKey))
  1699.             {
  1700.                 return Cache::get($strKey);
  1701.             }
  1702.             // Create a model from the database result
  1703.             $objPage = new PageModel();
  1704.             $objPage->setRow($intId->row());
  1705.             $objPage->loadDetails();
  1706.             Cache::set($strKey$objPage);
  1707.             return $objPage;
  1708.         }
  1709.         // Invalid ID
  1710.         if ($intId || !\strlen($intId))
  1711.         {
  1712.             return null;
  1713.         }
  1714.         $strKey __METHOD__ '-' $intId;
  1715.         // Try to load from cache
  1716.         if (Cache::has($strKey))
  1717.         {
  1718.             return Cache::get($strKey);
  1719.         }
  1720.         $objPage PageModel::findWithDetails($intId);
  1721.         Cache::set($strKey$objPage);
  1722.         return $objPage;
  1723.     }
  1724.     /**
  1725.      * Remove old XML files from the share directory
  1726.      *
  1727.      * @param boolean $blnReturn If true, only return the finds and don't delete
  1728.      *
  1729.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1730.      *             Use Automator::purgeXmlFiles() instead.
  1731.      */
  1732.     protected function removeOldFeeds($blnReturn=false)
  1733.     {
  1734.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::removeOldFeeds()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Automator::purgeXmlFiles()" instead.');
  1735.         $this->import(Automator::class, 'Automator');
  1736.         $this->Automator->purgeXmlFiles($blnReturn);
  1737.     }
  1738.     /**
  1739.      * Return true if a class exists (tries to autoload the class)
  1740.      *
  1741.      * @param string $strClass The class name
  1742.      *
  1743.      * @return boolean True if the class exists
  1744.      *
  1745.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1746.      *             Use the PHP function class_exists() instead.
  1747.      */
  1748.     protected function classFileExists($strClass)
  1749.     {
  1750.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::classFileExists()" has been deprecated and will no longer work in Contao 5.0. Use the PHP function "class_exists()" instead.');
  1751.         return class_exists($strClass);
  1752.     }
  1753.     /**
  1754.      * Restore basic entities
  1755.      *
  1756.      * @param string $strBuffer The string with the tags to be replaced
  1757.      *
  1758.      * @return string The string with the original entities
  1759.      *
  1760.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1761.      *             Use StringUtil::restoreBasicEntities() instead.
  1762.      */
  1763.     public static function restoreBasicEntities($strBuffer)
  1764.     {
  1765.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::restoreBasicEntities()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\StringUtil::restoreBasicEntities()" instead.');
  1766.         return StringUtil::restoreBasicEntities($strBuffer);
  1767.     }
  1768.     /**
  1769.      * Resize an image and crop it if necessary
  1770.      *
  1771.      * @param string  $image  The image path
  1772.      * @param integer $width  The target width
  1773.      * @param integer $height The target height
  1774.      * @param string  $mode   An optional resize mode
  1775.      *
  1776.      * @return boolean True if the image has been resized correctly
  1777.      *
  1778.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1779.      *             Use Image::resize() instead.
  1780.      */
  1781.     protected function resizeImage($image$width$height$mode='')
  1782.     {
  1783.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::resizeImage()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Image::resize()" instead.');
  1784.         return Image::resize($image$width$height$mode);
  1785.     }
  1786.     /**
  1787.      * Resize an image and crop it if necessary
  1788.      *
  1789.      * @param string  $image  The image path
  1790.      * @param integer $width  The target width
  1791.      * @param integer $height The target height
  1792.      * @param string  $mode   An optional resize mode
  1793.      * @param string  $target An optional target to be replaced
  1794.      * @param boolean $force  Override existing target images
  1795.      *
  1796.      * @return string|null The image path or null
  1797.      *
  1798.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1799.      *             Use Image::get() instead.
  1800.      */
  1801.     protected function getImage($image$width$height$mode=''$target=null$force=false)
  1802.     {
  1803.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getImage()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Image::get()" instead.');
  1804.         return Image::get($image$width$height$mode$target$force);
  1805.     }
  1806.     /**
  1807.      * Generate an image tag and return it as string
  1808.      *
  1809.      * @param string $src        The image path
  1810.      * @param string $alt        An optional alt attribute
  1811.      * @param string $attributes A string of other attributes
  1812.      *
  1813.      * @return string The image HTML tag
  1814.      *
  1815.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1816.      *             Use Image::getHtml() instead.
  1817.      */
  1818.     public static function generateImage($src$alt=''$attributes='')
  1819.     {
  1820.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::generateImage()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Image::getHtml()" instead.');
  1821.         return Image::getHtml($src$alt$attributes);
  1822.     }
  1823.     /**
  1824.      * Return the date picker string (see #3218)
  1825.      *
  1826.      * @return boolean
  1827.      *
  1828.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1829.      *             Specify "datepicker"=>true in your DCA file instead.
  1830.      */
  1831.     protected function getDatePickerString()
  1832.     {
  1833.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getDatePickerString()" has been deprecated and will no longer work in Contao 5.0. Specify "\'datepicker\' => true" in your DCA file instead.');
  1834.         return true;
  1835.     }
  1836.     /**
  1837.      * Return the installed back end languages as array
  1838.      *
  1839.      * @return array An array of available back end languages
  1840.      *
  1841.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1842.      *             Use the Contao\CoreBundle\Intl\Locales service instead.
  1843.      */
  1844.     protected function getBackendLanguages()
  1845.     {
  1846.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getBackendLanguages()" has been deprecated and will no longer work in Contao 5.0. Use the Contao\CoreBundle\Intl\Locales service instead.');
  1847.         return $this->getLanguages(true);
  1848.     }
  1849.     /**
  1850.      * Parse simple tokens that can be used to personalize newsletters
  1851.      *
  1852.      * @param string $strBuffer The text with the tokens to be replaced
  1853.      * @param array  $arrData   The replacement data as array
  1854.      *
  1855.      * @return string The text with the replaced tokens
  1856.      *
  1857.      * @deprecated Deprecated since Contao 4.10, to be removed in Contao 5.0;
  1858.      *             Use the SimpleTokenParser::class service instead.
  1859.      */
  1860.     protected function parseSimpleTokens($strBuffer$arrData)
  1861.     {
  1862.         trigger_deprecation('contao/core-bundle''4.10''Using "Contao\Controller::parseSimpleTokens()" has been deprecated and will no longer work in Contao 5.0. Use the "SimpleTokenParser::class" service instead.');
  1863.         return System::getContainer()->get(SimpleTokenParser::class)->parse($strBuffer$arrData);
  1864.     }
  1865.     /**
  1866.      * Convert a DCA file configuration to be used with widgets
  1867.      *
  1868.      * @param array  $arrData  The field configuration array
  1869.      * @param string $strName  The field name in the form
  1870.      * @param mixed  $varValue The field value
  1871.      * @param string $strField The field name in the database
  1872.      * @param string $strTable The table name
  1873.      *
  1874.      * @return array An array that can be passed to a widget
  1875.      *
  1876.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1877.      *             Use Widget::getAttributesFromDca() instead.
  1878.      */
  1879.     protected function prepareForWidget($arrData$strName$varValue=null$strField=''$strTable='')
  1880.     {
  1881.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::prepareForWidget()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Widget::getAttributesFromDca()" instead.');
  1882.         return Widget::getAttributesFromDca($arrData$strName$varValue$strField$strTable);
  1883.     }
  1884.     /**
  1885.      * Return the IDs of all child records of a particular record (see #2475)
  1886.      *
  1887.      * @author Andreas Schempp
  1888.      *
  1889.      * @param mixed   $arrParentIds An array of parent IDs
  1890.      * @param string  $strTable     The table name
  1891.      * @param boolean $blnSorting   True if the table has a sorting field
  1892.      * @param array   $arrReturn    The array to be returned
  1893.      * @param string  $strWhere     Additional WHERE condition
  1894.      *
  1895.      * @return array An array of child record IDs
  1896.      *
  1897.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1898.      *             Use Database::getChildRecords() instead.
  1899.      */
  1900.     protected function getChildRecords($arrParentIds$strTable$blnSorting=false$arrReturn=array(), $strWhere='')
  1901.     {
  1902.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getChildRecords()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Database::getChildRecords()" instead.');
  1903.         return $this->Database->getChildRecords($arrParentIds$strTable$blnSorting$arrReturn$strWhere);
  1904.     }
  1905.     /**
  1906.      * Return the IDs of all parent records of a particular record
  1907.      *
  1908.      * @param integer $intId    The ID of the record
  1909.      * @param string  $strTable The table name
  1910.      *
  1911.      * @return array An array of parent record IDs
  1912.      *
  1913.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1914.      *             Use Database::getParentRecords() instead.
  1915.      */
  1916.     protected function getParentRecords($intId$strTable)
  1917.     {
  1918.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getParentRecords()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Database::getParentRecords()" instead.');
  1919.         return $this->Database->getParentRecords($intId$strTable);
  1920.     }
  1921.     /**
  1922.      * Print an article as PDF and stream it to the browser
  1923.      *
  1924.      * @param ModuleModel $objArticle An article object
  1925.      *
  1926.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1927.      *             Use ModuleArticle->generatePdf() instead.
  1928.      */
  1929.     protected function printArticleAsPdf($objArticle)
  1930.     {
  1931.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::printArticleAsPdf()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\ModuleArticle->generatePdf()" instead.');
  1932.         $objArticle = new ModuleArticle($objArticle);
  1933.         $objArticle->generatePdf();
  1934.     }
  1935.     /**
  1936.      * Return all page sections as array
  1937.      *
  1938.      * @return array An array of active page sections
  1939.      *
  1940.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1941.      *             See https://github.com/contao/core/issues/4693.
  1942.      */
  1943.     public static function getPageSections()
  1944.     {
  1945.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::getPageSections()" has been deprecated and will no longer work in Contao 5.0.');
  1946.         return array('header''left''right''main''footer');
  1947.     }
  1948.     /**
  1949.      * Return a "selected" attribute if the option is selected
  1950.      *
  1951.      * @param string $strOption The option to check
  1952.      * @param mixed  $varValues One or more values to check against
  1953.      *
  1954.      * @return string The attribute or an empty string
  1955.      *
  1956.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1957.      *             Use Widget::optionSelected() instead.
  1958.      */
  1959.     public static function optionSelected($strOption$varValues)
  1960.     {
  1961.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::optionSelected()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Widget::optionSelected()" instead.');
  1962.         return Widget::optionSelected($strOption$varValues);
  1963.     }
  1964.     /**
  1965.      * Return a "checked" attribute if the option is checked
  1966.      *
  1967.      * @param string $strOption The option to check
  1968.      * @param mixed  $varValues One or more values to check against
  1969.      *
  1970.      * @return string The attribute or an empty string
  1971.      *
  1972.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1973.      *             Use Widget::optionChecked() instead.
  1974.      */
  1975.     public static function optionChecked($strOption$varValues)
  1976.     {
  1977.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::optionChecked()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Widget::optionChecked()" instead.');
  1978.         return Widget::optionChecked($strOption$varValues);
  1979.     }
  1980.     /**
  1981.      * Find a content element in the TL_CTE array and return the class name
  1982.      *
  1983.      * @param string $strName The content element name
  1984.      *
  1985.      * @return string The class name
  1986.      *
  1987.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  1988.      *             Use ContentElement::findClass() instead.
  1989.      */
  1990.     public static function findContentElement($strName)
  1991.     {
  1992.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::findContentElement()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\ContentElement::findClass()" instead.');
  1993.         return ContentElement::findClass($strName);
  1994.     }
  1995.     /**
  1996.      * Find a front end module in the FE_MOD array and return the class name
  1997.      *
  1998.      * @param string $strName The front end module name
  1999.      *
  2000.      * @return string The class name
  2001.      *
  2002.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2003.      *             Use Module::findClass() instead.
  2004.      */
  2005.     public static function findFrontendModule($strName)
  2006.     {
  2007.         trigger_deprecation('contao/core-bundle''4.0''Using Contao\Controller::findFrontendModule() has been deprecated and will no longer work in Contao 5.0. Use Contao\Module::findClass() instead.');
  2008.         return Module::findClass($strName);
  2009.     }
  2010.     /**
  2011.      * Create an initial version of a record
  2012.      *
  2013.      * @param string  $strTable The table name
  2014.      * @param integer $intId    The ID of the element to be versioned
  2015.      *
  2016.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2017.      *             Use Versions->initialize() instead.
  2018.      */
  2019.     protected function createInitialVersion($strTable$intId)
  2020.     {
  2021.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::createInitialVersion()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Versions->initialize()" instead.');
  2022.         $objVersions = new Versions($strTable$intId);
  2023.         $objVersions->initialize();
  2024.     }
  2025.     /**
  2026.      * Create a new version of a record
  2027.      *
  2028.      * @param string  $strTable The table name
  2029.      * @param integer $intId    The ID of the element to be versioned
  2030.      *
  2031.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  2032.      *             Use Versions->create() instead.
  2033.      */
  2034.     protected function createNewVersion($strTable$intId)
  2035.     {
  2036.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Controller::createNewVersion()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Versions->create()" instead.');
  2037.         $objVersions = new Versions($strTable$intId);
  2038.         $objVersions->create();
  2039.     }
  2040.     /**
  2041.      * Return the files matching a GLOB pattern
  2042.      *
  2043.      * @param string $pattern
  2044.      *
  2045.      * @return array|false
  2046.      */
  2047.     protected static function braceGlob($pattern)
  2048.     {
  2049.         // Use glob() if possible
  2050.         if (false === strpos($pattern'/**/') && (\defined('GLOB_BRACE') || false === strpos($pattern'{')))
  2051.         {
  2052.             return glob($pattern, \defined('GLOB_BRACE') ? GLOB_BRACE 0);
  2053.         }
  2054.         $finder = new Finder();
  2055.         $regex Glob::toRegex($pattern);
  2056.         // All files in the given template folder
  2057.         $filesIterator $finder
  2058.             ->files()
  2059.             ->followLinks()
  2060.             ->sortByName()
  2061.             ->in(\dirname($pattern))
  2062.         ;
  2063.         // Match the actual regex and filter the files
  2064.         $filesIterator $filesIterator->filter(static function (\SplFileInfo $info) use ($regex)
  2065.         {
  2066.             $path $info->getPathname();
  2067.             return preg_match($regex$path) && $info->isFile();
  2068.         });
  2069.         $files iterator_to_array($filesIterator);
  2070.         return array_keys($files);
  2071.     }
  2072. }
  2073. class_alias(Controller::class, 'Controller');