Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\BootstrapConfigStorageFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* Defines a factory for retrieving the config storage used pre-kernel.
|
||||
*/
|
||||
class BootstrapConfigStorageFactory {
|
||||
|
||||
/**
|
||||
* Returns a configuration storage implementation.
|
||||
*
|
||||
* @param $class_loader
|
||||
* The class loader. Normally Composer's ClassLoader, as included by the
|
||||
* front controller, but may also be decorated; e.g.,
|
||||
* \Symfony\Component\ClassLoader\ApcClassLoader.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* A configuration storage implementation.
|
||||
*/
|
||||
public static function get($class_loader = NULL) {
|
||||
$bootstrap_config_storage = Settings::get('bootstrap_config_storage');
|
||||
$storage_backend = FALSE;
|
||||
if (!empty($bootstrap_config_storage) && is_callable($bootstrap_config_storage)) {
|
||||
$storage_backend = call_user_func($bootstrap_config_storage, $class_loader);
|
||||
}
|
||||
// Fallback to the DatabaseStorage.
|
||||
return $storage_backend ?: self::getDatabaseStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Database configuration storage implementation.
|
||||
*
|
||||
* @return \Drupal\Core\Config\DatabaseStorage
|
||||
*/
|
||||
public static function getDatabaseStorage() {
|
||||
return new DatabaseStorage(Database::getConnection(), 'config');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a File-based configuration storage implementation.
|
||||
*
|
||||
* @return \Drupal\Core\Config\FileStorage
|
||||
*/
|
||||
public static function getFileStorage() {
|
||||
return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
|
||||
}
|
||||
}
|
308
core/lib/Drupal/Core/Config/CachedStorage.php
Normal file
308
core/lib/Drupal/Core/Config/CachedStorage.php
Normal file
|
@ -0,0 +1,308 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\CachedStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* Defines the cached storage.
|
||||
*
|
||||
* The class gets another storage and a cache backend injected. It reads from
|
||||
* the cache and delegates the read to the storage on a cache miss. It also
|
||||
* handles cache invalidation.
|
||||
*/
|
||||
class CachedStorage implements StorageInterface, StorageCacheInterface {
|
||||
use DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The configuration storage to be cached.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The instantiated Cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* List of listAll() prefixes with their results.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $findByPrefixCache = array();
|
||||
|
||||
/**
|
||||
* Constructs a new CachedStorage.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||
* A configuration storage to be cached.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* A cache backend used to store configuration.
|
||||
*/
|
||||
public function __construct(StorageInterface $storage, CacheBackendInterface $cache) {
|
||||
$this->storage = $storage;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::exists().
|
||||
*/
|
||||
public function exists($name) {
|
||||
// The cache would read in the entire data (instead of only checking whether
|
||||
// any data exists), and on a potential cache miss, an additional storage
|
||||
// lookup would have to happen, so check the storage directly.
|
||||
return $this->storage->exists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::read().
|
||||
*/
|
||||
public function read($name) {
|
||||
$cache_key = $this->getCacheKey($name);
|
||||
if ($cache = $this->cache->get($cache_key)) {
|
||||
// The cache contains either the cached configuration data or FALSE
|
||||
// if the configuration file does not exist.
|
||||
return $cache->data;
|
||||
}
|
||||
// Read from the storage on a cache miss and cache the data. Also cache
|
||||
// information about missing configuration objects.
|
||||
$data = $this->storage->read($name);
|
||||
$this->cache->set($cache_key, $data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function readMultiple(array $names) {
|
||||
$data_to_return = array();
|
||||
|
||||
$cache_keys_map = $this->getCacheKeys($names);
|
||||
$cache_keys = array_values($cache_keys_map);
|
||||
$cached_list = $this->cache->getMultiple($cache_keys);
|
||||
|
||||
if (!empty($cache_keys)) {
|
||||
// $cache_keys_map contains the full $name => $cache_key map, while
|
||||
// $cache_keys contains just the $cache_key values that weren't found in
|
||||
// the cache.
|
||||
// @see \Drupal\Core\Cache\CacheBackendInterface::getMultiple()
|
||||
$names_to_get = array_keys(array_intersect($cache_keys_map, $cache_keys));
|
||||
$list = $this->storage->readMultiple($names_to_get);
|
||||
// Cache configuration objects that were loaded from the storage, cache
|
||||
// missing configuration objects as an explicit FALSE.
|
||||
$items = array();
|
||||
foreach ($names_to_get as $name) {
|
||||
$data = isset($list[$name]) ? $list[$name] : FALSE;
|
||||
$data_to_return[$name] = $data;
|
||||
$items[$cache_keys_map[$name]] = array('data' => $data);
|
||||
}
|
||||
|
||||
$this->cache->setMultiple($items);
|
||||
}
|
||||
|
||||
// Add the configuration objects from the cache to the list.
|
||||
$cache_keys_inverse_map = array_flip($cache_keys_map);
|
||||
foreach ($cached_list as $cache_key => $cache) {
|
||||
$name = $cache_keys_inverse_map[$cache_key];
|
||||
$data_to_return[$name] = $cache->data;
|
||||
}
|
||||
|
||||
// Ensure that only existing configuration objects are returned, filter out
|
||||
// cached information about missing objects.
|
||||
return array_filter($data_to_return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::write().
|
||||
*/
|
||||
public function write($name, array $data) {
|
||||
if ($this->storage->write($name, $data)) {
|
||||
// While not all written data is read back, setting the cache instead of
|
||||
// just deleting it avoids cache rebuild stampedes.
|
||||
$this->cache->set($this->getCacheKey($name), $data);
|
||||
$this->findByPrefixCache = array();
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::delete().
|
||||
*/
|
||||
public function delete($name) {
|
||||
// If the cache was the first to be deleted, another process might start
|
||||
// rebuilding the cache before the storage is gone.
|
||||
if ($this->storage->delete($name)) {
|
||||
$this->cache->delete($this->getCacheKey($name));
|
||||
$this->findByPrefixCache = array();
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::rename().
|
||||
*/
|
||||
public function rename($name, $new_name) {
|
||||
// If the cache was the first to be deleted, another process might start
|
||||
// rebuilding the cache before the storage is renamed.
|
||||
if ($this->storage->rename($name, $new_name)) {
|
||||
$this->cache->delete($this->getCacheKey($name));
|
||||
$this->cache->delete($this->getCacheKey($new_name));
|
||||
$this->findByPrefixCache = array();
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::encode().
|
||||
*/
|
||||
public function encode($data) {
|
||||
return $this->storage->encode($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::decode().
|
||||
*/
|
||||
public function decode($raw) {
|
||||
return $this->storage->decode($raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
// Do not cache when a prefix is not provided.
|
||||
if ($prefix) {
|
||||
return $this->findByPrefix($prefix);
|
||||
}
|
||||
return $this->storage->listAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds configuration object names starting with a given prefix.
|
||||
*
|
||||
* Given the following configuration objects:
|
||||
* - node.type.article
|
||||
* - node.type.page
|
||||
*
|
||||
* Passing the prefix 'node.type.' will return an array containing the above
|
||||
* names.
|
||||
*
|
||||
* @param string $prefix
|
||||
* The prefix to search for
|
||||
*
|
||||
* @return array
|
||||
* An array containing matching configuration object names.
|
||||
*/
|
||||
protected function findByPrefix($prefix) {
|
||||
$cache_key = $this->getCacheKey($prefix);
|
||||
if (!isset($this->findByPrefixCache[$cache_key])) {
|
||||
$this->findByPrefixCache[$cache_key] = $this->storage->listAll($prefix);
|
||||
}
|
||||
return $this->findByPrefixCache[$cache_key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::deleteAll().
|
||||
*/
|
||||
public function deleteAll($prefix = '') {
|
||||
// If the cache was the first to be deleted, another process might start
|
||||
// rebuilding the cache before the storage is renamed.
|
||||
$names = $this->storage->listAll($prefix);
|
||||
if ($this->storage->deleteAll($prefix)) {
|
||||
$this->cache->deleteMultiple($this->getCacheKeys($names));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the static list cache.
|
||||
*/
|
||||
public function resetListCache() {
|
||||
$this->findByPrefixCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->storage->createCollection($collection),
|
||||
$this->cache
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
return $this->storage->getAllCollectionNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return $this->storage->getCollectionName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache key for a configuration name using the collection.
|
||||
*
|
||||
* @param string $name
|
||||
* The configuration name.
|
||||
*
|
||||
* @return string
|
||||
* The cache key for the configuration name.
|
||||
*/
|
||||
protected function getCacheKey($name) {
|
||||
return $this->getCollectionPrefix() . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache key map for an array of configuration names.
|
||||
*
|
||||
* @param array $names
|
||||
* The configuration names.
|
||||
*
|
||||
* @return array
|
||||
* An array of cache keys keyed by configuration names.
|
||||
*/
|
||||
protected function getCacheKeys(array $names) {
|
||||
$prefix = $this->getCollectionPrefix();
|
||||
$cache_keys = array_map(function($name) use ($prefix) {
|
||||
return $prefix . $name;
|
||||
}, $names);
|
||||
|
||||
return array_combine($names, $cache_keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache ID prefix to use for the collection.
|
||||
*
|
||||
* @return string
|
||||
* The cache ID prefix.
|
||||
*/
|
||||
protected function getCollectionPrefix() {
|
||||
$collection = $this->storage->getCollectionName();
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
return '';
|
||||
}
|
||||
return $collection . ':';
|
||||
}
|
||||
|
||||
}
|
311
core/lib/Drupal/Core/Config/Config.php
Normal file
311
core/lib/Drupal/Core/Config/Config.php
Normal file
|
@ -0,0 +1,311 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Config.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Defines the default configuration object.
|
||||
*
|
||||
* Encapsulates all capabilities needed for configuration handling for a
|
||||
* specific configuration object, including support for runtime overrides. The
|
||||
* overrides are handled on top of the stored configuration so they are not
|
||||
* saved back to storage.
|
||||
*
|
||||
* @ingroup config_api
|
||||
*/
|
||||
class Config extends StorableConfigBase {
|
||||
|
||||
/**
|
||||
* An event dispatcher instance to use for configuration events.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* The current runtime data.
|
||||
*
|
||||
* The configuration data from storage merged with module and settings
|
||||
* overrides.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $overriddenData;
|
||||
|
||||
/**
|
||||
* The current module overrides.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $moduleOverrides;
|
||||
|
||||
/**
|
||||
* The current settings overrides.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settingsOverrides;
|
||||
|
||||
/**
|
||||
* Constructs a configuration object.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object being constructed.
|
||||
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||
* A storage object to use for reading and writing the
|
||||
* configuration data.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* An event dispatcher instance to use for configuration events.
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
||||
* The typed configuration manager service.
|
||||
*/
|
||||
public function __construct($name, StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManagerInterface $typed_config) {
|
||||
$this->name = $name;
|
||||
$this->storage = $storage;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->typedConfigManager = $typed_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initWithData(array $data) {
|
||||
parent::initWithData($data);
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key = '') {
|
||||
if (!isset($this->overriddenData)) {
|
||||
$this->setOverriddenData();
|
||||
}
|
||||
if (empty($key)) {
|
||||
return $this->overriddenData;
|
||||
}
|
||||
else {
|
||||
$parts = explode('.', $key);
|
||||
if (count($parts) == 1) {
|
||||
return isset($this->overriddenData[$key]) ? $this->overriddenData[$key] : NULL;
|
||||
}
|
||||
else {
|
||||
$value = NestedArray::getValue($this->overriddenData, $parts, $key_exists);
|
||||
return $key_exists ? $value : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setData(array $data, $validate_keys = TRUE) {
|
||||
parent::setData($data, $validate_keys);
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets settings.php overrides for this configuration object.
|
||||
*
|
||||
* The overridden data only applies to this configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* The overridden values of the configuration data.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object.
|
||||
*/
|
||||
public function setSettingsOverride(array $data) {
|
||||
$this->settingsOverrides = $data;
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets module overrides for this configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* The overridden values of the configuration data.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object.
|
||||
*/
|
||||
public function setModuleOverride(array $data) {
|
||||
$this->moduleOverrides = $data;
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current data for this configuration object.
|
||||
*
|
||||
* Configuration overrides operate at two distinct layers: modules and
|
||||
* settings.php. Overrides in settings.php take precedence over values
|
||||
* provided by modules. Precedence or different module overrides is
|
||||
* determined by the priority of the config.factory.override tagged services.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object.
|
||||
*/
|
||||
protected function setOverriddenData() {
|
||||
$this->overriddenData = $this->data;
|
||||
if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
|
||||
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->moduleOverrides), TRUE);
|
||||
}
|
||||
if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) {
|
||||
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->settingsOverrides), TRUE);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current data, so overrides are re-applied.
|
||||
*
|
||||
* This method should be called after the original data or the overridden data
|
||||
* has been changed.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object.
|
||||
*/
|
||||
protected function resetOverriddenData() {
|
||||
unset($this->overriddenData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
parent::set($key, $value);
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear($key) {
|
||||
parent::clear($key);
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save($has_trusted_data = FALSE) {
|
||||
// Validate the configuration object name before saving.
|
||||
static::validateName($this->name);
|
||||
|
||||
// If there is a schema for this configuration object, cast all values to
|
||||
// conform to the schema.
|
||||
if (!$has_trusted_data) {
|
||||
if ($this->typedConfigManager->hasConfigSchema($this->name)) {
|
||||
// Ensure that the schema wrapper has the latest data.
|
||||
$this->schemaWrapper = NULL;
|
||||
foreach ($this->data as $key => $value) {
|
||||
$this->data[$key] = $this->castValue($key, $value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($this->data as $key => $value) {
|
||||
$this->validateValue($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->storage->write($this->name, $this->data);
|
||||
if (!$this->isNew) {
|
||||
Cache::invalidateTags($this->getCacheTags());
|
||||
}
|
||||
$this->isNew = FALSE;
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
|
||||
$this->originalData = $this->data;
|
||||
// Potentially configuration schema could have changed the underlying data's
|
||||
// types.
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the configuration object.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object.
|
||||
*/
|
||||
public function delete() {
|
||||
$this->data = array();
|
||||
$this->storage->delete($this->name);
|
||||
Cache::invalidateTags($this->getCacheTags());
|
||||
$this->isNew = TRUE;
|
||||
$this->resetOverriddenData();
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::DELETE, new ConfigCrudEvent($this));
|
||||
$this->originalData = $this->data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw data without overrides.
|
||||
*
|
||||
* @return array
|
||||
* The raw data.
|
||||
*/
|
||||
public function getRawData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets original data from this configuration object.
|
||||
*
|
||||
* Original data is the data as it is immediately after loading from
|
||||
* configuration storage before any changes. If this is a new configuration
|
||||
* object it will be an empty array.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Config::get()
|
||||
*
|
||||
* @param string $key
|
||||
* A string that maps to a key within the configuration data.
|
||||
* @param bool $apply_overrides
|
||||
* Apply any overrides to the original data. Defaults to TRUE.
|
||||
*
|
||||
* @return mixed
|
||||
* The data that was requested.
|
||||
*/
|
||||
public function getOriginal($key = '', $apply_overrides = TRUE) {
|
||||
$original_data = $this->originalData;
|
||||
if ($apply_overrides) {
|
||||
// Apply overrides.
|
||||
if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
|
||||
$original_data = NestedArray::mergeDeepArray(array($original_data, $this->moduleOverrides), TRUE);
|
||||
}
|
||||
if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) {
|
||||
$original_data = NestedArray::mergeDeepArray(array($original_data, $this->settingsOverrides), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($key)) {
|
||||
return $original_data;
|
||||
}
|
||||
else {
|
||||
$parts = explode('.', $key);
|
||||
if (count($parts) == 1) {
|
||||
return isset($original_data[$key]) ? $original_data[$key] : NULL;
|
||||
}
|
||||
else {
|
||||
$value = NestedArray::getValue($original_data, $parts, $key_exists);
|
||||
return $key_exists ? $value : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
289
core/lib/Drupal/Core/Config/ConfigBase.php
Normal file
289
core/lib/Drupal/Core/Config/ConfigBase.php
Normal file
|
@ -0,0 +1,289 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* Provides a base class for configuration objects with get/set support.
|
||||
*
|
||||
* Encapsulates all capabilities needed for runtime configuration handling for
|
||||
* a specific configuration object.
|
||||
*
|
||||
* Extend directly from this class for non-storable configuration where the
|
||||
* configuration API is desired but storage is not possible; for example, if
|
||||
* the data is derived at runtime. For storable configuration, extend
|
||||
* \Drupal\Core\Config\StorableConfigBase.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorableConfigBase
|
||||
* @see \Drupal\Core\Config\Config
|
||||
* @see \Drupal\Core\Theme\ThemeSettings
|
||||
*/
|
||||
abstract class ConfigBase implements CacheableDependencyInterface {
|
||||
use DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The name of the configuration object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The data of the configuration object.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* The maximum length of a configuration object name.
|
||||
*
|
||||
* Many filesystems (including HFS, NTFS, and ext4) have a maximum file name
|
||||
* length of 255 characters. To ensure that no configuration objects
|
||||
* incompatible with this limitation are created, we enforce a maximum name
|
||||
* length of 250 characters (leaving 5 characters for the file extension).
|
||||
*
|
||||
* @see http://en.wikipedia.org/wiki/Comparison_of_file_systems
|
||||
*
|
||||
* Configuration objects not stored on the filesystem should still be
|
||||
* restricted in name length so name can be used as a cache key.
|
||||
*/
|
||||
const MAX_NAME_LENGTH = 250;
|
||||
|
||||
/**
|
||||
* Returns the name of this configuration object.
|
||||
*
|
||||
* @return string
|
||||
* The name of the configuration object.
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this configuration object.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object.
|
||||
*
|
||||
* @return $this
|
||||
* The configuration object.
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the configuration object name.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigNameException
|
||||
*
|
||||
* @see Config::MAX_NAME_LENGTH
|
||||
*/
|
||||
public static function validateName($name) {
|
||||
// The name must be namespaced by owner.
|
||||
if (strpos($name, '.') === FALSE) {
|
||||
throw new ConfigNameException(SafeMarkup::format('Missing namespace in Config object name @name.', array(
|
||||
'@name' => $name,
|
||||
)));
|
||||
}
|
||||
// The name must be shorter than Config::MAX_NAME_LENGTH characters.
|
||||
if (strlen($name) > self::MAX_NAME_LENGTH) {
|
||||
throw new ConfigNameException(SafeMarkup::format('Config object name @name exceeds maximum allowed length of @length characters.', array(
|
||||
'@name' => $name,
|
||||
'@length' => self::MAX_NAME_LENGTH,
|
||||
)));
|
||||
}
|
||||
|
||||
// The name must not contain any of the following characters:
|
||||
// : ? * < > " ' / \
|
||||
if (preg_match('/[:?*<>"\'\/\\\\]/', $name)) {
|
||||
throw new ConfigNameException(SafeMarkup::format('Invalid character in Config object name @name.', array(
|
||||
'@name' => $name,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data from this configuration object.
|
||||
*
|
||||
* @param string $key
|
||||
* A string that maps to a key within the configuration data.
|
||||
* For instance in the following configuration array:
|
||||
* @code
|
||||
* array(
|
||||
* 'foo' => array(
|
||||
* 'bar' => 'baz',
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
* A key of 'foo.bar' would return the string 'baz'. However, a key of 'foo'
|
||||
* would return array('bar' => 'baz').
|
||||
* If no key is specified, then the entire data array is returned.
|
||||
*
|
||||
* @return mixed
|
||||
* The data that was requested.
|
||||
*/
|
||||
public function get($key = '') {
|
||||
if (empty($key)) {
|
||||
return $this->data;
|
||||
}
|
||||
else {
|
||||
$parts = explode('.', $key);
|
||||
if (count($parts) == 1) {
|
||||
return isset($this->data[$key]) ? $this->data[$key] : NULL;
|
||||
}
|
||||
else {
|
||||
$value = NestedArray::getValue($this->data, $parts, $key_exists);
|
||||
return $key_exists ? $value : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the data of this configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* The new configuration data.
|
||||
* @param bool $validate_keys
|
||||
* (optional) Whether the data should be verified for valid keys. Set to
|
||||
* FALSE if the $data is known to be valid already (for example, being
|
||||
* loaded from the config storage).
|
||||
*
|
||||
* @return $this
|
||||
* The configuration object.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigValueException
|
||||
* If any key in $data in any depth contains a dot.
|
||||
*/
|
||||
public function setData(array $data, $validate_keys = TRUE) {
|
||||
if ($validate_keys) {
|
||||
$this->validateKeys($data);
|
||||
}
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value in this configuration object.
|
||||
*
|
||||
* @param string $key
|
||||
* Identifier to store value in configuration.
|
||||
* @param mixed $value
|
||||
* Value to associate with identifier.
|
||||
*
|
||||
* @return $this
|
||||
* The configuration object.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigValueException
|
||||
* If $value is an array and any of its keys in any depth contains a dot.
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
// The dot/period is a reserved character; it may appear between keys, but
|
||||
// not within keys.
|
||||
if (is_array($value)) {
|
||||
$this->validateKeys($value);
|
||||
}
|
||||
$parts = explode('.', $key);
|
||||
if (count($parts) == 1) {
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
else {
|
||||
NestedArray::setValue($this->data, $parts, $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates all keys in a passed in config array structure.
|
||||
*
|
||||
* @param array $data
|
||||
* Configuration array structure.
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigValueException
|
||||
* If any key in $data in any depth contains a dot.
|
||||
*/
|
||||
protected function validateKeys(array $data) {
|
||||
foreach ($data as $key => $value) {
|
||||
if (strpos($key, '.') !== FALSE) {
|
||||
throw new ConfigValueException(SafeMarkup::format('@key key contains a dot which is not supported.', array('@key' => $key)));
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$this->validateKeys($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a value in this configuration object.
|
||||
*
|
||||
* @param string $key
|
||||
* Name of the key whose value should be unset.
|
||||
*
|
||||
* @return $this
|
||||
* The configuration object.
|
||||
*/
|
||||
public function clear($key) {
|
||||
$parts = explode('.', $key);
|
||||
if (count($parts) == 1) {
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
else {
|
||||
NestedArray::unsetValue($this->data, $parts);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges data into a configuration object.
|
||||
*
|
||||
* @param array $data_to_merge
|
||||
* An array containing data to merge.
|
||||
*
|
||||
* @return $this
|
||||
* The configuration object.
|
||||
*/
|
||||
public function merge(array $data_to_merge) {
|
||||
// Preserve integer keys so that configuration keys are not changed.
|
||||
$this->setData(NestedArray::mergeDeepArray(array($this->data, $data_to_merge), TRUE));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return ['config:' . $this->name];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
}
|
||||
|
||||
}
|
79
core/lib/Drupal/Core/Config/ConfigCollectionInfo.php
Normal file
79
core/lib/Drupal/Core/Config/ConfigCollectionInfo.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigCollectionInfo.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Gets information on all the possible configuration collections.
|
||||
*/
|
||||
class ConfigCollectionInfo extends Event {
|
||||
|
||||
/**
|
||||
* Configuration collection information keyed by collection name.
|
||||
*
|
||||
* The value is either the configuration factory override that is responsible
|
||||
* for the collection or null if there is not one.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $collections = array();
|
||||
|
||||
/**
|
||||
* Adds a collection to the list of possible collections.
|
||||
*
|
||||
* @param string $collection
|
||||
* Collection name to add.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryOverrideInterface
|
||||
* (optional) The configuration factory override service responsible for the
|
||||
* collection.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Exception thrown if $collection is equal to
|
||||
* \Drupal\Core\Config\StorageInterface::DEFAULT_COLLECTION
|
||||
*/
|
||||
public function addCollection($collection, ConfigFactoryOverrideInterface $override_service = NULL) {
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
throw new \InvalidArgumentException('Can not add the default collection to the ConfigCollectionInfo object');
|
||||
}
|
||||
$this->collections[$collection] = $override_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of possible collection names.
|
||||
*
|
||||
* @param bool $include_default
|
||||
* (Optional) Include the default collection. Defaults to TRUE.
|
||||
*
|
||||
* @return array
|
||||
* The list of possible collection names.
|
||||
*/
|
||||
public function getCollectionNames($include_default = TRUE) {
|
||||
$collection_names = array_keys($this->collections);
|
||||
sort($collection_names);
|
||||
if ($include_default) {
|
||||
array_unshift($collection_names, StorageInterface::DEFAULT_COLLECTION);
|
||||
}
|
||||
return $collection_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config factory override service responsible for the collection.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigFactoryOverrideInterface|NULL
|
||||
* The override service responsible for the collection if one exists. NULL
|
||||
* if not.
|
||||
*/
|
||||
public function getOverrideService($collection) {
|
||||
return isset($this->collections[$collection]) ? $this->collections[$collection] : NULL;
|
||||
}
|
||||
|
||||
}
|
57
core/lib/Drupal/Core/Config/ConfigCrudEvent.php
Normal file
57
core/lib/Drupal/Core/Config/ConfigCrudEvent.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigCrudEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a configuration event for event listeners.
|
||||
*/
|
||||
class ConfigCrudEvent extends Event {
|
||||
|
||||
/**
|
||||
* Configuration object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructs a configuration event object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Config
|
||||
* Configuration object.
|
||||
*/
|
||||
public function __construct(Config $config) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets configuration object.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object that caused the event to fire.
|
||||
*/
|
||||
public function getConfig() {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the provided configuration key's value has changed.
|
||||
*
|
||||
* @param string $key
|
||||
* The configuration key to check if it has changed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isChanged($key) {
|
||||
return $this->config->get($key) !== $this->config->getOriginal($key);
|
||||
}
|
||||
|
||||
}
|
||||
|
15
core/lib/Drupal/Core/Config/ConfigDuplicateUUIDException.php
Normal file
15
core/lib/Drupal/Core/Config/ConfigDuplicateUUIDException.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigDuplicateUUIDException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception thrown when a config object UUID causes a conflict.
|
||||
*/
|
||||
class ConfigDuplicateUUIDException extends ConfigException {
|
||||
|
||||
}
|
136
core/lib/Drupal/Core/Config/ConfigEvents.php
Normal file
136
core/lib/Drupal/Core/Config/ConfigEvents.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigEvents.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines events for the configuration system.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigCrudEvent
|
||||
*/
|
||||
final class ConfigEvents {
|
||||
|
||||
/**
|
||||
* Name of the event fired when saving a configuration object.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a configuration
|
||||
* object is saved. The event listener method receives a
|
||||
* \Drupal\Core\Config\ConfigCrudEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigCrudEvent
|
||||
* @see \Drupal\Core\Config\Config::save()
|
||||
* @see \Drupal\Core\Config\ConfigFactory::onConfigSave()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SAVE = 'config.save';
|
||||
|
||||
/**
|
||||
* Name of the event fired when deleting a configuration object.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a configuration
|
||||
* object is deleted. The event listener method receives a
|
||||
* \Drupal\Core\Config\ConfigCrudEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigCrudEvent
|
||||
* @see \Drupal\Core\Config\Config::delete()
|
||||
* @see \Drupal\Core\Config\ConfigFactory::onConfigDelete()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DELETE = 'config.delete';
|
||||
|
||||
/**
|
||||
* Name of the event fired when renaming a configuration object.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a configuration
|
||||
* object's name is changed. The event listener method receives a
|
||||
* \Drupal\Core\Config\ConfigRenameEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigRenameEvent
|
||||
* @see \Drupal\Core\Config\ConfigFactoryInterface::rename().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RENAME = 'config.rename';
|
||||
|
||||
/**
|
||||
* Name of the event fired when validating imported configuration.
|
||||
*
|
||||
* This event allows modules to perform additional validation operations when
|
||||
* configuration is being imported. The event listener method receives a
|
||||
* \Drupal\Core\Config\ConfigImporterEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporterEvent
|
||||
* @see \Drupal\Core\Config\ConfigImporter::validate().
|
||||
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber::onConfigImporterValidate().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IMPORT_VALIDATE = 'config.importer.validate';
|
||||
|
||||
/**
|
||||
* Name of the event fired when importing configuration to target storage.
|
||||
*
|
||||
* This event allows modules to perform additional actions when configuration
|
||||
* is imported. The event listener method receives a
|
||||
* \Drupal\Core\Config\ConfigImporterEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporterEvent
|
||||
* @see \Drupal\Core\Config\ConfigImporter::import().
|
||||
* @see \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber::onConfigImporterImport().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IMPORT = 'config.importer.import';
|
||||
|
||||
/**
|
||||
* Name of event fired when missing content dependencies are detected.
|
||||
*
|
||||
* Events subscribers are fired as part of the configuration import batch.
|
||||
* Each subscribe should call
|
||||
* \Drupal\Core\Config\MissingContentEvent::resolveMissingContent() when they
|
||||
* address a missing dependency. To address large amounts of dependencies
|
||||
* subscribers can call
|
||||
* \Drupal\Core\Config\MissingContentEvent::stopPropagation() which will stop
|
||||
* calling other events and guarantee that the configuration import batch will
|
||||
* fire the event again to continue processing missing content dependencies.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter::processMissingContent()
|
||||
* @see \Drupal\Core\Config\MissingContentEvent
|
||||
*/
|
||||
const IMPORT_MISSING_CONTENT = 'config.importer.missing_content';
|
||||
|
||||
/**
|
||||
* Name of event fired to collect information on all config collections.
|
||||
*
|
||||
* This event allows modules to add to the list of configuration collections
|
||||
* retrieved by \Drupal\Core\Config\ConfigManager::getConfigCollectionInfo().
|
||||
* The event listener method receives a
|
||||
* \Drupal\Core\Config\ConfigCollectionInfo instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigCollectionInfo
|
||||
* @see \Drupal\Core\Config\ConfigManager::getConfigCollectionInfo()
|
||||
* @see \Drupal\Core\Config\ConfigFactoryOverrideBase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COLLECTION_INFO = 'config.collection_info';
|
||||
|
||||
}
|
13
core/lib/Drupal/Core/Config/ConfigException.php
Normal file
13
core/lib/Drupal/Core/Config/ConfigException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* A base exception thrown in any configuration system operations.
|
||||
*/
|
||||
class ConfigException extends \RuntimeException {}
|
383
core/lib/Drupal/Core/Config/ConfigFactory.php
Normal file
383
core/lib/Drupal/Core/Config/ConfigFactory.php
Normal file
|
@ -0,0 +1,383 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Defines the configuration object factory.
|
||||
*
|
||||
* The configuration object factory instantiates a Config object for each
|
||||
* configuration object name that is accessed and returns it to callers.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Config
|
||||
*
|
||||
* Each configuration object gets a storage object injected, which
|
||||
* is used for reading and writing the configuration data.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageInterface
|
||||
*
|
||||
* @ingroup config_api
|
||||
*/
|
||||
class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* A storage instance for reading and writing configuration data.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* An event dispatcher instance to use for configuration events.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* Cached configuration objects.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config[]
|
||||
*/
|
||||
protected $cache = array();
|
||||
|
||||
/**
|
||||
* The typed config manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedConfigManager;
|
||||
|
||||
/**
|
||||
* An array of config factory override objects ordered by priority.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryOverrideInterface[]
|
||||
*/
|
||||
protected $configFactoryOverrides = array();
|
||||
|
||||
/**
|
||||
* Constructs the Config factory.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||
* The configuration storage engine.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* An event dispatcher instance to use for configuration events.
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
||||
* The typed configuration manager.
|
||||
*/
|
||||
public function __construct(StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManagerInterface $typed_config) {
|
||||
$this->storage = $storage;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->typedConfigManager = $typed_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEditable($name) {
|
||||
return $this->doGet($name, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($name) {
|
||||
return $this->doGet($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for a given name.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object to construct.
|
||||
* @param bool $immutable
|
||||
* (optional) Create an immutable configuration object. Defaults to TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
|
||||
* A configuration object.
|
||||
*/
|
||||
protected function doGet($name, $immutable = TRUE) {
|
||||
if ($config = $this->doLoadMultiple(array($name), $immutable)) {
|
||||
return $config[$name];
|
||||
}
|
||||
else {
|
||||
// If the configuration object does not exist in the configuration
|
||||
// storage, create a new object and add it to the static cache.
|
||||
$cache_key = $this->getConfigCacheKey($name, $immutable);
|
||||
$this->cache[$cache_key] = $this->createConfigObject($name, $immutable);
|
||||
|
||||
if ($immutable) {
|
||||
// Get and apply any overrides.
|
||||
$overrides = $this->loadOverrides(array($name));
|
||||
if (isset($overrides[$name])) {
|
||||
$this->cache[$cache_key]->setModuleOverride($overrides[$name]);
|
||||
}
|
||||
// Apply any settings.php overrides.
|
||||
if (isset($GLOBALS['config'][$name])) {
|
||||
$this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
|
||||
}
|
||||
}
|
||||
return $this->cache[$cache_key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMultiple(array $names) {
|
||||
return $this->doLoadMultiple($names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of configuration objects for the given names.
|
||||
*
|
||||
* @param array $names
|
||||
* List of names of configuration objects.
|
||||
* @param bool $immutable
|
||||
* (optional) Create an immutable configuration objects. Defaults to TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config[]|\Drupal\Core\Config\ImmutableConfig[]
|
||||
* List of successfully loaded configuration objects, keyed by name.
|
||||
*/
|
||||
protected function doLoadMultiple(array $names, $immutable = TRUE) {
|
||||
$list = array();
|
||||
|
||||
foreach ($names as $key => $name) {
|
||||
$cache_key = $this->getConfigCacheKey($name, $immutable);
|
||||
if (isset($this->cache[$cache_key])) {
|
||||
$list[$name] = $this->cache[$cache_key];
|
||||
unset($names[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-load remaining configuration files.
|
||||
if (!empty($names)) {
|
||||
// Initialise override information.
|
||||
$module_overrides = array();
|
||||
$storage_data = $this->storage->readMultiple($names);
|
||||
|
||||
if ($immutable && !empty($storage_data)) {
|
||||
// Only get module overrides if we have configuration to override.
|
||||
$module_overrides = $this->loadOverrides($names);
|
||||
}
|
||||
|
||||
foreach ($storage_data as $name => $data) {
|
||||
$cache_key = $this->getConfigCacheKey($name, $immutable);
|
||||
|
||||
$this->cache[$cache_key] = $this->createConfigObject($name, $immutable);
|
||||
$this->cache[$cache_key]->initWithData($data);
|
||||
if ($immutable) {
|
||||
if (isset($module_overrides[$name])) {
|
||||
$this->cache[$cache_key]->setModuleOverride($module_overrides[$name]);
|
||||
}
|
||||
if (isset($GLOBALS['config'][$name])) {
|
||||
$this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
|
||||
}
|
||||
}
|
||||
$list[$name] = $this->cache[$cache_key];
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get arbitrary overrides for the named configuration objects from modules.
|
||||
*
|
||||
* @param array $names
|
||||
* The names of the configuration objects to get overrides for.
|
||||
*
|
||||
* @return array
|
||||
* An array of overrides keyed by the configuration object name.
|
||||
*/
|
||||
protected function loadOverrides(array $names) {
|
||||
$overrides = array();
|
||||
foreach ($this->configFactoryOverrides as $override) {
|
||||
// Existing overrides take precedence since these will have been added
|
||||
// by events with a higher priority.
|
||||
$overrides = NestedArray::mergeDeepArray(array($override->loadOverrides($names), $overrides), TRUE);
|
||||
}
|
||||
return $overrides;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset($name = NULL) {
|
||||
if ($name) {
|
||||
// Clear all cached configuration for this name.
|
||||
foreach ($this->getConfigCacheKeys($name) as $cache_key) {
|
||||
unset($this->cache[$cache_key]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->cache = array();
|
||||
}
|
||||
|
||||
// Clear the static list cache if supported by the storage.
|
||||
if ($this->storage instanceof StorageCacheInterface) {
|
||||
$this->storage->resetListCache();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rename($old_name, $new_name) {
|
||||
Cache::invalidateTags($this->get($old_name)->getCacheTags());
|
||||
$this->storage->rename($old_name, $new_name);
|
||||
|
||||
// Clear out the static cache of any references to the old name.
|
||||
foreach ($this->getConfigCacheKeys($old_name) as $old_cache_key) {
|
||||
unset($this->cache[$old_cache_key]);
|
||||
}
|
||||
|
||||
// Prime the cache and load the configuration with the correct overrides.
|
||||
$config = $this->get($new_name);
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::RENAME, new ConfigRenameEvent($config, $old_name));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheKeys() {
|
||||
// Because get() adds overrides both from $GLOBALS and from
|
||||
// $this->configFactoryOverrides, add cache keys for each.
|
||||
$keys[] = 'global_overrides';
|
||||
foreach($this->configFactoryOverrides as $override) {
|
||||
$keys[] = $override->getCacheSuffix();
|
||||
}
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the static cache key for a given config name.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object.
|
||||
* @param bool $immutable
|
||||
* Whether or not the object is mutable.
|
||||
*
|
||||
* @return string
|
||||
* The cache key.
|
||||
*/
|
||||
protected function getConfigCacheKey($name, $immutable) {
|
||||
$suffix = '';
|
||||
if ($immutable) {
|
||||
$suffix = ':' . implode(':', $this->getCacheKeys());
|
||||
}
|
||||
return $name . $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the cache keys that match the provided config name.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object.
|
||||
*
|
||||
* @return array
|
||||
* An array of cache keys that match the provided config name.
|
||||
*/
|
||||
protected function getConfigCacheKeys($name) {
|
||||
return array_filter(array_keys($this->cache), function($key) use ($name) {
|
||||
// Return TRUE if the key is the name or starts with the configuration
|
||||
// name plus the delimiter.
|
||||
return $key === $name || strpos($key, $name . ':') === 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearStaticCache() {
|
||||
$this->cache = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
return $this->storage->listAll($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates stale static cache entries when configuration is saved.
|
||||
*
|
||||
* @param ConfigCrudEvent $event
|
||||
* The configuration event.
|
||||
*/
|
||||
public function onConfigSave(ConfigCrudEvent $event) {
|
||||
// Ensure that the static cache contains up to date configuration objects by
|
||||
// replacing the data on any entries for the configuration object apart
|
||||
// from the one that references the actual config object being saved.
|
||||
$saved_config = $event->getConfig();
|
||||
foreach ($this->getConfigCacheKeys($saved_config->getName()) as $cache_key) {
|
||||
$cached_config = $this->cache[$cache_key];
|
||||
if ($cached_config !== $saved_config) {
|
||||
// We can not just update the data since other things about the object
|
||||
// might have changed. For example, whether or not it is new.
|
||||
$this->cache[$cache_key]->initWithData($saved_config->getRawData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes stale static cache entries when configuration is deleted.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The configuration event.
|
||||
*/
|
||||
public function onConfigDelete(ConfigCrudEvent $event) {
|
||||
// Ensure that the static cache does not contain deleted configuration.
|
||||
foreach ($this->getConfigCacheKeys($event->getConfig()->getName()) as $cache_key) {
|
||||
unset($this->cache[$cache_key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::SAVE][] = array('onConfigSave', 255);
|
||||
$events[ConfigEvents::DELETE][] = array('onConfigDelete', 255);
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addOverride(ConfigFactoryOverrideInterface $config_factory_override) {
|
||||
$this->configFactoryOverrides[] = $config_factory_override;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration object.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration object name.
|
||||
* @param bool $immutable
|
||||
* Determines whether a mutable or immutable config object is returned.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
|
||||
* The configuration object.
|
||||
*/
|
||||
protected function createConfigObject($name, $immutable) {
|
||||
if ($immutable) {
|
||||
return new ImmutableConfig($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
|
||||
}
|
||||
return new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
|
||||
}
|
||||
|
||||
}
|
125
core/lib/Drupal/Core/Config/ConfigFactoryInterface.php
Normal file
125
core/lib/Drupal/Core/Config/ConfigFactoryInterface.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigFactoryInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines the interface for a configuration object factory.
|
||||
*
|
||||
* @ingroup config_api
|
||||
*/
|
||||
interface ConfigFactoryInterface {
|
||||
|
||||
/**
|
||||
* Returns an immutable configuration object for a given name.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object to construct.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ImmutableConfig
|
||||
* A configuration object.
|
||||
*/
|
||||
public function get($name);
|
||||
|
||||
/**
|
||||
* Returns an mutable configuration object for a given name.
|
||||
*
|
||||
* Should not be used for config that will have runtime effects. Therefore it
|
||||
* is always loaded override free.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object to construct.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* A configuration object.
|
||||
*/
|
||||
public function getEditable($name);
|
||||
|
||||
/**
|
||||
* Returns a list of configuration objects for the given names.
|
||||
*
|
||||
* This will pre-load all requested configuration objects does not create
|
||||
* new configuration objects. This method always return immutable objects.
|
||||
* ConfigFactoryInterface::getEditable() should be used to retrieve mutable
|
||||
* configuration objects, one by one.
|
||||
*
|
||||
* @param array $names
|
||||
* List of names of configuration objects.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ImmutableConfig[]
|
||||
* List of successfully loaded configuration objects, keyed by name.
|
||||
*/
|
||||
public function loadMultiple(array $names);
|
||||
|
||||
/**
|
||||
* Resets and re-initializes configuration objects. Internal use only.
|
||||
*
|
||||
* @param string|null $name
|
||||
* (optional) The name of the configuration object to reset. If omitted, all
|
||||
* configuration objects are reset.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset($name = NULL);
|
||||
|
||||
/**
|
||||
* Renames a configuration object using the storage.
|
||||
*
|
||||
* @param string $old_name
|
||||
* The old name of the configuration object.
|
||||
* @param string $new_name
|
||||
* The new name of the configuration object.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function rename($old_name, $new_name);
|
||||
|
||||
/**
|
||||
* The cache keys associated with the state of the config factory.
|
||||
*
|
||||
* All state information that can influence the result of a get() should be
|
||||
* included. Typically, this includes a key for each override added via
|
||||
* addOverride(). This allows external code to maintain caches of
|
||||
* configuration data in addition to or instead of caches maintained by the
|
||||
* factory.
|
||||
*
|
||||
* @return array
|
||||
* An array of strings, used to generate a cache ID.
|
||||
*/
|
||||
public function getCacheKeys();
|
||||
|
||||
/**
|
||||
* Clears the config factory static cache.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clearStaticCache();
|
||||
|
||||
/**
|
||||
* Gets configuration object names starting with a given prefix.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageInterface::listAll()
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration object
|
||||
* names that exist are returned.
|
||||
*
|
||||
* @return array
|
||||
* An array containing matching configuration object names.
|
||||
*/
|
||||
public function listAll($prefix = '');
|
||||
|
||||
/**
|
||||
* Adds config factory override services.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryOverrideInterface $config_factory_override
|
||||
* The config factory override service to add. It is added at the end of the
|
||||
* priority list (lower priority relative to existing ones).
|
||||
*/
|
||||
public function addOverride(ConfigFactoryOverrideInterface $config_factory_override);
|
||||
|
||||
}
|
121
core/lib/Drupal/Core/Config/ConfigFactoryOverrideBase.php
Normal file
121
core/lib/Drupal/Core/Config/ConfigFactoryOverrideBase.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigFactoryOverrideBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Defines a base event listener implementation configuration overrides.
|
||||
*/
|
||||
abstract class ConfigFactoryOverrideBase implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Reacts to the ConfigEvents::COLLECTION_INFO event.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCollectionInfo $collection_info
|
||||
* The configuration collection names event.
|
||||
*/
|
||||
abstract public function addCollections(ConfigCollectionInfo $collection_info);
|
||||
|
||||
/**
|
||||
* Actions to be performed to configuration override on configuration save.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The config CRUD event.
|
||||
*/
|
||||
abstract public function onConfigSave(ConfigCrudEvent $event);
|
||||
|
||||
/**
|
||||
* Actions to be performed to configuration override on configuration delete.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The config CRUD event.
|
||||
*/
|
||||
abstract public function onConfigDelete(ConfigCrudEvent $event);
|
||||
|
||||
/**
|
||||
* Actions to be performed to configuration override on configuration rename.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigRenameEvent $event
|
||||
* The config rename event.
|
||||
*/
|
||||
abstract public function onConfigRename(ConfigRenameEvent $event);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::COLLECTION_INFO][] = array('addCollections');
|
||||
$events[ConfigEvents::SAVE][] = array('onConfigSave', 20);
|
||||
$events[ConfigEvents::DELETE][] = array('onConfigDelete', 20);
|
||||
$events[ConfigEvents::RENAME][] = array('onConfigRename', 20);
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters data in the override based on what is currently in configuration.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* Current configuration object.
|
||||
* @param \Drupal\Core\Config\StorableConfigBase $override
|
||||
* Override object corresponding to the configuration to filter data in.
|
||||
*/
|
||||
protected function filterOverride(Config $config, StorableConfigBase $override) {
|
||||
$override_data = $override->get();
|
||||
$changed = $this->filterNestedArray($config->get(), $override_data);
|
||||
if (empty($override_data)) {
|
||||
// If no override values are left that would apply, remove the override.
|
||||
$override->delete();
|
||||
}
|
||||
elseif ($changed) {
|
||||
// Otherwise set the filtered override values back.
|
||||
$override->setData($override_data)->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters data in nested arrays.
|
||||
*
|
||||
* @param array $original_data
|
||||
* Original data array to filter against.
|
||||
* @param array $override_data
|
||||
* Override data to filter.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if $override_data was changed, FALSE otherwise.
|
||||
*/
|
||||
protected function filterNestedArray(array $original_data, array &$override_data) {
|
||||
$changed = FALSE;
|
||||
foreach ($override_data as $key => $value) {
|
||||
if (!isset($original_data[$key])) {
|
||||
// The original data is not there anymore, remove the override.
|
||||
unset($override_data[$key]);
|
||||
$changed = TRUE;
|
||||
}
|
||||
elseif (is_array($override_data[$key])) {
|
||||
if (is_array($original_data[$key])) {
|
||||
// Do the filtering one level deeper.
|
||||
$changed = $this->filterNestedArray($original_data[$key], $override_data[$key]);
|
||||
// If no overrides are left under this level, remove the level.
|
||||
if (empty($override_data[$key])) {
|
||||
unset($override_data[$key]);
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The override is an array but the value is not, this will not go
|
||||
// well, remove the override.
|
||||
unset($override_data[$key]);
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigFactoryOverrideInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines the interface for a configuration factory override object.
|
||||
*/
|
||||
interface ConfigFactoryOverrideInterface {
|
||||
|
||||
/**
|
||||
* Returns config overrides.
|
||||
*
|
||||
* @param array $names
|
||||
* A list of configuration names that are being loaded.
|
||||
*
|
||||
* @return array
|
||||
* An array keyed by configuration name of override data. Override data
|
||||
* contains a nested array structure of overrides.
|
||||
*/
|
||||
public function loadOverrides($names);
|
||||
|
||||
/**
|
||||
* The string to append to the configuration static cache name.
|
||||
*
|
||||
* @return string
|
||||
* A string to append to the configuration static cache name.
|
||||
*/
|
||||
public function getCacheSuffix();
|
||||
|
||||
/**
|
||||
* Creates a configuration object for use during install and synchronization.
|
||||
*
|
||||
* If the overrider stores its overrides in configuration collections then
|
||||
* it can have its own implementation of
|
||||
* \Drupal\Core\Config\StorableConfigBase. Configuration overriders can link
|
||||
* themselves to a configuration collection by listening to the
|
||||
* \Drupal\Core\Config\ConfigEvents::COLLECTION_INFO event and adding the
|
||||
* collections they are responsible for. Doing this will allow installation
|
||||
* and synchronization to use the overrider's implementation of
|
||||
* StorableConfigBase.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigCollectionInfo
|
||||
* @see \Drupal\Core\Config\ConfigImporter::importConfig()
|
||||
* @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
|
||||
*
|
||||
* @param string $name
|
||||
* The configuration object name.
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorableConfigBase
|
||||
* The configuration object for the provided name and collection.
|
||||
*/
|
||||
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigImportValidateEventSubscriberBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Defines a base event listener implementation for config sync validation.
|
||||
*/
|
||||
abstract class ConfigImportValidateEventSubscriberBase implements EventSubscriberInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Checks that the configuration synchronization is valid.
|
||||
*
|
||||
* @param ConfigImporterEvent $event
|
||||
* The config import event.
|
||||
*/
|
||||
abstract public function onConfigImporterValidate(ConfigImporterEvent $event);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::IMPORT_VALIDATE][] = array('onConfigImporterValidate', 20);
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
1055
core/lib/Drupal/Core/Config/ConfigImporter.php
Normal file
1055
core/lib/Drupal/Core/Config/ConfigImporter.php
Normal file
File diff suppressed because it is too large
Load diff
59
core/lib/Drupal/Core/Config/ConfigImporterEvent.php
Normal file
59
core/lib/Drupal/Core/Config/ConfigImporterEvent.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigImporterEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
class ConfigImporterEvent extends Event {
|
||||
/**
|
||||
* Configuration import object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
protected $configImporter;
|
||||
|
||||
/**
|
||||
* Constructs ConfigImporterEvent.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigImporter $config_importer
|
||||
* A config import object to notify listeners about.
|
||||
*/
|
||||
public function __construct(ConfigImporter $config_importer) {
|
||||
$this->configImporter = $config_importer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config import object.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigImporter
|
||||
* The ConfigImporter object.
|
||||
*/
|
||||
public function getConfigImporter() {
|
||||
return $this->configImporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of changes that will be imported.
|
||||
*
|
||||
* @param string $op
|
||||
* (optional) A change operation. Either delete, create or update. If
|
||||
* supplied the returned list will be limited to this operation.
|
||||
* @param string $collection
|
||||
* (optional) The collection to get the changelist for. Defaults to the
|
||||
* default collection.
|
||||
*
|
||||
* @return array
|
||||
* An array of config changes that are yet to be imported.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerInterface::getChangelist()
|
||||
*/
|
||||
public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
return $this->configImporter->getStorageComparer()->getChangelist($op, $collection);
|
||||
}
|
||||
|
||||
}
|
13
core/lib/Drupal/Core/Config/ConfigImporterException.php
Normal file
13
core/lib/Drupal/Core/Config/ConfigImporterException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigImporterException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception thrown when a config import fails.
|
||||
*/
|
||||
class ConfigImporterException extends ConfigException {}
|
630
core/lib/Drupal/Core/Config/ConfigInstaller.php
Normal file
630
core/lib/Drupal/Core/Config/ConfigInstaller.php
Normal file
|
@ -0,0 +1,630 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigInstaller.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityDependency;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class ConfigInstaller implements ConfigInstallerInterface {
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The active configuration storages, keyed by collection.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
protected $activeStorages;
|
||||
|
||||
/**
|
||||
* The typed configuration manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedConfig;
|
||||
|
||||
/**
|
||||
* The configuration manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigManagerInterface
|
||||
*/
|
||||
protected $configManager;
|
||||
|
||||
/**
|
||||
* The event dispatcher.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* The configuration storage that provides the default configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $sourceStorage;
|
||||
|
||||
/**
|
||||
* Is configuration being created as part of a configuration sync.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isSyncing = FALSE;
|
||||
|
||||
/**
|
||||
* Constructs the configuration installer.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param \Drupal\Core\Config\StorageInterface $active_storage
|
||||
* The active configuration storage.
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
||||
* The typed configuration manager.
|
||||
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
||||
* The configuration manager.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* The event dispatcher.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->activeStorages[$active_storage->getCollectionName()] = $active_storage;
|
||||
$this->typedConfig = $typed_config;
|
||||
$this->configManager = $config_manager;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installDefaultConfig($type, $name) {
|
||||
$extension_path = $this->drupalGetPath($type, $name);
|
||||
// Refresh the schema cache if the extension provides configuration schema
|
||||
// or is a theme.
|
||||
if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY) || $type == 'theme') {
|
||||
$this->typedConfig->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
$default_install_path = $this->getDefaultConfigDirectory($type, $name);
|
||||
if (is_dir($default_install_path)) {
|
||||
if (!$this->isSyncing()) {
|
||||
$storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION);
|
||||
$prefix = '';
|
||||
}
|
||||
else {
|
||||
// The configuration importer sets the source storage on the config
|
||||
// installer. The configuration importer handles all of the
|
||||
// configuration entity imports. We only need to ensure that simple
|
||||
// configuration is created when the extension is installed.
|
||||
$storage = $this->getSourceStorage();
|
||||
$prefix = $name . '.';
|
||||
}
|
||||
|
||||
// Gets a profile storage to search for overrides if necessary.
|
||||
$profile_storage = $this->getProfileStorage($name);
|
||||
|
||||
// Gather information about all the supported collections.
|
||||
$collection_info = $this->configManager->getConfigCollectionInfo();
|
||||
foreach ($collection_info->getCollectionNames() as $collection) {
|
||||
$config_to_create = $this->getConfigToCreate($storage, $collection, $prefix, $profile_storage);
|
||||
// If we're installing a profile ensure configuration that is overriding
|
||||
// is excluded.
|
||||
if ($name == $this->drupalGetProfile()) {
|
||||
$existing_configuration = $this->getActiveStorages($collection)->listAll();
|
||||
$config_to_create = array_diff_key($config_to_create, array_flip($existing_configuration));
|
||||
}
|
||||
if (!empty($config_to_create)) {
|
||||
$this->createConfiguration($collection, $config_to_create);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// During a drupal installation optional configuration is installed at the
|
||||
// end of the installation process.
|
||||
// @see install_install_profile()
|
||||
if (!$this->isSyncing() && !$this->drupalInstallationAttempted()) {
|
||||
$optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
|
||||
if (is_dir($optional_install_path)) {
|
||||
// Install any optional config the module provides.
|
||||
$storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION);
|
||||
$this->installOptionalConfig($storage, '');
|
||||
}
|
||||
// Install any optional configuration entities whose dependencies can now
|
||||
// be met. This searches all the installed modules config/optional
|
||||
// directories.
|
||||
$storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE);
|
||||
$this->installOptionalConfig($storage, [$type => $name]);
|
||||
}
|
||||
|
||||
// Reset all the static caches and list caches.
|
||||
$this->configFactory->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = []) {
|
||||
$profile = $this->drupalGetProfile();
|
||||
if (!$storage) {
|
||||
// Search the install profile's optional configuration too.
|
||||
$storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE);
|
||||
// The extension install storage ensures that overrides are used.
|
||||
$profile_storage = NULL;
|
||||
}
|
||||
elseif (isset($profile)) {
|
||||
// Creates a profile storage to search for overrides.
|
||||
$profile_install_path = $this->drupalGetPath('module', $profile) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
|
||||
$profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
|
||||
}
|
||||
else {
|
||||
// Profile has not been set yet. For example during the first steps of the
|
||||
// installer or during unit tests.
|
||||
$profile_storage = NULL;
|
||||
}
|
||||
|
||||
$enabled_extensions = $this->getEnabledExtensions();
|
||||
$existing_config = $this->getActiveStorages()->listAll();
|
||||
|
||||
$list = array_filter($storage->listAll(), function($config_name) use ($existing_config) {
|
||||
// Only list configuration that:
|
||||
// - does not already exist
|
||||
// - is a configuration entity (this also excludes config that has an
|
||||
// implicit dependency on modules that are not yet installed)
|
||||
return !in_array($config_name, $existing_config) && $this->configManager->getEntityTypeIdByName($config_name);
|
||||
});
|
||||
|
||||
$all_config = array_merge($existing_config, $list);
|
||||
$config_to_create = $storage->readMultiple($list);
|
||||
// Check to see if the corresponding override storage has any overrides.
|
||||
if ($profile_storage) {
|
||||
$config_to_create = $profile_storage->readMultiple($list) + $config_to_create;
|
||||
}
|
||||
foreach ($config_to_create as $config_name => $data) {
|
||||
// Exclude configuration where its dependencies cannot be met.
|
||||
if (!$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config)) {
|
||||
unset($config_to_create[$config_name]);
|
||||
}
|
||||
// Exclude configuration that does not have a matching dependency.
|
||||
elseif (!empty($dependency)) {
|
||||
// Create a light weight dependency object to check dependencies.
|
||||
$config_entity = new ConfigEntityDependency($config_name, $data);
|
||||
if (!$config_entity->hasDependency(key($dependency), reset($dependency))) {
|
||||
unset($config_to_create[$config_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($config_to_create)) {
|
||||
$this->createConfiguration(StorageInterface::DEFAULT_COLLECTION, $config_to_create, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets configuration data from the provided storage to create.
|
||||
*
|
||||
* @param StorageInterface $storage
|
||||
* The configuration storage to read configuration from.
|
||||
* @param string $collection
|
||||
* The configuration collection to use.
|
||||
* @param string $prefix
|
||||
* (optional) Limit to configuration starting with the provided string.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration data read from the source storage keyed by the
|
||||
* configuration object name.
|
||||
*/
|
||||
protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', StorageInterface $profile_storage = NULL) {
|
||||
if ($storage->getCollectionName() != $collection) {
|
||||
$storage = $storage->createCollection($collection);
|
||||
}
|
||||
$data = $storage->readMultiple($storage->listAll($prefix));
|
||||
|
||||
// Check to see if the corresponding override storage has any overrides.
|
||||
if ($profile_storage) {
|
||||
if ($profile_storage->getCollectionName() != $collection) {
|
||||
$profile_storage = $profile_storage->createCollection($collection);
|
||||
}
|
||||
$data = $profile_storage->readMultiple(array_keys($data)) + $data;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates configuration in a collection based on the provided list.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
* @param array $config_to_create
|
||||
* An array of configuration data to create, keyed by name.
|
||||
*/
|
||||
protected function createConfiguration($collection, array $config_to_create) {
|
||||
// Order the configuration to install in the order of dependencies.
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$dependency_manager = new ConfigDependencyManager();
|
||||
$config_names = $dependency_manager
|
||||
->setData($config_to_create)
|
||||
->sortAll();
|
||||
}
|
||||
else {
|
||||
$config_names = array_keys($config_to_create);
|
||||
}
|
||||
|
||||
foreach ($config_names as $name) {
|
||||
// Allow config factory overriders to use a custom configuration object if
|
||||
// they are responsible for the collection.
|
||||
$overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection);
|
||||
if ($overrider) {
|
||||
$new_config = $overrider->createConfigObject($name, $collection);
|
||||
}
|
||||
else {
|
||||
$new_config = new Config($name, $this->getActiveStorages($collection), $this->eventDispatcher, $this->typedConfig);
|
||||
}
|
||||
if ($config_to_create[$name] !== FALSE) {
|
||||
$new_config->setData($config_to_create[$name]);
|
||||
}
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION && $entity_type = $this->configManager->getEntityTypeIdByName($name)) {
|
||||
// If we are syncing do not create configuration entities. Pluggable
|
||||
// configuration entities can have dependencies on modules that are
|
||||
// not yet enabled. This approach means that any code that expects
|
||||
// default configuration entities to exist will be unstable after the
|
||||
// module has been enabled and before the config entity has been
|
||||
// imported.
|
||||
if ($this->isSyncing()) {
|
||||
continue;
|
||||
}
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entity_storage */
|
||||
$entity_storage = $this->configManager
|
||||
->getEntityManager()
|
||||
->getStorage($entity_type);
|
||||
// It is possible that secondary writes can occur during configuration
|
||||
// creation. Updates of such configuration are allowed.
|
||||
if ($this->getActiveStorages($collection)->exists($name)) {
|
||||
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
|
||||
$entity = $entity_storage->load($id);
|
||||
$entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
|
||||
}
|
||||
else {
|
||||
$entity = $entity_storage->createFromStorageRecord($new_config->get());
|
||||
}
|
||||
if ($entity->isInstallable()) {
|
||||
$entity->trustData()->save();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$new_config->save(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installCollectionDefaultConfig($collection) {
|
||||
$storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted());
|
||||
// Only install configuration for enabled extensions.
|
||||
$enabled_extensions = $this->getEnabledExtensions();
|
||||
$config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
|
||||
$provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
|
||||
return in_array($provider, $enabled_extensions);
|
||||
});
|
||||
if (!empty($config_to_install)) {
|
||||
$this->createConfiguration($collection, $storage->readMultiple($config_to_install));
|
||||
// Reset all the static caches and list caches.
|
||||
$this->configFactory->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSourceStorage(StorageInterface $storage) {
|
||||
$this->sourceStorage = $storage;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration storage that provides the default configuration.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface|null
|
||||
* The configuration storage that provides the default configuration.
|
||||
* Returns null if the source storage has not been set.
|
||||
*/
|
||||
public function getSourceStorage() {
|
||||
return $this->sourceStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration storage that provides the active configuration.
|
||||
*
|
||||
* @param string $collection
|
||||
* (optional) The configuration collection. Defaults to the default
|
||||
* collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* The configuration storage that provides the default configuration.
|
||||
*/
|
||||
protected function getActiveStorages($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if (!isset($this->activeStorages[$collection])) {
|
||||
$this->activeStorages[$collection] = reset($this->activeStorages)->createCollection($collection);
|
||||
}
|
||||
return $this->activeStorages[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSyncing($status) {
|
||||
if (!$status) {
|
||||
$this->sourceStorage = NULL;
|
||||
}
|
||||
$this->isSyncing = $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSyncing() {
|
||||
return $this->isSyncing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds pre-existing configuration objects for the provided extension.
|
||||
*
|
||||
* Extensions can not be installed if configuration objects exist in the
|
||||
* active storage with the same names. This can happen in a number of ways,
|
||||
* commonly:
|
||||
* - if a user has created configuration with the same name as that provided
|
||||
* by the extension.
|
||||
* - if the extension provides default configuration that does not depend on
|
||||
* it and the extension has been uninstalled and is about to the
|
||||
* reinstalled.
|
||||
*
|
||||
* @return array
|
||||
* Array of configuration object names that already exist keyed by
|
||||
* collection.
|
||||
*/
|
||||
protected function findPreExistingConfiguration(StorageInterface $storage) {
|
||||
$existing_configuration = array();
|
||||
// Gather information about all the supported collections.
|
||||
$collection_info = $this->configManager->getConfigCollectionInfo();
|
||||
|
||||
foreach ($collection_info->getCollectionNames() as $collection) {
|
||||
$config_to_create = array_keys($this->getConfigToCreate($storage, $collection));
|
||||
$active_storage = $this->getActiveStorages($collection);
|
||||
foreach ($config_to_create as $config_name) {
|
||||
if ($active_storage->exists($config_name)) {
|
||||
$existing_configuration[$collection][] = $config_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $existing_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkConfigurationToInstall($type, $name) {
|
||||
if ($this->isSyncing()) {
|
||||
// Configuration is assumed to already be checked by the config importer
|
||||
// validation events.
|
||||
return;
|
||||
}
|
||||
$config_install_path = $this->getDefaultConfigDirectory($type, $name);
|
||||
if (!is_dir($config_install_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$storage = new FileStorage($config_install_path, StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
$enabled_extensions = $this->getEnabledExtensions();
|
||||
// Add the extension that will be enabled to the list of enabled extensions.
|
||||
$enabled_extensions[] = $name;
|
||||
// Gets a profile storage to search for overrides if necessary.
|
||||
$profile_storage = $this->getProfileStorage($name);
|
||||
|
||||
// Check the dependencies of configuration provided by the module.
|
||||
$invalid_default_config = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions, $profile_storage);
|
||||
if (!empty($invalid_default_config)) {
|
||||
throw UnmetDependenciesException::create($name, $invalid_default_config);
|
||||
}
|
||||
|
||||
// Install profiles can not have config clashes. Configuration that
|
||||
// has the same name as a module's configuration will be used instead.
|
||||
if ($name != $this->drupalGetProfile()) {
|
||||
// Throw an exception if the module being installed contains configuration
|
||||
// that already exists. Additionally, can not continue installing more
|
||||
// modules because those may depend on the current module being installed.
|
||||
$existing_configuration = $this->findPreExistingConfiguration($storage);
|
||||
if (!empty($existing_configuration)) {
|
||||
throw PreExistingConfigException::create($name, $existing_configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds default configuration with unmet dependencies.
|
||||
*
|
||||
* @param array $enabled_extensions
|
||||
* A list of all the currently enabled modules and themes.
|
||||
*
|
||||
* @return array
|
||||
* List of configuration that has unmet dependencies
|
||||
*/
|
||||
protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, StorageInterface $profile_storage = NULL) {
|
||||
$config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION, '', $profile_storage);
|
||||
$all_config = array_merge($this->configFactory->listAll(), array_keys($config_to_create));
|
||||
return array_filter(array_keys($config_to_create), function($config_name) use ($enabled_extensions, $all_config, $config_to_create) {
|
||||
return !$this->validateDependencies($config_name, $config_to_create[$config_name], $enabled_extensions, $all_config);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an array of config data that contains dependency information.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The name of the configuration object that is being validated.
|
||||
* @param array $data
|
||||
* Configuration data.
|
||||
* @param array $enabled_extensions
|
||||
* A list of all the currently enabled modules and themes.
|
||||
* @param array $all_config
|
||||
* A list of all the active configuration names.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the dependencies are met, FALSE if not.
|
||||
*/
|
||||
protected function validateDependencies($config_name, array $data, array $enabled_extensions, array $all_config) {
|
||||
if (isset($data['dependencies'])) {
|
||||
$all_dependencies = $data['dependencies'];
|
||||
|
||||
// Ensure enforced dependencies are included.
|
||||
if (isset($all_dependencies['enforced'])) {
|
||||
$all_dependencies = array_merge($all_dependencies, $data['dependencies']['enforced']);
|
||||
unset($all_dependencies['enforced']);
|
||||
}
|
||||
// Ensure the configuration entity type provider is in the list of
|
||||
// dependencies.
|
||||
list($provider) = explode('.', $config_name, 2);
|
||||
if (!isset($all_dependencies['module'])) {
|
||||
$all_dependencies['module'][] = $provider;
|
||||
}
|
||||
elseif (!in_array($provider, $all_dependencies['module'])) {
|
||||
$all_dependencies['module'][] = $provider;
|
||||
}
|
||||
|
||||
foreach ($all_dependencies as $type => $dependencies) {
|
||||
$list_to_check = [];
|
||||
switch ($type) {
|
||||
case 'module':
|
||||
case 'theme':
|
||||
$list_to_check = $enabled_extensions;
|
||||
break;
|
||||
case 'config':
|
||||
$list_to_check = $all_config;
|
||||
break;
|
||||
}
|
||||
if (!empty($list_to_check)) {
|
||||
$missing = array_diff($dependencies, $list_to_check);
|
||||
if (!empty($missing)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of enabled extensions including both modules and themes.
|
||||
*
|
||||
* @return array
|
||||
* A list of enabled extensions which includes both modules and themes.
|
||||
*/
|
||||
protected function getEnabledExtensions() {
|
||||
// Read enabled extensions directly from configuration to avoid circular
|
||||
// dependencies on ModuleHandler and ThemeHandler.
|
||||
$extension_config = $this->configFactory->get('core.extension');
|
||||
$enabled_extensions = (array) $extension_config->get('module');
|
||||
$enabled_extensions += (array) $extension_config->get('theme');
|
||||
// Core can provide configuration.
|
||||
$enabled_extensions['core'] = 'core';
|
||||
return array_keys($enabled_extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the profile storage to use to check for profile overrides.
|
||||
*
|
||||
* @param string $installing_name
|
||||
* (optional) The name of the extension currently being installed.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface|null
|
||||
* A storage to access configuration from the installation profile. If a
|
||||
* Drupal installation is not in progress or we're installing the profile
|
||||
* itself, then it will return NULL as the profile storage should not be
|
||||
* used.
|
||||
*/
|
||||
protected function getProfileStorage($installing_name = '') {
|
||||
$profile = $this->drupalGetProfile();
|
||||
if ($this->drupalInstallationAttempted() && $profile != $installing_name) {
|
||||
// Profiles should not contain optional configuration so always use the
|
||||
// install directory.
|
||||
$profile_install_path = $this->getDefaultConfigDirectory('module', $profile);
|
||||
$profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
|
||||
}
|
||||
else {
|
||||
$profile_storage = NULL;
|
||||
}
|
||||
return $profile_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an extension's default configuration directory.
|
||||
*
|
||||
* @param string $type
|
||||
* Type of extension to install.
|
||||
* @param string $name
|
||||
* Name of extension to install.
|
||||
*
|
||||
* @return string
|
||||
* The extension's default configuration directory.
|
||||
*/
|
||||
protected function getDefaultConfigDirectory($type, $name) {
|
||||
return $this->drupalGetPath($type, $name) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for drupal_get_path().
|
||||
*
|
||||
* @param $type
|
||||
* The type of the item; one of 'core', 'profile', 'module', 'theme', or
|
||||
* 'theme_engine'.
|
||||
* @param $name
|
||||
* The name of the item for which the path is requested. Ignored for
|
||||
* $type 'core'.
|
||||
*
|
||||
* @return string
|
||||
* The path to the requested item or an empty string if the item is not
|
||||
* found.
|
||||
*/
|
||||
protected function drupalGetPath($type, $name) {
|
||||
return drupal_get_path($type, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the install profile from settings.
|
||||
*
|
||||
* @return string|null $profile
|
||||
* The name of the installation profile or NULL if no installation profile
|
||||
* is currently active. This is the case for example during the first steps
|
||||
* of the installer or during unit tests.
|
||||
*/
|
||||
protected function drupalGetProfile() {
|
||||
// Settings is safe to use because settings.php is written before any module
|
||||
// is installed.
|
||||
return Settings::get('install_profile');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for drupal_installation_attempted().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if a Drupal installation is currently being attempted.
|
||||
*/
|
||||
protected function drupalInstallationAttempted() {
|
||||
return drupal_installation_attempted();
|
||||
}
|
||||
|
||||
}
|
113
core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
Normal file
113
core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigInstallerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Interface for classes that install config.
|
||||
*/
|
||||
interface ConfigInstallerInterface {
|
||||
|
||||
/**
|
||||
* Installs the default configuration of a given extension.
|
||||
*
|
||||
* When an extension is installed, it searches all the default configuration
|
||||
* directories for all other extensions to locate any configuration with its
|
||||
* name prefix. For example, the Node module provides the frontpage view as a
|
||||
* default configuration file:
|
||||
* core/modules/node/config/install/views.view.frontpage.yml
|
||||
* When the Views module is installed after the Node module is already
|
||||
* enabled, the frontpage view will be installed.
|
||||
*
|
||||
* Additionally, the default configuration directory for the extension being
|
||||
* installed is searched to discover if it contains default configuration
|
||||
* that is owned by other enabled extensions. So, the frontpage view will also
|
||||
* be installed when the Node module is installed after Views.
|
||||
*
|
||||
* @param string $type
|
||||
* The extension type; e.g., 'module' or 'theme'.
|
||||
* @param string $name
|
||||
* The name of the module or theme to install default configuration for.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ExtensionInstallStorage
|
||||
*/
|
||||
public function installDefaultConfig($type, $name);
|
||||
|
||||
/**
|
||||
* Installs optional configuration.
|
||||
*
|
||||
* Optional configuration is only installed if:
|
||||
* - the configuration does not exist already.
|
||||
* - it's a configuration entity.
|
||||
* - its dependencies can be met.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface
|
||||
* (optional) The configuration storage to search for optional
|
||||
* configuration. If not provided, all enabled extension's optional
|
||||
* configuration directories will be searched.
|
||||
* @param array $dependency
|
||||
* (optional) If set, ensures that the configuration being installed has
|
||||
* this dependency. The format is dependency type as the key ('module',
|
||||
* 'theme', or 'config') and the dependency name as the value
|
||||
* ('book', 'bartik', 'views.view.frontpage').
|
||||
*/
|
||||
public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = []);
|
||||
|
||||
/**
|
||||
* Installs all default configuration in the specified collection.
|
||||
*
|
||||
* The function is useful if the site needs to respond to an event that has
|
||||
* just created another collection and we need to check all the installed
|
||||
* extensions for any matching configuration. For example, if a language has
|
||||
* just been created.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
*/
|
||||
public function installCollectionDefaultConfig($collection);
|
||||
|
||||
/**
|
||||
* Sets the configuration storage that provides the default configuration.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSourceStorage(StorageInterface $storage);
|
||||
|
||||
/**
|
||||
* Sets the status of the isSyncing flag.
|
||||
*
|
||||
* @param bool $status
|
||||
* The status of the sync flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSyncing($status);
|
||||
|
||||
/**
|
||||
* Gets the syncing state.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE is syncing flag set.
|
||||
*/
|
||||
public function isSyncing();
|
||||
|
||||
/**
|
||||
* Checks the configuration that will be installed for an extension.
|
||||
*
|
||||
* @param string $type
|
||||
* Type of extension to install.
|
||||
* @param string $name
|
||||
* Name of extension to install.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\UnmetDependenciesException
|
||||
* @throws \Drupal\Core\Config\PreExistingConfigException
|
||||
*/
|
||||
public function checkConfigurationToInstall($type, $name);
|
||||
|
||||
}
|
472
core/lib/Drupal/Core/Config/ConfigManager.php
Normal file
472
core/lib/Drupal/Core/Config/ConfigManager.php
Normal file
|
@ -0,0 +1,472 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Diff\Diff;
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
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\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 === array('false')) {
|
||||
// Added file.
|
||||
$source_data = array($this->t('File added'));
|
||||
}
|
||||
if ($target_data === array('false')) {
|
||||
// Deleted file.
|
||||
$target_data = array($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 = array();
|
||||
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 = array();
|
||||
$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 = array();
|
||||
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;
|
||||
$update_uuids = [];
|
||||
|
||||
$return = [
|
||||
'update' => [],
|
||||
'delete' => [],
|
||||
'unchanged' => [],
|
||||
];
|
||||
|
||||
// Try to fix any dependencies and find out what will happen to the
|
||||
// dependency graph.
|
||||
foreach ($dependents as $dependent) {
|
||||
/** @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;
|
||||
}
|
||||
if ($this->callOnDependencyRemoval($dependent, $original_dependencies, $type, $names)) {
|
||||
// Recalculate dependencies and update the dependency graph data.
|
||||
$dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->calculateDependencies());
|
||||
// Based on the updated data rebuild the list of dependents.
|
||||
$dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
// 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 $entity) {
|
||||
if ($entity->uuid() == $dependent->uuid()) {
|
||||
$fixed = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($fixed) {
|
||||
$return['update'][] = $dependent;
|
||||
$update_uuids[] = $dependent->uuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now that we've fixed all the possible dependencies the remaining need to
|
||||
// be deleted. Reverse the deletes so that entities are removed in the
|
||||
// correct order of dependence. For example, this ensures that fields are
|
||||
// removed before field storages.
|
||||
$return['delete'] = array_reverse($dependents);
|
||||
$delete_uuids = array_map(function($dependent) {
|
||||
return $dependent->uuid();
|
||||
}, $return['delete']);
|
||||
// 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, $update_uuids) {
|
||||
return !(in_array($dependent->uuid(), $delete_uuids) || in_array($dependent->uuid(), $update_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 = array(
|
||||
'config' => array(),
|
||||
'content' => array(),
|
||||
'module' => array(),
|
||||
'theme' => array(),
|
||||
);
|
||||
|
||||
// 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 = array();
|
||||
$missing_dependencies = array();
|
||||
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']);
|
||||
}
|
||||
}
|
||||
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] = array(
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'uuid' => $uuid,
|
||||
);
|
||||
}
|
||||
}
|
||||
return $missing_dependencies;
|
||||
}
|
||||
|
||||
}
|
182
core/lib/Drupal/Core/Config/ConfigManagerInterface.php
Normal file
182
core/lib/Drupal/Core/Config/ConfigManagerInterface.php
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigManagerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Provides an interface for configuration manager.
|
||||
*/
|
||||
interface ConfigManagerInterface {
|
||||
|
||||
/**
|
||||
* Returns the entity type of a configuration object.
|
||||
*
|
||||
* @param string $name
|
||||
* The configuration object name.
|
||||
*
|
||||
* @return string|null
|
||||
* Either the entity type name, or NULL if none match.
|
||||
*/
|
||||
public function getEntityTypeIdByName($name);
|
||||
|
||||
/**
|
||||
* Loads a configuration entity using the configuration name.
|
||||
*
|
||||
* @param string $name
|
||||
* The configuration object name.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The configuration entity or NULL if it does not exist.
|
||||
*/
|
||||
public function loadConfigEntityByName($name);
|
||||
|
||||
/**
|
||||
* Gets the entity manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityManagerInterface
|
||||
* The entity manager.
|
||||
*/
|
||||
public function getEntityManager();
|
||||
|
||||
/**
|
||||
* Gets the config factory.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigFactoryInterface
|
||||
* The entity manager.
|
||||
*/
|
||||
public function getConfigFactory();
|
||||
|
||||
/**
|
||||
* Creates a Diff object using the config data from the two storages.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The storage to diff configuration from.
|
||||
* @param \Drupal\Core\Config\StorageInterface $target_storage
|
||||
* The storage to diff configuration to.
|
||||
* @param string $source_name
|
||||
* The name of the configuration object in the source storage to diff.
|
||||
* @param string $target_name
|
||||
* (optional) The name of the configuration object in the target storage.
|
||||
* If omitted, the source name is used.
|
||||
* @param string $collection
|
||||
* (optional) The configuration collection name. Defaults to the default
|
||||
* collection.
|
||||
*
|
||||
* @return \Drupal\Component\Diff\Diff
|
||||
* A Diff object using the config data from the two storages.
|
||||
*
|
||||
* @todo Make renderer injectable
|
||||
*
|
||||
* @see \Drupal\Core\Diff\DiffFormatter
|
||||
*/
|
||||
public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL, $collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Creates a configuration snapshot following a successful import.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The storage to synchronize configuration from.
|
||||
* @param \Drupal\Core\Config\StorageInterface $snapshot_storage
|
||||
* The storage to synchronize configuration to.
|
||||
*/
|
||||
public function createSnapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage);
|
||||
|
||||
/**
|
||||
* Uninstalls the configuration of a given extension.
|
||||
*
|
||||
* @param string $type
|
||||
* The extension type; e.g., 'module' or 'theme'.
|
||||
* @param string $name
|
||||
* The name of the module or theme to install configuration for.
|
||||
*/
|
||||
public function uninstall($type, $name);
|
||||
|
||||
/**
|
||||
* Creates and populates a ConfigDependencyManager object.
|
||||
*
|
||||
* The configuration dependency manager is populated with data from the active
|
||||
* store.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*/
|
||||
public function getConfigDependencyManager();
|
||||
|
||||
/**
|
||||
* Finds config entities that are dependent on extensions or 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 \Drupal\Core\Config\Entity\ConfigEntityDependency[]
|
||||
* An array of configuration entity dependency objects.
|
||||
*/
|
||||
public function findConfigEntityDependents($type, array $names);
|
||||
|
||||
/**
|
||||
* Finds config entities that are dependent on extensions or 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 \Drupal\Core\Config\Entity\ConfigEntityInterface[]
|
||||
* An array of dependencies as configuration entities.
|
||||
*/
|
||||
public function findConfigEntityDependentsAsEntities($type, array $names);
|
||||
|
||||
/**
|
||||
* Lists which config entities to update and delete on removal of a dependency.
|
||||
*
|
||||
* @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.
|
||||
* @param bool $dry_run
|
||||
* If set to FALSE the entities returned in the list of updates will be
|
||||
* modified. In order to make the changes the caller needs to save them. If
|
||||
* set to TRUE the entities returned will not be modified.
|
||||
*
|
||||
* @return array
|
||||
* An array with the keys: 'update', 'delete' and 'unchanged'. The value of
|
||||
* each is a list of configuration entities that need to have that action
|
||||
* applied when the supplied dependencies are removed. Updates need to be
|
||||
* processed before deletes. The order of the deletes is significant and
|
||||
* must be processed in the returned order.
|
||||
*/
|
||||
public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE);
|
||||
|
||||
/**
|
||||
* Gets available collection information using the event system.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigCollectionInfo
|
||||
* The object which contains information about the available collections.
|
||||
*/
|
||||
public function getConfigCollectionInfo();
|
||||
|
||||
/**
|
||||
* Finds missing content dependencies declared in configuration entities.
|
||||
*
|
||||
* @return array
|
||||
* A list of missing content dependencies. The array is keyed by UUID. Each
|
||||
* value is an array with the following keys: 'entity_type', 'bundle' and
|
||||
* 'uuid'.
|
||||
*/
|
||||
public function findMissingContentDependencies();
|
||||
|
||||
}
|
108
core/lib/Drupal/Core/Config/ConfigModuleOverridesEvent.php
Normal file
108
core/lib/Drupal/Core/Config/ConfigModuleOverridesEvent.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigModuleOverridesEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event object to allow configuration to be overridden by modules.
|
||||
*/
|
||||
class ConfigModuleOverridesEvent extends Event {
|
||||
|
||||
/**
|
||||
* Configuration names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $names;
|
||||
|
||||
/**
|
||||
* Configuration overrides.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $overrides;
|
||||
|
||||
/**
|
||||
* The Language object used to override configuration data.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageInterface
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* Constructs a configuration overrides event object.
|
||||
*
|
||||
* @param array $names
|
||||
* A list of configuration names.
|
||||
* @param \Drupal\Core\Language\LanguageInterface
|
||||
* (optional) The language for this configuration.
|
||||
*/
|
||||
public function __construct(array $names, LanguageInterface $language = NULL) {
|
||||
$this->names = $names;
|
||||
$this->language = $language;
|
||||
$this->overrides = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets configuration names.
|
||||
*
|
||||
* @return array
|
||||
* The list of configuration names that can be overridden.
|
||||
*/
|
||||
public function getNames() {
|
||||
return $this->names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets configuration language.
|
||||
*
|
||||
* @return \Drupal\Core\Language\LanguageInterface
|
||||
* The configuration language object.
|
||||
*/
|
||||
public function getLanguage() {
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration overrides.
|
||||
*
|
||||
* @return array.
|
||||
* The array of configuration overrides.
|
||||
*/
|
||||
public function getOverrides() {
|
||||
return $this->overrides;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a configuration override for the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* The configuration object name to override.
|
||||
* @param array $values
|
||||
* The values in the configuration object to override.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOverride($name, array $values) {
|
||||
if (in_array($name, $this->names)) {
|
||||
if (isset($this->overrides[$name])) {
|
||||
// Existing overrides take precedence since these will have been added
|
||||
// by events with a higher priority.
|
||||
$this->overrides[$name] = NestedArray::mergeDeepArray(array($values, $this->overrides[$name]), TRUE);
|
||||
}
|
||||
else {
|
||||
$this->overrides[$name] = $values;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
13
core/lib/Drupal/Core/Config/ConfigNameException.php
Normal file
13
core/lib/Drupal/Core/Config/ConfigNameException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigNameException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception thrown when a config object name is invalid.
|
||||
*/
|
||||
class ConfigNameException extends ConfigException {}
|
13
core/lib/Drupal/Core/Config/ConfigPrefixLengthException.php
Normal file
13
core/lib/Drupal/Core/Config/ConfigPrefixLengthException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigPrefixLengthException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception thrown when the config prefix length is exceeded.
|
||||
*/
|
||||
class ConfigPrefixLengthException extends ConfigException {}
|
45
core/lib/Drupal/Core/Config/ConfigRenameEvent.php
Normal file
45
core/lib/Drupal/Core/Config/ConfigRenameEvent.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigRenameEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Configuration event fired when renaming a configuration object.
|
||||
*/
|
||||
class ConfigRenameEvent extends ConfigCrudEvent {
|
||||
|
||||
/**
|
||||
* The old configuration object name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $oldName;
|
||||
|
||||
/**
|
||||
* Constructs the config rename event.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* The configuration that has been renamed.
|
||||
* @param string $old_name
|
||||
* The old configuration object name.
|
||||
*/
|
||||
public function __construct(Config $config, $old_name) {
|
||||
$this->config = $config;
|
||||
$this->oldName = $old_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the old configuration object name.
|
||||
*
|
||||
* @return string
|
||||
* The old configuration object name.
|
||||
*/
|
||||
public function getOldName() {
|
||||
return $this->oldName;
|
||||
}
|
||||
|
||||
}
|
13
core/lib/Drupal/Core/Config/ConfigValueException.php
Normal file
13
core/lib/Drupal/Core/Config/ConfigValueException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigValueException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception thrown when config object values are invalid.
|
||||
*/
|
||||
class ConfigValueException extends ConfigException {}
|
334
core/lib/Drupal/Core/Config/DatabaseStorage.php
Normal file
334
core/lib/Drupal/Core/Config/DatabaseStorage.php
Normal file
|
@ -0,0 +1,334 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\DatabaseStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\SchemaObjectExistsException;
|
||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* Defines the Database storage.
|
||||
*/
|
||||
class DatabaseStorage implements StorageInterface {
|
||||
use DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The database table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* Additional database connection options to use in queries.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = array();
|
||||
|
||||
/**
|
||||
* The storage collection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $collection = StorageInterface::DEFAULT_COLLECTION;
|
||||
|
||||
/**
|
||||
* Constructs a new DatabaseStorage.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* A Database connection to use for reading and writing configuration data.
|
||||
* @param string $table
|
||||
* A database table name to store configuration data in.
|
||||
* @param array $options
|
||||
* (optional) Any additional database connection options to use in queries.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct(Connection $connection, $table, array $options = array(), $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->connection = $connection;
|
||||
$this->table = $table;
|
||||
$this->options = $options;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::exists().
|
||||
*/
|
||||
public function exists($name) {
|
||||
try {
|
||||
return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name = :name', 0, 1, array(
|
||||
':collection' => $this->collection,
|
||||
':name' => $name,
|
||||
), $this->options)->fetchField();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// If we attempt a read without actually having the database or the table
|
||||
// available, just return FALSE so the caller can handle it.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($name) {
|
||||
$data = FALSE;
|
||||
try {
|
||||
$raw = $this->connection->query('SELECT data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name = :name', array(':collection' => $this->collection, ':name' => $name), $this->options)->fetchField();
|
||||
if ($raw !== FALSE) {
|
||||
$data = $this->decode($raw);
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// If we attempt a read without actually having the database or the table
|
||||
// available, just return FALSE so the caller can handle it.
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function readMultiple(array $names) {
|
||||
$list = array();
|
||||
try {
|
||||
$list = $this->connection->query('SELECT name, data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name IN ( :names[] )', array(':collection' => $this->collection, ':names[]' => $names), $this->options)->fetchAllKeyed();
|
||||
foreach ($list as &$data) {
|
||||
$data = $this->decode($data);
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// If we attempt a read without actually having the database or the table
|
||||
// available, just return an empty array so the caller can handle it.
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($name, array $data) {
|
||||
$data = $this->encode($data);
|
||||
try {
|
||||
return $this->doWrite($name, $data);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// If there was an exception, try to create the table.
|
||||
if ($this->ensureTableExists()) {
|
||||
return $this->doWrite($name, $data);
|
||||
}
|
||||
// Some other failure that we can not recover from.
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method so we can re-try a write.
|
||||
*
|
||||
* @param string $name
|
||||
* The config name.
|
||||
* @param string $data
|
||||
* The config data, already dumped to a string.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function doWrite($name, $data) {
|
||||
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
|
||||
return (bool) $this->connection->merge($this->table, $options)
|
||||
->keys(array('collection', 'name'), array($this->collection, $name))
|
||||
->fields(array('data' => $data))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the config table exists and create it if not.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the table was created, FALSE otherwise.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\StorageException
|
||||
* If a database error occurs.
|
||||
*/
|
||||
protected function ensureTableExists() {
|
||||
try {
|
||||
if (!$this->connection->schema()->tableExists($this->table)) {
|
||||
$this->connection->schema()->createTable($this->table, static::schemaDefinition());
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
// If another process has already created the config table, attempting to
|
||||
// recreate it will throw an exception. In this case just catch the
|
||||
// exception and do nothing.
|
||||
catch (SchemaObjectExistsException $e) {
|
||||
return TRUE;
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
throw new StorageException($e->getMessage(), NULL, $e);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the schema for the configuration table.
|
||||
*/
|
||||
protected static function schemaDefinition() {
|
||||
$schema = array(
|
||||
'description' => 'The base table for configuration data.',
|
||||
'fields' => array(
|
||||
'collection' => array(
|
||||
'description' => 'Primary Key: Config object collection.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'name' => array(
|
||||
'description' => 'Primary Key: Config object name.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'data' => array(
|
||||
'description' => 'A serialized configuration object data.',
|
||||
'type' => 'blob',
|
||||
'not null' => FALSE,
|
||||
'size' => 'big',
|
||||
),
|
||||
),
|
||||
'primary key' => array('collection', 'name'),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::delete().
|
||||
*
|
||||
* @throws PDOException
|
||||
*
|
||||
* @todo Ignore replica targets for data manipulation operations.
|
||||
*/
|
||||
public function delete($name) {
|
||||
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
|
||||
return (bool) $this->connection->delete($this->table, $options)
|
||||
->condition('collection', $this->collection)
|
||||
->condition('name', $name)
|
||||
->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::rename().
|
||||
*
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function rename($name, $new_name) {
|
||||
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
|
||||
return (bool) $this->connection->update($this->table, $options)
|
||||
->fields(array('name' => $new_name))
|
||||
->condition('name', $name)
|
||||
->condition('collection', $this->collection)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::encode().
|
||||
*/
|
||||
public function encode($data) {
|
||||
return serialize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::decode().
|
||||
*
|
||||
* @throws ErrorException
|
||||
* unserialize() triggers E_NOTICE if the string cannot be unserialized.
|
||||
*/
|
||||
public function decode($raw) {
|
||||
$data = @unserialize($raw);
|
||||
return is_array($data) ? $data : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
try {
|
||||
$query = $this->connection->select($this->table);
|
||||
$query->fields($this->table, array('name'));
|
||||
$query->condition('collection', $this->collection, '=');
|
||||
$query->condition('name', $prefix . '%', 'LIKE');
|
||||
$query->orderBy('collection')->orderBy('name');
|
||||
return $query->execute()->fetchCol();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteAll($prefix = '') {
|
||||
try {
|
||||
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
|
||||
return (bool) $this->connection->delete($this->table, $options)
|
||||
->condition('name', $prefix . '%', 'LIKE')
|
||||
->condition('collection', $this->collection)
|
||||
->execute();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->connection,
|
||||
$this->table,
|
||||
$this->options,
|
||||
$collection
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
try {
|
||||
return $this->connection->query('SELECT DISTINCT collection FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection <> :collection ORDER by collection', array(
|
||||
':collection' => StorageInterface::DEFAULT_COLLECTION)
|
||||
)->fetchCol();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigDependencyDeleteFormTrait.
|
||||
*/
|
||||
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Config\ConfigManagerInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Lists affected configuration entities by a dependency removal.
|
||||
*
|
||||
* This trait relies on the StringTranslationTrait.
|
||||
*/
|
||||
trait ConfigDependencyDeleteFormTrait {
|
||||
|
||||
/**
|
||||
* Translates a string to the current language or to a given language.
|
||||
*
|
||||
* Provided by \Drupal\Core\StringTranslation\StringTranslationTrait.
|
||||
*/
|
||||
abstract protected function t($string, array $args = array(), array $options = array());
|
||||
|
||||
/**
|
||||
* Adds form elements to list affected configuration entities.
|
||||
*
|
||||
* @param array $form
|
||||
* The form array to add elements to.
|
||||
* @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.
|
||||
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
||||
* The config manager.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigManagerInterface::getConfigEntitiesToChangeOnDependencyRemoval()
|
||||
*/
|
||||
protected function addDependencyListsToForm(array &$form, $type, array $names, ConfigManagerInterface $config_manager, EntityManagerInterface $entity_manager) {
|
||||
// Get the dependent entities.
|
||||
$dependent_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval($type, $names);
|
||||
$entity_types = array();
|
||||
|
||||
$form['entity_updates'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Configuration updates'),
|
||||
'#description' => $this->t('The listed configuration will be updated.'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#access' => FALSE,
|
||||
);
|
||||
|
||||
foreach ($dependent_entities['update'] as $entity) {
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
if (!isset($form['entity_updates'][$entity_type_id])) {
|
||||
$entity_type = $entity_manager->getDefinition($entity_type_id);
|
||||
// Store the ID and label to sort the entity types and entities later.
|
||||
$label = $entity_type->getLabel();
|
||||
$entity_types[$entity_type_id] = $label;
|
||||
$form['entity_updates'][$entity_type_id] = array(
|
||||
'#theme' => 'item_list',
|
||||
'#title' => $label,
|
||||
'#items' => array(),
|
||||
);
|
||||
}
|
||||
$form['entity_updates'][$entity_type_id]['#items'][] = $entity->label() ?: $entity->id();
|
||||
}
|
||||
if (!empty($dependent_entities['update'])) {
|
||||
$form['entity_updates']['#access'] = TRUE;
|
||||
|
||||
// Add a weight key to the entity type sections.
|
||||
asort($entity_types, SORT_FLAG_CASE);
|
||||
$weight = 0;
|
||||
foreach ($entity_types as $entity_type_id => $label) {
|
||||
$form['entity_updates'][$entity_type_id]['#weight'] = $weight;
|
||||
// Sort the list of entity labels alphabetically.
|
||||
sort($form['entity_updates'][$entity_type_id]['#items'], SORT_FLAG_CASE);
|
||||
$weight++;
|
||||
}
|
||||
}
|
||||
|
||||
$form['entity_deletes'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Configuration deletions'),
|
||||
'#description' => $this->t('The listed configuration will be deleted.'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#access' => FALSE,
|
||||
);
|
||||
|
||||
foreach ($dependent_entities['delete'] as $entity) {
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
if (!isset($form['entity_deletes'][$entity_type_id])) {
|
||||
$entity_type = $entity_manager->getDefinition($entity_type_id);
|
||||
// Store the ID and label to sort the entity types and entities later.
|
||||
$label = $entity_type->getLabel();
|
||||
$entity_types[$entity_type_id] = $label;
|
||||
$form['entity_deletes'][$entity_type_id] = array(
|
||||
'#theme' => 'item_list',
|
||||
'#title' => $label,
|
||||
'#items' => array(),
|
||||
);
|
||||
}
|
||||
$form['entity_deletes'][$entity_type_id]['#items'][] = $entity->label() ?: $entity->id();
|
||||
}
|
||||
if (!empty($dependent_entities['delete'])) {
|
||||
$form['entity_deletes']['#access'] = TRUE;
|
||||
|
||||
// Add a weight key to the entity type sections.
|
||||
asort($entity_types, SORT_FLAG_CASE);
|
||||
$weight = 0;
|
||||
foreach ($entity_types as $entity_type_id => $label) {
|
||||
$form['entity_deletes'][$entity_type_id]['#weight'] = $weight;
|
||||
// Sort the list of entity labels alphabetically.
|
||||
sort($form['entity_deletes'][$entity_type_id]['#items'], SORT_FLAG_CASE);
|
||||
$weight++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
322
core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php
Normal file
322
core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php
Normal file
|
@ -0,0 +1,322 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigDependencyManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use Drupal\Component\Utility\SortArray;
|
||||
|
||||
/**
|
||||
* Provides a class to discover configuration entity dependencies.
|
||||
*
|
||||
* Configuration entities can depend on modules, themes and other configuration
|
||||
* entities. The dependency system is used during configuration installation,
|
||||
* uninstallation, and synchronization to ensure that configuration entities are
|
||||
* handled in the correct order. For example, node types are created before
|
||||
* their fields, and both are created before the view display configuration.
|
||||
*
|
||||
* The configuration dependency value is structured like this:
|
||||
* @code
|
||||
* array(
|
||||
* 'config => array(
|
||||
* // An array of configuration entity object names. Recalculated on save.
|
||||
* ),
|
||||
* 'content => array(
|
||||
* // An array of content entity configuration dependency names. The default
|
||||
* // format is "ENTITY_TYPE_ID:BUNDLE:UUID". Recalculated on save.
|
||||
* ),
|
||||
* 'module' => array(
|
||||
* // An array of module names. Recalculated on save.
|
||||
* ),
|
||||
* 'theme' => array(
|
||||
* // An array of theme names. Recalculated on save.
|
||||
* ),
|
||||
* 'enforced' => array(
|
||||
* // An array of configuration dependencies that the config entity is
|
||||
* // ensured to have regardless of the details of the configuration. These
|
||||
* // dependencies are not recalculated on save.
|
||||
* 'config' => array(),
|
||||
* 'content' => array(),
|
||||
* 'module' => array(),
|
||||
* 'theme' => array(),
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* Configuration entity dependencies are recalculated on save based on the
|
||||
* current values of the configuration. For example, a filter format will depend
|
||||
* on the modules that provide the filter plugins it configures. The filter
|
||||
* format can be reconfigured to use a different filter plugin provided by
|
||||
* another module. If this occurs, the dependencies will be recalculated on save
|
||||
* and the old module will be removed from the list of dependencies and replaced
|
||||
* with the new one.
|
||||
*
|
||||
* Configuration entity classes usually extend
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityBase. The base class provides a
|
||||
* generic implementation of the calculateDependencies() method that can
|
||||
* discover dependencies due to enforced dependencies, plugins, and third party
|
||||
* settings. If the configuration entity has dependencies that cannot be
|
||||
* discovered by the base class's implementation, then it needs to implement
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() to
|
||||
* calculate (and return) the dependencies. In this method, use
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency() to add
|
||||
* dependencies. Implementations should call the base class implementation to
|
||||
* inherit the generic functionality.
|
||||
*
|
||||
* Classes for configurable plugins are a special case. They can either declare
|
||||
* their configuration dependencies using the calculateDependencies() method
|
||||
* described in the paragraph above, or if they have only static dependencies,
|
||||
* these can be declared using the 'config_dependencies' annotation key.
|
||||
*
|
||||
* If an extension author wants a configuration entity to depend on something
|
||||
* that is not calculable then they can add these dependencies to the enforced
|
||||
* dependencies key. For example, the Forum module provides the forum node type
|
||||
* and in order for it to be deleted when the forum module is uninstalled it has
|
||||
* an enforced dependency on the module. The dependency on the Forum module can
|
||||
* not be calculated since there is nothing inherent in the state of the node
|
||||
* type configuration entity that depends on functionality provided by the Forum
|
||||
* module.
|
||||
*
|
||||
* Once declared properly, dependencies are saved to the configuration entity's
|
||||
* configuration object so that they can be checked without the module that
|
||||
* provides the configuration entity class being installed. This is important
|
||||
* for configuration synchronization, which needs to be able to validate
|
||||
* configuration in the staging directory before the synchronization has
|
||||
* occurred. Also, if you have a configuration entity object and you want to
|
||||
* get the current dependencies without recalculation, you can use
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies().
|
||||
*
|
||||
* When uninstalling a module or a theme, configuration entities that are
|
||||
* dependent will also be removed. This default behavior can lead to undesirable
|
||||
* side effects, such as a node view mode being entirely removed when the module
|
||||
* defining a field or formatter it uses is uninstalled. To prevent this,
|
||||
* configuration entity classes can implement
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval(),
|
||||
* which allows the entity class to remove dependencies instead of being deleted
|
||||
* themselves. Implementations should save the entity if dependencies have been
|
||||
* successfully removed, in order to register the newly cleaned-out dependency
|
||||
* list. So, for example, the node view mode configuration entity class
|
||||
* should implement this method to remove references to formatters if the plugin
|
||||
* that supplies them depends on a module that is being uninstalled.
|
||||
*
|
||||
* If a configuration entity is provided as default configuration by an
|
||||
* extension (module, theme, or profile), the extension has to depend on any
|
||||
* modules or themes that the configuration depends on. For example, if a view
|
||||
* configuration entity is provided by an installation profile and the view will
|
||||
* not work without a certain module, the profile must declare a dependency on
|
||||
* this module in its info.yml file. If you do not want your extension to always
|
||||
* depend on a particular module that one of its default configuration entities
|
||||
* depends on, you can use a sub-module: move the configuration entity to the
|
||||
* sub-module instead of including it in the main extension, and declare the
|
||||
* module dependency in the sub-module only.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency()
|
||||
* @see \Drupal\Core\Config\ConfigInstallerInterface::installDefaultConfig()
|
||||
* @see \Drupal\Core\Config\ConfigManagerInterface::uninstall()
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityDependency
|
||||
* @see \Drupal\Core\Entity\EntityInterface::getConfigDependencyName()
|
||||
* @see \Drupal\Core\Plugin\PluginDependencyTrait
|
||||
*/
|
||||
class ConfigDependencyManager {
|
||||
|
||||
/**
|
||||
* The config entity data.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Entity\ConfigEntityDependency[]
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* The directed acyclic graph.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $graph;
|
||||
|
||||
/**
|
||||
* Gets dependencies.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of dependency being checked. Either 'module', 'theme', 'config'
|
||||
* or 'content'.
|
||||
* @param string $name
|
||||
* The specific name to check. If $type equals 'module' or 'theme' then it
|
||||
* should be a module name or theme name. In the case of entity it should be
|
||||
* the full configuration object name.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
|
||||
* An array of config entity dependency objects that are dependent.
|
||||
*/
|
||||
public function getDependentEntities($type, $name) {
|
||||
$dependent_entities = array();
|
||||
|
||||
$entities_to_check = array();
|
||||
if ($type == 'config') {
|
||||
$entities_to_check[] = $name;
|
||||
}
|
||||
else {
|
||||
if ($type == 'module' || $type == 'theme' || $type == 'content') {
|
||||
$dependent_entities = array_filter($this->data, function (ConfigEntityDependency $entity) use ($type, $name) {
|
||||
return $entity->hasDependency($type, $name);
|
||||
});
|
||||
}
|
||||
// If checking content, module, or theme dependencies, discover which
|
||||
// entities are dependent on the entities that have a direct dependency.
|
||||
foreach ($dependent_entities as $entity) {
|
||||
$entities_to_check[] = $entity->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
$dependencies = array_merge($this->createGraphConfigEntityDependencies($entities_to_check), $dependent_entities);
|
||||
// Sort dependencies in the reverse order of the graph. So the least
|
||||
// dependent is at the top. For example, this ensures that fields are
|
||||
// always after field storages. This is because field storages need to be
|
||||
// created before a field.
|
||||
return array_reverse(array_intersect_key($this->graph, $dependencies));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the dependencies in order of most dependent last.
|
||||
*
|
||||
* @return array
|
||||
* The list of entities in order of most dependent last, otherwise
|
||||
* alphabetical.
|
||||
*/
|
||||
public function sortAll() {
|
||||
$graph = $this->getGraph();
|
||||
// Sort by reverse weight and alphabetically. The most dependent entities
|
||||
// are last and entities with the same weight are alphabetically ordered.
|
||||
uasort($graph, array($this, 'sortGraph'));
|
||||
return array_keys($graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the dependency graph by reverse weight and alphabetically.
|
||||
*
|
||||
* @param array $a
|
||||
* First item for comparison. The compared items should be associative
|
||||
* arrays that include a 'weight' and a 'component' key.
|
||||
* @param array $b
|
||||
* Second item for comparison.
|
||||
*
|
||||
* @return int
|
||||
* The comparison result for uasort().
|
||||
*/
|
||||
public function sortGraph(array $a, array $b) {
|
||||
$weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1;
|
||||
|
||||
if ($weight_cmp === 0) {
|
||||
return SortArray::sortByKeyString($a, $b, 'component');
|
||||
}
|
||||
return $weight_cmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a graph of config entity dependencies.
|
||||
*
|
||||
* @param array $entities_to_check
|
||||
* The configuration entity full configuration names to determine the
|
||||
* dependencies for.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
|
||||
* A graph of config entity dependency objects that are dependent on the
|
||||
* supplied entities to check.
|
||||
*/
|
||||
protected function createGraphConfigEntityDependencies($entities_to_check) {
|
||||
$dependent_entities = array();
|
||||
$graph = $this->getGraph();
|
||||
|
||||
foreach ($entities_to_check as $entity) {
|
||||
if (isset($graph[$entity]) && !empty($graph[$entity]['reverse_paths'])){
|
||||
foreach ($graph[$entity]['reverse_paths'] as $dependency => $value) {
|
||||
$dependent_entities[$dependency] = $this->data[$dependency];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $dependent_entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dependency graph of all the config entities.
|
||||
*
|
||||
* @return array
|
||||
* The dependency graph of all the config entities.
|
||||
*/
|
||||
protected function getGraph() {
|
||||
if (!isset($this->graph)) {
|
||||
$graph = array();
|
||||
foreach ($this->data as $entity) {
|
||||
$graph_key = $entity->getConfigDependencyName();
|
||||
$graph[$graph_key]['edges'] = array();
|
||||
$dependencies = $entity->getDependencies('config');
|
||||
if (!empty($dependencies)) {
|
||||
foreach ($dependencies as $dependency) {
|
||||
$graph[$graph_key]['edges'][$dependency] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
$graph_object = new Graph($graph);
|
||||
$this->graph = $graph_object->searchAndSort();
|
||||
}
|
||||
return $this->graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data to calculate dependencies for.
|
||||
*
|
||||
* The data is converted into lightweight ConfigEntityDependency objects.
|
||||
*
|
||||
* @param array $data
|
||||
* Configuration data keyed by configuration object name. Typically the
|
||||
* output of \Drupal\Core\Config\StorageInterface::loadMultiple().
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
array_walk($data, function (&$config, $name) {
|
||||
$config = new ConfigEntityDependency($name, $config);
|
||||
});
|
||||
$this->data = $data;
|
||||
$this->graph = NULL;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one of the lightweight ConfigEntityDependency objects.
|
||||
*
|
||||
* @param $name
|
||||
* The configuration dependency name.
|
||||
* @param array $dependencies
|
||||
* The configuration dependencies. The array is structured like this:
|
||||
* @code
|
||||
* array(
|
||||
* 'config => array(
|
||||
* // An array of configuration entity object names.
|
||||
* ),
|
||||
* 'content => array(
|
||||
* // An array of content entity configuration dependency names. The default
|
||||
* // format is "ENTITY_TYPE_ID:BUNDLE:UUID".
|
||||
* ),
|
||||
* 'module' => array(
|
||||
* // An array of module names.
|
||||
* ),
|
||||
* 'theme' => array(
|
||||
* // An array of theme names.
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function updateData($name, array $dependencies) {
|
||||
$this->graph = NULL;
|
||||
$this->data[$name] = new ConfigEntityDependency($name, ['dependencies' => $dependencies]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
608
core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
Normal file
608
core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
Normal file
|
@ -0,0 +1,608 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Config\ConfigException;
|
||||
use Drupal\Core\Config\Schema\SchemaIncompleteException;
|
||||
use Drupal\Core\Entity\Entity;
|
||||
use Drupal\Core\Config\ConfigDuplicateUUIDException;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Plugin\PluginDependencyTrait;
|
||||
|
||||
/**
|
||||
* Defines a base configuration entity class.
|
||||
*
|
||||
* @ingroup entity_api
|
||||
*/
|
||||
abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface {
|
||||
|
||||
use PluginDependencyTrait {
|
||||
addDependency as addDependencyTrait;
|
||||
}
|
||||
|
||||
/**
|
||||
* The original ID of the configuration entity.
|
||||
*
|
||||
* The ID of a configuration entity is a unique string (machine name). When a
|
||||
* configuration entity is updated and its machine name is renamed, the
|
||||
* original ID needs to be known.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $originalId;
|
||||
|
||||
/**
|
||||
* The name of the property that is used to store plugin configuration.
|
||||
*
|
||||
* This is needed when the entity uses a LazyPluginCollection, to dictate
|
||||
* where the plugin configuration should be stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pluginConfigKey;
|
||||
|
||||
/**
|
||||
* The enabled/disabled status of the configuration entity.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $status = TRUE;
|
||||
|
||||
/**
|
||||
* The UUID for this entity.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $uuid;
|
||||
|
||||
/**
|
||||
* Whether the config is being created, updated or deleted through the
|
||||
* import process.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isSyncing = FALSE;
|
||||
|
||||
/**
|
||||
* Whether the config is being deleted by the uninstall process.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isUninstalling = FALSE;
|
||||
|
||||
/**
|
||||
* The language code of the entity's default language.
|
||||
*
|
||||
* Assumed to be English by default. ConfigEntityStorage will set an
|
||||
* appropriate language when creating new entities. This default applies to
|
||||
* imported default configuration where the language code is missing. Those
|
||||
* should be assumed to be English. All configuration entities support third
|
||||
* party settings, so even configuration entities that do not directly
|
||||
* store settings involving text in a human language may have such third
|
||||
* party settings attached. This means configuration entities should be in one
|
||||
* of the configured languages or the built-in English.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $langcode = 'en';
|
||||
|
||||
/**
|
||||
* Third party entity settings.
|
||||
*
|
||||
* An array of key/value pairs keyed by provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $third_party_settings = array();
|
||||
|
||||
/**
|
||||
* Trust supplied data and not use configuration schema on save.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $trustedData = FALSE;
|
||||
|
||||
/**
|
||||
* Overrides Entity::__construct().
|
||||
*/
|
||||
public function __construct(array $values, $entity_type) {
|
||||
parent::__construct($values, $entity_type);
|
||||
|
||||
// Backup the original ID, if any.
|
||||
// Configuration entity IDs are strings, and '0' is a valid ID.
|
||||
$original_id = $this->id();
|
||||
if ($original_id !== NULL && $original_id !== '') {
|
||||
$this->setOriginalId($original_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOriginalId() {
|
||||
return $this->originalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOriginalId($id) {
|
||||
// Do not call the parent method since that would mark this entity as no
|
||||
// longer new. Unlike content entities, new configuration entities have an
|
||||
// ID.
|
||||
// @todo https://www.drupal.org/node/2478811 Document the entity life cycle
|
||||
// and the differences between config and content.
|
||||
$this->originalId = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Entity::isNew().
|
||||
*
|
||||
* EntityInterface::enforceIsNew() is only supported for newly created
|
||||
* configuration entities but has no effect after saving, since each
|
||||
* configuration entity is unique.
|
||||
*/
|
||||
public function isNew() {
|
||||
return !empty($this->enforceIsNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($property_name) {
|
||||
return isset($this->{$property_name}) ? $this->{$property_name} : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
if ($this instanceof EntityWithPluginCollectionInterface) {
|
||||
$plugin_collections = $this->getPluginCollections();
|
||||
if (isset($plugin_collections[$property_name])) {
|
||||
// If external code updates the settings, pass it along to the plugin.
|
||||
$plugin_collections[$property_name]->setConfiguration($value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->{$property_name} = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enable() {
|
||||
return $this->setStatus(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disable() {
|
||||
// An entity was disabled, invalidate its own cache tag.
|
||||
Cache::invalidateTags($this->getCacheTags());
|
||||
return $this->setStatus(FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStatus($status) {
|
||||
$this->status = (bool) $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function status() {
|
||||
return !empty($this->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSyncing($syncing) {
|
||||
$this->isSyncing = $syncing;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSyncing() {
|
||||
return $this->isSyncing;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUninstalling($uninstalling) {
|
||||
$this->isUninstalling = $uninstalling;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isUninstalling() {
|
||||
return $this->isUninstalling;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createDuplicate() {
|
||||
$duplicate = parent::createDuplicate();
|
||||
|
||||
// Prevent the new duplicate from being misinterpreted as a rename.
|
||||
$duplicate->setOriginalId(NULL);
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper callback for uasort() to sort configuration entities by weight and label.
|
||||
*/
|
||||
public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
|
||||
$a_weight = isset($a->weight) ? $a->weight : 0;
|
||||
$b_weight = isset($b->weight) ? $b->weight : 0;
|
||||
if ($a_weight == $b_weight) {
|
||||
$a_label = $a->label();
|
||||
$b_label = $b->label();
|
||||
return strnatcasecmp($a_label, $b_label);
|
||||
}
|
||||
return ($a_weight < $b_weight) ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toArray() {
|
||||
$properties = array();
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
|
||||
$entity_type = $this->getEntityType();
|
||||
|
||||
$properties_to_export = $entity_type->getPropertiesToExport();
|
||||
if (empty($properties_to_export)) {
|
||||
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
|
||||
$definition = $this->getTypedConfig()->getDefinition($config_name);
|
||||
if (!isset($definition['mapping'])) {
|
||||
throw new SchemaIncompleteException(SafeMarkup::format('Incomplete or missing schema for @config_name', array('@config_name' => $config_name)));
|
||||
}
|
||||
$properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
|
||||
}
|
||||
|
||||
$id_key = $entity_type->getKey('id');
|
||||
foreach ($properties_to_export as $property_name => $export_name) {
|
||||
// Special handling for IDs so that computed compound IDs work.
|
||||
// @see \Drupal\Core\Entity\EntityDisplayBase::id()
|
||||
if ($property_name == $id_key) {
|
||||
$properties[$export_name] = $this->id();
|
||||
}
|
||||
else {
|
||||
$properties[$export_name] = $this->get($property_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->third_party_settings)) {
|
||||
unset($properties['third_party_settings']);
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the typed config manager.
|
||||
*
|
||||
* @return \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected function getTypedConfig() {
|
||||
return \Drupal::service('config.typed');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
|
||||
if ($this instanceof EntityWithPluginCollectionInterface) {
|
||||
// Any changes to the plugin configuration must be saved to the entity's
|
||||
// copy as well.
|
||||
foreach ($this->getPluginCollections() as $plugin_config_key => $plugin_collection) {
|
||||
$this->set($plugin_config_key, $plugin_collection->getConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure this entity's UUID does not exist with a different ID, regardless
|
||||
// of whether it's new or updated.
|
||||
$matching_entities = $storage->getQuery()
|
||||
->condition('uuid', $this->uuid())
|
||||
->execute();
|
||||
$matched_entity = reset($matching_entities);
|
||||
if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
|
||||
throw new ConfigDuplicateUUIDException(SafeMarkup::format('Attempt to save a configuration entity %id with UUID %uuid when this UUID is already used for %matched', array('%id' => $this->id(), '%uuid' => $this->uuid(), '%matched' => $matched_entity)));
|
||||
}
|
||||
|
||||
// If this entity is not new, load the original entity for comparison.
|
||||
if (!$this->isNew()) {
|
||||
$original = $storage->loadUnchanged($this->getOriginalId());
|
||||
// Ensure that the UUID cannot be changed for an existing entity.
|
||||
if ($original && ($original->uuid() != $this->uuid())) {
|
||||
throw new ConfigDuplicateUUIDException(SafeMarkup::format('Attempt to save a configuration entity %id with UUID %uuid when this entity already exists with UUID %original_uuid', array('%id' => $this->id(), '%uuid' => $this->uuid(), '%original_uuid' => $original->uuid())));
|
||||
}
|
||||
}
|
||||
if (!$this->isSyncing() && !$this->trustedData) {
|
||||
// Ensure the correct dependencies are present. If the configuration is
|
||||
// being written during a configuration synchronization then there is no
|
||||
// need to recalculate the dependencies.
|
||||
$this->calculateDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
// Dependencies should be recalculated on every save. This ensures stale
|
||||
// dependencies are never saved.
|
||||
if (isset($this->dependencies['enforced'])) {
|
||||
$dependencies = $this->dependencies['enforced'];
|
||||
$this->dependencies = $dependencies;
|
||||
$this->dependencies['enforced'] = $dependencies;
|
||||
}
|
||||
else {
|
||||
$this->dependencies = array();
|
||||
}
|
||||
if ($this instanceof EntityWithPluginCollectionInterface) {
|
||||
// Configuration entities need to depend on the providers of any plugins
|
||||
// that they store the configuration for.
|
||||
foreach ($this->getPluginCollections() as $plugin_collection) {
|
||||
foreach ($plugin_collection as $instance) {
|
||||
$this->calculatePluginDependencies($instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this instanceof ThirdPartySettingsInterface) {
|
||||
// Configuration entities need to depend on the providers of any third
|
||||
// parties that they store the configuration for.
|
||||
foreach ($this->getThirdPartyProviders() as $provider) {
|
||||
$this->addDependency('module', $provider);
|
||||
}
|
||||
}
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function urlInfo($rel = 'edit-form', array $options = []) {
|
||||
return parent::urlInfo($rel, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function url($rel = 'edit-form', $options = array()) {
|
||||
return parent::url($rel, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function link($text = NULL, $rel = 'edit-form', array $options = []) {
|
||||
return parent::link($text, $rel, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
// Use cache tags that match the underlying config object's name.
|
||||
// @see \Drupal\Core\Config\ConfigBase::getCacheTags()
|
||||
return ['config:' . $this->getConfigDependencyName()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Entity\DependencyTrait:addDependency().
|
||||
*
|
||||
* Note that this function should only be called from implementations of
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies(),
|
||||
* as dependencies are recalculated during every entity save.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityDependency::hasDependency()
|
||||
*/
|
||||
protected function addDependency($type, $name) {
|
||||
// A config entity is always dependent on its provider. There is no need to
|
||||
// explicitly declare the dependency. An explicit dependency on Core, which
|
||||
// provides some plugins, is also not needed.
|
||||
if ($type == 'module' && ($name == $this->getEntityType()->getProvider() || $name == 'core')) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->addDependencyTrait($type, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDependencies() {
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigDependencyName() {
|
||||
return $this->getEntityType()->getConfigPrefix() . '.' . $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigTarget() {
|
||||
// For configuration entities, use the config ID for the config target
|
||||
// identifier. This ensures that default configuration (which does not yet
|
||||
// have UUIDs) can be provided and installed with references to the target,
|
||||
// and also makes config dependencies more readable.
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
$changed = FALSE;
|
||||
if (!empty($this->third_party_settings)) {
|
||||
$old_count = count($this->third_party_settings);
|
||||
$this->third_party_settings = array_diff_key($this->third_party_settings, array_flip($dependencies['module']));
|
||||
$changed = $old_count != count($this->third_party_settings);
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Override to never invalidate the entity's cache tag; the config system
|
||||
* already invalidates it.
|
||||
*/
|
||||
protected function invalidateTagsOnSave($update) {
|
||||
Cache::invalidateTags($this->getEntityType()->getListCacheTags());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Override to never invalidate the individual entities' cache tags; the
|
||||
* config system already invalidates them.
|
||||
*/
|
||||
protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
|
||||
Cache::invalidateTags($entity_type->getListCacheTags());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setThirdPartySetting($module, $key, $value) {
|
||||
$this->third_party_settings[$module][$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getThirdPartySetting($module, $key, $default = NULL) {
|
||||
if (isset($this->third_party_settings[$module][$key])) {
|
||||
return $this->third_party_settings[$module][$key];
|
||||
}
|
||||
else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getThirdPartySettings($module) {
|
||||
return isset($this->third_party_settings[$module]) ? $this->third_party_settings[$module] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unsetThirdPartySetting($module, $key) {
|
||||
unset($this->third_party_settings[$module][$key]);
|
||||
// If the third party is no longer storing any information, completely
|
||||
// remove the array holding the settings for this module.
|
||||
if (empty($this->third_party_settings[$module])) {
|
||||
unset($this->third_party_settings[$module]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getThirdPartyProviders() {
|
||||
return array_keys($this->third_party_settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::preDelete($storage, $entities);
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
if ($entity->isUninstalling() || $entity->isSyncing()) {
|
||||
// During extension uninstall and configuration synchronization
|
||||
// deletions are already managed.
|
||||
break;
|
||||
}
|
||||
// Fix or remove any dependencies.
|
||||
$config_entities = static::getConfigManager()->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity->getConfigDependencyName()], FALSE);
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent_entity */
|
||||
foreach ($config_entities['update'] as $dependent_entity) {
|
||||
$dependent_entity->save();
|
||||
}
|
||||
foreach ($config_entities['delete'] as $dependent_entity) {
|
||||
$dependent_entity->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration manager.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigManager
|
||||
* The configuration manager.
|
||||
*/
|
||||
protected static function getConfigManager() {
|
||||
return \Drupal::service('config.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInstallable() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trustData() {
|
||||
$this->trustedData = TRUE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasTrustedData() {
|
||||
return $this->trustedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save() {
|
||||
$return = parent::save();
|
||||
$this->trustedData = FALSE;
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
124
core/lib/Drupal/Core/Config/Entity/ConfigEntityBundleBase.php
Normal file
124
core/lib/Drupal/Core/Config/Entity/ConfigEntityBundleBase.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityBundleBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
|
||||
/**
|
||||
* A base class for config entity types that act as bundles.
|
||||
*
|
||||
* Entity types that want to use this base class must use bundle_of in their
|
||||
* annotation to specify for which entity type they are providing bundles for.
|
||||
*/
|
||||
abstract class ConfigEntityBundleBase extends ConfigEntityBase {
|
||||
|
||||
/**
|
||||
* Renames displays when a bundle is renamed.
|
||||
*/
|
||||
protected function renameDisplays() {
|
||||
// Rename entity displays.
|
||||
if ($this->getOriginalId() !== $this->id()) {
|
||||
foreach ($this->loadDisplays('entity_view_display') as $display) {
|
||||
$new_id = $this->getEntityType()->getBundleOf() . '.' . $this->id() . '.' . $display->getMode();
|
||||
$display->set('id', $new_id);
|
||||
$display->setTargetBundle($this->id());
|
||||
$display->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Rename entity form displays.
|
||||
if ($this->getOriginalId() !== $this->id()) {
|
||||
foreach ($this->loadDisplays('entity_form_display') as $form_display) {
|
||||
$new_id = $this->getEntityType()->getBundleOf() . '.' . $this->id() . '.' . $form_display->getMode();
|
||||
$form_display->set('id', $new_id);
|
||||
$form_display->setTargetBundle($this->id());
|
||||
$form_display->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes display if a bundle is deleted.
|
||||
*/
|
||||
protected function deleteDisplays() {
|
||||
// Remove entity displays of the deleted bundle.
|
||||
if ($displays = $this->loadDisplays('entity_view_display')) {
|
||||
$storage = $this->entityManager()->getStorage('entity_view_display');
|
||||
$storage->delete($displays);
|
||||
}
|
||||
|
||||
// Remove entity form displays of the deleted bundle.
|
||||
if ($displays = $this->loadDisplays('entity_form_display')) {
|
||||
$storage = $this->entityManager()->getStorage('entity_form_display');
|
||||
$storage->delete($displays);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
$entity_manager = $this->entityManager();
|
||||
$bundle_of = $this->getEntityType()->getBundleOf();
|
||||
if (!$update) {
|
||||
$entity_manager->onBundleCreate($this->id(), $bundle_of);
|
||||
}
|
||||
else {
|
||||
// Invalidate the render cache of entities for which this entity
|
||||
// is a bundle.
|
||||
if ($entity_manager->hasHandler($bundle_of, 'view_builder')) {
|
||||
$entity_manager->getViewBuilder($bundle_of)->resetCache();
|
||||
}
|
||||
// Entity bundle field definitions may depend on bundle settings.
|
||||
$entity_manager->clearCachedFieldDefinitions();
|
||||
|
||||
if ($this->getOriginalId() != $this->id()) {
|
||||
// If the entity was renamed, update the displays.
|
||||
$this->renameDisplays();
|
||||
$entity_manager->onBundleRename($this->getOriginalId(), $this->id(), $bundle_of);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$entity->deleteDisplays();
|
||||
\Drupal::entityManager()->onBundleDelete($entity->id(), $entity->getEntityType()->getBundleOf());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns view or form displays for this bundle.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID of the display type to load.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Display\EntityDisplayInterface[]
|
||||
* A list of matching displays.
|
||||
*/
|
||||
protected function loadDisplays($entity_type_id) {
|
||||
$ids = \Drupal::entityQuery($entity_type_id)
|
||||
->condition('id', $this->getEntityType()->getBundleOf() . '.' . $this->getOriginalId() . '.', 'STARTS_WITH')
|
||||
->execute();
|
||||
if ($ids) {
|
||||
$storage = $this->entityManager()->getStorage($entity_type_id);
|
||||
return $storage->loadMultiple($ids);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
103
core/lib/Drupal/Core/Config/Entity/ConfigEntityDependency.php
Normal file
103
core/lib/Drupal/Core/Config/Entity/ConfigEntityDependency.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityDependency.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
/**
|
||||
* Provides a value object to discover configuration dependencies.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*/
|
||||
class ConfigEntityDependency {
|
||||
|
||||
/**
|
||||
* The configuration entity's configuration object name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The configuration entity's dependencies.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dependencies;
|
||||
|
||||
/**
|
||||
* Constructs the configuration entity dependency from the entity values.
|
||||
*
|
||||
* @param string $name
|
||||
* The configuration entity's configuration object name.
|
||||
* @param array $values
|
||||
* (optional) The configuration entity's values.
|
||||
*/
|
||||
public function __construct($name, $values = array()) {
|
||||
$this->name = $name;
|
||||
if (isset($values['dependencies'])) {
|
||||
$this->dependencies = $values['dependencies'];
|
||||
}
|
||||
else {
|
||||
$this->dependencies = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration entity's dependencies of the supplied type.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of dependency to return. Either 'module', 'theme', 'config' or
|
||||
* 'content'.
|
||||
*
|
||||
* @return array
|
||||
* The list of dependencies of the supplied type.
|
||||
*/
|
||||
public function getDependencies($type) {
|
||||
$dependencies = array();
|
||||
if (isset($this->dependencies[$type])) {
|
||||
$dependencies = $this->dependencies[$type];
|
||||
}
|
||||
if ($type == 'module') {
|
||||
$dependencies[] = substr($this->name, 0, strpos($this->name, '.'));
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the entity is dependent on extensions or entities.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of dependency being checked. Either 'module', 'theme', 'config'
|
||||
* or 'content'.
|
||||
* @param string $name
|
||||
* The specific name to check. If $type equals 'module' or 'theme' then it
|
||||
* should be a module name or theme name. In the case of entity it should be
|
||||
* the full configuration object name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDependency($type, $name) {
|
||||
// A config entity is always dependent on its provider.
|
||||
if ($type == 'module' && strpos($this->name, $name . '.') === 0) {
|
||||
return TRUE;
|
||||
}
|
||||
return isset($this->dependencies[$type]) && array_search($name, $this->dependencies[$type]) !== FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration entity's configuration dependency name.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityInterface::getConfigDependencyName()
|
||||
*
|
||||
* @return string
|
||||
* The configuration dependency name for the entity.
|
||||
*/
|
||||
public function getConfigDependencyName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
}
|
229
core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php
Normal file
229
core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php
Normal file
|
@ -0,0 +1,229 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for configuration entities.
|
||||
*
|
||||
* @ingroup config_api
|
||||
* @ingroup entity_api
|
||||
*/
|
||||
interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInterface {
|
||||
|
||||
/**
|
||||
* Enables the configuration entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enable();
|
||||
|
||||
/**
|
||||
* Disables the configuration entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disable();
|
||||
|
||||
/**
|
||||
* Sets the status of the configuration entity.
|
||||
*
|
||||
* @param bool $status
|
||||
* The status of the configuration entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStatus($status);
|
||||
|
||||
/**
|
||||
* Sets the status of the isSyncing flag.
|
||||
*
|
||||
* @param bool $status
|
||||
* The status of the sync flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSyncing($status);
|
||||
|
||||
/**
|
||||
* Returns whether the configuration entity is enabled.
|
||||
*
|
||||
* Status implementations for configuration entities should follow these
|
||||
* general rules:
|
||||
* - Status does not affect the loading of entities. I.e. Disabling
|
||||
* configuration entities should only have UI/access implications.
|
||||
* - It should only take effect when a 'status' key is explicitly declared
|
||||
* in the entity_keys info of a configuration entity's annotation data.
|
||||
* - Each entity implementation (entity/controller) is responsible for
|
||||
* checking and managing the status.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the entity is enabled or not.
|
||||
*/
|
||||
public function status();
|
||||
|
||||
/**
|
||||
* Returns whether this entity is being changed as part of an import process.
|
||||
*
|
||||
* If you are writing code that responds to a change in this entity (insert,
|
||||
* update, delete, presave, etc.), and your code would result in a
|
||||
* configuration change (whether related to this configuration entity, another
|
||||
* configuration entity, or non-entity configuration) or your code would
|
||||
* result in a change to this entity itself, you need to check and see if this
|
||||
* entity change is part of an import process, and skip executing your code if
|
||||
* that is the case.
|
||||
*
|
||||
* For example, \Drupal\node\Entity\NodeType::postSave() adds the default body
|
||||
* field to newly created node type configuration entities, which is a
|
||||
* configuration change. You would not want this code to run during an import,
|
||||
* because imported entities were already given the body field when they were
|
||||
* originally created, and the imported configuration includes all of their
|
||||
* currently-configured fields. On the other hand,
|
||||
* \Drupal\field\Entity\FieldStorageConfig::preSave() and the methods it calls
|
||||
* make sure that the storage tables are created or updated for the field
|
||||
* storage configuration entity, which is not a configuration change, and it
|
||||
* must be done whether due to an import or not. So, the first method should
|
||||
* check $entity->isSyncing() and skip executing if it returns TRUE, and the
|
||||
* second should not perform this check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the configuration entity is being created, updated, or deleted
|
||||
* through the import process.
|
||||
*/
|
||||
public function isSyncing();
|
||||
|
||||
/**
|
||||
* Returns whether this entity is being changed during the uninstall process.
|
||||
*
|
||||
* If you are writing code that responds to a change in this entity (insert,
|
||||
* update, delete, presave, etc.), and your code would result in a
|
||||
* configuration change (whether related to this configuration entity, another
|
||||
* configuration entity, or non-entity configuration) or your code would
|
||||
* result in a change to this entity itself, you need to check and see if this
|
||||
* entity change is part of an uninstall process, and skip executing your code
|
||||
* if that is the case.
|
||||
*
|
||||
* For example, \Drupal\language\Entity\ConfigurableLanguage::preDelete()
|
||||
* prevents the API from deleting the default language. However during an
|
||||
* uninstall of the language module it is expected that the default language
|
||||
* should be deleted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUninstalling();
|
||||
|
||||
/**
|
||||
* Returns the value of a property.
|
||||
*
|
||||
* @param string $property_name
|
||||
* The name of the property that should be returned.
|
||||
*
|
||||
* @return mixed
|
||||
* The property if it exists, or NULL otherwise.
|
||||
*/
|
||||
public function get($property_name);
|
||||
|
||||
/**
|
||||
* Sets the value of a property.
|
||||
*
|
||||
* @param string $property_name
|
||||
* The name of the property that should be set.
|
||||
* @param mixed $value
|
||||
* The value the property should be set to.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set($property_name, $value);
|
||||
|
||||
/**
|
||||
* Calculates dependencies and stores them in the dependency property.
|
||||
*
|
||||
* @return array
|
||||
* An array of dependencies grouped by type (module, theme, entity).
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*/
|
||||
public function calculateDependencies();
|
||||
|
||||
/**
|
||||
* Informs the entity that entities it depends on will be deleted.
|
||||
*
|
||||
* This method allows configuration entities to remove dependencies instead
|
||||
* of being deleted themselves. Configuration entities can use this method to
|
||||
* avoid being unnecessarily deleted during an extension uninstallation.
|
||||
* For example, entity displays remove references to widgets and formatters if
|
||||
* the plugin that supplies them depends on a module that is being
|
||||
* uninstalled.
|
||||
*
|
||||
* If this method returns TRUE then the entity needs to be re-saved by the
|
||||
* caller for the changes to take effect. Implementations should not save the
|
||||
* entity.
|
||||
*
|
||||
* @param array $dependencies
|
||||
* An array of dependencies that will be deleted keyed by dependency type.
|
||||
* Dependency types are, for example, entity, module and theme.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity has changed, FALSE if not.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity has been changed as a result, FALSE if not.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
* @see \Drupal\Core\Config\ConfigEntityBase::preDelete()
|
||||
* @see \Drupal\Core\Config\ConfigManager::uninstall()
|
||||
* @see \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval()
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies);
|
||||
|
||||
/**
|
||||
* Gets the configuration dependencies.
|
||||
*
|
||||
* @return array
|
||||
* An array of dependencies, keyed by $type.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*/
|
||||
public function getDependencies();
|
||||
|
||||
/**
|
||||
* Checks whether this entity is installable.
|
||||
*
|
||||
* For example, a default view might not be installable if the base table
|
||||
* doesn't exist.
|
||||
*
|
||||
* @retun bool
|
||||
* TRUE if the entity is installable, FALSE otherwise.
|
||||
*/
|
||||
public function isInstallable();
|
||||
|
||||
/**
|
||||
* Sets that the data should be trusted.
|
||||
*
|
||||
* If the data is trusted then dependencies will not be calculated on save and
|
||||
* schema will not be used to cast the values. Generally this is only used
|
||||
* during module and theme installation. Once the config entity has been saved
|
||||
* the data will no longer be marked as trusted. This is an optimization for
|
||||
* creation of configuration during installation.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
|
||||
*/
|
||||
public function trustData();
|
||||
|
||||
/**
|
||||
* Gets whether on not the data is trusted.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the configuration data is trusted, FALSE if not.
|
||||
*/
|
||||
public function hasTrustedData();
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityListBuilder.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityListBuilder;
|
||||
|
||||
/**
|
||||
* Defines the default class to build a listing of configuration entities.
|
||||
*
|
||||
* @ingroup entity_api
|
||||
*/
|
||||
class ConfigEntityListBuilder extends EntityListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load() {
|
||||
$entity_ids = $this->getEntityIds();
|
||||
$entities = $this->storage->loadMultipleOverrideFree($entity_ids);
|
||||
|
||||
// Sort the entities using the entity class's sort() method.
|
||||
// See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
|
||||
uasort($entities, array($this->entityType->getClass(), 'sort'));
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
|
||||
if ($this->entityType->hasKey('status')) {
|
||||
if (!$entity->status() && $entity->hasLinkTemplate('enable')) {
|
||||
$operations['enable'] = array(
|
||||
'title' => t('Enable'),
|
||||
'weight' => -10,
|
||||
'url' => $entity->urlInfo('enable'),
|
||||
);
|
||||
}
|
||||
elseif ($entity->hasLinkTemplate('disable')) {
|
||||
$operations['disable'] = array(
|
||||
'title' => t('Disable'),
|
||||
'weight' => 40,
|
||||
'url' => $entity->urlInfo('disable'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
}
|
458
core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
Normal file
458
core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
Normal file
|
@ -0,0 +1,458 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\ConfigImporterException;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityMalformedException;
|
||||
use Drupal\Core\Entity\EntityStorageBase;
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines the storage class for configuration entities.
|
||||
*
|
||||
* Configuration object names of configuration entities are comprised of two
|
||||
* parts, separated by a dot:
|
||||
* - config_prefix: A string denoting the owner (module/extension) of the
|
||||
* configuration object, followed by arbitrary other namespace identifiers
|
||||
* that are declared by the owning extension; e.g., 'node.type'. The
|
||||
* config_prefix does NOT contain a trailing dot. It is defined by the entity
|
||||
* type's annotation.
|
||||
* - ID: A string denoting the entity ID within the entity type namespace; e.g.,
|
||||
* 'article'. Entity IDs may contain dots/periods. The entire remaining string
|
||||
* after the config_prefix in a config name forms the entity ID. Additional or
|
||||
* custom suffixes are not possible.
|
||||
*
|
||||
* @ingroup entity_api
|
||||
*/
|
||||
class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStorageInterface, ImportableEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Length limit of the configuration entity ID.
|
||||
*
|
||||
* Most file systems limit a file name's length to 255 characters, so
|
||||
* ConfigBase::MAX_NAME_LENGTH restricts the full configuration object name
|
||||
* to 250 characters (leaving 5 for the file extension). The config prefix
|
||||
* is limited by ConfigEntityType::PREFIX_LENGTH to 83 characters, so this
|
||||
* leaves 166 remaining characters for the configuration entity ID, with 1
|
||||
* additional character needed for the joining dot.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigBase::MAX_NAME_LENGTH
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityType::PREFIX_LENGTH
|
||||
*/
|
||||
const MAX_ID_LENGTH = 166;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $uuidKey = 'uuid';
|
||||
|
||||
/**
|
||||
* The config factory service.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The config storage service.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Static cache of entities, keyed first by entity ID, then by an extra key.
|
||||
*
|
||||
* The additional cache key is to maintain separate caches for different
|
||||
* states of config overrides.
|
||||
*
|
||||
* @var array
|
||||
* @see \Drupal\Core\Config\ConfigFactoryInterface::getCacheKeys().
|
||||
*/
|
||||
protected $entities = array();
|
||||
|
||||
/**
|
||||
* Determines if the underlying configuration is retrieved override free.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $overrideFree = FALSE;
|
||||
|
||||
/**
|
||||
* Constructs a ConfigEntityStorage object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory service.
|
||||
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
|
||||
* The UUID service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
|
||||
parent::__construct($entity_type);
|
||||
|
||||
$this->configFactory = $config_factory;
|
||||
$this->uuidService = $uuid_service;
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('config.factory'),
|
||||
$container->get('uuid'),
|
||||
$container->get('language_manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadRevision($revision_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Entity\EntityStorageInterface::deleteRevision().
|
||||
*/
|
||||
public function deleteRevision($revision_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix used to create the configuration name.
|
||||
*
|
||||
* The prefix consists of the config prefix from the entity type plus a dot
|
||||
* for separating from the ID.
|
||||
*
|
||||
* @return string
|
||||
* The full configuration prefix, for example 'views.view.'.
|
||||
*/
|
||||
protected function getPrefix() {
|
||||
return $this->entityType->getConfigPrefix() . '.';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getIDFromConfigName($config_name, $config_prefix) {
|
||||
return substr($config_name, strlen($config_prefix . '.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doLoadMultiple(array $ids = NULL) {
|
||||
$prefix = $this->getPrefix();
|
||||
|
||||
// Get the names of the configuration entities we are going to load.
|
||||
if ($ids === NULL) {
|
||||
$names = $this->configFactory->listAll($prefix);
|
||||
}
|
||||
else {
|
||||
$names = array();
|
||||
foreach ($ids as $id) {
|
||||
// Add the prefix to the ID to serve as the configuration object name.
|
||||
$names[] = $prefix . $id;
|
||||
}
|
||||
}
|
||||
|
||||
// Load all of the configuration entities.
|
||||
$records = array();
|
||||
foreach ($this->configFactory->loadMultiple($names) as $config) {
|
||||
$records[$config->get($this->idKey)] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
|
||||
}
|
||||
return $this->mapFromStorageRecords($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doCreate(array $values) {
|
||||
// Set default language to current language if not provided.
|
||||
$values += array($this->langcodeKey => $this->languageManager->getCurrentLanguage()->getId());
|
||||
$entity = new $this->entityClass($values, $this->entityTypeId);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete($entities) {
|
||||
foreach ($entities as $entity) {
|
||||
$this->configFactory->getEditable($this->getPrefix() . $entity->id())->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Entity\EntityStorageInterface::save().
|
||||
*
|
||||
* @throws EntityMalformedException
|
||||
* When attempting to save a configuration entity that has no ID.
|
||||
*/
|
||||
public function save(EntityInterface $entity) {
|
||||
// Configuration entity IDs are strings, and '0' is a valid ID.
|
||||
$id = $entity->id();
|
||||
if ($id === NULL || $id === '') {
|
||||
throw new EntityMalformedException('The entity does not have an ID.');
|
||||
}
|
||||
|
||||
// Check the configuration entity ID length.
|
||||
// @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH
|
||||
// @todo Consider moving this to a protected method on the parent class, and
|
||||
// abstracting it for all entity types.
|
||||
if (strlen($entity->get($this->idKey)) > self::MAX_ID_LENGTH) {
|
||||
throw new ConfigEntityIdLengthException(SafeMarkup::format('Configuration entity ID @id exceeds maximum allowed length of @length characters.', array(
|
||||
'@id' => $entity->get($this->idKey),
|
||||
'@length' => self::MAX_ID_LENGTH,
|
||||
)));
|
||||
}
|
||||
|
||||
return parent::save($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave($id, EntityInterface $entity) {
|
||||
$is_new = $entity->isNew();
|
||||
$prefix = $this->getPrefix();
|
||||
$config_name = $prefix . $entity->id();
|
||||
if ($id !== $entity->id()) {
|
||||
// Renaming a config object needs to cater for:
|
||||
// - Storage needs to access the original object.
|
||||
// - The object needs to be renamed/copied in ConfigFactory and reloaded.
|
||||
// - All instances of the object need to be renamed.
|
||||
$this->configFactory->rename($prefix . $id, $config_name);
|
||||
}
|
||||
$config = $this->configFactory->getEditable($config_name);
|
||||
|
||||
// Retrieve the desired properties and set them in config.
|
||||
$config->setData($this->mapToStorageRecord($entity));
|
||||
$config->save($entity->hasTrustedData());
|
||||
|
||||
// Update the entity with the values stored in configuration. It is possible
|
||||
// that configuration schema has casted some of the values.
|
||||
if (!$entity->hasTrustedData()) {
|
||||
$data = $this->mapFromStorageRecords(array($config->get()));
|
||||
$updated_entity = current($data);
|
||||
|
||||
foreach (array_keys($config->get()) as $property) {
|
||||
$value = $updated_entity->get($property);
|
||||
$entity->set($property, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $is_new ? SAVED_NEW : SAVED_UPDATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps from an entity object to the storage record.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @return array
|
||||
* The record to store.
|
||||
*/
|
||||
protected function mapToStorageRecord(EntityInterface $entity) {
|
||||
return $entity->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function has($id, EntityInterface $entity) {
|
||||
$prefix = $this->getPrefix();
|
||||
$config = $this->configFactory->get($prefix . $id);
|
||||
return !$config->isNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the static cache.
|
||||
*
|
||||
* @param array $ids
|
||||
* If not empty, return entities that match these IDs.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface[]
|
||||
* Array of entities from the entity cache.
|
||||
*/
|
||||
protected function getFromStaticCache(array $ids) {
|
||||
$entities = array();
|
||||
// Load any available entities from the internal cache.
|
||||
if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
|
||||
$config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
|
||||
foreach ($ids as $id) {
|
||||
if (!empty($this->entities[$id])) {
|
||||
if (isset($this->entities[$id][$config_overrides_key])) {
|
||||
$entities[$id] = $this->entities[$id][$config_overrides_key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the static entity cache.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface[] $entities
|
||||
* Entities to store in the cache.
|
||||
*/
|
||||
protected function setStaticCache(array $entities) {
|
||||
if ($this->entityType->isStaticallyCacheable()) {
|
||||
$config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->entities[$id][$config_overrides_key] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a hook on behalf of the entity.
|
||||
*
|
||||
* @param $hook
|
||||
* One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
*/
|
||||
protected function invokeHook($hook, EntityInterface $entity) {
|
||||
// Invoke the hook.
|
||||
$this->moduleHandler->invokeAll($this->entityTypeId . '_' . $hook, array($entity));
|
||||
// Invoke the respective entity-level hook.
|
||||
$this->moduleHandler->invokeAll('entity_' . $hook, array($entity, $this->entityTypeId));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getQueryServiceName() {
|
||||
return 'entity.query.config';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function importCreate($name, Config $new_config, Config $old_config) {
|
||||
$entity = $this->createFromStorageRecord($new_config->get());
|
||||
$entity->setSyncing(TRUE);
|
||||
$entity->save();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function importUpdate($name, Config $new_config, Config $old_config) {
|
||||
$id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
|
||||
$entity = $this->load($id);
|
||||
if (!$entity) {
|
||||
throw new ConfigImporterException(SafeMarkup::format('Attempt to update non-existing entity "@id".', array('@id' => $id)));
|
||||
}
|
||||
$entity->setSyncing(TRUE);
|
||||
$entity = $this->updateFromStorageRecord($entity, $new_config->get());
|
||||
$entity->save();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function importDelete($name, Config $new_config, Config $old_config) {
|
||||
$id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
|
||||
$entity = $this->load($id);
|
||||
$entity->setSyncing(TRUE);
|
||||
$entity->delete();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function importRename($old_name, Config $new_config, Config $old_config) {
|
||||
return $this->importUpdate($old_name, $new_config, $old_config);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createFromStorageRecord(array $values) {
|
||||
// Assign a new UUID if there is none yet.
|
||||
if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
|
||||
$values[$this->uuidKey] = $this->uuidService->generate();
|
||||
}
|
||||
$data = $this->mapFromStorageRecords(array($values));
|
||||
$entity = current($data);
|
||||
$entity->original = clone $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->postCreate($this);
|
||||
|
||||
// Modules might need to add or change the data initially held by the new
|
||||
// entity object, for instance to fill-in default values.
|
||||
$this->invokeHook('create', $entity);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) {
|
||||
$entity->original = clone $entity;
|
||||
|
||||
$data = $this->mapFromStorageRecords(array($values));
|
||||
$updated_entity = current($data);
|
||||
|
||||
foreach (array_keys($values) as $property) {
|
||||
$value = $updated_entity->get($property);
|
||||
$entity->set($property, $value);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadOverrideFree($id) {
|
||||
$entities = $this->loadMultipleOverrideFree([$id]);
|
||||
return isset($entities[$id]) ? $entities[$id] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMultipleOverrideFree(array $ids = NULL) {
|
||||
$this->overrideFree = TRUE;
|
||||
$entities = $this->loadMultiple($ids);
|
||||
$this->overrideFree = FALSE;
|
||||
return $entities;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for configuration entity storage.
|
||||
*/
|
||||
interface ConfigEntityStorageInterface extends EntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Extracts the configuration entity ID from the full configuration name.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The full configuration name to extract the ID from. E.g.
|
||||
* 'views.view.archive'.
|
||||
* @param string $config_prefix
|
||||
* The config prefix of the configuration entity. E.g. 'views.view'
|
||||
*
|
||||
* @return string
|
||||
* The ID of the configuration entity.
|
||||
*/
|
||||
public static function getIDFromConfigName($config_name, $config_prefix);
|
||||
|
||||
/**
|
||||
* Creates a configuration entity from storage values.
|
||||
*
|
||||
* Allows the configuration entity storage to massage storage values before
|
||||
* creating an entity.
|
||||
*
|
||||
* @param array $values
|
||||
* The array of values from the configuration storage.
|
||||
*
|
||||
* @return ConfigEntityInterface
|
||||
* The configuration entity.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityStorageBase::mapFromStorageRecords()
|
||||
* @see \Drupal\field\FieldStorageConfigStorage::mapFromStorageRecords()
|
||||
*/
|
||||
public function createFromStorageRecord(array $values);
|
||||
|
||||
/**
|
||||
* Updates a configuration entity from storage values.
|
||||
*
|
||||
* Allows the configuration entity storage to massage storage values before
|
||||
* updating an entity.
|
||||
*
|
||||
* @param ConfigEntityInterface $entity
|
||||
* The configuration entity to update.
|
||||
* @param array $values
|
||||
* The array of values from the configuration storage.
|
||||
*
|
||||
* @return ConfigEntityInterface
|
||||
* The configuration entity.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityStorageBase::mapFromStorageRecords()
|
||||
* @see \Drupal\field\FieldStorageConfigStorage::mapFromStorageRecords()
|
||||
*/
|
||||
public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values);
|
||||
|
||||
/**
|
||||
* Loads one entity in their original form without overrides.
|
||||
*
|
||||
* @param mixed $id
|
||||
* The ID of the entity to load.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* An entity object. NULL if no matching entity is found.
|
||||
*/
|
||||
public function loadOverrideFree($id);
|
||||
|
||||
/**
|
||||
* Loads one or more entities in their original form without overrides.
|
||||
*
|
||||
* @param $ids
|
||||
* An array of entity IDs, or NULL to load all entities.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface[]
|
||||
* An array of entity objects indexed by their IDs. Returns an empty array
|
||||
* if no matching entities are found.
|
||||
*/
|
||||
public function loadMultipleOverrideFree(array $ids = NULL);
|
||||
|
||||
}
|
200
core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
Normal file
200
core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityType.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException;
|
||||
use Drupal\Core\Entity\EntityType;
|
||||
use Drupal\Core\Config\ConfigPrefixLengthException;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Provides an implementation of a configuration entity type and its metadata.
|
||||
*/
|
||||
class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
|
||||
|
||||
/**
|
||||
* The config prefix set in the configuration entity type annotation.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityTypeInterface::getConfigPrefix()
|
||||
*/
|
||||
protected $config_prefix;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $static_cache = FALSE;
|
||||
|
||||
/**
|
||||
* Keys that are stored key value store for fast lookup.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $lookup_keys = [];
|
||||
|
||||
/**
|
||||
* The list of configuration entity properties to export from the annotation.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $config_export = [];
|
||||
|
||||
/**
|
||||
* The result of merging config_export annotation with the defaults.
|
||||
*
|
||||
* This is stored on the class so that it does not have to be recalculated.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mergedConfigExport = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
|
||||
* Exception thrown when the provided class is not an instance of
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityStorage.
|
||||
*/
|
||||
public function __construct($definition) {
|
||||
// Ensure a default list cache tag is set; do this before calling the parent
|
||||
// constructor, because we want "Configuration System style" cache tags.
|
||||
if (empty($this->list_cache_tags)) {
|
||||
$this->list_cache_tags = ['config:' . $definition['id'] . '_list'];
|
||||
}
|
||||
|
||||
parent::__construct($definition);
|
||||
// Always add a default 'uuid' key.
|
||||
$this->entity_keys['uuid'] = 'uuid';
|
||||
$this->entity_keys['langcode'] = 'langcode';
|
||||
if (isset($this->handlers['storage'])) {
|
||||
$this->checkStorageClass($this->handlers['storage']);
|
||||
}
|
||||
$this->handlers += array(
|
||||
'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage',
|
||||
);
|
||||
$this->lookup_keys[] = 'uuid';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigPrefix() {
|
||||
// Ensure that all configuration entities are prefixed by the name of the
|
||||
// module that provides the configuration entity type.
|
||||
if (isset($this->config_prefix)) {
|
||||
$config_prefix = $this->provider . '.' . $this->config_prefix;
|
||||
}
|
||||
else {
|
||||
$config_prefix = $this->provider . '.' . $this->id();
|
||||
}
|
||||
|
||||
if (strlen($config_prefix) > static::PREFIX_LENGTH) {
|
||||
throw new ConfigPrefixLengthException(SafeMarkup::format('The configuration file name prefix @config_prefix exceeds the maximum character limit of @max_char.', array(
|
||||
'@config_prefix' => $config_prefix,
|
||||
'@max_char' => static::PREFIX_LENGTH,
|
||||
)));
|
||||
}
|
||||
return $config_prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBaseTable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRevisionDataTable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRevisionTable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDataTable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigDependencyKey() {
|
||||
return 'config';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
|
||||
* Exception thrown when the provided class is not an instance of
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityStorage.
|
||||
*/
|
||||
public function setStorageClass($class) {
|
||||
$this->checkStorageClass($class);
|
||||
parent::setStorageClass($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the provided class is an instance of ConfigEntityStorage.
|
||||
*
|
||||
* @param string $class
|
||||
* The class to check.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
|
||||
*/
|
||||
protected function checkStorageClass($class) {
|
||||
if (!is_a($class, 'Drupal\Core\Config\Entity\ConfigEntityStorage', TRUE)) {
|
||||
throw new ConfigEntityStorageClassException(SafeMarkup::format('@class is not \Drupal\Core\Config\Entity\ConfigEntityStorage or it does not extend it', ['@class' => $class]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPropertiesToExport() {
|
||||
if (!empty($this->config_export)) {
|
||||
if (empty($this->mergedConfigExport)) {
|
||||
// Always add default properties to be exported.
|
||||
$this->mergedConfigExport = [
|
||||
'uuid' => 'uuid',
|
||||
'langcode' => 'langcode',
|
||||
'status' => 'status',
|
||||
'dependencies' => 'dependencies',
|
||||
'third_party_settings' => 'third_party_settings',
|
||||
];
|
||||
foreach ($this->config_export as $property => $name) {
|
||||
if (is_numeric($property)) {
|
||||
$this->mergedConfigExport[$name] = $name;
|
||||
}
|
||||
else {
|
||||
$this->mergedConfigExport[$property] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->mergedConfigExport;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLookupKeys() {
|
||||
return $this->lookup_keys;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ConfigEntityTypeInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for a configuration entity type and its metadata.
|
||||
*/
|
||||
interface ConfigEntityTypeInterface extends EntityTypeInterface {
|
||||
|
||||
/**
|
||||
* Length limit of the configuration entity prefix.
|
||||
*
|
||||
* Configuration entity names are composed of two parts:
|
||||
* - The config prefix, which is returned by getConfigPrefix() and is
|
||||
* composed of:
|
||||
* - The provider module name (limited to 50 characters by
|
||||
* DRUPAL_EXTENSION_NAME_MAX_LENGTH).
|
||||
* - The module-specific namespace identifier, which defaults to the
|
||||
* configuration entity type ID. Entity type IDs are limited to 32
|
||||
* characters by EntityTypeInterface::ID_MAX_LENGTH.
|
||||
* - The configuration entity ID.
|
||||
* So, a typical configuration entity filename will look something like:
|
||||
* provider_module_name.namespace_identifier.config_entity_id.yml
|
||||
*
|
||||
* Most file systems limit a file name's length to 255 characters, so
|
||||
* ConfigBase::MAX_NAME_LENGTH restricts the full configuration object name
|
||||
* to 250 characters (leaving 5 for the file extension). Therefore, in
|
||||
* order to leave sufficient characters to construct a configuration ID,
|
||||
* the configuration entity prefix is limited to 83 characters: up to 50
|
||||
* characters for the module name, 1 for the dot, and 32 for the namespace
|
||||
* identifier. This also allows modules with shorter names to define longer
|
||||
* namespace identifiers if desired.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigBase::MAX_NAME_LENGTH
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityTypeInterface::getConfigPrefix()
|
||||
* @see DRUPAL_EXTENSION_NAME_MAX_LENGTH
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH
|
||||
* @see \Drupal\Core\Entity\EntityTypeInterface::ID_MAX_LENGTH
|
||||
*/
|
||||
const PREFIX_LENGTH = 83;
|
||||
|
||||
/**
|
||||
* Gets the config prefix used by the configuration entity type.
|
||||
*
|
||||
* The config prefix is used to prefix configuration entity IDs when they are
|
||||
* stored in the configuration system. The default config prefix is
|
||||
* constructed from the name of the module that provides the entity type and
|
||||
* the ID of the entity type. If a config_prefix annotation is present it will
|
||||
* be used in place of the entity type ID.
|
||||
*
|
||||
* Prefixing with the module that provides the configuration entity type
|
||||
* ensures that configuration entities depend on the module that provides the
|
||||
* configuration entity type.
|
||||
*
|
||||
* @return string
|
||||
* The config prefix.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigPrefixLengthException
|
||||
* Exception thrown when the length of the prefix exceeds PREFIX_LENGTH.
|
||||
*/
|
||||
public function getConfigPrefix();
|
||||
|
||||
/**
|
||||
* Gets the config entity properties to export if declared on the annotation.
|
||||
*
|
||||
* @return array|NULL
|
||||
* The properties to export or NULL if they can not be determine from the
|
||||
* config entity type annotation.
|
||||
*/
|
||||
public function getPropertiesToExport();
|
||||
|
||||
/**
|
||||
* Gets the keys that are available for fast lookup.
|
||||
*
|
||||
* @return string[]
|
||||
* The list of lookup keys.
|
||||
*/
|
||||
public function getLookupKeys();
|
||||
|
||||
}
|
183
core/lib/Drupal/Core/Config/Entity/DraggableListBuilder.php
Normal file
183
core/lib/Drupal/Core/Config/Entity/DraggableListBuilder.php
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\DraggableListBuilder.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a draggable listing of configuration entities.
|
||||
*/
|
||||
abstract class DraggableListBuilder extends ConfigEntityListBuilder implements FormInterface {
|
||||
|
||||
/**
|
||||
* The key to use for the form element containing the entities.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entitiesKey = 'entities';
|
||||
|
||||
/**
|
||||
* The entities being listed.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface[]
|
||||
*/
|
||||
protected $entities = array();
|
||||
|
||||
/**
|
||||
* Name of the entity's weight field or FALSE if no field is provided.
|
||||
*
|
||||
* @var string|bool
|
||||
*/
|
||||
protected $weightKey = FALSE;
|
||||
|
||||
/**
|
||||
* The form builder.
|
||||
*
|
||||
* @var \Drupal\Core\Form\FormBuilderInterface
|
||||
*/
|
||||
protected $formBuilder;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage) {
|
||||
parent::__construct($entity_type, $storage);
|
||||
|
||||
// Check if the entity type supports weighting.
|
||||
if ($this->entityType->hasKey('weight')) {
|
||||
$this->weightKey = $this->entityType->getKey('weight');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = array();
|
||||
if (!empty($this->weightKey)) {
|
||||
$header['weight'] = t('Weight');
|
||||
}
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row = array();
|
||||
if (!empty($this->weightKey)) {
|
||||
// Override default values to markup elements.
|
||||
$row['#attributes']['class'][] = 'draggable';
|
||||
$row['#weight'] = $entity->get($this->weightKey);
|
||||
// Add weight column.
|
||||
$row['weight'] = array(
|
||||
'#type' => 'weight',
|
||||
'#title' => t('Weight for @title', array('@title' => $entity->label())),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $entity->get($this->weightKey),
|
||||
'#attributes' => array('class' => array('weight')),
|
||||
);
|
||||
}
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
if (!empty($this->weightKey)) {
|
||||
return $this->formBuilder()->getForm($this);
|
||||
}
|
||||
return parent::render();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form[$this->entitiesKey] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => $this->buildHeader(),
|
||||
'#empty' => t('There is no @label yet.', array('@label' => $this->entityType->getLabel())),
|
||||
'#tabledrag' => array(
|
||||
array(
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'weight',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->entities = $this->load();
|
||||
$delta = 10;
|
||||
// Change the delta of the weight field if have more than 20 entities.
|
||||
if (!empty($this->weightKey)) {
|
||||
$count = count($this->entities);
|
||||
if ($count > 20) {
|
||||
$delta = ceil($count / 2);
|
||||
}
|
||||
}
|
||||
foreach ($this->entities as $entity) {
|
||||
$row = $this->buildRow($entity);
|
||||
if (isset($row['label'])) {
|
||||
$row['label'] = array('#markup' => $row['label']);
|
||||
}
|
||||
if (isset($row['weight'])) {
|
||||
$row['weight']['#delta'] = $delta;
|
||||
}
|
||||
$form[$this->entitiesKey][$entity->id()] = $row;
|
||||
}
|
||||
|
||||
$form['actions']['#type'] = 'actions';
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save order'),
|
||||
'#button_type' => 'primary',
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// No validation.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $id => $value) {
|
||||
if (isset($this->entities[$id]) && $this->entities[$id]->get($this->weightKey) != $value['weight']) {
|
||||
// Save entity only when its weight was changed.
|
||||
$this->entities[$id]->set($this->weightKey, $value['weight']);
|
||||
$this->entities[$id]->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form builder.
|
||||
*
|
||||
* @return \Drupal\Core\Form\FormBuilderInterface
|
||||
* The form builder.
|
||||
*/
|
||||
protected function formBuilder() {
|
||||
if (!$this->formBuilder) {
|
||||
$this->formBuilder = \Drupal::formBuilder();
|
||||
}
|
||||
return $this->formBuilder;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity\Exception;
|
||||
|
||||
use Drupal\Core\Config\ConfigException;
|
||||
|
||||
/**
|
||||
* Defines an exception thrown when a configuration entity ID is too long.
|
||||
*/
|
||||
class ConfigEntityIdLengthException extends ConfigException {}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity\Exception;
|
||||
|
||||
use Drupal\Core\Config\ConfigException;
|
||||
|
||||
/**
|
||||
* Thrown when a storage class is not an instance of ConfigEntityStorage.
|
||||
*/
|
||||
class ConfigEntityStorageClassException extends ConfigException {}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ImportableEntityStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Config\Config;
|
||||
|
||||
/**
|
||||
* Provides an interface for responding to configuration imports.
|
||||
*
|
||||
* When configuration is synchronized between storages, the entity storage must
|
||||
* handle the synchronization of configuration data for its entity.
|
||||
*/
|
||||
interface ImportableEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Creates entities upon synchronizing configuration changes.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object.
|
||||
* @param \Drupal\Core\Config\Config $new_config
|
||||
* A configuration object containing the new configuration data.
|
||||
* @param \Drupal\Core\Config\Config $old_config
|
||||
* A configuration object containing the old configuration data.
|
||||
*/
|
||||
public function importCreate($name, Config $new_config, Config $old_config);
|
||||
|
||||
/**
|
||||
* Updates entities upon synchronizing configuration changes.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object.
|
||||
* @param \Drupal\Core\Config\Config $new_config
|
||||
* A configuration object containing the new configuration data.
|
||||
* @param \Drupal\Core\Config\Config $old_config
|
||||
* A configuration object containing the old configuration data.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigImporterException
|
||||
* Thrown when the config entity that should be updated can not be found.
|
||||
*/
|
||||
public function importUpdate($name, Config $new_config, Config $old_config);
|
||||
|
||||
/**
|
||||
* Delete entities upon synchronizing configuration changes.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the configuration object.
|
||||
* @param \Drupal\Core\Config\Config $new_config
|
||||
* A configuration object containing the new configuration data.
|
||||
* @param \Drupal\Core\Config\Config $old_config
|
||||
* A configuration object containing the old configuration data.
|
||||
*/
|
||||
public function importDelete($name, Config $new_config, Config $old_config);
|
||||
|
||||
/**
|
||||
* Renames entities upon synchronizing configuration changes.
|
||||
*
|
||||
* @param string $old_name
|
||||
* The original name of the configuration object.
|
||||
* @param \Drupal\Core\Config\Config $new_config
|
||||
* A configuration object containing the new configuration data.
|
||||
* @param \Drupal\Core\Config\Config $old_config
|
||||
* A configuration object containing the old configuration data.
|
||||
*/
|
||||
public function importRename($old_name, Config $new_config, Config $old_config);
|
||||
|
||||
}
|
202
core/lib/Drupal/Core/Config/Entity/Query/Condition.php
Normal file
202
core/lib/Drupal/Core/Config/Entity/Query/Condition.php
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\Query\Condition.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity\Query;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\Query\ConditionBase;
|
||||
use Drupal\Core\Entity\Query\ConditionInterface;
|
||||
use Drupal\Core\Entity\Query\QueryException;
|
||||
|
||||
/**
|
||||
* Defines the condition class for the config entity query.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\Query\Query
|
||||
*/
|
||||
class Condition extends ConditionBase {
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Entity\Query\ConditionInterface::compile().
|
||||
*/
|
||||
public function compile($configs) {
|
||||
$and = strtoupper($this->conjunction) == 'AND';
|
||||
$single_conditions = array();
|
||||
$condition_groups = array();
|
||||
foreach ($this->conditions as $condition) {
|
||||
if ($condition['field'] instanceOf ConditionInterface) {
|
||||
$condition_groups[] = $condition;
|
||||
}
|
||||
else {
|
||||
if (!isset($condition['operator'])) {
|
||||
$condition['operator'] = is_array($condition['value']) ? 'IN' : '=';
|
||||
}
|
||||
|
||||
// Lowercase condition value(s) for case-insensitive matches.
|
||||
if (is_array($condition['value'])) {
|
||||
$condition['value'] = array_map('Drupal\Component\Utility\Unicode::strtolower', $condition['value']);
|
||||
}
|
||||
elseif (!is_bool($condition['value'])) {
|
||||
$condition['value'] = Unicode::strtolower($condition['value']);
|
||||
}
|
||||
|
||||
$single_conditions[] = $condition;
|
||||
}
|
||||
}
|
||||
$return = array();
|
||||
if ($single_conditions) {
|
||||
foreach ($configs as $config_name => $config) {
|
||||
foreach ($single_conditions as $condition) {
|
||||
$match = $this->matchArray($condition, $config, explode('.', $condition['field']));
|
||||
// If AND and it's not matching, then the rest of conditions do not
|
||||
// matter and this config object does not match.
|
||||
// If OR and it is matching, then the rest of conditions do not
|
||||
// matter and this config object does match.
|
||||
if ($and != $match ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($match) {
|
||||
$return[$config_name] = $config;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (!$condition_groups || $and) {
|
||||
// If there were no single conditions then either:
|
||||
// - Complex conditions, OR: need to start from no entities.
|
||||
// - Complex conditions, AND: need to start from all entities.
|
||||
// - No complex conditions (AND/OR doesn't matter): need to return all
|
||||
// entities.
|
||||
$return = $configs;
|
||||
}
|
||||
foreach ($condition_groups as $condition) {
|
||||
$group_entities = $condition['field']->compile($configs);
|
||||
if ($and) {
|
||||
$return = array_intersect_key($return, $group_entities);
|
||||
}
|
||||
else {
|
||||
$return = $return + $group_entities;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Entity\Query\ConditionInterface::exists().
|
||||
*/
|
||||
public function exists($field, $langcode = NULL) {
|
||||
return $this->condition($field, NULL, 'IS NOT NULL', $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Entity\Query\ConditionInterface::notExists().
|
||||
*/
|
||||
public function notExists($field, $langcode = NULL) {
|
||||
return $this->condition($field, NULL, 'IS NULL', $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches for an array representing one or more config paths.
|
||||
*
|
||||
* @param array $condition
|
||||
* The condition array as created by the condition() method.
|
||||
* @param array $data
|
||||
* The config array or part of it.
|
||||
* @param array $needs_matching
|
||||
* The list of config array keys needing a match. Can contain config keys
|
||||
* and the * wildcard.
|
||||
* @param array $parents
|
||||
* The current list of parents.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE when the condition matched to the data else FALSE.
|
||||
*/
|
||||
protected function matchArray(array $condition, array $data, array $needs_matching, array $parents = array()) {
|
||||
$parent = array_shift($needs_matching);
|
||||
if ($parent === '*') {
|
||||
$candidates = array_keys($data);
|
||||
}
|
||||
else {
|
||||
// Avoid a notice when calling match() later.
|
||||
if (!isset($data[$parent])) {
|
||||
$data[$parent] = NULL;
|
||||
}
|
||||
$candidates = array($parent);
|
||||
}
|
||||
foreach ($candidates as $key) {
|
||||
if ($needs_matching) {
|
||||
if (is_array($data[$key])) {
|
||||
$new_parents = $parents;
|
||||
$new_parents[] = $key;
|
||||
if ($this->matchArray($condition, $data[$key], $needs_matching, $new_parents)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only try to match a scalar if there are no remaining keys in
|
||||
// $needs_matching as this indicates that we are looking for a specific
|
||||
// subkey and a scalar can never match that.
|
||||
elseif ($this->match($condition, $data[$key])) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual matching.
|
||||
*
|
||||
* @param array $condition
|
||||
* The condition array as created by the condition() method.
|
||||
* @param string $value
|
||||
* The value to match against.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE when matches else FALSE.
|
||||
*/
|
||||
protected function match(array $condition, $value) {
|
||||
if (isset($value)) {
|
||||
// We always want a case-insensitive match.
|
||||
if (!is_bool($value)) {
|
||||
$value = Unicode::strtolower($value);
|
||||
}
|
||||
|
||||
switch ($condition['operator']) {
|
||||
case '=':
|
||||
return $value == $condition['value'];
|
||||
case '>':
|
||||
return $value > $condition['value'];
|
||||
case '<':
|
||||
return $value < $condition['value'];
|
||||
case '>=':
|
||||
return $value >= $condition['value'];
|
||||
case '<=':
|
||||
return $value <= $condition['value'];
|
||||
case '<>':
|
||||
return $value != $condition['value'];
|
||||
case 'IN':
|
||||
return array_search($value, $condition['value']) !== FALSE;
|
||||
case 'NOT IN':
|
||||
return array_search($value, $condition['value']) === FALSE;
|
||||
case 'STARTS_WITH':
|
||||
return strpos($value, $condition['value']) === 0;
|
||||
case 'CONTAINS':
|
||||
return strpos($value, $condition['value']) !== FALSE;
|
||||
case 'ENDS_WITH':
|
||||
return substr($value, -strlen($condition['value'])) === (string) $condition['value'];
|
||||
case 'IS NOT NULL':
|
||||
return TRUE;
|
||||
case 'IS NULL':
|
||||
return FALSE;
|
||||
default:
|
||||
throw new QueryException('Invalid condition operator.');
|
||||
}
|
||||
}
|
||||
return $condition['operator'] === 'IS NULL';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\Query\InvalidLookupKeyException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity\Query;
|
||||
|
||||
/**
|
||||
* Exception thrown when a config entity uses an invalid lookup key.
|
||||
*/
|
||||
class InvalidLookupKeyException extends \LogicException {
|
||||
}
|
233
core/lib/Drupal/Core/Config/Entity/Query/Query.php
Normal file
233
core/lib/Drupal/Core/Config/Entity/Query/Query.php
Normal file
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\Query\Query.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity\Query;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Query\QueryBase;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
|
||||
/**
|
||||
* Defines the entity query for configuration entities.
|
||||
*/
|
||||
class Query extends QueryBase implements QueryInterface {
|
||||
|
||||
/**
|
||||
* Information about the entity type.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* The config factory used by the config entity query.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The key value factory.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
|
||||
*/
|
||||
protected $keyValueFactory;
|
||||
|
||||
/**
|
||||
* Constructs a Query object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param string $conjunction
|
||||
* - AND: all of the conditions on the query need to match.
|
||||
* - OR: at least one of the conditions on the query need to match.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
|
||||
* The key value factory.
|
||||
* @param array $namespaces
|
||||
* List of potential namespaces of the classes belonging to this query.
|
||||
*/
|
||||
function __construct(EntityTypeInterface $entity_type, $conjunction, ConfigFactoryInterface $config_factory, KeyValueFactoryInterface $key_value_factory, array $namespaces) {
|
||||
parent::__construct($entity_type, $conjunction, $namespaces);
|
||||
$this->configFactory = $config_factory;
|
||||
$this->keyValueFactory = $key_value_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Entity\Query\QueryBase::condition().
|
||||
*
|
||||
* Additional to the syntax defined in the QueryInterface you can use
|
||||
* placeholders (*) to match all keys of an subarray. Let's take the follow
|
||||
* yaml file as example:
|
||||
* @code
|
||||
* level1:
|
||||
* level2a:
|
||||
* level3: 1
|
||||
* level2b:
|
||||
* level3: 2
|
||||
* @endcode
|
||||
* Then you can filter out via $query->condition('level1.*.level3', 1).
|
||||
*/
|
||||
public function condition($property, $value = NULL, $operator = NULL, $langcode = NULL) {
|
||||
return parent::condition($property, $value, $operator, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Entity\Query\QueryInterface::execute().
|
||||
*/
|
||||
public function execute() {
|
||||
// Load the relevant config records.
|
||||
$configs = $this->loadRecords();
|
||||
|
||||
// Apply conditions.
|
||||
$result = $this->condition->compile($configs);
|
||||
|
||||
// Apply sort settings.
|
||||
foreach ($this->sort as $sort) {
|
||||
$direction = $sort['direction'] == 'ASC' ? -1 : 1;
|
||||
$field = $sort['field'];
|
||||
uasort($result, function($a, $b) use ($field, $direction) {
|
||||
return ($a[$field] <= $b[$field]) ? $direction : -$direction;
|
||||
});
|
||||
}
|
||||
|
||||
// Let the pager do its work.
|
||||
$this->initializePager();
|
||||
|
||||
if ($this->range) {
|
||||
$result = array_slice($result, $this->range['start'], $this->range['length'], TRUE);
|
||||
}
|
||||
if ($this->count) {
|
||||
return count($result);
|
||||
}
|
||||
|
||||
// Create the expected structure of entity_id => entity_id. Config
|
||||
// entities have string entity IDs.
|
||||
foreach ($result as $key => &$value) {
|
||||
$value = (string) $key;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the config records to examine for the query.
|
||||
*
|
||||
* @return array
|
||||
* Config records keyed by entity IDs.
|
||||
*/
|
||||
protected function loadRecords() {
|
||||
$prefix = $this->entityType->getConfigPrefix() . '.';
|
||||
$prefix_length = strlen($prefix);
|
||||
|
||||
// Search the conditions for restrictions on configuration object names.
|
||||
$names = FALSE;
|
||||
$id_condition = NULL;
|
||||
$id_key = $this->entityType->getKey('id');
|
||||
if ($this->condition->getConjunction() == 'AND') {
|
||||
$lookup_keys = $this->entityType->getLookupKeys();
|
||||
$conditions = $this->condition->conditions();
|
||||
foreach ($conditions as $condition_key => $condition) {
|
||||
$operator = $condition['operator'] ?: (is_array($condition['value']) ? 'IN' : '=');
|
||||
if (is_string($condition['field']) && ($operator == 'IN' || $operator == '=')) {
|
||||
// Special case ID lookups.
|
||||
if ($condition['field'] == $id_key) {
|
||||
$ids = (array) $condition['value'];
|
||||
$names = array_map(function ($id) use ($prefix) {
|
||||
return $prefix . $id;
|
||||
}, $ids);
|
||||
}
|
||||
elseif (in_array($condition['field'], $lookup_keys)) {
|
||||
// If we don't find anything then there are no matches. No point in
|
||||
// listing anything.
|
||||
$names = array();
|
||||
$keys = (array) $condition['value'];
|
||||
$keys = array_map(function ($value) use ($condition) {
|
||||
return $condition['field'] . ':' . $value;
|
||||
}, $keys);
|
||||
foreach ($this->getConfigKeyStore()->getMultiple($keys) as $list) {
|
||||
$names = array_merge($names, $list);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Save the first ID condition that is not an 'IN' or '=' for narrowing
|
||||
// down later.
|
||||
elseif (!$id_condition && $condition['field'] == $id_key) {
|
||||
$id_condition = $condition;
|
||||
}
|
||||
// We stop at the first restricting condition on name. In the case where
|
||||
// there are additional restricting conditions, results will be
|
||||
// eliminated when the conditions are checked on the loaded records.
|
||||
if ($names !== FALSE) {
|
||||
// If the condition has been responsible for narrowing the list of
|
||||
// configuration to check there is no point in checking it further.
|
||||
unset($conditions[$condition_key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no restrictions on IDs were found, we need to parse all records.
|
||||
if ($names === FALSE) {
|
||||
$names = $this->configFactory->listAll($prefix);
|
||||
}
|
||||
// In case we have an ID condition, try to narrow down the list of config
|
||||
// objects to load.
|
||||
if ($id_condition && !empty($names)) {
|
||||
$value = $id_condition['value'];
|
||||
$filter = NULL;
|
||||
switch ($id_condition['operator']) {
|
||||
case '<>':
|
||||
$filter = function ($name) use ($value, $prefix_length) {
|
||||
$id = substr($name, $prefix_length);
|
||||
return $id !== $value;
|
||||
};
|
||||
break;
|
||||
case 'STARTS_WITH':
|
||||
$filter = function ($name) use ($value, $prefix_length) {
|
||||
$id = substr($name, $prefix_length);
|
||||
return strpos($id, $value) === 0;
|
||||
};
|
||||
break;
|
||||
case 'CONTAINS':
|
||||
$filter = function ($name) use ($value, $prefix_length) {
|
||||
$id = substr($name, $prefix_length);
|
||||
return strpos($id, $value) !== FALSE;
|
||||
};
|
||||
break;
|
||||
case 'ENDS_WITH':
|
||||
$filter = function ($name) use ($value, $prefix_length) {
|
||||
$id = substr($name, $prefix_length);
|
||||
return strrpos($id, $value) === strlen($id) - strlen($value);
|
||||
};
|
||||
break;
|
||||
}
|
||||
if ($filter) {
|
||||
$names = array_filter($names, $filter);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the corresponding records.
|
||||
$records = array();
|
||||
foreach ($this->configFactory->loadMultiple($names) as $config) {
|
||||
$records[substr($config->getName(), $prefix_length)] = $config->get();
|
||||
}
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key value store used to store fast lookups.
|
||||
*
|
||||
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
* The key value store used to store fast lookups.
|
||||
*/
|
||||
protected function getConfigKeyStore() {
|
||||
return $this->keyValueFactory->get(QueryFactory::CONFIG_LOOKUP_PREFIX . $this->entityTypeId);
|
||||
}
|
||||
|
||||
}
|
263
core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
Normal file
263
core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
Normal file
|
@ -0,0 +1,263 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\Query\QueryFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity\Query;
|
||||
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\ConfigManagerInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Query\QueryBase;
|
||||
use Drupal\Core\Entity\Query\QueryException;
|
||||
use Drupal\Core\Entity\Query\QueryFactoryInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Provides a factory for creating entity query objects for the config backend.
|
||||
*/
|
||||
class QueryFactory implements QueryFactoryInterface, EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The prefix for the key value collection for fast lookups.
|
||||
*/
|
||||
const CONFIG_LOOKUP_PREFIX = 'config.entity.key_store.';
|
||||
|
||||
/**
|
||||
* The config factory used by the config entity query.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface;
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The namespace of this class, the parent class etc.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $namespaces;
|
||||
|
||||
/**
|
||||
* Constructs a QueryFactory object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config storage used by the config entity query.
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value
|
||||
* The key value factory.
|
||||
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
||||
* The configuration manager.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, KeyValueFactoryInterface $key_value, ConfigManagerInterface $config_manager) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->keyValueFactory = $key_value;
|
||||
$this->configManager = $config_manager;
|
||||
$this->namespaces = QueryBase::getNamespaces($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(EntityTypeInterface $entity_type, $conjunction) {
|
||||
return new Query($entity_type, $conjunction, $this->configFactory, $this->keyValueFactory, $this->namespaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
|
||||
throw new QueryException('Aggregation over configuration entities is not supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key value store used to store fast lookups.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
* The key value store used to store fast lookups.
|
||||
*/
|
||||
protected function getConfigKeyStore(EntityTypeInterface $entity_type) {
|
||||
return $this->keyValueFactory->get(static::CONFIG_LOOKUP_PREFIX . $entity_type->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates or adds lookup data.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* The configuration object that is being saved.
|
||||
*/
|
||||
protected function updateConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) {
|
||||
$config_key_store = $this->getConfigKeyStore($entity_type);
|
||||
foreach ($entity_type->getLookupKeys() as $lookup_key) {
|
||||
foreach ($this->getKeys($config, $lookup_key, 'get', $entity_type) as $key) {
|
||||
$values = $config_key_store->get($key, []);
|
||||
if (!in_array($config->getName(), $values, TRUE)) {
|
||||
$values[] = $config->getName();
|
||||
$config_key_store->set($key, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes lookup data.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* The configuration object that is being deleted.
|
||||
*/
|
||||
protected function deleteConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) {
|
||||
$config_key_store = $this->getConfigKeyStore($entity_type);
|
||||
foreach ($entity_type->getLookupKeys() as $lookup_key) {
|
||||
foreach ($this->getKeys($config, $lookup_key, 'getOriginal', $entity_type) as $key) {
|
||||
$values = $config_key_store->get($key, []);
|
||||
$pos = array_search($config->getName(), $values, TRUE);
|
||||
if ($pos !== FALSE) {
|
||||
unset($values[$pos]);
|
||||
}
|
||||
if (empty($values)) {
|
||||
$config_key_store->delete($key);
|
||||
}
|
||||
else {
|
||||
$config_key_store->set($key, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates lookup keys for configuration data.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* The configuration object.
|
||||
* @param string $key
|
||||
* The configuration key to look for.
|
||||
* @param string $get_method
|
||||
* Which method on the config object to call to get the value. Either 'get'
|
||||
* or 'getOriginal'.
|
||||
* @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type
|
||||
* The configuration entity type.
|
||||
*
|
||||
* @return array
|
||||
* An array of lookup keys concatenated to the configuration values.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\Entity\Query\InvalidLookupKeyException
|
||||
* The provided $key cannot end with a wildcard. This makes no sense since
|
||||
* you cannot do fast lookups against this.
|
||||
*/
|
||||
protected function getKeys(Config $config, $key, $get_method, ConfigEntityTypeInterface $entity_type) {
|
||||
if (substr($key, -1) == '*') {
|
||||
throw new InvalidLookupKeyException(strtr('%entity_type lookup key %key ends with a wildcard this can not be used as a lookup', ['%entity_type' => $entity_type->id(), '%key' => $key]));
|
||||
}
|
||||
$parts = explode('.*', $key);
|
||||
// Remove leading dots.
|
||||
array_walk($parts, function (&$value) {
|
||||
$value = trim($value, '.');
|
||||
});
|
||||
|
||||
$values = (array) $this->getValues($config, $parts[0], $get_method, $parts);
|
||||
|
||||
$output = array();
|
||||
// Flatten the array to a single dimension and add the key to all the
|
||||
// values.
|
||||
array_walk_recursive($values, function ($current) use (&$output, $key) {
|
||||
if (is_scalar($current)) {
|
||||
$current = $key . ':' . $current;
|
||||
}
|
||||
$output[] = $current;
|
||||
});
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all the values for a configuration key in a configuration object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* The configuration object.
|
||||
* @param string $key
|
||||
* The current key being checked.
|
||||
* @param string $get_method
|
||||
* Which method on the config object to call to get the value.
|
||||
* @param array $parts
|
||||
* All the parts of a configuration key we are checking.
|
||||
* @param int $start
|
||||
* Which position of $parts we are processing. Defaults to 0.
|
||||
*
|
||||
* @return array|NULL
|
||||
* The array of configuration values the match the provided key. NULL if
|
||||
* the configuration object does not have a value that corresponds to the
|
||||
* key.
|
||||
*/
|
||||
protected function getValues(Config $config, $key, $get_method, array $parts, $start = 0) {
|
||||
$value = $config->$get_method($key);
|
||||
if (is_array($value)) {
|
||||
$new_value = [];
|
||||
$start++;
|
||||
if (!isset($parts[$start])) {
|
||||
// The configuration object does not have a value that corresponds to
|
||||
// the key.
|
||||
return NULL;
|
||||
}
|
||||
foreach (array_keys($value) as $key_bit) {
|
||||
$new_key = $key . '.' . $key_bit;
|
||||
if (!empty($parts[$start])) {
|
||||
$new_key .= '.' . $parts[$start];
|
||||
}
|
||||
$new_value[] = $this->getValues($config, $new_key, $get_method, $parts, $start);
|
||||
}
|
||||
$value = $new_value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates configuration entity in the key store.
|
||||
*
|
||||
* @param ConfigCrudEvent $event
|
||||
* The configuration event.
|
||||
*/
|
||||
public function onConfigSave(ConfigCrudEvent $event) {
|
||||
$saved_config = $event->getConfig();
|
||||
$entity_type_id = $this->configManager->getEntityTypeIdByName($saved_config->getName());
|
||||
if ($entity_type_id) {
|
||||
$entity_type = $this->configManager->getEntityManager()->getDefinition($entity_type_id);
|
||||
$this->updateConfigKeyStore($entity_type, $saved_config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes configuration entity from key store.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The configuration event.
|
||||
*/
|
||||
public function onConfigDelete(ConfigCrudEvent $event) {
|
||||
$saved_config = $event->getConfig();
|
||||
$entity_type_id = $this->configManager->getEntityTypeIdByName($saved_config->getName());
|
||||
if ($entity_type_id) {
|
||||
$entity_type = $this->configManager->getEntityManager()->getDefinition($entity_type_id);
|
||||
$this->deleteConfigKeyStore($entity_type, $saved_config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::SAVE][] = array('onConfigSave', 128);
|
||||
$events[ConfigEvents::DELETE][] = array('onConfigDelete', 128);
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Entity\ThirdPartySettingsInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
/**
|
||||
* Interface for configuration entities to store third party information.
|
||||
*
|
||||
* A third party is a module that needs to store tightly coupled information to
|
||||
* the configuration entity. For example, a module alters the node type form
|
||||
* can use this to store its configuration so that it will be deployed with the
|
||||
* node type.
|
||||
*/
|
||||
interface ThirdPartySettingsInterface {
|
||||
|
||||
/**
|
||||
* Sets the value of a third-party setting.
|
||||
*
|
||||
* @param string $module
|
||||
* The module providing the third-party setting.
|
||||
* @param string $key
|
||||
* The setting name.
|
||||
* @param mixed $value
|
||||
* The setting value.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setThirdPartySetting($module, $key, $value);
|
||||
|
||||
/**
|
||||
* Gets the value of a third-party setting.
|
||||
*
|
||||
* @param string $module
|
||||
* The module providing the third-party setting.
|
||||
* @param string $key
|
||||
* The setting name.
|
||||
* @param mixed $default
|
||||
* The default value
|
||||
*
|
||||
* @return mixed
|
||||
* The value.
|
||||
*/
|
||||
public function getThirdPartySetting($module, $key, $default = NULL);
|
||||
|
||||
|
||||
/**
|
||||
* Gets all third-party settings of a given module.
|
||||
*
|
||||
* @param string $module
|
||||
* The module providing the third-party settings.
|
||||
*
|
||||
* @return array
|
||||
* An array of key-value pairs.
|
||||
*/
|
||||
public function getThirdPartySettings($module);
|
||||
|
||||
/**
|
||||
* Unsets a third-party setting.
|
||||
*
|
||||
* @param string $module
|
||||
* The module providing the third-party setting.
|
||||
* @param string $key
|
||||
* The setting name.
|
||||
*
|
||||
* @return mixed
|
||||
* The value.
|
||||
*/
|
||||
public function unsetThirdPartySetting($module, $key);
|
||||
|
||||
/**
|
||||
* Gets the list of third parties that store information.
|
||||
*
|
||||
* @return array
|
||||
* The list of third parties.
|
||||
*/
|
||||
public function getThirdPartyProviders();
|
||||
|
||||
}
|
140
core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
Normal file
140
core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ExtensionInstallStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Storage to access configuration and schema in enabled extensions.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigInstaller
|
||||
* @see \Drupal\Core\Config\TypedConfigManager
|
||||
*/
|
||||
class ExtensionInstallStorage extends InstallStorage {
|
||||
|
||||
/**
|
||||
* The active configuration store.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* Flag to include the profile in the list of enabled modules.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $includeProfile = TRUE;
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Config\InstallStorage::__construct().
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The active configuration store where the list of enabled modules and
|
||||
* themes is stored.
|
||||
* @param string $directory
|
||||
* The directory to scan in each extension to scan for files. Defaults to
|
||||
* 'config/install'.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
* @param bool $include_profile
|
||||
* (optional) Whether to include the install profile in extensions to
|
||||
* search and to get overrides from.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE) {
|
||||
$this->configStorage = $config_storage;
|
||||
$this->directory = $directory;
|
||||
$this->collection = $collection;
|
||||
$this->includeProfile = $include_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->configStorage,
|
||||
$this->directory,
|
||||
$collection
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all config object names and their folders.
|
||||
*
|
||||
* The list is based on enabled modules and themes. The active configuration
|
||||
* storage is used rather than \Drupal\Core\Extension\ModuleHandler and
|
||||
* \Drupal\Core\Extension\ThemeHandler in order to resolve circular
|
||||
* dependencies between these services and \Drupal\Core\Config\ConfigInstaller
|
||||
* and \Drupal\Core\Config\TypedConfigManager.
|
||||
*
|
||||
* @return array
|
||||
* An array mapping config object names with directories.
|
||||
*/
|
||||
protected function getAllFolders() {
|
||||
if (!isset($this->folders)) {
|
||||
$this->folders = array();
|
||||
$this->folders += $this->getCoreNames();
|
||||
|
||||
$install_profile = Settings::get('install_profile');
|
||||
$profile = drupal_get_profile();
|
||||
$extensions = $this->configStorage->read('core.extension');
|
||||
// @todo Remove this scan as part of https://www.drupal.org/node/2186491
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
if (!empty($extensions['module'])) {
|
||||
$modules = $extensions['module'];
|
||||
// Remove the install profile as this is handled later.
|
||||
unset($modules[$install_profile]);
|
||||
$profile_list = $listing->scan('profile');
|
||||
if ($profile && isset($profile_list[$profile])) {
|
||||
// Prime the drupal_get_filename() static cache with the profile info
|
||||
// file location so we can use drupal_get_path() on the active profile
|
||||
// during the module scan.
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
drupal_get_filename('profile', $profile, $profile_list[$profile]->getPathname());
|
||||
}
|
||||
$module_list_scan = $listing->scan('module');
|
||||
$module_list = array();
|
||||
foreach (array_keys($modules) as $module) {
|
||||
if (isset($module_list_scan[$module])) {
|
||||
$module_list[$module] = $module_list_scan[$module];
|
||||
}
|
||||
}
|
||||
$this->folders += $this->getComponentNames($module_list);
|
||||
}
|
||||
if (!empty($extensions['theme'])) {
|
||||
$theme_list_scan = $listing->scan('theme');
|
||||
foreach (array_keys($extensions['theme']) as $theme) {
|
||||
if (isset($theme_list_scan[$theme])) {
|
||||
$theme_list[$theme] = $theme_list_scan[$theme];
|
||||
}
|
||||
}
|
||||
$this->folders += $this->getComponentNames($theme_list);
|
||||
}
|
||||
|
||||
if ($this->includeProfile) {
|
||||
// The install profile can override module default configuration. We do
|
||||
// this by replacing the config file path from the module/theme with the
|
||||
// install profile version if there are any duplicates.
|
||||
if (isset($profile)) {
|
||||
if (!isset($profile_list)) {
|
||||
$profile_list = $listing->scan('profile');
|
||||
}
|
||||
if (isset($profile_list[$profile])) {
|
||||
$profile_folders = $this->getComponentNames(array($profile_list[$profile]));
|
||||
$this->folders = $profile_folders + $this->folders;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->folders;
|
||||
}
|
||||
}
|
||||
|
338
core/lib/Drupal/Core/Config/FileStorage.php
Normal file
338
core/lib/Drupal/Core/Config/FileStorage.php
Normal file
|
@ -0,0 +1,338 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\FileStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Defines the file storage.
|
||||
*/
|
||||
class FileStorage implements StorageInterface {
|
||||
|
||||
/**
|
||||
* The storage collection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $collection;
|
||||
|
||||
/**
|
||||
* The filesystem path for configuration objects.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory = '';
|
||||
|
||||
/**
|
||||
* Constructs a new FileStorage.
|
||||
*
|
||||
* @param string $directory
|
||||
* A directory path to use for reading and writing of configuration files.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct($directory, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->directory = $directory;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the configuration file.
|
||||
*
|
||||
* @return string
|
||||
* The path to the configuration file.
|
||||
*/
|
||||
public function getFilePath($name) {
|
||||
return $this->getCollectionDirectory() . '/' . $name . '.' . static::getFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension used by the file storage for all configuration files.
|
||||
*
|
||||
* @return string
|
||||
* The file extension.
|
||||
*/
|
||||
public static function getFileExtension() {
|
||||
return 'yml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the directory exists and create it if not.
|
||||
*/
|
||||
protected function ensureStorage() {
|
||||
$dir = $this->getCollectionDirectory();
|
||||
$success = file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
|
||||
// Only create .htaccess file in root directory.
|
||||
if ($dir == $this->directory) {
|
||||
$success = $success && file_save_htaccess($this->directory, TRUE, TRUE);
|
||||
}
|
||||
if (!$success) {
|
||||
throw new StorageException('Failed to create config directory ' . $dir);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::exists().
|
||||
*/
|
||||
public function exists($name) {
|
||||
return file_exists($this->getFilePath($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::read().
|
||||
*
|
||||
* @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
|
||||
*/
|
||||
public function read($name) {
|
||||
if (!$this->exists($name)) {
|
||||
return FALSE;
|
||||
}
|
||||
$data = file_get_contents($this->getFilePath($name));
|
||||
try {
|
||||
$data = $this->decode($data);
|
||||
}
|
||||
catch (InvalidDataTypeException $e) {
|
||||
throw new UnsupportedDataTypeConfigException(SafeMarkup::format('Invalid data type in config @name: !message', array(
|
||||
'@name' => $name,
|
||||
'!message' => $e->getMessage(),
|
||||
)));
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function readMultiple(array $names) {
|
||||
$list = array();
|
||||
foreach ($names as $name) {
|
||||
if ($data = $this->read($name)) {
|
||||
$list[$name] = $data;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($name, array $data) {
|
||||
try {
|
||||
$data = $this->encode($data);
|
||||
}
|
||||
catch (InvalidDataTypeException $e) {
|
||||
throw new StorageException(SafeMarkup::format('Invalid data type in config @name: !message', array(
|
||||
'@name' => $name,
|
||||
'!message' => $e->getMessage(),
|
||||
)));
|
||||
}
|
||||
|
||||
$target = $this->getFilePath($name);
|
||||
$status = @file_put_contents($target, $data);
|
||||
if ($status === FALSE) {
|
||||
// Try to make sure the directory exists and try writing again.
|
||||
$this->ensureStorage();
|
||||
$status = @file_put_contents($target, $data);
|
||||
}
|
||||
if ($status === FALSE) {
|
||||
throw new StorageException('Failed to write configuration file: ' . $this->getFilePath($name));
|
||||
}
|
||||
else {
|
||||
drupal_chmod($target);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::delete().
|
||||
*/
|
||||
public function delete($name) {
|
||||
if (!$this->exists($name)) {
|
||||
$dir = $this->getCollectionDirectory();
|
||||
if (!file_exists($dir)) {
|
||||
throw new StorageException($dir . '/ not found.');
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
return drupal_unlink($this->getFilePath($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::rename().
|
||||
*/
|
||||
public function rename($name, $new_name) {
|
||||
$status = @rename($this->getFilePath($name), $this->getFilePath($new_name));
|
||||
if ($status === FALSE) {
|
||||
throw new StorageException('Failed to rename configuration file from: ' . $this->getFilePath($name) . ' to: ' . $this->getFilePath($new_name));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::encode().
|
||||
*/
|
||||
public function encode($data) {
|
||||
return Yaml::encode($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::decode().
|
||||
*/
|
||||
public function decode($raw) {
|
||||
$data = Yaml::decode($raw);
|
||||
// A simple string is valid YAML for any reason.
|
||||
if (!is_array($data)) {
|
||||
return FALSE;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::listAll().
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
// glob() silently ignores the error of a non-existing search directory,
|
||||
// even with the GLOB_ERR flag.
|
||||
$dir = $this->getCollectionDirectory();
|
||||
if (!file_exists($dir)) {
|
||||
return array();
|
||||
}
|
||||
$extension = '.' . static::getFileExtension();
|
||||
// \GlobIterator on Windows requires an absolute path.
|
||||
$files = new \GlobIterator(realpath($dir) . '/' . $prefix . '*' . $extension);
|
||||
|
||||
$names = array();
|
||||
foreach ($files as $file) {
|
||||
$names[] = $file->getBasename($extension);
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::deleteAll().
|
||||
*/
|
||||
public function deleteAll($prefix = '') {
|
||||
$success = TRUE;
|
||||
$files = $this->listAll($prefix);
|
||||
foreach ($files as $name) {
|
||||
if (!$this->delete($name) && $success) {
|
||||
$success = FALSE;
|
||||
}
|
||||
}
|
||||
if ($success && $this->collection != StorageInterface::DEFAULT_COLLECTION) {
|
||||
// Remove empty directories.
|
||||
if (!(new \FilesystemIterator($this->getCollectionDirectory()))->valid()) {
|
||||
drupal_rmdir($this->getCollectionDirectory());
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->directory,
|
||||
$collection
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
$collections = $this->getAllCollectionNamesHelper($this->directory);
|
||||
sort($collections);
|
||||
return $collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for getAllCollectionNames().
|
||||
*
|
||||
* If the file storage has the following subdirectory structure:
|
||||
* ./another_collection/one
|
||||
* ./another_collection/two
|
||||
* ./collection/sub/one
|
||||
* ./collection/sub/two
|
||||
* this function will return:
|
||||
* @code
|
||||
* array(
|
||||
* 'another_collection.one',
|
||||
* 'another_collection.two',
|
||||
* 'collection.sub.one',
|
||||
* 'collection.sub.two',
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @param string $directory
|
||||
* The directory to check for sub directories. This allows this
|
||||
* function to be used recursively to discover all the collections in the
|
||||
* storage.
|
||||
*
|
||||
* @return array
|
||||
* A list of collection names contained within the provided directory.
|
||||
*/
|
||||
protected function getAllCollectionNamesHelper($directory) {
|
||||
$collections = array();
|
||||
foreach (new \DirectoryIterator($directory) as $fileinfo) {
|
||||
if ($fileinfo->isDir() && !$fileinfo->isDot()) {
|
||||
$collection = $fileinfo->getFilename();
|
||||
// Recursively call getAllCollectionNamesHelper() to discover if there
|
||||
// are subdirectories. Subdirectories represent a dotted collection
|
||||
// name.
|
||||
$sub_collections = $this->getAllCollectionNamesHelper($directory . '/' . $collection);
|
||||
if (!empty($sub_collections)) {
|
||||
// Build up the collection name by concatenating the subdirectory
|
||||
// names with the current directory name.
|
||||
foreach ($sub_collections as $sub_collection) {
|
||||
$collections[] = $collection . '.' . $sub_collection;
|
||||
}
|
||||
}
|
||||
// Check that the collection is valid by searching if for configuration
|
||||
// objects. A directory without any configuration objects is not a valid
|
||||
// collection.
|
||||
// \GlobIterator on Windows requires an absolute path.
|
||||
$files = new \GlobIterator(realpath($directory . '/' . $collection) . '/*.' . $this->getFileExtension());
|
||||
if (count($files)) {
|
||||
$collections[] = $collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory for the collection.
|
||||
*
|
||||
* @return string
|
||||
* The directory for the collection.
|
||||
*/
|
||||
protected function getCollectionDirectory() {
|
||||
if ($this->collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$dir = $this->directory;
|
||||
}
|
||||
else {
|
||||
$dir = $this->directory . '/' . str_replace('.', '/', $this->collection);
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
}
|
33
core/lib/Drupal/Core/Config/FileStorageFactory.php
Normal file
33
core/lib/Drupal/Core/Config/FileStorageFactory.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\FileStorageFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Provides a factory for creating config file storage objects.
|
||||
*/
|
||||
class FileStorageFactory {
|
||||
|
||||
/**
|
||||
* Returns a FileStorage object working with the active config directory.
|
||||
*
|
||||
* @return \Drupal\Core\Config\FileStorage FileStorage
|
||||
*/
|
||||
static function getActive() {
|
||||
return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FileStorage object working with the staging config directory.
|
||||
*
|
||||
* @return \Drupal\Core\Config\FileStorage FileStorage
|
||||
*/
|
||||
static function getStaging() {
|
||||
return new FileStorage(config_get_config_directory(CONFIG_STAGING_DIRECTORY));
|
||||
}
|
||||
|
||||
}
|
61
core/lib/Drupal/Core/Config/ImmutableConfig.php
Normal file
61
core/lib/Drupal/Core/Config/ImmutableConfig.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ImmutableConfig.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Defines the immutable configuration object.
|
||||
*
|
||||
* Encapsulates all capabilities needed for runtime configuration handling
|
||||
* except being able to change the configuration.
|
||||
*
|
||||
* If you need to be able to change configuration use
|
||||
* \Drupal\Core\Form\ConfigFormBaseTrait or
|
||||
* \Drupal\Core\Config\ConfigFactoryInterface::getEditable().
|
||||
*
|
||||
* @see \Drupal\Core\Form\ConfigFormBaseTrait
|
||||
* @see \Drupal\Core\Config\ConfigFactoryInterface::getEditable()
|
||||
* @see \Drupal\Core\Config\ConfigFactoryInterface::get()
|
||||
*
|
||||
* @ingroup config_api
|
||||
*/
|
||||
class ImmutableConfig extends Config {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
throw new ImmutableConfigException(SafeMarkup::format('Can not set values on immutable configuration !name:!key. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName(), '!key' => $key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear($key) {
|
||||
throw new ImmutableConfigException(SafeMarkup::format('Can not clear !key key in immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName(), '!key' => $key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save($has_trusted_data = FALSE) {
|
||||
throw new ImmutableConfigException(SafeMarkup::format('Can not save immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the configuration object.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object.
|
||||
*/
|
||||
public function delete() {
|
||||
throw new ImmutableConfigException(SafeMarkup::format('Can not delete immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName()]));
|
||||
}
|
||||
|
||||
}
|
16
core/lib/Drupal/Core/Config/ImmutableConfigException.php
Normal file
16
core/lib/Drupal/Core/Config/ImmutableConfigException.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ImmutableConfigException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception throw when an immutable config object is altered.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ImmutableConfig
|
||||
*/
|
||||
class ImmutableConfigException extends \LogicException {
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Importer\FinalMissingContentSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Importer;
|
||||
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Final event subscriber to the missing content event.
|
||||
*
|
||||
* Ensure that all missing content dependencies are removed from the event so
|
||||
* the importer can complete.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter::processMissingContent()
|
||||
*/
|
||||
class FinalMissingContentSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Handles the missing content event.
|
||||
*
|
||||
* @param \Drupal\Core\Config\Importer\MissingContentEvent $event
|
||||
* The missing content event.
|
||||
*/
|
||||
public function onMissingContent(MissingContentEvent $event) {
|
||||
foreach (array_keys($event->getMissingContent()) as $uuid) {
|
||||
$event->resolveMissingContent($uuid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
// This should always be the final event as it will mark all content
|
||||
// dependencies as resolved.
|
||||
$events[ConfigEvents::IMPORT_MISSING_CONTENT][] = array('onMissingContent', -1024);
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
64
core/lib/Drupal/Core/Config/Importer/MissingContentEvent.php
Normal file
64
core/lib/Drupal/Core/Config/Importer/MissingContentEvent.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Importer\MissingContentEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Importer;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a configuration event for event listeners.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Config\ConfigEvents::IMPORT_MISSING_CONTENT
|
||||
*/
|
||||
class MissingContentEvent extends Event {
|
||||
|
||||
/**
|
||||
* A list of missing content dependencies.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $missingContent;
|
||||
|
||||
/**
|
||||
* Constructs a configuration import missing content event object.
|
||||
*
|
||||
* @param array $missing_content
|
||||
* Missing content information.
|
||||
*/
|
||||
public function __construct(array $missing_content) {
|
||||
$this->missingContent = $missing_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets missing content information.
|
||||
*
|
||||
* @return array
|
||||
* A list of missing content dependencies. The array is keyed by UUID. Each
|
||||
* value is an array with the following keys: 'entity_type', 'bundle' and
|
||||
* 'uuid'.
|
||||
*/
|
||||
public function getMissingContent() {
|
||||
return $this->missingContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the missing content by removing it from the list.
|
||||
*
|
||||
* @param string $uuid
|
||||
* The UUID of the content entity to mark resolved.
|
||||
*
|
||||
* @return $this
|
||||
* The MissingContentEvent object.
|
||||
*/
|
||||
public function resolveMissingContent($uuid) {
|
||||
if (isset($this->missingContent[$uuid])) {
|
||||
unset($this->missingContent[$uuid]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
268
core/lib/Drupal/Core/Config/InstallStorage.php
Normal file
268
core/lib/Drupal/Core/Config/InstallStorage.php
Normal file
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\InstallStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* Storage used by the Drupal installer.
|
||||
*
|
||||
* This storage performs a full filesystem scan to discover all available
|
||||
* extensions and reads from all default config directories that exist.
|
||||
*
|
||||
* This special implementation MUST NOT be used anywhere else than the early
|
||||
* installer environment.
|
||||
*
|
||||
* @see \Drupal\Core\DependencyInjection\InstallServiceProvider
|
||||
*/
|
||||
class InstallStorage extends FileStorage {
|
||||
|
||||
/**
|
||||
* Extension sub-directory containing default configuration for installation.
|
||||
*/
|
||||
const CONFIG_INSTALL_DIRECTORY = 'config/install';
|
||||
|
||||
/**
|
||||
* Extension sub-directory containing optional configuration for installation.
|
||||
*/
|
||||
const CONFIG_OPTIONAL_DIRECTORY = 'config/optional';
|
||||
|
||||
/**
|
||||
* Extension sub-directory containing configuration schema.
|
||||
*/
|
||||
const CONFIG_SCHEMA_DIRECTORY = 'config/schema';
|
||||
|
||||
/**
|
||||
* Folder map indexed by configuration name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $folders;
|
||||
|
||||
/**
|
||||
* The directory to scan in each extension to scan for files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* Constructs an InstallStorage object.
|
||||
*
|
||||
* @param string $directory
|
||||
* The directory to scan in each extension to scan for files. Defaults to
|
||||
* 'config/install'.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct($directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->directory = $directory;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Config\FileStorage::getFilePath().
|
||||
*
|
||||
* Returns the path to the configuration file.
|
||||
*
|
||||
* Determines the owner and path to the default configuration file of a
|
||||
* requested config object name located in the installation profile, a module,
|
||||
* or a theme (in this order).
|
||||
*
|
||||
* @return string
|
||||
* The path to the configuration file.
|
||||
*
|
||||
* @todo Improve this when figuring out how we want to handle configuration in
|
||||
* installation profiles. E.g., a config object actually has to be searched
|
||||
* in the profile first (whereas the profile is never the owner), only
|
||||
* afterwards check for a corresponding module or theme.
|
||||
*/
|
||||
public function getFilePath($name) {
|
||||
$folders = $this->getAllFolders();
|
||||
if (isset($folders[$name])) {
|
||||
return $folders[$name] . '/' . $name . '.' . $this->getFileExtension();
|
||||
}
|
||||
// If any code in the early installer requests a configuration object that
|
||||
// does not exist anywhere as default config, then that must be mistake.
|
||||
throw new StorageException(format_string('Missing configuration file: @name', array(
|
||||
'@name' => $name,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists($name) {
|
||||
return array_key_exists($name, $this->getAllFolders());
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Config\FileStorage::write().
|
||||
*
|
||||
* @throws \Drupal\Core\Config\StorageException
|
||||
*/
|
||||
public function write($name, array $data) {
|
||||
throw new StorageException('Write operation is not allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Config\FileStorage::delete().
|
||||
*
|
||||
* @throws \Drupal\Core\Config\StorageException
|
||||
*/
|
||||
public function delete($name) {
|
||||
throw new StorageException('Delete operation is not allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Config\FileStorage::rename().
|
||||
*
|
||||
* @throws \Drupal\Core\Config\StorageException
|
||||
*/
|
||||
public function rename($name, $new_name) {
|
||||
throw new StorageException('Rename operation is not allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::listAll().
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
$names = array_keys($this->getAllFolders());
|
||||
if (!$prefix) {
|
||||
return $names;
|
||||
}
|
||||
else {
|
||||
$return = array();
|
||||
foreach ($names as $index => $name) {
|
||||
if (strpos($name, $prefix) === 0 ) {
|
||||
$return[$index] = $names[$index];
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all config object names and their folders.
|
||||
*
|
||||
* @return array
|
||||
* An array mapping config object names with directories.
|
||||
*/
|
||||
protected function getAllFolders() {
|
||||
if (!isset($this->folders)) {
|
||||
$this->folders = array();
|
||||
$this->folders += $this->getCoreNames();
|
||||
// Perform an ExtensionDiscovery scan as we cannot use drupal_get_path()
|
||||
// yet because the system module may not yet be enabled during install.
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
if ($profile = drupal_get_profile()) {
|
||||
$profile_list = $listing->scan('profile');
|
||||
if (isset($profile_list[$profile])) {
|
||||
// Prime the drupal_get_filename() static cache with the profile info
|
||||
// file location so we can use drupal_get_path() on the active profile
|
||||
// during the module scan.
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
drupal_get_filename('profile', $profile, $profile_list[$profile]->getPathname());
|
||||
$this->folders += $this->getComponentNames(array($profile_list[$profile]));
|
||||
}
|
||||
}
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
$this->folders += $this->getComponentNames($listing->scan('module'));
|
||||
$this->folders += $this->getComponentNames($listing->scan('theme'));
|
||||
}
|
||||
return $this->folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configuration names and folders for a list of modules or themes.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension[] $list
|
||||
* An associative array of Extension objects, keyed by extension name.
|
||||
*
|
||||
* @return array
|
||||
* Folders indexed by configuration name.
|
||||
*/
|
||||
public function getComponentNames(array $list) {
|
||||
$extension = '.' . $this->getFileExtension();
|
||||
$folders = array();
|
||||
foreach ($list as $extension_object) {
|
||||
// We don't have to use ExtensionDiscovery here because our list of
|
||||
// extensions was already obtained through an ExtensionDiscovery scan.
|
||||
$directory = $this->getComponentFolder($extension_object);
|
||||
if (file_exists($directory)) {
|
||||
$files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
|
||||
foreach ($files as $file) {
|
||||
$folders[$file->getBasename($extension)] = $directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configuration names and folders for Drupal core.
|
||||
*
|
||||
* @return array
|
||||
* Folders indexed by configuration name.
|
||||
*/
|
||||
public function getCoreNames() {
|
||||
$extension = '.' . $this->getFileExtension();
|
||||
$folders = array();
|
||||
$directory = $this->getCoreFolder();
|
||||
if (file_exists($directory)) {
|
||||
$files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
|
||||
foreach ($files as $file) {
|
||||
$folders[$file->getBasename($extension)] = $directory;
|
||||
}
|
||||
}
|
||||
return $folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder inside each component that contains the files.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* The Extension object for the component.
|
||||
*
|
||||
* @return string
|
||||
* The configuration folder name for this component.
|
||||
*/
|
||||
protected function getComponentFolder(Extension $extension) {
|
||||
return $extension->getPath() . '/' . $this->getCollectionDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder inside Drupal core that contains the files.
|
||||
*
|
||||
* @return string
|
||||
* The configuration folder name for core.
|
||||
*/
|
||||
protected function getCoreFolder() {
|
||||
return drupal_get_path('core', 'core') . '/' . $this->getCollectionDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Config\FileStorage::deleteAll().
|
||||
*
|
||||
* @throws \Drupal\Core\Config\StorageException
|
||||
*/
|
||||
public function deleteAll($prefix = '') {
|
||||
throw new StorageException('Delete operation is not allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the static cache.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->folders = NULL;
|
||||
}
|
||||
|
||||
}
|
117
core/lib/Drupal/Core/Config/NullStorage.php
Normal file
117
core/lib/Drupal/Core/Config/NullStorage.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\NullStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines a stub storage.
|
||||
*
|
||||
* This storage is always empty; the controller reads and writes nothing.
|
||||
*
|
||||
* The stub implementation is needed for synchronizing configuration during
|
||||
* installation of a module, in which case all configuration being shipped with
|
||||
* the module is known to be new. Therefore, the module installation process is
|
||||
* able to short-circuit the full diff against the active configuration; the
|
||||
* diff would yield all currently available configuration as items to remove,
|
||||
* since they do not exist in the module's default configuration directory.
|
||||
*
|
||||
* This also can be used for testing purposes.
|
||||
*/
|
||||
class NullStorage implements StorageInterface {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::exists().
|
||||
*/
|
||||
public function exists($name) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::read().
|
||||
*/
|
||||
public function read($name) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::readMultiple().
|
||||
*/
|
||||
public function readMultiple(array $names) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::write().
|
||||
*/
|
||||
public function write($name, array $data) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::delete().
|
||||
*/
|
||||
public function delete($name) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::rename().
|
||||
*/
|
||||
public function rename($name, $new_name) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::encode().
|
||||
*/
|
||||
public function encode($data) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::decode().
|
||||
*/
|
||||
public function decode($raw) {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::listAll().
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::deleteAll().
|
||||
*/
|
||||
public function deleteAll($prefix = '') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
101
core/lib/Drupal/Core/Config/PreExistingConfigException.php
Normal file
101
core/lib/Drupal/Core/Config/PreExistingConfigException.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\PreExistingConfigException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* An exception thrown if configuration with the same name already exists.
|
||||
*/
|
||||
class PreExistingConfigException extends ConfigException {
|
||||
|
||||
/**
|
||||
* A list of configuration objects that already exist in active configuration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configObjects = [];
|
||||
|
||||
/**
|
||||
* The name of the module that is being installed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* Gets the list of configuration objects that already exist.
|
||||
*
|
||||
* @return array
|
||||
* A list of configuration objects that already exist in active
|
||||
* configuration keyed by collection.
|
||||
*/
|
||||
public function getConfigObjects() {
|
||||
return $this->configObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the extension that is being installed.
|
||||
*
|
||||
* @return string
|
||||
* The name of the extension that is being installed.
|
||||
*/
|
||||
public function getExtension() {
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an exception for an extension and a list of configuration objects.
|
||||
*
|
||||
* @param $extension
|
||||
* The name of the extension that is being installed.
|
||||
* @param array $config_objects
|
||||
* A list of configuration objects that already exist in active
|
||||
* configuration, keyed by config collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\PreExistingConfigException
|
||||
*/
|
||||
public static function create($extension, array $config_objects) {
|
||||
$message = SafeMarkup::format('Configuration objects (@config_names) provided by @extension already exist in active configuration',
|
||||
array(
|
||||
'@config_names' => implode(', ', static::flattenConfigObjects($config_objects)),
|
||||
'@extension' => $extension
|
||||
)
|
||||
);
|
||||
$e = new static($message);
|
||||
$e->configObjects = $config_objects;
|
||||
$e->extension = $extension;
|
||||
return $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens the config object array to a single dimensional list.
|
||||
*
|
||||
* @param array $config_objects
|
||||
* A list of configuration objects that already exist in active
|
||||
* configuration, keyed by config collection.
|
||||
*
|
||||
* @return array
|
||||
* A list of configuration objects that have been prefixed with their
|
||||
* collection.
|
||||
*/
|
||||
public static function flattenConfigObjects(array $config_objects) {
|
||||
$flat_config_objects = array();
|
||||
foreach ($config_objects as $collection => $config_names) {
|
||||
$config_names = array_map(function ($config_name) use ($collection) {
|
||||
if ($collection != StorageInterface::DEFAULT_COLLECTION) {
|
||||
$config_name = str_replace('.', DIRECTORY_SEPARATOR, $collection) . DIRECTORY_SEPARATOR . $config_name;
|
||||
}
|
||||
return $config_name;
|
||||
}, $config_names);
|
||||
$flat_config_objects = array_merge($flat_config_objects, $config_names);
|
||||
}
|
||||
return $flat_config_objects;
|
||||
}
|
||||
|
||||
}
|
190
core/lib/Drupal/Core/Config/Schema/ArrayElement.php
Normal file
190
core/lib/Drupal/Core/Config/Schema/ArrayElement.php
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\ArrayElement.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\TypedData\TypedData;
|
||||
|
||||
/**
|
||||
* Defines a generic configuration element that contains multiple properties.
|
||||
*/
|
||||
abstract class ArrayElement extends TypedData implements \IteratorAggregate, TypedConfigInterface {
|
||||
|
||||
/**
|
||||
* The typed config manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedConfig;
|
||||
|
||||
/**
|
||||
* The configuration value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Parsed elements.
|
||||
*/
|
||||
protected $elements;
|
||||
|
||||
/**
|
||||
* Gets valid configuration data keys.
|
||||
*
|
||||
* @return array
|
||||
* Array of valid configuration data keys.
|
||||
*/
|
||||
protected function getAllKeys() {
|
||||
return is_array($this->value) ? array_keys($this->value) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an array of contained elements.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface[]
|
||||
* An array of elements contained in this element.
|
||||
*/
|
||||
protected function parse() {
|
||||
$elements = array();
|
||||
foreach ($this->getAllKeys() as $key) {
|
||||
$value = isset($this->value[$key]) ? $this->value[$key] : NULL;
|
||||
$definition = $this->getElementDefinition($key);
|
||||
$elements[$key] = $this->createElement($definition, $value, $key);
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data definition object for contained element.
|
||||
*
|
||||
* @param int|string $key
|
||||
* Property name or index of the element.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\DataDefinitionInterface
|
||||
*/
|
||||
protected abstract function getElementDefinition($key);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($name) {
|
||||
$parts = explode('.', $name);
|
||||
$root_key = array_shift($parts);
|
||||
$elements = $this->getElements();
|
||||
if (isset($elements[$root_key])) {
|
||||
$element = $elements[$root_key];
|
||||
// If $property_name contained a dot recurse into the keys.
|
||||
while ($element && ($key = array_shift($parts)) !== NULL) {
|
||||
if ($element instanceof TypedConfigInterface) {
|
||||
$element = $element->get($key);
|
||||
}
|
||||
else {
|
||||
$element = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($element)) {
|
||||
return $element;
|
||||
}
|
||||
else {
|
||||
throw new \InvalidArgumentException(SafeMarkup::format("The configuration property @key doesn't exist.", array('@key' => $name)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getElements() {
|
||||
if (!isset($this->elements)) {
|
||||
$this->elements = $this->parse();
|
||||
}
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return empty($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toArray() {
|
||||
return isset($this->value) ? $this->value : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onChange($name) {
|
||||
// Notify the parent of changes.
|
||||
if (isset($this->parent)) {
|
||||
$this->parent->onChange($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator();
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new \ArrayIterator($this->getElements());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a contained typed configuration object.
|
||||
*
|
||||
* @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
|
||||
* The data definition object.
|
||||
* @param mixed $value
|
||||
* (optional) The data value. If set, it has to match one of the supported
|
||||
* data type format as documented for the data type classes.
|
||||
* @param string $key
|
||||
* The key of the contained element.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
protected function createElement($definition, $value, $key) {
|
||||
return $this->typedConfig->create($definition, $value, $key, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data definition object from a type definition array and
|
||||
* actual configuration data.
|
||||
*
|
||||
* @param array $definition
|
||||
* The base type definition array, for which a data definition should be
|
||||
* created.
|
||||
* @param $value
|
||||
* The value of the configuration element.
|
||||
* @param string $key
|
||||
* The key of the contained element.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\DataDefinitionInterface
|
||||
*/
|
||||
protected function buildDataDefinition($definition, $value, $key) {
|
||||
return $this->typedConfig->buildDataDefinition($definition, $value, $key, $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the typed config manager on the instance.
|
||||
*
|
||||
* This must be called immediately after construction to enable
|
||||
* self::parseElement() and self::buildDataDefinition() to work.
|
||||
*
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
||||
*/
|
||||
public function setTypedConfig(TypedConfigManagerInterface $typed_config) {
|
||||
$this->typedConfig = $typed_config;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\ConfigSchemaAlterException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* Exception for when hook_config_schema_info_alter() adds or removes schema.
|
||||
*/
|
||||
class ConfigSchemaAlterException extends \RuntimeException {
|
||||
}
|
50
core/lib/Drupal/Core/Config/Schema/ConfigSchemaDiscovery.php
Normal file
50
core/lib/Drupal/Core/Config/Schema/ConfigSchemaDiscovery.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\ConfigSchemaDiscovery.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
||||
/**
|
||||
* Allows YAML files to define config schema types.
|
||||
*/
|
||||
class ConfigSchemaDiscovery implements DiscoveryInterface {
|
||||
|
||||
use DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* A storage instance for reading configuration schema data.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $schemaStorage;
|
||||
|
||||
/**
|
||||
* Constructs a ConfigSchemaDiscovery object.
|
||||
*
|
||||
* @param $schema_storage
|
||||
* The storage object to use for reading schema data.
|
||||
*/
|
||||
function __construct(StorageInterface $schema_storage) {
|
||||
$this->schemaStorage = $schema_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
$definitions = array();
|
||||
foreach ($this->schemaStorage->readMultiple($this->schemaStorage->listAll()) as $schema) {
|
||||
foreach ($schema as $type => $definition) {
|
||||
$definitions[$type] = $definition;
|
||||
}
|
||||
}
|
||||
return $definitions;
|
||||
}
|
||||
}
|
24
core/lib/Drupal/Core/Config/Schema/Element.php
Normal file
24
core/lib/Drupal/Core/Config/Schema/Element.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Element.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\TypedData\TypedData;
|
||||
|
||||
/**
|
||||
* Defines a generic configuration element.
|
||||
*/
|
||||
abstract class Element extends TypedData {
|
||||
|
||||
/**
|
||||
* The configuration value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
}
|
15
core/lib/Drupal/Core/Config/Schema/Ignore.php
Normal file
15
core/lib/Drupal/Core/Config/Schema/Ignore.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Ignore.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* Configuration property to ignore.
|
||||
*/
|
||||
class Ignore extends Element {
|
||||
|
||||
}
|
34
core/lib/Drupal/Core/Config/Schema/Mapping.php
Normal file
34
core/lib/Drupal/Core/Config/Schema/Mapping.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Mapping.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* Defines a mapping configuration element.
|
||||
*
|
||||
* This object may contain any number and type of nested properties and each
|
||||
* property key may have its own definition in the 'mapping' property of the
|
||||
* configuration schema.
|
||||
*
|
||||
* Properties in the configuration value that are not defined in the mapping
|
||||
* will get the 'undefined' data type.
|
||||
*
|
||||
* Read https://www.drupal.org/node/1905070 for more details about configuration
|
||||
* schema, types and type resolution.
|
||||
*/
|
||||
class Mapping extends ArrayElement {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getElementDefinition($key) {
|
||||
$value = isset($this->value[$key]) ? $this->value[$key] : NULL;
|
||||
$definition = isset($this->definition['mapping'][$key]) ? $this->definition['mapping'][$key] : array();
|
||||
return $this->buildDataDefinition($definition, $value, $key);
|
||||
}
|
||||
|
||||
}
|
130
core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
Normal file
130
core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\SchemaCheckTrait.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\TypedData\PrimitiveInterface;
|
||||
use Drupal\Core\TypedData\TraversableTypedDataInterface;
|
||||
use Drupal\Core\TypedData\Type\BooleanInterface;
|
||||
use Drupal\Core\TypedData\Type\StringInterface;
|
||||
use Drupal\Core\TypedData\Type\FloatInterface;
|
||||
use Drupal\Core\TypedData\Type\IntegerInterface;
|
||||
|
||||
/**
|
||||
* Provides a trait for checking configuration schema.
|
||||
*/
|
||||
trait SchemaCheckTrait {
|
||||
|
||||
/**
|
||||
* The config schema wrapper object for the configuration object under test.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Schema\Element
|
||||
*/
|
||||
protected $schema;
|
||||
|
||||
/**
|
||||
* The configuration object name under test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $configName;
|
||||
|
||||
/**
|
||||
* Checks the TypedConfigManager has a valid schema for the configuration.
|
||||
*
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
||||
* The TypedConfigManager.
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param array $config_data
|
||||
* The configuration data.
|
||||
*
|
||||
* @return array|bool
|
||||
* FALSE if no schema found. List of errors if any found. TRUE if fully
|
||||
* valid.
|
||||
*/
|
||||
public function checkConfigSchema(TypedConfigManagerInterface $typed_config, $config_name, $config_data) {
|
||||
$this->configName = $config_name;
|
||||
if (!$typed_config->hasConfigSchema($config_name)) {
|
||||
return FALSE;
|
||||
}
|
||||
$definition = $typed_config->getDefinition($config_name);
|
||||
$data_definition = $typed_config->buildDataDefinition($definition, $config_data);
|
||||
$this->schema = $typed_config->create($data_definition, $config_data);
|
||||
$errors = array();
|
||||
foreach ($config_data as $key => $value) {
|
||||
$errors = array_merge($errors, $this->checkValue($key, $value));
|
||||
}
|
||||
if (empty($errors)) {
|
||||
return TRUE;
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check data type.
|
||||
*
|
||||
* @param string $key
|
||||
* A string of configuration key.
|
||||
* @param mixed $value
|
||||
* Value of given key.
|
||||
*
|
||||
* @return array
|
||||
* List of errors found while checking with the corresponding schema.
|
||||
*/
|
||||
protected function checkValue($key, $value) {
|
||||
$error_key = $this->configName . ':' . $key;
|
||||
$element = $this->schema->get($key);
|
||||
if ($element instanceof Undefined) {
|
||||
return array($error_key => 'missing schema');
|
||||
}
|
||||
|
||||
// Do not check value if it is defined to be ignored.
|
||||
if ($element && $element instanceof Ignore) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ($element && is_scalar($value) || $value === NULL) {
|
||||
$success = FALSE;
|
||||
$type = gettype($value);
|
||||
if ($element instanceof PrimitiveInterface) {
|
||||
$success =
|
||||
($type == 'integer' && $element instanceof IntegerInterface) ||
|
||||
// Allow integer values in a float field.
|
||||
(($type == 'double' || $type == 'integer') && $element instanceof FloatInterface) ||
|
||||
($type == 'boolean' && $element instanceof BooleanInterface) ||
|
||||
($type == 'string' && $element instanceof StringInterface) ||
|
||||
// Null values are allowed for all types.
|
||||
($value === NULL);
|
||||
}
|
||||
$class = get_class($element);
|
||||
if (!$success) {
|
||||
return array($error_key => "variable type is $type but applied schema class is $class");
|
||||
}
|
||||
}
|
||||
else {
|
||||
$errors = array();
|
||||
if (!$element instanceof TraversableTypedDataInterface) {
|
||||
$errors[$error_key] = 'non-scalar value but not defined as an array (such as mapping or sequence)';
|
||||
}
|
||||
|
||||
// Go on processing so we can get errors on all levels. Any non-scalar
|
||||
// value must be an array so cast to an array.
|
||||
if (!is_array($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
// Recurse into any nested keys.
|
||||
foreach ($value as $nested_value_key => $nested_value) {
|
||||
$errors = array_merge($errors, $this->checkValue($key . '.' . $nested_value_key, $nested_value));
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
// No errors found.
|
||||
return array();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\SchemaIncompleteException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* An exception thrown when a config schema is incomplete.
|
||||
*/
|
||||
class SchemaIncompleteException extends \RuntimeException {
|
||||
}
|
37
core/lib/Drupal/Core/Config/Schema/Sequence.php
Normal file
37
core/lib/Drupal/Core/Config/Schema/Sequence.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Sequence.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* Defines a configuration element of type Sequence.
|
||||
*
|
||||
* This object may contain any number and type of nested elements that share
|
||||
* a common definition in the 'sequence' property of the configuration schema.
|
||||
*
|
||||
* Read https://www.drupal.org/node/1905070 for more details about configuration
|
||||
* schema, types and type resolution.
|
||||
*/
|
||||
class Sequence extends ArrayElement {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getElementDefinition($key) {
|
||||
$value = isset($this->value[$key]) ? $this->value[$key] : NULL;
|
||||
// @todo: Remove BC layer for sequence with hyphen in front. https://www.drupal.org/node/2444979
|
||||
$definition = array();
|
||||
if (isset($this->definition['sequence'][0])) {
|
||||
$definition = $this->definition['sequence'][0];
|
||||
}
|
||||
elseif ($this->definition['sequence']) {
|
||||
$definition = $this->definition['sequence'];
|
||||
}
|
||||
return $this->buildDataDefinition($definition, $value, $key);
|
||||
}
|
||||
|
||||
}
|
64
core/lib/Drupal/Core/Config/Schema/TypedConfigInterface.php
Normal file
64
core/lib/Drupal/Core/Config/Schema/TypedConfigInterface.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\TypedConfigInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\TypedData\TraversableTypedDataInterface;
|
||||
|
||||
/**
|
||||
* Interface for a typed configuration object that contains multiple elements.
|
||||
*
|
||||
* A list of typed configuration contains any number of items whose type
|
||||
* will depend on the configuration schema but also on the configuration
|
||||
* data being parsed.
|
||||
*
|
||||
* When implementing this interface which extends Traversable, make sure to list
|
||||
* IteratorAggregate or Iterator before this interface in the implements clause.
|
||||
*/
|
||||
interface TypedConfigInterface extends TraversableTypedDataInterface {
|
||||
|
||||
/**
|
||||
* Determines whether the data structure is empty.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if the data structure is empty, FALSE otherwise.
|
||||
*/
|
||||
public function isEmpty();
|
||||
|
||||
/**
|
||||
* Gets an array of contained elements.
|
||||
*
|
||||
* @return array
|
||||
* Array of \Drupal\Core\TypedData\TypedDataInterface objects.
|
||||
*/
|
||||
public function getElements();
|
||||
|
||||
/**
|
||||
* Gets a contained typed configuration element.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the property to get; e.g., 'title' or 'name'. Nested
|
||||
* elements can be get using multiple dot delimited names, for example,
|
||||
* 'page.front'.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If an invalid property name is given.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
* The property object.
|
||||
*/
|
||||
public function get($name);
|
||||
|
||||
/**
|
||||
* Returns an array of all property values.
|
||||
*
|
||||
* @return array
|
||||
* An array of property values, keyed by property name.
|
||||
*/
|
||||
public function toArray();
|
||||
|
||||
}
|
15
core/lib/Drupal/Core/Config/Schema/Undefined.php
Normal file
15
core/lib/Drupal/Core/Config/Schema/Undefined.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Undefined.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* Undefined configuration element.
|
||||
*/
|
||||
class Undefined extends Element {
|
||||
|
||||
}
|
229
core/lib/Drupal/Core/Config/StorableConfigBase.php
Normal file
229
core/lib/Drupal/Core/Config/StorableConfigBase.php
Normal file
|
@ -0,0 +1,229 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorableConfigBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Config\Schema\Ignore;
|
||||
use Drupal\Core\TypedData\PrimitiveInterface;
|
||||
use Drupal\Core\TypedData\Type\FloatInterface;
|
||||
use Drupal\Core\TypedData\Type\IntegerInterface;
|
||||
use Drupal\Core\Config\Schema\Undefined;
|
||||
|
||||
/**
|
||||
* Provides a base class for configuration objects with storage support.
|
||||
*
|
||||
* Encapsulates all capabilities needed for configuration handling for a
|
||||
* specific configuration object, including storage and data type casting.
|
||||
*
|
||||
* The default implementation in \Drupal\Core\Config\Config adds support for
|
||||
* runtime overrides. Extend from StorableConfigBase directly to manage
|
||||
* configuration with a storage backend that does not support overrides.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Config
|
||||
*/
|
||||
abstract class StorableConfigBase extends ConfigBase {
|
||||
|
||||
/**
|
||||
* The storage used to load and save this configuration object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The config schema wrapper object for this configuration object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Schema\Element
|
||||
*/
|
||||
protected $schemaWrapper;
|
||||
|
||||
/**
|
||||
* The typed config manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedConfigManager;
|
||||
|
||||
/**
|
||||
* Whether the configuration object is new or has been saved to the storage.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isNew = TRUE;
|
||||
|
||||
/**
|
||||
* The data of the configuration object.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $originalData = array();
|
||||
|
||||
/**
|
||||
* Saves the configuration object.
|
||||
*
|
||||
* @param bool $has_trusted_data
|
||||
* Set to TRUE if the configuration data has already been checked to ensure
|
||||
* it conforms to schema. Generally this is only used during module and
|
||||
* theme installation.
|
||||
*
|
||||
* Must invalidate the cache tags associated with the configuration object.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
|
||||
*/
|
||||
abstract public function save($has_trusted_data = FALSE);
|
||||
|
||||
/**
|
||||
* Deletes the configuration object.
|
||||
*
|
||||
* Must invalidate the cache tags associated with the configuration object.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
abstract public function delete();
|
||||
|
||||
/**
|
||||
* Initializes a configuration object with pre-loaded data.
|
||||
*
|
||||
* @param array $data
|
||||
* Array of loaded data for this configuration object.
|
||||
*
|
||||
* @return $this
|
||||
* The configuration object.
|
||||
*/
|
||||
public function initWithData(array $data) {
|
||||
$this->isNew = FALSE;
|
||||
$this->setData($data, FALSE);
|
||||
$this->originalData = $this->data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this configuration object is new.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this configuration object does not exist in storage.
|
||||
*/
|
||||
public function isNew() {
|
||||
return $this->isNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the storage used to load and save this configuration object.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* The configuration storage object.
|
||||
*/
|
||||
public function getStorage() {
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schema wrapper for the whole configuration object.
|
||||
*
|
||||
* The schema wrapper is dependent on the configuration name and the whole
|
||||
* data structure, so if the name or the data changes in any way, the wrapper
|
||||
* should be reset.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Schema\Element
|
||||
*/
|
||||
protected function getSchemaWrapper() {
|
||||
if (!isset($this->schemaWrapper)) {
|
||||
$definition = $this->typedConfigManager->getDefinition($this->name);
|
||||
$data_definition = $this->typedConfigManager->buildDataDefinition($definition, $this->data);
|
||||
$this->schemaWrapper = $this->typedConfigManager->create($data_definition, $this->data);
|
||||
}
|
||||
return $this->schemaWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the values are allowed data types.
|
||||
*
|
||||
* @param string $key
|
||||
* A string that maps to a key within the configuration data.
|
||||
* @param string $value
|
||||
* Value to associate with the key.
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
|
||||
* If the value is unsupported in configuration.
|
||||
*/
|
||||
protected function validateValue($key, $value) {
|
||||
// Minimal validation. Should not try to serialize resources or non-arrays.
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $nested_value_key => $nested_value) {
|
||||
$this->validateValue($key . '.' . $nested_value_key, $nested_value);
|
||||
}
|
||||
}
|
||||
elseif ($value !== NULL && !is_scalar($value)) {
|
||||
throw new UnsupportedDataTypeConfigException(SafeMarkup::format('Invalid data type for config element @name:@key', array(
|
||||
'@name' => $this->getName(),
|
||||
'@key' => $key,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts the value to correct data type using the configuration schema.
|
||||
*
|
||||
* @param string $key
|
||||
* A string that maps to a key within the configuration data.
|
||||
* @param string $value
|
||||
* Value to associate with the key.
|
||||
*
|
||||
* @return mixed
|
||||
* The value cast to the type indicated in the schema.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
|
||||
* If the value is unsupported in configuration.
|
||||
*/
|
||||
protected function castValue($key, $value) {
|
||||
$element = $this->getSchemaWrapper()->get($key);
|
||||
// Do not cast value if it is unknown or defined to be ignored.
|
||||
if ($element && ($element instanceof Undefined || $element instanceof Ignore)) {
|
||||
// Do validate the value (may throw UnsupportedDataTypeConfigException)
|
||||
// to ensure unsupported types are not supported in this case either.
|
||||
$this->validateValue($key, $value);
|
||||
return $value;
|
||||
}
|
||||
if (is_scalar($value) || $value === NULL) {
|
||||
if ($element && $element instanceof PrimitiveInterface) {
|
||||
// Special handling for integers and floats since the configuration
|
||||
// system is primarily concerned with saving values from the Form API
|
||||
// we have to special case the meaning of an empty string for numeric
|
||||
// types. In PHP this would be casted to a 0 but for the purposes of
|
||||
// configuration we need to treat this as a NULL.
|
||||
$empty_value = $value === '' && ($element instanceof IntegerInterface || $element instanceof FloatInterface);
|
||||
|
||||
if ($value === NULL || $empty_value) {
|
||||
$value = NULL;
|
||||
}
|
||||
else {
|
||||
$value = $element->getCastedValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Throw exception on any non-scalar or non-array value.
|
||||
if (!is_array($value)) {
|
||||
throw new UnsupportedDataTypeConfigException(SafeMarkup::format('Invalid data type for config element @name:@key', array(
|
||||
'@name' => $this->getName(),
|
||||
'@key' => $key,
|
||||
)));
|
||||
}
|
||||
// Recurse into any nested keys.
|
||||
foreach ($value as $nested_value_key => $nested_value) {
|
||||
$value[$nested_value_key] = $this->castValue($key . '.' . $nested_value_key, $nested_value);
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
20
core/lib/Drupal/Core/Config/StorageCacheInterface.php
Normal file
20
core/lib/Drupal/Core/Config/StorageCacheInterface.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageCacheInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines an interface for cached configuration storage.
|
||||
*/
|
||||
interface StorageCacheInterface {
|
||||
|
||||
/**
|
||||
* Reset the static cache of the listAll() cache.
|
||||
*/
|
||||
public function resetListCache();
|
||||
|
||||
}
|
468
core/lib/Drupal/Core/Config/StorageComparer.php
Normal file
468
core/lib/Drupal/Core/Config/StorageComparer.php
Normal file
|
@ -0,0 +1,468 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageComparer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\MemoryBackend;
|
||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* Defines a config storage comparer.
|
||||
*/
|
||||
class StorageComparer implements StorageComparerInterface {
|
||||
use DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The source storage used to discover configuration changes.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $sourceStorage;
|
||||
|
||||
/**
|
||||
* The source storages keyed by collection.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
protected $sourceStorages;
|
||||
|
||||
/**
|
||||
* The target storage used to write configuration changes.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $targetStorage;
|
||||
|
||||
/**
|
||||
* The target storages keyed by collection.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
protected $targetStorages;
|
||||
|
||||
/**
|
||||
* The configuration manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigManagerInterface
|
||||
*/
|
||||
protected $configManager;
|
||||
|
||||
/**
|
||||
* List of changes to between the source storage and the target storage.
|
||||
*
|
||||
* The list is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $changelist;
|
||||
|
||||
/**
|
||||
* Sorted list of all the configuration object names in the source storage.
|
||||
*
|
||||
* The list is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceNames = array();
|
||||
|
||||
/**
|
||||
* Sorted list of all the configuration object names in the target storage.
|
||||
*
|
||||
* The list is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $targetNames = array();
|
||||
|
||||
/**
|
||||
* A memory cache backend to statically cache source configuration data.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\MemoryBackend
|
||||
*/
|
||||
protected $sourceCacheStorage;
|
||||
|
||||
/**
|
||||
* A memory cache backend to statically cache target configuration data.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\MemoryBackend
|
||||
*/
|
||||
protected $targetCacheStorage;
|
||||
|
||||
/**
|
||||
* Constructs the Configuration storage comparer.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||
* Storage object used to read configuration.
|
||||
* @param \Drupal\Core\Config\StorageInterface $target_storage
|
||||
* Storage object used to write configuration.
|
||||
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
||||
* The configuration manager.
|
||||
*/
|
||||
public function __construct(StorageInterface $source_storage, StorageInterface $target_storage, ConfigManagerInterface $config_manager) {
|
||||
// Wrap the storages in a static cache so that multiple reads of the same
|
||||
// raw configuration object are not costly.
|
||||
$this->sourceCacheStorage = new MemoryBackend(__CLASS__ . '::source');
|
||||
$this->sourceStorage = new CachedStorage(
|
||||
$source_storage,
|
||||
$this->sourceCacheStorage
|
||||
);
|
||||
$this->targetCacheStorage = new MemoryBackend(__CLASS__ . '::target');
|
||||
$this->targetStorage = new CachedStorage(
|
||||
$target_storage,
|
||||
$this->targetCacheStorage
|
||||
);
|
||||
$this->configManager = $config_manager;
|
||||
$this->changelist[StorageInterface::DEFAULT_COLLECTION] = $this->getEmptyChangelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if (!isset($this->sourceStorages[$collection])) {
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->sourceStorages[$collection] = $this->sourceStorage;
|
||||
}
|
||||
else {
|
||||
$this->sourceStorages[$collection] = $this->sourceStorage->createCollection($collection);
|
||||
}
|
||||
}
|
||||
return $this->sourceStorages[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTargetStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if (!isset($this->targetStorages[$collection])) {
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->targetStorages[$collection] = $this->targetStorage;
|
||||
}
|
||||
else {
|
||||
$this->targetStorages[$collection] = $this->targetStorage->createCollection($collection);
|
||||
}
|
||||
}
|
||||
return $this->targetStorages[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEmptyChangelist() {
|
||||
return array(
|
||||
'create' => array(),
|
||||
'update' => array(),
|
||||
'delete' => array(),
|
||||
'rename' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if ($op) {
|
||||
return $this->changelist[$collection][$op];
|
||||
}
|
||||
return $this->changelist[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds changes to the changelist.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to add changes for.
|
||||
* @param string $op
|
||||
* The change operation performed. Either delete, create, rename, or update.
|
||||
* @param array $changes
|
||||
* Array of changes to add to the changelist.
|
||||
* @param array $sort_order
|
||||
* Array to sort that can be used to sort the changelist. This array must
|
||||
* contain all the items that are in the change list.
|
||||
*/
|
||||
protected function addChangeList($collection, $op, array $changes, array $sort_order = NULL) {
|
||||
// Only add changes that aren't already listed.
|
||||
$changes = array_diff($changes, $this->changelist[$collection][$op]);
|
||||
$this->changelist[$collection][$op] = array_merge($this->changelist[$collection][$op], $changes);
|
||||
if (isset($sort_order)) {
|
||||
$count = count($this->changelist[$collection][$op]);
|
||||
// Sort the changelist in the same order as the $sort_order array and
|
||||
// ensure the array is keyed from 0.
|
||||
$this->changelist[$collection][$op] = array_values(array_intersect($sort_order, $this->changelist[$collection][$op]));
|
||||
if ($count != count($this->changelist[$collection][$op])) {
|
||||
throw new \InvalidArgumentException(SafeMarkup::format('Sorting the @op changelist should not change its length.', array('@op' => $op)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createChangelist() {
|
||||
foreach ($this->getAllCollectionNames() as $collection) {
|
||||
$this->changelist[$collection] = $this->getEmptyChangelist();
|
||||
$this->getAndSortConfigData($collection);
|
||||
$this->addChangelistCreate($collection);
|
||||
$this->addChangelistUpdate($collection);
|
||||
$this->addChangelistDelete($collection);
|
||||
// Only collections that support configuration entities can have renames.
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->addChangelistRename($collection);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the delete changelist.
|
||||
*
|
||||
* The list of deletes is sorted so that dependencies are deleted after
|
||||
* configuration entities that depend on them. For example, fields should be
|
||||
* deleted after field storages.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistDelete($collection) {
|
||||
$deletes = array_diff(array_reverse($this->targetNames[$collection]), $this->sourceNames[$collection]);
|
||||
$this->addChangeList($collection, 'delete', $deletes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the create changelist.
|
||||
*
|
||||
* The list of creates is sorted so that dependencies are created before
|
||||
* configuration entities that depend on them. For example, field storages
|
||||
* should be created before fields.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistCreate($collection) {
|
||||
$creates = array_diff($this->sourceNames[$collection], $this->targetNames[$collection]);
|
||||
$this->addChangeList($collection, 'create', $creates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the update changelist.
|
||||
*
|
||||
* The list of updates is sorted so that dependencies are created before
|
||||
* configuration entities that depend on them. For example, field storages
|
||||
* should be updated before fields.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistUpdate($collection) {
|
||||
$recreates = array();
|
||||
foreach (array_intersect($this->sourceNames[$collection], $this->targetNames[$collection]) as $name) {
|
||||
$source_data = $this->getSourceStorage($collection)->read($name);
|
||||
$target_data = $this->getTargetStorage($collection)->read($name);
|
||||
if ($source_data !== $target_data) {
|
||||
if (isset($source_data['uuid']) && $source_data['uuid'] !== $target_data['uuid']) {
|
||||
// The entity has the same file as an existing entity but the UUIDs do
|
||||
// not match. This means that the entity has been recreated so config
|
||||
// synchronization should do the same.
|
||||
$recreates[] = $name;
|
||||
}
|
||||
else {
|
||||
$this->addChangeList($collection, 'update', array($name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($recreates)) {
|
||||
// Recreates should become deletes and creates. Deletes should be ordered
|
||||
// so that dependencies are deleted first.
|
||||
$this->addChangeList($collection, 'create', $recreates, $this->sourceNames[$collection]);
|
||||
$this->addChangeList($collection, 'delete', $recreates, array_reverse($this->targetNames[$collection]));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the rename changelist.
|
||||
*
|
||||
* The list of renames is created from the different source and target names
|
||||
* with same UUID. These changes will be removed from the create and delete
|
||||
* lists.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistRename($collection) {
|
||||
// Renames will be present in both the create and delete lists.
|
||||
$create_list = $this->getChangelist('create', $collection);
|
||||
$delete_list = $this->getChangelist('delete', $collection);
|
||||
if (empty($create_list) || empty($delete_list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$create_uuids = array();
|
||||
foreach ($this->sourceNames[$collection] as $name) {
|
||||
$data = $this->getSourceStorage($collection)->read($name);
|
||||
if (isset($data['uuid']) && in_array($name, $create_list)) {
|
||||
$create_uuids[$data['uuid']] = $name;
|
||||
}
|
||||
}
|
||||
if (empty($create_uuids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$renames = array();
|
||||
|
||||
// Renames should be ordered so that dependencies are renamed last. This
|
||||
// ensures that if there is logic in the configuration entity class to keep
|
||||
// names in sync it will still work. $this->targetNames is in the desired
|
||||
// order due to the use of configuration dependencies in
|
||||
// \Drupal\Core\Config\StorageComparer::getAndSortConfigData().
|
||||
// Node type is a good example of a configuration entity that renames other
|
||||
// configuration when it is renamed.
|
||||
// @see \Drupal\node\Entity\NodeType::postSave()
|
||||
foreach ($this->targetNames[$collection] as $name) {
|
||||
$data = $this->getTargetStorage($collection)->read($name);
|
||||
if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) {
|
||||
// Remove the item from the create list.
|
||||
$this->removeFromChangelist($collection, 'create', $create_uuids[$data['uuid']]);
|
||||
// Remove the item from the delete list.
|
||||
$this->removeFromChangelist($collection, 'delete', $name);
|
||||
// Create the rename name.
|
||||
$renames[] = $this->createRenameName($name, $create_uuids[$data['uuid']]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addChangeList($collection, 'rename', $renames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the entry from the given operation changelist for the given name.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
* @param string $op
|
||||
* The changelist to act on. Either delete, create, rename or update.
|
||||
* @param string $name
|
||||
* The name of the configuration to remove.
|
||||
*/
|
||||
protected function removeFromChangelist($collection, $op, $name) {
|
||||
$key = array_search($name, $this->changelist[$collection][$op]);
|
||||
if ($key !== FALSE) {
|
||||
unset($this->changelist[$collection][$op][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function moveRenameToUpdate($rename, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$names = $this->extractRenameNames($rename);
|
||||
$this->removeFromChangelist($collection, 'rename', $rename);
|
||||
$this->addChangeList($collection, 'update', array($names['new_name']), $this->sourceNames[$collection]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->changelist = array(StorageInterface::DEFAULT_COLLECTION => $this->getEmptyChangelist());
|
||||
$this->sourceNames = $this->targetNames = array();
|
||||
// Reset the static configuration data caches.
|
||||
$this->sourceCacheStorage->deleteAll();
|
||||
$this->targetCacheStorage->deleteAll();
|
||||
return $this->createChangelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasChanges() {
|
||||
foreach ($this->getAllCollectionNames() as $collection) {
|
||||
foreach (array('delete', 'create', 'update', 'rename') as $op) {
|
||||
if (!empty($this->changelist[$collection][$op])) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateSiteUuid() {
|
||||
$source = $this->sourceStorage->read('system.site');
|
||||
$target = $this->targetStorage->read('system.site');
|
||||
return $source['uuid'] === $target['uuid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sorts configuration data from the source and target storages.
|
||||
*/
|
||||
protected function getAndSortConfigData($collection) {
|
||||
$source_storage = $this->getSourceStorage($collection);
|
||||
$target_storage = $this->getTargetStorage($collection);
|
||||
$target_names = $target_storage->listAll();
|
||||
$source_names = $source_storage->listAll();
|
||||
// Prime the static caches by reading all the configuration in the source
|
||||
// and target storages.
|
||||
$target_data = $target_storage->readMultiple($target_names);
|
||||
$source_data = $source_storage->readMultiple($source_names);
|
||||
// If the collection only supports simple configuration do not use
|
||||
// configuration dependencies.
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$dependency_manager = new ConfigDependencyManager();
|
||||
$this->targetNames[$collection] = $dependency_manager->setData($target_data)->sortAll();
|
||||
$this->sourceNames[$collection] = $dependency_manager->setData($source_data)->sortAll();
|
||||
}
|
||||
else {
|
||||
$this->targetNames[$collection] = $target_names;
|
||||
$this->sourceNames[$collection] = $source_names;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a rename name from the old and new names for the object.
|
||||
*
|
||||
* @param string $old_name
|
||||
* The old configuration object name.
|
||||
* @param string $new_name
|
||||
* The new configuration object name.
|
||||
*
|
||||
* @return string
|
||||
* The configuration change name that encodes both the old and the new name.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerInterface::extractRenameNames()
|
||||
*/
|
||||
protected function createRenameName($name1, $name2) {
|
||||
return $name1 . '::' . $name2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extractRenameNames($name) {
|
||||
$names = explode('::', $name, 2);
|
||||
return array(
|
||||
'old_name' => $names[0],
|
||||
'new_name' => $names[1],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames($include_default = TRUE) {
|
||||
$collections = array_unique(array_merge($this->sourceStorage->getAllCollectionNames(), $this->targetStorage->getAllCollectionNames()));
|
||||
if ($include_default) {
|
||||
array_unshift($collections, StorageInterface::DEFAULT_COLLECTION);
|
||||
}
|
||||
return $collections;
|
||||
}
|
||||
|
||||
}
|
130
core/lib/Drupal/Core/Config/StorageComparerInterface.php
Normal file
130
core/lib/Drupal/Core/Config/StorageComparerInterface.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageComparerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines an interface for comparison of configuration storage objects.
|
||||
*/
|
||||
interface StorageComparerInterface {
|
||||
|
||||
/**
|
||||
* Gets the configuration source storage.
|
||||
*
|
||||
* @param string $collection
|
||||
* (optional) The storage collection to use. Defaults to the
|
||||
* default collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* Storage object used to read configuration.
|
||||
*/
|
||||
public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Gets the configuration target storage.
|
||||
*
|
||||
* @param string $collection
|
||||
* (optional) The storage collection to use. Defaults to the
|
||||
* default collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* Storage object used to write configuration.
|
||||
*/
|
||||
public function getTargetStorage($collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Gets an empty changelist.
|
||||
*
|
||||
* @return array
|
||||
* An empty changelist array.
|
||||
*/
|
||||
public function getEmptyChangelist();
|
||||
|
||||
/**
|
||||
* Gets the list of differences to import.
|
||||
*
|
||||
* @param string $op
|
||||
* (optional) A change operation. Either delete, create or update. If
|
||||
* supplied the returned list will be limited to this operation.
|
||||
* @param string $collection
|
||||
* (optional) The collection to get the changelist for. Defaults to the
|
||||
* default collection.
|
||||
*
|
||||
* @return array
|
||||
* An array of config changes that are yet to be imported.
|
||||
*/
|
||||
public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Recalculates the differences.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* An object which implements the StorageComparerInterface.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Checks if there are any operations with changes to process.
|
||||
*
|
||||
* Until the changelist has been calculated this will always be FALSE.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerInterface::createChangelist().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are changes to process and FALSE if not.
|
||||
*/
|
||||
public function hasChanges();
|
||||
|
||||
/**
|
||||
* Validates that the system.site::uuid in the source and target match.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if identical, FALSE if not.
|
||||
*/
|
||||
public function validateSiteUuid();
|
||||
|
||||
/**
|
||||
* Moves a rename operation to an update.
|
||||
*
|
||||
* @param string $rename
|
||||
* The rename name, as provided by ConfigImporter::createRenameName().
|
||||
* @param string $collection
|
||||
* (optional) The collection where the configuration is stored. Defaults to
|
||||
* the default collection.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter::createRenameName()
|
||||
*/
|
||||
public function moveRenameToUpdate($rename, $collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Extracts old and new configuration names from a configuration change name.
|
||||
*
|
||||
* @param string $name
|
||||
* The configuration change name, as provided by
|
||||
* ConfigImporter::createRenameName().
|
||||
*
|
||||
* @return array
|
||||
* An associative array of configuration names. The array keys are
|
||||
* 'old_name' and and 'new_name' representing the old and name configuration
|
||||
* object names during a rename operation.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparer::createRenameNames()
|
||||
*/
|
||||
public function extractRenameNames($name);
|
||||
|
||||
/**
|
||||
* Gets the existing collections from both the target and source storage.
|
||||
*
|
||||
* @param bool $include_default
|
||||
* (optional) Include the default collection. Defaults to TRUE.
|
||||
*
|
||||
* @return array
|
||||
* An array of existing collection names.
|
||||
*/
|
||||
public function getAllCollectionNames($include_default = TRUE);
|
||||
|
||||
}
|
13
core/lib/Drupal/Core/Config/StorageException.php
Normal file
13
core/lib/Drupal/Core/Config/StorageException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* An exception thrown in case of storage operation errors.
|
||||
*/
|
||||
class StorageException extends ConfigException {}
|
206
core/lib/Drupal/Core/Config/StorageInterface.php
Normal file
206
core/lib/Drupal/Core/Config/StorageInterface.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines an interface for configuration storage.
|
||||
*
|
||||
* Classes implementing this interface allow reading and writing configuration
|
||||
* data from and to the storage.
|
||||
*/
|
||||
interface StorageInterface {
|
||||
|
||||
/**
|
||||
* The default collection name.
|
||||
*/
|
||||
const DEFAULT_COLLECTION = '';
|
||||
|
||||
/**
|
||||
* Returns whether a configuration object exists.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of a configuration object to test.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the configuration object exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($name);
|
||||
|
||||
/**
|
||||
* Reads configuration data from the storage.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of a configuration object to load.
|
||||
*
|
||||
* @return array|bool
|
||||
* The configuration data stored for the configuration object name. If no
|
||||
* configuration data exists for the given name, FALSE is returned.
|
||||
*/
|
||||
public function read($name);
|
||||
|
||||
/**
|
||||
* Reads configuration data from the storage.
|
||||
*
|
||||
* @param array $names
|
||||
* List of names of the configuration objects to load.
|
||||
*
|
||||
* @return array
|
||||
* A list of the configuration data stored for the configuration object name
|
||||
* that could be loaded for the passed list of names.
|
||||
*/
|
||||
public function readMultiple(array $names);
|
||||
|
||||
/**
|
||||
* Writes configuration data to the storage.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of a configuration object to save.
|
||||
* @param array $data
|
||||
* The configuration data to write.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success, FALSE in case of an error.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\StorageException
|
||||
* If the back-end storage does not exist and cannot be created.
|
||||
*/
|
||||
public function write($name, array $data);
|
||||
|
||||
/**
|
||||
* Deletes a configuration object from the storage.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of a configuration object to delete.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success, FALSE otherwise.
|
||||
*/
|
||||
public function delete($name);
|
||||
|
||||
/**
|
||||
* Renames a configuration object in the storage.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of a configuration object to rename.
|
||||
* @param string $new_name
|
||||
* The new name of a configuration object.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success, FALSE otherwise.
|
||||
*/
|
||||
public function rename($name, $new_name);
|
||||
|
||||
/**
|
||||
* Encodes configuration data into the storage-specific format.
|
||||
*
|
||||
* @param array $data
|
||||
* The configuration data to encode.
|
||||
*
|
||||
* @return string
|
||||
* The encoded configuration data.
|
||||
*
|
||||
* This is a publicly accessible static method to allow for alternative
|
||||
* usages in data conversion scripts and also tests.
|
||||
*/
|
||||
public function encode($data);
|
||||
|
||||
/**
|
||||
* Decodes configuration data from the storage-specific format.
|
||||
*
|
||||
* @param string $raw
|
||||
* The raw configuration data string to decode.
|
||||
*
|
||||
* @return array
|
||||
* The decoded configuration data as an associative array.
|
||||
*
|
||||
* This is a publicly accessible static method to allow for alternative
|
||||
* usages in data conversion scripts and also tests.
|
||||
*/
|
||||
public function decode($raw);
|
||||
|
||||
/**
|
||||
* Gets configuration object names starting with a given prefix.
|
||||
*
|
||||
* Given the following configuration objects:
|
||||
* - node.type.article
|
||||
* - node.type.page
|
||||
*
|
||||
* Passing the prefix 'node.type.' will return an array containing the above
|
||||
* names.
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration object
|
||||
* names that exist are returned.
|
||||
*
|
||||
* @return array
|
||||
* An array containing matching configuration object names.
|
||||
*/
|
||||
public function listAll($prefix = '');
|
||||
|
||||
/**
|
||||
* Deletes configuration objects whose names start with a given prefix.
|
||||
*
|
||||
* Given the following configuration object names:
|
||||
* - node.type.article
|
||||
* - node.type.page
|
||||
*
|
||||
* Passing the prefix 'node.type.' will delete the above configuration
|
||||
* objects.
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration
|
||||
* objects that exist will be deleted.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE on success, FALSE otherwise.
|
||||
*/
|
||||
public function deleteAll($prefix = '');
|
||||
|
||||
/**
|
||||
* Creates a collection on the storage.
|
||||
*
|
||||
* A configuration storage can contain multiple sets of configuration objects
|
||||
* in partitioned collections. The collection name identifies the current
|
||||
* collection used.
|
||||
*
|
||||
* Implementations of this method must provide a new instance to avoid side
|
||||
* effects caused by the fact that Config objects have their storage injected.
|
||||
*
|
||||
* @param string $collection
|
||||
* The collection name. Valid collection names conform to the following
|
||||
* regex [a-zA-Z_.]. A storage does not need to have a collection set.
|
||||
* However, if a collection is set, then storage should use it to store
|
||||
* configuration in a way that allows retrieval of configuration for a
|
||||
* particular collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* A new instance of the storage backend with the collection set.
|
||||
*/
|
||||
public function createCollection($collection);
|
||||
|
||||
/**
|
||||
* Gets the existing collections.
|
||||
*
|
||||
* A configuration storage can contain multiple sets of configuration objects
|
||||
* in partitioned collections. The collection key name identifies the current
|
||||
* collection used.
|
||||
*
|
||||
* @return array
|
||||
* An array of existing collection names.
|
||||
*/
|
||||
public function getAllCollectionNames();
|
||||
|
||||
/**
|
||||
* Gets the name of the current collection the storage is using.
|
||||
*
|
||||
* @return string
|
||||
* The current collection name.
|
||||
*/
|
||||
public function getCollectionName();
|
||||
|
||||
}
|
111
core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php
Normal file
111
core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Testing\ConfigSchemaChecker.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Testing;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\Config\Schema\SchemaCheckTrait;
|
||||
use Drupal\Core\Config\Schema\SchemaIncompleteException;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Listens to the config save event and validates schema.
|
||||
*
|
||||
* If tests have the $strictConfigSchema property set to TRUE this event
|
||||
* listener will be added to the container and throw exceptions if configuration
|
||||
* is invalid.
|
||||
*
|
||||
* @see \Drupal\simpletest\WebTestBase::setUp()
|
||||
* @see \Drupal\simpletest\KernelTestBase::containerBuild()
|
||||
*/
|
||||
class ConfigSchemaChecker implements EventSubscriberInterface {
|
||||
use SchemaCheckTrait;
|
||||
|
||||
/**
|
||||
* The typed config manger.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedManager;
|
||||
|
||||
/**
|
||||
* An array of config checked already. Keyed by config name and a checksum.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $checked = array();
|
||||
|
||||
/**
|
||||
* Constructs the ConfigSchemaChecker object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_manager
|
||||
* The typed config manager.
|
||||
*/
|
||||
public function __construct(TypedConfigManagerInterface $typed_manager) {
|
||||
$this->typedManager = $typed_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that configuration complies with its schema on config save.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The configuration event.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\Schema\SchemaIncompleteException
|
||||
* Exception thrown when configuration does not match its schema.
|
||||
*/
|
||||
public function onConfigSave(ConfigCrudEvent $event) {
|
||||
// Only validate configuration if in the default collection. Other
|
||||
// collections may have incomplete configuration (for example language
|
||||
// overrides only). These are not valid in themselves.
|
||||
$saved_config = $event->getConfig();
|
||||
if ($saved_config->getStorage()->getCollectionName() != StorageInterface::DEFAULT_COLLECTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $saved_config->getName();
|
||||
$data = $saved_config->get();
|
||||
$checksum = crc32(serialize($data));
|
||||
$exceptions = array(
|
||||
// Following are used to test lack of or partial schema. Where partial
|
||||
// schema is provided, that is explicitly tested in specific tests.
|
||||
'config_schema_test.noschema',
|
||||
'config_schema_test.someschema',
|
||||
'config_schema_test.schema_data_types',
|
||||
'config_schema_test.no_schema_data_types',
|
||||
// Used to test application of schema to filtering of configuration.
|
||||
'config_test.dynamic.system',
|
||||
);
|
||||
if (!in_array($name, $exceptions) && !isset($this->checked[$name . ':' . $checksum])) {
|
||||
$this->checked[$name . ':' . $checksum] = TRUE;
|
||||
$errors = $this->checkConfigSchema($this->typedManager, $name, $data);
|
||||
if ($errors === FALSE) {
|
||||
throw new SchemaIncompleteException(SafeMarkup::format('No schema for @config_name', array('@config_name' => $name)));
|
||||
}
|
||||
elseif (is_array($errors)) {
|
||||
$text_errors = [];
|
||||
foreach ($errors as $key => $error) {
|
||||
$text_errors[] = SafeMarkup::format('@key @error', array('@key' => $key, '@error' => $error));
|
||||
}
|
||||
throw new SchemaIncompleteException(SafeMarkup::format('Schema errors for @config_name with the following errors: @errors', array('@config_name' => $name, '@errors' => implode(', ', $text_errors))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::SAVE][] = array('onConfigSave', 255);
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
355
core/lib/Drupal/Core/Config/TypedConfigManager.php
Normal file
355
core/lib/Drupal/Core/Config/TypedConfigManager.php
Normal file
|
@ -0,0 +1,355 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\TypedConfigManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\Schema\ArrayElement;
|
||||
use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
|
||||
use Drupal\Core\Config\Schema\ConfigSchemaDiscovery;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\TypedData\TypedDataManager;
|
||||
|
||||
/**
|
||||
* Manages config type plugins.
|
||||
*/
|
||||
class TypedConfigManager extends TypedDataManager implements TypedConfigManagerInterface {
|
||||
|
||||
/**
|
||||
* A storage instance for reading configuration data.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* A storage instance for reading configuration schema data.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $schemaStorage;
|
||||
|
||||
/**
|
||||
* The array of plugin definitions, keyed by plugin id.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definitions;
|
||||
|
||||
/**
|
||||
* Creates a new typed configuration manager.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $configStorage
|
||||
* The storage object to use for reading schema data
|
||||
* @param \Drupal\Core\Config\StorageInterface $schemaStorage
|
||||
* The storage object to use for reading schema data
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend to use for caching the definitions.
|
||||
*/
|
||||
public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler) {
|
||||
$this->configStorage = $configStorage;
|
||||
$this->schemaStorage = $schemaStorage;
|
||||
$this->setCacheBackend($cache, 'typed_config_definitions');
|
||||
$this->alterInfo('config_schema_info');
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!isset($this->discovery)) {
|
||||
$this->discovery = new ConfigSchemaDiscovery($this->schemaStorage);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets typed configuration data.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration object name.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Schema\TypedConfigInterface
|
||||
* Typed configuration data.
|
||||
*/
|
||||
public function get($name) {
|
||||
$data = $this->configStorage->read($name);
|
||||
$type_definition = $this->getDefinition($name);
|
||||
$data_definition = $this->buildDataDefinition($type_definition, $data);
|
||||
return $this->create($data_definition, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildDataDefinition(array $definition, $value, $name = NULL, $parent = NULL) {
|
||||
// Add default values for data type and replace variables.
|
||||
$definition += array('type' => 'undefined');
|
||||
|
||||
$type = $definition['type'];
|
||||
if (strpos($type, ']')) {
|
||||
// Replace variable names in definition.
|
||||
$replace = is_array($value) ? $value : array();
|
||||
if (isset($parent)) {
|
||||
$replace['%parent'] = $parent;
|
||||
}
|
||||
if (isset($name)) {
|
||||
$replace['%key'] = $name;
|
||||
}
|
||||
$type = $this->replaceName($type, $replace);
|
||||
// Remove the type from the definition so that it is replaced with the
|
||||
// concrete type from schema definitions.
|
||||
unset($definition['type']);
|
||||
}
|
||||
// Add default values from type definition.
|
||||
$definition += $this->getDefinition($type);
|
||||
|
||||
$data_definition = $this->createDataDefinition($definition['type']);
|
||||
|
||||
// Pass remaining values from definition array to data definition.
|
||||
foreach ($definition as $key => $value) {
|
||||
if (!isset($data_definition[$key])) {
|
||||
$data_definition[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $data_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
|
||||
$definitions = $this->getDefinitions();
|
||||
if (isset($definitions[$base_plugin_id])) {
|
||||
$type = $base_plugin_id;
|
||||
}
|
||||
elseif (strpos($base_plugin_id, '.') && $name = $this->getFallbackName($base_plugin_id)) {
|
||||
// Found a generic name, replacing the last element by '*'.
|
||||
$type = $name;
|
||||
}
|
||||
else {
|
||||
// If we don't have definition, return the 'undefined' element.
|
||||
$type = 'undefined';
|
||||
}
|
||||
$definition = $definitions[$type];
|
||||
// Check whether this type is an extension of another one and compile it.
|
||||
if (isset($definition['type'])) {
|
||||
$merge = $this->getDefinition($definition['type'], $exception_on_invalid);
|
||||
// Preserve integer keys on merge, so sequence item types can override
|
||||
// parent settings as opposed to adding unused second, third, etc. items.
|
||||
$definition = NestedArray::mergeDeepArray(array($merge, $definition), TRUE);
|
||||
// Unset type so we try the merge only once per type.
|
||||
unset($definition['type']);
|
||||
$this->definitions[$type] = $definition;
|
||||
}
|
||||
// Add type and default definition class.
|
||||
$definition += array(
|
||||
'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
|
||||
'type' => $type,
|
||||
);
|
||||
return $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearCachedDefinitions() {
|
||||
$this->schemaStorage->reset();
|
||||
parent::clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets fallback configuration schema name.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration name or key.
|
||||
*
|
||||
* @return null|string
|
||||
* The resolved schema name for the given configuration name or key. Returns
|
||||
* null if there is no schema name to fallback to. For example,
|
||||
* breakpoint.breakpoint.module.toolbar.narrow will check for definitions in
|
||||
* the following order:
|
||||
* breakpoint.breakpoint.module.toolbar.*
|
||||
* breakpoint.breakpoint.module.*.*
|
||||
* breakpoint.breakpoint.module.*
|
||||
* breakpoint.breakpoint.*.*.*
|
||||
* breakpoint.breakpoint.*
|
||||
* breakpoint.*.*.*.*
|
||||
* breakpoint.*
|
||||
* Colons are also used, for example,
|
||||
* block.settings.system_menu_block:footer will check for definitions in the
|
||||
* following order:
|
||||
* block.settings.system_menu_block:*
|
||||
* block.settings.*:*
|
||||
* block.settings.*
|
||||
* block.*.*:*
|
||||
* block.*
|
||||
*/
|
||||
protected function getFallbackName($name) {
|
||||
// Check for definition of $name with filesystem marker.
|
||||
$replaced = preg_replace('/([^\.:]+)([\.:\*]*)$/', '*\2', $name);
|
||||
if ($replaced != $name) {
|
||||
if (isset($this->definitions[$replaced])) {
|
||||
return $replaced;
|
||||
}
|
||||
else {
|
||||
// No definition for this level. Collapse multiple wildcards to a single
|
||||
// wildcard to see if there is a greedy match. For example,
|
||||
// breakpoint.breakpoint.*.* becomes
|
||||
// breakpoint.breakpoint.*
|
||||
$one_star = preg_replace('/\.([:\.\*]*)$/', '.*', $replaced);
|
||||
if ($one_star != $replaced && isset($this->definitions[$one_star])) {
|
||||
return $one_star;
|
||||
}
|
||||
// Check for next level. For example, if breakpoint.breakpoint.* has
|
||||
// been checked and no match found then check breakpoint.*.*
|
||||
return $this->getFallbackName($replaced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces variables in configuration name.
|
||||
*
|
||||
* The configuration name may contain one or more variables to be replaced,
|
||||
* enclosed in square brackets like '[name]' and will follow the replacement
|
||||
* rules defined by the replaceVariable() method.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration name with variables in square brackets.
|
||||
* @param mixed $data
|
||||
* Configuration data for the element.
|
||||
* @return string
|
||||
* Configuration name with variables replaced.
|
||||
*/
|
||||
protected function replaceName($name, $data) {
|
||||
if (preg_match_all("/\[(.*)\]/U", $name, $matches)) {
|
||||
// Build our list of '[value]' => replacement.
|
||||
$replace = array();
|
||||
foreach (array_combine($matches[0], $matches[1]) as $key => $value) {
|
||||
$replace[$key] = $this->replaceVariable($value, $data);
|
||||
}
|
||||
return strtr($name, $replace);
|
||||
}
|
||||
else {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces variable values in included names with configuration data.
|
||||
*
|
||||
* Variable values are nested configuration keys that will be replaced by
|
||||
* their value or some of these special strings:
|
||||
* - '%key', will be replaced by the element's key.
|
||||
* - '%parent', to reference the parent element.
|
||||
* - '%type', to reference the schema definition type. Can only be used in
|
||||
* combination with %parent.
|
||||
*
|
||||
* There may be nested configuration keys separated by dots or more complex
|
||||
* patterns like '%parent.name' which references the 'name' value of the
|
||||
* parent element.
|
||||
*
|
||||
* Example patterns:
|
||||
* - 'name.subkey', indicates a nested value of the current element.
|
||||
* - '%parent.name', will be replaced by the 'name' value of the parent.
|
||||
* - '%parent.%key', will be replaced by the parent element's key.
|
||||
* - '%parent.%type', will be replaced by the schema type of the parent.
|
||||
* - '%parent.%parent.%type', will be replaced by the schema type of the
|
||||
* parent's parent.
|
||||
*
|
||||
* @param string $value
|
||||
* Variable value to be replaced.
|
||||
* @param mixed $data
|
||||
* Configuration data for the element.
|
||||
*
|
||||
* @return string
|
||||
* The replaced value if a replacement found or the original value if not.
|
||||
*/
|
||||
protected function replaceVariable($value, $data) {
|
||||
$parts = explode('.', $value);
|
||||
// Process each value part, one at a time.
|
||||
while ($name = array_shift($parts)) {
|
||||
if (!is_array($data) || !isset($data[$name])) {
|
||||
// Key not found, return original value
|
||||
return $value;
|
||||
}
|
||||
elseif (!$parts) {
|
||||
// If no more parts left, this is the final property.
|
||||
return (string)$data[$name];
|
||||
}
|
||||
else {
|
||||
// Get nested value and continue processing.
|
||||
if ($name == '%parent') {
|
||||
/** @var \Drupal\Core\Config\Schema\ArrayElement $parent */
|
||||
// Switch replacement values with values from the parent.
|
||||
$parent = $data['%parent'];
|
||||
$data = $parent->getValue();
|
||||
$data['%type'] = $parent->getDataDefinition()->getDataType();
|
||||
// The special %parent and %key values now need to point one level up.
|
||||
if ($new_parent = $parent->getParent()) {
|
||||
$data['%parent'] = $new_parent;
|
||||
$data['%key'] = $new_parent->getName();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$data = $data[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasConfigSchema($name) {
|
||||
// The schema system falls back on the Undefined class for unknown types.
|
||||
$definition = $this->getDefinition($name);
|
||||
return is_array($definition) && ($definition['class'] != '\Drupal\Core\Config\Schema\Undefined');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterDefinitions(&$definitions) {
|
||||
$discovered_schema = array_keys($definitions);
|
||||
parent::alterDefinitions($definitions);
|
||||
$altered_schema = array_keys($definitions);
|
||||
if ($discovered_schema != $altered_schema) {
|
||||
$added_keys = array_diff($altered_schema, $discovered_schema);
|
||||
$removed_keys = array_diff($discovered_schema, $altered_schema);
|
||||
if (!empty($added_keys) && !empty($removed_keys)) {
|
||||
$message = 'Invoking hook_config_schema_info_alter() has added (@added) and removed (@removed) schema definitions';
|
||||
}
|
||||
elseif (!empty($added_keys)) {
|
||||
$message = 'Invoking hook_config_schema_info_alter() has added (@added) schema definitions';
|
||||
}
|
||||
else {
|
||||
$message = 'Invoking hook_config_schema_info_alter() has removed (@removed) schema definitions';
|
||||
}
|
||||
throw new ConfigSchemaAlterException(SafeMarkup::format($message, ['@added' => implode(',', $added_keys), '@removed' => implode(',', $removed_keys)]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstance($data_type, array $configuration = array()) {
|
||||
$instance = parent::createInstance($data_type, $configuration);
|
||||
// Enable elements to construct their own definitions using the typed config
|
||||
// manager.
|
||||
if ($instance instanceof ArrayElement) {
|
||||
$instance->setTypedConfig($this);
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
}
|
121
core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
Normal file
121
core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\TypedConfigManagerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\TypedData\DataDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for typed configuration manager.
|
||||
*
|
||||
* @package Drupal\Core\Config
|
||||
*/
|
||||
Interface TypedConfigManagerInterface extends PluginManagerInterface, CachedDiscoveryInterface {
|
||||
|
||||
/**
|
||||
* Gets typed configuration data.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration object name.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TraversableTypedDataInterface
|
||||
* Typed configuration element.
|
||||
*/
|
||||
public function get($name);
|
||||
|
||||
/**
|
||||
* Instantiates a typed configuration object.
|
||||
*
|
||||
* @param string $data_type
|
||||
* The data type, for which a typed configuration object should be
|
||||
* instantiated.
|
||||
* @param array $configuration
|
||||
* The plugin configuration array, i.e. an array with the following keys:
|
||||
* - data definition: The data definition object, i.e. an instance of
|
||||
* \Drupal\Core\TypedData\DataDefinitionInterface.
|
||||
* - name: (optional) If a property or list item is to be created, the name
|
||||
* of the property or the delta of the list item.
|
||||
* - parent: (optional) If a property or list item is to be created, the
|
||||
* parent typed data object implementing either the ListInterface or the
|
||||
* ComplexDataInterface.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Schema\Element
|
||||
* The instantiated typed configuration object.
|
||||
*/
|
||||
public function createInstance($data_type, array $configuration = array());
|
||||
|
||||
/**
|
||||
* Creates a new typed configuration object instance.
|
||||
*
|
||||
* @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
|
||||
* The data definition of the typed data object.
|
||||
* @param mixed $value
|
||||
* The data value. If set, it has to match one of the supported
|
||||
* data type format as documented for the data type classes.
|
||||
* @param string $name
|
||||
* (optional) If a property or list item is to be created, the name of the
|
||||
* property or the delta of the list item.
|
||||
* @param mixed $parent
|
||||
* (optional) If a property or list item is to be created, the parent typed
|
||||
* data object implementing either the ListInterface or the
|
||||
* ComplexDataInterface.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Schema\Element
|
||||
* The instantiated typed data object.
|
||||
*/
|
||||
public function create(DataDefinitionInterface $definition, $value, $name = NULL, $parent = NULL);
|
||||
|
||||
/**
|
||||
* Creates a new data definition object from a type definition array and
|
||||
* actual configuration data. Since type definitions may contain variables
|
||||
* to be replaced, we need the configuration value to create it.
|
||||
*
|
||||
* @param array $definition
|
||||
* The base type definition array, for which a data definition should be
|
||||
* created.
|
||||
* @param $value
|
||||
* Optional value of the configuration element.
|
||||
* @param string $name
|
||||
* Optional name of the configuration element.
|
||||
* @param object $parent
|
||||
* Optional parent element.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\DataDefinitionInterface
|
||||
* A data definition for the given data type.
|
||||
*/
|
||||
public function buildDataDefinition(array $definition, $value, $name = NULL, $parent = NULL);
|
||||
|
||||
/**
|
||||
* Checks if the configuration schema with the given config name exists.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration name.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if configuration schema exists, FALSE otherwise.
|
||||
*/
|
||||
public function hasConfigSchema($name);
|
||||
|
||||
/**
|
||||
* Gets a specific plugin definition.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* A plugin id.
|
||||
* @param bool $exception_on_invalid
|
||||
* Ignored with TypedConfigManagerInterface. Kept for compatibility with
|
||||
* DiscoveryInterface.
|
||||
*
|
||||
* @return array
|
||||
* A plugin definition array. If the given plugin id does not have typed
|
||||
* configuration definition assigned, the definition of an undefined
|
||||
* element type is returned.
|
||||
*/
|
||||
public function getDefinition($plugin_id, $exception_on_invalid = TRUE);
|
||||
|
||||
}
|
95
core/lib/Drupal/Core/Config/UnmetDependenciesException.php
Normal file
95
core/lib/Drupal/Core/Config/UnmetDependenciesException.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\UnmetDependenciesException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
|
||||
/**
|
||||
* An exception thrown if configuration has unmet dependencies.
|
||||
*/
|
||||
class UnmetDependenciesException extends ConfigException {
|
||||
|
||||
/**
|
||||
* A list of configuration objects that have unmet dependencies.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configObjects = [];
|
||||
|
||||
/**
|
||||
* The name of the extension that is being installed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* Gets the list of configuration objects that have unmet dependencies.
|
||||
*
|
||||
* @return array
|
||||
* A list of configuration objects that have unmet dependencies.
|
||||
*/
|
||||
public function getConfigObjects() {
|
||||
return $this->configObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the extension that is being installed.
|
||||
*
|
||||
* @return string
|
||||
* The name of the extension that is being installed.
|
||||
*/
|
||||
public function getExtension() {
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a translated message from the exception.
|
||||
*
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTranslatedMessage(TranslationInterface $string_translation, $extension) {
|
||||
return $string_translation->formatPlural(
|
||||
count($this->getConfigObjects()),
|
||||
'Unable to install @extension, %config_names has unmet dependencies.',
|
||||
'Unable to install @extension, %config_names have unmet dependencies.',
|
||||
[
|
||||
'%config_names' => implode(', ', $this->getConfigObjects()),
|
||||
'@extension' => $extension,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an exception for an extension and a list of configuration objects.
|
||||
*
|
||||
* @param $extension
|
||||
* The name of the extension that is being installed.
|
||||
* @param array $config_objects
|
||||
* A list of configuration object names that have unmet dependencies
|
||||
*
|
||||
* @return \Drupal\Core\Config\PreExistingConfigException
|
||||
*/
|
||||
public static function create($extension, array $config_objects) {
|
||||
$message = SafeMarkup::format('Configuration objects (@config_names) provided by @extension have unmet dependencies',
|
||||
array(
|
||||
'@config_names' => implode(', ', $config_objects),
|
||||
'@extension' => $extension
|
||||
)
|
||||
);
|
||||
$e = new static($message);
|
||||
$e->configObjects = $config_objects;
|
||||
$e->extension = $extension;
|
||||
return $e;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\UnsupportedDataTypeConfigException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception thrown when a config data type is invalid.
|
||||
*/
|
||||
class UnsupportedDataTypeConfigException extends ConfigException {
|
||||
}
|
Reference in a new issue