annotate core/lib/Drupal/Core/Config/ConfigInstaller.php @ 19:fa3358dc1485 tip

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