diff core/lib/Drupal/Core/Config/ConfigManager.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/lib/Drupal/Core/Config/ConfigManager.php	Wed Nov 29 16:09:58 2017 +0000
@@ -0,0 +1,494 @@
+<?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) {
+    // Determine the current list of dependent configuration entities and set up
+    // initial values.
+    $dependency_manager = $this->getConfigDependencyManager();
+    $dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
+    $original_dependencies = $dependents;
+    $delete_uuids = [];
+
+    $return = [
+      'update' => [],
+      'delete' => [],
+      'unchanged' => [],
+    ];
+
+    // Create a map of UUIDs to $original_dependencies key so that we can remove
+    // fixed dependencies.
+    $uuid_map = [];
+    foreach ($original_dependencies as $key => $entity) {
+      $uuid_map[$entity->uuid()] = $key;
+    }
+
+    // Try to fix any dependencies 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
+    // dependencies.
+    while ($dependent = array_pop($dependents)) {
+      /** @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, $original_dependencies, $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 dependents. This will
+        // remove entities that are no longer dependent after the recalculation.
+        $dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
+        // Remove any entities that we've already marked for deletion.
+        $dependents = array_filter($dependents, function ($dependent) use ($delete_uuids) {
+          return !in_array($dependent->uuid(), $delete_uuids);
+        });
+        // Ensure that the dependency has actually been fixed. It is possible
+        // that the dependent has multiple dependencies that cause it to be in
+        // the dependency chain.
+        $fixed = TRUE;
+        foreach ($dependents as $key => $entity) {
+          if ($entity->uuid() == $dependent->uuid()) {
+            $fixed = FALSE;
+            unset($dependents[$key]);
+            break;
+          }
+        }
+        if ($fixed) {
+          // Remove the fixed dependency from the list of original dependencies.
+          unset($original_dependencies[$uuid_map[$dependent->uuid()]]);
+          $return['update'][] = $dependent;
+        }
+      }
+      // If the entity cannot be fixed then it has to be deleted.
+      if (!$fixed) {
+        $delete_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 lists of UUIDs to filter the original list to work out which
+    // configuration entities are unchanged.
+    $return['unchanged'] = array_filter($original_dependencies, function ($dependent) use ($delete_uuids) {
+      return !(in_array($dependent->uuid(), $delete_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;
+  }
+
+}