Chris@0: [], Chris@0: 'services' => [ Chris@0: 'database' => [ Chris@0: 'class' => 'Drupal\Core\Database\Connection', Chris@0: 'factory' => 'Drupal\Core\Database\Database::getConnection', Chris@0: 'arguments' => ['default'], Chris@0: ], Chris@0: 'cache.container' => [ Chris@0: 'class' => 'Drupal\Core\Cache\DatabaseBackend', Chris@0: 'arguments' => ['@database', '@cache_tags_provider.container', 'container', DatabaseBackend::MAXIMUM_NONE], Chris@0: ], Chris@0: 'cache_tags_provider.container' => [ Chris@0: 'class' => 'Drupal\Core\Cache\DatabaseCacheTagsChecksum', Chris@0: 'arguments' => ['@database'], Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * Holds the class used for instantiating the bootstrap container. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: protected $bootstrapContainerClass = '\Drupal\Component\DependencyInjection\PhpArrayContainer'; Chris@0: Chris@0: /** Chris@0: * Holds the bootstrap container. Chris@0: * Chris@0: * @var \Symfony\Component\DependencyInjection\ContainerInterface Chris@0: */ Chris@0: protected $bootstrapContainer; Chris@0: Chris@0: /** Chris@0: * Holds the container instance. Chris@0: * Chris@0: * @var \Symfony\Component\DependencyInjection\ContainerInterface Chris@0: */ Chris@0: protected $container; Chris@0: Chris@0: /** Chris@0: * The environment, e.g. 'testing', 'install'. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: protected $environment; Chris@0: Chris@0: /** Chris@0: * Whether the kernel has been booted. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected $booted = FALSE; Chris@0: Chris@0: /** Chris@0: * Whether essential services have been set up properly by preHandle(). Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected $prepared = FALSE; Chris@0: Chris@0: /** Chris@0: * Holds the list of enabled modules. Chris@0: * Chris@0: * @var array Chris@0: * An associative array whose keys are module names and whose values are Chris@0: * ignored. Chris@0: */ Chris@0: protected $moduleList; Chris@0: Chris@0: /** Chris@0: * List of available modules and installation profiles. Chris@0: * Chris@0: * @var \Drupal\Core\Extension\Extension[] Chris@0: */ Chris@0: protected $moduleData = []; Chris@0: Chris@0: /** Chris@0: * The class loader object. Chris@0: * Chris@0: * @var \Composer\Autoload\ClassLoader Chris@0: */ Chris@0: protected $classLoader; Chris@0: Chris@0: /** Chris@0: * Config storage object used for reading enabled modules configuration. Chris@0: * Chris@0: * @var \Drupal\Core\Config\StorageInterface Chris@0: */ Chris@0: protected $configStorage; Chris@0: Chris@0: /** Chris@0: * Whether the container can be dumped. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected $allowDumping; Chris@0: Chris@0: /** Chris@0: * Whether the container needs to be rebuilt the next time it is initialized. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected $containerNeedsRebuild = FALSE; Chris@0: Chris@0: /** Chris@0: * Whether the container needs to be dumped once booting is complete. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected $containerNeedsDumping; Chris@0: Chris@0: /** Chris@0: * List of discovered services.yml pathnames. Chris@0: * Chris@0: * This is a nested array whose top-level keys are 'app' and 'site', denoting Chris@0: * the origin of a service provider. Site-specific providers have to be Chris@0: * collected separately, because they need to be processed last, so as to be Chris@0: * able to override services from application service providers. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $serviceYamls; Chris@0: Chris@0: /** Chris@0: * List of discovered service provider class names or objects. Chris@0: * Chris@0: * This is a nested array whose top-level keys are 'app' and 'site', denoting Chris@0: * the origin of a service provider. Site-specific providers have to be Chris@0: * collected separately, because they need to be processed last, so as to be Chris@0: * able to override services from application service providers. Chris@0: * Chris@0: * Allowing objects is for example used to allow Chris@0: * \Drupal\KernelTests\KernelTestBase to register itself as service provider. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $serviceProviderClasses; Chris@0: Chris@0: /** Chris@0: * List of instantiated service provider classes. Chris@0: * Chris@0: * @see \Drupal\Core\DrupalKernel::$serviceProviderClasses Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $serviceProviders; Chris@0: Chris@0: /** Chris@0: * Whether the PHP environment has been initialized. Chris@0: * Chris@0: * This legacy phase can only be booted once because it sets session INI Chris@0: * settings. If a session has already been started, re-generating these Chris@0: * settings would break the session. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected static $isEnvironmentInitialized = FALSE; Chris@0: Chris@0: /** Chris@0: * The site directory. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: protected $sitePath; Chris@0: Chris@0: /** Chris@0: * The app root. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: protected $root; Chris@0: Chris@0: /** Chris@0: * Create a DrupalKernel object from a request. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The request. Chris@0: * @param $class_loader Chris@0: * The class loader. Normally Composer's ClassLoader, as included by the Chris@0: * front controller, but may also be decorated; e.g., Chris@0: * \Symfony\Component\ClassLoader\ApcClassLoader. Chris@0: * @param string $environment Chris@0: * String indicating the environment, e.g. 'prod' or 'dev'. Chris@0: * @param bool $allow_dumping Chris@0: * (optional) FALSE to stop the container from being written to or read Chris@0: * from disk. Defaults to TRUE. Chris@0: * @param string $app_root Chris@0: * (optional) The path to the application root as a string. If not supplied, Chris@0: * the application root will be computed. Chris@0: * Chris@0: * @return static Chris@0: * Chris@0: * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException Chris@0: * In case the host name in the request is not trusted. Chris@0: */ Chris@0: public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE, $app_root = NULL) { Chris@0: $kernel = new static($environment, $class_loader, $allow_dumping, $app_root); Chris@0: static::bootEnvironment($app_root); Chris@0: $kernel->initializeSettings($request); Chris@0: return $kernel; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Constructs a DrupalKernel object. Chris@0: * Chris@0: * @param string $environment Chris@0: * String indicating the environment, e.g. 'prod' or 'dev'. Chris@0: * @param $class_loader Chris@0: * The class loader. Normally \Composer\Autoload\ClassLoader, as included by Chris@0: * the front controller, but may also be decorated; e.g., Chris@0: * \Symfony\Component\ClassLoader\ApcClassLoader. Chris@0: * @param bool $allow_dumping Chris@0: * (optional) FALSE to stop the container from being written to or read Chris@0: * from disk. Defaults to TRUE. Chris@0: * @param string $app_root Chris@0: * (optional) The path to the application root as a string. If not supplied, Chris@0: * the application root will be computed. Chris@0: */ Chris@0: public function __construct($environment, $class_loader, $allow_dumping = TRUE, $app_root = NULL) { Chris@0: $this->environment = $environment; Chris@0: $this->classLoader = $class_loader; Chris@0: $this->allowDumping = $allow_dumping; Chris@0: if ($app_root === NULL) { Chris@0: $app_root = static::guessApplicationRoot(); Chris@0: } Chris@0: $this->root = $app_root; Chris@0: } Chris@0: Chris@0: /** Chris@17: * Determine the application root directory based on this file's location. Chris@0: * Chris@0: * @return string Chris@0: * The application root. Chris@0: */ Chris@0: protected static function guessApplicationRoot() { Chris@17: // Determine the application root by: Chris@17: // - Removing the namespace directories from the path. Chris@17: // - Getting the path to the directory two levels up from the path Chris@17: // determined in the previous step. Chris@0: return dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the appropriate site directory for a request. Chris@0: * Chris@0: * Once the kernel has been created DrupalKernelInterface::getSitePath() is Chris@0: * preferred since it gets the statically cached result of this method. Chris@0: * Chris@0: * Site directories contain all site specific code. This includes settings.php Chris@0: * for bootstrap level configuration, file configuration stores, public file Chris@0: * storage and site specific modules and themes. Chris@0: * Chris@16: * A file named sites.php must be present in the sites directory for Chris@16: * multisite. If it doesn't exist, then 'sites/default' will be used. Chris@16: * Chris@0: * Finds a matching site directory file by stripping the website's hostname Chris@0: * from left to right and pathname from right to left. By default, the Chris@0: * directory must contain a 'settings.php' file for it to match. If the Chris@0: * parameter $require_settings is set to FALSE, then a directory without a Chris@0: * 'settings.php' file will match as well. The first configuration file found Chris@0: * will be used and the remaining ones will be ignored. If no configuration Chris@0: * file is found, returns a default value 'sites/default'. See Chris@0: * default.settings.php for examples on how the URL is converted to a Chris@0: * directory. Chris@0: * Chris@16: * The sites.php file in the sites directory can define aliases in an Chris@16: * associative array named $sites. The array is written in the format Chris@0: * '..' => 'directory'. As an example, to create a Chris@0: * directory alias for https://www.drupal.org:8080/mysite/test whose Chris@0: * configuration file is in sites/example.com, the array should be defined as: Chris@0: * @code Chris@0: * $sites = array( Chris@0: * '8080.www.drupal.org.mysite.test' => 'example.com', Chris@0: * ); Chris@0: * @endcode Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * @param bool $require_settings Chris@0: * Only directories with an existing settings.php file will be recognized. Chris@0: * Defaults to TRUE. During initial installation, this is set to FALSE so Chris@0: * that Drupal can detect a matching directory, then create a new Chris@0: * settings.php file in it. Chris@0: * @param string $app_root Chris@0: * (optional) The path to the application root as a string. If not supplied, Chris@0: * the application root will be computed. Chris@0: * Chris@0: * @return string Chris@0: * The path of the matching directory. Chris@0: * Chris@0: * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException Chris@0: * In case the host name in the request is invalid. Chris@0: * Chris@0: * @see \Drupal\Core\DrupalKernelInterface::getSitePath() Chris@0: * @see \Drupal\Core\DrupalKernelInterface::setSitePath() Chris@0: * @see default.settings.php Chris@0: * @see example.sites.php Chris@0: */ Chris@0: public static function findSitePath(Request $request, $require_settings = TRUE, $app_root = NULL) { Chris@0: if (static::validateHostname($request) === FALSE) { Chris@0: throw new BadRequestHttpException(); Chris@0: } Chris@0: Chris@0: if ($app_root === NULL) { Chris@0: $app_root = static::guessApplicationRoot(); Chris@0: } Chris@0: Chris@0: // Check for a simpletest override. Chris@0: if ($test_prefix = drupal_valid_test_ua()) { Chris@0: $test_db = new TestDatabase($test_prefix); Chris@0: return $test_db->getTestSitePath(); Chris@0: } Chris@0: Chris@0: // Determine whether multi-site functionality is enabled. Chris@0: if (!file_exists($app_root . '/sites/sites.php')) { Chris@0: return 'sites/default'; Chris@0: } Chris@0: Chris@0: // Otherwise, use find the site path using the request. Chris@0: $script_name = $request->server->get('SCRIPT_NAME'); Chris@0: if (!$script_name) { Chris@0: $script_name = $request->server->get('SCRIPT_FILENAME'); Chris@0: } Chris@0: $http_host = $request->getHttpHost(); Chris@0: Chris@0: $sites = []; Chris@0: include $app_root . '/sites/sites.php'; Chris@0: Chris@0: $uri = explode('/', $script_name); Chris@0: $server = explode('.', implode('.', array_reverse(explode(':', rtrim($http_host, '.'))))); Chris@0: for ($i = count($uri) - 1; $i > 0; $i--) { Chris@0: for ($j = count($server); $j > 0; $j--) { Chris@0: $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); Chris@0: if (isset($sites[$dir]) && file_exists($app_root . '/sites/' . $sites[$dir])) { Chris@0: $dir = $sites[$dir]; Chris@0: } Chris@0: if (file_exists($app_root . '/sites/' . $dir . '/settings.php') || (!$require_settings && file_exists($app_root . '/sites/' . $dir))) { Chris@0: return "sites/$dir"; Chris@0: } Chris@0: } Chris@0: } Chris@0: return 'sites/default'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setSitePath($path) { Chris@0: if ($this->booted && $path !== $this->sitePath) { Chris@0: throw new \LogicException('Site path cannot be changed after calling boot()'); Chris@0: } Chris@0: $this->sitePath = $path; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getSitePath() { Chris@0: return $this->sitePath; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getAppRoot() { Chris@0: return $this->root; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function boot() { Chris@0: if ($this->booted) { Chris@0: return $this; Chris@0: } Chris@0: Chris@0: // Ensure that findSitePath is set. Chris@0: if (!$this->sitePath) { Chris@0: throw new \Exception('Kernel does not have site path set before calling boot()'); Chris@0: } Chris@0: Chris@0: // Initialize the FileCacheFactory component. We have to do it here instead Chris@0: // of in \Drupal\Component\FileCache\FileCacheFactory because we can not use Chris@0: // the Settings object in a component. Chris@0: $configuration = Settings::get('file_cache'); Chris@0: Chris@0: // Provide a default configuration, if not set. Chris@0: if (!isset($configuration['default'])) { Chris@0: // @todo Use extension_loaded('apcu') for non-testbot Chris@0: // https://www.drupal.org/node/2447753. Chris@0: if (function_exists('apcu_fetch')) { Chris@0: $configuration['default']['cache_backend_class'] = '\Drupal\Component\FileCache\ApcuFileCacheBackend'; Chris@0: } Chris@0: } Chris@0: FileCacheFactory::setConfiguration($configuration); Chris@0: FileCacheFactory::setPrefix(Settings::getApcuPrefix('file_cache', $this->root)); Chris@0: Chris@0: $this->bootstrapContainer = new $this->bootstrapContainerClass(Settings::get('bootstrap_container_definition', $this->defaultBootstrapContainerDefinition)); Chris@0: Chris@0: // Initialize the container. Chris@0: $this->initializeContainer(); Chris@0: Chris@17: if (in_array('phar', stream_get_wrappers(), TRUE)) { Chris@17: // Set up a stream wrapper to handle insecurities due to PHP's builtin Chris@17: // phar stream wrapper. This is not registered as a regular stream wrapper Chris@17: // to prevent \Drupal\Core\File\FileSystem::validScheme() treating "phar" Chris@17: // as a valid scheme. Chris@17: try { Chris@17: $behavior = new PharStreamWrapperBehavior(); Chris@17: PharStreamWrapperManager::initialize( Chris@17: $behavior->withAssertion(new PharExtensionInterceptor()) Chris@17: ); Chris@17: } Chris@17: catch (\LogicException $e) { Chris@17: // Continue if the PharStreamWrapperManager is already initialized. For Chris@17: // example, this occurs during a module install. Chris@17: // @see \Drupal\Core\Extension\ModuleInstaller::install() Chris@17: }; Chris@17: stream_wrapper_unregister('phar'); Chris@17: stream_wrapper_register('phar', PharStreamWrapper::class); Chris@17: } Chris@17: Chris@0: $this->booted = TRUE; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function shutdown() { Chris@0: if (FALSE === $this->booted) { Chris@0: return; Chris@0: } Chris@0: $this->container->get('stream_wrapper_manager')->unregister(); Chris@0: $this->booted = FALSE; Chris@0: $this->container = NULL; Chris@0: $this->moduleList = NULL; Chris@0: $this->moduleData = []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getContainer() { Chris@0: return $this->container; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setContainer(ContainerInterface $container = NULL) { Chris@0: if (isset($this->container)) { Chris@0: throw new \Exception('The container should not override an existing container.'); Chris@0: } Chris@0: if ($this->booted) { Chris@0: throw new \Exception('The container cannot be set after a booted kernel.'); Chris@0: } Chris@0: Chris@0: $this->container = $container; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCachedContainerDefinition() { Chris@0: $cache = $this->bootstrapContainer->get('cache.container')->get($this->getContainerCacheKey()); Chris@0: Chris@0: if ($cache) { Chris@0: return $cache->data; Chris@0: } Chris@0: Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function loadLegacyIncludes() { Chris@0: require_once $this->root . '/core/includes/common.inc'; Chris@0: require_once $this->root . '/core/includes/database.inc'; Chris@0: require_once $this->root . '/core/includes/module.inc'; Chris@0: require_once $this->root . '/core/includes/theme.inc'; Chris@0: require_once $this->root . '/core/includes/pager.inc'; Chris@0: require_once $this->root . '/core/includes/menu.inc'; Chris@0: require_once $this->root . '/core/includes/tablesort.inc'; Chris@0: require_once $this->root . '/core/includes/file.inc'; Chris@0: require_once $this->root . '/core/includes/unicode.inc'; Chris@0: require_once $this->root . '/core/includes/form.inc'; Chris@0: require_once $this->root . '/core/includes/errors.inc'; Chris@0: require_once $this->root . '/core/includes/schema.inc'; Chris@0: require_once $this->root . '/core/includes/entity.inc'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function preHandle(Request $request) { Chris@13: // Sanitize the request. Chris@13: $request = RequestSanitizer::sanitize( Chris@13: $request, Chris@13: (array) Settings::get(RequestSanitizer::SANITIZE_WHITELIST, []), Chris@13: (bool) Settings::get(RequestSanitizer::SANITIZE_LOG, FALSE) Chris@13: ); Chris@0: Chris@0: $this->loadLegacyIncludes(); Chris@0: Chris@0: // Load all enabled modules. Chris@0: $this->container->get('module_handler')->loadAll(); Chris@0: Chris@0: // Register stream wrappers. Chris@0: $this->container->get('stream_wrapper_manager')->register(); Chris@0: Chris@0: // Initialize legacy request globals. Chris@0: $this->initializeRequestGlobals($request); Chris@0: Chris@0: // Put the request on the stack. Chris@0: $this->container->get('request_stack')->push($request); Chris@0: Chris@0: // Set the allowed protocols. Chris@0: UrlHelper::setAllowedProtocols($this->container->getParameter('filter_protocols')); Chris@0: Chris@0: // Override of Symfony's MIME type guesser singleton. Chris@0: MimeTypeGuesser::registerWithSymfonyGuesser($this->container); Chris@0: Chris@0: $this->prepared = TRUE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function discoverServiceProviders() { Chris@0: $this->serviceYamls = [ Chris@0: 'app' => [], Chris@0: 'site' => [], Chris@0: ]; Chris@0: $this->serviceProviderClasses = [ Chris@0: 'app' => [], Chris@0: 'site' => [], Chris@0: ]; Chris@0: $this->serviceYamls['app']['core'] = 'core/core.services.yml'; Chris@0: $this->serviceProviderClasses['app']['core'] = 'Drupal\Core\CoreServiceProvider'; Chris@0: Chris@0: // Retrieve enabled modules and register their namespaces. Chris@0: if (!isset($this->moduleList)) { Chris@0: $extensions = $this->getConfigStorage()->read('core.extension'); Chris@0: $this->moduleList = isset($extensions['module']) ? $extensions['module'] : []; Chris@0: } Chris@0: $module_filenames = $this->getModuleFileNames(); Chris@0: $this->classLoaderAddMultiplePsr4($this->getModuleNamespacesPsr4($module_filenames)); Chris@0: Chris@0: // Load each module's serviceProvider class. Chris@0: foreach ($module_filenames as $module => $filename) { Chris@0: $camelized = ContainerBuilder::camelize($module); Chris@0: $name = "{$camelized}ServiceProvider"; Chris@0: $class = "Drupal\\{$module}\\{$name}"; Chris@0: if (class_exists($class)) { Chris@0: $this->serviceProviderClasses['app'][$module] = $class; Chris@0: } Chris@0: $filename = dirname($filename) . "/$module.services.yml"; Chris@0: if (file_exists($filename)) { Chris@0: $this->serviceYamls['app'][$module] = $filename; Chris@0: } Chris@0: } Chris@0: Chris@0: // Add site-specific service providers. Chris@0: if (!empty($GLOBALS['conf']['container_service_providers'])) { Chris@0: foreach ($GLOBALS['conf']['container_service_providers'] as $class) { Chris@0: if ((is_string($class) && class_exists($class)) || (is_object($class) && ($class instanceof ServiceProviderInterface || $class instanceof ServiceModifierInterface))) { Chris@0: $this->serviceProviderClasses['site'][] = $class; Chris@0: } Chris@0: } Chris@0: } Chris@0: $this->addServiceFiles(Settings::get('container_yamls', [])); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getServiceProviders($origin) { Chris@0: return $this->serviceProviders[$origin]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function terminate(Request $request, Response $response) { Chris@0: // Only run terminate() when essential services have been set up properly Chris@0: // by preHandle() before. Chris@0: if (FALSE === $this->prepared) { Chris@0: return; Chris@0: } Chris@0: Chris@0: if ($this->getHttpKernel() instanceof TerminableInterface) { Chris@0: $this->getHttpKernel()->terminate($request, $response); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { Chris@0: // Ensure sane PHP environment variables. Chris@0: static::bootEnvironment(); Chris@0: Chris@0: try { Chris@0: $this->initializeSettings($request); Chris@0: Chris@0: // Redirect the user to the installation script if Drupal has not been Chris@0: // installed yet (i.e., if no $databases array has been defined in the Chris@0: // settings.php file) and we are not already installing. Chris@0: if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') { Chris@0: $response = new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']); Chris@0: } Chris@0: else { Chris@0: $this->boot(); Chris@0: $response = $this->getHttpKernel()->handle($request, $type, $catch); Chris@0: } Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: if ($catch === FALSE) { Chris@0: throw $e; Chris@0: } Chris@0: Chris@0: $response = $this->handleException($e, $request, $type); Chris@0: } Chris@0: Chris@0: // Adapt response headers to the current request. Chris@0: $response->prepare($request); Chris@0: Chris@0: return $response; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Converts an exception into a response. Chris@0: * Chris@0: * @param \Exception $e Chris@0: * An exception Chris@12: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * A Request instance Chris@0: * @param int $type Chris@0: * The type of the request (one of HttpKernelInterface::MASTER_REQUEST or Chris@0: * HttpKernelInterface::SUB_REQUEST) Chris@0: * Chris@12: * @return \Symfony\Component\HttpFoundation\Response Chris@0: * A Response instance Chris@0: * Chris@0: * @throws \Exception Chris@0: * If the passed in exception cannot be turned into a response. Chris@0: */ Chris@0: protected function handleException(\Exception $e, $request, $type) { Chris@0: if ($this->shouldRedirectToInstaller($e, $this->container ? $this->container->get('database') : NULL)) { Chris@0: return new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']); Chris@0: } Chris@0: Chris@0: if ($e instanceof HttpExceptionInterface) { Chris@0: $response = new Response($e->getMessage(), $e->getStatusCode()); Chris@0: $response->headers->add($e->getHeaders()); Chris@0: return $response; Chris@0: } Chris@0: Chris@0: throw $e; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function prepareLegacyRequest(Request $request) { Chris@0: $this->boot(); Chris@0: $this->preHandle($request); Chris@0: // Setup services which are normally initialized from within stack Chris@0: // middleware or during the request kernel event. Chris@0: if (PHP_SAPI !== 'cli') { Chris@0: $request->setSession($this->container->get('session')); Chris@0: } Chris@0: $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('')); Chris@0: $request->attributes->set(RouteObjectInterface::ROUTE_NAME, ''); Chris@0: $this->container->get('request_stack')->push($request); Chris@0: $this->container->get('router.request_context')->fromRequest($request); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns module data on the filesystem. Chris@0: * Chris@0: * @param $module Chris@0: * The name of the module. Chris@0: * Chris@0: * @return \Drupal\Core\Extension\Extension|bool Chris@0: * Returns an Extension object if the module is found, FALSE otherwise. Chris@0: */ Chris@0: protected function moduleData($module) { Chris@0: if (!$this->moduleData) { Chris@0: // First, find profiles. Chris@0: $listing = new ExtensionDiscovery($this->root); Chris@0: $listing->setProfileDirectories([]); Chris@0: $all_profiles = $listing->scan('profile'); Chris@0: $profiles = array_intersect_key($all_profiles, $this->moduleList); Chris@0: Chris@0: // If a module is within a profile directory but specifies another Chris@0: // profile for testing, it needs to be found in the parent profile. Chris@0: $settings = $this->getConfigStorage()->read('simpletest.settings'); Chris@0: $parent_profile = !empty($settings['parent_profile']) ? $settings['parent_profile'] : NULL; Chris@0: if ($parent_profile && !isset($profiles[$parent_profile])) { Chris@0: // In case both profile directories contain the same extension, the Chris@0: // actual profile always has precedence. Chris@0: $profiles = [$parent_profile => $all_profiles[$parent_profile]] + $profiles; Chris@0: } Chris@0: Chris@0: $profile_directories = array_map(function ($profile) { Chris@0: return $profile->getPath(); Chris@0: }, $profiles); Chris@0: $listing->setProfileDirectories($profile_directories); Chris@0: Chris@0: // Now find modules. Chris@0: $this->moduleData = $profiles + $listing->scan('module'); Chris@0: } Chris@0: return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements Drupal\Core\DrupalKernelInterface::updateModules(). Chris@0: * Chris@0: * @todo Remove obsolete $module_list parameter. Only $module_filenames is Chris@0: * needed. Chris@0: */ Chris@0: public function updateModules(array $module_list, array $module_filenames = []) { Chris@0: $pre_existing_module_namespaces = []; Chris@0: if ($this->booted && is_array($this->moduleList)) { Chris@0: $pre_existing_module_namespaces = $this->getModuleNamespacesPsr4($this->getModuleFileNames()); Chris@0: } Chris@0: $this->moduleList = $module_list; Chris@0: foreach ($module_filenames as $name => $extension) { Chris@0: $this->moduleData[$name] = $extension; Chris@0: } Chris@0: Chris@0: // If we haven't yet booted, we don't need to do anything: the new module Chris@0: // list will take effect when boot() is called. However we set a Chris@0: // flag that the container needs a rebuild, so that a potentially cached Chris@0: // container is not used. If we have already booted, then rebuild the Chris@0: // container in order to refresh the serviceProvider list and container. Chris@0: $this->containerNeedsRebuild = TRUE; Chris@0: if ($this->booted) { Chris@0: // We need to register any new namespaces to a new class loader because Chris@0: // the current class loader might have stored a negative result for a Chris@0: // class that is now available. Chris@0: // @see \Composer\Autoload\ClassLoader::findFile() Chris@0: $new_namespaces = array_diff_key( Chris@0: $this->getModuleNamespacesPsr4($this->getModuleFileNames()), Chris@0: $pre_existing_module_namespaces Chris@0: ); Chris@0: if (!empty($new_namespaces)) { Chris@0: $additional_class_loader = new ClassLoader(); Chris@0: $this->classLoaderAddMultiplePsr4($new_namespaces, $additional_class_loader); Chris@0: $additional_class_loader->register(); Chris@0: } Chris@0: Chris@0: $this->initializeContainer(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the container cache key based on the environment. Chris@0: * Chris@0: * The 'environment' consists of: Chris@0: * - The kernel environment string. Chris@0: * - The Drupal version constant. Chris@0: * - The deployment identifier from settings.php. This allows custom Chris@0: * deployments to force a container rebuild. Chris@0: * - The operating system running PHP. This allows compiler passes to optimize Chris@0: * services for different operating systems. Chris@0: * - The paths to any additional container YAMLs from settings.php. Chris@0: * Chris@0: * @return string Chris@0: * The cache key used for the service container. Chris@0: */ Chris@0: protected function getContainerCacheKey() { Chris@0: $parts = ['service_container', $this->environment, \Drupal::VERSION, Settings::get('deployment_identifier'), PHP_OS, serialize(Settings::get('container_yamls'))]; Chris@0: return implode(':', $parts); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the kernel parameters. Chris@0: * Chris@0: * @return array An array of kernel parameters Chris@0: */ Chris@0: protected function getKernelParameters() { Chris@0: return [ Chris@0: 'kernel.environment' => $this->environment, Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Initializes the service container. Chris@0: * Chris@0: * @return \Symfony\Component\DependencyInjection\ContainerInterface Chris@0: */ Chris@0: protected function initializeContainer() { Chris@0: $this->containerNeedsDumping = FALSE; Chris@0: $session_started = FALSE; Chris@0: if (isset($this->container)) { Chris@0: // Save the id of the currently logged in user. Chris@0: if ($this->container->initialized('current_user')) { Chris@0: $current_user_id = $this->container->get('current_user')->id(); Chris@0: } Chris@0: Chris@0: // If there is a session, close and save it. Chris@0: if ($this->container->initialized('session')) { Chris@0: $session = $this->container->get('session'); Chris@0: if ($session->isStarted()) { Chris@0: $session_started = TRUE; Chris@0: $session->save(); Chris@0: } Chris@0: unset($session); Chris@0: } Chris@0: } Chris@0: Chris@0: // If we haven't booted yet but there is a container, then we're asked to Chris@0: // boot the container injected via setContainer(). Chris@0: // @see \Drupal\KernelTests\KernelTestBase::setUp() Chris@0: if (isset($this->container) && !$this->booted) { Chris@0: $container = $this->container; Chris@0: } Chris@0: Chris@0: // If the module list hasn't already been set in updateModules and we are Chris@0: // not forcing a rebuild, then try and load the container from the cache. Chris@0: if (empty($this->moduleList) && !$this->containerNeedsRebuild) { Chris@0: $container_definition = $this->getCachedContainerDefinition(); Chris@0: } Chris@0: Chris@0: // If there is no container and no cached container definition, build a new Chris@0: // one from scratch. Chris@0: if (!isset($container) && !isset($container_definition)) { Chris@0: // Building the container creates 1000s of objects. Garbage collection of Chris@0: // these objects is expensive. This appears to be causing random Chris@0: // segmentation faults in PHP 5.6 due to Chris@0: // https://bugs.php.net/bug.php?id=72286. Once the container is rebuilt, Chris@0: // garbage collection is re-enabled. Chris@0: $disable_gc = version_compare(PHP_VERSION, '7', '<') && gc_enabled(); Chris@0: if ($disable_gc) { Chris@0: gc_collect_cycles(); Chris@0: gc_disable(); Chris@0: } Chris@0: $container = $this->compileContainer(); Chris@0: Chris@0: // Only dump the container if dumping is allowed. This is useful for Chris@0: // KernelTestBase, which never wants to use the real container, but always Chris@0: // the container builder. Chris@0: if ($this->allowDumping) { Chris@0: $dumper = new $this->phpArrayDumperClass($container); Chris@0: $container_definition = $dumper->getArray(); Chris@0: } Chris@0: // If garbage collection was disabled prior to rebuilding container, Chris@0: // re-enable it. Chris@0: if ($disable_gc) { Chris@0: gc_enable(); Chris@0: } Chris@0: } Chris@0: Chris@0: // The container was rebuilt successfully. Chris@0: $this->containerNeedsRebuild = FALSE; Chris@0: Chris@0: // Only create a new class if we have a container definition. Chris@0: if (isset($container_definition)) { Chris@0: $class = Settings::get('container_base_class', '\Drupal\Core\DependencyInjection\Container'); Chris@0: $container = new $class($container_definition); Chris@0: } Chris@0: Chris@0: $this->attachSynthetic($container); Chris@0: Chris@0: $this->container = $container; Chris@0: if ($session_started) { Chris@0: $this->container->get('session')->start(); Chris@0: } Chris@0: Chris@0: // The request stack is preserved across container rebuilds. Reinject the Chris@0: // new session into the master request if one was present before. Chris@0: if (($request_stack = $this->container->get('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE))) { Chris@0: if ($request = $request_stack->getMasterRequest()) { Chris@0: $subrequest = TRUE; Chris@0: if ($request->hasSession()) { Chris@0: $request->setSession($this->container->get('session')); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (!empty($current_user_id)) { Chris@0: $this->container->get('current_user')->setInitialAccountId($current_user_id); Chris@0: } Chris@0: Chris@0: \Drupal::setContainer($this->container); Chris@0: Chris@0: // Allow other parts of the codebase to react on container initialization in Chris@0: // subrequest. Chris@0: if (!empty($subrequest)) { Chris@0: $this->container->get('event_dispatcher')->dispatch(self::CONTAINER_INITIALIZE_SUBREQUEST_FINISHED); Chris@0: } Chris@0: Chris@0: // If needs dumping flag was set, dump the container. Chris@0: if ($this->containerNeedsDumping && !$this->cacheDrupalContainer($container_definition)) { Chris@0: $this->container->get('logger.factory')->get('DrupalKernel')->error('Container cannot be saved to cache.'); Chris@0: } Chris@0: Chris@0: return $this->container; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Setup a consistent PHP environment. Chris@0: * Chris@0: * This method sets PHP environment options we want to be sure are set Chris@0: * correctly for security or just saneness. Chris@0: * Chris@0: * @param string $app_root Chris@0: * (optional) The path to the application root as a string. If not supplied, Chris@0: * the application root will be computed. Chris@0: */ Chris@0: public static function bootEnvironment($app_root = NULL) { Chris@0: if (static::$isEnvironmentInitialized) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Determine the application root if it's not supplied. Chris@0: if ($app_root === NULL) { Chris@0: $app_root = static::guessApplicationRoot(); Chris@0: } Chris@0: Chris@0: // Include our bootstrap file. Chris@0: require_once $app_root . '/core/includes/bootstrap.inc'; Chris@0: Chris@0: // Enforce E_STRICT, but allow users to set levels not part of E_STRICT. Chris@0: error_reporting(E_STRICT | E_ALL); Chris@0: Chris@0: // Override PHP settings required for Drupal to work properly. Chris@0: // sites/default/default.settings.php contains more runtime settings. Chris@0: // The .htaccess file contains settings that cannot be changed at runtime. Chris@0: Chris@16: if (PHP_SAPI !== 'cli') { Chris@16: // Use session cookies, not transparent sessions that puts the session id Chris@16: // in the query string. Chris@16: ini_set('session.use_cookies', '1'); Chris@16: ini_set('session.use_only_cookies', '1'); Chris@16: ini_set('session.use_trans_sid', '0'); Chris@16: // Don't send HTTP headers using PHP's session handler. Chris@16: // Send an empty string to disable the cache limiter. Chris@16: ini_set('session.cache_limiter', ''); Chris@16: // Use httponly session cookies. Chris@16: ini_set('session.cookie_httponly', '1'); Chris@16: } Chris@0: Chris@0: // Set sane locale settings, to ensure consistent string, dates, times and Chris@0: // numbers handling. Chris@0: setlocale(LC_ALL, 'C'); Chris@0: Chris@17: // Set appropriate configuration for multi-byte strings. Chris@17: mb_internal_encoding('utf-8'); Chris@17: mb_language('uni'); Chris@0: Chris@0: // Indicate that code is operating in a test child site. Chris@0: if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) { Chris@0: if ($test_prefix = drupal_valid_test_ua()) { Chris@0: $test_db = new TestDatabase($test_prefix); Chris@0: // Only code that interfaces directly with tests should rely on this Chris@0: // constant; e.g., the error/exception handler conditionally adds further Chris@0: // error information into HTTP response headers that are consumed by Chris@0: // Simpletest's internal browser. Chris@0: define('DRUPAL_TEST_IN_CHILD_SITE', TRUE); Chris@0: Chris@0: // Web tests are to be conducted with runtime assertions active. Chris@0: assert_options(ASSERT_ACTIVE, TRUE); Chris@0: // Now synchronize PHP 5 and 7's handling of assertions as much as Chris@0: // possible. Chris@0: Handle::register(); Chris@0: Chris@0: // Log fatal errors to the test site directory. Chris@0: ini_set('log_errors', 1); Chris@0: ini_set('error_log', $app_root . '/' . $test_db->getTestSitePath() . '/error.log'); Chris@0: Chris@0: // Ensure that a rewritten settings.php is used if opcache is on. Chris@0: ini_set('opcache.validate_timestamps', 'on'); Chris@0: ini_set('opcache.revalidate_freq', 0); Chris@0: } Chris@0: else { Chris@0: // Ensure that no other code defines this. Chris@0: define('DRUPAL_TEST_IN_CHILD_SITE', FALSE); Chris@0: } Chris@0: } Chris@0: Chris@0: // Set the Drupal custom error handler. Chris@0: set_error_handler('_drupal_error_handler'); Chris@0: set_exception_handler('_drupal_exception_handler'); Chris@0: Chris@0: static::$isEnvironmentInitialized = TRUE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Locate site path and initialize settings singleton. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException Chris@0: * In case the host name in the request is not trusted. Chris@0: */ Chris@0: protected function initializeSettings(Request $request) { Chris@0: $site_path = static::findSitePath($request); Chris@0: $this->setSitePath($site_path); Chris@0: $class_loader_class = get_class($this->classLoader); Chris@0: Settings::initialize($this->root, $site_path, $this->classLoader); Chris@0: Chris@0: // Initialize our list of trusted HTTP Host headers to protect against Chris@0: // header attacks. Chris@0: $host_patterns = Settings::get('trusted_host_patterns', []); Chris@0: if (PHP_SAPI !== 'cli' && !empty($host_patterns)) { Chris@0: if (static::setupTrustedHosts($request, $host_patterns) === FALSE) { Chris@0: throw new BadRequestHttpException('The provided host name is not valid for this server.'); Chris@0: } Chris@0: } Chris@0: Chris@0: // If the class loader is still the same, possibly Chris@0: // upgrade to an optimized class loader. Chris@0: if ($class_loader_class == get_class($this->classLoader) Chris@0: && Settings::get('class_loader_auto_detect', TRUE)) { Chris@0: $prefix = Settings::getApcuPrefix('class_loader', $this->root); Chris@0: $loader = NULL; Chris@0: Chris@0: // We autodetect one of the following three optimized classloaders, if Chris@0: // their underlying extension exists. Chris@0: if (function_exists('apcu_fetch')) { Chris@0: $loader = new ApcClassLoader($prefix, $this->classLoader); Chris@0: } Chris@0: elseif (extension_loaded('wincache')) { Chris@0: $loader = new WinCacheClassLoader($prefix, $this->classLoader); Chris@0: } Chris@0: elseif (extension_loaded('xcache')) { Chris@0: $loader = new XcacheClassLoader($prefix, $this->classLoader); Chris@0: } Chris@0: if (!empty($loader)) { Chris@0: $this->classLoader->unregister(); Chris@0: // The optimized classloader might be persistent and store cache misses. Chris@0: // For example, once a cache miss is stored in APCu clearing it on a Chris@0: // specific web-head will not clear any other web-heads. Therefore Chris@0: // fallback to the composer class loader that only statically caches Chris@0: // misses. Chris@0: $old_loader = $this->classLoader; Chris@0: $this->classLoader = $loader; Chris@17: // Our class loaders are prepended to ensure they come first like the Chris@0: // class loader they are replacing. Chris@0: $old_loader->register(TRUE); Chris@0: $loader->register(TRUE); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Bootstraps the legacy global request variables. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @todo D8: Eliminate this entirely in favor of Request object. Chris@0: */ Chris@0: protected function initializeRequestGlobals(Request $request) { Chris@0: global $base_url; Chris@0: // Set and derived from $base_url by this function. Chris@0: global $base_path, $base_root; Chris@0: global $base_secure_url, $base_insecure_url; Chris@0: Chris@0: // Create base URL. Chris@0: $base_root = $request->getSchemeAndHttpHost(); Chris@0: $base_url = $base_root; Chris@0: Chris@0: // For a request URI of '/index.php/foo', $_SERVER['SCRIPT_NAME'] is Chris@0: // '/index.php', whereas $_SERVER['PHP_SELF'] is '/index.php/foo'. Chris@0: if ($dir = rtrim(dirname($request->server->get('SCRIPT_NAME')), '\/')) { Chris@0: // Remove "core" directory if present, allowing install.php, Chris@0: // authorize.php, and others to auto-detect a base path. Chris@0: $core_position = strrpos($dir, '/core'); Chris@0: if ($core_position !== FALSE && strlen($dir) - 5 == $core_position) { Chris@0: $base_path = substr($dir, 0, $core_position); Chris@0: } Chris@0: else { Chris@0: $base_path = $dir; Chris@0: } Chris@0: $base_url .= $base_path; Chris@0: $base_path .= '/'; Chris@0: } Chris@0: else { Chris@0: $base_path = '/'; Chris@0: } Chris@0: $base_secure_url = str_replace('http://', 'https://', $base_url); Chris@0: $base_insecure_url = str_replace('https://', 'http://', $base_url); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns service instances to persist from an old container to a new one. Chris@0: */ Chris@0: protected function getServicesToPersist(ContainerInterface $container) { Chris@0: $persist = []; Chris@0: foreach ($container->getParameter('persist_ids') as $id) { Chris@0: // It's pointless to persist services not yet initialized. Chris@0: if ($container->initialized($id)) { Chris@0: $persist[$id] = $container->get($id); Chris@0: } Chris@0: } Chris@0: return $persist; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Moves persistent service instances into a new container. Chris@0: */ Chris@0: protected function persistServices(ContainerInterface $container, array $persist) { Chris@0: foreach ($persist as $id => $object) { Chris@0: // Do not override services already set() on the new container, for Chris@0: // example 'service_container'. Chris@0: if (!$container->initialized($id)) { Chris@0: $container->set($id, $object); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function rebuildContainer() { Chris@0: // Empty module properties and for them to be reloaded from scratch. Chris@0: $this->moduleList = NULL; Chris@0: $this->moduleData = []; Chris@0: $this->containerNeedsRebuild = TRUE; Chris@0: return $this->initializeContainer(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidateContainer() { Chris@0: // An invalidated container needs a rebuild. Chris@0: $this->containerNeedsRebuild = TRUE; Chris@0: Chris@0: // If we have not yet booted, settings or bootstrap services might not yet Chris@0: // be available. In that case the container will not be loaded from cache Chris@0: // due to the above setting when the Kernel is booted. Chris@0: if (!$this->booted) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Also remove the container definition from the cache backend. Chris@0: $this->bootstrapContainer->get('cache.container')->deleteAll(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Attach synthetic values on to kernel. Chris@0: * Chris@12: * @param \Symfony\Component\DependencyInjection\ContainerInterface $container Chris@0: * Container object Chris@0: * Chris@12: * @return \Symfony\Component\DependencyInjection\ContainerInterface Chris@0: */ Chris@0: protected function attachSynthetic(ContainerInterface $container) { Chris@0: $persist = []; Chris@0: if (isset($this->container)) { Chris@0: $persist = $this->getServicesToPersist($this->container); Chris@0: } Chris@0: $this->persistServices($container, $persist); Chris@0: Chris@0: // All namespaces must be registered before we attempt to use any service Chris@0: // from the container. Chris@0: $this->classLoaderAddMultiplePsr4($container->getParameter('container.namespaces')); Chris@0: Chris@0: $container->set('kernel', $this); Chris@0: Chris@0: // Set the class loader which was registered as a synthetic service. Chris@0: $container->set('class_loader', $this->classLoader); Chris@0: return $container; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Compiles a new service container. Chris@0: * Chris@12: * @return \Drupal\Core\DependencyInjection\ContainerBuilder The compiled service container Chris@0: */ Chris@0: protected function compileContainer() { Chris@0: // We are forcing a container build so it is reasonable to assume that the Chris@0: // calling method knows something about the system has changed requiring the Chris@0: // container to be dumped to the filesystem. Chris@0: if ($this->allowDumping) { Chris@0: $this->containerNeedsDumping = TRUE; Chris@0: } Chris@0: Chris@0: $this->initializeServiceProviders(); Chris@0: $container = $this->getContainerBuilder(); Chris@0: $container->set('kernel', $this); Chris@0: $container->setParameter('container.modules', $this->getModulesParameter()); Chris@0: $container->setParameter('install_profile', $this->getInstallProfile()); Chris@0: Chris@0: // Get a list of namespaces and put it onto the container. Chris@0: $namespaces = $this->getModuleNamespacesPsr4($this->getModuleFileNames()); Chris@0: // Add all components in \Drupal\Core and \Drupal\Component that have one of Chris@0: // the following directories: Chris@0: // - Element Chris@0: // - Entity Chris@0: // - Plugin Chris@0: foreach (['Core', 'Component'] as $parent_directory) { Chris@0: $path = 'core/lib/Drupal/' . $parent_directory; Chris@0: $parent_namespace = 'Drupal\\' . $parent_directory; Chris@0: foreach (new \DirectoryIterator($this->root . '/' . $path) as $component) { Chris@0: /** @var $component \DirectoryIterator */ Chris@0: $pathname = $component->getPathname(); Chris@0: if (!$component->isDot() && $component->isDir() && ( Chris@0: is_dir($pathname . '/Plugin') || Chris@0: is_dir($pathname . '/Entity') || Chris@0: is_dir($pathname . '/Element') Chris@0: )) { Chris@0: $namespaces[$parent_namespace . '\\' . $component->getFilename()] = $path . '/' . $component->getFilename(); Chris@0: } Chris@0: } Chris@0: } Chris@0: $container->setParameter('container.namespaces', $namespaces); Chris@0: Chris@0: // Store the default language values on the container. This is so that the Chris@0: // default language can be configured using the configuration factory. This Chris@0: // avoids the circular dependencies that would created by Chris@0: // \Drupal\language\LanguageServiceProvider::alter() and allows the default Chris@0: // language to not be English in the installer. Chris@0: $default_language_values = Language::$defaultValues; Chris@0: if ($system = $this->getConfigStorage()->read('system.site')) { Chris@0: if ($default_language_values['id'] != $system['langcode']) { Chris@0: $default_language_values = ['id' => $system['langcode']]; Chris@0: } Chris@0: } Chris@0: $container->setParameter('language.default_values', $default_language_values); Chris@0: Chris@0: // Register synthetic services. Chris@0: $container->register('class_loader')->setSynthetic(TRUE); Chris@0: $container->register('kernel', 'Symfony\Component\HttpKernel\KernelInterface')->setSynthetic(TRUE); Chris@0: $container->register('service_container', 'Symfony\Component\DependencyInjection\ContainerInterface')->setSynthetic(TRUE); Chris@0: Chris@0: // Register application services. Chris@0: $yaml_loader = new YamlFileLoader($container); Chris@0: foreach ($this->serviceYamls['app'] as $filename) { Chris@0: $yaml_loader->load($filename); Chris@0: } Chris@0: foreach ($this->serviceProviders['app'] as $provider) { Chris@0: if ($provider instanceof ServiceProviderInterface) { Chris@0: $provider->register($container); Chris@0: } Chris@0: } Chris@0: // Register site-specific service overrides. Chris@0: foreach ($this->serviceYamls['site'] as $filename) { Chris@0: $yaml_loader->load($filename); Chris@0: } Chris@0: foreach ($this->serviceProviders['site'] as $provider) { Chris@0: if ($provider instanceof ServiceProviderInterface) { Chris@0: $provider->register($container); Chris@0: } Chris@0: } Chris@0: Chris@0: // Identify all services whose instances should be persisted when rebuilding Chris@0: // the container during the lifetime of the kernel (e.g., during a kernel Chris@0: // reboot). Include synthetic services, because by definition, they cannot Chris@0: // be automatically reinstantiated. Also include services tagged to persist. Chris@0: $persist_ids = []; Chris@0: foreach ($container->getDefinitions() as $id => $definition) { Chris@0: // It does not make sense to persist the container itself, exclude it. Chris@0: if ($id !== 'service_container' && ($definition->isSynthetic() || $definition->getTag('persist'))) { Chris@0: $persist_ids[] = $id; Chris@0: } Chris@0: } Chris@0: $container->setParameter('persist_ids', $persist_ids); Chris@0: Chris@0: $container->compile(); Chris@0: return $container; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers all service providers to the kernel. Chris@0: * Chris@0: * @throws \LogicException Chris@0: */ Chris@0: protected function initializeServiceProviders() { Chris@0: $this->discoverServiceProviders(); Chris@0: $this->serviceProviders = [ Chris@0: 'app' => [], Chris@0: 'site' => [], Chris@0: ]; Chris@0: foreach ($this->serviceProviderClasses as $origin => $classes) { Chris@0: foreach ($classes as $name => $class) { Chris@0: if (!is_object($class)) { Chris@0: $this->serviceProviders[$origin][$name] = new $class(); Chris@0: } Chris@0: else { Chris@0: $this->serviceProviders[$origin][$name] = $class; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a new ContainerBuilder instance used to build the service container. Chris@0: * Chris@12: * @return \Drupal\Core\DependencyInjection\ContainerBuilder Chris@0: */ Chris@0: protected function getContainerBuilder() { Chris@0: return new ContainerBuilder(new ParameterBag($this->getKernelParameters())); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Stores the container definition in a cache. Chris@0: * Chris@0: * @param array $container_definition Chris@0: * The container definition to cache. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the container was successfully cached. Chris@0: */ Chris@0: protected function cacheDrupalContainer(array $container_definition) { Chris@0: $saved = TRUE; Chris@0: try { Chris@0: $this->bootstrapContainer->get('cache.container')->set($this->getContainerCacheKey(), $container_definition); Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: // There is no way to get from the Cache API if the cache set was Chris@0: // successful or not, hence an Exception is caught and the caller informed Chris@0: // about the error condition. Chris@0: $saved = FALSE; Chris@0: } Chris@0: Chris@0: return $saved; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a http kernel from the container Chris@0: * Chris@0: * @return \Symfony\Component\HttpKernel\HttpKernelInterface Chris@0: */ Chris@0: protected function getHttpKernel() { Chris@0: return $this->container->get('http_kernel'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the active configuration storage to use during building the container. Chris@0: * Chris@0: * @return \Drupal\Core\Config\StorageInterface Chris@0: */ Chris@0: protected function getConfigStorage() { Chris@0: if (!isset($this->configStorage)) { Chris@0: // The active configuration storage may not exist yet; e.g., in the early Chris@0: // installer. Catch the exception thrown by config_get_config_directory(). Chris@0: try { Chris@0: $this->configStorage = BootstrapConfigStorageFactory::get($this->classLoader); Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: $this->configStorage = new NullStorage(); Chris@0: } Chris@0: } Chris@0: return $this->configStorage; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an array of Extension class parameters for all enabled modules. Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: protected function getModulesParameter() { Chris@0: $extensions = []; Chris@0: foreach ($this->moduleList as $name => $weight) { Chris@0: if ($data = $this->moduleData($name)) { Chris@0: $extensions[$name] = [ Chris@0: 'type' => $data->getType(), Chris@0: 'pathname' => $data->getPathname(), Chris@0: 'filename' => $data->getExtensionFilename(), Chris@0: ]; Chris@0: } Chris@0: } Chris@0: return $extensions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the file name for each enabled module. Chris@0: * Chris@0: * @return array Chris@0: * Array where each key is a module name, and each value is a path to the Chris@0: * respective *.info.yml file. Chris@0: */ Chris@0: protected function getModuleFileNames() { Chris@0: $filenames = []; Chris@0: foreach ($this->moduleList as $module => $weight) { Chris@0: if ($data = $this->moduleData($module)) { Chris@0: $filenames[$module] = $data->getPathname(); Chris@0: } Chris@0: } Chris@0: return $filenames; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the PSR-4 base directories for module namespaces. Chris@0: * Chris@0: * @param string[] $module_file_names Chris@0: * Array where each key is a module name, and each value is a path to the Chris@0: * respective *.info.yml file. Chris@0: * Chris@0: * @return string[] Chris@0: * Array where each key is a module namespace like 'Drupal\system', and each Chris@0: * value is the PSR-4 base directory associated with the module namespace. Chris@0: */ Chris@0: protected function getModuleNamespacesPsr4($module_file_names) { Chris@0: $namespaces = []; Chris@0: foreach ($module_file_names as $module => $filename) { Chris@0: $namespaces["Drupal\\$module"] = dirname($filename) . '/src'; Chris@0: } Chris@0: return $namespaces; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers a list of namespaces with PSR-4 directories for class loading. Chris@0: * Chris@0: * @param array $namespaces Chris@0: * Array where each key is a namespace like 'Drupal\system', and each value Chris@0: * is either a PSR-4 base directory, or an array of PSR-4 base directories Chris@0: * associated with this namespace. Chris@0: * @param object $class_loader Chris@0: * The class loader. Normally \Composer\Autoload\ClassLoader, as included by Chris@0: * the front controller, but may also be decorated; e.g., Chris@0: * \Symfony\Component\ClassLoader\ApcClassLoader. Chris@0: */ Chris@0: protected function classLoaderAddMultiplePsr4(array $namespaces = [], $class_loader = NULL) { Chris@0: if ($class_loader === NULL) { Chris@0: $class_loader = $this->classLoader; Chris@0: } Chris@0: foreach ($namespaces as $prefix => $paths) { Chris@0: if (is_array($paths)) { Chris@0: foreach ($paths as $key => $value) { Chris@0: $paths[$key] = $this->root . '/' . $value; Chris@0: } Chris@0: } Chris@0: elseif (is_string($paths)) { Chris@0: $paths = $this->root . '/' . $paths; Chris@0: } Chris@0: $class_loader->addPsr4($prefix . '\\', $paths); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Validates a hostname length. Chris@0: * Chris@0: * @param string $host Chris@0: * A hostname. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the length is appropriate, or FALSE otherwise. Chris@0: */ Chris@0: protected static function validateHostnameLength($host) { Chris@0: // Limit the length of the host name to 1000 bytes to prevent DoS attacks Chris@0: // with long host names. Chris@0: return strlen($host) <= 1000 Chris@0: // Limit the number of subdomains and port separators to prevent DoS attacks Chris@0: // in findSitePath(). Chris@0: && substr_count($host, '.') <= 100 Chris@0: && substr_count($host, ':') <= 100; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Validates the hostname supplied from the HTTP request. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The request object Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the hostname is valid, or FALSE otherwise. Chris@0: */ Chris@0: public static function validateHostname(Request $request) { Chris@0: // $request->getHost() can throw an UnexpectedValueException if it Chris@0: // detects a bad hostname, but it does not validate the length. Chris@0: try { Chris@0: $http_host = $request->getHost(); Chris@0: } Chris@0: catch (\UnexpectedValueException $e) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: if (static::validateHostnameLength($http_host) === FALSE) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: return TRUE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets up the lists of trusted HTTP Host headers. Chris@0: * Chris@0: * Since the HTTP Host header can be set by the user making the request, it Chris@0: * is possible to create an attack vectors against a site by overriding this. Chris@0: * Symfony provides a mechanism for creating a list of trusted Host values. Chris@0: * Chris@0: * Host patterns (as regular expressions) can be configured through Chris@0: * settings.php for multisite installations, sites using ServerAlias without Chris@0: * canonical redirection, or configurations where the site responds to default Chris@0: * requests. For example, Chris@0: * Chris@0: * @code Chris@0: * $settings['trusted_host_patterns'] = array( Chris@0: * '^example\.com$', Chris@0: * '^*.example\.com$', Chris@0: * ); Chris@0: * @endcode Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The request object. Chris@0: * @param array $host_patterns Chris@0: * The array of trusted host patterns. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the Host header is trusted, FALSE otherwise. Chris@0: * Chris@18: * @see https://www.drupal.org/docs/8/install/trusted-host-settings Chris@0: * @see \Drupal\Core\Http\TrustedHostsRequestFactory Chris@0: */ Chris@0: protected static function setupTrustedHosts(Request $request, $host_patterns) { Chris@0: $request->setTrustedHosts($host_patterns); Chris@0: Chris@0: // Get the host, which will validate the current request. Chris@0: try { Chris@0: $host = $request->getHost(); Chris@0: Chris@0: // Fake requests created through Request::create() without passing in the Chris@0: // server variables from the main request have a default host of Chris@0: // 'localhost'. If 'localhost' does not match any of the trusted host Chris@0: // patterns these fake requests would fail the host verification. Instead, Chris@0: // TrustedHostsRequestFactory makes sure to pass in the server variables Chris@0: // from the main request. Chris@0: $request_factory = new TrustedHostsRequestFactory($host); Chris@0: Request::setFactory([$request_factory, 'createRequest']); Chris@0: Chris@0: } Chris@0: catch (\UnexpectedValueException $e) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: return TRUE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add service files. Chris@0: * Chris@0: * @param string[] $service_yamls Chris@0: * A list of service files. Chris@0: */ Chris@0: protected function addServiceFiles(array $service_yamls) { Chris@0: $this->serviceYamls['site'] = array_filter($service_yamls, 'file_exists'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the active install profile. Chris@0: * Chris@0: * @return string|null Chris@0: * The name of the any active install profile or distribution. Chris@0: */ Chris@0: protected function getInstallProfile() { Chris@0: $config = $this->getConfigStorage()->read('core.extension'); Chris@17: if (isset($config['profile'])) { Chris@0: $install_profile = $config['profile']; Chris@0: } Chris@0: // @todo https://www.drupal.org/node/2831065 remove the BC layer. Chris@0: else { Chris@0: // If system_update_8300() has not yet run fallback to using settings. Chris@17: $settings = Settings::getAll(); Chris@17: $install_profile = isset($settings['install_profile']) ? $settings['install_profile'] : NULL; Chris@0: } Chris@0: Chris@0: // Normalize an empty string to a NULL value. Chris@0: return empty($install_profile) ? NULL : $install_profile; Chris@0: } Chris@0: Chris@0: }