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

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