ConfigInstaller.php 27 KB

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