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

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