Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Config / ConfigInstaller.php
1 <?php
2
3 namespace Drupal\Core\Config;
4
5 use Drupal\Component\Utility\Crypt;
6 use Drupal\Component\Utility\Unicode;
7 use Drupal\Core\Config\Entity\ConfigDependencyManager;
8 use Drupal\Core\Config\Entity\ConfigEntityDependency;
9 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
10
11 class ConfigInstaller implements ConfigInstallerInterface {
12
13   /**
14    * The configuration factory.
15    *
16    * @var \Drupal\Core\Config\ConfigFactoryInterface
17    */
18   protected $configFactory;
19
20   /**
21    * The active configuration storages, keyed by collection.
22    *
23    * @var \Drupal\Core\Config\StorageInterface[]
24    */
25   protected $activeStorages;
26
27   /**
28    * The typed configuration manager.
29    *
30    * @var \Drupal\Core\Config\TypedConfigManagerInterface
31    */
32   protected $typedConfig;
33
34   /**
35    * The configuration manager.
36    *
37    * @var \Drupal\Core\Config\ConfigManagerInterface
38    */
39   protected $configManager;
40
41   /**
42    * The event dispatcher.
43    *
44    * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
45    */
46   protected $eventDispatcher;
47
48   /**
49    * The configuration storage that provides the default configuration.
50    *
51    * @var \Drupal\Core\Config\StorageInterface
52    */
53   protected $sourceStorage;
54
55   /**
56    * Is configuration being created as part of a configuration sync.
57    *
58    * @var bool
59    */
60   protected $isSyncing = FALSE;
61
62   /**
63    * The name of the currently active installation profile.
64    *
65    * @var string
66    */
67   protected $installProfile;
68
69   /**
70    * Constructs the configuration installer.
71    *
72    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
73    *   The configuration factory.
74    * @param \Drupal\Core\Config\StorageInterface $active_storage
75    *   The active configuration storage.
76    * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
77    *   The typed configuration manager.
78    * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
79    *   The configuration manager.
80    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
81    *   The event dispatcher.
82    * @param string $install_profile
83    *   The name of the currently active installation profile.
84    */
85   public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile) {
86     $this->configFactory = $config_factory;
87     $this->activeStorages[$active_storage->getCollectionName()] = $active_storage;
88     $this->typedConfig = $typed_config;
89     $this->configManager = $config_manager;
90     $this->eventDispatcher = $event_dispatcher;
91     $this->installProfile = $install_profile;
92   }
93
94   /**
95    * {@inheritdoc}
96    */
97   public function installDefaultConfig($type, $name) {
98     $extension_path = $this->drupalGetPath($type, $name);
99     // Refresh the schema cache if the extension provides configuration schema
100     // or is a theme.
101     if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY) || $type == 'theme') {
102       $this->typedConfig->clearCachedDefinitions();
103     }
104
105     $default_install_path = $this->getDefaultConfigDirectory($type, $name);
106     if (is_dir($default_install_path)) {
107       if (!$this->isSyncing()) {
108         $storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION);
109         $prefix = '';
110       }
111       else {
112         // The configuration importer sets the source storage on the config
113         // installer. The configuration importer handles all of the
114         // configuration entity imports. We only need to ensure that simple
115         // configuration is created when the extension is installed.
116         $storage = $this->getSourceStorage();
117         $prefix = $name . '.';
118       }
119
120       // Gets profile storages to search for overrides if necessary.
121       $profile_storages = $this->getProfileStorages($name);
122
123       // Gather information about all the supported collections.
124       $collection_info = $this->configManager->getConfigCollectionInfo();
125       foreach ($collection_info->getCollectionNames() as $collection) {
126         $config_to_create = $this->getConfigToCreate($storage, $collection, $prefix, $profile_storages);
127         // If we're installing a profile ensure configuration that is overriding
128         // is excluded.
129         if ($name == $this->drupalGetProfile()) {
130           $existing_configuration = $this->getActiveStorages($collection)->listAll();
131           $config_to_create = array_diff_key($config_to_create, array_flip($existing_configuration));
132         }
133         if (!empty($config_to_create)) {
134           $this->createConfiguration($collection, $config_to_create);
135         }
136       }
137     }
138
139     // During a drupal installation optional configuration is installed at the
140     // end of the installation process.
141     // @see install_install_profile()
142     if (!$this->isSyncing() && !$this->drupalInstallationAttempted()) {
143       $optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
144       if (is_dir($optional_install_path)) {
145         // Install any optional config the module provides.
146         $storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION);
147         $this->installOptionalConfig($storage, '');
148       }
149       // Install any optional configuration entities whose dependencies can now
150       // be met. This searches all the installed modules config/optional
151       // directories.
152       $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE, $this->installProfile);
153       $this->installOptionalConfig($storage, [$type => $name]);
154     }
155
156     // Reset all the static caches and list caches.
157     $this->configFactory->reset();
158   }
159
160   /**
161    * {@inheritdoc}
162    */
163   public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = []) {
164     $profile = $this->drupalGetProfile();
165     $optional_profile_config = [];
166     if (!$storage) {
167       // Search the install profile's optional configuration too.
168       $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE, $this->installProfile);
169       // The extension install storage ensures that overrides are used.
170       $profile_storage = NULL;
171     }
172     elseif (!empty($profile)) {
173       // Creates a profile storage to search for overrides.
174       $profile_install_path = $this->drupalGetPath('module', $profile) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
175       $profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
176       $optional_profile_config = $profile_storage->listAll();
177     }
178     else {
179       // Profile has not been set yet. For example during the first steps of the
180       // installer or during unit tests.
181       $profile_storage = NULL;
182     }
183
184     $enabled_extensions = $this->getEnabledExtensions();
185     $existing_config = $this->getActiveStorages()->listAll();
186
187     $list = array_unique(array_merge($storage->listAll(), $optional_profile_config));
188     $list = array_filter($list, function ($config_name) use ($existing_config) {
189       // Only list configuration that:
190       // - does not already exist
191       // - is a configuration entity (this also excludes config that has an
192       //   implicit dependency on modules that are not yet installed)
193       return !in_array($config_name, $existing_config) && $this->configManager->getEntityTypeIdByName($config_name);
194     });
195
196     $all_config = array_merge($existing_config, $list);
197     $all_config = array_combine($all_config, $all_config);
198     $config_to_create = $storage->readMultiple($list);
199     // Check to see if the corresponding override storage has any overrides or
200     // new configuration that can be installed.
201     if ($profile_storage) {
202       $config_to_create = $profile_storage->readMultiple($list) + $config_to_create;
203     }
204     // Sort $config_to_create in the order of the least dependent first.
205     $dependency_manager = new ConfigDependencyManager();
206     $dependency_manager->setData($config_to_create);
207     $config_to_create = array_merge(array_flip($dependency_manager->sortAll()), $config_to_create);
208
209     foreach ($config_to_create as $config_name => $data) {
210       // Remove configuration where its dependencies cannot be met.
211       $remove = !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config);
212       // If $dependency is defined, remove configuration that does not have a
213       // matching dependency.
214       if (!$remove && !empty($dependency)) {
215         // Create a light weight dependency object to check dependencies.
216         $config_entity = new ConfigEntityDependency($config_name, $data);
217         $remove = !$config_entity->hasDependency(key($dependency), reset($dependency));
218       }
219
220       if ($remove) {
221         // Remove from the list of configuration to create.
222         unset($config_to_create[$config_name]);
223         // Remove from the list of all configuration. This ensures that any
224         // configuration that depends on this configuration is also removed.
225         unset($all_config[$config_name]);
226       }
227     }
228     if (!empty($config_to_create)) {
229       $this->createConfiguration(StorageInterface::DEFAULT_COLLECTION, $config_to_create, TRUE);
230     }
231   }
232
233   /**
234    * Gets configuration data from the provided storage to create.
235    *
236    * @param StorageInterface $storage
237    *   The configuration storage to read configuration from.
238    * @param string $collection
239    *   The configuration collection to use.
240    * @param string $prefix
241    *   (optional) Limit to configuration starting with the provided string.
242    * @param \Drupal\Core\Config\StorageInterface[] $profile_storages
243    *   An array of storage interfaces containing profile configuration to check
244    *   for overrides.
245    *
246    * @return array
247    *   An array of configuration data read from the source storage keyed by the
248    *   configuration object name.
249    */
250   protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', array $profile_storages = []) {
251     if ($storage->getCollectionName() != $collection) {
252       $storage = $storage->createCollection($collection);
253     }
254     $data = $storage->readMultiple($storage->listAll($prefix));
255
256     // Check to see if the corresponding override storage has any overrides.
257     foreach ($profile_storages as $profile_storage) {
258       if ($profile_storage->getCollectionName() != $collection) {
259         $profile_storage = $profile_storage->createCollection($collection);
260       }
261       $data = $profile_storage->readMultiple(array_keys($data)) + $data;
262     }
263     return $data;
264   }
265
266   /**
267    * Creates configuration in a collection based on the provided list.
268    *
269    * @param string $collection
270    *   The configuration collection.
271    * @param array $config_to_create
272    *   An array of configuration data to create, keyed by name.
273    */
274   protected function createConfiguration($collection, array $config_to_create) {
275     // Order the configuration to install in the order of dependencies.
276     if ($collection == StorageInterface::DEFAULT_COLLECTION) {
277       $dependency_manager = new ConfigDependencyManager();
278       $config_names = $dependency_manager
279         ->setData($config_to_create)
280         ->sortAll();
281     }
282     else {
283       $config_names = array_keys($config_to_create);
284     }
285
286     foreach ($config_names as $name) {
287       // Allow config factory overriders to use a custom configuration object if
288       // they are responsible for the collection.
289       $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection);
290       if ($overrider) {
291         $new_config = $overrider->createConfigObject($name, $collection);
292       }
293       else {
294         $new_config = new Config($name, $this->getActiveStorages($collection), $this->eventDispatcher, $this->typedConfig);
295       }
296       if ($config_to_create[$name] !== FALSE) {
297         $new_config->setData($config_to_create[$name]);
298         // Add a hash to configuration created through the installer so it is
299         // possible to know if the configuration was created by installing an
300         // extension and to track which version of the default config was used.
301         if (!$this->isSyncing() && $collection == StorageInterface::DEFAULT_COLLECTION) {
302           $new_config->set('_core.default_config_hash', Crypt::hashBase64(serialize($config_to_create[$name])));
303         }
304       }
305       if ($collection == StorageInterface::DEFAULT_COLLECTION && $entity_type = $this->configManager->getEntityTypeIdByName($name)) {
306         // If we are syncing do not create configuration entities. Pluggable
307         // configuration entities can have dependencies on modules that are
308         // not yet enabled. This approach means that any code that expects
309         // default configuration entities to exist will be unstable after the
310         // module has been enabled and before the config entity has been
311         // imported.
312         if ($this->isSyncing()) {
313           continue;
314         }
315         /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entity_storage */
316         $entity_storage = $this->configManager
317           ->getEntityManager()
318           ->getStorage($entity_type);
319         // It is possible that secondary writes can occur during configuration
320         // creation. Updates of such configuration are allowed.
321         if ($this->getActiveStorages($collection)->exists($name)) {
322           $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
323           $entity = $entity_storage->load($id);
324           $entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
325         }
326         else {
327           $entity = $entity_storage->createFromStorageRecord($new_config->get());
328         }
329         if ($entity->isInstallable()) {
330           $entity->trustData()->save();
331         }
332       }
333       else {
334         $new_config->save(TRUE);
335       }
336     }
337   }
338
339   /**
340    * {@inheritdoc}
341    */
342   public function installCollectionDefaultConfig($collection) {
343     $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted(), $this->installProfile);
344     // Only install configuration for enabled extensions.
345     $enabled_extensions = $this->getEnabledExtensions();
346     $config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
347       $provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
348       return in_array($provider, $enabled_extensions);
349     });
350     if (!empty($config_to_install)) {
351       $this->createConfiguration($collection, $storage->readMultiple($config_to_install));
352       // Reset all the static caches and list caches.
353       $this->configFactory->reset();
354     }
355   }
356
357   /**
358    * {@inheritdoc}
359    */
360   public function setSourceStorage(StorageInterface $storage) {
361     $this->sourceStorage = $storage;
362     return $this;
363   }
364
365   /**
366    * Gets the configuration storage that provides the default configuration.
367    *
368    * @return \Drupal\Core\Config\StorageInterface|null
369    *   The configuration storage that provides the default configuration.
370    *   Returns null if the source storage has not been set.
371    */
372   public function getSourceStorage() {
373     return $this->sourceStorage;
374   }
375
376   /**
377    * Gets the configuration storage that provides the active configuration.
378    *
379    * @param string $collection
380    *   (optional) The configuration collection. Defaults to the default
381    *   collection.
382    *
383    * @return \Drupal\Core\Config\StorageInterface
384    *   The configuration storage that provides the default configuration.
385    */
386   protected function getActiveStorages($collection = StorageInterface::DEFAULT_COLLECTION) {
387     if (!isset($this->activeStorages[$collection])) {
388       $this->activeStorages[$collection] = reset($this->activeStorages)->createCollection($collection);
389     }
390     return $this->activeStorages[$collection];
391   }
392
393   /**
394    * {@inheritdoc}
395    */
396   public function setSyncing($status) {
397     if (!$status) {
398       $this->sourceStorage = NULL;
399     }
400     $this->isSyncing = $status;
401     return $this;
402   }
403
404   /**
405    * {@inheritdoc}
406    */
407   public function isSyncing() {
408     return $this->isSyncing;
409   }
410
411   /**
412    * Finds pre-existing configuration objects for the provided extension.
413    *
414    * Extensions can not be installed if configuration objects exist in the
415    * active storage with the same names. This can happen in a number of ways,
416    * commonly:
417    * - if a user has created configuration with the same name as that provided
418    *   by the extension.
419    * - if the extension provides default configuration that does not depend on
420    *   it and the extension has been uninstalled and is about to the
421    *   reinstalled.
422    *
423    * @return array
424    *   Array of configuration object names that already exist keyed by
425    *   collection.
426    */
427   protected function findPreExistingConfiguration(StorageInterface $storage) {
428     $existing_configuration = [];
429     // Gather information about all the supported collections.
430     $collection_info = $this->configManager->getConfigCollectionInfo();
431
432     foreach ($collection_info->getCollectionNames() as $collection) {
433       $config_to_create = array_keys($this->getConfigToCreate($storage, $collection));
434       $active_storage = $this->getActiveStorages($collection);
435       foreach ($config_to_create as $config_name) {
436         if ($active_storage->exists($config_name)) {
437           $existing_configuration[$collection][] = $config_name;
438         }
439       }
440     }
441     return $existing_configuration;
442   }
443
444   /**
445    * {@inheritdoc}
446    */
447   public function checkConfigurationToInstall($type, $name) {
448     if ($this->isSyncing()) {
449       // Configuration is assumed to already be checked by the config importer
450       // validation events.
451       return;
452     }
453     $config_install_path = $this->getDefaultConfigDirectory($type, $name);
454     if (!is_dir($config_install_path)) {
455       return;
456     }
457
458     $storage = new FileStorage($config_install_path, StorageInterface::DEFAULT_COLLECTION);
459
460     $enabled_extensions = $this->getEnabledExtensions();
461     // Add the extension that will be enabled to the list of enabled extensions.
462     $enabled_extensions[] = $name;
463     // Gets profile storages to search for overrides if necessary.
464     $profile_storages = $this->getProfileStorages($name);
465
466     // Check the dependencies of configuration provided by the module.
467     list($invalid_default_config, $missing_dependencies) = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions, $profile_storages);
468     if (!empty($invalid_default_config)) {
469       throw UnmetDependenciesException::create($name, array_unique($missing_dependencies, SORT_REGULAR));
470     }
471
472     // Install profiles can not have config clashes. Configuration that
473     // has the same name as a module's configuration will be used instead.
474     if ($name != $this->drupalGetProfile()) {
475       // Throw an exception if the module being installed contains configuration
476       // that already exists. Additionally, can not continue installing more
477       // modules because those may depend on the current module being installed.
478       $existing_configuration = $this->findPreExistingConfiguration($storage);
479       if (!empty($existing_configuration)) {
480         throw PreExistingConfigException::create($name, $existing_configuration);
481       }
482     }
483   }
484
485   /**
486    * Finds default configuration with unmet dependencies.
487    *
488    * @param \Drupal\Core\Config\StorageInterface $storage
489    *   The storage containing the default configuration.
490    * @param array $enabled_extensions
491    *   A list of all the currently enabled modules and themes.
492    * @param \Drupal\Core\Config\StorageInterface[] $profile_storages
493    *   An array of storage interfaces containing profile configuration to check
494    *   for overrides.
495    *
496    * @return array
497    *   An array containing:
498    *     - A list of configuration that has unmet dependencies.
499    *     - An array that will be filled with the missing dependency names, keyed
500    *       by the dependents' names.
501    */
502   protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, array $profile_storages = []) {
503     $missing_dependencies = [];
504     $config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION, '', $profile_storages);
505     $all_config = array_merge($this->configFactory->listAll(), array_keys($config_to_create));
506     foreach ($config_to_create as $config_name => $config) {
507       if ($missing = $this->getMissingDependencies($config_name, $config, $enabled_extensions, $all_config)) {
508         $missing_dependencies[$config_name] = $missing;
509       }
510     }
511     return [
512       array_intersect_key($config_to_create, $missing_dependencies),
513       $missing_dependencies,
514     ];
515   }
516
517   /**
518    * Validates an array of config data that contains dependency information.
519    *
520    * @param string $config_name
521    *   The name of the configuration object that is being validated.
522    * @param array $data
523    *   Configuration data.
524    * @param array $enabled_extensions
525    *   A list of all the currently enabled modules and themes.
526    * @param array $all_config
527    *   A list of all the active configuration names.
528    *
529    * @return bool
530    *   TRUE if all dependencies are present, FALSE otherwise.
531    */
532   protected function validateDependencies($config_name, array $data, array $enabled_extensions, array $all_config) {
533     if (!isset($data['dependencies'])) {
534       // Simple config or a config entity without dependencies.
535       list($provider) = explode('.', $config_name, 2);
536       return in_array($provider, $enabled_extensions, TRUE);
537     }
538
539     $missing = $this->getMissingDependencies($config_name, $data, $enabled_extensions, $all_config);
540     return empty($missing);
541   }
542
543   /**
544    * Returns an array of missing dependencies for a config object.
545    *
546    * @param string $config_name
547    *   The name of the configuration object that is being validated.
548    * @param array $data
549    *   Configuration data.
550    * @param array $enabled_extensions
551    *   A list of all the currently enabled modules and themes.
552    * @param array $all_config
553    *   A list of all the active configuration names.
554    *
555    * @return array
556    *   A list of missing config dependencies.
557    */
558   protected function getMissingDependencies($config_name, array $data, array $enabled_extensions, array $all_config) {
559     $missing = [];
560     if (isset($data['dependencies'])) {
561       list($provider) = explode('.', $config_name, 2);
562       $all_dependencies = $data['dependencies'];
563
564       // Ensure enforced dependencies are included.
565       if (isset($all_dependencies['enforced'])) {
566         $all_dependencies = array_merge($all_dependencies, $data['dependencies']['enforced']);
567         unset($all_dependencies['enforced']);
568       }
569       // Ensure the configuration entity type provider is in the list of
570       // dependencies.
571       if (!isset($all_dependencies['module']) || !in_array($provider, $all_dependencies['module'])) {
572         $all_dependencies['module'][] = $provider;
573       }
574
575       foreach ($all_dependencies as $type => $dependencies) {
576         $list_to_check = [];
577         switch ($type) {
578           case 'module':
579           case 'theme':
580             $list_to_check = $enabled_extensions;
581             break;
582           case 'config':
583             $list_to_check = $all_config;
584             break;
585         }
586         if (!empty($list_to_check)) {
587           $missing = array_merge($missing, array_diff($dependencies, $list_to_check));
588         }
589       }
590     }
591
592     return $missing;
593   }
594
595   /**
596    * Gets the list of enabled extensions including both modules and themes.
597    *
598    * @return array
599    *   A list of enabled extensions which includes both modules and themes.
600    */
601   protected function getEnabledExtensions() {
602     // Read enabled extensions directly from configuration to avoid circular
603     // dependencies on ModuleHandler and ThemeHandler.
604     $extension_config = $this->configFactory->get('core.extension');
605     $enabled_extensions = (array) $extension_config->get('module');
606     $enabled_extensions += (array) $extension_config->get('theme');
607     // Core can provide configuration.
608     $enabled_extensions['core'] = 'core';
609     return array_keys($enabled_extensions);
610   }
611
612   /**
613    * Gets the profile storage to use to check for profile overrides.
614    *
615    * The install profile can override module configuration during a module
616    * install. Both the install and optional directories are checked for matching
617    * configuration. This allows profiles to override default configuration for
618    * modules they do not depend on.
619    *
620    * @param string $installing_name
621    *   (optional) The name of the extension currently being installed.
622    *
623    * @return \Drupal\Core\Config\StorageInterface[]|null
624    *   Storages to access configuration from the installation profile. If we're
625    *   installing the profile itself, then it will return an empty array as the
626    *   profile storage should not be used.
627    */
628   protected function getProfileStorages($installing_name = '') {
629     $profile = $this->drupalGetProfile();
630     $profile_storages = [];
631     if ($profile && $profile != $installing_name) {
632       $profile_path = $this->drupalGetPath('module', $profile);
633       foreach ([InstallStorage::CONFIG_INSTALL_DIRECTORY, InstallStorage::CONFIG_OPTIONAL_DIRECTORY] as $directory) {
634         if (is_dir($profile_path . '/' . $directory)) {
635           $profile_storages[] = new FileStorage($profile_path . '/' . $directory, StorageInterface::DEFAULT_COLLECTION);
636         }
637       }
638     }
639     return $profile_storages;
640   }
641
642   /**
643    * Gets an extension's default configuration directory.
644    *
645    * @param string $type
646    *   Type of extension to install.
647    * @param string $name
648    *   Name of extension to install.
649    *
650    * @return string
651    *   The extension's default configuration directory.
652    */
653   protected function getDefaultConfigDirectory($type, $name) {
654     return $this->drupalGetPath($type, $name) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
655   }
656
657   /**
658    * Wrapper for drupal_get_path().
659    *
660    * @param $type
661    *   The type of the item; one of 'core', 'profile', 'module', 'theme', or
662    *   'theme_engine'.
663    * @param $name
664    *   The name of the item for which the path is requested. Ignored for
665    *   $type 'core'.
666    *
667    * @return string
668    *   The path to the requested item or an empty string if the item is not
669    *   found.
670    */
671   protected function drupalGetPath($type, $name) {
672     return drupal_get_path($type, $name);
673   }
674
675   /**
676    * Gets the install profile from settings.
677    *
678    * @return string|null
679    *   The name of the installation profile or NULL if no installation profile
680    *   is currently active. This is the case for example during the first steps
681    *   of the installer or during unit tests.
682    */
683   protected function drupalGetProfile() {
684     return $this->installProfile;
685   }
686
687   /**
688    * Wrapper for drupal_installation_attempted().
689    *
690    * @return bool
691    *   TRUE if a Drupal installation is currently being attempted.
692    */
693   protected function drupalInstallationAttempted() {
694     return drupal_installation_attempted();
695   }
696
697 }