diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBundleBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBundleBase.php index cb64a004b3..6926c58e3d 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBundleBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBundleBase.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Config\Entity; use Drupal\Core\Config\ConfigNameException; +use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityStorageInterface; /** @@ -92,6 +93,36 @@ public function preSave(EntityStorageInterface $storage) { } } + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageInterface $storage, array $entities) { + // Prevent the deletion of bundle entities if there are existing content + // entities of this bundle. + $entity_type_manager = \Drupal::entityTypeManager(); + foreach ($entities as $entity) { + if ($bundle_of = $entity->getEntityType()->getBundleOf()) { + $bundle_of_entity_type = $entity_type_manager->getDefinition($bundle_of); + $storage = $entity_type_manager->getStorage($bundle_of); + + $has_data = (bool) $storage->getQuery() + ->condition($bundle_of_entity_type->getKey('bundle'), $entity->id(), '=') + ->accessCheck(FALSE) + ->range(0, 1) + ->execute(); + + if ($has_data) { + // Use the 409 (Conflict) status code to indicate that the deletion + // could not be completed due to a conflict with the current state of + // the target resource. + throw new EntityStorageException("The '{$entity->label()}' bundle contains data and can not be deleted.", 409); + } + } + } + + parent::preDelete($storage, $entities); + } + /** * Returns view or form displays for this bundle. * diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php b/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php index 4f9aff5d46..2e6f2c7707 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php @@ -70,12 +70,23 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $form_state->setRedirectUrl($untranslated_entity->toUrl('canonical')); } else { - $entity->delete(); + try { + $entity->delete(); + $this->messenger()->addStatus($message); + $this->logDeletionMessage(); + } + catch (EntityStorageException $e) { + // The 409 (Conflict) status code indicates that the deletion could not be + // completed due to a conflict with the current state of the entity. + if ($e->getCode() == 409) { + $this->messenger()->addError($e->getMessage()); + } + else { + throw $e; + } + } $form_state->setRedirectUrl($this->getRedirectUrl()); } - - $this->messenger()->addStatus($message); - $this->logDeletionMessage(); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php b/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php index 7832a2590e..a4afc938e4 100644 --- a/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php +++ b/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php @@ -119,10 +119,22 @@ protected function logDeletionMessage() { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $this->getEntity()->delete(); - $this->messenger()->addStatus($this->getDeletionMessage()); + try { + $this->getEntity()->delete(); + $this->messenger()->addStatus($this->getDeletionMessage()); + $this->logDeletionMessage(); + } + catch (EntityStorageException $e) { + // The 409 (Conflict) status code indicates that the deletion could not be + // completed due to a conflict with the current state of the entity. + if ($e->getCode() == 409) { + $this->messenger()->addStatus($e->getMessage()); + } + else { + throw $e; + } + } $form_state->setRedirectUrl($this->getCancelUrl()); - $this->logDeletionMessage(); } } diff --git a/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php b/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php index 2ff504cef1..4bdab6fcdd 100644 --- a/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php +++ b/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Entity\Form; +use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\BaseFormIdInterface; use Drupal\Core\Form\ConfirmFormBase; @@ -256,12 +257,28 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } if ($delete_entities) { - $storage->delete($delete_entities); - foreach ($delete_entities as $entity) { - $this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label has been deleted.', [ - '@entity-type' => $entity->getEntityType()->getSingularLabel(), - '%label' => $entity->label(), - ]); + try { + $storage->delete($delete_entities); + foreach ($delete_entities as $entity) { + $this->logger($entity->getEntityType()->getProvider()) + ->notice('The @entity-type %label has been deleted.', [ + '@entity-type' => $entity->getEntityType()->getSingularLabel(), + '%label' => $entity->label(), + ]); + } + } + catch (EntityStorageException $e) { + // The 409 (Conflict) status code indicates that the deletion could not be + // completed due to a conflict with the current state of the entity. + if ($e->getCode() == 409) { + $this->messenger()->addError($this->t('Failed deletion of entity %entity_label of exception: %exception', [ + '%entity_label' => $entity->label(), + '%exception' => $e->getMessage(), + ])); + } + else { + throw $e; + } } } diff --git a/core/modules/contact/tests/src/Functional/ContactStorageTest.php b/core/modules/contact/tests/src/Functional/ContactStorageTest.php index fbd938bb8a..87e5dcd8ea 100644 --- a/core/modules/contact/tests/src/Functional/ContactStorageTest.php +++ b/core/modules/contact/tests/src/Functional/ContactStorageTest.php @@ -73,4 +73,19 @@ public function testContactStorage() { $this->assertEqual($config->get('id'), $id); } + /** + * Deletes all forms. + */ + public function deleteContactForms() { + // This test uses the contact_storage_test module, which overrides the + // default NULL entity storage handler for contact messages, so we need to + // manually delete all contact messages before deleting the contact forms. + $messages = Message::loadMultiple(); + \Drupal::entityTypeManager() + ->getStorage('contact_message') + ->delete($messages); + + parent::deleteContactForms(); + } + } diff --git a/core/modules/shortcut/src/Entity/ShortcutSet.php b/core/modules/shortcut/src/Entity/ShortcutSet.php index fba28ac41e..2702b4f8e4 100644 --- a/core/modules/shortcut/src/Entity/ShortcutSet.php +++ b/core/modules/shortcut/src/Entity/ShortcutSet.php @@ -90,8 +90,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { * {@inheritdoc} */ public static function preDelete(EntityStorageInterface $storage, array $entities) { - parent::preDelete($storage, $entities); - foreach ($entities as $entity) { $storage->deleteAssignedShortcutSets($entity); @@ -104,6 +102,7 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie $entities = $controller->loadMultiple($shortcut_ids); $controller->delete($entities); } + parent::preDelete($storage, $entities); } /** diff --git a/core/modules/taxonomy/src/Entity/Vocabulary.php b/core/modules/taxonomy/src/Entity/Vocabulary.php index 015eb611dd..cfc70b5638 100644 --- a/core/modules/taxonomy/src/Entity/Vocabulary.php +++ b/core/modules/taxonomy/src/Entity/Vocabulary.php @@ -122,12 +122,11 @@ public function getDescription() { * {@inheritdoc} */ public static function preDelete(EntityStorageInterface $storage, array $entities) { - parent::preDelete($storage, $entities); - // Only load terms without a parent, child terms will get deleted too. $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); $terms = $term_storage->loadMultiple($storage->getToplevelTids(array_keys($entities))); $term_storage->delete($terms); + parent::preDelete($storage, $entities); } /**