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