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

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