From 49d60f113dbf1adaba8519ffd0624d8d7581882f Mon Sep 17 00:00:00 2001 From: Lee Rowlands Date: Wed, 30 Oct 2019 15:36:20 +1000 Subject: [PATCH] Issue #2915036 by ndf, Dane Powell, tim.plunkett, Lendude, jofitz, koppie, larowlan, fubarhouse, sime, alex.skrypnyk, Joseph Zhao: Display mode configurations don't get updated with new fields --- core/modules/field/field.module | 17 +++ .../field/src/EntityDisplayRebuilder.php | 98 +++++++++++++ .../FieldImportDeleteUninstallUiTest.php | 3 +- .../field/tests/src/Functional/FormTest.php | 4 + .../src/Kernel/DisplayModeUpdateTest.php | 138 ++++++++++++++++++ .../modules/entity_test/entity_test.module | 8 +- 6 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 core/modules/field/src/EntityDisplayRebuilder.php create mode 100644 core/modules/field/tests/src/Kernel/DisplayModeUpdateTest.php diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 6e47f6d6cdd..e7c48b8fa64 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -11,6 +11,7 @@ use Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface; use Drupal\field\ConfigImporterFieldPurger; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\field\EntityDisplayRebuilder; use Drupal\field\FieldConfigInterface; use Drupal\field\FieldStorageConfigInterface; use Drupal\Core\Form\FormStateInterface; @@ -335,6 +336,22 @@ function field_form_config_admin_import_form_alter(&$form, FormStateInterface $f } } +/** + * Implements hook_ENTITY_TYPE_insert() for 'field_config'. + */ +function field_field_config_insert(FieldConfigInterface $field) { + if ($field->isSyncing()) { + // Don't change anything during a configuration sync. + return; + } + + // 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. + \Drupal::classResolver(EntityDisplayRebuilder::class) + ->rebuildEntityTypeDisplays($field->getTargetEntityTypeId(), $field->getTargetBundle()); +} + /** * Implements hook_ENTITY_TYPE_update() for 'field_storage_config'. * diff --git a/core/modules/field/src/EntityDisplayRebuilder.php b/core/modules/field/src/EntityDisplayRebuilder.php new file mode 100644 index 00000000000..74c39737441 --- /dev/null +++ b/core/modules/field/src/EntityDisplayRebuilder.php @@ -0,0 +1,98 @@ +entityTypeManager = $entity_type_manager; + $this->entityDisplayRepository = $entity_display_repository; + $this->entityTypeBundleInfo = $entity_type_bundle_info; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('entity_display.repository'), + $container->get('entity_type.bundle.info') + ); + } + + /** + * Rebuild displays for single Entity Type. + * + * @param string $entity_type_id + * The entity type machine name. + * + * @param string $bundle + * The bundle we need to rebuild. + */ + public function rebuildEntityTypeDisplays($entity_type_id, $bundle) { + // Get the displays. + $view_modes = $this->entityDisplayRepository->getViewModeOptions($entity_type_id); + $form_modes = $this->entityDisplayRepository->getFormModeOptions($entity_type_id); + + // Save view mode displays. + $view_mode_ids = array_map(function ($view_mode) use ($entity_type_id, $bundle) { + return "$entity_type_id.$bundle.$view_mode"; + }, array_keys($view_modes)); + foreach ($this->entityTypeManager->getStorage('entity_view_display')->loadMultiple($view_mode_ids) as $display) { + $display->save(); + } + // Save form mode displays. + $form_mode_ids = array_map(function ($form_mode) use ($entity_type_id, $bundle) { + return "$entity_type_id.$bundle.$form_mode"; + }, array_keys($form_modes)); + foreach ($this->entityTypeManager->getStorage('entity_form_display')->loadMultiple($form_mode_ids) as $display) { + $display->save(); + } + } + +} diff --git a/core/modules/field/tests/src/Functional/FieldImportDeleteUninstallUiTest.php b/core/modules/field/tests/src/Functional/FieldImportDeleteUninstallUiTest.php index eece976f573..25a798b8b9b 100644 --- a/core/modules/field/tests/src/Functional/FieldImportDeleteUninstallUiTest.php +++ b/core/modules/field/tests/src/Functional/FieldImportDeleteUninstallUiTest.php @@ -86,9 +86,10 @@ class FieldImportDeleteUninstallUiTest extends FieldTestBase { unset($core_extension['module']['telephone']); $sync->write('core.extension', $core_extension); - // Stage the field deletion + // Stage the field deletion including its dependencies. $sync->delete('field.storage.entity_test.field_tel'); $sync->delete('field.field.entity_test.entity_test.field_tel'); + $sync->delete('core.entity_form_display.entity_test.entity_test.default'); $this->drupalGet('admin/config/development/configuration'); // Test that the message for one field being purged during a configuration // synchronization is correct. diff --git a/core/modules/field/tests/src/Functional/FormTest.php b/core/modules/field/tests/src/Functional/FormTest.php index 91da759d676..671f01ac50a 100644 --- a/core/modules/field/tests/src/Functional/FormTest.php +++ b/core/modules/field/tests/src/Functional/FormTest.php @@ -713,6 +713,10 @@ class FormTest extends FieldTestBase { ]) ->save(); + // We need to rebuild hook information after setting the component through + // the API. + $this->rebuildAll(); + $this->drupalGet('entity_test/add'); $this->assertUniqueText("From $hook(): prefix on $field_name parent element."); if ($widget === 'test_field_widget_multiple_single_value') { 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 00000000000..f1294a90f79 --- /dev/null +++ b/core/modules/field/tests/src/Kernel/DisplayModeUpdateTest.php @@ -0,0 +1,138 @@ + 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + ]); + $default_view_display->save(); + $this->defaultViewDisplayName = $default_view_display->getConfigDependencyName(); + + // Create 'default' form-display. + $default_form_display = EntityFormDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + ]); + $default_form_display->save(); + $this->defaultFormDisplayName = $default_form_display->getConfigDependencyName(); + + // Create a view-mode 'foobar', create view-display that uses it. + EntityViewMode::create([ + 'id' => 'entity_test.foobar', + 'targetEntityType' => 'entity_test', + 'status' => TRUE, + 'enabled' => TRUE, + ])->save(); + $foobar_view_display = EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'foobar', + 'status' => TRUE, + ]); + $foobar_view_display->save(); + $this->foobarViewDisplayName = $foobar_view_display->getConfigDependencyName(); + + // Create a new form-mode 'foobar', create form-display that uses it. + EntityFormMode::create([ + 'id' => 'entity_test.foobar', + 'targetEntityType' => 'entity_test', + 'status' => TRUE, + 'enabled' => TRUE, + ])->save(); + $foobar_form_display = EntityFormDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'foobar', + 'status' => TRUE, + ]); + $foobar_form_display->save(); + $this->foobarFormDisplayName = $foobar_form_display->getConfigDependencyName(); + } + + /** + * Ensure display modes are updated when fields are created. + */ + public function testDisplayModeUpdateAfterFieldCreation() { + + // Sanity test: field has not been created yet, so should not exist in display. + $this->assertArrayNotHasKey('field_test', \Drupal::config($this->defaultViewDisplayName)->get('hidden')); + $this->assertArrayNotHasKey('field_test', \Drupal::config($this->defaultFormDisplayName)->get('hidden')); + $this->assertArrayNotHasKey('field_test', \Drupal::config($this->foobarViewDisplayName)->get('hidden')); + $this->assertArrayNotHasKey('field_test', \Drupal::config($this->foobarFormDisplayName)->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($this->defaultViewDisplayName)->get('hidden')); + $this->assertArrayHasKey('field_test', \Drupal::config($this->defaultFormDisplayName)->get('hidden')); + $this->assertArrayHasKey('field_test', \Drupal::config($this->foobarViewDisplayName)->get('hidden')); + $this->assertArrayHasKey('field_test', \Drupal::config($this->foobarFormDisplayName)->get('hidden')); + + } + +} diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index e9b5e76890f..7b14af03ab2 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -282,11 +282,9 @@ function entity_test_entity_form_mode_info_alter(&$form_modes) { $entity_info = \Drupal::entityTypeManager()->getDefinitions(); foreach ($entity_info as $entity_type => $info) { if ($entity_info[$entity_type]->getProvider() == 'entity_test') { - $form_modes[$entity_type] = [ - 'compact' => [ - 'label' => t('Compact version'), - 'status' => TRUE, - ], + $form_modes[$entity_type]['compact'] = [ + 'label' => t('Compact version'), + 'status' => TRUE, ]; } }