ConfigInstaller.php 29 KB

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