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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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() {
|
||||
$batch_operations = array();
|
||||
$this->createExtensionChangelist();
|
||||
|
||||
// Ensure that the changes have been validated.
|
||||
$this->validate();
|
||||
|
||||
|
@ -25,61 +49,137 @@ class BatchConfigImporter extends ConfigImporter {
|
|||
// Another process is synchronizing configuration.
|
||||
throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
|
||||
}
|
||||
$this->totalToProcess = 0;
|
||||
foreach(array('create', 'delete', 'update') as $op) {
|
||||
$this->totalToProcess += count($this->getUnprocessed($op));
|
||||
|
||||
$modules = $this->getUnprocessedExtensions('module');
|
||||
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.
|
||||
* The batch context.
|
||||
*/
|
||||
public function processBatch(array &$context) {
|
||||
$operation = $this->getNextOperation();
|
||||
public function processExtensionBatch(array &$context) {
|
||||
$operation = $this->getNextExtensionOperation();
|
||||
if (!empty($operation)) {
|
||||
$this->process($operation['op'], $operation['name']);
|
||||
$context['message'] = t('Synchronizing @name.', array('@name' => $operation['name']));
|
||||
$context['finished'] = $this->batchProgress();
|
||||
$this->processExtension($operation['type'], $operation['op'], $operation['name']);
|
||||
$context['message'] = t('Synchronising extensions: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
|
||||
$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 {
|
||||
$context['finished'] = 1;
|
||||
}
|
||||
if ($context['finished'] >= 1) {
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
|
||||
// The import is now complete.
|
||||
$this->lock->release(static::LOCK_ID);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets percentage of progress made.
|
||||
* Finishes the batch.
|
||||
*
|
||||
* @return float
|
||||
* The percentage of progress made expressed as a float between 0 and 1.
|
||||
* @param array $context.
|
||||
* The batch context.
|
||||
*/
|
||||
protected function batchProgress() {
|
||||
$processed_count = count($this->processed['create']) + count($this->processed['delete']) + count($this->processed['update']);
|
||||
return $processed_count / $this->totalToProcess;
|
||||
public function finishBatch(array &$context) {
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
|
||||
// The import is now complete.
|
||||
$this->lock->release(static::LOCK_ID);
|
||||
$this->reset();
|
||||
$context['message'] = t('Finalising configuration synchronisation.');
|
||||
$context['finished'] = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next operation to perform.
|
||||
* Gets the next extension operation to perform.
|
||||
*
|
||||
* @return array|bool
|
||||
* An array containing the next operation and extension name to perform it
|
||||
* on. If there is nothing left to do returns FALSE;
|
||||
*/
|
||||
protected function getNextExtensionOperation() {
|
||||
foreach (array('install', 'uninstall') as $op) {
|
||||
$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 configuration operation to perform.
|
||||
*
|
||||
* @return array|bool
|
||||
* An array containing the next operation and configuration name to perform
|
||||
* it on. If there is nothing left to do returns FALSE;
|
||||
*/
|
||||
protected function getNextOperation() {
|
||||
foreach(array('create', 'delete', 'update') as $op) {
|
||||
$names = $this->getUnprocessed($op);
|
||||
if (!empty($names)) {
|
||||
protected function getNextConfigurationOperation() {
|
||||
// The order configuration operations is processed is important. Deletes
|
||||
// have to come first so that recreates can work.
|
||||
foreach (array('delete', 'create', 'update') as $op) {
|
||||
$config_names = $this->getUnprocessedConfiguration($op);
|
||||
if (!empty($config_names)) {
|
||||
return array(
|
||||
'op' => $op,
|
||||
'name' => array_shift($names),
|
||||
'name' => array_shift($config_names),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Drupal\Component\Utility\String;
|
||||
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
|
||||
use Drupal\Core\DependencyInjection\DependencySerialization;
|
||||
|
@ -71,16 +73,30 @@ class ConfigImporter extends DependencySerialization {
|
|||
/**
|
||||
* The typed config manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManager
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedConfigManager;
|
||||
|
||||
/**
|
||||
* List of changes processed by the import().
|
||||
* List of configuration file changes processed by the import().
|
||||
*
|
||||
* @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.
|
||||
|
@ -89,6 +105,27 @@ class ConfigImporter extends DependencySerialization {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -103,14 +140,21 @@ class ConfigImporter extends DependencySerialization {
|
|||
* The lock backend to ensure multiple imports do not occur at the same time.
|
||||
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
|
||||
* 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->eventDispatcher = $event_dispatcher;
|
||||
$this->configManager = $config_manager;
|
||||
$this->lock = $lock;
|
||||
$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() {
|
||||
$this->storageComparer->reset();
|
||||
$this->processed = $this->storageComparer->getEmptyChangelist();
|
||||
$this->processedConfiguration = $this->storageComparer->getEmptyChangelist();
|
||||
$this->processedExtensions = $this->getEmptyExtensionsProcessedList();
|
||||
$this->createExtensionChangelist();
|
||||
$this->validated = FALSE;
|
||||
$this->processedSystemTheme = FALSE;
|
||||
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
|
||||
* The operations to check for changes. Defaults to all operations, i.e.
|
||||
|
@ -146,9 +212,9 @@ class ConfigImporter extends DependencySerialization {
|
|||
* @return bool
|
||||
* 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) {
|
||||
if (count($this->getUnprocessed($op))) {
|
||||
if (count($this->getUnprocessedConfiguration($op))) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
@ -161,8 +227,8 @@ class ConfigImporter extends DependencySerialization {
|
|||
* @return array
|
||||
* An array containing a list of processed changes.
|
||||
*/
|
||||
public function getProcessed() {
|
||||
return $this->processed;
|
||||
public function getProcessedConfiguration() {
|
||||
return $this->processedConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,8 +239,8 @@ class ConfigImporter extends DependencySerialization {
|
|||
* @param string $name
|
||||
* The name of the configuration processed.
|
||||
*/
|
||||
protected function setProcessed($op, $name) {
|
||||
$this->processed[$op][] = $name;
|
||||
protected function setProcessedConfiguration($op, $name) {
|
||||
$this->processedConfiguration[$op][] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,8 +253,139 @@ class ConfigImporter extends DependencySerialization {
|
|||
* @return array
|
||||
* An array of configuration names.
|
||||
*/
|
||||
public function getUnprocessed($op) {
|
||||
return array_diff($this->storageComparer->getChangelist($op), $this->processed[$op]);
|
||||
public function getUnprocessedConfiguration($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.
|
||||
*/
|
||||
public function import() {
|
||||
if ($this->hasUnprocessedChanges()) {
|
||||
if ($this->hasUnprocessedConfigurationChanges()) {
|
||||
$this->createExtensionChangelist();
|
||||
|
||||
// Ensure that the changes have been validated.
|
||||
$this->validate();
|
||||
|
||||
|
@ -208,19 +407,20 @@ class ConfigImporter extends DependencySerialization {
|
|||
// Another process is synchronizing configuration.
|
||||
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
|
||||
// to handle dependencies correctly.
|
||||
// @todo Implement proper dependency ordering using
|
||||
// https://drupal.org/node/2080823
|
||||
foreach (array('delete', 'create', 'update') as $op) {
|
||||
foreach ($this->getUnprocessed($op) as $name) {
|
||||
$this->process($op, $name);
|
||||
foreach ($this->getUnprocessedConfiguration($op) as $name) {
|
||||
$this->processConfiguration($op, $name);
|
||||
}
|
||||
}
|
||||
// Allow modules to react to a import.
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
|
||||
|
||||
|
||||
// The import is now complete.
|
||||
$this->lock->release(static::LOCK_ID);
|
||||
$this->reset();
|
||||
|
@ -253,12 +453,58 @@ class ConfigImporter extends DependencySerialization {
|
|||
* @param string $name
|
||||
* The name of the configuration to process.
|
||||
*/
|
||||
protected function process($op, $name) {
|
||||
protected function processConfiguration($op, $name) {
|
||||
if (!$this->importInvokeOwner($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.
|
||||
*
|
||||
|
@ -277,7 +523,7 @@ class ConfigImporter extends DependencySerialization {
|
|||
$config->setData($data ? $data : array());
|
||||
$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)));
|
||||
}
|
||||
$entity_storage->$method($name, $new_config, $old_config);
|
||||
$this->setProcessed($op, $name);
|
||||
$this->setProcessedConfiguration($op, $name);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
@ -341,4 +587,74 @@ class ConfigImporter extends DependencySerialization {
|
|||
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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -75,7 +89,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
*/
|
||||
public function installDefaultConfig($type, $name) {
|
||||
// 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 . '.');
|
||||
|
||||
// Work out if this extension provides default configuration for any other
|
||||
|
@ -130,6 +144,16 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$new_config->setData($data[$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
|
||||
->getEntityManager()
|
||||
->getStorage($entity_type);
|
||||
|
@ -159,4 +183,49 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$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);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigFactory() {
|
||||
return $this->configFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -31,6 +31,14 @@ interface ConfigManagerInterface {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\String;
|
||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||
|
||||
/**
|
||||
|
@ -118,11 +120,23 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* The change operation performed. Either delete, create or update.
|
||||
* @param array $changes
|
||||
* Array of changes to add to the changelist.
|
||||
* @param array $sort_order
|
||||
* Array to sort that can be used to sort the changelist. This array must
|
||||
* contain all the items that are in the change list.
|
||||
*/
|
||||
protected function addChangeList($op, array $changes) {
|
||||
protected function addChangeList($op, array $changes, array $sort_order = NULL) {
|
||||
// Only add changes that aren't already listed.
|
||||
$changes = array_diff($changes, $this->changelist[$op]);
|
||||
$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)) {
|
||||
// Recreates should become deletes and creates. Deletes should be ordered
|
||||
// so that dependencies are deleted first.
|
||||
$this->addChangeList('create', $recreates);
|
||||
$this->addChangeList('delete', array_reverse($recreates));
|
||||
$this->addChangeList('create', $recreates, $this->sourceNames);
|
||||
$this->addChangeList('delete', $recreates, array_reverse($this->targetNames));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class ConfigImportSubscriber implements EventSubscriberInterface {
|
|||
*/
|
||||
public function onConfigImporterValidate(ConfigImporterEvent $event) {
|
||||
foreach (array('delete', 'create', 'update') as $op) {
|
||||
foreach ($event->getConfigImporter()->getUnprocessed($op) as $name) {
|
||||
foreach ($event->getConfigImporter()->getUnprocessedConfiguration($op) as $name) {
|
||||
Config::validateName($name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -582,6 +582,12 @@ class ModuleHandler implements ModuleHandlerInterface {
|
|||
// Required for module installation checks.
|
||||
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();
|
||||
foreach ($module_list as $module) {
|
||||
$enabled = $extension_config->get("module.$module") !== NULL;
|
||||
|
@ -671,6 +677,18 @@ class ModuleHandler implements ModuleHandlerInterface {
|
|||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// If the module has no current updates, but has some that were
|
||||
|
@ -732,7 +750,7 @@ class ModuleHandler implements ModuleHandlerInterface {
|
|||
|
||||
// Skip already uninstalled modules.
|
||||
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
|
||||
// an updated list to work.
|
||||
$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.
|
||||
$this->configInstaller->installDefaultConfig('theme', $key);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use Drupal\block\BlockInterface;
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\language\Entity\Language;
|
||||
use Drupal\system\Entity\Menu;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
|
||||
/**
|
||||
|
@ -422,10 +423,12 @@ function block_user_role_delete($role) {
|
|||
/**
|
||||
* Implements hook_menu_delete().
|
||||
*/
|
||||
function block_menu_delete($menu) {
|
||||
foreach (entity_load_multiple('block') as $block) {
|
||||
if ($block->get('plugin') == 'system_menu_block:' . $menu->id()) {
|
||||
$block->delete();
|
||||
function block_menu_delete(Menu $menu) {
|
||||
if (!$menu->isSyncing()) {
|
||||
foreach (entity_load_multiple('block') as $block) {
|
||||
if ($block->get('plugin') == 'system_menu_block:' . $menu->id()) {
|
||||
$block->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,6 +184,14 @@ class CommentManager implements CommentManagerInterface {
|
|||
))
|
||||
->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.
|
||||
entity_get_display($entity_type, $bundle, 'default')
|
||||
->setComponent($field_name, array(
|
||||
|
@ -192,6 +200,15 @@ class CommentManager implements CommentManagerInterface {
|
|||
'weight' => 20,
|
||||
))
|
||||
->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);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
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\Form\FormBase;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
@ -72,6 +76,20 @@ class ConfigSync extends FormBase {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -89,8 +107,12 @@ class ConfigSync extends FormBase {
|
|||
* The url generator service.
|
||||
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
|
||||
* 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->targetStorage = $targetStorage;
|
||||
$this->lock = $lock;
|
||||
|
@ -98,6 +120,8 @@ class ConfigSync extends FormBase {
|
|||
$this->configManager = $config_manager;
|
||||
$this->urlGenerator = $url_generator;
|
||||
$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('config.manager'),
|
||||
$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->configManager,
|
||||
$this->lock,
|
||||
$this->typedConfigManager
|
||||
$this->typedConfigManager,
|
||||
$this->moduleHandler,
|
||||
$this->themeHandler
|
||||
);
|
||||
if ($config_importer->alreadyImporting()) {
|
||||
drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
|
||||
}
|
||||
else{
|
||||
$config_importer->initialize();
|
||||
$operations = $config_importer->initialize();
|
||||
$batch = array(
|
||||
'operations' => array(
|
||||
array(array(get_class($this), 'processBatch'), array($config_importer)),
|
||||
),
|
||||
'operations' => array(),
|
||||
'finished' => array(get_class($this), 'finishBatch'),
|
||||
'title' => t('Synchronizing configuration'),
|
||||
'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.'),
|
||||
'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);
|
||||
}
|
||||
|
@ -253,13 +282,13 @@ class ConfigSync extends FormBase {
|
|||
* @param $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'])) {
|
||||
$context['sandbox']['config_importer'] = $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('node', 'node');
|
||||
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||
|
||||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
|
@ -55,9 +57,10 @@ class ConfigImportRecreateTest extends DrupalUnitTestBase {
|
|||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.manager'),
|
||||
$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() {
|
||||
|
@ -89,21 +92,19 @@ class ConfigImportRecreateTest extends DrupalUnitTestBase {
|
|||
$this->configImporter->reset();
|
||||
// A node type, a field, a field instance an entity view display and an
|
||||
// entity form display will be recreated.
|
||||
$creates = $this->configImporter->getUnprocessed('create');
|
||||
$deletes = $this->configImporter->getUnprocessed('delete');
|
||||
$creates = $this->configImporter->getUnprocessedConfiguration('create');
|
||||
$deletes = $this->configImporter->getUnprocessedConfiguration('delete');
|
||||
$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(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->configImporter->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);
|
||||
$this->assertEqual('Node type one', $content_type->label());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\config\Tests;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
|
@ -14,7 +15,9 @@ use Drupal\simpletest\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() {
|
||||
return array(
|
||||
|
@ -38,6 +41,7 @@ class ConfigImportUITest extends WebTestBase {
|
|||
function testImport() {
|
||||
$name = 'system.site';
|
||||
$dynamic_name = 'config_test.dynamic.new';
|
||||
/** @var \Drupal\Core\Config\StorageInterface $staging */
|
||||
$staging = $this->container->get('config.storage.staging');
|
||||
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
|
@ -65,16 +69,61 @@ class ConfigImportUITest extends WebTestBase {
|
|||
$staging->write($dynamic_name, $original_dynamic_data);
|
||||
$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.
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$this->assertText($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'));
|
||||
|
||||
// Import and verify that both do not appear anymore.
|
||||
$this->drupalPostForm(NULL, array(), t('Import all'));
|
||||
$this->assertNoText($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'));
|
||||
|
||||
// Verify that there are no further changes to import.
|
||||
|
@ -88,6 +137,85 @@ class ConfigImportUITest extends WebTestBase {
|
|||
|
||||
// Verify the cache got cleared.
|
||||
$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.
|
||||
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.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
|
@ -60,9 +62,10 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
|||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.manager'),
|
||||
$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']['delete']));
|
||||
|
||||
// Verify that there is nothing more to import.
|
||||
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
||||
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -193,7 +195,7 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
|||
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
||||
|
||||
// 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']));
|
||||
|
||||
// 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() {
|
||||
parent::setUp();
|
||||
$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
|
||||
// configuration files.
|
||||
\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)) {
|
||||
$site_mail = ini_get('sendmail_from');
|
||||
}
|
||||
\Drupal::config('contact.category.feedback')->set('recipients', array($site_mail))->save();
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,19 @@ function content_translation_install() {
|
|||
// hook_module_implements_alter() is run among the last ones.
|
||||
module_set_weight('content_translation', 10);
|
||||
\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);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDeleted() {
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,4 +40,12 @@ interface FieldInstanceConfigInterface extends ConfigEntityInterface, FieldDefin
|
|||
*/
|
||||
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
|
||||
dependencies:
|
||||
entity:
|
||||
- field.field.forum.forum_container
|
||||
- field.field.taxonomy_term.forum_container
|
||||
- taxonomy.vocabulary.forums
|
||||
|
|
|
@ -16,72 +16,75 @@ function forum_install() {
|
|||
$locked['forum'] = 'forum';
|
||||
\Drupal::state()->set('node.type.locked', $locked);
|
||||
|
||||
// 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
|
||||
// enabled, the field might exist but still be marked inactive.
|
||||
if (!field_info_field('node', 'taxonomy_forums')) {
|
||||
entity_create('field_config', array(
|
||||
'name' => 'taxonomy_forums',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'taxonomy_term_reference',
|
||||
'settings' => array(
|
||||
'allowed_values' => array(
|
||||
array(
|
||||
'vocabulary' => 'forums',
|
||||
'parent' => 0,
|
||||
if (!\Drupal::service('config.installer')->isSyncing()) {
|
||||
// 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
|
||||
// enabled, the field might exist but still be marked inactive.
|
||||
if (!field_info_field('node', 'taxonomy_forums')) {
|
||||
entity_create('field_config', array(
|
||||
'name' => 'taxonomy_forums',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'taxonomy_term_reference',
|
||||
'settings' => array(
|
||||
'allowed_values' => array(
|
||||
array(
|
||||
'vocabulary' => 'forums',
|
||||
'parent' => 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
))->save();
|
||||
))->save();
|
||||
|
||||
// Create a default forum so forum posts can be created.
|
||||
$term = entity_create('taxonomy_term', array(
|
||||
'name' => t('General discussion'),
|
||||
'description' => '',
|
||||
'parent' => array(0),
|
||||
'vid' => 'forums',
|
||||
'forum_container' => 0,
|
||||
// Create a default forum so forum posts can be created.
|
||||
$term = entity_create('taxonomy_term', array(
|
||||
'name' => t('General discussion'),
|
||||
'description' => '',
|
||||
'parent' => array(0),
|
||||
'vid' => 'forums',
|
||||
'forum_container' => 0,
|
||||
));
|
||||
$term->save();
|
||||
|
||||
// Create the instance on the bundle.
|
||||
entity_create('field_instance_config', array(
|
||||
'field_name' => 'taxonomy_forums',
|
||||
'entity_type' => 'node',
|
||||
'label' => 'Forums',
|
||||
'bundle' => 'forum',
|
||||
'required' => TRUE,
|
||||
))->save();
|
||||
|
||||
// Assign form display settings for the 'default' form mode.
|
||||
entity_get_form_display('node', 'forum', 'default')
|
||||
->setComponent('taxonomy_forums', array(
|
||||
'type' => 'options_select',
|
||||
))
|
||||
->save();
|
||||
|
||||
// Assign display settings for the 'default' and 'teaser' view modes.
|
||||
entity_get_display('node', 'forum', 'default')
|
||||
->setComponent('taxonomy_forums', array(
|
||||
'type' => 'taxonomy_term_reference_link',
|
||||
'weight' => 10,
|
||||
))
|
||||
->save();
|
||||
|
||||
entity_get_display('node', 'forum', 'teaser')
|
||||
->setComponent('taxonomy_forums', array(
|
||||
'type' => 'taxonomy_term_reference_link',
|
||||
'weight' => 10,
|
||||
))
|
||||
->save();
|
||||
}
|
||||
// Add the comment field to the forum node type.
|
||||
$fields = entity_load_multiple_by_properties('field_config', array(
|
||||
'type' => 'comment',
|
||||
'name' => 'comment_forum',
|
||||
'include_deleted' => FALSE,
|
||||
));
|
||||
$term->save();
|
||||
|
||||
// Create the instance on the bundle.
|
||||
entity_create('field_instance_config', array(
|
||||
'field_name' => 'taxonomy_forums',
|
||||
'entity_type' => 'node',
|
||||
'label' => 'Forums',
|
||||
'bundle' => 'forum',
|
||||
'required' => TRUE,
|
||||
))->save();
|
||||
|
||||
// Assign form display settings for the 'default' form mode.
|
||||
entity_get_form_display('node', 'forum', 'default')
|
||||
->setComponent('taxonomy_forums', array(
|
||||
'type' => 'options_select',
|
||||
))
|
||||
->save();
|
||||
|
||||
// Assign display settings for the 'default' and 'teaser' view modes.
|
||||
entity_get_display('node', 'forum', 'default')
|
||||
->setComponent('taxonomy_forums', array(
|
||||
'type' => 'taxonomy_term_reference_link',
|
||||
'weight' => 10,
|
||||
))
|
||||
->save();
|
||||
entity_get_display('node', 'forum', 'teaser')
|
||||
->setComponent('taxonomy_forums', array(
|
||||
'type' => 'taxonomy_term_reference_link',
|
||||
'weight' => 10,
|
||||
))
|
||||
->save();
|
||||
}
|
||||
// Add the comment field to the forum node type.
|
||||
$fields = entity_load_multiple_by_properties('field_config', array(
|
||||
'type' => 'comment',
|
||||
'name' => 'comment_forum',
|
||||
'include_deleted' => FALSE,
|
||||
));
|
||||
if (empty($fields)) {
|
||||
Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'comment_forum');
|
||||
if (empty($fields)) {
|
||||
Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'comment_forum');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -459,7 +459,9 @@ function node_uninstall() {
|
|||
$types = \Drupal::configFactory()->listAll('node.type.');
|
||||
foreach ($types as $config_name) {
|
||||
$type = \Drupal::config($config_name)->get('type');
|
||||
\Drupal::config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save();
|
||||
if (\Drupal::moduleHandler()->moduleExists('language')) {
|
||||
\Drupal::config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete remaining general module variables.
|
||||
|
|
|
@ -87,7 +87,7 @@ class SearchPageRepository implements SearchPageRepositoryInterface {
|
|||
}
|
||||
|
||||
// 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.
|
||||
// Write directly to active storage to avoid early instantiation of
|
||||
// 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.
|
||||
$class = get_class($this);
|
||||
|
|
|
@ -1526,7 +1526,9 @@ abstract class TestBase {
|
|||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.manager'),
|
||||
$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.
|
||||
|
|
|
@ -182,7 +182,7 @@ function simpletest_uninstall() {
|
|||
// Do not clean the environment in case the Simpletest module is uninstalled
|
||||
// in a (recursive) test for itself, since simpletest_clean_environment()
|
||||
// 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();
|
||||
}
|
||||
// Delete verbose test output and any other testing framework files.
|
||||
|
|
|
@ -4,6 +4,13 @@ bundle: article
|
|||
mode: default
|
||||
status: true
|
||||
content:
|
||||
field_image:
|
||||
label: hidden
|
||||
type: image
|
||||
settings:
|
||||
image_style: large
|
||||
image_link: ''
|
||||
weight: -1
|
||||
body:
|
||||
label: hidden
|
||||
type: text_default
|
||||
|
@ -14,13 +21,6 @@ content:
|
|||
weight: 10
|
||||
label: above
|
||||
settings: { }
|
||||
field_image:
|
||||
label: hidden
|
||||
type: image
|
||||
settings:
|
||||
image_style: large
|
||||
image_link: ''
|
||||
weight: -1
|
||||
dependencies:
|
||||
entity:
|
||||
- field.instance.node.article.body
|
||||
|
|
|
@ -4,6 +4,13 @@ bundle: article
|
|||
mode: teaser
|
||||
status: true
|
||||
content:
|
||||
field_image:
|
||||
label: hidden
|
||||
type: image
|
||||
settings:
|
||||
image_style: medium
|
||||
image_link: content
|
||||
weight: -1
|
||||
body:
|
||||
label: hidden
|
||||
type: text_summary_or_trimmed
|
||||
|
@ -15,13 +22,6 @@ content:
|
|||
weight: 10
|
||||
label: above
|
||||
settings: { }
|
||||
field_image:
|
||||
label: hidden
|
||||
type: image
|
||||
settings:
|
||||
image_style: medium
|
||||
image_link: content
|
||||
weight: -1
|
||||
dependencies:
|
||||
entity:
|
||||
- entity.view_mode.node.teaser
|
||||
|
|
Loading…
Reference in New Issue