From 0ded73fd80d4b54b6570a0e7dd612121fd3303aa Mon Sep 17 00:00:00 2001 From: Jakob Perry Date: Fri, 2 Mar 2018 09:52:46 -0800 Subject: [PATCH] 2670730-81-89--8.5.0-rc1 --- core/core.link_relation_types.yml | 3 + core/core.services.yml | 5 + .../Core/Action/Plugin/Action/DeleteAction.php | 99 +++++++ .../Action/Derivative/EntityActionDeriverBase.php | 15 +- .../Derivative/EntityDeleteActionDeriver.php | 40 +++ .../Entity/EntityDeleteMultipleAccessCheck.php | 86 ++++++ .../Drupal/Core/Entity/Form/DeleteMultipleForm.php | 323 +++++++++++++++++++++ .../Core/Entity/Routing/AdminHtmlRouteProvider.php | 10 + .../Entity/Routing/DefaultHtmlRouteProvider.php | 24 ++ .../action/tests/src/Functional/BulkFormTest.php | 2 +- core/modules/comment/comment.routing.yml | 12 +- .../system.action.comment_delete_action.yml | 2 +- .../comment/config/schema/comment.schema.yml | 2 + core/modules/comment/src/Entity/Comment.php | 1 + .../comment/src/Form/CommentAdminOverview.php | 6 +- .../comment/src/Form/ConfirmDeleteMultiple.php | 170 +---------- .../comment/src/Plugin/Action/DeleteComment.php | 90 +----- .../install/system.action.node_delete_action.yml | 2 +- core/modules/node/config/schema/node.schema.yml | 2 + core/modules/node/node.routing.yml | 11 +- core/modules/node/src/Entity/Node.php | 4 +- core/modules/node/src/Form/DeleteMultiple.php | 177 +---------- core/modules/node/src/Plugin/Action/DeleteNode.php | 91 +----- .../tests/src/Functional/Views/BulkFormTest.php | 10 +- core/modules/system/system.post_update.php | 19 ++ .../entity_test/src/Entity/EntityTestMulRevPub.php | 4 +- .../entity_test/src/Entity/EntityTestRev.php | 4 +- .../Update/UpdateActionsWithEntityPluginsTest.php | 29 ++ .../Entity/DeleteMultipleFormTest.php | 157 ++++++++++ .../KernelTests/Core/Action/DeleteActionTest.php | 85 ++++++ .../KernelTests/Core/Action/PublishActionTest.php | 2 +- .../KernelTests/Core/Action/SaveActionTest.php | 2 +- .../Tests/Listeners/DeprecationListenerTrait.php | 2 + 33 files changed, 982 insertions(+), 509 deletions(-) create mode 100644 core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php create mode 100644 core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php create mode 100644 core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php create mode 100644 core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php create mode 100644 core/tests/Drupal/FunctionalTests/Entity/DeleteMultipleFormTest.php create mode 100644 core/tests/Drupal/KernelTests/Core/Action/DeleteActionTest.php diff --git a/core/core.link_relation_types.yml b/core/core.link_relation_types.yml index d4ffce3..21cdd64 100644 --- a/core/core.link_relation_types.yml +++ b/core/core.link_relation_types.yml @@ -9,6 +9,9 @@ add-page: delete-form: uri: https://drupal.org/link-relations/delete-form description: A form where a resource of this type can be deleted. +delete-multiple-form: + uri: https://drupal.org/link-relations/delete-multiple-form + description: A form where multiple resources of this type can be deleted. revision: uri: https://drupal.org/link-relations/revision description: A particular version of this resource. diff --git a/core/core.services.yml b/core/core.services.yml index 92b2280..43e59a0 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1126,6 +1126,11 @@ services: arguments: ['@entity_type.manager', '@entity_type.bundle.info'] tags: - { name: access_check, applies_to: _entity_create_any_access } + access_check.entity_delete_multiple: + class: Drupal\Core\Entity\EntityDeleteMultipleAccessCheck + arguments: ['@entity_type.manager', '@tempstore.private', '@request_stack'] + tags: + - { name: access_check, applies_to: _entity_delete_multiple_access } access_check.theme: class: Drupal\Core\Theme\ThemeAccessCheck arguments: ['@theme_handler'] diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php new file mode 100644 index 0000000..fcf76a4 --- /dev/null +++ b/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php @@ -0,0 +1,99 @@ +currentUser = $current_user; + $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm'); + + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('tempstore.private'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $entities) { + /** @var \Drupal\Core\Entity\EntityInterface[] $entities */ + $selection = []; + foreach ($entities as $entity) { + $langcode = $entity->language()->getId(); + $selection[$entity->id()][$langcode] = $langcode; + } + $this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection); + } + + /** + * {@inheritdoc} + */ + public function execute($object = NULL) { + $this->executeMultiple([$object]); + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + return $object->access('delete', $account, $return_as_object); + } + +} diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php index 29159c2..40282b5 100644 --- a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php +++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php @@ -6,6 +6,9 @@ use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Core\StringTranslation\TranslationManager; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -13,6 +16,8 @@ */ abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface { + use StringTranslationTrait; + /** * The entity type manager. * @@ -25,16 +30,22 @@ * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. + * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation + * The string translation service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) { $this->entityTypeManager = $entity_type_manager; + $this->stringTranslation = $string_translation; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, $base_plugin_id) { - return new static($container->get('entity_type.manager')); + return new static( + $container->get('entity_type.manager'), + $container->get('string_translation') + ); } /** diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php new file mode 100644 index 0000000..4be4588 --- /dev/null +++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php @@ -0,0 +1,40 @@ +derivatives)) { + $definitions = []; + foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) { + $definition = $base_plugin_definition; + $definition['type'] = $entity_type_id; + $definition['label'] = $this->t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]); + $definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.delete_multiple_form'; + $definitions[$entity_type_id] = $definition; + } + $this->derivatives = $definitions; + } + + return $this->derivatives; + } + + /** + * {@inheritdoc} + */ + protected function isApplicable(EntityTypeInterface $entity_type) { + return $entity_type->hasLinkTemplate('delete-multiple-form'); + } + +} diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php new file mode 100644 index 0000000..470c1b0 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php @@ -0,0 +1,86 @@ +entityTypeManager = $entity_type_manager; + $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm'); + $this->requestStack = $request_stack; + } + + /** + * Checks if the user has delete access for at least one item of the store. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * @param string $entity_type_id + * Entity type ID. + * + * @return \Drupal\Core\Access\AccessResult + * Allowed or forbidden, neutral if tempstore is empty. + */ + public function access(AccountInterface $account, $entity_type_id) { + if (!$this->requestStack->getCurrentRequest()->getSession()) { + return AccessResult::neutral(); + } + $selection = $this->tempStore->get($account->id() . ':' . $entity_type_id); + if (empty($selection) || !is_array($selection)) { + return AccessResult::neutral(); + } + + $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($selection)); + foreach ($entities as $entity) { + // As long as the user has access to delete one entity allow access to the + // delete form. Access will be checked again in + // Drupal\Core\Entity\Form\DeleteMultipleForm::submit() in case it has + // changed in the meantime. + if ($entity->access('delete', $account)) { + return AccessResult::allowed(); + } + } + return AccessResult::forbidden(); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php b/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php new file mode 100644 index 0000000..aea3291 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php @@ -0,0 +1,323 @@ + langcodes format. + * + * @var array + */ + protected $selection = []; + + /** + * The entity type definition. + * + * @var \Drupal\Core\Entity\EntityTypeInterface + */ + protected $entityType; + + /** + * Constructs a new DeleteMultiple object. + * + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory + * The tempstore factory. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger service. + */ + public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, MessengerInterface $messenger) { + $this->currentUser = $current_user; + $this->entityTypeManager = $entity_type_manager; + $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm'); + $this->messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('current_user'), + $container->get('entity_type.manager'), + $container->get('tempstore.private'), + $container->get('messenger') + ); + } + + /** + * {@inheritdoc} + */ + public function getBaseFormId() { + return 'entity_delete_multiple_confirm_form'; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + // Get entity type ID from the route because ::buildForm has not yet been + // called. + $entity_type_id = $this->getRouteMatch()->getParameter('entity_type_id'); + return $entity_type_id . '_delete_multiple_confirm_form'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this @item?', 'Are you sure you want to delete these @items?', [ + '@item' => $this->entityType->getSingularLabel(), + '@items' => $this->entityType->getPluralLabel(), + ]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + if ($this->entityType->hasLinkTemplate('collection')) { + return new Url('entity.' . $this->entityTypeId . '.collection'); + } + else { + return new Url(''); + } + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) { + $this->entityTypeId = $entity_type_id; + $this->entityType = $this->entityTypeManager->getDefinition($this->entityTypeId); + $this->selection = $this->tempStore->get($this->currentUser->id() . ':' . $entity_type_id); + if (empty($this->entityTypeId) || empty($this->selection)) { + return new RedirectResponse($this->getCancelUrl() + ->setAbsolute() + ->toString()); + } + + $items = []; + $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($this->selection)); + foreach ($this->selection as $id => $selected_langcodes) { + $entity = $entities[$id]; + foreach ($selected_langcodes as $langcode) { + $key = $id . ':' . $langcode; + if ($entity instanceof TranslatableInterface) { + $entity = $entity->getTranslation($langcode); + $default_key = $id . ':' . $entity->getUntranslated()->language()->getId(); + + // Build a nested list of translations that will be deleted if the + // entity has multiple translations. + $entity_languages = $entity->getTranslationLanguages(); + if (count($entity_languages) > 1 && $entity->isDefaultTranslation()) { + $names = []; + foreach ($entity_languages as $translation_langcode => $language) { + $names[] = $language->getName(); + unset($items[$id . ':' . $translation_langcode]); + } + $items[$default_key] = [ + 'label' => [ + '#markup' => $this->t('@label (Original translation) - The following @entity_type translations will be deleted:', + [ + '@label' => $entity->label(), + '@entity_type' => $this->entityType->getSingularLabel(), + ]), + ], + 'deleted_translations' => [ + '#theme' => 'item_list', + '#items' => $names, + ], + ]; + } + elseif (!isset($items[$default_key])) { + $items[$key] = $entity->label(); + } + } + elseif (!isset($items[$key])) { + $items[$key] = $entity->label(); + } + } + } + + $form['entities'] = [ + '#theme' => 'item_list', + '#items' => $items, + ]; + $form = parent::buildForm($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $total_count = 0; + $delete_entities = []; + $delete_translations = []; + $inaccessible_entities = []; + $storage = $this->entityTypeManager->getStorage($this->entityTypeId); + + $entities = $storage->loadMultiple(array_keys($this->selection)); + foreach ($this->selection as $id => $selected_langcodes) { + $entity = $entities[$id]; + if (!$entity->access('delete', $this->currentUser)) { + $inaccessible_entities[] = $entity; + continue; + } + foreach ($selected_langcodes as $langcode) { + if ($entity instanceof TranslatableInterface) { + $entity = $entity->getTranslation($langcode); + // If the entity is the default translation then deleting it will + // delete all the translations. + if ($entity->isDefaultTranslation()) { + $delete_entities[$id] = $entity; + // If there are translations already marked for deletion then remove + // them as they will be deleted anyway. + unset($delete_translations[$id]); + // Update the total count. Since a single delete will delete all + // translations, we need to add the number of translations to the + // count. + $total_count += count($entity->getTranslationLanguages()); + } + // Add the translation to the list of translations to be deleted + // unless the default translation is being deleted. + elseif (!isset($delete_entities[$id])) { + $delete_translations[$id][] = $entity; + } + } + elseif (!isset($delete_entities[$id])) { + $delete_entities[$id] = $entity; + $total_count++; + } + } + } + + 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()->getLowercaseLabel(), + '%label' => $entity->label(), + ]); + } + } + + if ($delete_translations) { + /** @var \Drupal\Core\Entity\TranslatableInterface[][] $delete_translations */ + foreach ($delete_translations as $id => $translations) { + $entity = $entities[$id]->getUntranslated(); + foreach ($translations as $translation) { + $entity->removeTranslation($translation->language()->getId()); + } + $entity->save(); + foreach ($translations as $translation) { + $this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label @language translation has been deleted.', [ + '@entity-type' => $entity->getEntityType()->getLowercaseLabel(), + '%label' => $entity->label(), + '@language' => $translation->language()->getName(), + ]); + } + $total_count += count($translations); + } + } + + if ($total_count) { + $this->messenger->addStatus($this->getDeletedMessage($total_count)); + } + if ($inaccessible_entities) { + $this->messenger->addWarning($this->getInaccessibleMessage(count($inaccessible_entities))); + } + $this->tempStore->delete($this->currentUser->id()); + $form_state->setRedirectUrl($this->getCancelUrl()); + } + + /** + * Returns the message to show the user after an item was deleted. + * + * @param int $count + * Count of deleted translations. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The item deleted message. + */ + protected function getDeletedMessage($count) { + return $this->formatPlural($count, 'Deleted @count item.', 'Deleted @count items.'); + } + + /** + * Returns the message to show the user when an item has not been deleted. + * + * @param int $count + * Count of deleted translations. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The item inaccessible message. + */ + protected function getInaccessibleMessage($count) { + return $this->formatPlural($count, "@count item has not been deleted because you do not have the necessary permissions.", "@count items have not been deleted because you do not have the necessary permissions."); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php index 1abee11..b04fc3e 100644 --- a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php +++ b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php @@ -54,4 +54,14 @@ protected function getDeleteFormRoute(EntityTypeInterface $entity_type) { } } + /** + * {@inheritdoc} + */ + protected function getDeleteMultipleFormRoute(EntityTypeInterface $entity_type) { + if ($route = parent::getDeleteMultipleFormRoute($entity_type)) { + $route->setOption('_admin_route', TRUE); + return $route; + } + } + } diff --git a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php index fb40fd5..0a99b33 100644 --- a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php @@ -24,6 +24,7 @@ * - edit-form * - delete-form * - collection + * - delete-multiple-form * * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider. */ @@ -98,6 +99,10 @@ public function getRoutes(EntityTypeInterface $entity_type) { $collection->add("entity.{$entity_type_id}.collection", $collection_route); } + if ($delete_multiple_route = $this->getDeleteMultipleFormRoute($entity_type)) { + $collection->add('entity.' . $entity_type->id() . '.delete_multiple_form', $delete_multiple_route); + } + return $collection; } @@ -350,4 +355,23 @@ protected function getEntityTypeIdKeyType(EntityTypeInterface $entity_type) { return $field_storage_definitions[$entity_type->getKey('id')]->getType(); } + /** + * Returns the delete multiple form route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getDeleteMultipleFormRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('delete-multiple-form') && $entity_type->hasHandlerClass('form', 'delete-multiple-confirm')) { + $route = new Route($entity_type->getLinkTemplate('delete-multiple-form')); + $route->setDefault('_form', $entity_type->getFormClass('delete-multiple-confirm')); + $route->setDefault('entity_type_id', $entity_type->id()); + $route->setRequirement('_entity_delete_multiple_access', $entity_type->id()); + return $route; + } + } + } diff --git a/core/modules/action/tests/src/Functional/BulkFormTest.php b/core/modules/action/tests/src/Functional/BulkFormTest.php index 439ffe4..ec6ca48 100644 --- a/core/modules/action/tests/src/Functional/BulkFormTest.php +++ b/core/modules/action/tests/src/Functional/BulkFormTest.php @@ -148,7 +148,7 @@ public function testBulkForm() { $errors = $this->xpath('//div[contains(@class, "messages--status")]'); $this->assertFalse($errors, 'No action message shown.'); $this->drupalPostForm(NULL, [], t('Delete')); - $this->assertText(t('Deleted 5 posts.')); + $this->assertText(t('Deleted 5 content items.')); // Check if we got redirected to the original page. $this->assertUrl('test_bulk_form'); } diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml index 80ee86c..6daeace 100644 --- a/core/modules/comment/comment.routing.yml +++ b/core/modules/comment/comment.routing.yml @@ -59,8 +59,18 @@ comment.multiple_delete_confirm: defaults: _title: 'Delete' _form: '\Drupal\comment\Form\ConfirmDeleteMultiple' + entity_type_id: 'comment' requirements: - _permission: 'administer comments' + _entity_delete_multiple_access: 'comment' + +entity.comment.delete_multiple_form: + path: '/admin/content/comment/delete' + defaults: + _title: 'Delete' + _form: '\Drupal\comment\Form\ConfirmDeleteMultiple' + entity_type_id: 'comment' + requirements: + _entity_delete_multiple_access: 'comment' comment.reply: path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}' diff --git a/core/modules/comment/config/install/system.action.comment_delete_action.yml b/core/modules/comment/config/install/system.action.comment_delete_action.yml index 035299e..08f7966 100644 --- a/core/modules/comment/config/install/system.action.comment_delete_action.yml +++ b/core/modules/comment/config/install/system.action.comment_delete_action.yml @@ -6,5 +6,5 @@ dependencies: id: comment_delete_action label: 'Delete comment' type: comment -plugin: comment_delete_action +plugin: entity:delete_action:comment configuration: { } diff --git a/core/modules/comment/config/schema/comment.schema.yml b/core/modules/comment/config/schema/comment.schema.yml index c29088b..0f64318 100644 --- a/core/modules/comment/config/schema/comment.schema.yml +++ b/core/modules/comment/config/schema/comment.schema.yml @@ -44,6 +44,8 @@ action.configuration.comment_unpublish_action: type: action_configuration_default label: 'Unpublish comment configuration' +# @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. +# @see https://www.drupal.org/node/2934349 action.configuration.comment_delete_action: type: action_configuration_default label: 'Delete comment configuration' diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index 233c752..5f5a611 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -56,6 +56,7 @@ * links = { * "canonical" = "/comment/{comment}", * "delete-form" = "/comment/{comment}/delete", + * "delete-multiple-form" = "/admin/content/comment/delete", * "edit-form" = "/comment/{comment}/edit", * "create" = "/comment", * }, diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php index 4fe7291..8a93ecd 100644 --- a/core/modules/comment/src/Form/CommentAdminOverview.php +++ b/core/modules/comment/src/Form/CommentAdminOverview.php @@ -290,9 +290,9 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $info[$comment->id()][$langcode] = $langcode; } $this->tempStoreFactory - ->get('comment_multiple_delete_confirm') - ->set($this->currentUser()->id(), $info); - $form_state->setRedirect('comment.multiple_delete_confirm'); + ->get('entity_delete_multiple_confirm') + ->set($this->currentUser()->id() . ':comment', $info); + $form_state->setRedirect('entity.comment.delete_multiple_form'); } } diff --git a/core/modules/comment/src/Form/ConfirmDeleteMultiple.php b/core/modules/comment/src/Form/ConfirmDeleteMultiple.php index 7044393..825b0af 100644 --- a/core/modules/comment/src/Form/ConfirmDeleteMultiple.php +++ b/core/modules/comment/src/Form/ConfirmDeleteMultiple.php @@ -2,76 +2,21 @@ namespace Drupal\comment\Form; -use Drupal\comment\CommentStorageInterface; -use Drupal\Core\TempStore\PrivateTempStoreFactory; -use Drupal\Core\Form\ConfirmFormBase; -use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Entity\Form\DeleteMultipleForm as EntityDeleteMultipleForm; use Drupal\Core\Url; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides the comment multiple delete confirmation form. * * @internal */ -class ConfirmDeleteMultiple extends ConfirmFormBase { - - /** - * The tempstore factory. - * - * @var \Drupal\Core\TempStore\PrivateTempStoreFactory - */ - protected $tempStoreFactory; - - /** - * The comment storage. - * - * @var \Drupal\comment\CommentStorageInterface - */ - protected $commentStorage; - - /** - * An array of comments to be deleted. - * - * @var string[][] - */ - protected $commentInfo; - - /** - * Creates an new ConfirmDeleteMultiple form. - * - * @param \Drupal\comment\CommentStorageInterface $comment_storage - * The comment storage. - * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory - * The tempstore factory. - */ - public function __construct(CommentStorageInterface $comment_storage, PrivateTempStoreFactory $temp_store_factory) { - $this->commentStorage = $comment_storage; - $this->tempStoreFactory = $temp_store_factory; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('entity.manager')->getStorage('comment'), - $container->get('tempstore.private') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'comment_multiple_delete_confirm'; - } +class ConfirmDeleteMultiple extends EntityDeleteMultipleForm { /** * {@inheritdoc} */ public function getQuestion() { - return $this->formatPlural(count($this->commentInfo), 'Are you sure you want to delete this comment and all its children?', 'Are you sure you want to delete these comments and all their children?'); + return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this comment and all its children?', 'Are you sure you want to delete these comments and all their children?'); } /** @@ -84,116 +29,15 @@ public function getCancelUrl() { /** * {@inheritdoc} */ - public function getConfirmText() { - return $this->t('Delete'); + protected function getDeletedMessage($count) { + return $this->formatPlural($count, 'Deleted @count comment.', 'Deleted @count comments.'); } /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { - $this->commentInfo = $this->tempStoreFactory->get('comment_multiple_delete_confirm')->get($this->currentUser()->id()); - if (empty($this->commentInfo)) { - return $this->redirect('comment.admin'); - } - /** @var \Drupal\comment\CommentInterface[] $comments */ - $comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo)); - - $items = []; - foreach ($this->commentInfo as $id => $langcodes) { - foreach ($langcodes as $langcode) { - $comment = $comments[$id]->getTranslation($langcode); - $key = $id . ':' . $langcode; - $default_key = $id . ':' . $comment->getUntranslated()->language()->getId(); - - // If we have a translated entity we build a nested list of translations - // that will be deleted. - $languages = $comment->getTranslationLanguages(); - if (count($languages) > 1 && $comment->isDefaultTranslation()) { - $names = []; - foreach ($languages as $translation_langcode => $language) { - $names[] = $language->getName(); - unset($items[$id . ':' . $translation_langcode]); - } - $items[$default_key] = [ - 'label' => [ - '#markup' => $this->t('@label (Original translation) - The following comment translations will be deleted:', ['@label' => $comment->label()]), - ], - 'deleted_translations' => [ - '#theme' => 'item_list', - '#items' => $names, - ], - ]; - } - elseif (!isset($items[$default_key])) { - $items[$key] = $comment->label(); - } - } - } - - $form['comments'] = [ - '#theme' => 'item_list', - '#items' => $items, - ]; - - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - if ($form_state->getValue('confirm') && !empty($this->commentInfo)) { - $total_count = 0; - $delete_comments = []; - /** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */ - $delete_translations = []; - /** @var \Drupal\comment\CommentInterface[] $comments */ - $comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo)); - - foreach ($this->commentInfo as $id => $langcodes) { - foreach ($langcodes as $langcode) { - $comment = $comments[$id]->getTranslation($langcode); - if ($comment->isDefaultTranslation()) { - $delete_comments[$id] = $comment; - unset($delete_translations[$id]); - $total_count += count($comment->getTranslationLanguages()); - } - elseif (!isset($delete_comments[$id])) { - $delete_translations[$id][] = $comment; - } - } - } - - if ($delete_comments) { - $this->commentStorage->delete($delete_comments); - $this->logger('content')->notice('Deleted @count comments.', ['@count' => count($delete_comments)]); - } - - if ($delete_translations) { - $count = 0; - foreach ($delete_translations as $id => $translations) { - $comment = $comments[$id]->getUntranslated(); - foreach ($translations as $translation) { - $comment->removeTranslation($translation->language()->getId()); - } - $comment->save(); - $count += count($translations); - } - if ($count) { - $total_count += $count; - $this->logger('content')->notice('Deleted @count comment translations.', ['@count' => $count]); - } - } - - if ($total_count) { - drupal_set_message($this->formatPlural($total_count, 'Deleted 1 comment.', 'Deleted @count comments.')); - } - - $this->tempStoreFactory->get('comment_multiple_delete_confirm')->delete($this->currentUser()->id()); - } - - $form_state->setRedirectUrl($this->getCancelUrl()); + protected function getInaccessibleMessage($count) { + return $this->formatPlural($count, "@count comment has not been deleted because you do not have the necessary permissions.", "@count comments have not been deleted because you do not have the necessary permissions."); } } diff --git a/core/modules/comment/src/Plugin/Action/DeleteComment.php b/core/modules/comment/src/Plugin/Action/DeleteComment.php index 5c6941d..ef6116c 100644 --- a/core/modules/comment/src/Plugin/Action/DeleteComment.php +++ b/core/modules/comment/src/Plugin/Action/DeleteComment.php @@ -2,97 +2,33 @@ namespace Drupal\comment\Plugin\Action; -use Drupal\Core\Action\ActionBase; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Action\Plugin\Action\DeleteAction; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Deletes a comment. * + * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. + * Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. + * + * @see \Drupal\Core\Action\Plugin\Action\DeleteAction + * @see https://www.drupal.org/node/2934349 + * * @Action( * id = "comment_delete_action", - * label = @Translation("Delete comment"), - * type = "comment", - * confirm_form_route_name = "comment.multiple_delete_confirm" + * label = @Translation("Delete comment") * ) */ -class DeleteComment extends ActionBase implements ContainerFactoryPluginInterface { - - /** - * The tempstore object. - * - * @var \Drupal\Core\TempStore\PrivateTempStore - */ - protected $tempStore; - - /** - * The current user. - * - * @var \Drupal\Core\Session\AccountInterface - */ - protected $currentUser; - - /** - * Constructs a new DeleteComment object. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin ID for the plugin instance. - * @param array $plugin_definition - * The plugin implementation definition. - * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory - * The tempstore factory. - * @param \Drupal\Core\Session\AccountInterface $current_user - * The current user. - */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { - $this->currentUser = $current_user; - $this->tempStore = $temp_store_factory->get('comment_multiple_delete_confirm'); - parent::__construct($configuration, $plugin_id, $plugin_definition); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('tempstore.private'), - $container->get('current_user') - ); - } - - /** - * {@inheritdoc} - */ - public function executeMultiple(array $entities) { - $info = []; - /** @var \Drupal\comment\CommentInterface $comment */ - foreach ($entities as $comment) { - $langcode = $comment->language()->getId(); - $info[$comment->id()][$langcode] = $langcode; - } - $this->tempStore->set($this->currentUser->id(), $info); - } - - /** - * {@inheritdoc} - */ - public function execute($entity = NULL) { - $this->executeMultiple([$entity]); - } +class DeleteComment extends DeleteAction { /** * {@inheritdoc} */ - public function access($comment, AccountInterface $account = NULL, $return_as_object = FALSE) { - /** @var \Drupal\comment\CommentInterface $comment */ - return $comment->access('delete', $account, $return_as_object); + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $temp_store_factory, $current_user); + @trigger_error(__NAMESPACE__ . '\DeleteComment is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', E_USER_DEPRECATED); } } diff --git a/core/modules/node/config/install/system.action.node_delete_action.yml b/core/modules/node/config/install/system.action.node_delete_action.yml index 97662dc..da2d886 100644 --- a/core/modules/node/config/install/system.action.node_delete_action.yml +++ b/core/modules/node/config/install/system.action.node_delete_action.yml @@ -6,5 +6,5 @@ dependencies: id: node_delete_action label: 'Delete content' type: node -plugin: node_delete_action +plugin: entity:delete_action:node configuration: { } diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml index 50b3f3e..b6fb7bc 100644 --- a/core/modules/node/config/schema/node.schema.yml +++ b/core/modules/node/config/schema/node.schema.yml @@ -80,6 +80,8 @@ action.configuration.node_save_action: type: action_configuration_default label: 'Save content configuration' +# @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. +# @see https://www.drupal.org/node/2934349 action.configuration.node_delete_action: type: action_configuration_default label: 'Delete content configuration' diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml index a016e3a..d592bd1 100644 --- a/core/modules/node/node.routing.yml +++ b/core/modules/node/node.routing.yml @@ -2,8 +2,17 @@ node.multiple_delete_confirm: path: '/admin/content/node/delete' defaults: _form: '\Drupal\node\Form\DeleteMultiple' + entity_type_id: 'node' requirements: - _permission: 'administer nodes' + _entity_delete_multiple_access: 'node' + +entity.node.delete_multiple_form: + path: '/admin/content/node/delete' + defaults: + _form: '\Drupal\node\Form\DeleteMultiple' + entity_type_id: 'node' + requirements: + _entity_delete_multiple_access: 'node' node.add_page: path: '/node/add' diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index 368ce1b..ccee95b 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -33,7 +33,8 @@ * "form" = { * "default" = "Drupal\node\NodeForm", * "delete" = "Drupal\node\Form\NodeDeleteForm", - * "edit" = "Drupal\node\NodeForm" + * "edit" = "Drupal\node\NodeForm", + * "delete-multiple-confirm" = "Drupal\node\Form\DeleteMultiple" * }, * "route_provider" = { * "html" = "Drupal\node\Entity\NodeRouteProvider", @@ -71,6 +72,7 @@ * links = { * "canonical" = "/node/{node}", * "delete-form" = "/node/{node}/delete", + * "delete-multiple-form" = "/admin/content/node/delete", * "edit-form" = "/node/{node}/edit", * "version-history" = "/node/{node}/revisions", * "revision" = "/node/{node}/revisions/{node_revision}/view", diff --git a/core/modules/node/src/Form/DeleteMultiple.php b/core/modules/node/src/Form/DeleteMultiple.php index 9d36aa5..a36de5f 100644 --- a/core/modules/node/src/Form/DeleteMultiple.php +++ b/core/modules/node/src/Form/DeleteMultiple.php @@ -2,78 +2,15 @@ namespace Drupal\node\Form; -use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Form\ConfirmFormBase; -use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Entity\Form\DeleteMultipleForm as EntityDeleteMultipleForm; use Drupal\Core\Url; -use Drupal\Core\TempStore\PrivateTempStoreFactory; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; /** * Provides a node deletion confirmation form. * * @internal */ -class DeleteMultiple extends ConfirmFormBase { - - /** - * The array of nodes to delete. - * - * @var string[][] - */ - protected $nodeInfo = []; - - /** - * The tempstore factory. - * - * @var \Drupal\Core\TempStore\PrivateTempStoreFactory - */ - protected $tempStoreFactory; - - /** - * The node storage. - * - * @var \Drupal\Core\Entity\EntityStorageInterface - */ - protected $storage; - - /** - * Constructs a DeleteMultiple form object. - * - * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory - * The tempstore factory. - * @param \Drupal\Core\Entity\EntityManagerInterface $manager - * The entity manager. - */ - public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityManagerInterface $manager) { - $this->tempStoreFactory = $temp_store_factory; - $this->storage = $manager->getStorage('node'); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('tempstore.private'), - $container->get('entity.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'node_multiple_delete_confirm'; - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - return $this->formatPlural(count($this->nodeInfo), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?'); - } +class DeleteMultiple extends EntityDeleteMultipleForm { /** * {@inheritdoc} @@ -85,117 +22,15 @@ public function getCancelUrl() { /** * {@inheritdoc} */ - public function getConfirmText() { - return t('Delete'); + protected function getDeletedMessage($count) { + return $this->formatPlural($count, 'Deleted @count content item.', 'Deleted @count content items.'); } /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { - $this->nodeInfo = $this->tempStoreFactory->get('node_multiple_delete_confirm')->get(\Drupal::currentUser()->id()); - if (empty($this->nodeInfo)) { - return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString()); - } - /** @var \Drupal\node\NodeInterface[] $nodes */ - $nodes = $this->storage->loadMultiple(array_keys($this->nodeInfo)); - - $items = []; - foreach ($this->nodeInfo as $id => $langcodes) { - foreach ($langcodes as $langcode) { - $node = $nodes[$id]->getTranslation($langcode); - $key = $id . ':' . $langcode; - $default_key = $id . ':' . $node->getUntranslated()->language()->getId(); - - // If we have a translated entity we build a nested list of translations - // that will be deleted. - $languages = $node->getTranslationLanguages(); - if (count($languages) > 1 && $node->isDefaultTranslation()) { - $names = []; - foreach ($languages as $translation_langcode => $language) { - $names[] = $language->getName(); - unset($items[$id . ':' . $translation_langcode]); - } - $items[$default_key] = [ - 'label' => [ - '#markup' => $this->t('@label (Original translation) - The following content translations will be deleted:', ['@label' => $node->label()]), - ], - 'deleted_translations' => [ - '#theme' => 'item_list', - '#items' => $names, - ], - ]; - } - elseif (!isset($items[$default_key])) { - $items[$key] = $node->label(); - } - } - } - - $form['nodes'] = [ - '#theme' => 'item_list', - '#items' => $items, - ]; - $form = parent::buildForm($form, $form_state); - - return $form; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - if ($form_state->getValue('confirm') && !empty($this->nodeInfo)) { - $total_count = 0; - $delete_nodes = []; - /** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */ - $delete_translations = []; - /** @var \Drupal\node\NodeInterface[] $nodes */ - $nodes = $this->storage->loadMultiple(array_keys($this->nodeInfo)); - - foreach ($this->nodeInfo as $id => $langcodes) { - foreach ($langcodes as $langcode) { - $node = $nodes[$id]->getTranslation($langcode); - if ($node->isDefaultTranslation()) { - $delete_nodes[$id] = $node; - unset($delete_translations[$id]); - $total_count += count($node->getTranslationLanguages()); - } - elseif (!isset($delete_nodes[$id])) { - $delete_translations[$id][] = $node; - } - } - } - - if ($delete_nodes) { - $this->storage->delete($delete_nodes); - $this->logger('content')->notice('Deleted @count posts.', ['@count' => count($delete_nodes)]); - } - - if ($delete_translations) { - $count = 0; - foreach ($delete_translations as $id => $translations) { - $node = $nodes[$id]->getUntranslated(); - foreach ($translations as $translation) { - $node->removeTranslation($translation->language()->getId()); - } - $node->save(); - $count += count($translations); - } - if ($count) { - $total_count += $count; - $this->logger('content')->notice('Deleted @count content translations.', ['@count' => $count]); - } - } - - if ($total_count) { - drupal_set_message($this->formatPlural($total_count, 'Deleted 1 post.', 'Deleted @count posts.')); - } - - $this->tempStoreFactory->get('node_multiple_delete_confirm')->delete(\Drupal::currentUser()->id()); - } - - $form_state->setRedirect('system.admin_content'); + protected function getInaccessibleMessage($count) { + return $this->formatPlural($count, "@count content item has not been deleted because you do not have the necessary permissions.", "@count content items have not been deleted because you do not have the necessary permissions."); } } diff --git a/core/modules/node/src/Plugin/Action/DeleteNode.php b/core/modules/node/src/Plugin/Action/DeleteNode.php index 19f8a7a..64fbe12 100644 --- a/core/modules/node/src/Plugin/Action/DeleteNode.php +++ b/core/modules/node/src/Plugin/Action/DeleteNode.php @@ -2,98 +2,33 @@ namespace Drupal\node\Plugin\Action; -use Drupal\Core\Action\ActionBase; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Action\Plugin\Action\DeleteAction; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Redirects to a node deletion form. * + * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. + * Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. + * + * @see \Drupal\Core\Action\Plugin\Action\DeleteAction + * @see https://www.drupal.org/node/2934349 + * * @Action( * id = "node_delete_action", - * label = @Translation("Delete content"), - * type = "node", - * confirm_form_route_name = "node.multiple_delete_confirm" + * label = @Translation("Delete content") * ) */ -class DeleteNode extends ActionBase implements ContainerFactoryPluginInterface { - - /** - * The tempstore object. - * - * @var \Drupal\Core\TempStore\SharedTempStore - */ - protected $tempStore; - - /** - * The current user. - * - * @var \Drupal\Core\Session\AccountInterface - */ - protected $currentUser; - - /** - * Constructs a new DeleteNode object. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin ID for the plugin instance. - * @param mixed $plugin_definition - * The plugin implementation definition. - * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory - * The tempstore factory. - * @param \Drupal\Core\Session\AccountInterface $current_user - * Current user. - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { - $this->currentUser = $current_user; - $this->tempStore = $temp_store_factory->get('node_multiple_delete_confirm'); - - parent::__construct($configuration, $plugin_id, $plugin_definition); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('tempstore.private'), - $container->get('current_user') - ); - } - - /** - * {@inheritdoc} - */ - public function executeMultiple(array $entities) { - $info = []; - /** @var \Drupal\node\NodeInterface $node */ - foreach ($entities as $node) { - $langcode = $node->language()->getId(); - $info[$node->id()][$langcode] = $langcode; - } - $this->tempStore->set($this->currentUser->id(), $info); - } - - /** - * {@inheritdoc} - */ - public function execute($object = NULL) { - $this->executeMultiple([$object]); - } +class DeleteNode extends DeleteAction { /** * {@inheritdoc} */ - public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { - /** @var \Drupal\node\NodeInterface $object */ - return $object->access('delete', $account, $return_as_object); + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $temp_store_factory, $current_user); + @trigger_error(__NAMESPACE__ . '\DeleteNode is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', E_USER_DEPRECATED); } } diff --git a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php index 319890b..894a460 100644 --- a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php +++ b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php @@ -249,15 +249,15 @@ public function testBulkDeletion() { $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); $label = $this->loadNode(1)->label(); - $this->assertText("$label (Original translation) - The following content translations will be deleted:"); + $this->assertText("$label (Original translation) - The following content item translations will be deleted:"); $label = $this->loadNode(2)->label(); - $this->assertText("$label (Original translation) - The following content translations will be deleted:"); + $this->assertText("$label (Original translation) - The following content item translations will be deleted:"); $label = $this->loadNode(3)->getTranslation('en')->label(); $this->assertText($label); - $this->assertNoText("$label (Original translation) - The following content translations will be deleted:"); + $this->assertNoText("$label (Original translation) - The following content item translations will be deleted:"); $label = $this->loadNode(4)->label(); $this->assertText($label); - $this->assertNoText("$label (Original translation) - The following content translations will be deleted:"); + $this->assertNoText("$label (Original translation) - The following content item translations will be deleted:"); $this->drupalPostForm(NULL, [], t('Delete')); @@ -273,7 +273,7 @@ public function testBulkDeletion() { $node = $this->loadNode(5); $this->assertTrue($node, '5: Node has not been deleted'); - $this->assertText('Deleted 8 posts.'); + $this->assertText('Deleted 8 content items.'); } /** diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index da13077..66fa24a 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -111,3 +111,22 @@ function system_post_update_change_action_plugins() { } } } + +/** + * Change plugin IDs of delete actions. + */ +function system_post_update_change_delete_action_plugins() { + $old_new_action_id_map = [ + 'comment_delete_action' => 'entity:delete_action:comment', + 'node_delete_action' => 'entity:delete_action:node', + ]; + + /** @var \Drupal\system\Entity\Action[] $actions */ + $actions = \Drupal::entityTypeManager()->getStorage('action')->loadMultiple(); + foreach ($actions as $action) { + if (isset($old_new_action_id_map[$action->getPlugin()->getPluginId()])) { + $action->setPlugin($old_new_action_id_map[$action->getPlugin()->getPluginId()]); + $action->save(); + } + } +} diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php index 862958e..8fa69a3 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php @@ -17,7 +17,8 @@ * "access" = "Drupal\entity_test\EntityTestAccessControlHandler", * "form" = { * "default" = "Drupal\entity_test\EntityTestForm", - * "delete" = "Drupal\entity_test\EntityTestDeleteForm" + * "delete" = "Drupal\entity_test\EntityTestDeleteForm", + * "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm" * }, * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", @@ -45,6 +46,7 @@ * "add-form" = "/entity_test_mulrevpub/add", * "canonical" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}", * "delete-form" = "/entity_test/delete/entity_test_mulrevpub/{entity_test_mulrevpub}", + * "delete-multiple-form" = "/entity_test/delete", * "edit-form" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}/edit", * "revision" = "/entity_test_mulrevpub/{entity_test_mulrevpub}/revision/{entity_test_mulrevpub_revision}/view", * } diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php index 5aa3ae1..6fd4904 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php @@ -16,7 +16,8 @@ * "view_builder" = "Drupal\entity_test\EntityTestViewBuilder", * "form" = { * "default" = "Drupal\entity_test\EntityTestForm", - * "delete" = "Drupal\entity_test\EntityTestDeleteForm" + * "delete" = "Drupal\entity_test\EntityTestDeleteForm", + * "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm" * }, * "view_builder" = "Drupal\entity_test\EntityTestViewBuilder", * "translation" = "Drupal\content_translation\ContentTranslationHandler", @@ -41,6 +42,7 @@ * "add-form" = "/entity_test_rev/add", * "canonical" = "/entity_test_rev/manage/{entity_test_rev}", * "delete-form" = "/entity_test/delete/entity_test_rev/{entity_test_rev}", + * "delete-multiple-form" = "/entity_test_rev/delete_multiple", * "edit-form" = "/entity_test_rev/manage/{entity_test_rev}/edit", * "revision" = "/entity_test_rev/{entity_test_rev}/revision/{entity_test_rev_revision}/view", * } diff --git a/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php index 4094d00..e3bdcf6 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php @@ -53,4 +53,33 @@ public function testUpdateActionsWithEntityPlugins() { } } + /** + * Tests upgrading comment and node delete actions to generic entity ones. + * + * @see system_post_update_change_delete_action_plugins() + */ + public function testUpdateDeleteActionsWithEntityPlugins() { + // comment_delete_actions is not part of the dump files. + $array = [ + 'node_delete_action' => ['node_delete_action', 'entity:delete_action:node'], + ]; + + foreach ($array as $key => list($before, $after)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = Action::load($key); + $this->assertSame($before, $action->getPlugin()->getPluginId()); + } + + $this->runUpdates(); + + foreach ($array as $key => list($before, $after)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = Action::load($key); + $this->assertSame($after, $action->getPlugin()->getPluginId()); + + // Check that the type the action is based on will be a module dependency. + $this->assertArraySubset(['module' => [$action->getPluginDefinition()['type']]], $action->getDependencies()); + } + } + } diff --git a/core/tests/Drupal/FunctionalTests/Entity/DeleteMultipleFormTest.php b/core/tests/Drupal/FunctionalTests/Entity/DeleteMultipleFormTest.php new file mode 100644 index 0000000..45f88d6 --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Entity/DeleteMultipleFormTest.php @@ -0,0 +1,157 @@ + 'default', + 'label' => 'Default', + ])->save(); + $this->account = $this->drupalCreateUser(['administer entity_test content']); + $this->drupalLogin($this->account); + } + + /** + * Tests the delete form for translatable entities. + */ + public function testTranslatableEntities() { + ConfigurableLanguage::create(['id' => 'es'])->save(); + ConfigurableLanguage::create(['id' => 'fr'])->save(); + + $selection = []; + + $entity1 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity1']); + $entity1->addTranslation('es', ['name' => 'entity1 spanish']); + $entity1->addTranslation('fr', ['name' => 'entity1 french']); + $entity1->save(); + $selection[$entity1->id()]['en'] = 'en'; + + $entity2 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity2']); + $entity2->addTranslation('es', ['name' => 'entity2 spanish']); + $entity2->addTranslation('fr', ['name' => 'entity2 french']); + $entity2->save(); + $selection[$entity2->id()]['es'] = 'es'; + $selection[$entity2->id()]['fr'] = 'fr'; + + $entity3 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity3']); + $entity3->addTranslation('es', ['name' => 'entity3 spanish']); + $entity3->addTranslation('fr', ['name' => 'entity3 french']); + $entity3->save(); + $selection[$entity3->id()]['fr'] = 'fr'; + + // This entity will be inaccessible because of + // Drupal\entity_test\EntityTestAccessControlHandler. + $entity4 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'forbid_access']); + $entity4->save(); + $selection[$entity4->id()]['en'] = 'en'; + + // Add the selection to the tempstore just like DeleteAction would. + $tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm'); + $tempstore->set($this->account->id() . ':entity_test_mulrevpub', $selection); + + $this->drupalGet('/entity_test/delete'); + $assert = $this->assertSession(); + $assert->statusCodeEquals(200); + $assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these test entity - revisions, data table, and published interface entities?'); + $list_selector = '#entity-test-mulrevpub-delete-multiple-confirm-form > div.item-list > ul'; + $assert->elementTextContains('css', $list_selector, 'entity1 (Original translation) - The following test entity - revisions, data table, and published interface translations will be deleted:'); + $assert->elementTextContains('css', $list_selector, 'entity2 spanish'); + $assert->elementTextContains('css', $list_selector, 'entity2 french'); + $assert->elementTextNotContains('css', $list_selector, 'entity3 spanish'); + $assert->elementTextContains('css', $list_selector, 'entity3 french'); + $delete_button = $this->getSession()->getPage()->findButton('Delete'); + $delete_button->click(); + $assert = $this->assertSession(); + $assert->addressEquals('/user/' . $this->account->id()); + $assert->responseContains('Deleted 6 items.'); + $assert->responseContains('1 item has not been deleted because you do not have the necessary permissions.'); + + \Drupal::entityTypeManager()->getStorage('entity_test_mulrevpub')->resetCache(); + $remaining_entities = EntityTestMulRevPub::loadMultiple([$entity1->id(), $entity2->id(), $entity3->id(), $entity4->id()]); + $this->assertCount(3, $remaining_entities); + } + + /** + * Tests the delete form for untranslatable entities. + */ + public function testUntranslatableEntities() { + $selection = []; + + $entity1 = EntityTestRev::create(['type' => 'default', 'name' => 'entity1']); + $entity1->save(); + $selection[$entity1->id()]['en'] = 'en'; + + $entity2 = EntityTestRev::create(['type' => 'default', 'name' => 'entity2']); + $entity2->save(); + $selection[$entity2->id()]['en'] = 'en'; + + // This entity will be inaccessible because of + // Drupal\entity_test\EntityTestAccessControlHandler. + $entity3 = EntityTestRev::create(['type' => 'default', 'name' => 'forbid_access']); + $entity3->save(); + $selection[$entity3->id()]['en'] = 'en'; + + // This entity will be inaccessible because of + // Drupal\entity_test\EntityTestAccessControlHandler. + $entity4 = EntityTestRev::create(['type' => 'default', 'name' => 'forbid_access']); + $entity4->save(); + $selection[$entity4->id()]['en'] = 'en'; + + // Add the selection to the tempstore just like DeleteAction would. + $tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm'); + $tempstore->set($this->account->id() . ':entity_test_rev', $selection); + + $this->drupalGet('/entity_test_rev/delete_multiple'); + $assert = $this->assertSession(); + $assert->statusCodeEquals(200); + $assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these test entity - revisions entities?'); + $list_selector = '#entity-test-rev-delete-multiple-confirm-form > div.item-list > ul'; + $assert->elementTextContains('css', $list_selector, 'entity1'); + $assert->elementTextContains('css', $list_selector, 'entity2'); + $delete_button = $this->getSession()->getPage()->findButton('Delete'); + $delete_button->click(); + $assert = $this->assertSession(); + $assert->addressEquals('/user/' . $this->account->id()); + $assert->responseContains('Deleted 2 items.'); + $assert->responseContains('2 items have not been deleted because you do not have the necessary permissions.'); + + \Drupal::entityTypeManager()->getStorage('entity_test_mulrevpub')->resetCache(); + $remaining_entities = EntityTestRev::loadMultiple([$entity1->id(), $entity2->id(), $entity3->id(), $entity4->id()]); + $this->assertCount(2, $remaining_entities); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Action/DeleteActionTest.php b/core/tests/Drupal/KernelTests/Core/Action/DeleteActionTest.php new file mode 100644 index 0000000..e5a2bd9 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Action/DeleteActionTest.php @@ -0,0 +1,85 @@ +installEntitySchema('entity_test_mulrevpub'); + $this->installEntitySchema('user'); + $this->installSchema('system', ['sequences', 'key_value_expire']); + + $this->testUser = User::create([ + 'name' => 'foobar', + 'mail' => 'foobar@example.com', + ]); + $this->testUser->save(); + \Drupal::service('current_user')->setAccount($this->testUser); + } + + /** + * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver::getDerivativeDefinitions + */ + public function testGetDerivativeDefinitions() { + $deriver = new EntityDeleteActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation()); + $this->assertEquals([ + 'entity_test_mulrevpub' => [ + 'type' => 'entity_test_mulrevpub', + 'label' => 'Delete test entity - revisions, data table, and published interface', + 'action_label' => 'Delete', + 'confirm_form_route_name' => 'entity.entity_test_mulrevpub.delete_multiple_form', + ], + 'entity_test_rev' => [ + 'type' => 'entity_test_rev', + 'label' => 'Delete test entity - revisions', + 'action_label' => 'Delete', + 'confirm_form_route_name' => 'entity.entity_test_rev.delete_multiple_form', + ], + ], $deriver->getDerivativeDefinitions([ + 'action_label' => 'Delete', + ])); + } + + /** + * @covers \Drupal\Core\Action\Plugin\Action\DeleteAction::execute + */ + public function testDeleteAction() { + $entity = EntityTestMulRevPub::create(['name' => 'test']); + $entity->save(); + + $action = Action::create([ + 'id' => 'entity_delete_action', + 'plugin' => 'entity:delete_action:entity_test_mulrevpub', + ]); + $action->save(); + + $action->execute([$entity]); + $this->assertArraySubset(['module' => ['entity_test']], $action->getDependencies()); + + /** @var \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store */ + $temp_store = \Drupal::service('tempstore.private'); + $store_entries = $temp_store->get('entity_delete_multiple_confirm')->get($this->testUser->id() . ':entity_test_mulrevpub'); + $this->assertArraySubset([$this->testUser->id() => ['en' => 'en']], $store_entries); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php b/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php index 04bf1c0..d381de1 100644 --- a/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php @@ -29,7 +29,7 @@ protected function setUp() { * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver::getDerivativeDefinitions */ public function testGetDerivativeDefinitions() { - $deriver = new EntityPublishedActionDeriver(\Drupal::entityTypeManager()); + $deriver = new EntityPublishedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation()); $this->assertArraySubset([ 'entity_test_mulrevpub' => [ 'type' => 'entity_test_mulrevpub', diff --git a/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php b/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php index 118f542..ed9b287 100644 --- a/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php @@ -29,7 +29,7 @@ protected function setUp() { * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver::getDerivativeDefinitions */ public function testGetDerivativeDefinitions() { - $deriver = new EntityChangedActionDeriver(\Drupal::entityTypeManager()); + $deriver = new EntityChangedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation()); $this->assertArraySubset([ 'entity_test_mul_changed' => [ 'type' => 'entity_test_mul_changed', diff --git a/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php index 216f900..d96193e 100644 --- a/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php +++ b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php @@ -187,6 +187,8 @@ public static function getSkippedDeprecations() { 'The "session_handler.write_check" service relies on the deprecated "Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler" class. It should either be deprecated or its implementation upgraded.', 'Not setting the strict option of the Choice constraint to true is deprecated since Symfony 3.4 and will throw an exception in 4.0.', 'Using the Yaml::PARSE_KEYS_AS_STRINGS flag is deprecated since Symfony 3.4 as it will be removed in 4.0. Quote your keys when they are evaluable instead.', + 'Drupal\node\Plugin\Action\DeleteNode is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', + 'Drupal\comment\Plugin\Action\DeleteComment is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', ]; } -- 2.10.1 (Apple Git-78)