123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- <?php
- namespace Drupal\Core\Config;
- use Drupal\Component\Diff\Diff;
- use Drupal\Core\Config\Entity\ConfigDependencyManager;
- use Drupal\Core\Config\Entity\ConfigEntityInterface;
- use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
- use Drupal\Core\Entity\EntityManagerInterface;
- use Drupal\Core\Entity\EntityTypeInterface;
- use Drupal\Core\Serialization\Yaml;
- use Drupal\Core\StringTranslation\StringTranslationTrait;
- use Drupal\Core\StringTranslation\TranslationInterface;
- use Symfony\Component\EventDispatcher\EventDispatcherInterface;
- /**
- * The ConfigManager provides helper functions for the configuration system.
- */
- class ConfigManager implements ConfigManagerInterface {
- use StringTranslationTrait;
- /**
- * The entity manager.
- *
- * @var \Drupal\Core\Entity\EntityManagerInterface
- */
- protected $entityManager;
- /**
- * The configuration factory.
- *
- * @var \Drupal\Core\Config\ConfigFactoryInterface
- */
- protected $configFactory;
- /**
- * The typed config manager.
- *
- * @var \Drupal\Core\Config\TypedConfigManagerInterface
- */
- protected $typedConfigManager;
- /**
- * The active configuration storage.
- *
- * @var \Drupal\Core\Config\StorageInterface
- */
- protected $activeStorage;
- /**
- * The event dispatcher.
- *
- * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
- */
- protected $eventDispatcher;
- /**
- * The configuration collection info.
- *
- * @var \Drupal\Core\Config\ConfigCollectionInfo
- */
- protected $configCollectionInfo;
- /**
- * The configuration storages keyed by collection name.
- *
- * @var \Drupal\Core\Config\StorageInterface[]
- */
- protected $storages;
- /**
- * Creates ConfigManager objects.
- *
- * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
- * The entity manager.
- * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
- * The configuration factory.
- * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
- * The typed config manager.
- * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
- * The string translation service.
- * @param \Drupal\Core\Config\StorageInterface $active_storage
- * The active configuration storage.
- * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
- * The event dispatcher.
- */
- public function __construct(EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config_manager, TranslationInterface $string_translation, StorageInterface $active_storage, EventDispatcherInterface $event_dispatcher) {
- $this->entityManager = $entity_manager;
- $this->configFactory = $config_factory;
- $this->typedConfigManager = $typed_config_manager;
- $this->stringTranslation = $string_translation;
- $this->activeStorage = $active_storage;
- $this->eventDispatcher = $event_dispatcher;
- }
- /**
- * {@inheritdoc}
- */
- public function getEntityTypeIdByName($name) {
- $entities = array_filter($this->entityManager->getDefinitions(), function (EntityTypeInterface $entity_type) use ($name) {
- return ($entity_type instanceof ConfigEntityTypeInterface && $config_prefix = $entity_type->getConfigPrefix()) && strpos($name, $config_prefix . '.') === 0;
- });
- return key($entities);
- }
- /**
- * {@inheritdoc}
- */
- public function loadConfigEntityByName($name) {
- $entity_type_id = $this->getEntityTypeIdByName($name);
- if ($entity_type_id) {
- $entity_type = $this->entityManager->getDefinition($entity_type_id);
- $id = substr($name, strlen($entity_type->getConfigPrefix()) + 1);
- return $this->entityManager->getStorage($entity_type_id)->load($id);
- }
- return NULL;
- }
- /**
- * {@inheritdoc}
- */
- public function getEntityManager() {
- return $this->entityManager;
- }
- /**
- * {@inheritdoc}
- */
- public function getConfigFactory() {
- return $this->configFactory;
- }
- /**
- * {@inheritdoc}
- */
- public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
- if ($collection != StorageInterface::DEFAULT_COLLECTION) {
- $source_storage = $source_storage->createCollection($collection);
- $target_storage = $target_storage->createCollection($collection);
- }
- if (!isset($target_name)) {
- $target_name = $source_name;
- }
- // The output should show configuration object differences formatted as YAML.
- // But the configuration is not necessarily stored in files. Therefore, they
- // need to be read and parsed, and lastly, dumped into YAML strings.
- $source_data = explode("\n", Yaml::encode($source_storage->read($source_name)));
- $target_data = explode("\n", Yaml::encode($target_storage->read($target_name)));
- // Check for new or removed files.
- if ($source_data === ['false']) {
- // Added file.
- // Cast the result of t() to a string, as the diff engine doesn't know
- // about objects.
- $source_data = [(string) $this->t('File added')];
- }
- if ($target_data === ['false']) {
- // Deleted file.
- // Cast the result of t() to a string, as the diff engine doesn't know
- // about objects.
- $target_data = [(string) $this->t('File removed')];
- }
- return new Diff($source_data, $target_data);
- }
- /**
- * {@inheritdoc}
- */
- public function createSnapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
- // Empty the snapshot of all configuration.
- $snapshot_storage->deleteAll();
- foreach ($snapshot_storage->getAllCollectionNames() as $collection) {
- $snapshot_collection = $snapshot_storage->createCollection($collection);
- $snapshot_collection->deleteAll();
- }
- foreach ($source_storage->listAll() as $name) {
- $snapshot_storage->write($name, $source_storage->read($name));
- }
- // Copy collections as well.
- foreach ($source_storage->getAllCollectionNames() as $collection) {
- $source_collection = $source_storage->createCollection($collection);
- $snapshot_collection = $snapshot_storage->createCollection($collection);
- foreach ($source_collection->listAll() as $name) {
- $snapshot_collection->write($name, $source_collection->read($name));
- }
- }
- }
- /**
- * {@inheritdoc}
- */
- public function uninstall($type, $name) {
- $entities = $this->getConfigEntitiesToChangeOnDependencyRemoval($type, [$name], FALSE);
- // Fix all dependent configuration entities.
- /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
- foreach ($entities['update'] as $entity) {
- $entity->save();
- }
- // Remove all dependent configuration entities.
- foreach ($entities['delete'] as $entity) {
- $entity->setUninstalling(TRUE);
- $entity->delete();
- }
- $config_names = $this->configFactory->listAll($name . '.');
- foreach ($config_names as $config_name) {
- $this->configFactory->getEditable($config_name)->delete();
- }
- // Remove any matching configuration from collections.
- foreach ($this->activeStorage->getAllCollectionNames() as $collection) {
- $collection_storage = $this->activeStorage->createCollection($collection);
- $collection_storage->deleteAll($name . '.');
- }
- $schema_dir = drupal_get_path($type, $name) . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY;
- if (is_dir($schema_dir)) {
- // Refresh the schema cache if uninstalling an extension that provides
- // configuration schema.
- $this->typedConfigManager->clearCachedDefinitions();
- }
- }
- /**
- * {@inheritdoc}
- */
- public function getConfigDependencyManager() {
- $dependency_manager = new ConfigDependencyManager();
- // Read all configuration using the factory. This ensures that multiple
- // deletes during the same request benefit from the static cache. Using the
- // factory also ensures configuration entity dependency discovery has no
- // dependencies on the config entity classes. Assume data with UUID is a
- // config entity. Only configuration entities can be depended on so we can
- // ignore everything else.
- $data = array_map(function ($config) {
- $data = $config->get();
- if (isset($data['uuid'])) {
- return $data;
- }
- return FALSE;
- }, $this->configFactory->loadMultiple($this->activeStorage->listAll()));
- $dependency_manager->setData(array_filter($data));
- return $dependency_manager;
- }
- /**
- * {@inheritdoc}
- */
- public function findConfigEntityDependents($type, array $names, ConfigDependencyManager $dependency_manager = NULL) {
- if (!$dependency_manager) {
- $dependency_manager = $this->getConfigDependencyManager();
- }
- $dependencies = [];
- foreach ($names as $name) {
- $dependencies = array_merge($dependencies, $dependency_manager->getDependentEntities($type, $name));
- }
- return $dependencies;
- }
- /**
- * {@inheritdoc}
- */
- public function findConfigEntityDependentsAsEntities($type, array $names, ConfigDependencyManager $dependency_manager = NULL) {
- $dependencies = $this->findConfigEntityDependents($type, $names, $dependency_manager);
- $entities = [];
- $definitions = $this->entityManager->getDefinitions();
- foreach ($dependencies as $config_name => $dependency) {
- // Group by entity type to efficient load entities using
- // \Drupal\Core\Entity\EntityStorageInterface::loadMultiple().
- $entity_type_id = $this->getEntityTypeIdByName($config_name);
- // It is possible that a non-configuration entity will be returned if a
- // simple configuration object has a UUID key. This would occur if the
- // dependents of the system module are calculated since system.site has
- // a UUID key.
- if ($entity_type_id) {
- $id = substr($config_name, strlen($definitions[$entity_type_id]->getConfigPrefix()) + 1);
- $entities[$entity_type_id][] = $id;
- }
- }
- $entities_to_return = [];
- foreach ($entities as $entity_type_id => $entities_to_load) {
- $storage = $this->entityManager->getStorage($entity_type_id);
- // Remove the keys since there are potential ID clashes from different
- // configuration entity types.
- $entities_to_return = array_merge($entities_to_return, array_values($storage->loadMultiple($entities_to_load)));
- }
- return $entities_to_return;
- }
- /**
- * {@inheritdoc}
- */
- public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE) {
- $dependency_manager = $this->getConfigDependencyManager();
- // Store the list of dependents in three separate variables. This allows us
- // to determine how the dependency graph changes as entities are fixed by
- // calling the onDependencyRemoval() method.
- // The list of original dependents on $names. This list never changes.
- $original_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
- // The current list of dependents on $names. This list is recalculated when
- // calling an entity's onDependencyRemoval() method results in the entity
- // changing. This list is passed to each entity's onDependencyRemoval()
- // method as the list of affected entities.
- $current_dependents = $original_dependents;
- // The list of dependents to process. This list changes as entities are
- // processed and are either fixed or deleted.
- $dependents_to_process = $original_dependents;
- // Initialize other variables.
- $affected_uuids = [];
- $return = [
- 'update' => [],
- 'delete' => [],
- 'unchanged' => [],
- ];
- // Try to fix the dependents and find out what will happen to the dependency
- // graph. Entities are processed in the order of most dependent first. For
- // example, this ensures that Menu UI third party dependencies on node types
- // are fixed before processing the node type's other dependents.
- while ($dependent = array_pop($dependents_to_process)) {
- /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent */
- if ($dry_run) {
- // Clone the entity so any changes do not change any static caches.
- $dependent = clone $dependent;
- }
- $fixed = FALSE;
- if ($this->callOnDependencyRemoval($dependent, $current_dependents, $type, $names)) {
- // Recalculate dependencies and update the dependency graph data.
- $dependent->calculateDependencies();
- $dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->getDependencies());
- // Based on the updated data rebuild the list of current dependents.
- // This will remove entities that are no longer dependent after the
- // recalculation.
- $current_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
- // Rebuild the list of entities that we need to process using the new
- // list of current dependents and removing any entities that we've
- // already processed.
- $dependents_to_process = array_filter($current_dependents, function ($current_dependent) use ($affected_uuids) {
- return !in_array($current_dependent->uuid(), $affected_uuids);
- });
- // Ensure that the dependent has actually been fixed. It is possible
- // that other dependencies cause it to still be in the list.
- $fixed = TRUE;
- foreach ($dependents_to_process as $key => $entity) {
- if ($entity->uuid() == $dependent->uuid()) {
- $fixed = FALSE;
- unset($dependents_to_process[$key]);
- break;
- }
- }
- if ($fixed) {
- $affected_uuids[] = $dependent->uuid();
- $return['update'][] = $dependent;
- }
- }
- // If the entity cannot be fixed then it has to be deleted.
- if (!$fixed) {
- $affected_uuids[] = $dependent->uuid();
- // Deletes should occur in the order of the least dependent first. For
- // example, this ensures that fields are removed before field storages.
- array_unshift($return['delete'], $dependent);
- }
- }
- // Use the list of affected UUIDs to filter the original list to work out
- // which configuration entities are unchanged.
- $return['unchanged'] = array_filter($original_dependents, function ($dependent) use ($affected_uuids) {
- return !(in_array($dependent->uuid(), $affected_uuids));
- });
- return $return;
- }
- /**
- * {@inheritdoc}
- */
- public function getConfigCollectionInfo() {
- if (!isset($this->configCollectionInfo)) {
- $this->configCollectionInfo = new ConfigCollectionInfo();
- $this->eventDispatcher->dispatch(ConfigEvents::COLLECTION_INFO, $this->configCollectionInfo);
- }
- return $this->configCollectionInfo;
- }
- /**
- * Calls an entity's onDependencyRemoval() method.
- *
- * A helper method to call onDependencyRemoval() with the correct list of
- * affected entities. This list should only contain dependencies on the
- * entity. Configuration and content entity dependencies will be converted
- * into entity objects.
- *
- * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
- * The entity to call onDependencyRemoval() on.
- * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependent_entities
- * The list of dependent configuration entities.
- * @param string $type
- * The type of dependency being checked. Either 'module', 'theme', 'config'
- * or 'content'.
- * @param array $names
- * The specific names to check. If $type equals 'module' or 'theme' then it
- * should be a list of module names or theme names. In the case of 'config'
- * or 'content' it should be a list of configuration dependency names.
- *
- * @return bool
- * TRUE if the entity has changed as a result of calling the
- * onDependencyRemoval() method, FALSE if not.
- */
- protected function callOnDependencyRemoval(ConfigEntityInterface $entity, array $dependent_entities, $type, array $names) {
- $entity_dependencies = $entity->getDependencies();
- if (empty($entity_dependencies)) {
- // No dependent entities nothing to do.
- return FALSE;
- }
- $affected_dependencies = [
- 'config' => [],
- 'content' => [],
- 'module' => [],
- 'theme' => [],
- ];
- // Work out if any of the entity's dependencies are going to be affected.
- if (isset($entity_dependencies[$type])) {
- // Work out which dependencies the entity has in common with the provided
- // $type and $names.
- $affected_dependencies[$type] = array_intersect($entity_dependencies[$type], $names);
- // If the dependencies are entities we need to convert them into objects.
- if ($type == 'config' || $type == 'content') {
- $affected_dependencies[$type] = array_map(function ($name) use ($type) {
- if ($type == 'config') {
- return $this->loadConfigEntityByName($name);
- }
- else {
- // Ignore the bundle.
- list($entity_type_id,, $uuid) = explode(':', $name);
- return $this->entityManager->loadEntityByConfigTarget($entity_type_id, $uuid);
- }
- }, $affected_dependencies[$type]);
- }
- }
- // Merge any other configuration entities into the list of affected
- // dependencies if necessary.
- if (isset($entity_dependencies['config'])) {
- foreach ($dependent_entities as $dependent_entity) {
- if (in_array($dependent_entity->getConfigDependencyName(), $entity_dependencies['config'])) {
- $affected_dependencies['config'][] = $dependent_entity;
- }
- }
- }
- // Key the entity arrays by config dependency name to make searching easy.
- foreach (['config', 'content'] as $dependency_type) {
- $affected_dependencies[$dependency_type] = array_combine(
- array_map(function ($entity) {
- return $entity->getConfigDependencyName();
- }, $affected_dependencies[$dependency_type]),
- $affected_dependencies[$dependency_type]
- );
- }
- // Inform the entity.
- return $entity->onDependencyRemoval($affected_dependencies);
- }
- /**
- * {@inheritdoc}
- */
- public function findMissingContentDependencies() {
- $content_dependencies = [];
- $missing_dependencies = [];
- foreach ($this->activeStorage->readMultiple($this->activeStorage->listAll()) as $config_data) {
- if (isset($config_data['dependencies']['content'])) {
- $content_dependencies = array_merge($content_dependencies, $config_data['dependencies']['content']);
- }
- if (isset($config_data['dependencies']['enforced']['content'])) {
- $content_dependencies = array_merge($content_dependencies, $config_data['dependencies']['enforced']['content']);
- }
- }
- foreach (array_unique($content_dependencies) as $content_dependency) {
- // Format of the dependency is entity_type:bundle:uuid.
- list($entity_type, $bundle, $uuid) = explode(':', $content_dependency, 3);
- if (!$this->entityManager->loadEntityByUuid($entity_type, $uuid)) {
- $missing_dependencies[$uuid] = [
- 'entity_type' => $entity_type,
- 'bundle' => $bundle,
- 'uuid' => $uuid,
- ];
- }
- }
- return $missing_dependencies;
- }
- }
|