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