<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

/**
 * Automatically loads class files based on a mapper array
 *
 * The class stores namespaces and classes and automatically loads the class
 * files upon their first usage. It uses a mapper array to support complex
 * nesting and arbitrary subfolders to store the class files in.
 *
 * Usage:
 *
 *     ClassLoader::addNamespace('Custom');
 *     ClassLoader::addClass('Custom\Calendar', 'calendar/Calendar.php');
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 *
 * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.
 *             Use the Composer autoloader instead.
 */
class ClassLoader
{

	/**
	 * Known namespaces
	 * @var array
	 */
	protected static $namespaces = array('Contao');

	/**
	 * Known classes
	 * @var array
	 */
	protected static $classes = array();

	/**
	 * Add a new namespace
	 *
	 * @param string $name The namespace name
	 *
	 * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.
	 */
	public static function addNamespace($name)
	{
		@trigger_error('Using ClassLoader::addNamespace() has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

		if (\in_array($name, self::$namespaces))
		{
			return;
		}

		array_unshift(self::$namespaces, $name);
	}

	/**
	 * Add multiple new namespaces
	 *
	 * @param array $names An array of namespace names
	 *
	 * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.
	 */
	public static function addNamespaces($names)
	{
		@trigger_error('Using ClassLoader::addNamespaces() has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

		foreach ($names as $name)
		{
			self::addNamespace($name);
		}
	}

	/**
	 * Return the namespaces as array
	 *
	 * @return array An array of all namespaces
	 *
	 * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.
	 */
	public static function getNamespaces()
	{
		@trigger_error('Using ClassLoader::getNamespaces() has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

		return self::$namespaces;
	}

	/**
	 * Add a new class with its file path
	 *
	 * @param string $class The class name
	 * @param string $file  The path to the class file
	 *
	 * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.
	 */
	public static function addClass($class, $file)
	{
		@trigger_error('Using ClassLoader::addClass() has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

		self::$classes[$class] = $file;
	}

	/**
	 * Add multiple new classes with their file paths
	 *
	 * @param array $classes An array of classes
	 *
	 * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.
	 */
	public static function addClasses($classes)
	{
		@trigger_error('Using ClassLoader::addClasses() has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

		foreach ($classes as $class=>$file)
		{
			self::addClass($class, $file);
		}
	}

	/**
	 * Return the classes as array.
	 *
	 * @return array An array of all classes
	 *
	 * @deprecated Deprecated since Contao 4.2, to be removed in Contao 5.
	 */
	public static function getClasses()
	{
		@trigger_error('Using ClassLoader::getClasses() has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

		return self::$classes;
	}

	/**
	 * Autoload a class and create an alias in the global namespace
	 *
	 * To preserve backwards compatibility with Contao 2 extensions, all core
	 * classes will be aliased into the global namespace.
	 *
	 * @param string $class The class name
	 */
	public static function load($class)
	{
		if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false))
		{
			return;
		}

		$rootDir = System::getContainer()->getParameter('kernel.project_dir');

		// The class file is set in the mapper
		if (isset(self::$classes[$class]))
		{
			if (Config::get('debugMode'))
			{
				$GLOBALS['TL_DEBUG']['classes_set'][$class] = $class;
			}

			include $rootDir . '/' . self::$classes[$class];
		}

		// Find the class in the registered namespaces
		elseif (($namespaced = self::findClass($class)) !== null)
		{
			if (!class_exists($namespaced, false) && !interface_exists($namespaced, false) && !trait_exists($namespaced, false))
			{
				if (Config::get('debugMode'))
				{
					$GLOBALS['TL_DEBUG']['classes_aliased'][$class] = $namespaced;
				}

				include $rootDir . '/' . self::$classes[$namespaced];
			}

			class_alias($namespaced, $class);
		}

		// Try to map the class to a Contao class loaded via Composer
		elseif (strncmp($class, 'Contao\\', 7) !== 0)
		{
			$namespaced = 'Contao\\' . $class;

			if (class_exists($namespaced) || interface_exists($namespaced) || trait_exists($namespaced))
			{
				if (Config::get('debugMode'))
				{
					$GLOBALS['TL_DEBUG']['classes_composerized'][$class] = $namespaced;
				}

				if (!class_exists($class, false) && !interface_exists($class, false) && !trait_exists($class, false))
				{
					class_alias($namespaced, $class);
				}
			}
		}

		// Pass the request to other autoloaders (e.g. Swift)
	}

	/**
	 * Search the namespaces for a matching entry
	 *
	 * @param string $class The class name
	 *
	 * @return string|null The full path including the namespace or null
	 */
	protected static function findClass($class)
	{
		foreach (self::$namespaces as $namespace)
		{
			if (isset(self::$classes[$namespace . '\\' . $class]))
			{
				return $namespace . '\\' . $class;
			}
		}

		return null;
	}

	/**
	 * Register the autoloader
	 */
	public static function register()
	{
		spl_autoload_register('ClassLoader::load');
	}

	/**
	 * Scan the module directories for config/autoload.php files and then
	 * register the autoloader on the SPL stack
	 */
	public static function scanAndRegister()
	{
		$strCacheDir = System::getContainer()->getParameter('kernel.cache_dir');

		// Try to load from cache
		if (file_exists($strCacheDir . '/contao/config/autoload.php'))
		{
			include $strCacheDir . '/contao/config/autoload.php';
		}
		else
		{
			try
			{
				$files = System::getContainer()->get('contao.resource_locator')->locate('config/autoload.php', null, false);
			}
			catch (\InvalidArgumentException $e)
			{
				$files = array();
			}

			foreach ($files as $file)
			{
				include $file;
			}
		}

		self::register();
	}
}

class_alias(ClassLoader::class, 'ClassLoader');


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

use Contao\Model\Collection;

/**
 * Reads and writes content elements
 *
 * @property integer $id
 * @property integer $pid
 * @property string  $ptable
 * @property integer $sorting
 * @property integer $tstamp
 * @property string  $type
 * @property string  $headline
 * @property string  $text
 * @property boolean $addImage
 * @property string  $singleSRC
 * @property string  $alt
 * @property string  $title
 * @property string  $size
 * @property string  $imagemargin
 * @property string  $imageUrl
 * @property boolean $fullsize
 * @property string  $caption
 * @property string  $floating
 * @property string  $html
 * @property string  $listtype
 * @property string  $listitems
 * @property string  $tableitems
 * @property string  $summary
 * @property boolean $thead
 * @property boolean $tfoot
 * @property boolean $tleft
 * @property boolean $sortable
 * @property integer $sortIndex
 * @property string  $sortOrder
 * @property string  $mooHeadline
 * @property string  $mooStyle
 * @property string  $mooClasses
 * @property string  $highlight
 * @property string  $shClass
 * @property string  $code
 * @property string  $url
 * @property boolean $target
 * @property string  $titleText
 * @property string  $linkTitle
 * @property string  $embed
 * @property string  $rel
 * @property boolean $useImage
 * @property string  $multiSRC
 * @property string  $orderSRC
 * @property boolean $useHomeDir
 * @property integer $perRow
 * @property integer $perPage
 * @property integer $numberOfItems
 * @property string  $sortBy
 * @property boolean $metaIgnore
 * @property string  $galleryTpl
 * @property string  $customTpl
 * @property string  $playerSRC
 * @property string  $youtube
 * @property string  $posterSRC
 * @property string  $playerSize
 * @property integer $sliderDelay
 * @property integer $sliderSpeed
 * @property integer $sliderStartSlide
 * @property boolean $sliderContinuous
 * @property integer $cteAlias
 * @property integer $articleAlias
 * @property integer $article
 * @property integer $form
 * @property integer $module
 * @property boolean $protected
 * @property string  $groups
 * @property boolean $guests
 * @property string  $cssID
 * @property boolean $invisible
 * @property string  $start
 * @property string  $stop
 * @property string  $com_order
 * @property integer $com_perPage
 * @property boolean $com_moderate
 * @property boolean $com_bbcode
 * @property boolean $com_disableCaptcha
 * @property boolean $com_requireLogin
 * @property string  $com_template
 * @property string  $typePrefix
 * @property array   $classes
 * @property integer $origId
 *
 * @method static ContentModel|null findById($id, array $opt=array())
 * @method static ContentModel|null findByPk($id, array $opt=array())
 * @method static ContentModel|null findByIdOrAlias($val, array $opt=array())
 * @method static ContentModel|null findOneBy($col, $val, array $opt=array())
 * @method static ContentModel|null findOneByPid($val, array $opt=array())
 * @method static ContentModel|null findOneByPtable($val, array $opt=array())
 * @method static ContentModel|null findOneBySorting($val, array $opt=array())
 * @method static ContentModel|null findOneByTstamp($val, array $opt=array())
 * @method static ContentModel|null findOneByType($val, array $opt=array())
 * @method static ContentModel|null findOneByHeadline($val, array $opt=array())
 * @method static ContentModel|null findOneByText($val, array $opt=array())
 * @method static ContentModel|null findOneByAddImage($val, array $opt=array())
 * @method static ContentModel|null findOneBySingleSRC($val, array $opt=array())
 * @method static ContentModel|null findOneByAlt($val, array $opt=array())
 * @method static ContentModel|null findOneByTitle($val, array $opt=array())
 * @method static ContentModel|null findOneBySize($val, array $opt=array())
 * @method static ContentModel|null findOneByImagemargin($val, array $opt=array())
 * @method static ContentModel|null findOneByImageUrl($val, array $opt=array())
 * @method static ContentModel|null findOneByFullsize($val, array $opt=array())
 * @method static ContentModel|null findOneByCaption($val, array $opt=array())
 * @method static ContentModel|null findOneByFloating($val, array $opt=array())
 * @method static ContentModel|null findOneByHtml($val, array $opt=array())
 * @method static ContentModel|null findOneByListtype($val, array $opt=array())
 * @method static ContentModel|null findOneByListitems($val, array $opt=array())
 * @method static ContentModel|null findOneByTableitems($val, array $opt=array())
 * @method static ContentModel|null findOneBySummary($val, array $opt=array())
 * @method static ContentModel|null findOneByThead($val, array $opt=array())
 * @method static ContentModel|null findOneByTfoot($val, array $opt=array())
 * @method static ContentModel|null findOneByTleft($val, array $opt=array())
 * @method static ContentModel|null findOneBySortable($val, array $opt=array())
 * @method static ContentModel|null findOneBySortIndex($val, array $opt=array())
 * @method static ContentModel|null findOneBySortOrder($val, array $opt=array())
 * @method static ContentModel|null findOneByMooHeadline($val, array $opt=array())
 * @method static ContentModel|null findOneByMooStyle($val, array $opt=array())
 * @method static ContentModel|null findOneByMooClasses($val, array $opt=array())
 * @method static ContentModel|null findOneByHighlight($val, array $opt=array())
 * @method static ContentModel|null findOneByShClass($val, array $opt=array())
 * @method static ContentModel|null findOneByCode($val, array $opt=array())
 * @method static ContentModel|null findOneByUrl($val, array $opt=array())
 * @method static ContentModel|null findOneByTarget($val, array $opt=array())
 * @method static ContentModel|null findOneByTitleText($val, array $opt=array())
 * @method static ContentModel|null findOneByLinkTitle($val, array $opt=array())
 * @method static ContentModel|null findOneByEmbed($val, array $opt=array())
 * @method static ContentModel|null findOneByRel($val, array $opt=array())
 * @method static ContentModel|null findOneByUseImage($val, array $opt=array())
 * @method static ContentModel|null findOneByMultiSRC($val, array $opt=array())
 * @method static ContentModel|null findOneByOrderSRC($val, array $opt=array())
 * @method static ContentModel|null findOneByUseHomeDir($val, array $opt=array())
 * @method static ContentModel|null findOneByPerRow($val, array $opt=array())
 * @method static ContentModel|null findOneByPerPage($val, array $opt=array())
 * @method static ContentModel|null findOneByNumberOfItems($val, array $opt=array())
 * @method static ContentModel|null findOneBySortBy($val, array $opt=array())
 * @method static ContentModel|null findOneByMetaIgnore($val, array $opt=array())
 * @method static ContentModel|null findOneByGalleryTpl($val, array $opt=array())
 * @method static ContentModel|null findOneByCustomTpl($val, array $opt=array())
 * @method static ContentModel|null findOneByPlayerSRC($val, array $opt=array())
 * @method static ContentModel|null findOneByYoutube($val, array $opt=array())
 * @method static ContentModel|null findOneByPosterSRC($val, array $opt=array())
 * @method static ContentModel|null findOneByPlayerSize($val, array $opt=array())
 * @method static ContentModel|null findOneByAutoplay($val, array $opt=array())
 * @method static ContentModel|null findOneBySliderDelay($val, array $opt=array())
 * @method static ContentModel|null findOneBySliderSpeed($val, array $opt=array())
 * @method static ContentModel|null findOneBySliderStartSlide($val, array $opt=array())
 * @method static ContentModel|null findOneBySliderContinuous($val, array $opt=array())
 * @method static ContentModel|null findOneByCteAlias($val, array $opt=array())
 * @method static ContentModel|null findOneByArticleAlias($val, array $opt=array())
 * @method static ContentModel|null findOneByArticle($val, array $opt=array())
 * @method static ContentModel|null findOneByForm($val, array $opt=array())
 * @method static ContentModel|null findOneByModule($val, array $opt=array())
 * @method static ContentModel|null findOneByProtected($val, array $opt=array())
 * @method static ContentModel|null findOneByGroups($val, array $opt=array())
 * @method static ContentModel|null findOneByGuests($val, array $opt=array())
 * @method static ContentModel|null findOneByCssID($val, array $opt=array())
 * @method static ContentModel|null findOneBySpace($val, array $opt=array())
 * @method static ContentModel|null findOneByInvisible($val, array $opt=array())
 * @method static ContentModel|null findOneByStart($val, array $opt=array())
 * @method static ContentModel|null findOneByStop($val, array $opt=array())
 * @method static ContentModel|null findOneByCom_order($val, array $opt=array())
 * @method static ContentModel|null findOneByCom_perPage($val, array $opt=array())
 * @method static ContentModel|null findOneByCom_moderate($val, array $opt=array())
 * @method static ContentModel|null findOneByCom_bbcode($val, array $opt=array())
 * @method static ContentModel|null findOneByCom_disableCaptcha($val, array $opt=array())
 * @method static ContentModel|null findOneByCom_requireLogin($val, array $opt=array())
 * @method static ContentModel|null findOneByCom_template($val, array $opt=array())
 *
 * @method static Collection|ContentModel[]|ContentModel|null findByPid($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByPtable($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySorting($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByTstamp($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByType($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByHeadline($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByText($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByAddImage($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySingleSRC($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByAlt($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByTitle($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySize($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByImagemargin($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByImageUrl($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByFullsize($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCaption($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByFloating($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByHtml($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByListtype($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByListitems($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByTableitems($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySummary($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByThead($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByTfoot($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByTleft($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySortable($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySortIndex($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySortOrder($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByMooHeadline($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByMooStyle($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByMooClasses($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByHighlight($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByShClass($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCode($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByUrl($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByTarget($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByTitleText($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByLinkTitle($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByEmbed($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByRel($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByUseImage($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByMultiSRC($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByOrderSRC($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByUseHomeDir($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByPerRow($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByPerPage($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByNumberOfItems($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySortBy($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByMetaIgnore($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByGalleryTpl($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCustomTpl($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByPlayerSRC($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByYoutube($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByPosterSRC($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByPlayerSize($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByAutoplay($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySliderDelay($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySliderSpeed($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySliderStartSlide($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySliderContinuous($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCteAlias($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByArticleAlias($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByArticle($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByForm($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByModule($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByProtected($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByGroups($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByGuests($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCssID($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBySpace($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByInvisible($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByStart($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByStop($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCom_order($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCom_perPage($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCom_moderate($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCom_bbcode($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCom_disableCaptcha($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCom_requireLogin($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findByCom_template($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findMultipleByIds($val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findBy($col, $val, array $opt=array())
 * @method static Collection|ContentModel[]|ContentModel|null findAll(array $opt=array())
 *
 * @method static integer countById($id, array $opt=array())
 * @method static integer countByPid($val, array $opt=array())
 * @method static integer countByPtable($val, array $opt=array())
 * @method static integer countBySorting($val, array $opt=array())
 * @method static integer countByTstamp($val, array $opt=array())
 * @method static integer countByType($val, array $opt=array())
 * @method static integer countByHeadline($val, array $opt=array())
 * @method static integer countByText($val, array $opt=array())
 * @method static integer countByAddImage($val, array $opt=array())
 * @method static integer countBySingleSRC($val, array $opt=array())
 * @method static integer countByAlt($val, array $opt=array())
 * @method static integer countByTitle($val, array $opt=array())
 * @method static integer countBySize($val, array $opt=array())
 * @method static integer countByImagemargin($val, array $opt=array())
 * @method static integer countByImageUrl($val, array $opt=array())
 * @method static integer countByFullsize($val, array $opt=array())
 * @method static integer countByCaption($val, array $opt=array())
 * @method static integer countByFloating($val, array $opt=array())
 * @method static integer countByHtml($val, array $opt=array())
 * @method static integer countByListtype($val, array $opt=array())
 * @method static integer countByListitems($val, array $opt=array())
 * @method static integer countByTableitems($val, array $opt=array())
 * @method static integer countBySummary($val, array $opt=array())
 * @method static integer countByThead($val, array $opt=array())
 * @method static integer countByTfoot($val, array $opt=array())
 * @method static integer countByTleft($val, array $opt=array())
 * @method static integer countBySortable($val, array $opt=array())
 * @method static integer countBySortIndex($val, array $opt=array())
 * @method static integer countBySortOrder($val, array $opt=array())
 * @method static integer countByMooHeadline($val, array $opt=array())
 * @method static integer countByMooStyle($val, array $opt=array())
 * @method static integer countByMooClasses($val, array $opt=array())
 * @method static integer countByHighlight($val, array $opt=array())
 * @method static integer countByShClass($val, array $opt=array())
 * @method static integer countByCode($val, array $opt=array())
 * @method static integer countByUrl($val, array $opt=array())
 * @method static integer countByTarget($val, array $opt=array())
 * @method static integer countByTitleText($val, array $opt=array())
 * @method static integer countByLinkTitle($val, array $opt=array())
 * @method static integer countByEmbed($val, array $opt=array())
 * @method static integer countByRel($val, array $opt=array())
 * @method static integer countByUseImage($val, array $opt=array())
 * @method static integer countByMultiSRC($val, array $opt=array())
 * @method static integer countByOrderSRC($val, array $opt=array())
 * @method static integer countByUseHomeDir($val, array $opt=array())
 * @method static integer countByPerRow($val, array $opt=array())
 * @method static integer countByPerPage($val, array $opt=array())
 * @method static integer countByNumberOfItems($val, array $opt=array())
 * @method static integer countBySortBy($val, array $opt=array())
 * @method static integer countByMetaIgnore($val, array $opt=array())
 * @method static integer countByGalleryTpl($val, array $opt=array())
 * @method static integer countByCustomTpl($val, array $opt=array())
 * @method static integer countByPlayerSRC($val, array $opt=array())
 * @method static integer countByYoutube($val, array $opt=array())
 * @method static integer countByPosterSRC($val, array $opt=array())
 * @method static integer countByPlayerSize($val, array $opt=array())
 * @method static integer countByAutoplay($val, array $opt=array())
 * @method static integer countBySliderDelay($val, array $opt=array())
 * @method static integer countBySliderSpeed($val, array $opt=array())
 * @method static integer countBySliderStartSlide($val, array $opt=array())
 * @method static integer countBySliderContinuous($val, array $opt=array())
 * @method static integer countByCteAlias($val, array $opt=array())
 * @method static integer countByArticleAlias($val, array $opt=array())
 * @method static integer countByArticle($val, array $opt=array())
 * @method static integer countByForm($val, array $opt=array())
 * @method static integer countByModule($val, array $opt=array())
 * @method static integer countByProtected($val, array $opt=array())
 * @method static integer countByGroups($val, array $opt=array())
 * @method static integer countByGuests($val, array $opt=array())
 * @method static integer countByCssID($val, array $opt=array())
 * @method static integer countBySpace($val, array $opt=array())
 * @method static integer countByInvisible($val, array $opt=array())
 * @method static integer countByStart($val, array $opt=array())
 * @method static integer countByStop($val, array $opt=array())
 * @method static integer countByCom_order($val, array $opt=array())
 * @method static integer countByCom_perPage($val, array $opt=array())
 * @method static integer countByCom_moderate($val, array $opt=array())
 * @method static integer countByCom_bbcode($val, array $opt=array())
 * @method static integer countByCom_disableCaptcha($val, array $opt=array())
 * @method static integer countByCom_requireLogin($val, array $opt=array())
 * @method static integer countByCom_template($val, array $opt=array())
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class ContentModel extends Model
{

	/**
	 * Table name
	 * @var string
	 */
	protected static $strTable = 'tl_content';

	/**
	 * Find all published content elements by their parent ID and parent table
	 *
	 * @param integer $intPid         The article ID
	 * @param string  $strParentTable The parent table name
	 * @param array   $arrOptions     An optional options array
	 *
	 * @return Collection|ContentModel[]|ContentModel|null A collection of models or null if there are no content elements
	 */
	public static function findPublishedByPidAndTable($intPid, $strParentTable, array $arrOptions=array())
	{
		$t = static::$strTable;

		// Also handle empty ptable fields
		if ($strParentTable == 'tl_article')
		{
			$arrColumns = array("$t.pid=? AND ($t.ptable=? OR $t.ptable='')");
		}
		else
		{
			$arrColumns = array("$t.pid=? AND $t.ptable=?");
		}

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.invisible=''";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::findBy($arrColumns, array($intPid, $strParentTable), $arrOptions);
	}

	/**
	 * Find all published content elements by their parent ID and parent table
	 *
	 * @param integer $intPid         The article ID
	 * @param string  $strParentTable The parent table name
	 * @param array   $arrOptions     An optional options array
	 *
	 * @return integer The number of matching rows
	 */
	public static function countPublishedByPidAndTable($intPid, $strParentTable, array $arrOptions=array())
	{
		$t = static::$strTable;

		// Also handle empty ptable fields (backwards compatibility)
		if ($strParentTable == 'tl_article')
		{
			$arrColumns = array("$t.pid=? AND ($t.ptable=? OR $t.ptable='')");
		}
		else
		{
			$arrColumns = array("$t.pid=? AND $t.ptable=?");
		}

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.invisible=''";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::countBy($arrColumns, array($intPid, $strParentTable), $arrOptions);
	}
}

class_alias(ContentModel::class, 'ContentModel');


<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\ManagerBundle\ContaoManager\ApiCommand;

use Contao\ManagerBundle\Api\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;

class RemoveDotEnvCommand extends Command
{
    /**
     * @var string
     */
    private $projectDir;

    public function __construct(Application $application)
    {
        parent::__construct();

        $this->projectDir = $application->getProjectDir();
    }

    /**
     * {@inheritdoc}
     */
    protected function configure(): void
    {
        parent::configure();

        $this
            ->setName('dot-env:remove')
            ->setDescription('Removes a parameter from the .env file.')
            ->addArgument('key', InputArgument::REQUIRED, 'The variable name')
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output): void
    {
        $fs = new Filesystem();
        $path = $this->projectDir.'/.env';

        if (!$fs->exists($path)) {
            return;
        }

        $content = '';
        $lines = file($path, FILE_IGNORE_NEW_LINES);

        if (false === $lines) {
            throw new \RuntimeException(sprintf('Could not read "%s" file.', $path));
        }

        $key = $input->getArgument('key');

        foreach ($lines as $line) {
            if (0 === strpos($line, $key.'=')) {
                continue;
            }

            $content .= $line."\n";
        }

        if (empty($content)) {
            $fs->remove($path);
        } else {
            $fs->dumpFile($path, $content);
        }
    }
}


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

use Contao\CoreBundle\Event\ContaoCoreEvents;
use Contao\CoreBundle\Event\PreviewUrlConvertEvent;
use Contao\CoreBundle\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * Set up the front end preview frames.
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class BackendPreview extends Backend
{

	/**
	 * Initialize the controller
	 *
	 * 1. Import the user
	 * 2. Call the parent constructor
	 * 3. Authenticate the user
	 * 4. Load the language files
	 * DO NOT CHANGE THIS ORDER!
	 */
	public function __construct()
	{
		$this->import(BackendUser::class, 'User');
		parent::__construct();

		if (!System::getContainer()->get('security.authorization_checker')->isGranted('ROLE_USER'))
		{
			throw new AccessDeniedException('Access denied');
		}

		System::loadLanguageFile('default');
	}

	/**
	 * Run the controller and parse the template
	 *
	 * @return Response
	 */
	public function run()
	{
		$objRouter = System::getContainer()->get('router');

		// Switch to a particular member (see #6546)
		if ($strUser = Input::get('user'))
		{
			$objAuthenticator = System::getContainer()->get('contao.security.frontend_preview_authenticator');

			if (!$objAuthenticator->authenticateFrontendUser($strUser, false))
			{
				$objAuthenticator->removeFrontendAuthentication();
			}

			$arrParameters = array();

			if (Input::get('url'))
			{
				$arrParameters['url'] = Input::get('url');
			}

			if (Input::get('page'))
			{
				$arrParameters['page'] = Input::get('page');
			}

			return new RedirectResponse($objRouter->generate('contao_backend_preview', $arrParameters));
		}

		$objTemplate = new BackendTemplate('be_preview');
		$objTemplate->base = Environment::get('base');
		$objTemplate->language = $GLOBALS['TL_LANGUAGE'];
		$objTemplate->title = StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['fePreview']);
		$objTemplate->host = Environment::get('host');
		$objTemplate->charset = Config::get('characterSet');
		$objTemplate->site = Input::get('site', true);
		$objTemplate->switchHref = $objRouter->generate('contao_backend_switch');
		$objTemplate->user = System::getContainer()->get('contao.security.token_checker')->getFrontendUsername();

		$strUrl = null;

		if (Input::get('url'))
		{
			$strUrl = Environment::get('base') . Input::get('url');
		}
		elseif (Input::get('page'))
		{
			$strUrl = $this->redirectToFrontendPage(Input::get('page'), Input::get('article'), true);
		}
		else
		{
			$event = new PreviewUrlConvertEvent();
			System::getContainer()->get('event_dispatcher')->dispatch(ContaoCoreEvents::PREVIEW_URL_CONVERT, $event);
			$strUrl = $event->getUrl();
		}

		if ($strUrl === null)
		{
			$strUrl = System::getContainer()->get('router')->generate('contao_root', array(), UrlGeneratorInterface::ABSOLUTE_URL);
		}

		$objTemplate->url = $strUrl;

		return $objTemplate->getResponse();
	}
}

class_alias(BackendPreview::class, 'BackendPreview');


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * Provide methods to handle file uploads in the back end.
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class FileUpload extends Backend
{

	/**
	 * Error indicator
	 * @var boolean
	 */
	protected $blnHasError = false;

	/**
	 * Resized indicator
	 * @var boolean
	 */
	protected $blnHasResized = false;

	/**
	 * Field name
	 * @var string
	 */
	protected $strName = 'files';

	/**
	 * Make the constructor public
	 */
	public function __construct()
	{
		parent::__construct();
	}

	/**
	 * Return true if there was an error
	 *
	 * @return boolean
	 */
	public function hasError()
	{
		return $this->blnHasError;
	}

	/**
	 * Return true if there was a resized image
	 *
	 * @return boolean
	 */
	public function hasResized()
	{
		return $this->blnHasResized;
	}

	/**
	 * Override the field name
	 *
	 * @param string $strName
	 */
	public function setName($strName)
	{
		$this->strName = $strName;
	}

	/**
	 * Check the uploaded files and move them to the target directory
	 *
	 * @param string $strTarget
	 *
	 * @return array
	 *
	 * @throws \Exception
	 */
	public function uploadTo($strTarget)
	{
		if ($strTarget == '' || Validator::isInsecurePath($strTarget))
		{
			throw new \InvalidArgumentException('Invalid target path ' . $strTarget);
		}

		$maxlength_kb = static::getMaxUploadSize();
		$maxlength_kb_readable = $this->getReadableSize($maxlength_kb);
		$arrUploaded = array();
		$arrFiles = $this->getFilesFromGlobal();

		foreach ($arrFiles as $file)
		{
			// Sanitize the filename
			try
			{
				$file['name'] = StringUtil::sanitizeFileName($file['name']);
			}
			catch (\InvalidArgumentException $e)
			{
				Message::addError($GLOBALS['TL_LANG']['ERR']['filename']);
				$this->blnHasError = true;

				continue;
			}

			// Invalid file name
			if (!Validator::isValidFileName($file['name']))
			{
				Message::addError($GLOBALS['TL_LANG']['ERR']['filename']);
				$this->blnHasError = true;
			}

			// File was not uploaded
			elseif (!is_uploaded_file($file['tmp_name']))
			{
				if ($file['error'] == 1 || $file['error'] == 2)
				{
					Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['filesize'], $maxlength_kb_readable));
					$this->blnHasError = true;
				}
				elseif ($file['error'] == 3)
				{
					Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['filepartial'], $file['name']));
					$this->blnHasError = true;
				}
				elseif ($file['error'] > 0)
				{
					Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['fileerror'], $file['error'], $file['name']));
					$this->blnHasError = true;
				}
			}

			// File is too big
			elseif ($file['size'] > $maxlength_kb)
			{
				Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['filesize'], $maxlength_kb_readable));
				$this->blnHasError = true;
			}

			else
			{
				$strExtension = strtolower(substr($file['name'], strrpos($file['name'], '.') + 1));

				// Image is too big
				if (\in_array($strExtension, array('gif', 'jpg', 'jpeg', 'png')) && System::getContainer()->getParameter('contao.image.reject_large_uploads'))
				{
					$arrImageSize = getimagesize($file['tmp_name']);

					if ($arrImageSize[0] > Config::get('gdMaxImgWidth') || $arrImageSize[1] > Config::get('gdMaxImgHeight'))
					{
						Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['largeImage'], Config::get('gdMaxImgWidth'), Config::get('gdMaxImgHeight')));
						$this->blnHasError = true;

						continue;
					}
				}

				// File type not allowed
				if (!\in_array($strExtension, StringUtil::trimsplit(',', strtolower(Config::get('uploadTypes')))))
				{
					Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['filetype'], $strExtension));
					$this->blnHasError = true;
				}

				// Move the file to its destination
				else
				{
					$this->import(Files::class, 'Files');
					$strNewFile = $strTarget . '/' . $file['name'];

					// Set CHMOD and resize if neccessary
					if ($this->Files->move_uploaded_file($file['tmp_name'], $strNewFile))
					{
						$this->Files->chmod($strNewFile, 0666 & ~umask());

						// Notify the user
						Message::addConfirmation(sprintf($GLOBALS['TL_LANG']['MSC']['fileUploaded'], $file['name']));
						$this->log('File "' . $strNewFile . '" has been uploaded', __METHOD__, TL_FILES);

						// Resize the uploaded image if necessary
						$this->resizeUploadedImage($strNewFile);

						$arrUploaded[] = $strNewFile;
					}
				}
			}
		}

		return $arrUploaded;
	}

	/**
	 * Generate the markup for the default uploader
	 *
	 * @return string
	 */
	public function generateMarkup()
	{
		$return = '

    <input type="file" name="' . $this->strName . '[]" class="tl_upload_field" onfocus="Backend.getScrollOffset()" multiple required>

';

		if (isset($GLOBALS['TL_LANG']['tl_files']['fileupload'][1]))
		{
			$return .= '

' . sprintf($GLOBALS['TL_LANG']['tl_files']['fileupload'][1], System::getReadableSize(static::getMaxUploadSize()), Config::get('gdMaxImgWidth') . 'x' . Config::get('gdMaxImgHeight')) . '
';
		}

		return $return;
	}

	/**
	 * Get the files from the global $_FILES array
	 *
	 * @return array
	 */
	protected function getFilesFromGlobal()
	{
		// The "multiple" attribute is not set
		if (!\is_array($_FILES[$this->strName]['name']))
		{
			return array($_FILES[$this->strName]);
		}

		$arrFiles = array();
		$intCount = \count($_FILES[$this->strName]['name']);

		for ($i=0; $i<$intCount; $i++)
		{
			if ($_FILES[$this->strName]['name'][$i] == '')
			{
				continue;
			}

			$arrFiles[] = array
			(
				'name'     => $_FILES[$this->strName]['name'][$i],
				'type'     => $_FILES[$this->strName]['type'][$i],
				'tmp_name' => $_FILES[$this->strName]['tmp_name'][$i],
				'error'    => $_FILES[$this->strName]['error'][$i],
				'size'     => $_FILES[$this->strName]['size'][$i],
			);
		}

		return $arrFiles;
	}

	/**
	 * Return the maximum upload file size in bytes
	 *
	 * @return string
	 *
	 * @deprecated Deprecated since Contao 4.6, to be removed in Contao 5.0.
	 *             Use FileUpload::getMaxUploadSize() instead.
	 */
	protected function getMaximumUploadSize()
	{
		@trigger_error('Using FileUpload::getMaximumUploadSize() has been deprecated and will no longer work in Contao 5.0. Use FileUpload::getMaxUploadSize() instead.', E_USER_DEPRECATED);

		return static::getMaxUploadSize();
	}

	/**
	 * Return the maximum upload file size in bytes
	 *
	 * @return string
	 */
	public static function getMaxUploadSize()
	{
		return min(UploadedFile::getMaxFilesize(), Config::get('maxFileSize'));
	}

	/**
	 * Resize an uploaded image if neccessary
	 *
	 * @param string $strImage
	 *
	 * @return boolean
	 */
	protected function resizeUploadedImage($strImage)
	{
		// The feature is disabled
		if (Config::get('imageWidth') < 1 && Config::get('imageHeight') < 1)
		{
			return false;
		}

		$objFile = new File($strImage);

		// Not an image
		if (!$objFile->isSvgImage && !$objFile->isGdImage)
		{
			return false;
		}

		$arrImageSize = $objFile->imageSize;

		// The image is too big to be handled by the GD library
		if ($objFile->isGdImage && ($arrImageSize[0] > Config::get('gdMaxImgWidth') || $arrImageSize[1] > Config::get('gdMaxImgHeight')))
		{
			Message::addInfo(sprintf($GLOBALS['TL_LANG']['MSC']['fileExceeds'], $objFile->basename));
			$this->log('File "' . $strImage . '" is too big to be resized automatically', __METHOD__, TL_FILES);

			return false;
		}

		$blnResize = false;

		// The image exceeds the maximum image width
		if ($arrImageSize[0] > Config::get('imageWidth'))
		{
			$blnResize = true;
			$intWidth = Config::get('imageWidth');
			$intHeight = round(Config::get('imageWidth') * $arrImageSize[1] / $arrImageSize[0]);
			$arrImageSize = array($intWidth, $intHeight);
		}

		// The image exceeds the maximum image height
		if ($arrImageSize[1] > Config::get('imageHeight'))
		{
			$blnResize = true;
			$intWidth = round(Config::get('imageHeight') * $arrImageSize[0] / $arrImageSize[1]);
			$intHeight = Config::get('imageHeight');
			$arrImageSize = array($intWidth, $intHeight);
		}

		// Resized successfully
		if ($blnResize)
		{
			$container = System::getContainer();
			$rootDir = $container->getParameter('kernel.project_dir');
			$container->get('contao.image.image_factory')->create($rootDir . '/' . $strImage, array($arrImageSize[0], $arrImageSize[1]), $rootDir . '/' . $strImage);

			Message::addInfo(sprintf($GLOBALS['TL_LANG']['MSC']['fileResized'], $objFile->basename));
			$this->log('File "' . $strImage . '" was scaled down to the maximum dimensions', __METHOD__, TL_FILES);
			$this->blnHasResized = true;

			return true;
		}

		return false;
	}
}

