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

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