vendor/contao/manager-bundle/src/HttpKernel/ContaoKernel.php line 93

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\ManagerBundle\HttpKernel;
  11. use AppBundle\AppBundle;
  12. use Contao\ManagerBundle\Api\ManagerConfig;
  13. use Contao\ManagerBundle\ContaoManager\Plugin;
  14. use Contao\ManagerPlugin\Bundle\BundleLoader;
  15. use Contao\ManagerPlugin\Bundle\Config\ConfigResolverFactory;
  16. use Contao\ManagerPlugin\Bundle\Parser\DelegatingParser;
  17. use Contao\ManagerPlugin\Bundle\Parser\IniParser;
  18. use Contao\ManagerPlugin\Bundle\Parser\JsonParser;
  19. use Contao\ManagerPlugin\Config\ConfigPluginInterface;
  20. use Contao\ManagerPlugin\Config\ContainerBuilder as PluginContainerBuilder;
  21. use Contao\ManagerPlugin\HttpKernel\HttpCacheSubscriberPluginInterface;
  22. use Contao\ManagerPlugin\PluginLoader;
  23. use FOS\HttpCache\SymfonyCache\HttpCacheProvider;
  24. use Symfony\Component\Config\Loader\LoaderInterface;
  25. use Symfony\Component\Console\Input\InputInterface;
  26. use Symfony\Component\DependencyInjection\ContainerBuilder;
  27. use Symfony\Component\Dotenv\Dotenv;
  28. use Symfony\Component\ErrorHandler\Debug;
  29. use Symfony\Component\Filesystem\Path;
  30. use Symfony\Component\HttpFoundation\Request;
  31. use Symfony\Component\HttpKernel\HttpKernelInterface;
  32. use Symfony\Component\HttpKernel\Kernel;
  33. class ContaoKernel extends Kernel implements HttpCacheProvider
  34. {
  35. protected static ?string $projectDir = null;
  36. private ?PluginLoader $pluginLoader = null;
  37. private ?BundleLoader $bundleLoader = null;
  38. private ?JwtManager $jwtManager = null;
  39. private ?ManagerConfig $managerConfig = null;
  40. private ?ContaoCache $httpCache = null;
  41. public function shutdown(): void
  42. {
  43. // Reset bundle loader to re-calculate bundle order after cache:clear
  44. if ($this->booted) {
  45. $this->bundleLoader = null;
  46. }
  47. parent::shutdown();
  48. }
  49. public function registerBundles(): array
  50. {
  51. $bundles = [];
  52. $this->addBundlesFromPlugins($bundles);
  53. return $bundles;
  54. }
  55. public function getProjectDir(): string
  56. {
  57. if (null === self::$projectDir) {
  58. throw new \LogicException('ContaoKernel::setProjectDir() must be called to initialize the Contao kernel');
  59. }
  60. return self::$projectDir;
  61. }
  62. /**
  63. * @deprecated since Symfony 4.2, use getProjectDir() instead
  64. */
  65. public function getRootDir(): string
  66. {
  67. return Path::join($this->getProjectDir(), 'app');
  68. }
  69. public function getCacheDir(): string
  70. {
  71. return Path::join($this->getProjectDir(), 'var/cache', $this->getEnvironment());
  72. }
  73. public function getLogDir(): string
  74. {
  75. return Path::join($this->getProjectDir(), 'var/logs');
  76. }
  77. public function getPluginLoader(): PluginLoader
  78. {
  79. if (null === $this->pluginLoader) {
  80. $this->pluginLoader = new PluginLoader();
  81. $config = $this->getManagerConfig()->all();
  82. if (
  83. isset($config['contao_manager']['disabled_packages'])
  84. && \is_array($config['contao_manager']['disabled_packages'])
  85. ) {
  86. $this->pluginLoader->setDisabledPackages($config['contao_manager']['disabled_packages']);
  87. }
  88. }
  89. return $this->pluginLoader;
  90. }
  91. public function setPluginLoader(PluginLoader $pluginLoader): void
  92. {
  93. $this->pluginLoader = $pluginLoader;
  94. }
  95. public function getBundleLoader(): BundleLoader
  96. {
  97. if (null === $this->bundleLoader) {
  98. $parser = new DelegatingParser();
  99. $parser->addParser(new JsonParser());
  100. $parser->addParser(new IniParser(Path::join($this->getProjectDir(), 'system/modules')));
  101. $this->bundleLoader = new BundleLoader($this->getPluginLoader(), new ConfigResolverFactory(), $parser);
  102. }
  103. return $this->bundleLoader;
  104. }
  105. public function setBundleLoader(BundleLoader $bundleLoader): void
  106. {
  107. $this->bundleLoader = $bundleLoader;
  108. }
  109. public function getJwtManager(): ?JwtManager
  110. {
  111. return $this->jwtManager;
  112. }
  113. public function setJwtManager(JwtManager $jwtManager): void
  114. {
  115. $this->jwtManager = $jwtManager;
  116. }
  117. public function getManagerConfig(): ManagerConfig
  118. {
  119. return $this->managerConfig ??= new ManagerConfig($this->getProjectDir());
  120. }
  121. public function setManagerConfig(ManagerConfig $managerConfig): void
  122. {
  123. $this->managerConfig = $managerConfig;
  124. }
  125. public function registerContainerConfiguration(LoaderInterface $loader): void
  126. {
  127. $loader->load(
  128. function (ContainerBuilder $container) use ($loader): void {
  129. if ($parametersFile = $this->getConfigFile('parameters', $container)) {
  130. $loader->load($parametersFile);
  131. }
  132. $config = $this->getManagerConfig()->all();
  133. $plugins = $this->getPluginLoader()->getInstancesOf(PluginLoader::CONFIG_PLUGINS);
  134. /** @var array<ConfigPluginInterface> $plugins */
  135. foreach ($plugins as $plugin) {
  136. $plugin->registerContainerConfiguration($loader, $config);
  137. }
  138. // Reload the parameters.yml file
  139. if ($parametersFile) {
  140. $loader->load($parametersFile);
  141. }
  142. if ($configFile = $this->getConfigFile('config_'.$this->getEnvironment(), $container)) {
  143. $loader->load($configFile);
  144. } elseif ($configFile = $this->getConfigFile('config', $container)) {
  145. $loader->load($configFile);
  146. }
  147. // Automatically load the services.yml file if it exists
  148. if ($servicesFile = $this->getConfigFile('services', $container)) {
  149. $loader->load($servicesFile);
  150. }
  151. if ($container->fileExists(Path::join($this->getProjectDir(), 'src'), false)) {
  152. $loader->load(__DIR__.'/../Resources/skeleton/config/services.php');
  153. }
  154. }
  155. );
  156. }
  157. public function getHttpCache(): ContaoCache
  158. {
  159. if (null !== $this->httpCache) {
  160. return $this->httpCache;
  161. }
  162. $this->httpCache = new ContaoCache($this, Path::join($this->getProjectDir(), 'var/cache/prod/http_cache'));
  163. /** @var array<HttpCacheSubscriberPluginInterface> $plugins */
  164. $plugins = $this->getPluginLoader()->getInstancesOf(HttpCacheSubscriberPluginInterface::class);
  165. foreach ($plugins as $plugin) {
  166. foreach ($plugin->getHttpCacheSubscribers() as $subscriber) {
  167. $this->httpCache->addSubscriber($subscriber);
  168. }
  169. }
  170. return $this->httpCache;
  171. }
  172. /**
  173. * Sets the project directory (the Contao kernel does not know its location).
  174. */
  175. public static function setProjectDir(string $projectDir): void
  176. {
  177. self::$projectDir = realpath($projectDir) ?: $projectDir;
  178. }
  179. /**
  180. * @return ContaoKernel|ContaoCache
  181. */
  182. public static function fromRequest(string $projectDir, Request $request): HttpKernelInterface
  183. {
  184. self::loadEnv($projectDir, 'jwt');
  185. if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? null) {
  186. Request::setTrustedHosts(explode(',', $trustedHosts));
  187. }
  188. if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? null) {
  189. $trustedHeaderSet = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO;
  190. // If we have a limited list of trusted hosts, we can safely use the X-Forwarded-Host header
  191. if ($trustedHosts) {
  192. $trustedHeaderSet |= Request::HEADER_X_FORWARDED_HOST;
  193. }
  194. Request::setTrustedProxies(explode(',', $trustedProxies), $trustedHeaderSet);
  195. }
  196. $jwtManager = null;
  197. $env = null;
  198. $parseJwt = 'jwt' === $_SERVER['APP_ENV'];
  199. if ($parseJwt) {
  200. $env = 'prod';
  201. $jwtManager = new JwtManager($projectDir);
  202. $jwt = $jwtManager->parseRequest($request);
  203. if (\is_array($jwt) && ($jwt['debug'] ?? false)) {
  204. $env = 'dev';
  205. }
  206. $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env;
  207. }
  208. $kernel = static::create($projectDir, $env);
  209. if ($parseJwt) {
  210. $kernel->setJwtManager($jwtManager);
  211. }
  212. // Enable the Symfony reverse proxy if not disabled explicitly
  213. if (!($_SERVER['DISABLE_HTTP_CACHE'] ?? null) && !$kernel->isDebug()) {
  214. return $kernel->getHttpCache();
  215. }
  216. return $kernel;
  217. }
  218. public static function fromInput(string $projectDir, InputInterface $input): self
  219. {
  220. $env = $input->getParameterOption(['--env', '-e'], null);
  221. self::loadEnv($projectDir, $env ?: 'prod');
  222. return static::create($projectDir, $env);
  223. }
  224. protected function getContainerBuilder(): PluginContainerBuilder
  225. {
  226. $container = new PluginContainerBuilder($this->getPluginLoader(), []);
  227. $container->getParameterBag()->add($this->getKernelParameters());
  228. return $container;
  229. }
  230. protected function initializeContainer(): void
  231. {
  232. parent::initializeContainer();
  233. if (null === ($container = $this->getContainer())) {
  234. return;
  235. }
  236. // Set the plugin loader again, so it is available at runtime (synthetic service)
  237. $container->set('contao_manager.plugin_loader', $this->getPluginLoader());
  238. // Set the JWT manager only if the debug mode has not been configured in env variables
  239. if ($jwtManager = $this->getJwtManager()) {
  240. $container->set('contao_manager.jwt_manager', $jwtManager);
  241. }
  242. }
  243. private function getConfigFile(string $file, ContainerBuilder $container): ?string
  244. {
  245. $projectDir = $this->getProjectDir();
  246. $exists = [];
  247. foreach (['.yaml', '.yml', '.php', '.xml'] as $ext) {
  248. if ($container->fileExists($path = Path::join($projectDir, 'config', $file.$ext))) {
  249. $exists[] = $path;
  250. }
  251. }
  252. // Fallback to the legacy config file (see #566)
  253. foreach (['.yaml', '.yml'] as $ext) {
  254. $path = Path::join($projectDir, 'app/config', $file.$ext);
  255. // Only trigger deprecation if no file exists in the root config folder
  256. if ($container->fileExists($path) && [] === $exists) {
  257. trigger_deprecation('contao/manager-bundle', '4.9', sprintf('Storing the "%s" file in the "app/config" folder has been deprecated and will no longer work in Contao 5.0. Move it to the "config" folder instead.', $file.$ext));
  258. $exists[] = $path;
  259. }
  260. }
  261. return $exists[0] ?? null;
  262. }
  263. private function addBundlesFromPlugins(array &$bundles): void
  264. {
  265. $configs = $this->getBundleLoader()->getBundleConfigs(
  266. 'dev' === $this->getEnvironment(),
  267. $this->debug ? null : Path::join($this->getCacheDir(), 'bundles.map')
  268. );
  269. foreach ($configs as $config) {
  270. $bundles[$config->getName()] = $config->getBundleInstance($this);
  271. }
  272. // Autoload AppBundle for convenience
  273. $appBundle = AppBundle::class;
  274. if (!isset($bundles[$appBundle]) && class_exists($appBundle)) {
  275. $bundles[$appBundle] = new $appBundle();
  276. }
  277. }
  278. private static function create(string $projectDir, ?string $env = null): self
  279. {
  280. $env ??= $_SERVER['APP_ENV'] ?? 'prod';
  281. if ('dev' !== $env && 'prod' !== $env) {
  282. throw new \RuntimeException('The Contao Managed Edition only supports the "dev" and "prod" environments');
  283. }
  284. Plugin::autoloadModules(Path::join($projectDir, 'system/modules'));
  285. static::setProjectDir($projectDir);
  286. if ('dev' === $env) {
  287. Debug::enable();
  288. }
  289. return new static($env, 'dev' === $env);
  290. }
  291. private static function loadEnv(string $projectDir, string $defaultEnv = 'prod'): void
  292. {
  293. // Load cached env vars if the .env.local.php file exists
  294. // See https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/config/bootstrap.php
  295. if (\is_array($env = @include Path::join($projectDir, '.env.local.php'))) {
  296. foreach ($env as $k => $v) {
  297. $_ENV[$k] ??= isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v;
  298. }
  299. } elseif (file_exists($filePath = Path::join($projectDir, '.env'))) {
  300. (new Dotenv(false))->loadEnv($filePath, 'APP_ENV', $defaultEnv);
  301. }
  302. $_SERVER += $_ENV;
  303. $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null ?: $defaultEnv;
  304. }
  305. }