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

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