Issue #1808248 by alexpott, beejeebus, tayzlor, Nitesh Sethia: Add a separate module install/uninstall step to the config import process.
parent
8866760fca
commit
ae702dc56d
|
@ -612,4 +612,14 @@ class Drupal {
|
||||||
return static::$container->get('form_builder');
|
return static::$container->get('form_builder');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the syncing state.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Returns TRUE is syncing flag set.
|
||||||
|
*/
|
||||||
|
public function isConfigSyncing() {
|
||||||
|
return static::$container->get('config.installer')->isSyncing();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,34 @@ namespace Drupal\Core\Config;
|
||||||
*/
|
*/
|
||||||
class BatchConfigImporter extends ConfigImporter {
|
class BatchConfigImporter extends ConfigImporter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of extensions to process.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $totalExtensionsToProcess = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of configuration objects to process.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $totalConfigurationToProcess = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the config importer in preparation for processing a batch.
|
* Initializes the config importer in preparation for processing a batch.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of method names that to be called by the batch. If there are
|
||||||
|
* modules or themes to process then an extra step is added.
|
||||||
|
*
|
||||||
|
* @throws ConfigImporterException
|
||||||
|
* If the configuration is already importing.
|
||||||
*/
|
*/
|
||||||
public function initialize() {
|
public function initialize() {
|
||||||
|
$batch_operations = array();
|
||||||
|
$this->createExtensionChangelist();
|
||||||
|
|
||||||
// Ensure that the changes have been validated.
|
// Ensure that the changes have been validated.
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
|
@ -25,61 +49,137 @@ class BatchConfigImporter extends ConfigImporter {
|
||||||
// Another process is synchronizing configuration.
|
// Another process is synchronizing configuration.
|
||||||
throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
|
throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
|
||||||
}
|
}
|
||||||
$this->totalToProcess = 0;
|
|
||||||
foreach(array('create', 'delete', 'update') as $op) {
|
$modules = $this->getUnprocessedExtensions('module');
|
||||||
$this->totalToProcess += count($this->getUnprocessed($op));
|
foreach (array('install', 'uninstall') as $op) {
|
||||||
|
$this->totalExtensionsToProcess += count($modules[$op]);
|
||||||
}
|
}
|
||||||
|
$themes = $this->getUnprocessedExtensions('theme');
|
||||||
|
foreach (array('enable', 'disable') as $op) {
|
||||||
|
$this->totalExtensionsToProcess += count($themes[$op]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have extensions to process.
|
||||||
|
if ($this->totalExtensionsToProcess > 0) {
|
||||||
|
$batch_operations[] = 'processExtensionBatch';
|
||||||
|
}
|
||||||
|
|
||||||
|
$batch_operations[] = 'processConfigurationBatch';
|
||||||
|
$batch_operations[] = 'finishBatch';
|
||||||
|
return $batch_operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes batch.
|
* Processes extensions as a batch operation.
|
||||||
*
|
*
|
||||||
* @param array $context.
|
* @param array $context.
|
||||||
* The batch context.
|
* The batch context.
|
||||||
*/
|
*/
|
||||||
public function processBatch(array &$context) {
|
public function processExtensionBatch(array &$context) {
|
||||||
$operation = $this->getNextOperation();
|
$operation = $this->getNextExtensionOperation();
|
||||||
if (!empty($operation)) {
|
if (!empty($operation)) {
|
||||||
$this->process($operation['op'], $operation['name']);
|
$this->processExtension($operation['type'], $operation['op'], $operation['name']);
|
||||||
$context['message'] = t('Synchronizing @name.', array('@name' => $operation['name']));
|
$context['message'] = t('Synchronising extensions: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
|
||||||
$context['finished'] = $this->batchProgress();
|
$processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']);
|
||||||
|
$processed_count += count($this->processedExtensions['theme']['disable']) + count($this->processedExtensions['theme']['enable']);
|
||||||
|
$context['finished'] = $processed_count / $this->totalExtensionsToProcess;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$context['finished'] = 1;
|
$context['finished'] = 1;
|
||||||
}
|
}
|
||||||
if ($context['finished'] >= 1) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes configuration as a batch operation.
|
||||||
|
*
|
||||||
|
* @param array $context.
|
||||||
|
* The batch context.
|
||||||
|
*/
|
||||||
|
public function processConfigurationBatch(array &$context) {
|
||||||
|
// The first time this is called we need to calculate the total to process.
|
||||||
|
// This involves recalculating the changelist which will ensure that if
|
||||||
|
// extensions have been processed any configuration affected will be taken
|
||||||
|
// into account.
|
||||||
|
if ($this->totalConfigurationToProcess == 0) {
|
||||||
|
$this->storageComparer->reset();
|
||||||
|
foreach (array('delete', 'create', 'update') as $op) {
|
||||||
|
$this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$operation = $this->getNextConfigurationOperation();
|
||||||
|
if (!empty($operation)) {
|
||||||
|
$this->processConfiguration($operation['op'], $operation['name']);
|
||||||
|
$context['message'] = t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
|
||||||
|
$processed_count = count($this->processedConfiguration['create']) + count($this->processedConfiguration['delete']) + count($this->processedConfiguration['update']);
|
||||||
|
$context['finished'] = $processed_count / $this->totalConfigurationToProcess;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$context['finished'] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes the batch.
|
||||||
|
*
|
||||||
|
* @param array $context.
|
||||||
|
* The batch context.
|
||||||
|
*/
|
||||||
|
public function finishBatch(array &$context) {
|
||||||
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
|
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
|
||||||
// The import is now complete.
|
// The import is now complete.
|
||||||
$this->lock->release(static::LOCK_ID);
|
$this->lock->release(static::LOCK_ID);
|
||||||
$this->reset();
|
$this->reset();
|
||||||
}
|
$context['message'] = t('Finalising configuration synchronisation.');
|
||||||
|
$context['finished'] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets percentage of progress made.
|
* Gets the next extension operation to perform.
|
||||||
*
|
*
|
||||||
* @return float
|
* @return array|bool
|
||||||
* The percentage of progress made expressed as a float between 0 and 1.
|
* An array containing the next operation and extension name to perform it
|
||||||
|
* on. If there is nothing left to do returns FALSE;
|
||||||
*/
|
*/
|
||||||
protected function batchProgress() {
|
protected function getNextExtensionOperation() {
|
||||||
$processed_count = count($this->processed['create']) + count($this->processed['delete']) + count($this->processed['update']);
|
foreach (array('install', 'uninstall') as $op) {
|
||||||
return $processed_count / $this->totalToProcess;
|
$modules = $this->getUnprocessedExtensions('module');
|
||||||
|
if (!empty($modules[$op])) {
|
||||||
|
return array(
|
||||||
|
'op' => $op,
|
||||||
|
'type' => 'module',
|
||||||
|
'name' => array_shift($modules[$op]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (array('enable', 'disable') as $op) {
|
||||||
|
$themes = $this->getUnprocessedExtensions('theme');
|
||||||
|
if (!empty($themes[$op])) {
|
||||||
|
return array(
|
||||||
|
'op' => $op,
|
||||||
|
'type' => 'theme',
|
||||||
|
'name' => array_shift($themes[$op]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the next operation to perform.
|
* Gets the next configuration operation to perform.
|
||||||
*
|
*
|
||||||
* @return array|bool
|
* @return array|bool
|
||||||
* An array containing the next operation and configuration name to perform
|
* An array containing the next operation and configuration name to perform
|
||||||
* it on. If there is nothing left to do returns FALSE;
|
* it on. If there is nothing left to do returns FALSE;
|
||||||
*/
|
*/
|
||||||
protected function getNextOperation() {
|
protected function getNextConfigurationOperation() {
|
||||||
foreach(array('create', 'delete', 'update') as $op) {
|
// The order configuration operations is processed is important. Deletes
|
||||||
$names = $this->getUnprocessed($op);
|
// have to come first so that recreates can work.
|
||||||
if (!empty($names)) {
|
foreach (array('delete', 'create', 'update') as $op) {
|
||||||
|
$config_names = $this->getUnprocessedConfiguration($op);
|
||||||
|
if (!empty($config_names)) {
|
||||||
return array(
|
return array(
|
||||||
'op' => $op,
|
'op' => $op,
|
||||||
'name' => array_shift($names),
|
'name' => array_shift($config_names),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
namespace Drupal\Core\Config;
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||||
|
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||||
use Drupal\Component\Utility\String;
|
use Drupal\Component\Utility\String;
|
||||||
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
|
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
|
||||||
use Drupal\Core\DependencyInjection\DependencySerialization;
|
use Drupal\Core\DependencyInjection\DependencySerialization;
|
||||||
|
@ -71,16 +73,30 @@ class ConfigImporter extends DependencySerialization {
|
||||||
/**
|
/**
|
||||||
* The typed config manager.
|
* The typed config manager.
|
||||||
*
|
*
|
||||||
* @var \Drupal\Core\Config\TypedConfigManager
|
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||||
*/
|
*/
|
||||||
protected $typedConfigManager;
|
protected $typedConfigManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of changes processed by the import().
|
* List of configuration file changes processed by the import().
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $processed;
|
protected $processedConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of extension changes processed by the import().
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $processedExtensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of extension changes to be processed by the import().
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $extensionChangelist;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates changes to import have been validated.
|
* Indicates changes to import have been validated.
|
||||||
|
@ -89,6 +105,27 @@ class ConfigImporter extends DependencySerialization {
|
||||||
*/
|
*/
|
||||||
protected $validated;
|
protected $validated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The module handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $moduleHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The theme handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Extension\ThemeHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $themeHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag set to import system.theme during processing theme enable and disables.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $processedSystemTheme = FALSE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a configuration import object.
|
* Constructs a configuration import object.
|
||||||
*
|
*
|
||||||
|
@ -103,14 +140,21 @@ class ConfigImporter extends DependencySerialization {
|
||||||
* The lock backend to ensure multiple imports do not occur at the same time.
|
* The lock backend to ensure multiple imports do not occur at the same time.
|
||||||
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
|
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
|
||||||
* The typed configuration manager.
|
* The typed configuration manager.
|
||||||
|
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||||
|
* The module handler
|
||||||
|
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
|
||||||
|
* The theme handler
|
||||||
*/
|
*/
|
||||||
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManager $typed_config) {
|
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
|
||||||
$this->storageComparer = $storage_comparer;
|
$this->storageComparer = $storage_comparer;
|
||||||
$this->eventDispatcher = $event_dispatcher;
|
$this->eventDispatcher = $event_dispatcher;
|
||||||
$this->configManager = $config_manager;
|
$this->configManager = $config_manager;
|
||||||
$this->lock = $lock;
|
$this->lock = $lock;
|
||||||
$this->typedConfigManager = $typed_config;
|
$this->typedConfigManager = $typed_config;
|
||||||
$this->processed = $this->storageComparer->getEmptyChangelist();
|
$this->moduleHandler = $module_handler;
|
||||||
|
$this->themeHandler = $theme_handler;
|
||||||
|
$this->processedConfiguration = $this->storageComparer->getEmptyChangelist();
|
||||||
|
$this->processedExtensions = $this->getEmptyExtensionsProcessedList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,13 +175,35 @@ class ConfigImporter extends DependencySerialization {
|
||||||
*/
|
*/
|
||||||
public function reset() {
|
public function reset() {
|
||||||
$this->storageComparer->reset();
|
$this->storageComparer->reset();
|
||||||
$this->processed = $this->storageComparer->getEmptyChangelist();
|
$this->processedConfiguration = $this->storageComparer->getEmptyChangelist();
|
||||||
|
$this->processedExtensions = $this->getEmptyExtensionsProcessedList();
|
||||||
|
$this->createExtensionChangelist();
|
||||||
$this->validated = FALSE;
|
$this->validated = FALSE;
|
||||||
|
$this->processedSystemTheme = FALSE;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if there are any unprocessed changes.
|
* Gets an empty list of extensions to process.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An empty list of extensions to process.
|
||||||
|
*/
|
||||||
|
protected function getEmptyExtensionsProcessedList() {
|
||||||
|
return array(
|
||||||
|
'module' => array(
|
||||||
|
'install' => array(),
|
||||||
|
'uninstall' => array(),
|
||||||
|
),
|
||||||
|
'theme' => array(
|
||||||
|
'enable' => array(),
|
||||||
|
'disable' => array(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are any unprocessed configuration changes.
|
||||||
*
|
*
|
||||||
* @param array $ops
|
* @param array $ops
|
||||||
* The operations to check for changes. Defaults to all operations, i.e.
|
* The operations to check for changes. Defaults to all operations, i.e.
|
||||||
|
@ -146,9 +212,9 @@ class ConfigImporter extends DependencySerialization {
|
||||||
* @return bool
|
* @return bool
|
||||||
* TRUE if there are changes to process and FALSE if not.
|
* TRUE if there are changes to process and FALSE if not.
|
||||||
*/
|
*/
|
||||||
public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')) {
|
public function hasUnprocessedConfigurationChanges($ops = array('delete', 'create', 'update')) {
|
||||||
foreach ($ops as $op) {
|
foreach ($ops as $op) {
|
||||||
if (count($this->getUnprocessed($op))) {
|
if (count($this->getUnprocessedConfiguration($op))) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,8 +227,8 @@ class ConfigImporter extends DependencySerialization {
|
||||||
* @return array
|
* @return array
|
||||||
* An array containing a list of processed changes.
|
* An array containing a list of processed changes.
|
||||||
*/
|
*/
|
||||||
public function getProcessed() {
|
public function getProcessedConfiguration() {
|
||||||
return $this->processed;
|
return $this->processedConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,8 +239,8 @@ class ConfigImporter extends DependencySerialization {
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* The name of the configuration processed.
|
* The name of the configuration processed.
|
||||||
*/
|
*/
|
||||||
protected function setProcessed($op, $name) {
|
protected function setProcessedConfiguration($op, $name) {
|
||||||
$this->processed[$op][] = $name;
|
$this->processedConfiguration[$op][] = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -187,8 +253,139 @@ class ConfigImporter extends DependencySerialization {
|
||||||
* @return array
|
* @return array
|
||||||
* An array of configuration names.
|
* An array of configuration names.
|
||||||
*/
|
*/
|
||||||
public function getUnprocessed($op) {
|
public function getUnprocessedConfiguration($op) {
|
||||||
return array_diff($this->storageComparer->getChangelist($op), $this->processed[$op]);
|
return array_diff($this->storageComparer->getChangelist($op), $this->processedConfiguration[$op]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets list of processed extension changes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array containing a list of processed extension changes.
|
||||||
|
*/
|
||||||
|
public function getProcessedExtensions() {
|
||||||
|
return $this->processedExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current import has processed extensions.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if the ConfigImporter has processed extensions.
|
||||||
|
*/
|
||||||
|
protected function hasProcessedExtensions() {
|
||||||
|
$compare = array_diff($this->processedExtensions, getEmptyExtensionsProcessedList());
|
||||||
|
return !empty($compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an extension change as processed.
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* The type of extension, either 'theme' or 'module'.
|
||||||
|
* @param string $op
|
||||||
|
* The change operation performed, either install or uninstall.
|
||||||
|
* @param string $name
|
||||||
|
* The name of the extension processed.
|
||||||
|
*/
|
||||||
|
protected function setProcessedExtension($type, $op, $name) {
|
||||||
|
$this->processedExtensions[$type][$op][] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the extension change list.
|
||||||
|
*/
|
||||||
|
protected function createExtensionChangelist() {
|
||||||
|
// Read the extensions information to determine changes.
|
||||||
|
$current_extensions = $this->storageComparer->getTargetStorage()->read('core.extension');
|
||||||
|
$new_extensions = $this->storageComparer->getSourceStorage()->read('core.extension');
|
||||||
|
|
||||||
|
// If there is no extension information in staging then exit. This is
|
||||||
|
// probably due to an empty staging directory.
|
||||||
|
if (!$new_extensions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of modules with dependency weights as values.
|
||||||
|
$module_data = system_rebuild_module_data();
|
||||||
|
// Set the actual module weights.
|
||||||
|
$module_list = array_combine(array_keys($module_data), array_keys($module_data));
|
||||||
|
$module_list = array_map(function ($module) use ($module_data) {
|
||||||
|
return $module_data[$module]->sort;
|
||||||
|
}, $module_list);
|
||||||
|
|
||||||
|
// Work out what modules to install and uninstall.
|
||||||
|
$uninstall = array_diff(array_keys($current_extensions['module']), array_keys($new_extensions['module']));
|
||||||
|
$install = array_diff(array_keys($new_extensions['module']), array_keys($current_extensions['module']));
|
||||||
|
// Sort the module list by their weights. So that dependencies
|
||||||
|
// are uninstalled last.
|
||||||
|
asort($module_list);
|
||||||
|
$uninstall = array_intersect(array_keys($module_list), $uninstall);
|
||||||
|
// Sort the module list by their weights (reverse). So that dependencies
|
||||||
|
// are installed first.
|
||||||
|
arsort($module_list);
|
||||||
|
$install = array_intersect(array_keys($module_list), $install);
|
||||||
|
|
||||||
|
// Work out what themes to enable and to disable.
|
||||||
|
$enable = array_diff(array_keys($new_extensions['theme']), array_keys($current_extensions['theme']));
|
||||||
|
$disable = array_diff(array_keys($current_extensions['theme']), array_keys($new_extensions['theme']));
|
||||||
|
|
||||||
|
$this->extensionChangelist = array(
|
||||||
|
'module' => array(
|
||||||
|
'uninstall' => $uninstall,
|
||||||
|
'install' => $install,
|
||||||
|
),
|
||||||
|
'theme' => array(
|
||||||
|
'enable' => $enable,
|
||||||
|
'disable' => $disable,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list changes for extensions.
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* The type of extension, either 'theme' or 'module'.
|
||||||
|
* @param string $op
|
||||||
|
* The change operation to get the unprocessed list for, either install
|
||||||
|
* or uninstall.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of extension names.
|
||||||
|
*/
|
||||||
|
protected function getExtensionChangelist($type, $op = NULL) {
|
||||||
|
if ($op) {
|
||||||
|
return $this->extensionChangelist[$type][$op];
|
||||||
|
}
|
||||||
|
return $this->extensionChangelist[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of unprocessed changes for extensions.
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* The type of extension, either 'theme' or 'module'.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of extension names.
|
||||||
|
*/
|
||||||
|
public function getUnprocessedExtensions($type) {
|
||||||
|
$changelist = $this->getExtensionChangelist($type);
|
||||||
|
|
||||||
|
if ($type == 'theme') {
|
||||||
|
$unprocessed = array(
|
||||||
|
'enable' => array_diff($changelist['enable'], $this->processedExtensions[$type]['enable']),
|
||||||
|
'disable' => array_diff($changelist['disable'], $this->processedExtensions[$type]['disable']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$unprocessed = array(
|
||||||
|
'install' => array_diff($changelist['install'], $this->processedExtensions[$type]['install']),
|
||||||
|
'uninstall' => array_diff($changelist['uninstall'], $this->processedExtensions[$type]['uninstall']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $unprocessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,7 +397,9 @@ class ConfigImporter extends DependencySerialization {
|
||||||
* The ConfigImporter instance.
|
* The ConfigImporter instance.
|
||||||
*/
|
*/
|
||||||
public function import() {
|
public function import() {
|
||||||
if ($this->hasUnprocessedChanges()) {
|
if ($this->hasUnprocessedConfigurationChanges()) {
|
||||||
|
$this->createExtensionChangelist();
|
||||||
|
|
||||||
// Ensure that the changes have been validated.
|
// Ensure that the changes have been validated.
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
|
@ -208,19 +407,20 @@ class ConfigImporter extends DependencySerialization {
|
||||||
// Another process is synchronizing configuration.
|
// Another process is synchronizing configuration.
|
||||||
throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
|
throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process any extension changes before importing configuration.
|
||||||
|
$this->handleExtensions();
|
||||||
|
|
||||||
// First pass deleted, then new, and lastly changed configuration, in order
|
// First pass deleted, then new, and lastly changed configuration, in order
|
||||||
// to handle dependencies correctly.
|
// to handle dependencies correctly.
|
||||||
// @todo Implement proper dependency ordering using
|
|
||||||
// https://drupal.org/node/2080823
|
|
||||||
foreach (array('delete', 'create', 'update') as $op) {
|
foreach (array('delete', 'create', 'update') as $op) {
|
||||||
foreach ($this->getUnprocessed($op) as $name) {
|
foreach ($this->getUnprocessedConfiguration($op) as $name) {
|
||||||
$this->process($op, $name);
|
$this->processConfiguration($op, $name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Allow modules to react to a import.
|
// Allow modules to react to a import.
|
||||||
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
|
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
|
||||||
|
|
||||||
|
|
||||||
// The import is now complete.
|
// The import is now complete.
|
||||||
$this->lock->release(static::LOCK_ID);
|
$this->lock->release(static::LOCK_ID);
|
||||||
$this->reset();
|
$this->reset();
|
||||||
|
@ -253,12 +453,58 @@ class ConfigImporter extends DependencySerialization {
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* The name of the configuration to process.
|
* The name of the configuration to process.
|
||||||
*/
|
*/
|
||||||
protected function process($op, $name) {
|
protected function processConfiguration($op, $name) {
|
||||||
if (!$this->importInvokeOwner($op, $name)) {
|
if (!$this->importInvokeOwner($op, $name)) {
|
||||||
$this->importConfig($op, $name);
|
$this->importConfig($op, $name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes an extension change.
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* The type of extension, either 'module' or 'theme'.
|
||||||
|
* @param string $op
|
||||||
|
* The change operation.
|
||||||
|
* @param string $name
|
||||||
|
* The name of the extension to process.
|
||||||
|
*/
|
||||||
|
protected function processExtension($type, $op, $name) {
|
||||||
|
// Set the config installer to use the staging directory instead of the
|
||||||
|
// extensions own default config directories.
|
||||||
|
\Drupal::service('config.installer')
|
||||||
|
->setSyncing(TRUE)
|
||||||
|
->setSourceStorage($this->storageComparer->getSourceStorage());
|
||||||
|
if ($type == 'module') {
|
||||||
|
$this->moduleHandler->$op(array($name), FALSE);
|
||||||
|
// Installing a module can cause a kernel boot therefore reinject all the
|
||||||
|
// services.
|
||||||
|
$this->reInjectMe();
|
||||||
|
// During a module install or uninstall the container is rebuilt and the
|
||||||
|
// module handler is called from drupal_get_complete_schema(). This causes
|
||||||
|
// the container's instance of the module handler not to have loaded all
|
||||||
|
// the enabled modules.
|
||||||
|
$this->moduleHandler->loadAll();
|
||||||
|
}
|
||||||
|
if ($type == 'theme') {
|
||||||
|
// Theme disables possible remove default or admin themes therefore we
|
||||||
|
// need to import this before doing any. If there are no disables and
|
||||||
|
// the default or admin theme is change this will be picked up whilst
|
||||||
|
// processing configuration.
|
||||||
|
if ($op == 'disable' && $this->processedSystemTheme === FALSE) {
|
||||||
|
$this->importConfig('update', 'system.theme');
|
||||||
|
$this->configManager->getConfigFactory()->reset('system.theme');
|
||||||
|
$this->processedSystemTheme = TRUE;
|
||||||
|
}
|
||||||
|
$this->themeHandler->$op(array($name));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setProcessedExtension($type, $op, $name);
|
||||||
|
\Drupal::service('config.installer')
|
||||||
|
->setSyncing(FALSE)
|
||||||
|
->resetSourceStorage();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a configuration change from the source to the target storage.
|
* Writes a configuration change from the source to the target storage.
|
||||||
*
|
*
|
||||||
|
@ -277,7 +523,7 @@ class ConfigImporter extends DependencySerialization {
|
||||||
$config->setData($data ? $data : array());
|
$config->setData($data ? $data : array());
|
||||||
$config->save();
|
$config->save();
|
||||||
}
|
}
|
||||||
$this->setProcessed($op, $name);
|
$this->setProcessedConfiguration($op, $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -325,7 +571,7 @@ class ConfigImporter extends DependencySerialization {
|
||||||
throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type)));
|
throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type)));
|
||||||
}
|
}
|
||||||
$entity_storage->$method($name, $new_config, $old_config);
|
$entity_storage->$method($name, $new_config, $old_config);
|
||||||
$this->setProcessed($op, $name);
|
$this->setProcessedConfiguration($op, $name);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
@ -341,4 +587,74 @@ class ConfigImporter extends DependencySerialization {
|
||||||
return !$this->lock->lockMayBeAvailable(static::LOCK_ID);
|
return !$this->lock->lockMayBeAvailable(static::LOCK_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identifier for events and locks.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The identifier for events and locks.
|
||||||
|
*/
|
||||||
|
public function getId() {
|
||||||
|
return static::LOCK_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a configuration object will be updated by the import.
|
||||||
|
*
|
||||||
|
* @param string $config_name
|
||||||
|
* The configuration object name.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if the configuration object will be updated.
|
||||||
|
*/
|
||||||
|
protected function hasUpdate($config_name) {
|
||||||
|
return in_array($config_name, $this->getUnprocessedConfiguration('update'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle changes to installed modules and themes.
|
||||||
|
*/
|
||||||
|
protected function handleExtensions() {
|
||||||
|
$processed_extension = FALSE;
|
||||||
|
foreach (array('install', 'uninstall') as $op) {
|
||||||
|
$modules = $this->getUnprocessedExtensions('module');
|
||||||
|
foreach($modules[$op] as $module) {
|
||||||
|
$processed_extension = TRUE;
|
||||||
|
$this->processExtension('module', $op, $module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (array('enable', 'disable') as $op) {
|
||||||
|
$themes = $this->getUnprocessedExtensions('theme');
|
||||||
|
foreach($themes[$op] as $theme) {
|
||||||
|
$processed_extension = TRUE;
|
||||||
|
$this->processExtension('theme', $op, $theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($processed_extension) {
|
||||||
|
// Recalculate differences as default config could have been imported.
|
||||||
|
$this->storageComparer->reset();
|
||||||
|
$this->processed = $this->storageComparer->getEmptyChangelist();
|
||||||
|
// Modules have been updated. Services etc might have changed.
|
||||||
|
// We don't reinject storage comparer because swapping out the active
|
||||||
|
// store during config import is a complete nonsense.
|
||||||
|
$this->recalculateChangelist = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the service dependencies from \Drupal.
|
||||||
|
*
|
||||||
|
* Since the ConfigImporter handles module installation the kernel and the
|
||||||
|
* container can be rebuilt and altered during processing. It is necessary to
|
||||||
|
* keep the services used by the importer in sync.
|
||||||
|
*/
|
||||||
|
protected function reInjectMe() {
|
||||||
|
$this->eventDispatcher = \Drupal::service('event_dispatcher');
|
||||||
|
$this->configFactory = \Drupal::configFactory();
|
||||||
|
$this->entityManager = \Drupal::entityManager();
|
||||||
|
$this->lock = \Drupal::lock();
|
||||||
|
$this->typedConfigManager = \Drupal::service('config.typed');
|
||||||
|
$this->moduleHandler = \Drupal::moduleHandler();
|
||||||
|
$this->themeHandler = \Drupal::service('theme_handler');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,20 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
||||||
*/
|
*/
|
||||||
protected $eventDispatcher;
|
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.
|
* Constructs the configuration installer.
|
||||||
*
|
*
|
||||||
|
@ -75,7 +89,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
||||||
*/
|
*/
|
||||||
public function installDefaultConfig($type, $name) {
|
public function installDefaultConfig($type, $name) {
|
||||||
// Get all default configuration owned by this extension.
|
// Get all default configuration owned by this extension.
|
||||||
$source_storage = new ExtensionInstallStorage($this->activeStorage);
|
$source_storage = $this->getSourceStorage();
|
||||||
$config_to_install = $source_storage->listAll($name . '.');
|
$config_to_install = $source_storage->listAll($name . '.');
|
||||||
|
|
||||||
// Work out if this extension provides default configuration for any other
|
// Work out if this extension provides default configuration for any other
|
||||||
|
@ -130,6 +144,16 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
||||||
$new_config->setData($data[$name]);
|
$new_config->setData($data[$name]);
|
||||||
}
|
}
|
||||||
if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) {
|
if ($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;
|
||||||
|
}
|
||||||
$entity_storage = $this->configManager
|
$entity_storage = $this->configManager
|
||||||
->getEntityManager()
|
->getEntityManager()
|
||||||
->getStorage($entity_type);
|
->getStorage($entity_type);
|
||||||
|
@ -159,4 +183,49 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
||||||
$this->configFactory->reset();
|
$this->configFactory->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setSourceStorage(StorageInterface $storage) {
|
||||||
|
$this->sourceStorage = $storage;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function resetSourceStorage() {
|
||||||
|
$this->sourceStorage = null;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration storage that provides the default configuration.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageInterface
|
||||||
|
* The configuration storage that provides the default configuration.
|
||||||
|
*/
|
||||||
|
public function getSourceStorage() {
|
||||||
|
if (!isset($this->sourceStorage)) {
|
||||||
|
// Default to using the ExtensionInstallStorage which searches extension's
|
||||||
|
// config directories for default configuration.
|
||||||
|
$this->sourceStorage = new ExtensionInstallStorage($this->activeStorage);
|
||||||
|
}
|
||||||
|
return $this->sourceStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setSyncing($status) {
|
||||||
|
$this->isSyncing = $status;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isSyncing() {
|
||||||
|
return $this->isSyncing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,4 +37,38 @@ interface ConfigInstallerInterface {
|
||||||
*/
|
*/
|
||||||
public function installDefaultConfig($type, $name);
|
public function installDefaultConfig($type, $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the configuration storage that provides the default configuration.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* The configuration installer.
|
||||||
|
*/
|
||||||
|
public function setSourceStorage(StorageInterface $storage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the configuration storage that provides the default configuration.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* The configuration installer.
|
||||||
|
*/
|
||||||
|
public function resetSourceStorage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of the isSyncing flag.
|
||||||
|
*
|
||||||
|
* @param bool $status
|
||||||
|
* The status of the sync flag.
|
||||||
|
*/
|
||||||
|
public function setSyncing($status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the syncing state.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Returns TRUE is syncing flag set.
|
||||||
|
*/
|
||||||
|
public function isSyncing();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,13 @@ class ConfigManager implements ConfigManagerInterface {
|
||||||
return $this->entityManager;
|
return $this->entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getConfigFactory() {
|
||||||
|
return $this->configFactory;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -31,6 +31,14 @@ interface ConfigManagerInterface {
|
||||||
*/
|
*/
|
||||||
public function getEntityManager();
|
public function getEntityManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the config factory.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\ConfigFactoryInterface
|
||||||
|
* The entity manager.
|
||||||
|
*/
|
||||||
|
public function getConfigFactory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a formatted diff of a named config between two storages.
|
* Return a formatted diff of a named config between two storages.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Drupal\Core\Config;
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\String;
|
||||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,11 +120,23 @@ class StorageComparer implements StorageComparerInterface {
|
||||||
* The change operation performed. Either delete, create or update.
|
* The change operation performed. Either delete, create or update.
|
||||||
* @param array $changes
|
* @param array $changes
|
||||||
* Array of changes to add to the changelist.
|
* 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($op, array $changes) {
|
protected function addChangeList($op, array $changes, array $sort_order = NULL) {
|
||||||
// Only add changes that aren't already listed.
|
// Only add changes that aren't already listed.
|
||||||
$changes = array_diff($changes, $this->changelist[$op]);
|
$changes = array_diff($changes, $this->changelist[$op]);
|
||||||
$this->changelist[$op] = array_merge($this->changelist[$op], $changes);
|
$this->changelist[$op] = array_merge($this->changelist[$op], $changes);
|
||||||
|
if (isset($sort_order)) {
|
||||||
|
$count = count($this->changelist[$op]);
|
||||||
|
// Sort the changlist in the same order as the $sort_order array and
|
||||||
|
// ensure the array is keyed from 0.
|
||||||
|
$this->changelist[$op] = array_values(array_intersect($sort_order, $this->changelist[$op]));
|
||||||
|
if ($count != count($this->changelist[$op])) {
|
||||||
|
throw new \InvalidArgumentException(String::format('Sorting the @op changelist should not change its length.', array('@op' => $op)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,8 +202,9 @@ class StorageComparer implements StorageComparerInterface {
|
||||||
if (!empty($recreates)) {
|
if (!empty($recreates)) {
|
||||||
// Recreates should become deletes and creates. Deletes should be ordered
|
// Recreates should become deletes and creates. Deletes should be ordered
|
||||||
// so that dependencies are deleted first.
|
// so that dependencies are deleted first.
|
||||||
$this->addChangeList('create', $recreates);
|
$this->addChangeList('create', $recreates, $this->sourceNames);
|
||||||
$this->addChangeList('delete', array_reverse($recreates));
|
$this->addChangeList('delete', $recreates, array_reverse($this->targetNames));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class ConfigImportSubscriber implements EventSubscriberInterface {
|
||||||
*/
|
*/
|
||||||
public function onConfigImporterValidate(ConfigImporterEvent $event) {
|
public function onConfigImporterValidate(ConfigImporterEvent $event) {
|
||||||
foreach (array('delete', 'create', 'update') as $op) {
|
foreach (array('delete', 'create', 'update') as $op) {
|
||||||
foreach ($event->getConfigImporter()->getUnprocessed($op) as $name) {
|
foreach ($event->getConfigImporter()->getUnprocessedConfiguration($op) as $name) {
|
||||||
Config::validateName($name);
|
Config::validateName($name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -582,6 +582,12 @@ class ModuleHandler implements ModuleHandlerInterface {
|
||||||
// Required for module installation checks.
|
// Required for module installation checks.
|
||||||
include_once DRUPAL_ROOT . '/core/includes/install.inc';
|
include_once DRUPAL_ROOT . '/core/includes/install.inc';
|
||||||
|
|
||||||
|
/** @var \Drupal\Core\Config\ConfigInstaller $config_installer */
|
||||||
|
$config_installer = \Drupal::service('config.installer');
|
||||||
|
$sync_status = $config_installer->isSyncing();
|
||||||
|
if ($sync_status) {
|
||||||
|
$source_storage = $config_installer->getSourceStorage();
|
||||||
|
}
|
||||||
$modules_installed = array();
|
$modules_installed = array();
|
||||||
foreach ($module_list as $module) {
|
foreach ($module_list as $module) {
|
||||||
$enabled = $extension_config->get("module.$module") !== NULL;
|
$enabled = $extension_config->get("module.$module") !== NULL;
|
||||||
|
@ -671,6 +677,18 @@ class ModuleHandler implements ModuleHandlerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install default configuration of the module.
|
// Install default configuration of the module.
|
||||||
|
$config_installer = \Drupal::service('config.installer');
|
||||||
|
if ($sync_status) {
|
||||||
|
$config_installer
|
||||||
|
->setSyncing(TRUE)
|
||||||
|
->setSourceStorage($source_storage);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If we're not in a config synchronisation reset the source storage
|
||||||
|
// so that the extension install storage will pick up the new
|
||||||
|
// configuration.
|
||||||
|
$config_installer->resetSourceStorage();
|
||||||
|
}
|
||||||
\Drupal::service('config.installer')->installDefaultConfig('module', $module);
|
\Drupal::service('config.installer')->installDefaultConfig('module', $module);
|
||||||
|
|
||||||
// If the module has no current updates, but has some that were
|
// If the module has no current updates, but has some that were
|
||||||
|
@ -732,7 +750,7 @@ class ModuleHandler implements ModuleHandlerInterface {
|
||||||
|
|
||||||
// Skip already uninstalled modules.
|
// Skip already uninstalled modules.
|
||||||
if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) {
|
if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) {
|
||||||
$module_list[$dependent] = TRUE;
|
$module_list[$dependent] = $dependent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,15 @@ class ThemeHandler implements ThemeHandlerInterface {
|
||||||
// Refresh the theme list as installation of default configuration needs
|
// Refresh the theme list as installation of default configuration needs
|
||||||
// an updated list to work.
|
// an updated list to work.
|
||||||
$this->reset();
|
$this->reset();
|
||||||
|
|
||||||
|
// The default config installation storage only knows about the currently
|
||||||
|
// enabled list of themes, so it has to be reset in order to pick up the
|
||||||
|
// default config of the newly installed theme. However, do not reset the
|
||||||
|
// source storage when synchronizing configuration, since that would
|
||||||
|
// needlessly trigger a reload of the whole configuration to be imported.
|
||||||
|
if (!$this->configInstaller->isSyncing()) {
|
||||||
|
$this->configInstaller->resetSourceStorage();
|
||||||
|
}
|
||||||
// Install default configuration of the theme.
|
// Install default configuration of the theme.
|
||||||
$this->configInstaller->installDefaultConfig('theme', $key);
|
$this->configInstaller->installDefaultConfig('theme', $key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
use Drupal\block\BlockInterface;
|
use Drupal\block\BlockInterface;
|
||||||
use Drupal\Component\Plugin\Exception\PluginException;
|
use Drupal\Component\Plugin\Exception\PluginException;
|
||||||
use Drupal\language\Entity\Language;
|
use Drupal\language\Entity\Language;
|
||||||
|
use Drupal\system\Entity\Menu;
|
||||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -422,13 +423,15 @@ function block_user_role_delete($role) {
|
||||||
/**
|
/**
|
||||||
* Implements hook_menu_delete().
|
* Implements hook_menu_delete().
|
||||||
*/
|
*/
|
||||||
function block_menu_delete($menu) {
|
function block_menu_delete(Menu $menu) {
|
||||||
|
if (!$menu->isSyncing()) {
|
||||||
foreach (entity_load_multiple('block') as $block) {
|
foreach (entity_load_multiple('block') as $block) {
|
||||||
if ($block->get('plugin') == 'system_menu_block:' . $menu->id()) {
|
if ($block->get('plugin') == 'system_menu_block:' . $menu->id()) {
|
||||||
$block->delete();
|
$block->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_ENTITY_TYPE_delete() for 'language_entity'.
|
* Implements hook_ENTITY_TYPE_delete() for 'language_entity'.
|
||||||
|
|
|
@ -184,6 +184,14 @@ class CommentManager implements CommentManagerInterface {
|
||||||
))
|
))
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
// The comment field should be hidden in all other form displays.
|
||||||
|
foreach ($this->entityManager->getFormModes($entity_type) as $id => $form_mode) {
|
||||||
|
$display = entity_get_form_display($entity_type, $bundle, $id);
|
||||||
|
// Only update existing displays.
|
||||||
|
if ($display && !$display->isNew()) {
|
||||||
|
$display->removeComponent($field_name)->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
// Set default to display comment list.
|
// Set default to display comment list.
|
||||||
entity_get_display($entity_type, $bundle, 'default')
|
entity_get_display($entity_type, $bundle, 'default')
|
||||||
->setComponent($field_name, array(
|
->setComponent($field_name, array(
|
||||||
|
@ -192,6 +200,15 @@ class CommentManager implements CommentManagerInterface {
|
||||||
'weight' => 20,
|
'weight' => 20,
|
||||||
))
|
))
|
||||||
->save();
|
->save();
|
||||||
|
// The comment field should be hidden in all other view displays.
|
||||||
|
foreach ($this->entityManager->getViewModes($entity_type) as $id => $view_mode) {
|
||||||
|
$display = entity_get_display($entity_type, $bundle, $id);
|
||||||
|
// Only update existing displays.
|
||||||
|
if ($display && !$display->isNew()) {
|
||||||
|
$display->removeComponent($field_name)->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
$this->addBodyField($entity_type, $field_name);
|
$this->addBodyField($entity_type, $field_name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
namespace Drupal\config\Form;
|
namespace Drupal\config\Form;
|
||||||
|
|
||||||
|
use Drupal\Component\Uuid\UuidInterface;
|
||||||
|
use Drupal\Core\Entity\EntityManagerInterface;
|
||||||
|
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||||
|
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||||
use Drupal\Core\Config\ConfigManagerInterface;
|
use Drupal\Core\Config\ConfigManagerInterface;
|
||||||
use Drupal\Core\Form\FormBase;
|
use Drupal\Core\Form\FormBase;
|
||||||
use Drupal\Core\Config\StorageInterface;
|
use Drupal\Core\Config\StorageInterface;
|
||||||
|
@ -72,6 +76,20 @@ class ConfigSync extends FormBase {
|
||||||
*/
|
*/
|
||||||
protected $typedConfigManager;
|
protected $typedConfigManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The module handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $moduleHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The theme handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Extension\ThemeHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $themeHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the object.
|
* Constructs the object.
|
||||||
*
|
*
|
||||||
|
@ -89,8 +107,12 @@ class ConfigSync extends FormBase {
|
||||||
* The url generator service.
|
* The url generator service.
|
||||||
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
|
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
|
||||||
* The typed configuration manager.
|
* The typed configuration manager.
|
||||||
|
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||||
|
* The module handler
|
||||||
|
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
|
||||||
|
* The theme handler
|
||||||
*/
|
*/
|
||||||
public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config) {
|
public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
|
||||||
$this->sourceStorage = $sourceStorage;
|
$this->sourceStorage = $sourceStorage;
|
||||||
$this->targetStorage = $targetStorage;
|
$this->targetStorage = $targetStorage;
|
||||||
$this->lock = $lock;
|
$this->lock = $lock;
|
||||||
|
@ -98,6 +120,8 @@ class ConfigSync extends FormBase {
|
||||||
$this->configManager = $config_manager;
|
$this->configManager = $config_manager;
|
||||||
$this->urlGenerator = $url_generator;
|
$this->urlGenerator = $url_generator;
|
||||||
$this->typedConfigManager = $typed_config;
|
$this->typedConfigManager = $typed_config;
|
||||||
|
$this->moduleHandler = $module_handler;
|
||||||
|
$this->themeHandler = $theme_handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,7 +135,9 @@ class ConfigSync extends FormBase {
|
||||||
$container->get('event_dispatcher'),
|
$container->get('event_dispatcher'),
|
||||||
$container->get('config.manager'),
|
$container->get('config.manager'),
|
||||||
$container->get('url_generator'),
|
$container->get('url_generator'),
|
||||||
$container->get('config.typed')
|
$container->get('config.typed'),
|
||||||
|
$container->get('module_handler'),
|
||||||
|
$container->get('theme_handler')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,24 +248,27 @@ class ConfigSync extends FormBase {
|
||||||
$this->eventDispatcher,
|
$this->eventDispatcher,
|
||||||
$this->configManager,
|
$this->configManager,
|
||||||
$this->lock,
|
$this->lock,
|
||||||
$this->typedConfigManager
|
$this->typedConfigManager,
|
||||||
|
$this->moduleHandler,
|
||||||
|
$this->themeHandler
|
||||||
);
|
);
|
||||||
if ($config_importer->alreadyImporting()) {
|
if ($config_importer->alreadyImporting()) {
|
||||||
drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
|
drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
$config_importer->initialize();
|
$operations = $config_importer->initialize();
|
||||||
$batch = array(
|
$batch = array(
|
||||||
'operations' => array(
|
'operations' => array(),
|
||||||
array(array(get_class($this), 'processBatch'), array($config_importer)),
|
|
||||||
),
|
|
||||||
'finished' => array(get_class($this), 'finishBatch'),
|
'finished' => array(get_class($this), 'finishBatch'),
|
||||||
'title' => t('Synchronizing configuration'),
|
'title' => t('Synchronizing configuration'),
|
||||||
'init_message' => t('Starting configuration synchronization.'),
|
'init_message' => t('Starting configuration synchronization.'),
|
||||||
'progress_message' => t('Synchronized @current configuration files out of @total.'),
|
'progress_message' => t('Completed @current step of @total.'),
|
||||||
'error_message' => t('Configuration synchronization has encountered an error.'),
|
'error_message' => t('Configuration synchronization has encountered an error.'),
|
||||||
'file' => drupal_get_path('module', 'config') . '/config.admin.inc',
|
'file' => drupal_get_path('module', 'config') . '/config.admin.inc',
|
||||||
);
|
);
|
||||||
|
foreach ($operations as $operation) {
|
||||||
|
$batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $operation));
|
||||||
|
}
|
||||||
|
|
||||||
batch_set($batch);
|
batch_set($batch);
|
||||||
}
|
}
|
||||||
|
@ -253,13 +282,13 @@ class ConfigSync extends FormBase {
|
||||||
* @param $context
|
* @param $context
|
||||||
* The batch context.
|
* The batch context.
|
||||||
*/
|
*/
|
||||||
public static function processBatch(BatchConfigImporter $config_importer, &$context) {
|
public static function processBatch(BatchConfigImporter $config_importer, $operation, &$context) {
|
||||||
if (!isset($context['sandbox']['config_importer'])) {
|
if (!isset($context['sandbox']['config_importer'])) {
|
||||||
$context['sandbox']['config_importer'] = $config_importer;
|
$context['sandbox']['config_importer'] = $config_importer;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config_importer = $context['sandbox']['config_importer'];
|
$config_importer = $context['sandbox']['config_importer'];
|
||||||
$config_importer->processBatch($context);
|
$config_importer->$operation($context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\config\Tests\ConfigImportAllTest.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\config\Tests;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\StorageComparer;
|
||||||
|
use Drupal\system\Tests\Module\ModuleTestBase;
|
||||||
|
|
||||||
|
class ConfigImportAllTest extends ModuleTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The profile to install as a basis for testing.
|
||||||
|
*
|
||||||
|
* Using the standard profile as this has a lot of additional configuration.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $profile = 'standard';
|
||||||
|
|
||||||
|
public static function getInfo() {
|
||||||
|
return array(
|
||||||
|
'name' => 'Import configuration from all modules and the standard profile',
|
||||||
|
'description' => 'Tests the largest configuration import possible with the modules and profiles provided by core.',
|
||||||
|
'group' => 'Configuration',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a fixed set of modules can be installed and uninstalled.
|
||||||
|
*/
|
||||||
|
public function testInstallUninstall() {
|
||||||
|
|
||||||
|
// Get a list of modules to enable.
|
||||||
|
$all_modules = system_rebuild_module_data();
|
||||||
|
$all_modules = array_filter($all_modules, function ($module) {
|
||||||
|
// Filter hidden, already enabled modules and modules in the Testing
|
||||||
|
// package.
|
||||||
|
if (!empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing') {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Install every module possible.
|
||||||
|
\Drupal::moduleHandler()->install(array_keys($all_modules));
|
||||||
|
|
||||||
|
$this->assertModules(array_keys($all_modules), TRUE);
|
||||||
|
foreach($all_modules as $module => $info) {
|
||||||
|
$this->assertModuleConfig($module);
|
||||||
|
$this->assertModuleTablesExist($module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export active config to staging
|
||||||
|
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||||
|
|
||||||
|
system_list_reset();
|
||||||
|
$this->resetAll();
|
||||||
|
|
||||||
|
// Delete every field on the site so all modules can be uninstalled. For
|
||||||
|
// example, if a comment field exists then module becomes required and can
|
||||||
|
// not be uninstalled.
|
||||||
|
$fields = \Drupal::service('field.info')->getFields();
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
entity_invoke_bundle_hook('delete', $field->entity_type, $field->entity_type . '__' . $field->name);
|
||||||
|
$field->delete();
|
||||||
|
}
|
||||||
|
// Purge the data.
|
||||||
|
field_purge_batch(1000);
|
||||||
|
|
||||||
|
system_list_reset();
|
||||||
|
$all_modules = system_rebuild_module_data();
|
||||||
|
$modules_to_uninstall = array_filter($all_modules, function ($module) {
|
||||||
|
// Filter required and not enabled modules.
|
||||||
|
if (!empty($module->info['required']) || $module->status == FALSE) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertTrue(isset($modules_to_uninstall['comment']), 'The comment module will be disabled');
|
||||||
|
|
||||||
|
// Uninstall all modules that can be uninstalled.
|
||||||
|
\Drupal::moduleHandler()->uninstall(array_keys($modules_to_uninstall));
|
||||||
|
|
||||||
|
$this->assertModules(array_keys($modules_to_uninstall), FALSE);
|
||||||
|
foreach($modules_to_uninstall as $module => $info) {
|
||||||
|
$this->assertNoModuleConfig($module);
|
||||||
|
$this->assertModuleTablesDoNotExist($module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import the configuration thereby re-installing all the modules.
|
||||||
|
$this->configImporter()->import();
|
||||||
|
|
||||||
|
// Check that all modules that were uninstalled are now reinstalled.
|
||||||
|
$this->assertModules(array_keys($modules_to_uninstall), TRUE);
|
||||||
|
foreach($modules_to_uninstall as $module => $info) {
|
||||||
|
$this->assertModuleConfig($module);
|
||||||
|
$this->assertModuleTablesExist($module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we have no configuration changes to import.
|
||||||
|
$storage_comparer = new StorageComparer(
|
||||||
|
$this->container->get('config.storage.staging'),
|
||||||
|
$this->container->get('config.storage')
|
||||||
|
);
|
||||||
|
$this->assertIdentical($storage_comparer->createChangelist()->getChangelist(), $storage_comparer->getEmptyChangelist());
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,8 @@ class ConfigImportRecreateTest extends DrupalUnitTestBase {
|
||||||
$this->installSchema('system', 'config_snapshot');
|
$this->installSchema('system', 'config_snapshot');
|
||||||
$this->installSchema('node', 'node');
|
$this->installSchema('node', 'node');
|
||||||
|
|
||||||
|
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||||
|
|
||||||
// Set up the ConfigImporter object for testing.
|
// Set up the ConfigImporter object for testing.
|
||||||
$storage_comparer = new StorageComparer(
|
$storage_comparer = new StorageComparer(
|
||||||
$this->container->get('config.storage.staging'),
|
$this->container->get('config.storage.staging'),
|
||||||
|
@ -55,9 +57,10 @@ class ConfigImportRecreateTest extends DrupalUnitTestBase {
|
||||||
$this->container->get('event_dispatcher'),
|
$this->container->get('event_dispatcher'),
|
||||||
$this->container->get('config.manager'),
|
$this->container->get('config.manager'),
|
||||||
$this->container->get('lock'),
|
$this->container->get('lock'),
|
||||||
$this->container->get('config.typed')
|
$this->container->get('config.typed'),
|
||||||
|
$this->container->get('module_handler'),
|
||||||
|
$this->container->get('theme_handler')
|
||||||
);
|
);
|
||||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRecreateEntity() {
|
public function testRecreateEntity() {
|
||||||
|
@ -89,21 +92,19 @@ class ConfigImportRecreateTest extends DrupalUnitTestBase {
|
||||||
$this->configImporter->reset();
|
$this->configImporter->reset();
|
||||||
// A node type, a field, a field instance an entity view display and an
|
// A node type, a field, a field instance an entity view display and an
|
||||||
// entity form display will be recreated.
|
// entity form display will be recreated.
|
||||||
$creates = $this->configImporter->getUnprocessed('create');
|
$creates = $this->configImporter->getUnprocessedConfiguration('create');
|
||||||
$deletes = $this->configImporter->getUnprocessed('delete');
|
$deletes = $this->configImporter->getUnprocessedConfiguration('delete');
|
||||||
$this->assertEqual(5, count($creates), 'There are 5 configuration items to create.');
|
$this->assertEqual(5, count($creates), 'There are 5 configuration items to create.');
|
||||||
$this->assertEqual(5, count($deletes), 'There are 5 configuration items to delete.');
|
$this->assertEqual(5, count($deletes), 'There are 5 configuration items to delete.');
|
||||||
$this->assertEqual(0, count($this->configImporter->getUnprocessed('update')), 'There are no configuration items to update.');
|
$this->assertEqual(0, count($this->configImporter->getUnprocessedConfiguration('update')), 'There are no configuration items to update.');
|
||||||
$this->assertIdentical($creates, array_reverse($deletes), 'Deletes and creates contain the same configuration names in opposite orders due to dependencies.');
|
$this->assertIdentical($creates, array_reverse($deletes), 'Deletes and creates contain the same configuration names in opposite orders due to dependencies.');
|
||||||
|
|
||||||
$this->configImporter->import();
|
$this->configImporter->import();
|
||||||
|
|
||||||
// Verify that there is nothing more to import.
|
// Verify that there is nothing more to import.
|
||||||
$this->assertFalse($this->configImporter->reset()->hasUnprocessedChanges());
|
$this->assertFalse($this->configImporter->reset()->hasUnprocessedConfigurationChanges());
|
||||||
$content_type = entity_load('node_type', $type_name);
|
$content_type = entity_load('node_type', $type_name);
|
||||||
$this->assertEqual('Node type one', $content_type->label());
|
$this->assertEqual('Node type one', $content_type->label());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
namespace Drupal\config\Tests;
|
namespace Drupal\config\Tests;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\InstallStorage;
|
||||||
use Drupal\simpletest\WebTestBase;
|
use Drupal\simpletest\WebTestBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +15,9 @@ use Drupal\simpletest\WebTestBase;
|
||||||
*/
|
*/
|
||||||
class ConfigImportUITest extends WebTestBase {
|
class ConfigImportUITest extends WebTestBase {
|
||||||
|
|
||||||
public static $modules = array('config', 'config_test');
|
// Enable the Options and Text modules to ensure dependencies are handled
|
||||||
|
// correctly.
|
||||||
|
public static $modules = array('config', 'config_test', 'config_import_test', 'text', 'options');
|
||||||
|
|
||||||
public static function getInfo() {
|
public static function getInfo() {
|
||||||
return array(
|
return array(
|
||||||
|
@ -38,6 +41,7 @@ class ConfigImportUITest extends WebTestBase {
|
||||||
function testImport() {
|
function testImport() {
|
||||||
$name = 'system.site';
|
$name = 'system.site';
|
||||||
$dynamic_name = 'config_test.dynamic.new';
|
$dynamic_name = 'config_test.dynamic.new';
|
||||||
|
/** @var \Drupal\Core\Config\StorageInterface $staging */
|
||||||
$staging = $this->container->get('config.storage.staging');
|
$staging = $this->container->get('config.storage.staging');
|
||||||
|
|
||||||
$this->drupalGet('admin/config/development/configuration');
|
$this->drupalGet('admin/config/development/configuration');
|
||||||
|
@ -65,16 +69,61 @@ class ConfigImportUITest extends WebTestBase {
|
||||||
$staging->write($dynamic_name, $original_dynamic_data);
|
$staging->write($dynamic_name, $original_dynamic_data);
|
||||||
$this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
|
$this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
|
||||||
|
|
||||||
|
// Enable the Action and Ban modules during import. The Ban
|
||||||
|
// module is used because it creates a table during the install. The Action
|
||||||
|
// module is used because it creates a single simple configuration file
|
||||||
|
// during the install.
|
||||||
|
$core_extension = \Drupal::config('core.extension')->get();
|
||||||
|
$core_extension['module']['action'] = 0;
|
||||||
|
$core_extension['module']['ban'] = 0;
|
||||||
|
$core_extension['module'] = module_config_sort($core_extension['module']);
|
||||||
|
$core_extension['theme']['bartik'] = 0;
|
||||||
|
$staging->write('core.extension', $core_extension);
|
||||||
|
|
||||||
|
// Use the install storage so that we can read configuration from modules
|
||||||
|
// and themes that are not installed.
|
||||||
|
$install_storage = new InstallStorage();
|
||||||
|
|
||||||
|
// Set the Bartik theme as default.
|
||||||
|
$system_theme = \Drupal::config('system.theme')->get();
|
||||||
|
$system_theme['default'] = 'bartik';
|
||||||
|
$staging->write('system.theme', $system_theme);
|
||||||
|
$staging->write('bartik.settings', $install_storage->read('bartik.settings'));
|
||||||
|
|
||||||
|
// Read the action config from module default config folder.
|
||||||
|
$action_settings = $install_storage->read('action.settings');
|
||||||
|
$action_settings['recursion_limit'] = 50;
|
||||||
|
$staging->write('action.settings', $action_settings);
|
||||||
|
|
||||||
|
// Uninstall the Options and Text modules to ensure that dependencies are
|
||||||
|
// handled correctly. Options depends on Text so Text should be installed
|
||||||
|
// first. Since they were enabled during the test setup the core.extension
|
||||||
|
// file in staging will already contain them.
|
||||||
|
\Drupal::moduleHandler()->uninstall(array('text', 'options'));
|
||||||
|
|
||||||
|
// Set the state system to record installations and uninstallations.
|
||||||
|
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_installed', array());
|
||||||
|
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_uninstalled', array());
|
||||||
|
|
||||||
// Verify that both appear as ready to import.
|
// Verify that both appear as ready to import.
|
||||||
$this->drupalGet('admin/config/development/configuration');
|
$this->drupalGet('admin/config/development/configuration');
|
||||||
$this->assertText($name);
|
$this->assertText($name);
|
||||||
$this->assertText($dynamic_name);
|
$this->assertText($dynamic_name);
|
||||||
|
$this->assertText('core.extension');
|
||||||
|
$this->assertText('system.theme');
|
||||||
|
$this->assertText('action.settings');
|
||||||
|
$this->assertText('bartik.settings');
|
||||||
$this->assertFieldById('edit-submit', t('Import all'));
|
$this->assertFieldById('edit-submit', t('Import all'));
|
||||||
|
|
||||||
// Import and verify that both do not appear anymore.
|
// Import and verify that both do not appear anymore.
|
||||||
$this->drupalPostForm(NULL, array(), t('Import all'));
|
$this->drupalPostForm(NULL, array(), t('Import all'));
|
||||||
$this->assertNoText($name);
|
$this->assertNoText($name);
|
||||||
$this->assertNoText($dynamic_name);
|
$this->assertNoText($dynamic_name);
|
||||||
|
$this->assertNoText('core.extension');
|
||||||
|
$this->assertNoText('system.theme');
|
||||||
|
$this->assertNoText('action.settings');
|
||||||
|
$this->assertNoText('bartik.settings');
|
||||||
|
|
||||||
$this->assertNoFieldById('edit-submit', t('Import all'));
|
$this->assertNoFieldById('edit-submit', t('Import all'));
|
||||||
|
|
||||||
// Verify that there are no further changes to import.
|
// Verify that there are no further changes to import.
|
||||||
|
@ -88,6 +137,85 @@ class ConfigImportUITest extends WebTestBase {
|
||||||
|
|
||||||
// Verify the cache got cleared.
|
// Verify the cache got cleared.
|
||||||
$this->assertTrue(isset($GLOBALS['hook_cache_flush']));
|
$this->assertTrue(isset($GLOBALS['hook_cache_flush']));
|
||||||
|
|
||||||
|
$this->rebuildContainer();
|
||||||
|
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('ban'), 'Ban module installed during import.');
|
||||||
|
$this->assertTrue(\Drupal::database()->schema()->tableExists('ban_ip'), 'The database table ban_ip exists.');
|
||||||
|
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('action'), 'Action module installed during import.');
|
||||||
|
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('options'), 'Options module installed during import.');
|
||||||
|
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('text'), 'Text module installed during import.');
|
||||||
|
|
||||||
|
$theme_info = \Drupal::service('theme_handler')->listInfo();
|
||||||
|
$this->assertTrue($theme_info['bartik']->status, 'Bartik theme enabled during import.');
|
||||||
|
|
||||||
|
// Ensure installations and uninstallation occur as expected.
|
||||||
|
$installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
|
||||||
|
$uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
|
||||||
|
$expected = array('ban', 'action', 'text', 'options');
|
||||||
|
$this->assertIdentical($expected, $installed, 'Ban, Action, Text and Options modules installed in the correct order.');
|
||||||
|
$this->assertTrue(empty($uninstalled), 'No modules uninstalled during import');
|
||||||
|
|
||||||
|
// Verify that the action.settings configuration object was only written
|
||||||
|
// once during the import process and only with the value set in the staged
|
||||||
|
// configuration. This verifies that the module's default configuration is
|
||||||
|
// used during configuration import and, additionally, that after installing
|
||||||
|
// a module, that configuration is not synced twice.
|
||||||
|
$recursion_limit_values = \Drupal::state()->get('ConfigImportUITest.action.settings.recursion_limit', array());
|
||||||
|
$this->assertIdentical($recursion_limit_values, array(50));
|
||||||
|
|
||||||
|
$core_extension = \Drupal::config('core.extension')->get();
|
||||||
|
unset($core_extension['module']['action']);
|
||||||
|
unset($core_extension['module']['ban']);
|
||||||
|
unset($core_extension['module']['options']);
|
||||||
|
unset($core_extension['module']['text']);
|
||||||
|
unset($core_extension['theme']['bartik']);
|
||||||
|
$core_extension['disabled']['theme']['bartik'] = 0;
|
||||||
|
$staging->write('core.extension', $core_extension);
|
||||||
|
$staging->delete('action.settings');
|
||||||
|
$staging->delete('text.settings');
|
||||||
|
|
||||||
|
$system_theme = \Drupal::config('system.theme')->get();
|
||||||
|
$system_theme['default'] = 'stark';
|
||||||
|
$system_theme['admin'] = 'stark';
|
||||||
|
$staging->write('system.theme', $system_theme);
|
||||||
|
|
||||||
|
// Set the state system to record installations and uninstallations.
|
||||||
|
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_installed', array());
|
||||||
|
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_uninstalled', array());
|
||||||
|
|
||||||
|
// Verify that both appear as ready to import.
|
||||||
|
$this->drupalGet('admin/config/development/configuration');
|
||||||
|
$this->assertText('core.extension');
|
||||||
|
$this->assertText('system.theme');
|
||||||
|
$this->assertText('action.settings');
|
||||||
|
|
||||||
|
// Import and verify that both do not appear anymore.
|
||||||
|
$this->drupalPostForm(NULL, array(), t('Import all'));
|
||||||
|
$this->assertNoText('core.extension');
|
||||||
|
$this->assertNoText('system.theme');
|
||||||
|
$this->assertNoText('action.settings');
|
||||||
|
|
||||||
|
$this->rebuildContainer();
|
||||||
|
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('ban'), 'Ban module uninstalled during import.');
|
||||||
|
$this->assertFalse(\Drupal::database()->schema()->tableExists('ban_ip'), 'The database table ban_ip does not exist.');
|
||||||
|
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('action'), 'Action module uninstalled during import.');
|
||||||
|
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('options'), 'Options module uninstalled during import.');
|
||||||
|
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('text'), 'Text module uninstalled during import.');
|
||||||
|
|
||||||
|
// Ensure installations and uninstallation occur as expected.
|
||||||
|
$installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
|
||||||
|
$uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
|
||||||
|
$expected = array('options', 'text', 'ban', 'action');
|
||||||
|
$this->assertIdentical($expected, $uninstalled, 'Options, Text, Action and Ban modules uninstalled in the correct order.');
|
||||||
|
$this->assertTrue(empty($installed), 'No modules installed during import');
|
||||||
|
|
||||||
|
$theme_info = \Drupal::service('theme_handler')->listInfo();
|
||||||
|
$this->assertTrue(isset($theme_info['bartik']) && !$theme_info['bartik']->status, 'Bartik theme disabled during import.');
|
||||||
|
|
||||||
|
// Verify that the action.settings configuration object was only deleted
|
||||||
|
// once during the import process.
|
||||||
|
$delete_called = \Drupal::state()->get('ConfigImportUITest.action.settings.delete', 0);
|
||||||
|
$this->assertIdentical($delete_called, 1, "The action.settings configuration was deleted once during configuration import.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -50,6 +50,8 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
||||||
// so it has to be cleared out manually.
|
// so it has to be cleared out manually.
|
||||||
unset($GLOBALS['hook_config_test']);
|
unset($GLOBALS['hook_config_test']);
|
||||||
|
|
||||||
|
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||||
|
|
||||||
// Set up the ConfigImporter object for testing.
|
// Set up the ConfigImporter object for testing.
|
||||||
$storage_comparer = new StorageComparer(
|
$storage_comparer = new StorageComparer(
|
||||||
$this->container->get('config.storage.staging'),
|
$this->container->get('config.storage.staging'),
|
||||||
|
@ -60,9 +62,10 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
||||||
$this->container->get('event_dispatcher'),
|
$this->container->get('event_dispatcher'),
|
||||||
$this->container->get('config.manager'),
|
$this->container->get('config.manager'),
|
||||||
$this->container->get('lock'),
|
$this->container->get('lock'),
|
||||||
$this->container->get('config.typed')
|
$this->container->get('config.typed'),
|
||||||
|
$this->container->get('module_handler'),
|
||||||
|
$this->container->get('theme_handler')
|
||||||
);
|
);
|
||||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,8 +148,7 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
||||||
$this->assertTrue(isset($GLOBALS['hook_config_test']['predelete']));
|
$this->assertTrue(isset($GLOBALS['hook_config_test']['predelete']));
|
||||||
$this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
|
$this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
|
||||||
|
|
||||||
// Verify that there is nothing more to import.
|
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
|
||||||
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -193,7 +195,7 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
||||||
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
||||||
|
|
||||||
// Verify that there is nothing more to import.
|
// Verify that there is nothing more to import.
|
||||||
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,7 +250,7 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
||||||
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
||||||
|
|
||||||
// Verify that there is nothing more to import.
|
// Verify that there is nothing more to import.
|
||||||
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class ConfigOverrideTest extends DrupalUnitTestBase {
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
$this->installSchema('system', 'config_snapshot');
|
$this->installSchema('system', 'config_snapshot');
|
||||||
|
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -36,6 +36,7 @@ class ConfigSnapshotTest extends DrupalUnitTestBase {
|
||||||
// Update the config snapshot. This allows the parent::setUp() to write
|
// Update the config snapshot. This allows the parent::setUp() to write
|
||||||
// configuration files.
|
// configuration files.
|
||||||
\Drupal::service('config.manager')->createSnapshot(\Drupal::service('config.storage'), \Drupal::service('config.storage.snapshot'));
|
\Drupal::service('config.manager')->createSnapshot(\Drupal::service('config.storage'), \Drupal::service('config.storage.snapshot'));
|
||||||
|
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
name: 'Configuration import test'
|
||||||
|
type: module
|
||||||
|
package: Testing
|
||||||
|
version: VERSION
|
||||||
|
core: 8.x
|
||||||
|
hidden: true
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Provides configuration import test helpers.
|
||||||
|
*/
|
|
@ -0,0 +1,6 @@
|
||||||
|
services:
|
||||||
|
config_import_test.event_subscriber:
|
||||||
|
class: Drupal\config_import_test\EventSubscriber
|
||||||
|
tags:
|
||||||
|
- { name: event_subscriber }
|
||||||
|
arguments: ['@state']
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\config_import_test\EventSubscriber.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\config_import_test;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\ConfigCrudEvent;
|
||||||
|
use Drupal\Core\Config\ConfigEvents;
|
||||||
|
use Drupal\Core\Config\ConfigImporterEvent;
|
||||||
|
use Drupal\Core\KeyValueStore\StateInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config import subscriber for config import events.
|
||||||
|
*/
|
||||||
|
class EventSubscriber implements EventSubscriberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key value store.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\KeyValueStore\StateInterface
|
||||||
|
*/
|
||||||
|
protected $state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the event subscriber.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\KeyValueStore\StateInterface $state
|
||||||
|
* The key value store.
|
||||||
|
*/
|
||||||
|
public function __construct(StateInterface $state) {
|
||||||
|
$this->state = $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the configuration to be imported.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigImporterEvent $event
|
||||||
|
* The Event to process.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\Core\Config\ConfigNameException
|
||||||
|
*/
|
||||||
|
public function onConfigImporterValidate(ConfigImporterEvent $event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reacts to a config save and records information in state for testing.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||||
|
*/
|
||||||
|
public function onConfigSave(ConfigCrudEvent $event) {
|
||||||
|
$config = $event->getConfig();
|
||||||
|
if ($config->getName() == 'action.settings') {
|
||||||
|
$values = $this->state->get('ConfigImportUITest.action.settings.recursion_limit', array());
|
||||||
|
$values[] = $config->get('recursion_limit');
|
||||||
|
$this->state->set('ConfigImportUITest.action.settings.recursion_limit', $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($config->getName() == 'core.extension') {
|
||||||
|
$installed = $this->state->get('ConfigImportUITest.core.extension.modules_installed', array());
|
||||||
|
$uninstalled = $this->state->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
|
||||||
|
$original = $config->getOriginal('module');
|
||||||
|
$data = $config->get('module');
|
||||||
|
$install = array_diff_key($data, $original);
|
||||||
|
if (!empty($install)) {
|
||||||
|
$installed[] = key($install);
|
||||||
|
}
|
||||||
|
$uninstall = array_diff_key($original, $data);
|
||||||
|
if (!empty($uninstall)) {
|
||||||
|
$uninstalled[] = key($uninstall);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->state->set('ConfigImportUITest.core.extension.modules_installed', $installed);
|
||||||
|
$this->state->set('ConfigImportUITest.core.extension.modules_uninstalled', $uninstalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reacts to a config delete and records information in state for testing.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||||
|
*/
|
||||||
|
public function onConfigDelete(ConfigCrudEvent $event) {
|
||||||
|
$config = $event->getConfig();
|
||||||
|
if ($config->getName() == 'action.settings') {
|
||||||
|
$value = $this->state->get('ConfigImportUITest.action.settings.delete', 0);
|
||||||
|
$this->state->set('ConfigImportUITest.action.settings.delete', $value + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the methods in this class that should be listeners.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of event listener definitions.
|
||||||
|
*/
|
||||||
|
static function getSubscribedEvents() {
|
||||||
|
$events[ConfigEvents::SAVE][] = array('onConfigSave', 40);
|
||||||
|
$events[ConfigEvents::DELETE][] = array('onConfigDelete', 40);
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,5 +15,11 @@ function contact_install() {
|
||||||
if (empty($site_mail)) {
|
if (empty($site_mail)) {
|
||||||
$site_mail = ini_get('sendmail_from');
|
$site_mail = ini_get('sendmail_from');
|
||||||
}
|
}
|
||||||
|
$config = \Drupal::config('contact.category.feedback');
|
||||||
|
// Update the recipients if the default configuration entity has been created.
|
||||||
|
// We should never rely on default config entities as during enabling a module
|
||||||
|
// during config sync they will not exist.
|
||||||
|
if (!$config->isNew()) {
|
||||||
\Drupal::config('contact.category.feedback')->set('recipients', array($site_mail))->save();
|
\Drupal::config('contact.category.feedback')->set('recipients', array($site_mail))->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -88,6 +88,19 @@ function content_translation_install() {
|
||||||
// hook_module_implements_alter() is run among the last ones.
|
// hook_module_implements_alter() is run among the last ones.
|
||||||
module_set_weight('content_translation', 10);
|
module_set_weight('content_translation', 10);
|
||||||
\Drupal::service('language_negotiator')->saveConfiguration(Language::TYPE_CONTENT, array(LanguageNegotiationUrl::METHOD_ID => 0));
|
\Drupal::service('language_negotiator')->saveConfiguration(Language::TYPE_CONTENT, array(LanguageNegotiationUrl::METHOD_ID => 0));
|
||||||
|
|
||||||
|
$config_names = \Drupal::configFactory()->listAll('field.field.');
|
||||||
|
foreach ($config_names as $name) {
|
||||||
|
\Drupal::config($name)
|
||||||
|
->set('settings.translation_sync', FALSE)
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
$config_names = \Drupal::configFactory()->listAll('field.instance.');
|
||||||
|
foreach ($config_names as $name) {
|
||||||
|
\Drupal::config($name)
|
||||||
|
->set('settings.translation_sync', FALSE)
|
||||||
|
->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,3 +118,21 @@ function content_translation_enable() {
|
||||||
$message = t('<a href="!settings_url">Enable translation</a> for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em>, or any other element you wish to translate.', $t_args);
|
$message = t('<a href="!settings_url">Enable translation</a> for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em>, or any other element you wish to translate.', $t_args);
|
||||||
drupal_set_message($message, 'warning');
|
drupal_set_message($message, 'warning');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_uninstall().
|
||||||
|
*/
|
||||||
|
function content_translation_uninstall() {
|
||||||
|
$config_names = \Drupal::configFactory()->listAll('field.field.');
|
||||||
|
foreach ($config_names as $name) {
|
||||||
|
\Drupal::config($name)
|
||||||
|
->clear('settings.translation_sync')
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
$config_names = \Drupal::configFactory()->listAll('field.instance.');
|
||||||
|
foreach ($config_names as $name) {
|
||||||
|
\Drupal::config($name)
|
||||||
|
->clear('settings.translation_sync')
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -794,4 +794,11 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
|
||||||
return $this->field->hasCustomStorage();
|
return $this->field->hasCustomStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isDeleted() {
|
||||||
|
return $this->deleted;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,4 +40,12 @@ interface FieldInstanceConfigInterface extends ConfigEntityInterface, FieldDefin
|
||||||
*/
|
*/
|
||||||
public function targetBundle();
|
public function targetBundle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the deleted flag of the field instance.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Returns TRUE if the instance is deleted.
|
||||||
|
*/
|
||||||
|
public function isDeleted();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,5 +15,5 @@ settings: { }
|
||||||
field_type: list_boolean
|
field_type: list_boolean
|
||||||
dependencies:
|
dependencies:
|
||||||
entity:
|
entity:
|
||||||
- field.field.forum.forum_container
|
- field.field.taxonomy_term.forum_container
|
||||||
- taxonomy.vocabulary.forums
|
- taxonomy.vocabulary.forums
|
||||||
|
|
|
@ -16,6 +16,7 @@ function forum_install() {
|
||||||
$locked['forum'] = 'forum';
|
$locked['forum'] = 'forum';
|
||||||
\Drupal::state()->set('node.type.locked', $locked);
|
\Drupal::state()->set('node.type.locked', $locked);
|
||||||
|
|
||||||
|
if (!\Drupal::service('config.installer')->isSyncing()) {
|
||||||
// Create the 'taxonomy_forums' field if it doesn't already exist. If forum
|
// Create the 'taxonomy_forums' field if it doesn't already exist. If forum
|
||||||
// is being enabled at the same time as taxonomy after both modules have been
|
// is being enabled at the same time as taxonomy after both modules have been
|
||||||
// enabled, the field might exist but still be marked inactive.
|
// enabled, the field might exist but still be marked inactive.
|
||||||
|
@ -67,6 +68,7 @@ function forum_install() {
|
||||||
'weight' => 10,
|
'weight' => 10,
|
||||||
))
|
))
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
entity_get_display('node', 'forum', 'teaser')
|
entity_get_display('node', 'forum', 'teaser')
|
||||||
->setComponent('taxonomy_forums', array(
|
->setComponent('taxonomy_forums', array(
|
||||||
'type' => 'taxonomy_term_reference_link',
|
'type' => 'taxonomy_term_reference_link',
|
||||||
|
@ -84,6 +86,7 @@ function forum_install() {
|
||||||
Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'comment_forum');
|
Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'comment_forum');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_uninstall().
|
* Implements hook_uninstall().
|
||||||
|
|
|
@ -459,8 +459,10 @@ function node_uninstall() {
|
||||||
$types = \Drupal::configFactory()->listAll('node.type.');
|
$types = \Drupal::configFactory()->listAll('node.type.');
|
||||||
foreach ($types as $config_name) {
|
foreach ($types as $config_name) {
|
||||||
$type = \Drupal::config($config_name)->get('type');
|
$type = \Drupal::config($config_name)->get('type');
|
||||||
|
if (\Drupal::moduleHandler()->moduleExists('language')) {
|
||||||
\Drupal::config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save();
|
\Drupal::config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete remaining general module variables.
|
// Delete remaining general module variables.
|
||||||
\Drupal::state()->delete('node.node_access_needs_rebuild');
|
\Drupal::state()->delete('node.node_access_needs_rebuild');
|
||||||
|
|
|
@ -87,7 +87,7 @@ class SearchPageRepository implements SearchPageRepositoryInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, use the first active search page.
|
// Otherwise, use the first active search page.
|
||||||
return reset($search_pages);
|
return is_array($search_pages) ? reset($search_pages) : FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -148,7 +148,7 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
|
||||||
// \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work.
|
// \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work.
|
||||||
// Write directly to active storage to avoid early instantiation of
|
// Write directly to active storage to avoid early instantiation of
|
||||||
// the event dispatcher which can prevent modules from registering events.
|
// the event dispatcher which can prevent modules from registering events.
|
||||||
\Drupal::service('config.storage')->write('core.extension', array('module' => array()));
|
\Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array()));
|
||||||
|
|
||||||
// Collect and set a fixed module list.
|
// Collect and set a fixed module list.
|
||||||
$class = get_class($this);
|
$class = get_class($this);
|
||||||
|
|
|
@ -1526,7 +1526,9 @@ abstract class TestBase {
|
||||||
$this->container->get('event_dispatcher'),
|
$this->container->get('event_dispatcher'),
|
||||||
$this->container->get('config.manager'),
|
$this->container->get('config.manager'),
|
||||||
$this->container->get('lock'),
|
$this->container->get('lock'),
|
||||||
$this->container->get('config.typed')
|
$this->container->get('config.typed'),
|
||||||
|
$this->container->get('module_handler'),
|
||||||
|
$this->container->get('theme_handler')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Always recalculate the changelist when called.
|
// Always recalculate the changelist when called.
|
||||||
|
|
|
@ -182,7 +182,7 @@ function simpletest_uninstall() {
|
||||||
// Do not clean the environment in case the Simpletest module is uninstalled
|
// Do not clean the environment in case the Simpletest module is uninstalled
|
||||||
// in a (recursive) test for itself, since simpletest_clean_environment()
|
// in a (recursive) test for itself, since simpletest_clean_environment()
|
||||||
// would also delete the test site of the parent test process.
|
// would also delete the test site of the parent test process.
|
||||||
if (!DRUPAL_TEST_IN_CHILD_SITE) {
|
if (!drupal_valid_test_ua()) {
|
||||||
simpletest_clean_environment();
|
simpletest_clean_environment();
|
||||||
}
|
}
|
||||||
// Delete verbose test output and any other testing framework files.
|
// Delete verbose test output and any other testing framework files.
|
||||||
|
|
|
@ -4,6 +4,13 @@ bundle: article
|
||||||
mode: default
|
mode: default
|
||||||
status: true
|
status: true
|
||||||
content:
|
content:
|
||||||
|
field_image:
|
||||||
|
label: hidden
|
||||||
|
type: image
|
||||||
|
settings:
|
||||||
|
image_style: large
|
||||||
|
image_link: ''
|
||||||
|
weight: -1
|
||||||
body:
|
body:
|
||||||
label: hidden
|
label: hidden
|
||||||
type: text_default
|
type: text_default
|
||||||
|
@ -14,13 +21,6 @@ content:
|
||||||
weight: 10
|
weight: 10
|
||||||
label: above
|
label: above
|
||||||
settings: { }
|
settings: { }
|
||||||
field_image:
|
|
||||||
label: hidden
|
|
||||||
type: image
|
|
||||||
settings:
|
|
||||||
image_style: large
|
|
||||||
image_link: ''
|
|
||||||
weight: -1
|
|
||||||
dependencies:
|
dependencies:
|
||||||
entity:
|
entity:
|
||||||
- field.instance.node.article.body
|
- field.instance.node.article.body
|
||||||
|
|
|
@ -4,6 +4,13 @@ bundle: article
|
||||||
mode: teaser
|
mode: teaser
|
||||||
status: true
|
status: true
|
||||||
content:
|
content:
|
||||||
|
field_image:
|
||||||
|
label: hidden
|
||||||
|
type: image
|
||||||
|
settings:
|
||||||
|
image_style: medium
|
||||||
|
image_link: content
|
||||||
|
weight: -1
|
||||||
body:
|
body:
|
||||||
label: hidden
|
label: hidden
|
||||||
type: text_summary_or_trimmed
|
type: text_summary_or_trimmed
|
||||||
|
@ -15,13 +22,6 @@ content:
|
||||||
weight: 10
|
weight: 10
|
||||||
label: above
|
label: above
|
||||||
settings: { }
|
settings: { }
|
||||||
field_image:
|
|
||||||
label: hidden
|
|
||||||
type: image
|
|
||||||
settings:
|
|
||||||
image_style: medium
|
|
||||||
image_link: content
|
|
||||||
weight: -1
|
|
||||||
dependencies:
|
dependencies:
|
||||||
entity:
|
entity:
|
||||||
- entity.view_mode.node.teaser
|
- entity.view_mode.node.teaser
|
||||||
|
|
Loading…
Reference in New Issue