vendor/contao/core-bundle/src/Resources/contao/models/PageModel.php line 1325

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\Exception\NoRootPageFoundException;
  11. use Contao\CoreBundle\Routing\Page\PageRoute;
  12. use Contao\CoreBundle\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag;
  13. use Contao\CoreBundle\Routing\ResponseContext\JsonLd\ContaoPageSchema;
  14. use Contao\CoreBundle\Routing\ResponseContext\JsonLd\JsonLdManager;
  15. use Contao\CoreBundle\Util\LocaleUtil;
  16. use Contao\Model\Collection;
  17. use Contao\Model\Registry;
  18. use Symfony\Cmf\Component\Routing\RouteObjectInterface;
  19. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  20. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  21. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  22. /**
  23. * Reads and writes pages
  24. *
  25. * @property string|integer $id
  26. * @property string|integer $pid
  27. * @property string|integer $sorting
  28. * @property string|integer $tstamp
  29. * @property string $title
  30. * @property string $alias
  31. * @property string $type
  32. * @property string|integer $routePriority
  33. * @property string $pageTitle
  34. * @property string $language
  35. * @property string $robots
  36. * @property string|null $description
  37. * @property string $redirect
  38. * @property string|boolean $alwaysForward
  39. * @property string|integer $jumpTo
  40. * @property string|boolean $redirectBack
  41. * @property string $url
  42. * @property string|boolean $target
  43. * @property string $dns
  44. * @property string $staticFiles
  45. * @property string $staticPlugins
  46. * @property string|boolean $fallback
  47. * @property string|boolean $disableLanguageRedirect
  48. * @property string|boolean $maintenanceMode
  49. * @property string|null $favicon
  50. * @property string|null $robotsTxt
  51. * @property string $mailerTransport
  52. * @property string|integer $enableCanonical
  53. * @property string $canonicalLink
  54. * @property string $canonicalKeepParams
  55. * @property string $adminEmail
  56. * @property string $dateFormat
  57. * @property string $timeFormat
  58. * @property string $datimFormat
  59. * @property string $validAliasCharacters
  60. * @property string|boolean $useFolderUrl
  61. * @property string $urlPrefix
  62. * @property string $urlSuffix
  63. * @property string|boolean $useSSL
  64. * @property string|boolean $autoforward
  65. * @property string|boolean $protected
  66. * @property string|array|null $groups
  67. * @property string|boolean $includeLayout
  68. * @property string|integer $layout
  69. * @property string|integer $subpageLayout
  70. * @property string|boolean $includeCache
  71. * @property string|integer|boolean $cache
  72. * @property string|boolean $alwaysLoadFromCache
  73. * @property string|integer|boolean $clientCache
  74. * @property string|boolean $includeChmod
  75. * @property string|integer $cuser
  76. * @property string|integer $cgroup
  77. * @property string $chmod
  78. * @property string|boolean $noSearch
  79. * @property string|boolean $requireItem
  80. * @property string $cssClass
  81. * @property string $sitemap
  82. * @property string|boolean $hide
  83. * @property string|boolean $guests
  84. * @property string|integer $tabindex
  85. * @property string $accesskey
  86. * @property string|boolean $published
  87. * @property string|integer $start
  88. * @property string|integer $stop
  89. * @property string|boolean $enforceTwoFactor
  90. * @property string|integer $twoFactorJumpTo
  91. *
  92. * @property array $trail
  93. * @property string $mainAlias
  94. * @property string $mainTitle
  95. * @property string $mainPageTitle
  96. * @property string $parentAlias
  97. * @property string $parentTitle
  98. * @property string $parentPageTitle
  99. * @property string $folderUrl
  100. * @property boolean $isPublic
  101. * @property integer $rootId
  102. * @property string $rootAlias
  103. * @property string $rootTitle
  104. * @property string $rootPageTitle
  105. * @property integer $rootSorting
  106. * @property string $domain
  107. * @property string $rootLanguage
  108. * @property boolean $rootIsPublic
  109. * @property boolean $rootIsFallback
  110. * @property string|boolean $rootUseSSL
  111. * @property string $rootFallbackLanguage
  112. * @property boolean $minifyMarkup
  113. * @property integer $layoutId
  114. * @property boolean $hasJQuery
  115. * @property boolean $hasMooTools
  116. * @property string $template
  117. * @property string $templateGroup
  118. * @property boolean $useAutoItem
  119. *
  120. * @method static PageModel|null findById($id, array $opt=array())
  121. * @method static PageModel|null findByPk($id, array $opt=array())
  122. * @method static PageModel|null findByIdOrAlias($val, array $opt=array())
  123. * @method static PageModel|null findOneBy($col, $val, array $opt=array())
  124. * @method static PageModel|null findOneByPid($val, array $opt=array())
  125. * @method static PageModel|null findOneBySorting($val, array $opt=array())
  126. * @method static PageModel|null findOneByTstamp($val, array $opt=array())
  127. * @method static PageModel|null findOneByTitle($val, array $opt=array())
  128. * @method static PageModel|null findOneByAlias($val, array $opt=array())
  129. * @method static PageModel|null findOneByType($val, array $opt=array())
  130. * @method static PageModel|null findOneByRoutePriority($val, array $opt=array())
  131. * @method static PageModel|null findOneByPageTitle($val, array $opt=array())
  132. * @method static PageModel|null findOneByLanguage($val, array $opt=array())
  133. * @method static PageModel|null findOneByRobots($val, array $opt=array())
  134. * @method static PageModel|null findOneByDescription($val, array $opt=array())
  135. * @method static PageModel|null findOneByRedirect($val, array $opt=array())
  136. * @method static PageModel|null findOneByAlwaysForward($val, array $opt=array())
  137. * @method static PageModel|null findOneByJumpTo($val, array $opt=array())
  138. * @method static PageModel|null findOneByRedirectBack($val, array $opt=array())
  139. * @method static PageModel|null findOneByUrl($val, array $opt=array())
  140. * @method static PageModel|null findOneByTarget($val, array $opt=array())
  141. * @method static PageModel|null findOneByDns($val, array $opt=array())
  142. * @method static PageModel|null findOneByStaticFiles($val, array $opt=array())
  143. * @method static PageModel|null findOneByStaticPlugins($val, array $opt=array())
  144. * @method static PageModel|null findOneByFallback($val, array $opt=array())
  145. * @method static PageModel|null findOneByDisableLanguageRedirect($val, array $opt=array())
  146. * @method static PageModel|null findOneByFavicon($val, array $opt=array())
  147. * @method static PageModel|null findOneByRobotsTxt($val, array $opt=array())
  148. * @method static PageModel|null findOneByMailerTransport($val, array $opt=array())
  149. * @method static PageModel|null findOneByEnableCanonical($val, array $opt=array())
  150. * @method static PageModel|null findOneByCanonicalLink($val, array $opt=array())
  151. * @method static PageModel|null findOneByCanonicalKeepParams($val, array $opt=array())
  152. * @method static PageModel|null findOneByAdminEmail($val, array $opt=array())
  153. * @method static PageModel|null findOneByDateFormat($val, array $opt=array())
  154. * @method static PageModel|null findOneByTimeFormat($val, array $opt=array())
  155. * @method static PageModel|null findOneByDatimFormat($val, array $opt=array())
  156. * @method static PageModel|null findOneByValidAliasCharacters($val, array $opt=array())
  157. * @method static PageModel|null findOneByUseFolderUrl($val, array $opt=array())
  158. * @method static PageModel|null findOneByUrlPrefix($val, array $opt=array())
  159. * @method static PageModel|null findOneByUrlSuffix($val, array $opt=array())
  160. * @method static PageModel|null findOneByUseSSL($val, array $opt=array())
  161. * @method static PageModel|null findOneByAutoforward($val, array $opt=array())
  162. * @method static PageModel|null findOneByProtected($val, array $opt=array())
  163. * @method static PageModel|null findOneByGroups($val, array $opt=array())
  164. * @method static PageModel|null findOneByIncludeLayout($val, array $opt=array())
  165. * @method static PageModel|null findOneByLayout($val, array $opt=array())
  166. * @method static PageModel|null findOneBySubpageLayout($val, array $opt=array())
  167. * @method static PageModel|null findOneByIncludeCache($val, array $opt=array())
  168. * @method static PageModel|null findOneByCache($val, array $opt=array())
  169. * @method static PageModel|null findOneByIncludeChmod($val, array $opt=array())
  170. * @method static PageModel|null findOneByCuser($val, array $opt=array())
  171. * @method static PageModel|null findOneByCgroup($val, array $opt=array())
  172. * @method static PageModel|null findOneByChmod($val, array $opt=array())
  173. * @method static PageModel|null findOneByNoSearch($val, array $opt=array())
  174. * @method static PageModel|null findOneByCssClass($val, array $opt=array())
  175. * @method static PageModel|null findOneBySitemap($val, array $opt=array())
  176. * @method static PageModel|null findOneByHide($val, array $opt=array())
  177. * @method static PageModel|null findOneByGuests($val, array $opt=array())
  178. * @method static PageModel|null findOneByTabindex($val, array $opt=array())
  179. * @method static PageModel|null findOneByAccesskey($val, array $opt=array())
  180. * @method static PageModel|null findOneByPublished($val, array $opt=array())
  181. * @method static PageModel|null findOneByStart($val, array $opt=array())
  182. * @method static PageModel|null findOneByStop($val, array $opt=array())
  183. * @method static PageModel|null findOneByEnforceTwoFactor($val, array $opt=array())
  184. * @method static PageModel|null findOneByTwoFactorJumpTo($val, array $opt=array())
  185. *
  186. * @method static Collection|PageModel[]|PageModel|null findByPid($val, array $opt=array())
  187. * @method static Collection|PageModel[]|PageModel|null findBySorting($val, array $opt=array())
  188. * @method static Collection|PageModel[]|PageModel|null findByTstamp($val, array $opt=array())
  189. * @method static Collection|PageModel[]|PageModel|null findByTitle($val, array $opt=array())
  190. * @method static Collection|PageModel[]|PageModel|null findByAlias($val, array $opt=array())
  191. * @method static Collection|PageModel[]|PageModel|null findByType($val, array $opt=array())
  192. * @method static Collection|PageModel[]|PageModel|null findByRoutePriority($val, array $opt=array())
  193. * @method static Collection|PageModel[]|PageModel|null findByPageTitle($val, array $opt=array())
  194. * @method static Collection|PageModel[]|PageModel|null findByLanguage($val, array $opt=array())
  195. * @method static Collection|PageModel[]|PageModel|null findByRobots($val, array $opt=array())
  196. * @method static Collection|PageModel[]|PageModel|null findByDescription($val, array $opt=array())
  197. * @method static Collection|PageModel[]|PageModel|null findByRedirect($val, array $opt=array())
  198. * @method static Collection|PageModel[]|PageModel|null findByAlwaysForward($val, array $opt=array())
  199. * @method static Collection|PageModel[]|PageModel|null findByJumpTo($val, array $opt=array())
  200. * @method static Collection|PageModel[]|PageModel|null findByRedirectBack($val, array $opt=array())
  201. * @method static Collection|PageModel[]|PageModel|null findByUrl($val, array $opt=array())
  202. * @method static Collection|PageModel[]|PageModel|null findByTarget($val, array $opt=array())
  203. * @method static Collection|PageModel[]|PageModel|null findByDns($val, array $opt=array())
  204. * @method static Collection|PageModel[]|PageModel|null findByStaticFiles($val, array $opt=array())
  205. * @method static Collection|PageModel[]|PageModel|null findByStaticPlugins($val, array $opt=array())
  206. * @method static Collection|PageModel[]|PageModel|null findByFallback($val, array $opt=array())
  207. * @method static Collection|PageModel[]|PageModel|null findByDisableLanguageRedirect($val, array $opt=array())
  208. * @method static Collection|PageModel[]|PageModel|null findByFavicon($val, array $opt=array())
  209. * @method static Collection|PageModel[]|PageModel|null findByRobotsTxt($val, array $opt=array())
  210. * @method static Collection|PageModel[]|PageModel|null findByMailerTransport($val, array $opt=array())
  211. * @method static Collection|PageModel[]|PageModel|null findByEnableCanonical($val, array $opt=array())
  212. * @method static Collection|PageModel[]|PageModel|null findByCanonicalLink($val, array $opt=array())
  213. * @method static Collection|PageModel[]|PageModel|null findByCanonicalKeepParams($val, array $opt=array())
  214. * @method static Collection|PageModel[]|PageModel|null findByAdminEmail($val, array $opt=array())
  215. * @method static Collection|PageModel[]|PageModel|null findByDateFormat($val, array $opt=array())
  216. * @method static Collection|PageModel[]|PageModel|null findByTimeFormat($val, array $opt=array())
  217. * @method static Collection|PageModel[]|PageModel|null findByDatimFormat($val, array $opt=array())
  218. * @method static Collection|PageModel[]|PageModel|null findByValidAliasCharacters($val, array $opt=array())
  219. * @method static Collection|PageModel[]|PageModel|null findByUseFolderUrl($val, array $opt=array())
  220. * @method static Collection|PageModel[]|PageModel|null findByUrlPrefix($val, array $opt=array())
  221. * @method static Collection|PageModel[]|PageModel|null findByUrlSuffix($val, array $opt=array())
  222. * @method static Collection|PageModel[]|PageModel|null findByUseSSL($val, array $opt=array())
  223. * @method static Collection|PageModel[]|PageModel|null findByAutoforward($val, array $opt=array())
  224. * @method static Collection|PageModel[]|PageModel|null findByProtected($val, array $opt=array())
  225. * @method static Collection|PageModel[]|PageModel|null findByGroups($val, array $opt=array())
  226. * @method static Collection|PageModel[]|PageModel|null findByIncludeLayout($val, array $opt=array())
  227. * @method static Collection|PageModel[]|PageModel|null findByLayout($val, array $opt=array())
  228. * @method static Collection|PageModel[]|PageModel|null findBySubpageLayout($val, array $opt=array())
  229. * @method static Collection|PageModel[]|PageModel|null findByIncludeCache($val, array $opt=array())
  230. * @method static Collection|PageModel[]|PageModel|null findByCache($val, array $opt=array())
  231. * @method static Collection|PageModel[]|PageModel|null findByIncludeChmod($val, array $opt=array())
  232. * @method static Collection|PageModel[]|PageModel|null findByCuser($val, array $opt=array())
  233. * @method static Collection|PageModel[]|PageModel|null findByCgroup($val, array $opt=array())
  234. * @method static Collection|PageModel[]|PageModel|null findByChmod($val, array $opt=array())
  235. * @method static Collection|PageModel[]|PageModel|null findByNoSearch($val, array $opt=array())
  236. * @method static Collection|PageModel[]|PageModel|null findByCssClass($val, array $opt=array())
  237. * @method static Collection|PageModel[]|PageModel|null findBySitemap($val, array $opt=array())
  238. * @method static Collection|PageModel[]|PageModel|null findByHide($val, array $opt=array())
  239. * @method static Collection|PageModel[]|PageModel|null findByGuests($val, array $opt=array())
  240. * @method static Collection|PageModel[]|PageModel|null findByTabindex($val, array $opt=array())
  241. * @method static Collection|PageModel[]|PageModel|null findByAccesskey($val, array $opt=array())
  242. * @method static Collection|PageModel[]|PageModel|null findByPublished($val, array $opt=array())
  243. * @method static Collection|PageModel[]|PageModel|null findByStart($val, array $opt=array())
  244. * @method static Collection|PageModel[]|PageModel|null findByStop($val, array $opt=array())
  245. * @method static Collection|PageModel[]|PageModel|null findByEnforceTwoFactor($val, array $opt=array())
  246. * @method static Collection|PageModel[]|PageModel|null findByTwoFactorJumpTo($val, array $opt=array())
  247. * @method static Collection|PageModel[]|PageModel|null findMultipleByIds($val, array $opt=array())
  248. * @method static Collection|PageModel[]|PageModel|null findBy($col, $val, array $opt=array())
  249. * @method static Collection|PageModel[]|PageModel|null findAll(array $opt=array())
  250. *
  251. * @method static integer countById($id, array $opt=array())
  252. * @method static integer countByPid($val, array $opt=array())
  253. * @method static integer countBySorting($val, array $opt=array())
  254. * @method static integer countByTstamp($val, array $opt=array())
  255. * @method static integer countByTitle($val, array $opt=array())
  256. * @method static integer countByAlias($val, array $opt=array())
  257. * @method static integer countByType($val, array $opt=array())
  258. * @method static integer countByRoutePriority($val, array $opt=array())
  259. * @method static integer countByPageTitle($val, array $opt=array())
  260. * @method static integer countByLanguage($val, array $opt=array())
  261. * @method static integer countByRobots($val, array $opt=array())
  262. * @method static integer countByDescription($val, array $opt=array())
  263. * @method static integer countByRedirect($val, array $opt=array())
  264. * @method static integer countByAlwaysForward($val, array $opt=array())
  265. * @method static integer countByJumpTo($val, array $opt=array())
  266. * @method static integer countByRedirectBack($val, array $opt=array())
  267. * @method static integer countByUrl($val, array $opt=array())
  268. * @method static integer countByTarget($val, array $opt=array())
  269. * @method static integer countByDns($val, array $opt=array())
  270. * @method static integer countByStaticFiles($val, array $opt=array())
  271. * @method static integer countByStaticPlugins($val, array $opt=array())
  272. * @method static integer countByFallback($val, array $opt=array())
  273. * @method static integer countByDisableLanguageRedirect($val, array $opt=array())
  274. * @method static integer countByFavicon($val, array $opt=array())
  275. * @method static integer countByRobotsTxt($val, array $opt=array())
  276. * @method static integer countByMailerTransport($val, array $opt=array())
  277. * @method static integer countByEnableCanonical($val, array $opt=array())
  278. * @method static integer countByCanonicalLink($val, array $opt=array())
  279. * @method static integer countByCanonicalKeepParams($val, array $opt=array())
  280. * @method static integer countByAdminEmail($val, array $opt=array())
  281. * @method static integer countByDateFormat($val, array $opt=array())
  282. * @method static integer countByTimeFormat($val, array $opt=array())
  283. * @method static integer countByDatimFormat($val, array $opt=array())
  284. * @method static integer countByValidAliasCharacters($val, array $opt=array())
  285. * @method static integer countByUseFolderUrl($val, array $opt=array())
  286. * @method static integer countByUrlPrefix($val, array $opt=array())
  287. * @method static integer countByUrlSuffix($val, array $opt=array())
  288. * @method static integer countByUseSSL($val, array $opt=array())
  289. * @method static integer countByAutoforward($val, array $opt=array())
  290. * @method static integer countByProtected($val, array $opt=array())
  291. * @method static integer countByGroups($val, array $opt=array())
  292. * @method static integer countByIncludeLayout($val, array $opt=array())
  293. * @method static integer countByLayout($val, array $opt=array())
  294. * @method static integer countBySubpageLayout($val, array $opt=array())
  295. * @method static integer countByIncludeCache($val, array $opt=array())
  296. * @method static integer countByCache($val, array $opt=array())
  297. * @method static integer countByIncludeChmod($val, array $opt=array())
  298. * @method static integer countByCuser($val, array $opt=array())
  299. * @method static integer countByCgroup($val, array $opt=array())
  300. * @method static integer countByChmod($val, array $opt=array())
  301. * @method static integer countByNoSearch($val, array $opt=array())
  302. * @method static integer countByCssClass($val, array $opt=array())
  303. * @method static integer countBySitemap($val, array $opt=array())
  304. * @method static integer countByHide($val, array $opt=array())
  305. * @method static integer countByGuests($val, array $opt=array())
  306. * @method static integer countByTabindex($val, array $opt=array())
  307. * @method static integer countByAccesskey($val, array $opt=array())
  308. * @method static integer countByPublished($val, array $opt=array())
  309. * @method static integer countByStart($val, array $opt=array())
  310. * @method static integer countByStop($val, array $opt=array())
  311. * @method static integer countByEnforceTwoFactor($val, array $opt=array())
  312. * @method static integer countByTwoFactorJumpTo($val, array $opt=array())
  313. */
  314. class PageModel extends Model
  315. {
  316. /**
  317. * Table name
  318. * @var string
  319. */
  320. protected static $strTable = 'tl_page';
  321. /**
  322. * Details loaded
  323. * @var boolean
  324. */
  325. protected $blnDetailsLoaded = false;
  326. private static ?array $prefixes = null;
  327. private static ?array $suffixes = null;
  328. public function __set($strKey, $varValue)
  329. {
  330. // Deprecate setting dynamic page attributes if they are set on the global $objPage
  331. if (\in_array($strKey, array('pageTitle', 'description', 'robots', 'noSearch'), true) && ($GLOBALS['objPage'] ?? null) === $this)
  332. {
  333. trigger_deprecation('contao/core-bundle', '4.12', sprintf('Overriding "%s" is deprecated and will not work in Contao 5.0 anymore. Use the ResponseContext instead.', $strKey));
  334. $responseContext = System::getContainer()->get('contao.routing.response_context_accessor')->getResponseContext();
  335. if (!$responseContext)
  336. {
  337. parent::__set($strKey, $varValue);
  338. return;
  339. }
  340. if (\in_array($strKey, array('pageTitle', 'description', 'robots')) && $responseContext->has(HtmlHeadBag::class))
  341. {
  342. /** @var HtmlHeadBag $htmlHeadBag */
  343. $htmlHeadBag = $responseContext->get(HtmlHeadBag::class);
  344. $htmlDecoder = System::getContainer()->get('contao.string.html_decoder');
  345. switch ($strKey)
  346. {
  347. case 'pageTitle':
  348. $htmlHeadBag->setTitle($htmlDecoder->inputEncodedToPlainText($varValue ?? ''));
  349. break;
  350. case 'description':
  351. $htmlHeadBag->setMetaDescription($htmlDecoder->inputEncodedToPlainText($varValue ?? ''));
  352. break;
  353. case 'robots':
  354. $htmlHeadBag->setMetaRobots($varValue);
  355. break;
  356. }
  357. }
  358. if ('noSearch' === $strKey && $responseContext->has(JsonLdManager::class))
  359. {
  360. /** @var JsonLdManager $jsonLdManager */
  361. $jsonLdManager = $responseContext->get(JsonLdManager::class);
  362. if ($jsonLdManager->getGraphForSchema(JsonLdManager::SCHEMA_CONTAO)->has(ContaoPageSchema::class))
  363. {
  364. /** @var ContaoPageSchema $schema */
  365. $schema = $jsonLdManager->getGraphForSchema(JsonLdManager::SCHEMA_CONTAO)->get(ContaoPageSchema::class);
  366. $schema->setNoSearch((bool) $varValue);
  367. }
  368. }
  369. }
  370. parent::__set($strKey, $varValue);
  371. }
  372. public static function reset()
  373. {
  374. self::$prefixes = null;
  375. self::$suffixes = null;
  376. }
  377. /**
  378. * Find a published page by its ID
  379. *
  380. * @param integer $intId The page ID
  381. * @param array $arrOptions An optional options array
  382. *
  383. * @return PageModel|null The model or null if there is no published page
  384. */
  385. public static function findPublishedById($intId, array $arrOptions=array())
  386. {
  387. $t = static::$strTable;
  388. $arrColumns = array("$t.id=?");
  389. if (!static::isPreviewMode($arrOptions))
  390. {
  391. $time = Date::floorToMinute();
  392. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  393. }
  394. return static::findOneBy($arrColumns, $intId, $arrOptions);
  395. }
  396. /**
  397. * Find published pages by their PID
  398. *
  399. * @param integer $intPid The parent ID
  400. * @param array $arrOptions An optional options array
  401. *
  402. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  403. */
  404. public static function findPublishedByPid($intPid, array $arrOptions=array())
  405. {
  406. $t = static::$strTable;
  407. $arrColumns = array("$t.pid=?");
  408. if (!static::isPreviewMode($arrOptions))
  409. {
  410. $time = Date::floorToMinute();
  411. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  412. }
  413. return static::findBy($arrColumns, $intPid, $arrOptions);
  414. }
  415. /**
  416. * Find the first published root page by its host name and language
  417. *
  418. * @param string $strHost The host name
  419. * @param mixed $varLanguage An ISO language code or an array of ISO language codes
  420. * @param array $arrOptions An optional options array
  421. *
  422. * @return PageModel|null The model or null if there is no matching root page
  423. *
  424. * @deprecated Deprecated since Contao 4.7, to be removed in Contao 5.0.
  425. */
  426. public static function findFirstPublishedRootByHostAndLanguage($strHost, $varLanguage, array $arrOptions=array())
  427. {
  428. trigger_deprecation('contao/core-bundle', '4.7', 'Using "Contao\PageModel::findFirstPublishedRootByHostAndLanguage()" has been deprecated and will no longer work Contao 5.0.');
  429. $t = static::$strTable;
  430. $objDatabase = Database::getInstance();
  431. if (\is_array($varLanguage))
  432. {
  433. $arrColumns = array("$t.type='root' AND ($t.dns=? OR $t.dns='')");
  434. if (!empty($varLanguage))
  435. {
  436. $arrColumns[] = "($t.language IN('" . implode("','", $varLanguage) . "') OR $t.fallback='1')";
  437. }
  438. else
  439. {
  440. $arrColumns[] = "$t.fallback='1'";
  441. }
  442. if (!isset($arrOptions['order']))
  443. {
  444. $arrOptions['order'] = "$t.dns DESC" . (!empty($varLanguage) ? ", " . $objDatabase->findInSet("$t.language", array_reverse($varLanguage)) . " DESC" : "") . ", $t.sorting";
  445. }
  446. if (!static::isPreviewMode($arrOptions))
  447. {
  448. $time = Date::floorToMinute();
  449. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  450. }
  451. return static::findOneBy($arrColumns, $strHost, $arrOptions);
  452. }
  453. $arrColumns = array("$t.type='root' AND ($t.dns=? OR $t.dns='') AND ($t.language=? OR $t.fallback='1')");
  454. $arrValues = array($strHost, $varLanguage);
  455. if (!isset($arrOptions['order']))
  456. {
  457. $arrOptions['order'] = "$t.dns DESC, $t.fallback";
  458. }
  459. if (!static::isPreviewMode($arrOptions))
  460. {
  461. $time = Date::floorToMinute();
  462. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  463. }
  464. return static::findOneBy($arrColumns, $arrValues, $arrOptions);
  465. }
  466. /**
  467. * Find the first published page by its parent ID
  468. *
  469. * @param integer $intPid The parent page's ID
  470. * @param array $arrOptions An optional options array
  471. *
  472. * @return PageModel|null The model or null if there is no published page
  473. */
  474. public static function findFirstPublishedByPid($intPid, array $arrOptions=array())
  475. {
  476. $t = static::$strTable;
  477. $unroutableTypes = System::getContainer()->get('contao.routing.page_registry')->getUnroutableTypes();
  478. $arrColumns = array("$t.pid=? AND $t.type!='root' AND $t.type NOT IN ('" . implode("', '", $unroutableTypes) . "')");
  479. if (!static::isPreviewMode($arrOptions))
  480. {
  481. $time = Date::floorToMinute();
  482. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  483. }
  484. if (!isset($arrOptions['order']))
  485. {
  486. $arrOptions['order'] = "$t.sorting";
  487. }
  488. return static::findOneBy($arrColumns, $intPid, $arrOptions);
  489. }
  490. /**
  491. * Find the first published regular page by its parent ID
  492. *
  493. * @param integer $intPid The parent page's ID
  494. * @param array $arrOptions An optional options array
  495. *
  496. * @return PageModel|null The model or null if there is no published regular page
  497. */
  498. public static function findFirstPublishedRegularByPid($intPid, array $arrOptions=array())
  499. {
  500. $t = static::$strTable;
  501. $arrColumns = array("$t.pid=? AND $t.type='regular'");
  502. if (!static::isPreviewMode($arrOptions))
  503. {
  504. $time = Date::floorToMinute();
  505. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  506. }
  507. if (!isset($arrOptions['order']))
  508. {
  509. $arrOptions['order'] = "$t.sorting";
  510. }
  511. return static::findOneBy($arrColumns, $intPid, $arrOptions);
  512. }
  513. /**
  514. * Find the first published page by its type and parent ID
  515. *
  516. * @param string $strType The page type
  517. * @param integer $intPid The parent page's ID
  518. * @param array $arrOptions An optional options array
  519. *
  520. * @return PageModel|null The model or null if there is no published regular page
  521. */
  522. public static function findFirstPublishedByTypeAndPid($strType, $intPid, array $arrOptions=array())
  523. {
  524. $t = static::$strTable;
  525. $arrColumns = array("$t.pid=? AND $t.type=?");
  526. if (!static::isPreviewMode($arrOptions))
  527. {
  528. $time = Date::floorToMinute();
  529. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  530. }
  531. if (!isset($arrOptions['order']))
  532. {
  533. $arrOptions['order'] = "$t.sorting";
  534. }
  535. return static::findOneBy($arrColumns, array($intPid, $strType), $arrOptions);
  536. }
  537. /**
  538. * Find an error 401 page by its parent ID
  539. *
  540. * @param integer $intPid The parent page's ID
  541. * @param array $arrOptions An optional options array
  542. *
  543. * @return PageModel|null The model or null if there is no 401 page
  544. */
  545. public static function find401ByPid($intPid, array $arrOptions=array())
  546. {
  547. $t = static::$strTable;
  548. $arrColumns = array("$t.pid=? AND $t.type='error_401'");
  549. if (!static::isPreviewMode($arrOptions))
  550. {
  551. $time = Date::floorToMinute();
  552. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  553. }
  554. if (!isset($arrOptions['order']))
  555. {
  556. $arrOptions['order'] = "$t.sorting";
  557. }
  558. return static::findOneBy($arrColumns, $intPid, $arrOptions);
  559. }
  560. /**
  561. * Find an error 403 page by its parent ID
  562. *
  563. * @param integer $intPid The parent page's ID
  564. * @param array $arrOptions An optional options array
  565. *
  566. * @return PageModel|null The model or null if there is no 403 page
  567. */
  568. public static function find403ByPid($intPid, array $arrOptions=array())
  569. {
  570. $t = static::$strTable;
  571. $arrColumns = array("$t.pid=? AND $t.type='error_403'");
  572. if (!static::isPreviewMode($arrOptions))
  573. {
  574. $time = Date::floorToMinute();
  575. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  576. }
  577. if (!isset($arrOptions['order']))
  578. {
  579. $arrOptions['order'] = "$t.sorting";
  580. }
  581. return static::findOneBy($arrColumns, $intPid, $arrOptions);
  582. }
  583. /**
  584. * Find an error 404 page by its parent ID
  585. *
  586. * @param integer $intPid The parent page's ID
  587. * @param array $arrOptions An optional options array
  588. *
  589. * @return PageModel|null The model or null if there is no 404 page
  590. */
  591. public static function find404ByPid($intPid, array $arrOptions=array())
  592. {
  593. $t = static::$strTable;
  594. $arrColumns = array("$t.pid=? AND $t.type='error_404'");
  595. if (!static::isPreviewMode($arrOptions))
  596. {
  597. $time = Date::floorToMinute();
  598. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  599. }
  600. if (!isset($arrOptions['order']))
  601. {
  602. $arrOptions['order'] = "$t.sorting";
  603. }
  604. return static::findOneBy($arrColumns, $intPid, $arrOptions);
  605. }
  606. /**
  607. * Find pages matching a list of possible alias names
  608. *
  609. * @param array $arrAliases An array of possible alias names
  610. * @param array $arrOptions An optional options array
  611. *
  612. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  613. */
  614. public static function findByAliases($arrAliases, array $arrOptions=array())
  615. {
  616. if (empty($arrAliases) || !\is_array($arrAliases))
  617. {
  618. return null;
  619. }
  620. // Remove everything that is not an alias
  621. $arrAliases = array_filter(array_map(static function ($v) { return preg_match('/^[\w\/.-]+$/u', $v) ? $v : null; }, $arrAliases));
  622. // Return if nothing is left
  623. if (empty($arrAliases))
  624. {
  625. return null;
  626. }
  627. $t = static::$strTable;
  628. $arrColumns = array("$t.alias IN('" . implode("','", array_filter($arrAliases)) . "')");
  629. // Check the publication status (see #4652)
  630. if (!static::isPreviewMode($arrOptions))
  631. {
  632. $time = Date::floorToMinute();
  633. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  634. }
  635. if (!isset($arrOptions['order']))
  636. {
  637. $arrOptions['order'] = Database::getInstance()->findInSet("$t.alias", $arrAliases);
  638. }
  639. return static::findBy($arrColumns, null, $arrOptions);
  640. }
  641. /**
  642. * Find pages that have a similar alias
  643. *
  644. * @return Collection|PageModel[]|null A collection of models or null if there are no pages
  645. */
  646. public static function findSimilarByAlias(self $pageModel)
  647. {
  648. if ('' === $pageModel->alias)
  649. {
  650. return null;
  651. }
  652. $pageModel->loadDetails();
  653. $t = static::$strTable;
  654. $alias = '%' . self::stripPrefixesAndSuffixes($pageModel->alias, $pageModel->urlPrefix, $pageModel->urlSuffix) . '%';
  655. return static::findBy(array("$t.alias LIKE ?", "$t.id!=?"), array($alias, $pageModel->id));
  656. }
  657. /**
  658. * Find published pages by their ID or aliases
  659. *
  660. * @param mixed $varId The numeric ID or the alias name
  661. * @param array $arrOptions An optional options array
  662. *
  663. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  664. */
  665. public static function findPublishedByIdOrAlias($varId, array $arrOptions=array())
  666. {
  667. $t = static::$strTable;
  668. $arrColumns = !preg_match('/^[1-9]\d*$/', $varId) ? array("BINARY $t.alias=?") : array("$t.id=?");
  669. if (!static::isPreviewMode($arrOptions))
  670. {
  671. $time = Date::floorToMinute();
  672. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  673. }
  674. return static::findBy($arrColumns, $varId, $arrOptions);
  675. }
  676. /**
  677. * Find all published subpages by their parent ID and exclude pages only visible for guests
  678. *
  679. * @param integer $intPid The parent page's ID
  680. * @param boolean $blnShowHidden If true, hidden pages will be included
  681. * @param boolean $blnIsSitemap If true, the sitemap settings apply
  682. *
  683. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  684. *
  685. * @deprecated Deprecated since Contao 4.9, to be removed in Contao 5.0;
  686. * use PageModel::findPublishedByPid() instead and filter the guests pages yourself
  687. */
  688. public static function findPublishedSubpagesWithoutGuestsByPid($intPid, $blnShowHidden=false, $blnIsSitemap=false)
  689. {
  690. trigger_deprecation('contao/core-bundle', '4.9', 'Using PageModel::findPublishedSubpagesWithoutGuestsByPid() has been deprecated and will no longer work Contao 5.0. Use PageModel::findPublishedByPid() instead and filter the guests pages yourself.');
  691. $time = Date::floorToMinute();
  692. $tokenChecker = System::getContainer()->get('contao.security.token_checker');
  693. $blnFeUserLoggedIn = $tokenChecker->hasFrontendUser();
  694. $blnBeUserLoggedIn = $tokenChecker->isPreviewMode();
  695. $unroutableTypes = System::getContainer()->get('contao.routing.page_registry')->getUnroutableTypes();
  696. $objSubpages = Database::getInstance()->prepare("SELECT p1.*, (SELECT COUNT(*) FROM tl_page p2 WHERE p2.pid=p1.id AND p2.type!='root' AND p2.type NOT IN ('" . implode("', '", $unroutableTypes) . "')" . (!$blnShowHidden ? ($blnIsSitemap ? " AND (p2.hide='' OR sitemap='map_always')" : " AND p2.hide=''") : "") . ($blnFeUserLoggedIn ? " AND p2.guests=''" : "") . (!$blnBeUserLoggedIn ? " AND p2.published='1' AND (p2.start='' OR p2.start<=$time) AND (p2.stop='' OR p2.stop>$time)" : "") . ") AS subpages FROM tl_page p1 WHERE p1.pid=? AND p1.type!='root' AND p1.type NOT IN ('" . implode("', '", $unroutableTypes) . "')" . (!$blnShowHidden ? ($blnIsSitemap ? " AND (p1.hide='' OR sitemap='map_always')" : " AND p1.hide=''") : "") . ($blnFeUserLoggedIn ? " AND p1.guests=''" : "") . (!$blnBeUserLoggedIn ? " AND p1.published='1' AND (p1.start='' OR p1.start<=$time) AND (p1.stop='' OR p1.stop>$time)" : "") . " ORDER BY p1.sorting")
  697. ->execute($intPid);
  698. if ($objSubpages->numRows < 1)
  699. {
  700. return null;
  701. }
  702. return static::createCollectionFromDbResult($objSubpages, 'tl_page');
  703. }
  704. /**
  705. * Find all published regular pages by their IDs
  706. *
  707. * @param array $arrIds An array of page IDs
  708. * @param array $arrOptions An optional options array
  709. *
  710. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  711. */
  712. public static function findPublishedRegularByIds($arrIds, array $arrOptions=array())
  713. {
  714. if (empty($arrIds) || !\is_array($arrIds))
  715. {
  716. return null;
  717. }
  718. $t = static::$strTable;
  719. $unroutableTypes = System::getContainer()->get('contao.routing.page_registry')->getUnroutableTypes();
  720. $arrColumns = array("$t.id IN(" . implode(',', array_map('\intval', $arrIds)) . ") AND $t.type NOT IN ('" . implode("', '", $unroutableTypes) . "')");
  721. if (empty($arrOptions['includeRoot']))
  722. {
  723. $arrColumns[] = "$t.type!='root'";
  724. }
  725. if (!static::isPreviewMode($arrOptions))
  726. {
  727. $time = Date::floorToMinute();
  728. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  729. }
  730. if (!isset($arrOptions['order']))
  731. {
  732. $arrOptions['order'] = Database::getInstance()->findInSet("$t.id", $arrIds);
  733. }
  734. return static::findBy($arrColumns, null, $arrOptions);
  735. }
  736. /**
  737. * Find all published regular pages by their IDs and exclude pages only visible for guests
  738. *
  739. * @param array $arrIds An array of page IDs
  740. * @param array $arrOptions An optional options array
  741. *
  742. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  743. *
  744. * @deprecated Deprecated since Contao 4.12, to be removed in Contao 5;
  745. * use PageModel::findPublishedRegularByIds() instead.
  746. */
  747. public static function findPublishedRegularWithoutGuestsByIds($arrIds, array $arrOptions=array())
  748. {
  749. trigger_deprecation('contao/core-bundle', '4.12', 'Using PageModel::findPublishedRegularWithoutGuestsByIds() has been deprecated and will no longer work in Contao 5.0. Use PageModel::findPublishedRegularByIds() instead.');
  750. if (empty($arrIds) || !\is_array($arrIds))
  751. {
  752. return null;
  753. }
  754. $t = static::$strTable;
  755. $unroutableTypes = System::getContainer()->get('contao.routing.page_registry')->getUnroutableTypes();
  756. $arrColumns = array("$t.id IN(" . implode(',', array_map('\intval', $arrIds)) . ") AND $t.type NOT IN ('" . implode("', '", $unroutableTypes) . "')");
  757. if (empty($arrOptions['includeRoot']))
  758. {
  759. $arrColumns[] = "$t.type!='root'";
  760. }
  761. if (System::getContainer()->get('contao.security.token_checker')->hasFrontendUser())
  762. {
  763. $arrColumns[] = "$t.guests=''";
  764. }
  765. if (!static::isPreviewMode($arrOptions))
  766. {
  767. $time = Date::floorToMinute();
  768. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  769. }
  770. if (!isset($arrOptions['order']))
  771. {
  772. $arrOptions['order'] = Database::getInstance()->findInSet("$t.id", $arrIds);
  773. }
  774. return static::findBy($arrColumns, null, $arrOptions);
  775. }
  776. /**
  777. * Find all published regular pages by their parent IDs
  778. *
  779. * @param integer $intPid The parent page's ID
  780. * @param array $arrOptions An optional options array
  781. *
  782. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  783. */
  784. public static function findPublishedRegularByPid($intPid, array $arrOptions=array())
  785. {
  786. $t = static::$strTable;
  787. $unroutableTypes = System::getContainer()->get('contao.routing.page_registry')->getUnroutableTypes();
  788. $arrColumns = array("$t.pid=? AND $t.type!='root' AND $t.type NOT IN ('" . implode("', '", $unroutableTypes) . "')");
  789. if (!static::isPreviewMode($arrOptions))
  790. {
  791. $time = Date::floorToMinute();
  792. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  793. }
  794. if (!isset($arrOptions['order']))
  795. {
  796. $arrOptions['order'] = "$t.sorting";
  797. }
  798. return static::findBy($arrColumns, $intPid, $arrOptions);
  799. }
  800. /**
  801. * Find all published regular pages by their parent IDs and exclude pages only visible for guests
  802. *
  803. * @param integer $intPid The parent page's ID
  804. * @param array $arrOptions An optional options array
  805. *
  806. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
  807. *
  808. * @deprecated Deprecated since Contao 4.12, to be removed in Contao 5;
  809. * use PageModel::findPublishedRegularByPid() instead.
  810. */
  811. public static function findPublishedRegularWithoutGuestsByPid($intPid, array $arrOptions=array())
  812. {
  813. trigger_deprecation('contao/core-bundle', '4.12', 'Using PageModel::findPublishedRegularWithoutGuestsByPid() has been deprecated and will no longer work in Contao 5.0. Use PageModel::findPublishedRegularByPid() instead.');
  814. $t = static::$strTable;
  815. $unroutableTypes = System::getContainer()->get('contao.routing.page_registry')->getUnroutableTypes();
  816. $arrColumns = array("$t.pid=? AND $t.type!='root' AND $t.type NOT IN ('" . implode("', '", $unroutableTypes) . "')");
  817. if (System::getContainer()->get('contao.security.token_checker')->hasFrontendUser())
  818. {
  819. $arrColumns[] = "$t.guests=''";
  820. }
  821. if (!static::isPreviewMode($arrOptions))
  822. {
  823. $time = Date::floorToMinute();
  824. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  825. }
  826. if (!isset($arrOptions['order']))
  827. {
  828. $arrOptions['order'] = "$t.sorting";
  829. }
  830. return static::findBy($arrColumns, $intPid, $arrOptions);
  831. }
  832. /**
  833. * Find the language fallback page by hostname
  834. *
  835. * @param string $strHost The hostname
  836. * @param array $arrOptions An optional options array
  837. *
  838. * @return PageModel|Model|null The model or null if there is no fallback page
  839. */
  840. public static function findPublishedFallbackByHostname($strHost, array $arrOptions=array())
  841. {
  842. // Try to load from the registry (see #8544)
  843. if (empty($arrOptions))
  844. {
  845. $objModel = Registry::getInstance()->fetch(static::$strTable, $strHost, 'contao.dns-fallback');
  846. if ($objModel !== null)
  847. {
  848. return $objModel;
  849. }
  850. }
  851. $t = static::$strTable;
  852. $arrColumns = array("$t.dns=? AND $t.fallback='1'");
  853. if (isset($arrOptions['fallbackToEmpty']) && $arrOptions['fallbackToEmpty'] === true)
  854. {
  855. $arrColumns = array("($t.dns=? OR $t.dns='') AND $t.fallback='1'");
  856. if (!isset($arrOptions['order']))
  857. {
  858. $arrOptions['order'] = "$t.dns DESC";
  859. }
  860. }
  861. if (!static::isPreviewMode($arrOptions))
  862. {
  863. $time = Date::floorToMinute();
  864. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  865. }
  866. return static::findOneBy($arrColumns, $strHost, $arrOptions);
  867. }
  868. /**
  869. * Finds the published root pages
  870. *
  871. * @param array $arrOptions An optional options array
  872. *
  873. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no parent pages
  874. */
  875. public static function findPublishedRootPages(array $arrOptions=array())
  876. {
  877. $t = static::$strTable;
  878. $arrColumns = array("$t.type='root'");
  879. if (isset($arrOptions['dns']))
  880. {
  881. $arrColumns = array("$t.type='root' AND $t.dns=?");
  882. }
  883. if (!static::isPreviewMode($arrOptions))
  884. {
  885. $time = Date::floorToMinute();
  886. $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<=$time) AND ($t.stop='' OR $t.stop>$time)";
  887. }
  888. return static::findBy($arrColumns, $arrOptions['dns'] ?? null, $arrOptions);
  889. }
  890. /**
  891. * Find the parent pages of a page
  892. *
  893. * @param integer $intId The page's ID
  894. *
  895. * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no parent pages
  896. */
  897. public static function findParentsById($intId)
  898. {
  899. $arrModels = array();
  900. while ($intId > 0 && ($objPage = static::findByPk($intId)) !== null)
  901. {
  902. $intId = $objPage->pid;
  903. $arrModels[] = $objPage;
  904. }
  905. if (empty($arrModels))
  906. {
  907. return null;
  908. }
  909. return static::createCollection($arrModels, 'tl_page');
  910. }
  911. /**
  912. * Find the first active page by its member groups
  913. *
  914. * @param array $arrIds An array of member group IDs
  915. *
  916. * @return PageModel|null The model or null if there is no matching member group
  917. */
  918. public static function findFirstActiveByMemberGroups($arrIds)
  919. {
  920. if (empty($arrIds) || !\is_array($arrIds))
  921. {
  922. return null;
  923. }
  924. $time = Date::floorToMinute();
  925. $objDatabase = Database::getInstance();
  926. $arrIds = array_map('\intval', $arrIds);
  927. $objResult = $objDatabase->prepare("SELECT p.* FROM tl_member_group g LEFT JOIN tl_page p ON g.jumpTo=p.id WHERE g.id IN(" . implode(',', $arrIds) . ") AND g.jumpTo>0 AND g.redirect='1' AND g.disable!='1' AND (g.start='' OR g.start<=$time) AND (g.stop='' OR g.stop>$time) AND p.published='1' AND (p.start='' OR p.start<=$time) AND (p.stop='' OR p.stop>$time) ORDER BY " . $objDatabase->findInSet('g.id', $arrIds))
  928. ->limit(1)
  929. ->execute();
  930. if ($objResult->numRows < 1)
  931. {
  932. return null;
  933. }
  934. $objRegistry = Registry::getInstance();
  935. /** @var PageModel|Model $objPage */
  936. if ($objPage = $objRegistry->fetch('tl_page', $objResult->id))
  937. {
  938. return $objPage;
  939. }
  940. return new static($objResult);
  941. }
  942. /**
  943. * Find a page by its ID and return it with the inherited details
  944. *
  945. * @param integer|string $intId The page's ID
  946. *
  947. * @return PageModel|null The model or null if there is no matching page
  948. */
  949. public static function findWithDetails($intId)
  950. {
  951. $objPage = static::findByPk($intId);
  952. if ($objPage === null)
  953. {
  954. return null;
  955. }
  956. return $objPage->loadDetails();
  957. }
  958. /**
  959. * Register the contao.dns-fallback alias when the model is attached to the registry
  960. *
  961. * @param Registry $registry The model registry
  962. */
  963. public function onRegister(Registry $registry)
  964. {
  965. parent::onRegister($registry);
  966. // Register this model as being the fallback page for a given dns
  967. if ($this->fallback && $this->type == 'root' && !$registry->isRegisteredAlias($this, 'contao.dns-fallback', $this->dns))
  968. {
  969. $registry->registerAlias($this, 'contao.dns-fallback', $this->dns);
  970. }
  971. }
  972. /**
  973. * Unregister the contao.dns-fallback alias when the model is detached from the registry
  974. *
  975. * @param Registry $registry The model registry
  976. */
  977. public function onUnregister(Registry $registry)
  978. {
  979. parent::onUnregister($registry);
  980. // Unregister the fallback page
  981. if ($this->fallback && $this->type == 'root' && $registry->isRegisteredAlias($this, 'contao.dns-fallback', $this->dns))
  982. {
  983. $registry->unregisterAlias($this, 'contao.dns-fallback', $this->dns);
  984. }
  985. }
  986. /**
  987. * Get the details of a page including inherited parameters
  988. *
  989. * @return PageModel The page model
  990. *
  991. * @throws NoRootPageFoundException If no root page is found
  992. */
  993. public function loadDetails()
  994. {
  995. // Loaded already
  996. if ($this->blnDetailsLoaded)
  997. {
  998. return $this;
  999. }
  1000. // Set some default values
  1001. $this->protected = (bool) $this->protected;
  1002. $this->groups = $this->protected ? StringUtil::deserialize($this->groups, true) : array();
  1003. $this->layout = ($this->includeLayout && $this->layout) ? $this->layout : false;
  1004. $this->cache = $this->includeCache ? $this->cache : false;
  1005. $this->alwaysLoadFromCache = $this->includeCache ? $this->alwaysLoadFromCache : false;
  1006. $this->clientCache = $this->includeCache ? $this->clientCache : false;
  1007. $pid = $this->pid;
  1008. $type = $this->type;
  1009. $alias = $this->alias;
  1010. $name = $this->title;
  1011. $title = $this->pageTitle ?: $this->title;
  1012. $folderUrl = '';
  1013. $palias = '';
  1014. $pname = '';
  1015. $ptitle = '';
  1016. $trail = array($this->id, $pid);
  1017. $time = time();
  1018. // Inherit the settings
  1019. if ($this->type == 'root')
  1020. {
  1021. $objParentPage = $this; // see #4610
  1022. }
  1023. else
  1024. {
  1025. // Load all parent pages
  1026. $objParentPage = self::findParentsById($pid);
  1027. if ($objParentPage !== null)
  1028. {
  1029. while ($pid > 0 && $type != 'root' && $objParentPage->next())
  1030. {
  1031. $pid = $objParentPage->pid;
  1032. $type = $objParentPage->type;
  1033. // Parent title
  1034. if (!$ptitle)
  1035. {
  1036. $palias = $objParentPage->alias;
  1037. $pname = $objParentPage->title;
  1038. $ptitle = $objParentPage->pageTitle ?: $objParentPage->title;
  1039. }
  1040. // Page title
  1041. if ($type != 'root')
  1042. {
  1043. // If $folderUrl is not yet set, use the alias of the first
  1044. // parent page if it is not a root page (see #2129)
  1045. if (!$folderUrl && $objParentPage->alias && $objParentPage->alias !== 'index' && $objParentPage->alias !== '/')
  1046. {
  1047. $folderUrl = $objParentPage->alias . '/';
  1048. }
  1049. $alias = $objParentPage->alias;
  1050. $name = $objParentPage->title;
  1051. $title = $objParentPage->pageTitle ?: $objParentPage->title;
  1052. $trail[] = $objParentPage->pid;
  1053. }
  1054. // Cache
  1055. if ($objParentPage->includeCache)
  1056. {
  1057. $this->cache = $this->cache !== false ? $this->cache : $objParentPage->cache;
  1058. $this->alwaysLoadFromCache = $this->alwaysLoadFromCache !== false ? $this->alwaysLoadFromCache : $objParentPage->alwaysLoadFromCache;
  1059. $this->clientCache = $this->clientCache !== false ? $this->clientCache : $objParentPage->clientCache;
  1060. }
  1061. // Layout
  1062. if ($objParentPage->includeLayout && $this->layout === false)
  1063. {
  1064. $this->layout = $objParentPage->subpageLayout ?: $objParentPage->layout;
  1065. }
  1066. // Protection
  1067. if ($objParentPage->protected && $this->protected === false)
  1068. {
  1069. $this->protected = true;
  1070. $this->groups = StringUtil::deserialize($objParentPage->groups, true);
  1071. }
  1072. }
  1073. }
  1074. // Set the titles
  1075. $this->mainAlias = $alias;
  1076. $this->mainTitle = $name;
  1077. $this->mainPageTitle = $title;
  1078. $this->parentAlias = $palias;
  1079. $this->parentTitle = $pname;
  1080. $this->parentPageTitle = $ptitle;
  1081. $this->folderUrl = $folderUrl;
  1082. }
  1083. $container = System::getContainer();
  1084. $request = $container->get('request_stack')->getCurrentRequest();
  1085. // Set the root ID and title
  1086. if ($objParentPage !== null && $objParentPage->type == 'root')
  1087. {
  1088. $this->rootId = $objParentPage->id;
  1089. $this->rootAlias = $objParentPage->alias;
  1090. $this->rootTitle = $objParentPage->title;
  1091. $this->rootPageTitle = $objParentPage->pageTitle ?: $objParentPage->title;
  1092. $this->rootSorting = $objParentPage->sorting;
  1093. $this->domain = $objParentPage->dns;
  1094. $this->rootLanguage = $objParentPage->language;
  1095. $this->language = $objParentPage->language;
  1096. $this->staticFiles = $objParentPage->staticFiles;
  1097. $this->staticPlugins = $objParentPage->staticPlugins;
  1098. $this->dateFormat = $objParentPage->dateFormat;
  1099. $this->timeFormat = $objParentPage->timeFormat;
  1100. $this->datimFormat = $objParentPage->datimFormat;
  1101. $this->validAliasCharacters = $objParentPage->validAliasCharacters;
  1102. $this->urlPrefix = $objParentPage->urlPrefix;
  1103. $this->urlSuffix = $objParentPage->urlSuffix;
  1104. $this->disableLanguageRedirect = $objParentPage->disableLanguageRedirect;
  1105. $this->adminEmail = $objParentPage->adminEmail;
  1106. $this->enforceTwoFactor = $objParentPage->enforceTwoFactor;
  1107. $this->twoFactorJumpTo = $objParentPage->twoFactorJumpTo;
  1108. $this->useFolderUrl = $objParentPage->useFolderUrl;
  1109. $this->mailerTransport = $objParentPage->mailerTransport;
  1110. $this->enableCanonical = $objParentPage->enableCanonical;
  1111. $this->useAutoItem = Config::get('useAutoItem');
  1112. $this->maintenanceMode = $objParentPage->maintenanceMode;
  1113. // Store whether the root page has been published
  1114. $this->rootIsPublic = ($objParentPage->published && (!$objParentPage->start || $objParentPage->start <= $time) && (!$objParentPage->stop || $objParentPage->stop > $time));
  1115. $this->rootIsFallback = (bool) $objParentPage->fallback;
  1116. $this->rootUseSSL = $objParentPage->useSSL;
  1117. $this->rootFallbackLanguage = $objParentPage->language;
  1118. // Store the fallback language (see #6874)
  1119. if (!$objParentPage->fallback)
  1120. {
  1121. $this->rootFallbackLanguage = null;
  1122. $objFallback = static::findPublishedFallbackByHostname($objParentPage->dns);
  1123. if ($objFallback !== null)
  1124. {
  1125. $this->rootFallbackLanguage = $objFallback->language;
  1126. }
  1127. }
  1128. if ($container->getParameter('contao.legacy_routing'))
  1129. {
  1130. $this->urlPrefix = $container->getParameter('contao.prepend_locale') ? LocaleUtil::formatAsLanguageTag($objParentPage->language) : '';
  1131. $this->urlSuffix = $container->getParameter('contao.url_suffix');
  1132. }
  1133. }
  1134. // No root page found
  1135. elseif ($request && $container->get('contao.routing.scope_matcher')->isFrontendRequest($request) && $this->type != 'root')
  1136. {
  1137. $container->get('monolog.logger.contao.error')->error('Page ID "' . $this->id . '" does not belong to a root page');
  1138. throw new NoRootPageFoundException('No root page found');
  1139. }
  1140. $this->trail = array_reverse($trail);
  1141. // Use the global date format if none is set (see #6104)
  1142. if (!$this->dateFormat)
  1143. {
  1144. $this->dateFormat = Config::get('dateFormat');
  1145. }
  1146. if (!$this->timeFormat)
  1147. {
  1148. $this->timeFormat = Config::get('timeFormat');
  1149. }
  1150. if (!$this->datimFormat)
  1151. {
  1152. $this->datimFormat = Config::get('datimFormat');
  1153. }
  1154. $this->isPublic = ($this->published && (!$this->start || $this->start <= $time) && (!$this->stop || $this->stop > $time));
  1155. // HOOK: add custom logic
  1156. if (!empty($GLOBALS['TL_HOOKS']['loadPageDetails']) && \is_array($GLOBALS['TL_HOOKS']['loadPageDetails']))
  1157. {
  1158. $parentModels = array();
  1159. if ($objParentPage instanceof Collection)
  1160. {
  1161. $parentModels = $objParentPage->getModels();
  1162. }
  1163. foreach ($GLOBALS['TL_HOOKS']['loadPageDetails'] as $callback)
  1164. {
  1165. System::importStatic($callback[0])->{$callback[1]}($parentModels, $this);
  1166. }
  1167. }
  1168. // Prevent saving (see #6506 and #7199)
  1169. $this->preventSaving();
  1170. $this->blnDetailsLoaded = true;
  1171. return $this;
  1172. }
  1173. /**
  1174. * Generate a front end URL
  1175. *
  1176. * @param string $strParams An optional string of URL parameters
  1177. * @param string $strForceLang Force a certain language
  1178. *
  1179. * @throws RouteNotFoundException
  1180. * @throws ResourceNotFoundException
  1181. *
  1182. * @return string A URL that can be used in the front end
  1183. */
  1184. public function getFrontendUrl($strParams=null, $strForceLang=null)
  1185. {
  1186. $page = $this;
  1187. $page->loadDetails();
  1188. if ($strForceLang !== null)
  1189. {
  1190. trigger_deprecation('contao/core-bundle', '4.0', 'Using "Contao\PageModel::getFrontendUrl()" with $strForceLang has been deprecated and will no longer work in Contao 5.0.');
  1191. $strForceLang = LocaleUtil::formatAsLanguageTag($strForceLang);
  1192. $page = $page->cloneOriginal();
  1193. $page->preventSaving(false);
  1194. $page->language = $strForceLang;
  1195. $page->rootLanguage = $strForceLang;
  1196. if (System::getContainer()->getParameter('contao.legacy_routing'))
  1197. {
  1198. $page->urlPrefix = System::getContainer()->getParameter('contao.prepend_locale') ? $strForceLang : '';
  1199. }
  1200. }
  1201. $objRouter = System::getContainer()->get('router');
  1202. $referenceType = $this->domain && $objRouter->getContext()->getHost() !== $this->domain ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH;
  1203. try
  1204. {
  1205. $strUrl = $objRouter->generate(PageRoute::PAGE_BASED_ROUTE_NAME, array(RouteObjectInterface::CONTENT_OBJECT => $page, 'parameters' => $strParams), $referenceType);
  1206. }
  1207. catch (RouteNotFoundException $e)
  1208. {
  1209. $pageRegistry = System::getContainer()->get('contao.routing.page_registry');
  1210. if (!$pageRegistry->isRoutable($this))
  1211. {
  1212. throw new ResourceNotFoundException(sprintf('Page ID %s is not routable', $this->id), 0, $e);
  1213. }
  1214. throw $e;
  1215. }
  1216. // Make the URL relative to the base path
  1217. if (0 === strncmp($strUrl, '/', 1))
  1218. {
  1219. $strUrl = substr($strUrl, \strlen(Environment::get('path')) + 1);
  1220. }
  1221. return $this->applyLegacyLogic($strUrl, $strParams);
  1222. }
  1223. /**
  1224. * Generate an absolute URL depending on the current rewriteURL setting
  1225. *
  1226. * @param string $strParams An optional string of URL parameters
  1227. *
  1228. * @throws RouteNotFoundException
  1229. * @throws ResourceNotFoundException
  1230. *
  1231. * @return string An absolute URL that can be used in the front end
  1232. */
  1233. public function getAbsoluteUrl($strParams=null)
  1234. {
  1235. $this->loadDetails();
  1236. $objRouter = System::getContainer()->get('router');
  1237. try
  1238. {
  1239. $strUrl = $objRouter->generate(PageRoute::PAGE_BASED_ROUTE_NAME, array(RouteObjectInterface::CONTENT_OBJECT => $this, 'parameters' => $strParams), UrlGeneratorInterface::ABSOLUTE_URL);
  1240. }
  1241. catch (RouteNotFoundException $e)
  1242. {
  1243. $pageRegistry = System::getContainer()->get('contao.routing.page_registry');
  1244. if (!$pageRegistry->isRoutable($this))
  1245. {
  1246. throw new ResourceNotFoundException(sprintf('Page ID %s is not routable', $this->id), 0, $e);
  1247. }
  1248. throw $e;
  1249. }
  1250. return $this->applyLegacyLogic($strUrl, $strParams);
  1251. }
  1252. /**
  1253. * Generate the front end preview URL
  1254. *
  1255. * @param string $strParams An optional string of URL parameters
  1256. *
  1257. * @throws RouteNotFoundException
  1258. * @throws ResourceNotFoundException
  1259. *
  1260. * @return string The front end preview URL
  1261. */
  1262. public function getPreviewUrl($strParams=null)
  1263. {
  1264. $container = System::getContainer();
  1265. if (!$previewScript = $container->getParameter('contao.preview_script'))
  1266. {
  1267. return $this->getAbsoluteUrl($strParams);
  1268. }
  1269. $this->loadDetails();
  1270. $context = $container->get('router')->getContext();
  1271. $baseUrl = $context->getBaseUrl();
  1272. // Add the preview script
  1273. $context->setBaseUrl($previewScript);
  1274. $objRouter = System::getContainer()->get('router');
  1275. try
  1276. {
  1277. $strUrl = $objRouter->generate(PageRoute::PAGE_BASED_ROUTE_NAME, array(RouteObjectInterface::CONTENT_OBJECT => $this, 'parameters' => $strParams), UrlGeneratorInterface::ABSOLUTE_URL);
  1278. }
  1279. catch (RouteNotFoundException $e)
  1280. {
  1281. $pageRegistry = System::getContainer()->get('contao.routing.page_registry');
  1282. if (!$pageRegistry->isRoutable($this))
  1283. {
  1284. throw new ResourceNotFoundException(sprintf('Page ID %s is not routable', $this->id), 0, $e);
  1285. }
  1286. throw $e;
  1287. }
  1288. finally
  1289. {
  1290. $context->setBaseUrl($baseUrl);
  1291. }
  1292. return $this->applyLegacyLogic($strUrl, $strParams);
  1293. }
  1294. /**
  1295. * Return the slug options
  1296. *
  1297. * @return array The slug options
  1298. */
  1299. public function getSlugOptions()
  1300. {
  1301. // Use primary language for slug generation, until fixed in ICU or ausi/slug-generator (see #2413)
  1302. $slugOptions = array('locale'=>LocaleUtil::getPrimaryLanguage($this->language));
  1303. if ($this->validAliasCharacters)
  1304. {
  1305. $slugOptions['validChars'] = $this->validAliasCharacters;
  1306. }
  1307. return $slugOptions;
  1308. }
  1309. /**
  1310. * Modifies a URL from the URL generator.
  1311. *
  1312. * @param string $strUrl
  1313. * @param string|null $strParams
  1314. *
  1315. * @return string
  1316. */
  1317. private function applyLegacyLogic($strUrl, $strParams)
  1318. {
  1319. // Decode sprintf placeholders
  1320. if ($strParams !== null && strpos($strParams, '%') !== false)
  1321. {
  1322. trigger_deprecation('contao/core-bundle', '4.2', 'Using sprintf placeholders in URLs has been deprecated and will no longer work in Contao 5.0.');
  1323. $arrMatches = array();
  1324. preg_match_all('/%([sducoxXbgGeEfF])/', $strParams, $arrMatches);
  1325. foreach (array_unique($arrMatches[1]) as $v)
  1326. {
  1327. $strUrl = str_replace('%25' . $v, '%' . $v, $strUrl);
  1328. }
  1329. }
  1330. // HOOK: add custom logic
  1331. if (isset($GLOBALS['TL_HOOKS']['generateFrontendUrl']) && \is_array($GLOBALS['TL_HOOKS']['generateFrontendUrl']))
  1332. {
  1333. trigger_deprecation('contao/core-bundle', '4.0', 'Using the "generateFrontendUrl" hook has been deprecated and will no longer work in Contao 5.0.');
  1334. foreach ($GLOBALS['TL_HOOKS']['generateFrontendUrl'] as $callback)
  1335. {
  1336. $strUrl = System::importStatic($callback[0])->{$callback[1]}($this->row(), $strParams ?? '', $strUrl);
  1337. }
  1338. return $strUrl;
  1339. }
  1340. return $strUrl;
  1341. }
  1342. private static function stripPrefixesAndSuffixes(string $alias, string $urlPrefix, string $urlSuffix): string
  1343. {
  1344. if (null === self::$prefixes || null === self::$suffixes)
  1345. {
  1346. $rows = Database::getInstance()
  1347. ->execute("SELECT urlPrefix, urlSuffix FROM tl_page WHERE type='root'")
  1348. ->fetchAllAssoc()
  1349. ;
  1350. self::$prefixes = array();
  1351. self::$suffixes = array();
  1352. foreach (array_column($rows, 'urlPrefix') as $prefix)
  1353. {
  1354. $prefix = trim($prefix, '/');
  1355. if ('' !== $prefix)
  1356. {
  1357. self::$prefixes[] = $prefix . '/';
  1358. }
  1359. }
  1360. foreach (array_column($rows, 'urlSuffix') as $suffix)
  1361. {
  1362. self::$suffixes[] = $suffix;
  1363. }
  1364. }
  1365. $prefixes = self::$prefixes;
  1366. if (!empty($urlPrefix))
  1367. {
  1368. $prefixes[] = $urlPrefix . '/';
  1369. }
  1370. if (null !== ($prefixRegex = self::regexArray($prefixes)))
  1371. {
  1372. $alias = preg_replace('/^' . $prefixRegex . '/i', '', $alias);
  1373. }
  1374. if (null !== ($suffixRegex = self::regexArray(array_merge(array($urlSuffix), self::$suffixes))))
  1375. {
  1376. $alias = preg_replace('/' . $suffixRegex . '$/i', '', $alias);
  1377. }
  1378. return $alias;
  1379. }
  1380. private static function regexArray(array $data): ?string
  1381. {
  1382. $data = array_filter(array_unique($data));
  1383. if (0 === \count($data))
  1384. {
  1385. return null;
  1386. }
  1387. usort($data, static fn ($v, $k) => \strlen($v));
  1388. foreach ($data as $k => $v)
  1389. {
  1390. $data[$k] = preg_quote($v, '/');
  1391. }
  1392. return '(' . implode('|', $data) . ')';
  1393. }
  1394. }
  1395. class_alias(PageModel::class, 'PageModel');