Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Core/DrupalKernel.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 7a779792577d |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Core; | |
4 | |
5 use Composer\Autoload\ClassLoader; | |
6 use Drupal\Component\Assertion\Handle; | |
7 use Drupal\Component\FileCache\FileCacheFactory; | |
8 use Drupal\Component\Utility\Unicode; | |
9 use Drupal\Component\Utility\UrlHelper; | |
10 use Drupal\Core\Cache\DatabaseBackend; | |
11 use Drupal\Core\Config\BootstrapConfigStorageFactory; | |
12 use Drupal\Core\Config\NullStorage; | |
13 use Drupal\Core\Database\Database; | |
14 use Drupal\Core\DependencyInjection\ContainerBuilder; | |
15 use Drupal\Core\DependencyInjection\ServiceModifierInterface; | |
16 use Drupal\Core\DependencyInjection\ServiceProviderInterface; | |
17 use Drupal\Core\DependencyInjection\YamlFileLoader; | |
18 use Drupal\Core\Extension\ExtensionDiscovery; | |
19 use Drupal\Core\File\MimeType\MimeTypeGuesser; | |
20 use Drupal\Core\Http\TrustedHostsRequestFactory; | |
21 use Drupal\Core\Installer\InstallerRedirectTrait; | |
22 use Drupal\Core\Language\Language; | |
23 use Drupal\Core\Site\Settings; | |
24 use Drupal\Core\Test\TestDatabase; | |
25 use Symfony\Cmf\Component\Routing\RouteObjectInterface; | |
26 use Symfony\Component\ClassLoader\ApcClassLoader; | |
27 use Symfony\Component\ClassLoader\WinCacheClassLoader; | |
28 use Symfony\Component\ClassLoader\XcacheClassLoader; | |
29 use Symfony\Component\DependencyInjection\ContainerInterface; | |
30 use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; | |
31 use Symfony\Component\HttpFoundation\RedirectResponse; | |
32 use Symfony\Component\HttpFoundation\Request; | |
33 use Symfony\Component\HttpFoundation\Response; | |
34 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | |
35 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; | |
36 use Symfony\Component\HttpKernel\TerminableInterface; | |
37 use Symfony\Component\Routing\Route; | |
38 | |
39 /** | |
40 * The DrupalKernel class is the core of Drupal itself. | |
41 * | |
42 * This class is responsible for building the Dependency Injection Container and | |
43 * also deals with the registration of service providers. It allows registered | |
44 * service providers to add their services to the container. Core provides the | |
45 * CoreServiceProvider, which, in addition to registering any core services that | |
46 * cannot be registered in the core.services.yaml file, adds any compiler passes | |
47 * needed by core, e.g. for processing tagged services. Each module can add its | |
48 * own service provider, i.e. a class implementing | |
49 * Drupal\Core\DependencyInjection\ServiceProvider, to register services to the | |
50 * container, or modify existing services. | |
51 */ | |
52 class DrupalKernel implements DrupalKernelInterface, TerminableInterface { | |
53 use InstallerRedirectTrait; | |
54 | |
55 /** | |
56 * Holds the class used for dumping the container to a PHP array. | |
57 * | |
58 * In combination with swapping the container class this is useful to e.g. | |
59 * dump to the human-readable PHP array format to debug the container | |
60 * definition in an easier way. | |
61 * | |
62 * @var string | |
63 */ | |
64 protected $phpArrayDumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper'; | |
65 | |
66 /** | |
67 * Holds the default bootstrap container definition. | |
68 * | |
69 * @var array | |
70 */ | |
71 protected $defaultBootstrapContainerDefinition = [ | |
72 'parameters' => [], | |
73 'services' => [ | |
74 'database' => [ | |
75 'class' => 'Drupal\Core\Database\Connection', | |
76 'factory' => 'Drupal\Core\Database\Database::getConnection', | |
77 'arguments' => ['default'], | |
78 ], | |
79 'cache.container' => [ | |
80 'class' => 'Drupal\Core\Cache\DatabaseBackend', | |
81 'arguments' => ['@database', '@cache_tags_provider.container', 'container', DatabaseBackend::MAXIMUM_NONE], | |
82 ], | |
83 'cache_tags_provider.container' => [ | |
84 'class' => 'Drupal\Core\Cache\DatabaseCacheTagsChecksum', | |
85 'arguments' => ['@database'], | |
86 ], | |
87 ], | |
88 ]; | |
89 | |
90 /** | |
91 * Holds the class used for instantiating the bootstrap container. | |
92 * | |
93 * @var string | |
94 */ | |
95 protected $bootstrapContainerClass = '\Drupal\Component\DependencyInjection\PhpArrayContainer'; | |
96 | |
97 /** | |
98 * Holds the bootstrap container. | |
99 * | |
100 * @var \Symfony\Component\DependencyInjection\ContainerInterface | |
101 */ | |
102 protected $bootstrapContainer; | |
103 | |
104 /** | |
105 * Holds the container instance. | |
106 * | |
107 * @var \Symfony\Component\DependencyInjection\ContainerInterface | |
108 */ | |
109 protected $container; | |
110 | |
111 /** | |
112 * The environment, e.g. 'testing', 'install'. | |
113 * | |
114 * @var string | |
115 */ | |
116 protected $environment; | |
117 | |
118 /** | |
119 * Whether the kernel has been booted. | |
120 * | |
121 * @var bool | |
122 */ | |
123 protected $booted = FALSE; | |
124 | |
125 /** | |
126 * Whether essential services have been set up properly by preHandle(). | |
127 * | |
128 * @var bool | |
129 */ | |
130 protected $prepared = FALSE; | |
131 | |
132 /** | |
133 * Holds the list of enabled modules. | |
134 * | |
135 * @var array | |
136 * An associative array whose keys are module names and whose values are | |
137 * ignored. | |
138 */ | |
139 protected $moduleList; | |
140 | |
141 /** | |
142 * List of available modules and installation profiles. | |
143 * | |
144 * @var \Drupal\Core\Extension\Extension[] | |
145 */ | |
146 protected $moduleData = []; | |
147 | |
148 /** | |
149 * The class loader object. | |
150 * | |
151 * @var \Composer\Autoload\ClassLoader | |
152 */ | |
153 protected $classLoader; | |
154 | |
155 /** | |
156 * Config storage object used for reading enabled modules configuration. | |
157 * | |
158 * @var \Drupal\Core\Config\StorageInterface | |
159 */ | |
160 protected $configStorage; | |
161 | |
162 /** | |
163 * Whether the container can be dumped. | |
164 * | |
165 * @var bool | |
166 */ | |
167 protected $allowDumping; | |
168 | |
169 /** | |
170 * Whether the container needs to be rebuilt the next time it is initialized. | |
171 * | |
172 * @var bool | |
173 */ | |
174 protected $containerNeedsRebuild = FALSE; | |
175 | |
176 /** | |
177 * Whether the container needs to be dumped once booting is complete. | |
178 * | |
179 * @var bool | |
180 */ | |
181 protected $containerNeedsDumping; | |
182 | |
183 /** | |
184 * List of discovered services.yml pathnames. | |
185 * | |
186 * This is a nested array whose top-level keys are 'app' and 'site', denoting | |
187 * the origin of a service provider. Site-specific providers have to be | |
188 * collected separately, because they need to be processed last, so as to be | |
189 * able to override services from application service providers. | |
190 * | |
191 * @var array | |
192 */ | |
193 protected $serviceYamls; | |
194 | |
195 /** | |
196 * List of discovered service provider class names or objects. | |
197 * | |
198 * This is a nested array whose top-level keys are 'app' and 'site', denoting | |
199 * the origin of a service provider. Site-specific providers have to be | |
200 * collected separately, because they need to be processed last, so as to be | |
201 * able to override services from application service providers. | |
202 * | |
203 * Allowing objects is for example used to allow | |
204 * \Drupal\KernelTests\KernelTestBase to register itself as service provider. | |
205 * | |
206 * @var array | |
207 */ | |
208 protected $serviceProviderClasses; | |
209 | |
210 /** | |
211 * List of instantiated service provider classes. | |
212 * | |
213 * @see \Drupal\Core\DrupalKernel::$serviceProviderClasses | |
214 * | |
215 * @var array | |
216 */ | |
217 protected $serviceProviders; | |
218 | |
219 /** | |
220 * Whether the PHP environment has been initialized. | |
221 * | |
222 * This legacy phase can only be booted once because it sets session INI | |
223 * settings. If a session has already been started, re-generating these | |
224 * settings would break the session. | |
225 * | |
226 * @var bool | |
227 */ | |
228 protected static $isEnvironmentInitialized = FALSE; | |
229 | |
230 /** | |
231 * The site directory. | |
232 * | |
233 * @var string | |
234 */ | |
235 protected $sitePath; | |
236 | |
237 /** | |
238 * The app root. | |
239 * | |
240 * @var string | |
241 */ | |
242 protected $root; | |
243 | |
244 /** | |
245 * Create a DrupalKernel object from a request. | |
246 * | |
247 * @param \Symfony\Component\HttpFoundation\Request $request | |
248 * The request. | |
249 * @param $class_loader | |
250 * The class loader. Normally Composer's ClassLoader, as included by the | |
251 * front controller, but may also be decorated; e.g., | |
252 * \Symfony\Component\ClassLoader\ApcClassLoader. | |
253 * @param string $environment | |
254 * String indicating the environment, e.g. 'prod' or 'dev'. | |
255 * @param bool $allow_dumping | |
256 * (optional) FALSE to stop the container from being written to or read | |
257 * from disk. Defaults to TRUE. | |
258 * @param string $app_root | |
259 * (optional) The path to the application root as a string. If not supplied, | |
260 * the application root will be computed. | |
261 * | |
262 * @return static | |
263 * | |
264 * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException | |
265 * In case the host name in the request is not trusted. | |
266 */ | |
267 public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE, $app_root = NULL) { | |
268 $kernel = new static($environment, $class_loader, $allow_dumping, $app_root); | |
269 static::bootEnvironment($app_root); | |
270 $kernel->initializeSettings($request); | |
271 return $kernel; | |
272 } | |
273 | |
274 /** | |
275 * Constructs a DrupalKernel object. | |
276 * | |
277 * @param string $environment | |
278 * String indicating the environment, e.g. 'prod' or 'dev'. | |
279 * @param $class_loader | |
280 * The class loader. Normally \Composer\Autoload\ClassLoader, as included by | |
281 * the front controller, but may also be decorated; e.g., | |
282 * \Symfony\Component\ClassLoader\ApcClassLoader. | |
283 * @param bool $allow_dumping | |
284 * (optional) FALSE to stop the container from being written to or read | |
285 * from disk. Defaults to TRUE. | |
286 * @param string $app_root | |
287 * (optional) The path to the application root as a string. If not supplied, | |
288 * the application root will be computed. | |
289 */ | |
290 public function __construct($environment, $class_loader, $allow_dumping = TRUE, $app_root = NULL) { | |
291 $this->environment = $environment; | |
292 $this->classLoader = $class_loader; | |
293 $this->allowDumping = $allow_dumping; | |
294 if ($app_root === NULL) { | |
295 $app_root = static::guessApplicationRoot(); | |
296 } | |
297 $this->root = $app_root; | |
298 } | |
299 | |
300 /** | |
301 * Determine the application root directory based on assumptions. | |
302 * | |
303 * @return string | |
304 * The application root. | |
305 */ | |
306 protected static function guessApplicationRoot() { | |
307 return dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))); | |
308 } | |
309 | |
310 /** | |
311 * Returns the appropriate site directory for a request. | |
312 * | |
313 * Once the kernel has been created DrupalKernelInterface::getSitePath() is | |
314 * preferred since it gets the statically cached result of this method. | |
315 * | |
316 * Site directories contain all site specific code. This includes settings.php | |
317 * for bootstrap level configuration, file configuration stores, public file | |
318 * storage and site specific modules and themes. | |
319 * | |
320 * Finds a matching site directory file by stripping the website's hostname | |
321 * from left to right and pathname from right to left. By default, the | |
322 * directory must contain a 'settings.php' file for it to match. If the | |
323 * parameter $require_settings is set to FALSE, then a directory without a | |
324 * 'settings.php' file will match as well. The first configuration file found | |
325 * will be used and the remaining ones will be ignored. If no configuration | |
326 * file is found, returns a default value 'sites/default'. See | |
327 * default.settings.php for examples on how the URL is converted to a | |
328 * directory. | |
329 * | |
330 * If a file named sites.php is present in the sites directory, it will be | |
331 * loaded prior to scanning for directories. That file can define aliases in | |
332 * an associative array named $sites. The array is written in the format | |
333 * '<port>.<domain>.<path>' => 'directory'. As an example, to create a | |
334 * directory alias for https://www.drupal.org:8080/mysite/test whose | |
335 * configuration file is in sites/example.com, the array should be defined as: | |
336 * @code | |
337 * $sites = array( | |
338 * '8080.www.drupal.org.mysite.test' => 'example.com', | |
339 * ); | |
340 * @endcode | |
341 * | |
342 * @param \Symfony\Component\HttpFoundation\Request $request | |
343 * The current request. | |
344 * @param bool $require_settings | |
345 * Only directories with an existing settings.php file will be recognized. | |
346 * Defaults to TRUE. During initial installation, this is set to FALSE so | |
347 * that Drupal can detect a matching directory, then create a new | |
348 * settings.php file in it. | |
349 * @param string $app_root | |
350 * (optional) The path to the application root as a string. If not supplied, | |
351 * the application root will be computed. | |
352 * | |
353 * @return string | |
354 * The path of the matching directory. | |
355 * | |
356 * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException | |
357 * In case the host name in the request is invalid. | |
358 * | |
359 * @see \Drupal\Core\DrupalKernelInterface::getSitePath() | |
360 * @see \Drupal\Core\DrupalKernelInterface::setSitePath() | |
361 * @see default.settings.php | |
362 * @see example.sites.php | |
363 */ | |
364 public static function findSitePath(Request $request, $require_settings = TRUE, $app_root = NULL) { | |
365 if (static::validateHostname($request) === FALSE) { | |
366 throw new BadRequestHttpException(); | |
367 } | |
368 | |
369 if ($app_root === NULL) { | |
370 $app_root = static::guessApplicationRoot(); | |
371 } | |
372 | |
373 // Check for a simpletest override. | |
374 if ($test_prefix = drupal_valid_test_ua()) { | |
375 $test_db = new TestDatabase($test_prefix); | |
376 return $test_db->getTestSitePath(); | |
377 } | |
378 | |
379 // Determine whether multi-site functionality is enabled. | |
380 if (!file_exists($app_root . '/sites/sites.php')) { | |
381 return 'sites/default'; | |
382 } | |
383 | |
384 // Otherwise, use find the site path using the request. | |
385 $script_name = $request->server->get('SCRIPT_NAME'); | |
386 if (!$script_name) { | |
387 $script_name = $request->server->get('SCRIPT_FILENAME'); | |
388 } | |
389 $http_host = $request->getHttpHost(); | |
390 | |
391 $sites = []; | |
392 include $app_root . '/sites/sites.php'; | |
393 | |
394 $uri = explode('/', $script_name); | |
395 $server = explode('.', implode('.', array_reverse(explode(':', rtrim($http_host, '.'))))); | |
396 for ($i = count($uri) - 1; $i > 0; $i--) { | |
397 for ($j = count($server); $j > 0; $j--) { | |
398 $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); | |
399 if (isset($sites[$dir]) && file_exists($app_root . '/sites/' . $sites[$dir])) { | |
400 $dir = $sites[$dir]; | |
401 } | |
402 if (file_exists($app_root . '/sites/' . $dir . '/settings.php') || (!$require_settings && file_exists($app_root . '/sites/' . $dir))) { | |
403 return "sites/$dir"; | |
404 } | |
405 } | |
406 } | |
407 return 'sites/default'; | |
408 } | |
409 | |
410 /** | |
411 * {@inheritdoc} | |
412 */ | |
413 public function setSitePath($path) { | |
414 if ($this->booted && $path !== $this->sitePath) { | |
415 throw new \LogicException('Site path cannot be changed after calling boot()'); | |
416 } | |
417 $this->sitePath = $path; | |
418 } | |
419 | |
420 /** | |
421 * {@inheritdoc} | |
422 */ | |
423 public function getSitePath() { | |
424 return $this->sitePath; | |
425 } | |
426 | |
427 /** | |
428 * {@inheritdoc} | |
429 */ | |
430 public function getAppRoot() { | |
431 return $this->root; | |
432 } | |
433 | |
434 /** | |
435 * {@inheritdoc} | |
436 */ | |
437 public function boot() { | |
438 if ($this->booted) { | |
439 return $this; | |
440 } | |
441 | |
442 // Ensure that findSitePath is set. | |
443 if (!$this->sitePath) { | |
444 throw new \Exception('Kernel does not have site path set before calling boot()'); | |
445 } | |
446 | |
447 // Initialize the FileCacheFactory component. We have to do it here instead | |
448 // of in \Drupal\Component\FileCache\FileCacheFactory because we can not use | |
449 // the Settings object in a component. | |
450 $configuration = Settings::get('file_cache'); | |
451 | |
452 // Provide a default configuration, if not set. | |
453 if (!isset($configuration['default'])) { | |
454 // @todo Use extension_loaded('apcu') for non-testbot | |
455 // https://www.drupal.org/node/2447753. | |
456 if (function_exists('apcu_fetch')) { | |
457 $configuration['default']['cache_backend_class'] = '\Drupal\Component\FileCache\ApcuFileCacheBackend'; | |
458 } | |
459 } | |
460 FileCacheFactory::setConfiguration($configuration); | |
461 FileCacheFactory::setPrefix(Settings::getApcuPrefix('file_cache', $this->root)); | |
462 | |
463 $this->bootstrapContainer = new $this->bootstrapContainerClass(Settings::get('bootstrap_container_definition', $this->defaultBootstrapContainerDefinition)); | |
464 | |
465 // Initialize the container. | |
466 $this->initializeContainer(); | |
467 | |
468 $this->booted = TRUE; | |
469 | |
470 return $this; | |
471 } | |
472 | |
473 /** | |
474 * {@inheritdoc} | |
475 */ | |
476 public function shutdown() { | |
477 if (FALSE === $this->booted) { | |
478 return; | |
479 } | |
480 $this->container->get('stream_wrapper_manager')->unregister(); | |
481 $this->booted = FALSE; | |
482 $this->container = NULL; | |
483 $this->moduleList = NULL; | |
484 $this->moduleData = []; | |
485 } | |
486 | |
487 /** | |
488 * {@inheritdoc} | |
489 */ | |
490 public function getContainer() { | |
491 return $this->container; | |
492 } | |
493 | |
494 /** | |
495 * {@inheritdoc} | |
496 */ | |
497 public function setContainer(ContainerInterface $container = NULL) { | |
498 if (isset($this->container)) { | |
499 throw new \Exception('The container should not override an existing container.'); | |
500 } | |
501 if ($this->booted) { | |
502 throw new \Exception('The container cannot be set after a booted kernel.'); | |
503 } | |
504 | |
505 $this->container = $container; | |
506 return $this; | |
507 } | |
508 | |
509 /** | |
510 * {@inheritdoc} | |
511 */ | |
512 public function getCachedContainerDefinition() { | |
513 $cache = $this->bootstrapContainer->get('cache.container')->get($this->getContainerCacheKey()); | |
514 | |
515 if ($cache) { | |
516 return $cache->data; | |
517 } | |
518 | |
519 return NULL; | |
520 } | |
521 | |
522 /** | |
523 * {@inheritdoc} | |
524 */ | |
525 public function loadLegacyIncludes() { | |
526 require_once $this->root . '/core/includes/common.inc'; | |
527 require_once $this->root . '/core/includes/database.inc'; | |
528 require_once $this->root . '/core/includes/module.inc'; | |
529 require_once $this->root . '/core/includes/theme.inc'; | |
530 require_once $this->root . '/core/includes/pager.inc'; | |
531 require_once $this->root . '/core/includes/menu.inc'; | |
532 require_once $this->root . '/core/includes/tablesort.inc'; | |
533 require_once $this->root . '/core/includes/file.inc'; | |
534 require_once $this->root . '/core/includes/unicode.inc'; | |
535 require_once $this->root . '/core/includes/form.inc'; | |
536 require_once $this->root . '/core/includes/errors.inc'; | |
537 require_once $this->root . '/core/includes/schema.inc'; | |
538 require_once $this->root . '/core/includes/entity.inc'; | |
539 } | |
540 | |
541 /** | |
542 * {@inheritdoc} | |
543 */ | |
544 public function preHandle(Request $request) { | |
545 | |
546 $this->loadLegacyIncludes(); | |
547 | |
548 // Load all enabled modules. | |
549 $this->container->get('module_handler')->loadAll(); | |
550 | |
551 // Register stream wrappers. | |
552 $this->container->get('stream_wrapper_manager')->register(); | |
553 | |
554 // Initialize legacy request globals. | |
555 $this->initializeRequestGlobals($request); | |
556 | |
557 // Put the request on the stack. | |
558 $this->container->get('request_stack')->push($request); | |
559 | |
560 // Set the allowed protocols. | |
561 UrlHelper::setAllowedProtocols($this->container->getParameter('filter_protocols')); | |
562 | |
563 // Override of Symfony's MIME type guesser singleton. | |
564 MimeTypeGuesser::registerWithSymfonyGuesser($this->container); | |
565 | |
566 $this->prepared = TRUE; | |
567 } | |
568 | |
569 /** | |
570 * {@inheritdoc} | |
571 */ | |
572 public function discoverServiceProviders() { | |
573 $this->serviceYamls = [ | |
574 'app' => [], | |
575 'site' => [], | |
576 ]; | |
577 $this->serviceProviderClasses = [ | |
578 'app' => [], | |
579 'site' => [], | |
580 ]; | |
581 $this->serviceYamls['app']['core'] = 'core/core.services.yml'; | |
582 $this->serviceProviderClasses['app']['core'] = 'Drupal\Core\CoreServiceProvider'; | |
583 | |
584 // Retrieve enabled modules and register their namespaces. | |
585 if (!isset($this->moduleList)) { | |
586 $extensions = $this->getConfigStorage()->read('core.extension'); | |
587 $this->moduleList = isset($extensions['module']) ? $extensions['module'] : []; | |
588 } | |
589 $module_filenames = $this->getModuleFileNames(); | |
590 $this->classLoaderAddMultiplePsr4($this->getModuleNamespacesPsr4($module_filenames)); | |
591 | |
592 // Load each module's serviceProvider class. | |
593 foreach ($module_filenames as $module => $filename) { | |
594 $camelized = ContainerBuilder::camelize($module); | |
595 $name = "{$camelized}ServiceProvider"; | |
596 $class = "Drupal\\{$module}\\{$name}"; | |
597 if (class_exists($class)) { | |
598 $this->serviceProviderClasses['app'][$module] = $class; | |
599 } | |
600 $filename = dirname($filename) . "/$module.services.yml"; | |
601 if (file_exists($filename)) { | |
602 $this->serviceYamls['app'][$module] = $filename; | |
603 } | |
604 } | |
605 | |
606 // Add site-specific service providers. | |
607 if (!empty($GLOBALS['conf']['container_service_providers'])) { | |
608 foreach ($GLOBALS['conf']['container_service_providers'] as $class) { | |
609 if ((is_string($class) && class_exists($class)) || (is_object($class) && ($class instanceof ServiceProviderInterface || $class instanceof ServiceModifierInterface))) { | |
610 $this->serviceProviderClasses['site'][] = $class; | |
611 } | |
612 } | |
613 } | |
614 $this->addServiceFiles(Settings::get('container_yamls', [])); | |
615 } | |
616 | |
617 /** | |
618 * {@inheritdoc} | |
619 */ | |
620 public function getServiceProviders($origin) { | |
621 return $this->serviceProviders[$origin]; | |
622 } | |
623 | |
624 /** | |
625 * {@inheritdoc} | |
626 */ | |
627 public function terminate(Request $request, Response $response) { | |
628 // Only run terminate() when essential services have been set up properly | |
629 // by preHandle() before. | |
630 if (FALSE === $this->prepared) { | |
631 return; | |
632 } | |
633 | |
634 if ($this->getHttpKernel() instanceof TerminableInterface) { | |
635 $this->getHttpKernel()->terminate($request, $response); | |
636 } | |
637 } | |
638 | |
639 /** | |
640 * {@inheritdoc} | |
641 */ | |
642 public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { | |
643 // Ensure sane PHP environment variables. | |
644 static::bootEnvironment(); | |
645 | |
646 try { | |
647 $this->initializeSettings($request); | |
648 | |
649 // Redirect the user to the installation script if Drupal has not been | |
650 // installed yet (i.e., if no $databases array has been defined in the | |
651 // settings.php file) and we are not already installing. | |
652 if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') { | |
653 $response = new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']); | |
654 } | |
655 else { | |
656 $this->boot(); | |
657 $response = $this->getHttpKernel()->handle($request, $type, $catch); | |
658 } | |
659 } | |
660 catch (\Exception $e) { | |
661 if ($catch === FALSE) { | |
662 throw $e; | |
663 } | |
664 | |
665 $response = $this->handleException($e, $request, $type); | |
666 } | |
667 | |
668 // Adapt response headers to the current request. | |
669 $response->prepare($request); | |
670 | |
671 return $response; | |
672 } | |
673 | |
674 /** | |
675 * Converts an exception into a response. | |
676 * | |
677 * @param \Exception $e | |
678 * An exception | |
679 * @param Request $request | |
680 * A Request instance | |
681 * @param int $type | |
682 * The type of the request (one of HttpKernelInterface::MASTER_REQUEST or | |
683 * HttpKernelInterface::SUB_REQUEST) | |
684 * | |
685 * @return Response | |
686 * A Response instance | |
687 * | |
688 * @throws \Exception | |
689 * If the passed in exception cannot be turned into a response. | |
690 */ | |
691 protected function handleException(\Exception $e, $request, $type) { | |
692 if ($this->shouldRedirectToInstaller($e, $this->container ? $this->container->get('database') : NULL)) { | |
693 return new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']); | |
694 } | |
695 | |
696 if ($e instanceof HttpExceptionInterface) { | |
697 $response = new Response($e->getMessage(), $e->getStatusCode()); | |
698 $response->headers->add($e->getHeaders()); | |
699 return $response; | |
700 } | |
701 | |
702 throw $e; | |
703 } | |
704 | |
705 /** | |
706 * {@inheritdoc} | |
707 */ | |
708 public function prepareLegacyRequest(Request $request) { | |
709 $this->boot(); | |
710 $this->preHandle($request); | |
711 // Setup services which are normally initialized from within stack | |
712 // middleware or during the request kernel event. | |
713 if (PHP_SAPI !== 'cli') { | |
714 $request->setSession($this->container->get('session')); | |
715 } | |
716 $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('<none>')); | |
717 $request->attributes->set(RouteObjectInterface::ROUTE_NAME, '<none>'); | |
718 $this->container->get('request_stack')->push($request); | |
719 $this->container->get('router.request_context')->fromRequest($request); | |
720 return $this; | |
721 } | |
722 | |
723 /** | |
724 * Returns module data on the filesystem. | |
725 * | |
726 * @param $module | |
727 * The name of the module. | |
728 * | |
729 * @return \Drupal\Core\Extension\Extension|bool | |
730 * Returns an Extension object if the module is found, FALSE otherwise. | |
731 */ | |
732 protected function moduleData($module) { | |
733 if (!$this->moduleData) { | |
734 // First, find profiles. | |
735 $listing = new ExtensionDiscovery($this->root); | |
736 $listing->setProfileDirectories([]); | |
737 $all_profiles = $listing->scan('profile'); | |
738 $profiles = array_intersect_key($all_profiles, $this->moduleList); | |
739 | |
740 // If a module is within a profile directory but specifies another | |
741 // profile for testing, it needs to be found in the parent profile. | |
742 $settings = $this->getConfigStorage()->read('simpletest.settings'); | |
743 $parent_profile = !empty($settings['parent_profile']) ? $settings['parent_profile'] : NULL; | |
744 if ($parent_profile && !isset($profiles[$parent_profile])) { | |
745 // In case both profile directories contain the same extension, the | |
746 // actual profile always has precedence. | |
747 $profiles = [$parent_profile => $all_profiles[$parent_profile]] + $profiles; | |
748 } | |
749 | |
750 $profile_directories = array_map(function ($profile) { | |
751 return $profile->getPath(); | |
752 }, $profiles); | |
753 $listing->setProfileDirectories($profile_directories); | |
754 | |
755 // Now find modules. | |
756 $this->moduleData = $profiles + $listing->scan('module'); | |
757 } | |
758 return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE; | |
759 } | |
760 | |
761 /** | |
762 * Implements Drupal\Core\DrupalKernelInterface::updateModules(). | |
763 * | |
764 * @todo Remove obsolete $module_list parameter. Only $module_filenames is | |
765 * needed. | |
766 */ | |
767 public function updateModules(array $module_list, array $module_filenames = []) { | |
768 $pre_existing_module_namespaces = []; | |
769 if ($this->booted && is_array($this->moduleList)) { | |
770 $pre_existing_module_namespaces = $this->getModuleNamespacesPsr4($this->getModuleFileNames()); | |
771 } | |
772 $this->moduleList = $module_list; | |
773 foreach ($module_filenames as $name => $extension) { | |
774 $this->moduleData[$name] = $extension; | |
775 } | |
776 | |
777 // If we haven't yet booted, we don't need to do anything: the new module | |
778 // list will take effect when boot() is called. However we set a | |
779 // flag that the container needs a rebuild, so that a potentially cached | |
780 // container is not used. If we have already booted, then rebuild the | |
781 // container in order to refresh the serviceProvider list and container. | |
782 $this->containerNeedsRebuild = TRUE; | |
783 if ($this->booted) { | |
784 // We need to register any new namespaces to a new class loader because | |
785 // the current class loader might have stored a negative result for a | |
786 // class that is now available. | |
787 // @see \Composer\Autoload\ClassLoader::findFile() | |
788 $new_namespaces = array_diff_key( | |
789 $this->getModuleNamespacesPsr4($this->getModuleFileNames()), | |
790 $pre_existing_module_namespaces | |
791 ); | |
792 if (!empty($new_namespaces)) { | |
793 $additional_class_loader = new ClassLoader(); | |
794 $this->classLoaderAddMultiplePsr4($new_namespaces, $additional_class_loader); | |
795 $additional_class_loader->register(); | |
796 } | |
797 | |
798 $this->initializeContainer(); | |
799 } | |
800 } | |
801 | |
802 /** | |
803 * Returns the container cache key based on the environment. | |
804 * | |
805 * The 'environment' consists of: | |
806 * - The kernel environment string. | |
807 * - The Drupal version constant. | |
808 * - The deployment identifier from settings.php. This allows custom | |
809 * deployments to force a container rebuild. | |
810 * - The operating system running PHP. This allows compiler passes to optimize | |
811 * services for different operating systems. | |
812 * - The paths to any additional container YAMLs from settings.php. | |
813 * | |
814 * @return string | |
815 * The cache key used for the service container. | |
816 */ | |
817 protected function getContainerCacheKey() { | |
818 $parts = ['service_container', $this->environment, \Drupal::VERSION, Settings::get('deployment_identifier'), PHP_OS, serialize(Settings::get('container_yamls'))]; | |
819 return implode(':', $parts); | |
820 } | |
821 | |
822 /** | |
823 * Returns the kernel parameters. | |
824 * | |
825 * @return array An array of kernel parameters | |
826 */ | |
827 protected function getKernelParameters() { | |
828 return [ | |
829 'kernel.environment' => $this->environment, | |
830 ]; | |
831 } | |
832 | |
833 /** | |
834 * Initializes the service container. | |
835 * | |
836 * @return \Symfony\Component\DependencyInjection\ContainerInterface | |
837 */ | |
838 protected function initializeContainer() { | |
839 $this->containerNeedsDumping = FALSE; | |
840 $session_started = FALSE; | |
841 if (isset($this->container)) { | |
842 // Save the id of the currently logged in user. | |
843 if ($this->container->initialized('current_user')) { | |
844 $current_user_id = $this->container->get('current_user')->id(); | |
845 } | |
846 | |
847 // If there is a session, close and save it. | |
848 if ($this->container->initialized('session')) { | |
849 $session = $this->container->get('session'); | |
850 if ($session->isStarted()) { | |
851 $session_started = TRUE; | |
852 $session->save(); | |
853 } | |
854 unset($session); | |
855 } | |
856 } | |
857 | |
858 // If we haven't booted yet but there is a container, then we're asked to | |
859 // boot the container injected via setContainer(). | |
860 // @see \Drupal\KernelTests\KernelTestBase::setUp() | |
861 if (isset($this->container) && !$this->booted) { | |
862 $container = $this->container; | |
863 } | |
864 | |
865 // If the module list hasn't already been set in updateModules and we are | |
866 // not forcing a rebuild, then try and load the container from the cache. | |
867 if (empty($this->moduleList) && !$this->containerNeedsRebuild) { | |
868 $container_definition = $this->getCachedContainerDefinition(); | |
869 } | |
870 | |
871 // If there is no container and no cached container definition, build a new | |
872 // one from scratch. | |
873 if (!isset($container) && !isset($container_definition)) { | |
874 // Building the container creates 1000s of objects. Garbage collection of | |
875 // these objects is expensive. This appears to be causing random | |
876 // segmentation faults in PHP 5.6 due to | |
877 // https://bugs.php.net/bug.php?id=72286. Once the container is rebuilt, | |
878 // garbage collection is re-enabled. | |
879 $disable_gc = version_compare(PHP_VERSION, '7', '<') && gc_enabled(); | |
880 if ($disable_gc) { | |
881 gc_collect_cycles(); | |
882 gc_disable(); | |
883 } | |
884 $container = $this->compileContainer(); | |
885 | |
886 // Only dump the container if dumping is allowed. This is useful for | |
887 // KernelTestBase, which never wants to use the real container, but always | |
888 // the container builder. | |
889 if ($this->allowDumping) { | |
890 $dumper = new $this->phpArrayDumperClass($container); | |
891 $container_definition = $dumper->getArray(); | |
892 } | |
893 // If garbage collection was disabled prior to rebuilding container, | |
894 // re-enable it. | |
895 if ($disable_gc) { | |
896 gc_enable(); | |
897 } | |
898 } | |
899 | |
900 // The container was rebuilt successfully. | |
901 $this->containerNeedsRebuild = FALSE; | |
902 | |
903 // Only create a new class if we have a container definition. | |
904 if (isset($container_definition)) { | |
905 $class = Settings::get('container_base_class', '\Drupal\Core\DependencyInjection\Container'); | |
906 $container = new $class($container_definition); | |
907 } | |
908 | |
909 $this->attachSynthetic($container); | |
910 | |
911 $this->container = $container; | |
912 if ($session_started) { | |
913 $this->container->get('session')->start(); | |
914 } | |
915 | |
916 // The request stack is preserved across container rebuilds. Reinject the | |
917 // new session into the master request if one was present before. | |
918 if (($request_stack = $this->container->get('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE))) { | |
919 if ($request = $request_stack->getMasterRequest()) { | |
920 $subrequest = TRUE; | |
921 if ($request->hasSession()) { | |
922 $request->setSession($this->container->get('session')); | |
923 } | |
924 } | |
925 } | |
926 | |
927 if (!empty($current_user_id)) { | |
928 $this->container->get('current_user')->setInitialAccountId($current_user_id); | |
929 } | |
930 | |
931 \Drupal::setContainer($this->container); | |
932 | |
933 // Allow other parts of the codebase to react on container initialization in | |
934 // subrequest. | |
935 if (!empty($subrequest)) { | |
936 $this->container->get('event_dispatcher')->dispatch(self::CONTAINER_INITIALIZE_SUBREQUEST_FINISHED); | |
937 } | |
938 | |
939 // If needs dumping flag was set, dump the container. | |
940 if ($this->containerNeedsDumping && !$this->cacheDrupalContainer($container_definition)) { | |
941 $this->container->get('logger.factory')->get('DrupalKernel')->error('Container cannot be saved to cache.'); | |
942 } | |
943 | |
944 return $this->container; | |
945 } | |
946 | |
947 /** | |
948 * Setup a consistent PHP environment. | |
949 * | |
950 * This method sets PHP environment options we want to be sure are set | |
951 * correctly for security or just saneness. | |
952 * | |
953 * @param string $app_root | |
954 * (optional) The path to the application root as a string. If not supplied, | |
955 * the application root will be computed. | |
956 */ | |
957 public static function bootEnvironment($app_root = NULL) { | |
958 if (static::$isEnvironmentInitialized) { | |
959 return; | |
960 } | |
961 | |
962 // Determine the application root if it's not supplied. | |
963 if ($app_root === NULL) { | |
964 $app_root = static::guessApplicationRoot(); | |
965 } | |
966 | |
967 // Include our bootstrap file. | |
968 require_once $app_root . '/core/includes/bootstrap.inc'; | |
969 | |
970 // Enforce E_STRICT, but allow users to set levels not part of E_STRICT. | |
971 error_reporting(E_STRICT | E_ALL); | |
972 | |
973 // Override PHP settings required for Drupal to work properly. | |
974 // sites/default/default.settings.php contains more runtime settings. | |
975 // The .htaccess file contains settings that cannot be changed at runtime. | |
976 | |
977 // Use session cookies, not transparent sessions that puts the session id in | |
978 // the query string. | |
979 ini_set('session.use_cookies', '1'); | |
980 ini_set('session.use_only_cookies', '1'); | |
981 ini_set('session.use_trans_sid', '0'); | |
982 // Don't send HTTP headers using PHP's session handler. | |
983 // Send an empty string to disable the cache limiter. | |
984 ini_set('session.cache_limiter', ''); | |
985 // Use httponly session cookies. | |
986 ini_set('session.cookie_httponly', '1'); | |
987 | |
988 // Set sane locale settings, to ensure consistent string, dates, times and | |
989 // numbers handling. | |
990 setlocale(LC_ALL, 'C'); | |
991 | |
992 // Detect string handling method. | |
993 Unicode::check(); | |
994 | |
995 // Indicate that code is operating in a test child site. | |
996 if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) { | |
997 if ($test_prefix = drupal_valid_test_ua()) { | |
998 $test_db = new TestDatabase($test_prefix); | |
999 // Only code that interfaces directly with tests should rely on this | |
1000 // constant; e.g., the error/exception handler conditionally adds further | |
1001 // error information into HTTP response headers that are consumed by | |
1002 // Simpletest's internal browser. | |
1003 define('DRUPAL_TEST_IN_CHILD_SITE', TRUE); | |
1004 | |
1005 // Web tests are to be conducted with runtime assertions active. | |
1006 assert_options(ASSERT_ACTIVE, TRUE); | |
1007 // Now synchronize PHP 5 and 7's handling of assertions as much as | |
1008 // possible. | |
1009 Handle::register(); | |
1010 | |
1011 // Log fatal errors to the test site directory. | |
1012 ini_set('log_errors', 1); | |
1013 ini_set('error_log', $app_root . '/' . $test_db->getTestSitePath() . '/error.log'); | |
1014 | |
1015 // Ensure that a rewritten settings.php is used if opcache is on. | |
1016 ini_set('opcache.validate_timestamps', 'on'); | |
1017 ini_set('opcache.revalidate_freq', 0); | |
1018 } | |
1019 else { | |
1020 // Ensure that no other code defines this. | |
1021 define('DRUPAL_TEST_IN_CHILD_SITE', FALSE); | |
1022 } | |
1023 } | |
1024 | |
1025 // Set the Drupal custom error handler. | |
1026 set_error_handler('_drupal_error_handler'); | |
1027 set_exception_handler('_drupal_exception_handler'); | |
1028 | |
1029 static::$isEnvironmentInitialized = TRUE; | |
1030 } | |
1031 | |
1032 /** | |
1033 * Locate site path and initialize settings singleton. | |
1034 * | |
1035 * @param \Symfony\Component\HttpFoundation\Request $request | |
1036 * The current request. | |
1037 * | |
1038 * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException | |
1039 * In case the host name in the request is not trusted. | |
1040 */ | |
1041 protected function initializeSettings(Request $request) { | |
1042 $site_path = static::findSitePath($request); | |
1043 $this->setSitePath($site_path); | |
1044 $class_loader_class = get_class($this->classLoader); | |
1045 Settings::initialize($this->root, $site_path, $this->classLoader); | |
1046 | |
1047 // Initialize our list of trusted HTTP Host headers to protect against | |
1048 // header attacks. | |
1049 $host_patterns = Settings::get('trusted_host_patterns', []); | |
1050 if (PHP_SAPI !== 'cli' && !empty($host_patterns)) { | |
1051 if (static::setupTrustedHosts($request, $host_patterns) === FALSE) { | |
1052 throw new BadRequestHttpException('The provided host name is not valid for this server.'); | |
1053 } | |
1054 } | |
1055 | |
1056 // If the class loader is still the same, possibly | |
1057 // upgrade to an optimized class loader. | |
1058 if ($class_loader_class == get_class($this->classLoader) | |
1059 && Settings::get('class_loader_auto_detect', TRUE)) { | |
1060 $prefix = Settings::getApcuPrefix('class_loader', $this->root); | |
1061 $loader = NULL; | |
1062 | |
1063 // We autodetect one of the following three optimized classloaders, if | |
1064 // their underlying extension exists. | |
1065 if (function_exists('apcu_fetch')) { | |
1066 $loader = new ApcClassLoader($prefix, $this->classLoader); | |
1067 } | |
1068 elseif (extension_loaded('wincache')) { | |
1069 $loader = new WinCacheClassLoader($prefix, $this->classLoader); | |
1070 } | |
1071 elseif (extension_loaded('xcache')) { | |
1072 $loader = new XcacheClassLoader($prefix, $this->classLoader); | |
1073 } | |
1074 if (!empty($loader)) { | |
1075 $this->classLoader->unregister(); | |
1076 // The optimized classloader might be persistent and store cache misses. | |
1077 // For example, once a cache miss is stored in APCu clearing it on a | |
1078 // specific web-head will not clear any other web-heads. Therefore | |
1079 // fallback to the composer class loader that only statically caches | |
1080 // misses. | |
1081 $old_loader = $this->classLoader; | |
1082 $this->classLoader = $loader; | |
1083 // Our class loaders are preprended to ensure they come first like the | |
1084 // class loader they are replacing. | |
1085 $old_loader->register(TRUE); | |
1086 $loader->register(TRUE); | |
1087 } | |
1088 } | |
1089 } | |
1090 | |
1091 /** | |
1092 * Bootstraps the legacy global request variables. | |
1093 * | |
1094 * @param \Symfony\Component\HttpFoundation\Request $request | |
1095 * The current request. | |
1096 * | |
1097 * @todo D8: Eliminate this entirely in favor of Request object. | |
1098 */ | |
1099 protected function initializeRequestGlobals(Request $request) { | |
1100 global $base_url; | |
1101 // Set and derived from $base_url by this function. | |
1102 global $base_path, $base_root; | |
1103 global $base_secure_url, $base_insecure_url; | |
1104 | |
1105 // Create base URL. | |
1106 $base_root = $request->getSchemeAndHttpHost(); | |
1107 $base_url = $base_root; | |
1108 | |
1109 // For a request URI of '/index.php/foo', $_SERVER['SCRIPT_NAME'] is | |
1110 // '/index.php', whereas $_SERVER['PHP_SELF'] is '/index.php/foo'. | |
1111 if ($dir = rtrim(dirname($request->server->get('SCRIPT_NAME')), '\/')) { | |
1112 // Remove "core" directory if present, allowing install.php, | |
1113 // authorize.php, and others to auto-detect a base path. | |
1114 $core_position = strrpos($dir, '/core'); | |
1115 if ($core_position !== FALSE && strlen($dir) - 5 == $core_position) { | |
1116 $base_path = substr($dir, 0, $core_position); | |
1117 } | |
1118 else { | |
1119 $base_path = $dir; | |
1120 } | |
1121 $base_url .= $base_path; | |
1122 $base_path .= '/'; | |
1123 } | |
1124 else { | |
1125 $base_path = '/'; | |
1126 } | |
1127 $base_secure_url = str_replace('http://', 'https://', $base_url); | |
1128 $base_insecure_url = str_replace('https://', 'http://', $base_url); | |
1129 } | |
1130 | |
1131 /** | |
1132 * Returns service instances to persist from an old container to a new one. | |
1133 */ | |
1134 protected function getServicesToPersist(ContainerInterface $container) { | |
1135 $persist = []; | |
1136 foreach ($container->getParameter('persist_ids') as $id) { | |
1137 // It's pointless to persist services not yet initialized. | |
1138 if ($container->initialized($id)) { | |
1139 $persist[$id] = $container->get($id); | |
1140 } | |
1141 } | |
1142 return $persist; | |
1143 } | |
1144 | |
1145 /** | |
1146 * Moves persistent service instances into a new container. | |
1147 */ | |
1148 protected function persistServices(ContainerInterface $container, array $persist) { | |
1149 foreach ($persist as $id => $object) { | |
1150 // Do not override services already set() on the new container, for | |
1151 // example 'service_container'. | |
1152 if (!$container->initialized($id)) { | |
1153 $container->set($id, $object); | |
1154 } | |
1155 } | |
1156 } | |
1157 | |
1158 /** | |
1159 * {@inheritdoc} | |
1160 */ | |
1161 public function rebuildContainer() { | |
1162 // Empty module properties and for them to be reloaded from scratch. | |
1163 $this->moduleList = NULL; | |
1164 $this->moduleData = []; | |
1165 $this->containerNeedsRebuild = TRUE; | |
1166 return $this->initializeContainer(); | |
1167 } | |
1168 | |
1169 /** | |
1170 * {@inheritdoc} | |
1171 */ | |
1172 public function invalidateContainer() { | |
1173 // An invalidated container needs a rebuild. | |
1174 $this->containerNeedsRebuild = TRUE; | |
1175 | |
1176 // If we have not yet booted, settings or bootstrap services might not yet | |
1177 // be available. In that case the container will not be loaded from cache | |
1178 // due to the above setting when the Kernel is booted. | |
1179 if (!$this->booted) { | |
1180 return; | |
1181 } | |
1182 | |
1183 // Also remove the container definition from the cache backend. | |
1184 $this->bootstrapContainer->get('cache.container')->deleteAll(); | |
1185 } | |
1186 | |
1187 /** | |
1188 * Attach synthetic values on to kernel. | |
1189 * | |
1190 * @param ContainerInterface $container | |
1191 * Container object | |
1192 * | |
1193 * @return ContainerInterface | |
1194 */ | |
1195 protected function attachSynthetic(ContainerInterface $container) { | |
1196 $persist = []; | |
1197 if (isset($this->container)) { | |
1198 $persist = $this->getServicesToPersist($this->container); | |
1199 } | |
1200 $this->persistServices($container, $persist); | |
1201 | |
1202 // All namespaces must be registered before we attempt to use any service | |
1203 // from the container. | |
1204 $this->classLoaderAddMultiplePsr4($container->getParameter('container.namespaces')); | |
1205 | |
1206 $container->set('kernel', $this); | |
1207 | |
1208 // Set the class loader which was registered as a synthetic service. | |
1209 $container->set('class_loader', $this->classLoader); | |
1210 return $container; | |
1211 } | |
1212 | |
1213 /** | |
1214 * Compiles a new service container. | |
1215 * | |
1216 * @return ContainerBuilder The compiled service container | |
1217 */ | |
1218 protected function compileContainer() { | |
1219 // We are forcing a container build so it is reasonable to assume that the | |
1220 // calling method knows something about the system has changed requiring the | |
1221 // container to be dumped to the filesystem. | |
1222 if ($this->allowDumping) { | |
1223 $this->containerNeedsDumping = TRUE; | |
1224 } | |
1225 | |
1226 $this->initializeServiceProviders(); | |
1227 $container = $this->getContainerBuilder(); | |
1228 $container->set('kernel', $this); | |
1229 $container->setParameter('container.modules', $this->getModulesParameter()); | |
1230 $container->setParameter('install_profile', $this->getInstallProfile()); | |
1231 | |
1232 // Get a list of namespaces and put it onto the container. | |
1233 $namespaces = $this->getModuleNamespacesPsr4($this->getModuleFileNames()); | |
1234 // Add all components in \Drupal\Core and \Drupal\Component that have one of | |
1235 // the following directories: | |
1236 // - Element | |
1237 // - Entity | |
1238 // - Plugin | |
1239 foreach (['Core', 'Component'] as $parent_directory) { | |
1240 $path = 'core/lib/Drupal/' . $parent_directory; | |
1241 $parent_namespace = 'Drupal\\' . $parent_directory; | |
1242 foreach (new \DirectoryIterator($this->root . '/' . $path) as $component) { | |
1243 /** @var $component \DirectoryIterator */ | |
1244 $pathname = $component->getPathname(); | |
1245 if (!$component->isDot() && $component->isDir() && ( | |
1246 is_dir($pathname . '/Plugin') || | |
1247 is_dir($pathname . '/Entity') || | |
1248 is_dir($pathname . '/Element') | |
1249 )) { | |
1250 $namespaces[$parent_namespace . '\\' . $component->getFilename()] = $path . '/' . $component->getFilename(); | |
1251 } | |
1252 } | |
1253 } | |
1254 $container->setParameter('container.namespaces', $namespaces); | |
1255 | |
1256 // Store the default language values on the container. This is so that the | |
1257 // default language can be configured using the configuration factory. This | |
1258 // avoids the circular dependencies that would created by | |
1259 // \Drupal\language\LanguageServiceProvider::alter() and allows the default | |
1260 // language to not be English in the installer. | |
1261 $default_language_values = Language::$defaultValues; | |
1262 if ($system = $this->getConfigStorage()->read('system.site')) { | |
1263 if ($default_language_values['id'] != $system['langcode']) { | |
1264 $default_language_values = ['id' => $system['langcode']]; | |
1265 } | |
1266 } | |
1267 $container->setParameter('language.default_values', $default_language_values); | |
1268 | |
1269 // Register synthetic services. | |
1270 $container->register('class_loader')->setSynthetic(TRUE); | |
1271 $container->register('kernel', 'Symfony\Component\HttpKernel\KernelInterface')->setSynthetic(TRUE); | |
1272 $container->register('service_container', 'Symfony\Component\DependencyInjection\ContainerInterface')->setSynthetic(TRUE); | |
1273 | |
1274 // Register application services. | |
1275 $yaml_loader = new YamlFileLoader($container); | |
1276 foreach ($this->serviceYamls['app'] as $filename) { | |
1277 $yaml_loader->load($filename); | |
1278 } | |
1279 foreach ($this->serviceProviders['app'] as $provider) { | |
1280 if ($provider instanceof ServiceProviderInterface) { | |
1281 $provider->register($container); | |
1282 } | |
1283 } | |
1284 // Register site-specific service overrides. | |
1285 foreach ($this->serviceYamls['site'] as $filename) { | |
1286 $yaml_loader->load($filename); | |
1287 } | |
1288 foreach ($this->serviceProviders['site'] as $provider) { | |
1289 if ($provider instanceof ServiceProviderInterface) { | |
1290 $provider->register($container); | |
1291 } | |
1292 } | |
1293 | |
1294 // Identify all services whose instances should be persisted when rebuilding | |
1295 // the container during the lifetime of the kernel (e.g., during a kernel | |
1296 // reboot). Include synthetic services, because by definition, they cannot | |
1297 // be automatically reinstantiated. Also include services tagged to persist. | |
1298 $persist_ids = []; | |
1299 foreach ($container->getDefinitions() as $id => $definition) { | |
1300 // It does not make sense to persist the container itself, exclude it. | |
1301 if ($id !== 'service_container' && ($definition->isSynthetic() || $definition->getTag('persist'))) { | |
1302 $persist_ids[] = $id; | |
1303 } | |
1304 } | |
1305 $container->setParameter('persist_ids', $persist_ids); | |
1306 | |
1307 $container->compile(); | |
1308 return $container; | |
1309 } | |
1310 | |
1311 /** | |
1312 * Registers all service providers to the kernel. | |
1313 * | |
1314 * @throws \LogicException | |
1315 */ | |
1316 protected function initializeServiceProviders() { | |
1317 $this->discoverServiceProviders(); | |
1318 $this->serviceProviders = [ | |
1319 'app' => [], | |
1320 'site' => [], | |
1321 ]; | |
1322 foreach ($this->serviceProviderClasses as $origin => $classes) { | |
1323 foreach ($classes as $name => $class) { | |
1324 if (!is_object($class)) { | |
1325 $this->serviceProviders[$origin][$name] = new $class(); | |
1326 } | |
1327 else { | |
1328 $this->serviceProviders[$origin][$name] = $class; | |
1329 } | |
1330 } | |
1331 } | |
1332 } | |
1333 | |
1334 /** | |
1335 * Gets a new ContainerBuilder instance used to build the service container. | |
1336 * | |
1337 * @return ContainerBuilder | |
1338 */ | |
1339 protected function getContainerBuilder() { | |
1340 return new ContainerBuilder(new ParameterBag($this->getKernelParameters())); | |
1341 } | |
1342 | |
1343 /** | |
1344 * Stores the container definition in a cache. | |
1345 * | |
1346 * @param array $container_definition | |
1347 * The container definition to cache. | |
1348 * | |
1349 * @return bool | |
1350 * TRUE if the container was successfully cached. | |
1351 */ | |
1352 protected function cacheDrupalContainer(array $container_definition) { | |
1353 $saved = TRUE; | |
1354 try { | |
1355 $this->bootstrapContainer->get('cache.container')->set($this->getContainerCacheKey(), $container_definition); | |
1356 } | |
1357 catch (\Exception $e) { | |
1358 // There is no way to get from the Cache API if the cache set was | |
1359 // successful or not, hence an Exception is caught and the caller informed | |
1360 // about the error condition. | |
1361 $saved = FALSE; | |
1362 } | |
1363 | |
1364 return $saved; | |
1365 } | |
1366 | |
1367 /** | |
1368 * Gets a http kernel from the container | |
1369 * | |
1370 * @return \Symfony\Component\HttpKernel\HttpKernelInterface | |
1371 */ | |
1372 protected function getHttpKernel() { | |
1373 return $this->container->get('http_kernel'); | |
1374 } | |
1375 | |
1376 /** | |
1377 * Returns the active configuration storage to use during building the container. | |
1378 * | |
1379 * @return \Drupal\Core\Config\StorageInterface | |
1380 */ | |
1381 protected function getConfigStorage() { | |
1382 if (!isset($this->configStorage)) { | |
1383 // The active configuration storage may not exist yet; e.g., in the early | |
1384 // installer. Catch the exception thrown by config_get_config_directory(). | |
1385 try { | |
1386 $this->configStorage = BootstrapConfigStorageFactory::get($this->classLoader); | |
1387 } | |
1388 catch (\Exception $e) { | |
1389 $this->configStorage = new NullStorage(); | |
1390 } | |
1391 } | |
1392 return $this->configStorage; | |
1393 } | |
1394 | |
1395 /** | |
1396 * Returns an array of Extension class parameters for all enabled modules. | |
1397 * | |
1398 * @return array | |
1399 */ | |
1400 protected function getModulesParameter() { | |
1401 $extensions = []; | |
1402 foreach ($this->moduleList as $name => $weight) { | |
1403 if ($data = $this->moduleData($name)) { | |
1404 $extensions[$name] = [ | |
1405 'type' => $data->getType(), | |
1406 'pathname' => $data->getPathname(), | |
1407 'filename' => $data->getExtensionFilename(), | |
1408 ]; | |
1409 } | |
1410 } | |
1411 return $extensions; | |
1412 } | |
1413 | |
1414 /** | |
1415 * Gets the file name for each enabled module. | |
1416 * | |
1417 * @return array | |
1418 * Array where each key is a module name, and each value is a path to the | |
1419 * respective *.info.yml file. | |
1420 */ | |
1421 protected function getModuleFileNames() { | |
1422 $filenames = []; | |
1423 foreach ($this->moduleList as $module => $weight) { | |
1424 if ($data = $this->moduleData($module)) { | |
1425 $filenames[$module] = $data->getPathname(); | |
1426 } | |
1427 } | |
1428 return $filenames; | |
1429 } | |
1430 | |
1431 /** | |
1432 * Gets the PSR-4 base directories for module namespaces. | |
1433 * | |
1434 * @param string[] $module_file_names | |
1435 * Array where each key is a module name, and each value is a path to the | |
1436 * respective *.info.yml file. | |
1437 * | |
1438 * @return string[] | |
1439 * Array where each key is a module namespace like 'Drupal\system', and each | |
1440 * value is the PSR-4 base directory associated with the module namespace. | |
1441 */ | |
1442 protected function getModuleNamespacesPsr4($module_file_names) { | |
1443 $namespaces = []; | |
1444 foreach ($module_file_names as $module => $filename) { | |
1445 $namespaces["Drupal\\$module"] = dirname($filename) . '/src'; | |
1446 } | |
1447 return $namespaces; | |
1448 } | |
1449 | |
1450 /** | |
1451 * Registers a list of namespaces with PSR-4 directories for class loading. | |
1452 * | |
1453 * @param array $namespaces | |
1454 * Array where each key is a namespace like 'Drupal\system', and each value | |
1455 * is either a PSR-4 base directory, or an array of PSR-4 base directories | |
1456 * associated with this namespace. | |
1457 * @param object $class_loader | |
1458 * The class loader. Normally \Composer\Autoload\ClassLoader, as included by | |
1459 * the front controller, but may also be decorated; e.g., | |
1460 * \Symfony\Component\ClassLoader\ApcClassLoader. | |
1461 */ | |
1462 protected function classLoaderAddMultiplePsr4(array $namespaces = [], $class_loader = NULL) { | |
1463 if ($class_loader === NULL) { | |
1464 $class_loader = $this->classLoader; | |
1465 } | |
1466 foreach ($namespaces as $prefix => $paths) { | |
1467 if (is_array($paths)) { | |
1468 foreach ($paths as $key => $value) { | |
1469 $paths[$key] = $this->root . '/' . $value; | |
1470 } | |
1471 } | |
1472 elseif (is_string($paths)) { | |
1473 $paths = $this->root . '/' . $paths; | |
1474 } | |
1475 $class_loader->addPsr4($prefix . '\\', $paths); | |
1476 } | |
1477 } | |
1478 | |
1479 /** | |
1480 * Validates a hostname length. | |
1481 * | |
1482 * @param string $host | |
1483 * A hostname. | |
1484 * | |
1485 * @return bool | |
1486 * TRUE if the length is appropriate, or FALSE otherwise. | |
1487 */ | |
1488 protected static function validateHostnameLength($host) { | |
1489 // Limit the length of the host name to 1000 bytes to prevent DoS attacks | |
1490 // with long host names. | |
1491 return strlen($host) <= 1000 | |
1492 // Limit the number of subdomains and port separators to prevent DoS attacks | |
1493 // in findSitePath(). | |
1494 && substr_count($host, '.') <= 100 | |
1495 && substr_count($host, ':') <= 100; | |
1496 } | |
1497 | |
1498 /** | |
1499 * Validates the hostname supplied from the HTTP request. | |
1500 * | |
1501 * @param \Symfony\Component\HttpFoundation\Request $request | |
1502 * The request object | |
1503 * | |
1504 * @return bool | |
1505 * TRUE if the hostname is valid, or FALSE otherwise. | |
1506 */ | |
1507 public static function validateHostname(Request $request) { | |
1508 // $request->getHost() can throw an UnexpectedValueException if it | |
1509 // detects a bad hostname, but it does not validate the length. | |
1510 try { | |
1511 $http_host = $request->getHost(); | |
1512 } | |
1513 catch (\UnexpectedValueException $e) { | |
1514 return FALSE; | |
1515 } | |
1516 | |
1517 if (static::validateHostnameLength($http_host) === FALSE) { | |
1518 return FALSE; | |
1519 } | |
1520 | |
1521 return TRUE; | |
1522 } | |
1523 | |
1524 /** | |
1525 * Sets up the lists of trusted HTTP Host headers. | |
1526 * | |
1527 * Since the HTTP Host header can be set by the user making the request, it | |
1528 * is possible to create an attack vectors against a site by overriding this. | |
1529 * Symfony provides a mechanism for creating a list of trusted Host values. | |
1530 * | |
1531 * Host patterns (as regular expressions) can be configured through | |
1532 * settings.php for multisite installations, sites using ServerAlias without | |
1533 * canonical redirection, or configurations where the site responds to default | |
1534 * requests. For example, | |
1535 * | |
1536 * @code | |
1537 * $settings['trusted_host_patterns'] = array( | |
1538 * '^example\.com$', | |
1539 * '^*.example\.com$', | |
1540 * ); | |
1541 * @endcode | |
1542 * | |
1543 * @param \Symfony\Component\HttpFoundation\Request $request | |
1544 * The request object. | |
1545 * @param array $host_patterns | |
1546 * The array of trusted host patterns. | |
1547 * | |
1548 * @return bool | |
1549 * TRUE if the Host header is trusted, FALSE otherwise. | |
1550 * | |
1551 * @see https://www.drupal.org/node/1992030 | |
1552 * @see \Drupal\Core\Http\TrustedHostsRequestFactory | |
1553 */ | |
1554 protected static function setupTrustedHosts(Request $request, $host_patterns) { | |
1555 $request->setTrustedHosts($host_patterns); | |
1556 | |
1557 // Get the host, which will validate the current request. | |
1558 try { | |
1559 $host = $request->getHost(); | |
1560 | |
1561 // Fake requests created through Request::create() without passing in the | |
1562 // server variables from the main request have a default host of | |
1563 // 'localhost'. If 'localhost' does not match any of the trusted host | |
1564 // patterns these fake requests would fail the host verification. Instead, | |
1565 // TrustedHostsRequestFactory makes sure to pass in the server variables | |
1566 // from the main request. | |
1567 $request_factory = new TrustedHostsRequestFactory($host); | |
1568 Request::setFactory([$request_factory, 'createRequest']); | |
1569 | |
1570 } | |
1571 catch (\UnexpectedValueException $e) { | |
1572 return FALSE; | |
1573 } | |
1574 | |
1575 return TRUE; | |
1576 } | |
1577 | |
1578 /** | |
1579 * Add service files. | |
1580 * | |
1581 * @param string[] $service_yamls | |
1582 * A list of service files. | |
1583 */ | |
1584 protected function addServiceFiles(array $service_yamls) { | |
1585 $this->serviceYamls['site'] = array_filter($service_yamls, 'file_exists'); | |
1586 } | |
1587 | |
1588 /** | |
1589 * Gets the active install profile. | |
1590 * | |
1591 * @return string|null | |
1592 * The name of the any active install profile or distribution. | |
1593 */ | |
1594 protected function getInstallProfile() { | |
1595 $config = $this->getConfigStorage()->read('core.extension'); | |
1596 if (!empty($config['profile'])) { | |
1597 $install_profile = $config['profile']; | |
1598 } | |
1599 // @todo https://www.drupal.org/node/2831065 remove the BC layer. | |
1600 else { | |
1601 // If system_update_8300() has not yet run fallback to using settings. | |
1602 $install_profile = Settings::get('install_profile'); | |
1603 } | |
1604 | |
1605 // Normalize an empty string to a NULL value. | |
1606 return empty($install_profile) ? NULL : $install_profile; | |
1607 } | |
1608 | |
1609 } |