diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 14340b8bf0..c12aa19b0d 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -335,6 +335,26 @@ function field_form_config_admin_import_form_alter(&$form, FormStateInterface $f } /** + * Implements hook_ENTITY_TYPE_insert() for 'field_config'. + * + * Allow other view modes to update their configuration for the new field. + * Otherwise, configuration for view modes won't get updated until the mode + * is used for the first time, creating noise in config diffs. + * + * @see https://www.drupal.org/node/2915036 + */ +function field_field_config_insert(FieldConfigInterface $field) { + if ($field->isSyncing()) { + // Don't change anything during a configuration sync. + return; + } + + /** @var \Drupal\field\EntityDisplayRebuilder $display_rebuilder */ + $display_rebuilder = \Drupal::service('field.entity_display_rebuilder'); + $display_rebuilder->rebuildEntityTypeDisplays($field->getTargetEntityTypeId()); +} + +/** * Implements hook_ENTITY_TYPE_update() for 'field_storage_config'. * * Reset the field handler settings, when the storage target_type is changed on @@ -408,3 +428,12 @@ function field_field_config_presave(FieldConfigInterface $field) { ]); } } + +/** + * Implements hook_modules_uninstalled(). + */ +function field_modules_uninstalled($modules) { + /** @var \Drupal\field\EntityDisplayRebuilder $display_rebuilder */ + $display_rebuilder = \Drupal::service('field.entity_display_rebuilder'); + $display_rebuilder->rebuildAllEntityTypeDisplays(TRUE); +} diff --git a/core/modules/field/field.services.yml b/core/modules/field/field.services.yml index 321cf916cf..3ca8cfabe3 100644 --- a/core/modules/field/field.services.yml +++ b/core/modules/field/field.services.yml @@ -5,3 +5,6 @@ services: - { name: module_install.uninstall_validator } arguments: ['@entity_type.manager', '@string_translation', '@plugin.manager.field.field_type'] lazy: true + field.entity_display_rebuilder: + class: Drupal\field\EntityDisplayRebuilder + arguments: ['@entity_type.manager', '@entity_display.repository', '@entity_type.bundle.info'] diff --git a/core/modules/field/src/EntityDisplayRebuilder.php b/core/modules/field/src/EntityDisplayRebuilder.php new file mode 100644 index 0000000000..b7fe23d5b1 --- /dev/null +++ b/core/modules/field/src/EntityDisplayRebuilder.php @@ -0,0 +1,98 @@ +entityTypeManager = $entity_type_manager; + $this->entityDisplayRepository = $entity_display_repository; + } + + /** + * Rebuild displays for all Entity Types. + * + * @param bool $include_default_displays + * Add 'default' displays. Defaults to FALSE. + */ + public function rebuildAllEntityTypeDisplays($include_default_displays = FALSE) { + + // Loop through all entity types. + $entity_types = $this->entityTypeManager->getDefinitions(); + foreach ($entity_types as $entity_type_id => $entity_type_definition) { + $this->rebuildEntityTypeDisplays($entity_type_id, $include_default_displays); + } + + } + + /** + * Rebuild displays for single Entity Type. + * + * @param string $entity_type_id + * The entity type machine name. + * @param bool $include_default_displays + * Add 'default' displays. Defaults to FALSE. + */ + public function rebuildEntityTypeDisplays($entity_type_id, $include_default_displays = FALSE) { + // Loop through all bundles. + $entity_bundles = entity_get_bundles($entity_type_id); + foreach ($entity_bundles as $bundle => $bundle_definition) { + + // Get the displays. + $view_modes = $this->entityDisplayRepository->getViewModes($entity_type_id); + $form_modes = $this->entityDisplayRepository->getFormModes($entity_type_id); + + // Explicitly add default displays. + if ($include_default_displays) { + $view_modes['default'] = []; + $form_modes['default'] = []; + } + + // Update view modes displays. + foreach ($view_modes as $view_mode_id => $view_mode) { + if ($display = EntityViewDisplay::load($entity_type_id . '.' . $bundle . '.' . $view_mode_id)) { + $display->save(); + } + } + // Update form modes displays. + foreach ($form_modes as $form_mode_id => $form_mode) { + if ($display = EntityFormDisplay::load($entity_type_id . '.' . $bundle . '.' . $form_mode_id)) { + $display->save(); + } + } + + } + + } + +} diff --git a/core/modules/field/tests/src/Kernel/DisplayModeUpdateTest.php b/core/modules/field/tests/src/Kernel/DisplayModeUpdateTest.php new file mode 100644 index 0000000000..33b50ceab3 --- /dev/null +++ b/core/modules/field/tests/src/Kernel/DisplayModeUpdateTest.php @@ -0,0 +1,93 @@ + 'entity_test.default', + 'targetEntityType' => 'entity_test', + 'status' => TRUE, + 'enabled' => TRUE, + ])->save(); + + $display = EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + ]); + $display->save(); + + // Create foobar display. + EntityViewMode::create([ + 'id' => 'entity_test.foobar', + 'targetEntityType' => 'entity_test', + 'status' => TRUE, + 'enabled' => TRUE, + ])->save(); + + $display = EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'foobar', + 'status' => TRUE, + ]); + $display->save(); + + // Sanity test: field has not been created yet, so should not exist in display. + $this->assertArrayNotHasKey('field_test', \Drupal::config($default_config_name)->get('hidden')); + $this->assertArrayNotHasKey('field_test', \Drupal::config($foobar_config_name)->get('hidden')); + + $field_storage = FieldStorageConfig::create([ + 'field_name' => 'field_test', + 'entity_type' => 'entity_test', + 'type' => 'test_field', + 'cardinality' => 1, + ]); + $field_storage->save(); + + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'entity_test', + ])->save(); + + // Ensure field is added to display modes. + $this->assertArrayHasKey('field_test', \Drupal::config($default_config_name)->get('hidden')); + $this->assertArrayHasKey('field_test', \Drupal::config($foobar_config_name)->get('hidden')); + + $entityFieldManager = \Drupal::service('entity_field.manager'); + + $fields = $entityFieldManager->getFieldDefinitions('entity_test', 'entity_test'); + + if (isset($fields['field_test'])) { + $fields['field_test']->delete(); + } + + // Ensure field is removed from display modes. + $this->assertArrayNotHasKey('field_test', \Drupal::config($default_config_name)->get('hidden')); + $this->assertArrayNotHasKey('field_test', \Drupal::config($foobar_config_name)->get('hidden')); + + } + +} diff --git a/core/modules/language/tests/src/Kernel/LanguageDisplayConfigurationAfterUninstallTest.php b/core/modules/language/tests/src/Kernel/LanguageDisplayConfigurationAfterUninstallTest.php new file mode 100644 index 0000000000..b253bbd2e0 --- /dev/null +++ b/core/modules/language/tests/src/Kernel/LanguageDisplayConfigurationAfterUninstallTest.php @@ -0,0 +1,114 @@ +installSchema('user', ['users_data']); + + // Create 'default' view-display. + $display = EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + ]); + $display->save(); + + // Create 'default' form-display. + $display = EntityFormDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + ]); + $display->save(); + + // Create a view-mode 'manual', create view-display that uses it. + EntityViewMode::create([ + 'id' => 'entity_test.manual', + 'targetEntityType' => 'entity_test', + 'status' => TRUE, + 'enabled' => TRUE, + ])->save(); + $display = EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'manual', + 'status' => TRUE, + ]); + $display->save(); + + // Create a new form-mode 'manual', create form-display that uses it. + EntityFormMode::create([ + 'id' => 'entity_test.manual', + 'targetEntityType' => 'entity_test', + 'status' => TRUE, + 'enabled' => TRUE, + ])->save(); + $display = EntityFormDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'manual', + 'status' => TRUE, + ]); + $display->save(); + + } + + /** + * Tests display configuration after uninstalling Language module. + */ + public function testConfigAfterUninstall() { + $default_view_display = 'core.entity_view_display.entity_test.entity_test.default'; + $default_form_display = 'core.entity_view_display.entity_test.entity_test.default'; + $manual_view_display = 'core.entity_view_display.entity_test.entity_test.manual'; + $manual_form_display = 'core.entity_view_display.entity_test.entity_test.manual'; + + // 1. Validate that "langcode" key is there. + $this->assertArrayHasKey('langcode', \Drupal::config($default_view_display)->get('hidden')); + $this->assertArrayHasKey('langcode', \Drupal::config($default_form_display)->get('hidden')); + $this->assertArrayHasKey('langcode', \Drupal::config($manual_view_display)->get('hidden')); + $this->assertArrayHasKey('langcode', \Drupal::config($manual_form_display)->get('hidden')); + + // 2. Uninstall language module. + \Drupal::service('module_installer')->uninstall(['language']); + + // 3. Test that "langcode" key is removed from field config. + $this->assertArrayNotHasKey('langcode', \Drupal::config($default_view_display)->get('hidden')); + $this->assertArrayNotHasKey('langcode', \Drupal::config($default_form_display)->get('hidden')); + $this->assertArrayNotHasKey('langcode', \Drupal::config($manual_view_display)->get('hidden')); + $this->assertArrayNotHasKey('langcode', \Drupal::config($manual_form_display)->get('hidden')); + + } + +}