class_alias(FileUpload::class, 'FileUpload');


<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\CoreBundle\DependencyInjection;

use Contao\CoreBundle\Picker\PickerProviderInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Gd\Imagine;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class ContaoCoreExtension extends Extension
{
    /**
     * {@inheritdoc}
     */
    public function getAlias(): string
    {
        return 'contao';
    }

    /**
     * {@inheritdoc}
     */
    public function getConfiguration(array $config, ContainerBuilder $container): Configuration
    {
        return new Configuration(
             $container->getParameter('kernel.project_dir'),
             $container->getParameter('kernel.default_locale')
         );
    }

    /**
     * {@inheritdoc}
     */
    public function load(array $configs, ContainerBuilder $container): void
    {
        $configuration = new Configuration(
            $container->getParameter('kernel.project_dir'),
            $container->getParameter('kernel.default_locale')
        );

        $config = $this->processConfiguration($configuration, $configs);

        $loader = new YamlFileLoader(
            $container,
            new FileLocator(__DIR__.'/../Resources/config')
        );

        static $files = [
            'commands.yml',
            'listener.yml',
            'services.yml',
        ];

        foreach ($files as $file) {
            $loader->load($file);
        }

        $container->setParameter('contao.web_dir', $config['web_dir']);
        $container->setParameter('contao.prepend_locale', $config['prepend_locale']);
        $container->setParameter('contao.encryption_key', $config['encryption_key']);
        $container->setParameter('contao.url_suffix', $config['url_suffix']);
        $container->setParameter('contao.upload_path', $config['upload_path']);
        $container->setParameter('contao.csrf_token_name', $config['csrf_token_name']);
        $container->setParameter('contao.pretty_error_screens', $config['pretty_error_screens']);
        $container->setParameter('contao.error_level', $config['error_level']);
        $container->setParameter('contao.locales', $config['locales']);
        $container->setParameter('contao.image.bypass_cache', $config['image']['bypass_cache']);
        $container->setParameter('contao.image.target_dir', $config['image']['target_dir']);
        $container->setParameter('contao.image.valid_extensions', $config['image']['valid_extensions']);
        $container->setParameter('contao.image.imagine_options', $config['image']['imagine_options']);
        $container->setParameter('contao.image.reject_large_uploads', $config['image']['reject_large_uploads']);
        $container->setParameter('contao.security.two_factor.enforce_backend', $config['security']['two_factor']['enforce_backend']);

        if (isset($config['localconfig'])) {
            $container->setParameter('contao.localconfig', $config['localconfig']);
        }

        $this->setImagineService($config, $container);
        $this->overwriteImageTargetDir($config, $container);

        $container
            ->registerForAutoconfiguration(PickerProviderInterface::class)
            ->addTag('contao.picker_provider')
        ;
    }

    /**
     * Configures the "contao.image.imagine" service.
     */
    private function setImagineService(array $config, ContainerBuilder $container): void
    {
        $imagineServiceId = $config['image']['imagine_service'];

        // Generate if not present
        if (null === $imagineServiceId) {
            $class = $this->getImagineImplementation();
            $imagineServiceId = 'contao.image.imagine.'.ContainerBuilder::hash($class);

            $container->setDefinition($imagineServiceId, new Definition($class));
        }

        $container->setAlias('contao.image.imagine', $imagineServiceId)->setPublic(true);
    }

    private function getImagineImplementation(): string
    {
        static $magicks = ['Gmagick', 'Imagick'];

        foreach ($magicks as $name) {
            $class = 'Imagine\\'.$name.'\Imagine';

            // Will throw an exception if the PHP implementation is not available
            try {
                new $class();
            } catch (RuntimeException $e) {
                continue;
            }

            return $class;
        }

        return Imagine::class; // see #616
    }

    /**
     * Reads the old contao.image.target_path parameter.
     */
    private function overwriteImageTargetDir(array $config, ContainerBuilder $container): void
    {
        if (!isset($config['image']['target_path'])) {
            return;
        }

        $container->setParameter(
            'contao.image.target_dir',
            $container->getParameter('kernel.project_dir').'/'.$config['image']['target_path']
        );

        @trigger_error('Using the contao.image.target_path parameter has been deprecated and will no longer work in Contao 5.0. Use the contao.image.target_dir parameter instead.', E_USER_DEPRECATED);
    }
}


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

