diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php index 768b2e5..1b169de 100644 --- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php +++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php @@ -443,12 +443,50 @@ public function onDependencyRemoval(array $dependencies) { $this->setComponent($name, $component); $changed = TRUE; } + // If there are unresolved deleted dependencies left, disable this + // component to avoid the removal of the entire display entity. + if (static::isUnresolved($renderer->calculateDependencies(), $dependencies)) { + $this->removeComponent($name); + unset($this->hidden[$name]); + $changed = TRUE; + } } } return $changed; } /** + * Checks if the plugin has unresolved dependencies against the display entity + * removed dependencies. + * + * Note: + * 1. The two arguments have not the same structure. + * 2. $removed_dependencies has already sane defaults. All the types of events + * are filled in, even with empty arrays. + * + * @param array[] $plugin_dependencies + * A list of dependencies having the same structure as the return value of + * ConfigEntityInterface::calculateDependencies(). + * @param array[] $removed_dependencies + * A list of dependencies having the same structure as the input argument of + * ConfigEntityInterface::onDependencyRemoval(). + * + * @return bool + * TRUE if there are unresolved dependencies in $dependencies1. + * + * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() + * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval() + */ + protected static function isUnresolved(array $plugin_dependencies, array $removed_dependencies) { + foreach ($plugin_dependencies as $type => $dependencies) { + if (array_diff($dependencies, array_keys($removed_dependencies[$type]))) { + return TRUE; + } + } + return FALSE; + } + + /** * {@inheritdoc} */ public function __sleep() { diff --git a/core/lib/Drupal/Core/Field/PluginSettingsInterface.php b/core/lib/Drupal/Core/Field/PluginSettingsInterface.php index 0c31481..cc30eed 100644 --- a/core/lib/Drupal/Core/Field/PluginSettingsInterface.php +++ b/core/lib/Drupal/Core/Field/PluginSettingsInterface.php @@ -111,7 +111,7 @@ public function setThirdPartySetting($module, $key, $value); * * @param array $dependencies * An array of dependencies that will be deleted keyed by dependency type. - * Dependency types are, for example, entity, module and theme. + * Dependency types are 'config', 'content', 'module' and 'theme'. * * @return bool * TRUE if the plugin configuration has changed, FALSE if not. diff --git a/core/modules/field/tests/modules/field_test/config/schema/field_test.schema.yml b/core/modules/field/tests/modules/field_test/config/schema/field_test.schema.yml index 4f1c711..04dc5fe 100644 --- a/core/modules/field/tests/modules/field_test/config/schema/field_test.schema.yml +++ b/core/modules/field/tests/modules/field_test/config/schema/field_test.schema.yml @@ -43,6 +43,9 @@ field.widget.settings.test_field_widget: role: type: string label: 'A referenced role' + role2: + type: string + label: 'A 2nd referenced role' field.widget.settings.test_field_widget_multiple: type: mapping diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldWidget/TestFieldWidget.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldWidget/TestFieldWidget.php index d68954d..f29b097 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldWidget/TestFieldWidget.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldWidget/TestFieldWidget.php @@ -35,6 +35,7 @@ public static function defaultSettings() { return array( 'test_widget_setting' => 'dummy test string', 'role' => 'anonymous', + 'role2' => 'anonymous', ) + parent::defaultSettings(); } @@ -84,10 +85,14 @@ public function errorElement(array $element, ConstraintViolationInterface $viola */ public function calculateDependencies() { $dependencies = parent::calculateDependencies(); - if (!empty($role_id = $this->getSetting('role'))) { - // Create a dependency on the role config entity referenced in settings. - $dependencies['config'][] = "user.role.$role_id"; + + foreach (['role', 'role2'] as $setting) { + if (!empty($role_id = $this->getSetting($setting))) { + // Create a dependency on the role config entity referenced in settings. + $dependencies['config'][] = "user.role.$role_id"; + } } + return $dependencies; } @@ -97,6 +102,12 @@ public function calculateDependencies() { public function onDependencyRemoval(array $dependencies) { $changed = parent::onDependencyRemoval($dependencies); + // Only the setting 'role' is resolved here. When the dependency related to + // this setting is removed, is expected that the widget component will be + // update accordingly in the display entity. The 'role2' setting is + // deliberately left out. When the dependency corresponding to this setting + // is removed, is expected that the widget component will be disabled from + // the display entity. if (!empty($role_id = $this->getSetting('role'))) { if (!empty($dependencies['config']["user.role.$role_id"])) { $this->setSetting('role', 'anonymous'); diff --git a/core/modules/field_ui/src/Tests/EntityDisplayTest.php b/core/modules/field_ui/src/Tests/EntityDisplayTest.php index 5dded95..61d02aa 100644 --- a/core/modules/field_ui/src/Tests/EntityDisplayTest.php +++ b/core/modules/field_ui/src/Tests/EntityDisplayTest.php @@ -17,6 +17,7 @@ use Drupal\node\Entity\NodeType; use Drupal\simpletest\KernelTestBase; use Drupal\user\Entity\Role; +use Drupal\user\RoleInterface; /** * Tests the entity display configuration entities. @@ -445,12 +446,16 @@ public function testOnDependencyRemoval() { */ public function testComponentDependencies() { $this->installEntitySchema('user'); - // Create an arbitrary user role. - $role = Role::create([ - 'id' => Unicode::strtolower($this->randomMachineName()), - 'label' => $this->randomString(), - ]); - $role->save(); + /** @var \Drupal\user\RoleInterface[] $roles */ + $roles = []; + // Create two arbitrary user roles. + for ($i = 0; $i < 2; $i++) { + $roles[$i] = Role::create([ + 'id' => Unicode::strtolower($this->randomMachineName()), + 'label' => $this->randomString(), + ]); + $roles[$i]->save(); + } // Create a field of type 'test_field' attached to 'entity_test'. $field_name = Unicode::strtolower($this->randomMachineName()); @@ -474,34 +479,45 @@ public function testComponentDependencies() { ]); $form_display->save(); - $dependency = 'user.role.' . $role->id(); + $dependencies = ['user.role.' . $roles[0]->id(), 'user.role.' . $roles[1]->id()]; - // The config object should not depend on user role $role. - $this->assertFalse($this->isConfigDependency($dependency, $form_display)); + // The config object should not depend on none of the two $roles. + $this->assertFalse($this->isConfigDependency($dependencies[0], $form_display)); + $this->assertFalse($this->isConfigDependency($dependencies[1], $form_display)); // Add a widget of type 'test_field_widget'. $component = [ 'type' => 'test_field_widget', 'settings' => [ 'test_widget_setting' => $this->randomString(), - 'role' => $role->id(), + 'role' => $roles[0]->id(), + 'role2' => $roles[1]->id(), ], ]; $form_display->setComponent($field_name, $component); $form_display->save(); - // Now, the form display should depend on user role $role. - $this->assertTrue($this->isConfigDependency($dependency, $form_display)); + // Now, the form display should depend on both user roles $roles. + $this->assertTrue($this->isConfigDependency($dependencies[0], $form_display)); + $this->assertTrue($this->isConfigDependency($dependencies[1], $form_display)); - // Delete the dependency user role entity. - $role->delete(); + // Delete the first user role entity. + $roles[0]->delete(); // Reload the form display. $form_display = EntityFormDisplay::load($form_display->id()); - // The form display should not depend on $role anymore. - $this->assertFalse($this->isConfigDependency($dependency, $form_display)); + // The form display should not depend on $role[0] anymore. + $this->assertFalse($this->isConfigDependency($dependencies[0], $form_display)); // The form display should depend on 'anonymous' user role. $this->assertTrue($this->isConfigDependency('user.role.anonymous', $form_display)); + + // Delete the 2nd user role entity. + $roles[1]->delete(); + + // The display exists. + $this->assertNotNull($form_display = EntityFormDisplay::load($form_display->id())); + // The component has been removed. + $this->assertNull($form_display->getComponent($field_name)); } /**