ConfigInstaller.php 28 KB

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