diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 5e11697..2f6e8fe 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -260,6 +260,51 @@ protected function process($op, $name) { } /** + * Checks that the operation is still valid. + * + * During a configuration import secondary writes and deletes are possible. + * This method checks that the operation is still valid before processing a + * configuration change. + * + * @param string $op + * The change operation. + * @param string $name + * The name of the configuration to process. + * + * @return bool + * TRUE is to continue processing, FALSE otherwise. + */ + protected function checkOp($op, $name) { + $target_exists = $this->storageComparer->getTargetStorage()->exists($name); + switch ($op) { + case 'delete': + if (!$target_exists) { + // The configuration has already been deleted. For example, a field + // is automatically deleted if all the instances are. + $this->setProcessed($op, $name); + return FALSE; + } + break; + + case 'create': + if ($target_exists) { + // Become an update. This is possible since updates are run after + // creates. + $this->storageComparer->swapOp('create', 'update', $name); + return FALSE; + } + break; + + case 'update': + if (!$target_exists) { + // Error? + } + break; + } + return TRUE; + } + + /** * Writes a configuration change from the source to the target storage. * * @param string $op @@ -328,7 +373,6 @@ protected function importInvokeOwner($op, $name) { $this->setProcessed($op, $name); return TRUE; } - return FALSE; } /** diff --git a/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php index ac89537..6170856 100644 --- a/core/lib/Drupal/Core/Config/StorageComparer.php +++ b/core/lib/Drupal/Core/Config/StorageComparer.php @@ -234,4 +234,12 @@ protected function getAndSortConfigData() { $this->sourceNames = $dependency_manager->setData($this->sourceData)->sortAll(); } + /** + * {@inheritdoc} + */ + public function swapOp($from, $to, $name) { + $key = array_search($name, $this->changelist[$from]); + unset($this->changelist[$from][$key]); + $this->addChangeList($to, array($name)); + } } diff --git a/core/lib/Drupal/Core/Config/StorageComparerInterface.php b/core/lib/Drupal/Core/Config/StorageComparerInterface.php index 3c6c666..bc27635 100644 --- a/core/lib/Drupal/Core/Config/StorageComparerInterface.php +++ b/core/lib/Drupal/Core/Config/StorageComparerInterface.php @@ -80,4 +80,18 @@ public function hasChanges($ops = array('delete', 'create', 'update')); */ public function validateSiteUuid(); + /** + * Moves a configuration name from one change list to another. + * + * @param string $name + * The configuration object name. + * @param string $from + * The current change operation. Either delete, create or update. + * @param string $to + * The change operation to change to. Either delete, create or update. + * + * @return $this + */ + public function swapOp($name, $from, $to); + } diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php index e8eee2e..de4a109 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php @@ -75,7 +75,7 @@ class CustomBlockType extends ConfigEntityBase implements CustomBlockTypeInterfa public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - if (!$update) { + if (!$update && !$this->isSyncing()) { entity_invoke_bundle_hook('create', 'custom_block', $this->id()); if (!$this->isSyncing()) { custom_block_add_body_field($this->id); diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module index 75d9dfd..88acbcb 100644 --- a/core/modules/entity_reference/entity_reference.module +++ b/core/modules/entity_reference/entity_reference.module @@ -71,6 +71,11 @@ function entity_reference_field_config_update(FieldConfigInterface $field) { return; } + if ($field->isSyncing()) { + // Don't change anything during a configuration sync. + return; + } + if ($field->getSetting('target_type') == $field->original->getSetting('target_type')) { // Target type didn't change. return; diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php index d2388f7..0728ebf 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php @@ -7,6 +7,8 @@ namespace Drupal\field\Tests; +use Drupal\Component\Utility\String; + /** * Tests deleting fields and instances as part of config import. */ @@ -31,13 +33,21 @@ public static function getInfo() { * Tests deleting fields and instances as part of config import. */ public function testImportDelete() { + // At this point there are 5 field configuration objects in the active + // storage. + // - field.field.entity_test.field_test_import + // - field.field.entity_test.field_test_import_2 + // - field.instance.entity_test.entity_test.field_test_import + // - field.instance.entity_test.entity_test.field_test_import_2 + // - field.instance.entity_test.test_bundle.field_test_import_2 + $field_name = 'field_test_import'; $field_id = "entity_test.$field_name"; $field_name_2 = 'field_test_import_2'; $field_id_2 = "entity_test.$field_name_2"; - $instance_id = "entity_test.test_bundle.$field_name"; - $instance_id_2a = "entity_test.test_bundle.$field_name_2"; - $instance_id_2b = "entity_test.test_bundle_2.$field_name_2"; + $instance_id = "entity_test.entity_test.$field_name"; + $instance_id_2a = "entity_test.entity_test.$field_name_2"; + $instance_id_2b = "entity_test.test_bundle.$field_name_2"; $field_config_name = "field.field.$field_id"; $field_config_name_2 = "field.field.$field_id_2"; $instance_config_name = "field.instance.$instance_id"; @@ -45,7 +55,7 @@ public function testImportDelete() { $instance_config_name_2b = "field.instance.$instance_id_2b"; // Create a second bundle for the 'Entity test' entity type. - entity_test_create_bundle('test_bundle_2'); + entity_test_create_bundle('test_bundle'); // Import default config. $this->installConfig(array('field_test_config')); @@ -57,11 +67,14 @@ public function testImportDelete() { $active = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); $this->copyConfig($active, $staging); - $staging->delete($field_config_name); - $staging->delete($field_config_name_2); - $staging->delete($instance_config_name); - $staging->delete($instance_config_name_2a); - $staging->delete($instance_config_name_2b); + $this->assertTrue($staging->delete($field_config_name), String::format('Deleted field: !field', array('!field' => $field_config_name))); + $this->assertTrue($staging->delete($field_config_name_2), String::format('Deleted field: !field', array('!field' => $field_config_name_2))); + $this->assertTrue($staging->delete($instance_config_name), String::format('Deleted field instance: !field_instance', array('!field_instance' => $instance_config_name))); + $this->assertTrue($staging->delete($instance_config_name_2a), String::format('Deleted field instance: !field_instance', array('!field_instance' => $instance_config_name_2a))); + $this->assertTrue($staging->delete($instance_config_name_2b), String::format('Deleted field instance: !field_instance', array('!field_instance' => $instance_config_name_2b))); + + $deletes = $this->configImporter()->getUnprocessed('delete'); + $this->assertEqual(count($deletes), 5, 'Importing configuration will delete 3 field instances and 2 fields.'); // Import the content of the staging directory. $this->configImporter()->import(); diff --git a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php index 4b36306..e1ea563 100644 --- a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php +++ b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php @@ -238,7 +238,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // Clear the filter cache whenever a text format is updated. Cache::deleteTags(array('filter_format' => $this->id())); } - else { + elseif (!$this->isSyncing()) { // Default configuration of modules and installation profiles is allowed // to specify a list of user roles to grant access to for the new format; // apply the defined user role permissions when a new format is inserted diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 472cad4..28a92e3 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -387,6 +387,7 @@ function image_filter_keyword($value, $current_pixels, $new_pixels) { */ function image_entity_presave(EntityInterface $entity) { $field = FALSE; + $entity_type_id = $entity->getEntityTypeId(); if ($entity_type_id == 'field_instance_config') { $field = $entity->getField(); @@ -399,6 +400,10 @@ function image_entity_presave(EntityInterface $entity) { return; } + if ($field->isSyncing()) { + return; + } + $fid = $entity->settings['default_image']['fid']; if ($fid) { $original_fid = isset($entity->original) ? $entity->original->settings['default_image']['fid'] : NULL; diff --git a/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php index 7bd7537..6ac64a6 100644 --- a/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php +++ b/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php @@ -105,7 +105,9 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // The old image style name needs flushing after a rename. $this->original->flush(); // Update field instance settings if necessary. - static::replaceImageStyle($this); + if (!$this->isSyncing()) { + static::replaceImageStyle($this); + } } else { // Flush image style when updating without changing the name. @@ -126,7 +128,7 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti // Check whether field instance settings need to be updated. // In case no replacement style was specified, all image fields that are // using the deleted style are left in a broken state. - if ($new_id = $style->getReplacementID()) { + if (!$style->isSyncing() && $new_id = $style->getReplacementID()) { // The deleted ID is still set as originalID. $style->setName($new_id); static::replaceImageStyle($style); diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index e89feb8..c6e4e36 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -145,6 +145,11 @@ function menu_menu_insert(Menu $menu) { if (\Drupal::moduleHandler()->moduleExists('block')) { \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); } + + if ($menu->isSyncing()) { + return; + } + // Make sure the menu is present in the active menus variable so that its // items may appear in the menu active trail. // See menu_set_active_menu_names(). @@ -334,6 +339,9 @@ function menu_node_update(EntityInterface $node) { * Implements hook_node_type_insert(). */ function menu_node_type_insert(NodeTypeInterface $type) { + if ($type->isSyncing()) { + return; + } \Drupal::config('menu.entity.node.' . $type->id()) ->set('available_menus', array('main')) ->set('parent', 'main:0') @@ -344,6 +352,9 @@ function menu_node_type_insert(NodeTypeInterface $type) { * Implements hook_node_type_delete(). */ function menu_node_type_delete(NodeTypeInterface $type) { + if ($type->isSyncing()) { + return; + } \Drupal::config('menu.entity.node.' . $type->id())->delete(); } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php index f5a7ba7..ec46b6b 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php @@ -102,7 +102,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { if (!$update) { entity_invoke_bundle_hook('create', 'taxonomy_term', $this->id()); } - elseif ($this->getOriginalId() != $this->id()) { + elseif ($this->getOriginalId() != $this->id() && !$this->isSyncing()) { // Reflect machine name changes in the definitions of existing 'taxonomy' // fields. $field_ids = array(); @@ -153,6 +153,13 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie public static function postDelete(EntityStorageInterface $storage, array $entities) { parent::postDelete($storage, $entities); + // Reset caches. + $storage->resetCache(array_keys($entities)); + + if (reset($entities)->isSyncing()) { + return; + } + $vocabularies = array(); foreach ($entities as $vocabulary) { $vocabularies[$vocabulary->id()] = $vocabulary->id();