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

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