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

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