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

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