/**
 * Provide methods regarding FAQs.
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class ModuleFaq extends Frontend
{

	/**
	 * Add FAQs to the indexer
	 *
	 * @param array   $arrPages
	 * @param integer $intRoot
	 * @param boolean $blnIsSitemap
	 *
	 * @return array
	 */
	public function getSearchablePages($arrPages, $intRoot=0, $blnIsSitemap=false)
	{
		$arrRoot = array();

		if ($intRoot > 0)
		{
			$arrRoot = $this->Database->getChildRecords($intRoot, 'tl_page');
		}

		$arrProcessed = array();
		$time = Date::floorToMinute();

		// Get all categories
		$objFaq = FaqCategoryModel::findAll();

		// Walk through each category
		if ($objFaq !== null)
		{
			while ($objFaq->next())
			{
				// Skip FAQs without target page
				if (!$objFaq->jumpTo)
				{
					continue;
				}

				// Skip FAQs outside the root nodes
				if (!empty($arrRoot) && !\in_array($objFaq->jumpTo, $arrRoot))
				{
					continue;
				}

				// Get the URL of the jumpTo page
				if (!isset($arrProcessed[$objFaq->jumpTo]))
				{
					$objParent = PageModel::findWithDetails($objFaq->jumpTo);

					// The target page does not exist
					if ($objParent === null)
					{
						continue;
					}

					// The target page has not been published (see #5520)
					if (!$objParent->published || ($objParent->start != '' && $objParent->start > $time) || ($objParent->stop != '' && $objParent->stop <= ($time + 60)))
					{
						continue;
					}

					if ($blnIsSitemap)
					{
						// The target page is protected (see #8416)
						if ($objParent->protected)
						{
							continue;
						}

						// The target page is exempt from the sitemap (see #6418)
						if ($objParent->sitemap == 'map_never')
						{
							continue;
						}
					}

					// Generate the URL
					$arrProcessed[$objFaq->jumpTo] = $objParent->getAbsoluteUrl(Config::get('useAutoItem') ? '/%s' : '/items/%s');
				}

				$strUrl = $arrProcessed[$objFaq->jumpTo];

				// Get the items
				$objItems = FaqModel::findPublishedByPid($objFaq->id);

				if ($objItems !== null)
				{
					while ($objItems->next())
					{
						$arrPages[] = sprintf(preg_replace('/%(?!s)/', '%%', $strUrl), ($objItems->alias ?: $objItems->id));
					}
				}
			}
		}

		return $arrPages;
	}
}

