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

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