vendor/contao/core-bundle/src/Framework/ContaoFramework.php line 405

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of Contao.
  5. *
  6. * (c) Leo Feyer
  7. *
  8. * @license LGPL-3.0-or-later
  9. */
  10. namespace Contao\CoreBundle\Framework;
  11. use Contao\ClassLoader;
  12. use Contao\Config;
  13. use Contao\Controller;
  14. use Contao\CoreBundle\Exception\LegacyRoutingException;
  15. use Contao\CoreBundle\Exception\RedirectResponseException;
  16. use Contao\CoreBundle\Routing\ScopeMatcher;
  17. use Contao\CoreBundle\Security\Authentication\Token\TokenChecker;
  18. use Contao\CoreBundle\Session\LazySessionAccess;
  19. use Contao\CoreBundle\Util\LocaleUtil;
  20. use Contao\Environment;
  21. use Contao\Input;
  22. use Contao\InsertTags;
  23. use Contao\Model\Registry;
  24. use Contao\PageModel;
  25. use Contao\RequestToken;
  26. use Contao\System;
  27. use Contao\TemplateLoader;
  28. use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  29. use Symfony\Component\DependencyInjection\ContainerAwareTrait;
  30. use Symfony\Component\Filesystem\Filesystem;
  31. use Symfony\Component\Filesystem\Path;
  32. use Symfony\Component\HttpFoundation\Request;
  33. use Symfony\Component\HttpFoundation\RequestStack;
  34. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  35. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  36. use Symfony\Contracts\Service\ResetInterface;
  37. /**
  38. * @internal Do not use this class in your code; use the "contao.framework" service instead
  39. */
  40. class ContaoFramework implements ContaoFrameworkInterface, ContainerAwareInterface, ResetInterface
  41. {
  42. use ContainerAwareTrait;
  43. private static bool $initialized = false;
  44. private static string $nonce = '';
  45. private RequestStack $requestStack;
  46. private ScopeMatcher $scopeMatcher;
  47. private TokenChecker $tokenChecker;
  48. private Filesystem $filesystem;
  49. private UrlGeneratorInterface $urlGenerator;
  50. private string $projectDir;
  51. private int $errorLevel;
  52. private bool $legacyRouting;
  53. private ?Request $request = null;
  54. private bool $isFrontend = false;
  55. private array $adapterCache = [];
  56. private array $hookListeners = [];
  57. private bool $setLoginConstantsOnInit = false;
  58. public function __construct(RequestStack $requestStack, ScopeMatcher $scopeMatcher, TokenChecker $tokenChecker, Filesystem $filesystem, UrlGeneratorInterface $urlGenerator, string $projectDir, int $errorLevel, bool $legacyRouting)
  59. {
  60. $this->requestStack = $requestStack;
  61. $this->scopeMatcher = $scopeMatcher;
  62. $this->tokenChecker = $tokenChecker;
  63. $this->filesystem = $filesystem;
  64. $this->urlGenerator = $urlGenerator;
  65. $this->projectDir = $projectDir;
  66. $this->errorLevel = $errorLevel;
  67. $this->legacyRouting = $legacyRouting;
  68. }
  69. public function reset(): void
  70. {
  71. $this->adapterCache = [];
  72. $this->isFrontend = false;
  73. self::$nonce = '';
  74. if (!$this->isInitialized()) {
  75. return;
  76. }
  77. Controller::resetControllerCache();
  78. Environment::reset();
  79. Input::resetCache();
  80. Input::resetUnusedGet();
  81. InsertTags::reset();
  82. PageModel::reset();
  83. Registry::getInstance()->reset();
  84. }
  85. public function isInitialized(): bool
  86. {
  87. return self::$initialized;
  88. }
  89. /**
  90. * @throws \LogicException
  91. */
  92. public function initialize(bool $isFrontend = false): void
  93. {
  94. if ($this->isInitialized()) {
  95. return;
  96. }
  97. // Set before calling any methods to prevent recursion
  98. self::$initialized = true;
  99. if (null === $this->container) {
  100. throw new \LogicException('The service container has not been set.');
  101. }
  102. $this->isFrontend = $isFrontend;
  103. $this->request = $this->requestStack->getMainRequest();
  104. $this->setConstants();
  105. $this->initializeFramework();
  106. if (!$this->legacyRouting) {
  107. $this->throwOnLegacyRoutingHooks();
  108. }
  109. }
  110. public function setHookListeners(array $hookListeners): void
  111. {
  112. $this->hookListeners = $hookListeners;
  113. }
  114. /**
  115. * @template T of object
  116. *
  117. * @param class-string<T> $class
  118. *
  119. * @return T
  120. */
  121. public function createInstance($class, $args = [])
  122. {
  123. if (\in_array('getInstance', get_class_methods($class), true)) {
  124. return \call_user_func_array([$class, 'getInstance'], $args);
  125. }
  126. $reflection = new \ReflectionClass($class);
  127. return $reflection->newInstanceArgs($args);
  128. }
  129. /**
  130. * @template T
  131. *
  132. * @param class-string<T> $class
  133. *
  134. * @return Adapter<T>&T
  135. *
  136. * @phpstan-return Adapter<T>
  137. */
  138. public function getAdapter($class): Adapter
  139. {
  140. return $this->adapterCache[$class] ??= new Adapter($class);
  141. }
  142. public static function getNonce(): string
  143. {
  144. if ('' === self::$nonce) {
  145. self::$nonce = bin2hex(random_bytes(16));
  146. }
  147. return self::$nonce;
  148. }
  149. /**
  150. * @deprecated Deprecated since Contao 4.9, to be removed in Contao 5.0
  151. */
  152. public function setLoginConstants(): void
  153. {
  154. // Check if the constants have already been defined (see #5137)
  155. if (\defined('BE_USER_LOGGED_IN') || \defined('FE_USER_LOGGED_IN')) {
  156. return;
  157. }
  158. // If the framework has not been initialized yet, set the login constants on init (#4968)
  159. if (!$this->isInitialized()) {
  160. $this->setLoginConstantsOnInit = true;
  161. return;
  162. }
  163. if ('FE' === $this->getMode()) {
  164. \define('BE_USER_LOGGED_IN', $this->tokenChecker->isPreviewMode());
  165. \define('FE_USER_LOGGED_IN', $this->tokenChecker->hasFrontendUser());
  166. } else {
  167. \define('BE_USER_LOGGED_IN', false);
  168. \define('FE_USER_LOGGED_IN', false);
  169. }
  170. }
  171. /**
  172. * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0
  173. */
  174. private function setConstants(): void
  175. {
  176. if (!\defined('TL_MODE')) {
  177. \define('TL_MODE', $this->getMode());
  178. }
  179. \define('TL_START', microtime(true));
  180. \define('TL_ROOT', $this->projectDir);
  181. \define('TL_REFERER_ID', $this->getRefererId());
  182. if (!\defined('TL_SCRIPT')) {
  183. \define('TL_SCRIPT', $this->getRoute());
  184. }
  185. // Define the login status constants (see #4099, #5279)
  186. if ($this->setLoginConstantsOnInit || null === $this->requestStack->getCurrentRequest()) {
  187. $this->setLoginConstants();
  188. }
  189. // Define the relative path to the installation (see #5339)
  190. \define('TL_PATH', $this->getPath());
  191. }
  192. private function getMode(): ?string
  193. {
  194. if (true === $this->isFrontend) {
  195. return 'FE';
  196. }
  197. if (null === $this->request) {
  198. return null;
  199. }
  200. if ($this->scopeMatcher->isBackendRequest($this->request)) {
  201. return 'BE';
  202. }
  203. if ($this->scopeMatcher->isFrontendRequest($this->request)) {
  204. return 'FE';
  205. }
  206. return null;
  207. }
  208. private function getRefererId(): ?string
  209. {
  210. if (null === $this->request) {
  211. return null;
  212. }
  213. return $this->request->attributes->get('_contao_referer_id', '');
  214. }
  215. private function getRoute(): ?string
  216. {
  217. if (null === $this->request) {
  218. return null;
  219. }
  220. return substr($this->request->getBaseUrl().$this->request->getPathInfo(), \strlen($this->request->getBasePath().'/'));
  221. }
  222. private function getPath(): ?string
  223. {
  224. if (null === $this->request) {
  225. return null;
  226. }
  227. return $this->request->getBasePath();
  228. }
  229. private function initializeFramework(): void
  230. {
  231. // Set the error_reporting level
  232. error_reporting($this->errorLevel);
  233. $this->includeHelpers();
  234. $this->includeBasicClasses();
  235. // Set the container
  236. System::setContainer($this->container);
  237. $config = $this->getAdapter(Config::class);
  238. // Preload the configuration (see #5872)
  239. $config->preload();
  240. // Register the class loader
  241. ClassLoader::scanAndRegister();
  242. $this->initializeLegacySessionAccess();
  243. $this->setDefaultLanguage();
  244. // Fully load the configuration
  245. $config->getInstance();
  246. $this->registerHookListeners();
  247. $this->validateInstallation();
  248. Input::initialize();
  249. TemplateLoader::initialize();
  250. $this->setTimezone();
  251. $this->triggerInitializeSystemHook();
  252. $this->handleRequestToken();
  253. }
  254. private function includeHelpers(): void
  255. {
  256. require __DIR__.'/../Resources/contao/helper/functions.php';
  257. require __DIR__.'/../Resources/contao/config/constants.php';
  258. }
  259. /**
  260. * Includes the basic classes required for further processing.
  261. */
  262. private function includeBasicClasses(): void
  263. {
  264. static $basicClasses = [
  265. 'System',
  266. 'Config',
  267. 'ClassLoader',
  268. 'TemplateLoader',
  269. 'ModuleLoader',
  270. ];
  271. foreach ($basicClasses as $class) {
  272. if (!class_exists($class, false)) {
  273. require_once __DIR__.'/../Resources/contao/library/Contao/'.$class.'.php';
  274. }
  275. }
  276. }
  277. /**
  278. * Initializes session access for $_SESSION['FE_DATA'] and $_SESSION['BE_DATA'].
  279. */
  280. private function initializeLegacySessionAccess(): void
  281. {
  282. if (!$session = $this->getSession()) {
  283. return;
  284. }
  285. if (!$session->isStarted()) {
  286. $_SESSION = new LazySessionAccess($session, $this->request && $this->request->hasPreviousSession());
  287. } else {
  288. $_SESSION['BE_DATA'] = $session->getBag('contao_backend');
  289. $_SESSION['FE_DATA'] = $session->getBag('contao_frontend');
  290. }
  291. }
  292. private function setDefaultLanguage(): void
  293. {
  294. $language = 'en';
  295. if (null !== $this->request) {
  296. $language = LocaleUtil::formatAsLanguageTag($this->request->getLocale());
  297. }
  298. // Deprecated since Contao 4.0, to be removed in Contao 5.0
  299. $GLOBALS['TL_LANGUAGE'] = $language;
  300. }
  301. /**
  302. * Redirects to the install tool if the installation is incomplete.
  303. */
  304. private function validateInstallation(): void
  305. {
  306. if (null === $this->request) {
  307. return;
  308. }
  309. static $installRoutes = [
  310. 'contao_install',
  311. 'contao_install_redirect',
  312. ];
  313. if (\in_array($this->request->attributes->get('_route'), $installRoutes, true)) {
  314. return;
  315. }
  316. if (!$this->getAdapter(Config::class)->isComplete()) {
  317. throw new RedirectResponseException($this->urlGenerator->generate('contao_install', [], UrlGeneratorInterface::ABSOLUTE_URL));
  318. }
  319. }
  320. private function setTimezone(): void
  321. {
  322. $config = $this->getAdapter(Config::class);
  323. $this->iniSet('date.timezone', (string) $config->get('timeZone'));
  324. date_default_timezone_set((string) $config->get('timeZone'));
  325. }
  326. private function triggerInitializeSystemHook(): void
  327. {
  328. if (
  329. !empty($GLOBALS['TL_HOOKS']['initializeSystem'])
  330. && \is_array($GLOBALS['TL_HOOKS']['initializeSystem'])
  331. && is_dir(Path::join($this->projectDir, 'system/tmp'))
  332. ) {
  333. foreach ($GLOBALS['TL_HOOKS']['initializeSystem'] as $callback) {
  334. System::importStatic($callback[0])->{$callback[1]}();
  335. }
  336. }
  337. if ($this->filesystem->exists($filePath = Path::join($this->projectDir, 'system/config/initconfig.php'))) {
  338. trigger_deprecation('contao/core-bundle', '4.0', 'Using the "initconfig.php" file has been deprecated and will no longer work in Contao 5.0.');
  339. include $filePath;
  340. }
  341. }
  342. private function handleRequestToken(): void
  343. {
  344. $requestToken = $this->getAdapter(RequestToken::class);
  345. // Deprecated since Contao 4.0, to be removed in Contao 5.0
  346. if (!\defined('REQUEST_TOKEN')) {
  347. \define('REQUEST_TOKEN', 'cli' === \PHP_SAPI ? null : $requestToken->get());
  348. }
  349. }
  350. private function iniSet(string $key, string $value): void
  351. {
  352. if (\function_exists('ini_set')) {
  353. ini_set($key, $value);
  354. }
  355. }
  356. private function getSession(): ?SessionInterface
  357. {
  358. if (null === $this->request || !$this->request->hasSession()) {
  359. return null;
  360. }
  361. return $this->request->getSession();
  362. }
  363. private function registerHookListeners(): void
  364. {
  365. foreach ($this->hookListeners as $hookName => $priorities) {
  366. if (isset($GLOBALS['TL_HOOKS'][$hookName]) && \is_array($GLOBALS['TL_HOOKS'][$hookName])) {
  367. if (isset($priorities[0])) {
  368. $priorities[0] = array_merge($GLOBALS['TL_HOOKS'][$hookName], $priorities[0]);
  369. } else {
  370. $priorities[0] = $GLOBALS['TL_HOOKS'][$hookName];
  371. krsort($priorities);
  372. }
  373. }
  374. $GLOBALS['TL_HOOKS'][$hookName] = array_merge(...$priorities);
  375. }
  376. }
  377. private function throwOnLegacyRoutingHooks(): void
  378. {
  379. if (empty($GLOBALS['TL_HOOKS']['getPageIdFromUrl']) && empty($GLOBALS['TL_HOOKS']['getRootPageFromUrl'])) {
  380. return;
  381. }
  382. throw new LegacyRoutingException('Legacy routing is required to support the "getPageIdFromUrl" and "getRootPageFromUrl" hooks. Check the Symfony inspector for more information.');
  383. }
  384. }