class_alias(ModuleFaq::class, 'ModuleFaq');


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

use Contao\CoreBundle\Exception\NoRootPageFoundException;
use Contao\Model\Collection;
use Contao\Model\Registry;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * Reads and writes pages
 *
 * @property integer $id
 * @property integer $pid
 * @property integer $sorting
 * @property integer $tstamp
 * @property string  $title
 * @property string  $alias
 * @property string  $type
 * @property string  $pageTitle
 * @property string  $language
 * @property string  $robots
 * @property string  $description
 * @property string  $redirect
 * @property integer $jumpTo
 * @property boolean $redirectBack
 * @property string  $url
 * @property boolean $target
 * @property string  $dns
 * @property string  $staticFiles
 * @property string  $staticPlugins
 * @property boolean $fallback
 * @property string  $adminEmail
 * @property string  $dateFormat
 * @property string  $timeFormat
 * @property string  $datimFormat
 * @property string  $validAliasCharacters
 * @property boolean $createSitemap
 * @property string  $sitemapName
 * @property boolean $useSSL
 * @property boolean $autoforward
 * @property boolean $protected
 * @property string  $groups
 * @property boolean $includeLayout
 * @property integer $layout
 * @property boolean $includeCache
 * @property integer $cache
 * @property integer $clientCache
 * @property boolean $includeChmod
 * @property integer $cuser
 * @property integer $cgroup
 * @property string  $chmod
 * @property boolean $noSearch
 * @property boolean $requireItem
 * @property string  $cssClass
 * @property string  $sitemap
 * @property boolean $hide
 * @property boolean $guests
 * @property integer $tabindex
 * @property boolean $accesskey
 * @property boolean $published
 * @property string  $start
 * @property string  $stop
 * @property array   $trail
 * @property string  $mainAlias
 * @property string  $mainTitle
 * @property string  $mainPageTitle
 * @property string  $parentAlias
 * @property string  $parentTitle
 * @property string  $parentPageTitle
 * @property string  $folderUrl
 * @property boolean $isPublic
 * @property integer $rootId
 * @property string  $rootAlias
 * @property string  $rootTitle
 * @property string  $rootPageTitle
 * @property integer $rootSorting
 * @property string  $domain
 * @property string  $rootLanguage
 * @property boolean $rootIsPublic
 * @property boolean $rootIsFallback
 * @property boolean $rootUseSSL
 * @property string  $rootFallbackLanguage
 * @property array   $subpages
 * @property boolean $minifyMarkup
 * @property integer $layoutId
 * @property boolean $hasJQuery
 * @property boolean $hasMooTools
 * @property string  $template
 * @property string  $templateGroup
 *
 * @method static PageModel|null findById($id, array $opt=array())
 * @method static PageModel|null findByPk($id, array $opt=array())
 * @method static PageModel|null findByIdOrAlias($val, array $opt=array())
 * @method static PageModel|null findOneBy($col, $val, array $opt=array())
 * @method static PageModel|null findOneByPid($val, array $opt=array())
 * @method static PageModel|null findOneBySorting($val, array $opt=array())
 * @method static PageModel|null findOneByTstamp($val, array $opt=array())
 * @method static PageModel|null findOneByTitle($val, array $opt=array())
 * @method static PageModel|null findOneByAlias($val, array $opt=array())
 * @method static PageModel|null findOneByType($val, array $opt=array())
 * @method static PageModel|null findOneByPageTitle($val, array $opt=array())
 * @method static PageModel|null findOneByLanguage($val, array $opt=array())
 * @method static PageModel|null findOneByRobots($val, array $opt=array())
 * @method static PageModel|null findOneByDescription($val, array $opt=array())
 * @method static PageModel|null findOneByRedirect($val, array $opt=array())
 * @method static PageModel|null findOneByJumpTo($val, array $opt=array())
 * @method static PageModel|null findOneByRedirectBack($val, array $opt=array())
 * @method static PageModel|null findOneByUrl($val, array $opt=array())
 * @method static PageModel|null findOneByTarget($val, array $opt=array())
 * @method static PageModel|null findOneByDns($val, array $opt=array())
 * @method static PageModel|null findOneByStaticFiles($val, array $opt=array())
 * @method static PageModel|null findOneByStaticPlugins($val, array $opt=array())
 * @method static PageModel|null findOneByFallback($val, array $opt=array())
 * @method static PageModel|null findOneByAdminEmail($val, array $opt=array())
 * @method static PageModel|null findOneByDateFormat($val, array $opt=array())
 * @method static PageModel|null findOneByTimeFormat($val, array $opt=array())
 * @method static PageModel|null findOneByDatimFormat($val, array $opt=array())
 * @method static PageModel|null findOneByCreateSitemap($val, array $opt=array())
 * @method static PageModel|null findOneBySitemapName($val, array $opt=array())
 * @method static PageModel|null findOneByUseSSL($val, array $opt=array())
 * @method static PageModel|null findOneByAutoforward($val, array $opt=array())
 * @method static PageModel|null findOneByProtected($val, array $opt=array())
 * @method static PageModel|null findOneByGroups($val, array $opt=array())
 * @method static PageModel|null findOneByIncludeLayout($val, array $opt=array())
 * @method static PageModel|null findOneByLayout($val, array $opt=array())
 * @method static PageModel|null findOneByIncludeCache($val, array $opt=array())
 * @method static PageModel|null findOneByCache($val, array $opt=array())
 * @method static PageModel|null findOneByIncludeChmod($val, array $opt=array())
 * @method static PageModel|null findOneByCuser($val, array $opt=array())
 * @method static PageModel|null findOneByCgroup($val, array $opt=array())
 * @method static PageModel|null findOneByChmod($val, array $opt=array())
 * @method static PageModel|null findOneByNoSearch($val, array $opt=array())
 * @method static PageModel|null findOneByCssClass($val, array $opt=array())
 * @method static PageModel|null findOneBySitemap($val, array $opt=array())
 * @method static PageModel|null findOneByHide($val, array $opt=array())
 * @method static PageModel|null findOneByGuests($val, array $opt=array())
 * @method static PageModel|null findOneByTabindex($val, array $opt=array())
 * @method static PageModel|null findOneByAccesskey($val, array $opt=array())
 * @method static PageModel|null findOneByPublished($val, array $opt=array())
 * @method static PageModel|null findOneByStart($val, array $opt=array())
 * @method static PageModel|null findOneByStop($val, array $opt=array())
 *
 * @method static Collection|PageModel[]|PageModel|null findByPid($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findBySorting($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByTstamp($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByTitle($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByAlias($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByType($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByPageTitle($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByLanguage($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByRobots($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByDescription($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByRedirect($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByJumpTo($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByRedirectBack($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByUrl($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByTarget($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByDns($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByStaticFiles($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByStaticPlugins($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByFallback($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByAdminEmail($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByDateFormat($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByTimeFormat($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByDatimFormat($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByCreateSitemap($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findBySitemapName($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByUseSSL($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByAutoforward($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByProtected($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByGroups($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByIncludeLayout($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByLayout($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByIncludeCache($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByCache($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByIncludeChmod($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByCuser($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByCgroup($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByChmod($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByNoSearch($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByCssClass($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findBySitemap($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByHide($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByGuests($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByTabindex($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByAccesskey($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByPublished($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByStart($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findByStop($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findMultipleByIds($val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findBy($col, $val, array $opt=array())
 * @method static Collection|PageModel[]|PageModel|null findAll(array $opt=array())
 *
 * @method static integer countById($id, array $opt=array())
 * @method static integer countByPid($val, array $opt=array())
 * @method static integer countBySorting($val, array $opt=array())
 * @method static integer countByTstamp($val, array $opt=array())
 * @method static integer countByTitle($val, array $opt=array())
 * @method static integer countByAlias($val, array $opt=array())
 * @method static integer countByType($val, array $opt=array())
 * @method static integer countByPageTitle($val, array $opt=array())
 * @method static integer countByLanguage($val, array $opt=array())
 * @method static integer countByRobots($val, array $opt=array())
 * @method static integer countByDescription($val, array $opt=array())
 * @method static integer countByRedirect($val, array $opt=array())
 * @method static integer countByJumpTo($val, array $opt=array())
 * @method static integer countByRedirectBack($val, array $opt=array())
 * @method static integer countByUrl($val, array $opt=array())
 * @method static integer countByTarget($val, array $opt=array())
 * @method static integer countByDns($val, array $opt=array())
 * @method static integer countByStaticFiles($val, array $opt=array())
 * @method static integer countByStaticPlugins($val, array $opt=array())
 * @method static integer countByFallback($val, array $opt=array())
 * @method static integer countByAdminEmail($val, array $opt=array())
 * @method static integer countByDateFormat($val, array $opt=array())
 * @method static integer countByTimeFormat($val, array $opt=array())
 * @method static integer countByDatimFormat($val, array $opt=array())
 * @method static integer countByCreateSitemap($val, array $opt=array())
 * @method static integer countBySitemapName($val, array $opt=array())
 * @method static integer countByUseSSL($val, array $opt=array())
 * @method static integer countByAutoforward($val, array $opt=array())
 * @method static integer countByProtected($val, array $opt=array())
 * @method static integer countByGroups($val, array $opt=array())
 * @method static integer countByIncludeLayout($val, array $opt=array())
 * @method static integer countByLayout($val, array $opt=array())
 * @method static integer countByIncludeCache($val, array $opt=array())
 * @method static integer countByCache($val, array $opt=array())
 * @method static integer countByIncludeChmod($val, array $opt=array())
 * @method static integer countByCuser($val, array $opt=array())
 * @method static integer countByCgroup($val, array $opt=array())
 * @method static integer countByChmod($val, array $opt=array())
 * @method static integer countByNoSearch($val, array $opt=array())
 * @method static integer countByCssClass($val, array $opt=array())
 * @method static integer countBySitemap($val, array $opt=array())
 * @method static integer countByHide($val, array $opt=array())
 * @method static integer countByGuests($val, array $opt=array())
 * @method static integer countByTabindex($val, array $opt=array())
 * @method static integer countByAccesskey($val, array $opt=array())
 * @method static integer countByPublished($val, array $opt=array())
 * @method static integer countByStart($val, array $opt=array())
 * @method static integer countByStop($val, array $opt=array())
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class PageModel extends Model
{

	/**
	 * Table name
	 * @var string
	 */
	protected static $strTable = 'tl_page';

	/**
	 * Details loaded
	 * @var boolean
	 */
	protected $blnDetailsLoaded = false;

	/**
	 * Find a published page by its ID
	 *
	 * @param integer $intId      The page ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return PageModel|null The model or null if there is no published page
	 */
	public static function findPublishedById($intId, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.id=?");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		return static::findOneBy($arrColumns, $intId, $arrOptions);
	}

	/**
	 * Find published pages by their PID
	 *
	 * @param integer $intPid     The parent ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
	 */
	public static function findPublishedByPid($intPid, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.pid=?");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		return static::findBy($arrColumns, $intPid, $arrOptions);
	}

	/**
	 * Find the first published root page by its host name and language
	 *
	 * @param string $strHost     The host name
	 * @param mixed  $varLanguage An ISO language code or an array of ISO language codes
	 * @param array  $arrOptions  An optional options array
	 *
	 * @return PageModel|null The model or null if there is no matching root page
	 *
	 * @deprecated Deprecated since Contao 4.7, to be removed in Contao 5.0.
	 */
	public static function findFirstPublishedRootByHostAndLanguage($strHost, $varLanguage, array $arrOptions=array())
	{
		@trigger_error('Using PageModel::findFirstPublishedRootByHostAndLanguage() has been deprecated and will no longer work Contao 5.0.', E_USER_DEPRECATED);

		$t = static::$strTable;
		$objDatabase = Database::getInstance();

		if (\is_array($varLanguage))
		{
			$arrColumns = array("$t.type='root' AND ($t.dns=? OR $t.dns='')");

			if (!empty($varLanguage))
			{
				$arrColumns[] = "($t.language IN('". implode("','", $varLanguage) ."') OR $t.fallback='1')";
			}
			else
			{
				$arrColumns[] = "$t.fallback='1'";
			}

			if (!isset($arrOptions['order']))
			{
				$arrOptions['order'] = "$t.dns DESC" . (!empty($varLanguage) ? ", " . $objDatabase->findInSet("$t.language", array_reverse($varLanguage)) . " DESC" : "") . ", $t.sorting";
			}

			if (!static::isPreviewMode($arrOptions))
			{
				$time = Date::floorToMinute();
				$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
			}

			return static::findOneBy($arrColumns, $strHost, $arrOptions);
		}
		else
		{
			$arrColumns = array("$t.type='root' AND ($t.dns=? OR $t.dns='') AND ($t.language=? OR $t.fallback='1')");
			$arrValues = array($strHost, $varLanguage);

			if (!isset($arrOptions['order']))
			{
				$arrOptions['order'] = "$t.dns DESC, $t.fallback";
			}

			if (!static::isPreviewMode($arrOptions))
			{
				$time = Date::floorToMinute();
				$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
			}

			return static::findOneBy($arrColumns, $arrValues, $arrOptions);
		}
	}

	/**
	 * Find the first published page by its parent ID
	 *
	 * @param integer $intPid     The parent page's ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return PageModel|null The model or null if there is no published page
	 */
	public static function findFirstPublishedByPid($intPid, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.pid=? AND $t.type!='root' AND $t.type!='error_401' AND $t.type!='error_403' AND $t.type!='error_404'");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::findOneBy($arrColumns, $intPid, $arrOptions);
	}

	/**
	 * Find the first published regular page by its parent ID
	 *
	 * @param integer $intPid     The parent page's ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return PageModel|null The model or null if there is no published regular page
	 */
	public static function findFirstPublishedRegularByPid($intPid, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.pid=? AND $t.type='regular'");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::findOneBy($arrColumns, $intPid, $arrOptions);
	}

	/**
	 * Find an error 401 page by its parent ID
	 *
	 * @param integer $intPid     The parent page's ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return PageModel|null The model or null if there is no 401 page
	 */
	public static function find401ByPid($intPid, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.pid=? AND $t.type='error_401'");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::findOneBy($arrColumns, $intPid, $arrOptions);
	}

	/**
	 * Find an error 403 page by its parent ID
	 *
	 * @param integer $intPid     The parent page's ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return PageModel|null The model or null if there is no 403 page
	 */
	public static function find403ByPid($intPid, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.pid=? AND $t.type='error_403'");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::findOneBy($arrColumns, $intPid, $arrOptions);
	}

	/**
	 * Find an error 404 page by its parent ID
	 *
	 * @param integer $intPid     The parent page's ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return PageModel|null The model or null if there is no 404 page
	 */
	public static function find404ByPid($intPid, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.pid=? AND $t.type='error_404'");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::findOneBy($arrColumns, $intPid, $arrOptions);
	}

	/**
	 * Find pages matching a list of possible alias names
	 *
	 * @param array $arrAliases An array of possible alias names
	 * @param array $arrOptions An optional options array
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
	 */
	public static function findByAliases($arrAliases, array $arrOptions=array())
	{
		if (empty($arrAliases) || !\is_array($arrAliases))
		{
			return null;
		}

		// Remove everything that is not an alias
		$arrAliases = array_filter(array_map(function ($v) { return preg_match('/^[\w\/.-]+$/u', $v) ? $v : null; }, $arrAliases));

		// Return if nothing is left
		if (empty($arrAliases))
		{
			return null;
		}

		$t = static::$strTable;
		$arrColumns = array("$t.alias IN('" . implode("','", array_filter($arrAliases)) . "')");

		// Check the publication status (see #4652)
		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = Database::getInstance()->findInSet("$t.alias", $arrAliases);
		}

		return static::findBy($arrColumns, null, $arrOptions);
	}

	/**
	 * Find published pages by their ID or aliases
	 *
	 * @param mixed $varId      The numeric ID or the alias name
	 * @param array $arrOptions An optional options array
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
	 */
	public static function findPublishedByIdOrAlias($varId, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = !preg_match('/^[1-9]\d*$/', $varId) ? array("$t.alias=?") : array("$t.id=?");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		return static::findBy($arrColumns, $varId, $arrOptions);
	}

	/**
	 * Find all published subpages by their parent ID and exclude pages only visible for guests
	 *
	 * @param integer $intPid        The parent page's ID
	 * @param boolean $blnShowHidden If true, hidden pages will be included
	 * @param boolean $blnIsSitemap  If true, the sitemap settings apply
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
	 */
	public static function findPublishedSubpagesWithoutGuestsByPid($intPid, $blnShowHidden=false, $blnIsSitemap=false)
	{
		$time = Date::floorToMinute();

		$objSubpages = Database::getInstance()->prepare("SELECT p1.*, (SELECT COUNT(*) FROM tl_page p2 WHERE p2.pid=p1.id AND p2.type!='root' AND p2.type!='error_401' AND p2.type!='error_403' AND p2.type!='error_404'" . (!$blnShowHidden ? ($blnIsSitemap ? " AND (p2.hide='' OR sitemap='map_always')" : " AND p2.hide=''") : "") . (FE_USER_LOGGED_IN ? " AND p2.guests=''" : "") . (!BE_USER_LOGGED_IN ? " AND (p2.start='' OR p2.start<='$time') AND (p2.stop='' OR p2.stop>'" . ($time + 60) . "') AND p2.published='1'" : "") . ") AS subpages FROM tl_page p1 WHERE p1.pid=? AND p1.type!='root' AND p1.type!='error_401' AND p1.type!='error_403' AND p1.type!='error_404'" . (!$blnShowHidden ? ($blnIsSitemap ? " AND (p1.hide='' OR sitemap='map_always')" : " AND p1.hide=''") : "") . (FE_USER_LOGGED_IN ? " AND p1.guests=''" : "") . (!BE_USER_LOGGED_IN ? " AND (p1.start='' OR p1.start<='$time') AND (p1.stop='' OR p1.stop>'" . ($time + 60) . "') AND p1.published='1'" : "") . " ORDER BY p1.sorting")
											   ->execute($intPid);

		if ($objSubpages->numRows < 1)
		{
			return null;
		}

		return static::createCollectionFromDbResult($objSubpages, 'tl_page');
	}

	/**
	 * Find all published regular pages by their IDs and exclude pages only visible for guests
	 *
	 * @param array $arrIds     An array of page IDs
	 * @param array $arrOptions An optional options array
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
	 */
	public static function findPublishedRegularWithoutGuestsByIds($arrIds, array $arrOptions=array())
	{
		if (empty($arrIds) || !\is_array($arrIds))
		{
			return null;
		}

		$t = static::$strTable;
		$arrColumns = array("$t.id IN(" . implode(',', array_map('\intval', $arrIds)) . ") AND $t.type!='error_401' AND $t.type!='error_403' AND $t.type!='error_404'");

		if (empty($arrOptions['includeRoot']))
		{
			$arrColumns[] = "$t.type!='root'";
		}

		if (FE_USER_LOGGED_IN)
		{
			$arrColumns[] = "$t.guests=''";
		}

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = Database::getInstance()->findInSet("$t.id", $arrIds);
		}

		return static::findBy($arrColumns, null, $arrOptions);
	}

	/**
	 * Find all published regular pages by their parent IDs and exclude pages only visible for guests
	 *
	 * @param integer $intPid     The parent page's ID
	 * @param array   $arrOptions An optional options array
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no pages
	 */
	public static function findPublishedRegularWithoutGuestsByPid($intPid, array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.pid=? AND $t.type!='root' AND $t.type!='error_401' AND $t.type!='error_403' AND $t.type!='error_404'");

		if (FE_USER_LOGGED_IN)
		{
			$arrColumns[] = "$t.guests=''";
		}

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		if (!isset($arrOptions['order']))
		{
			$arrOptions['order'] = "$t.sorting";
		}

		return static::findBy($arrColumns, $intPid, $arrOptions);
	}

	/**
	 * Find the language fallback page by hostname
	 *
	 * @param string $strHost    The hostname
	 * @param array  $arrOptions An optional options array
	 *
	 * @return PageModel|Model|null The model or null if there is not fallback page
	 */
	public static function findPublishedFallbackByHostname($strHost, array $arrOptions=array())
	{
		// Try to load from the registry (see #8544)
		if (empty($arrOptions))
		{
			$objModel = Registry::getInstance()->fetch(static::$strTable, $strHost, 'contao.dns-fallback');

			if ($objModel !== null)
			{
				return $objModel;
			}
		}

		$t = static::$strTable;
		$arrColumns = array("$t.dns=? AND $t.fallback='1'");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		return static::findOneBy($arrColumns, $strHost, $arrOptions);
	}

	/**
	 * Finds the published root pages
	 *
	 * @param array $arrOptions An optional options array
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no parent pages
	 */
	public static function findPublishedRootPages(array $arrOptions=array())
	{
		$t = static::$strTable;
		$arrColumns = array("$t.type=?");

		if (!static::isPreviewMode($arrOptions))
		{
			$time = Date::floorToMinute();
			$arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
		}

		return static::findBy($arrColumns, 'root', $arrOptions);
	}

	/**
	 * Find the parent pages of a page
	 *
	 * @param integer $intId The page's ID
	 *
	 * @return Collection|PageModel[]|PageModel|null A collection of models or null if there are no parent pages
	 */
	public static function findParentsById($intId)
	{
		$arrModels = array();

		while ($intId > 0 && ($objPage = static::findByPk($intId)) !== null)
		{
			$intId = $objPage->pid;
			$arrModels[] = $objPage;
		}

		if (empty($arrModels))
		{
			return null;
		}

		return static::createCollection($arrModels, 'tl_page');
	}

	/**
	 * Find the first active page by its member groups
	 *
	 * @param string $arrIds An array of member group IDs
	 *
	 * @return PageModel|null The model or null if there is no matching member group
	 */
	public static function findFirstActiveByMemberGroups($arrIds)
	{
		if (empty($arrIds) || !\is_array($arrIds))
		{
			return null;
		}

		$time = Date::floorToMinute();
		$objDatabase = Database::getInstance();
		$arrIds = array_map('\intval', $arrIds);

		$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 + 60) . "') AND p.published='1' AND (p.start='' OR p.start<='$time') AND (p.stop='' OR p.stop>'" . ($time + 60) . "') ORDER BY " . $objDatabase->findInSet('g.id', $arrIds))
								 ->limit(1)
								 ->execute();

		if ($objResult->numRows < 1)
		{
			return null;
		}

		$objRegistry = Registry::getInstance();

		/** @var PageModel|Model $objPage */
		if ($objPage = $objRegistry->fetch('tl_page', $objResult->id))
		{
			return $objPage;
		}

		return new static($objResult);
	}

	/**
	 * Find a page by its ID and return it with the inherited details
	 *
	 * @param integer $intId The page's ID
	 *
	 * @return PageModel|null The model or null if there is no matching page
	 */
	public static function findWithDetails($intId)
	{
		$objPage = static::findByPk($intId);

		if ($objPage === null)
		{
			return null;
		}

		return $objPage->loadDetails();
	}

	/**
	 * Register the contao.dns-fallback alias when the model is attached to the registry
	 *
	 * @param Registry $registry The model registry
	 */
	public function onRegister(Registry $registry)
	{
		parent::onRegister($registry);

		// Register this model as being the fallback page for a given dns
		if ($this->fallback && $this->type == 'root' && !$registry->isRegisteredAlias($this, 'contao.dns-fallback', $this->dns))
		{
			$registry->registerAlias($this, 'contao.dns-fallback', $this->dns);
		}
	}

	/**
	 * Unregister the contao.dns-fallback alias when the model is detached from the registry
	 *
	 * @param Registry $registry The model registry
	 */
	public function onUnregister(Registry $registry)
	{
		parent::onUnregister($registry);

		// Unregister the fallback page
		if ($this->fallback && $this->type == 'root' && $registry->isRegisteredAlias($this, 'contao.dns-fallback', $this->dns))
		{
			$registry->unregisterAlias($this, 'contao.dns-fallback', $this->dns);
		}
	}

	/**
	 * Get the details of a page including inherited parameters
	 *
	 * @return PageModel The page model
	 *
	 * @throws NoRootPageFoundException If no root page is found
	 */
	public function loadDetails()
	{
		// Loaded already
		if ($this->blnDetailsLoaded)
		{
			return $this;
		}

		// Set some default values
		$this->protected = (bool) $this->protected;
		$this->groups = $this->protected ? StringUtil::deserialize($this->groups) : false;
		$this->layout = $this->includeLayout ? $this->layout : false;
		$this->cache = $this->includeCache ? $this->cache : false;
		$this->clientCache = $this->includeCache ? $this->clientCache : false;

		$pid = $this->pid;
		$type = $this->type;
		$alias = $this->alias;
		$name = $this->title;
		$title = $this->pageTitle ?: $this->title;
		$folderUrl = '';
		$palias = '';
		$pname = '';
		$ptitle = '';
		$trail = array($this->id, $pid);
		$time = Date::floorToMinute();

		// Inherit the settings
		if ($this->type == 'root')
		{
			$objParentPage = $this; // see #4610
		}
		else
		{
			// Load all parent pages
			$objParentPage = self::findParentsById($pid);

			if ($objParentPage !== null)
			{
				while ($pid > 0 && $type != 'root' && $objParentPage->next())
				{
					$pid = $objParentPage->pid;
					$type = $objParentPage->type;

					// Parent title
					if ($ptitle == '')
					{
						$palias = $objParentPage->alias;
						$pname = $objParentPage->title;
						$ptitle = $objParentPage->pageTitle ?: $objParentPage->title;
					}

					// Page title
					if ($type != 'root')
					{
						$alias = $objParentPage->alias;
						$name = $objParentPage->title;
						$title = $objParentPage->pageTitle ?: $objParentPage->title;
						$folderUrl = basename($alias) . '/' . $folderUrl;
						$trail[] = $objParentPage->pid;
					}

					// Cache
					if ($objParentPage->includeCache)
					{
						$this->cache = $this->cache !== false ? $this->cache : $objParentPage->cache;
						$this->clientCache = $this->clientCache !== false ? $this->clientCache : $objParentPage->clientCache;
					}

					// Layout
					if ($objParentPage->includeLayout && $this->layout === false)
					{
						$this->layout = $objParentPage->layout;
					}

					// Protection
					if ($objParentPage->protected && $this->protected === false)
					{
						$this->protected = true;
						$this->groups = StringUtil::deserialize($objParentPage->groups);
					}
				}
			}

			// Set the titles
			$this->mainAlias = $alias;
			$this->mainTitle = $name;
			$this->mainPageTitle = $title;
			$this->parentAlias = $palias;
			$this->parentTitle = $pname;
			$this->parentPageTitle = $ptitle;
			$this->folderUrl = $folderUrl;
		}

		// Set the root ID and title
		if ($objParentPage !== null && $objParentPage->type == 'root')
		{
			$this->rootId = $objParentPage->id;
			$this->rootAlias = $objParentPage->alias;
			$this->rootTitle = $objParentPage->title;
			$this->rootPageTitle = $objParentPage->pageTitle ?: $objParentPage->title;
			$this->rootSorting = $objParentPage->sorting;
			$this->domain = $objParentPage->dns;
			$this->rootLanguage = $objParentPage->language;
			$this->language = $objParentPage->language;
			$this->staticFiles = $objParentPage->staticFiles;
			$this->staticPlugins = $objParentPage->staticPlugins;
			$this->dateFormat = $objParentPage->dateFormat;
			$this->timeFormat = $objParentPage->timeFormat;
			$this->datimFormat = $objParentPage->datimFormat;
			$this->validAliasCharacters = $objParentPage->validAliasCharacters;
			$this->adminEmail = $objParentPage->adminEmail;

			// Store whether the root page has been published
			$this->rootIsPublic = ($objParentPage->published && ($objParentPage->start == '' || $objParentPage->start <= $time) && ($objParentPage->stop == '' || $objParentPage->stop > ($time + 60)));
			$this->rootIsFallback = true;
			$this->rootUseSSL = $objParentPage->useSSL;
			$this->rootFallbackLanguage = $objParentPage->language;

			// Store the fallback language (see #6874)
			if (!$objParentPage->fallback)
			{
				$this->rootIsFallback = false;
				$this->rootFallbackLanguage = null;

				$objFallback = static::findPublishedFallbackByHostname($objParentPage->dns);

				if ($objFallback !== null)
				{
					$this->rootFallbackLanguage = $objFallback->language;
				}
			}
		}

		// No root page found
		elseif (TL_MODE == 'FE' && $this->type != 'root')
		{
			System::log('Page ID "'. $this->id .'" does not belong to a root page', __METHOD__, TL_ERROR);
			throw new NoRootPageFoundException('No root page found');
		}

		$this->trail = array_reverse($trail);

		// Use the global date format if none is set (see #6104)
		if ($this->dateFormat == '')
		{
			$this->dateFormat = Config::get('dateFormat');
		}
		if ($this->timeFormat == '')
		{
			$this->timeFormat = Config::get('timeFormat');
		}
		if ($this->datimFormat == '')
		{
			$this->datimFormat = Config::get('datimFormat');
		}

		$this->isPublic = ($this->published && ($this->start == '' || $this->start <= $time) && ($this->stop == '' || $this->stop > ($time + 60)));

		// HOOK: add custom logic
		if (!empty($GLOBALS['TL_HOOKS']['loadPageDetails']) && \is_array($GLOBALS['TL_HOOKS']['loadPageDetails']))
		{
			$parentModels = array();

			if ($objParentPage instanceof Collection)
			{
				$parentModels = $objParentPage->getModels();
			}

			foreach ($GLOBALS['TL_HOOKS']['loadPageDetails'] as $callback)
			{
				System::importStatic($callback[0])->{$callback[1]}($parentModels, $this);
			}
		}

		// Prevent saving (see #6506 and #7199)
		$this->preventSaving();
		$this->blnDetailsLoaded = true;

		return $this;
	}

	/**
	 * Generate a front end URL
	 *
	 * @param string $strParams    An optional string of URL parameters
	 * @param string $strForceLang Force a certain language
	 *
	 * @return string An URL that can be used in the front end
	 */
	public function getFrontendUrl($strParams=null, $strForceLang=null)
	{
		if ($strForceLang !== null)
		{
			@trigger_error('Using PageModel::getFrontendUrl() with $strForceLang has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);
		}

		$this->loadDetails();

		$objUrlGenerator = System::getContainer()->get('contao.routing.url_generator');

		$strUrl = $objUrlGenerator->generate
		(
			($this->alias ?: $this->id) . $strParams,
			array
			(
				'_locale' => ($strForceLang ?: $this->rootLanguage),
				'_domain' => $this->domain,
				'_ssl' => (bool) $this->rootUseSSL,
			)
		);

		// Make the URL relative to the base path
		if (0 === strncmp($strUrl, '/', 1))
		{
			$strUrl = substr($strUrl, \strlen(Environment::get('path')) + 1);
		}

		$strUrl = $this->applyLegacyLogic($strUrl, $strParams);

		return $strUrl;
	}

	/**
	 * Generate an absolute URL depending on the current rewriteURL setting
	 *
	 * @param string $strParams An optional string of URL parameters
	 *
	 * @return string An absolute URL that can be used in the front end
	 */
	public function getAbsoluteUrl($strParams=null)
	{
		$this->loadDetails();

		$objUrlGenerator = System::getContainer()->get('contao.routing.url_generator');

		$strUrl = $objUrlGenerator->generate
		(
			($this->alias ?: $this->id) . $strParams,
			array
			(
				'_locale' => $this->rootLanguage,
				'_domain' => $this->domain,
				'_ssl' => (bool) $this->rootUseSSL,
			),
			UrlGeneratorInterface::ABSOLUTE_URL
		);

		$strUrl = $this->applyLegacyLogic($strUrl, $strParams);

		return $strUrl;
	}

	/**
	 * Return the slug options
	 *
	 * @return array The slug options
	 */
	public function getSlugOptions()
	{
		$slugOptions = array('locale'=>$this->language);

		if ($this->validAliasCharacters)
		{
			$slugOptions['validChars'] = $this->validAliasCharacters;
		}

		return $slugOptions;
	}

	/**
	 * Modifies a URL from the URL generator.
	 *
	 * @param string $strUrl
	 * @param string $strParams
	 *
	 * @return string
	 */
	private function applyLegacyLogic($strUrl, $strParams)
	{
		// Decode sprintf placeholders
		if (strpos($strParams, '%') !== false)
		{
			@trigger_error('Using sprintf placeholders in URLs has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

			$arrMatches = array();
			preg_match_all('/%([sducoxXbgGeEfF])/', $strParams, $arrMatches);

			foreach (array_unique($arrMatches[1]) as $v)
			{
				$strUrl = str_replace('%25' . $v, '%' . $v, $strUrl);
			}
		}

		// HOOK: add custom logic
		if (isset($GLOBALS['TL_HOOKS']['generateFrontendUrl']) && \is_array($GLOBALS['TL_HOOKS']['generateFrontendUrl']))
		{
			@trigger_error('Using the "generateFrontendUrl" hook has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

			foreach ($GLOBALS['TL_HOOKS']['generateFrontendUrl'] as $callback)
			{
				$strUrl = System::importStatic($callback[0])->{$callback[1]}($this->row(), $strParams, $strUrl);
			}

			return $strUrl;
		}

		return $strUrl;
	}
}

class_alias(PageModel::class, 'PageModel');