Skip to content
Snippets Groups Projects
Commit f57cb2dd authored by Daniel Wehner's avatar Daniel Wehner
Browse files

Merge pull request #29 from bojanz/add-delete-action

Provide a generic delete entity action.
parents c2c49954 e01f0543
No related branches found
No related tags found
No related merge requests found
action.configuration.entity_delete_action:*:
type: action_configuration_default
label: 'Delete entity configuration'
views.field.rendered_entity:
type: views_field
label: 'Rendered entity'
......
<?php
/**
* @file
* Contains \Drupal\entity\Form\DeleteMultiple.
*/
namespace Drupal\entity\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an entities deletion confirmation form.
*/
class DeleteMultiple extends ConfirmFormBase {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The tempstore.
*
* @var \Drupal\user\SharedTempStore
*/
protected $tempStore;
/**
* The entity type id.
*
* @var string
*/
protected $entityTypeId;
/**
* The selection, in the entity_id => langcodes format.
*
* @var array
*/
protected $selection = [];
/**
* 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\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
*/
public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory) {
$this->currentUser = $current_user;
$this->entityTypeManager = $entity_type_manager;
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('current_user'),
$container->get('entity_type.manager'),
$container->get('user.private_tempstore')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'entity_delete_multiple_confirm';
}
/**
* {@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?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.' . $this->entityTypeId . '.collection');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete');
}
/**
* {@inheritdoc}
*
* @param string $entity_type_id
* The entity type id.
*/
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
$this->entityTypeId = $entity_type_id;
$this->selection = $this->tempStore->get($this->currentUser->id());
if (empty($this->entityTypeId) || empty($this->selection)) {
return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
}
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
$entities = $storage->loadMultiple(array_keys($this->selection));
$items = [];
foreach ($this->selection as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$entity = $entities[$id]->getTranslation($langcode);
$key = $id . ':' . $langcode;
$default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
// If we have a translated entity we build a nested list of translations
// that will be deleted.
$languages = $entity->getTranslationLanguages();
if (count($languages) > 1 && $entity->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) - <em>The following translations will be deleted:</em>', ['@label' => $entity->label()]),
],
'deleted_translations' => [
'#theme' => 'item_list',
'#items' => $names,
],
];
}
elseif (!isset($items[$default_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 = [];
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
$entities = $storage->loadMultiple(array_keys($this->selection));
foreach ($this->selection as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$entity = $entities[$id]->getTranslation($langcode);
if ($entity->isDefaultTranslation()) {
$delete_entities[$id] = $entity;
unset($delete_translations[$id]);
$total_count += count($entity->getTranslationLanguages());
}
elseif (!isset($delete_entities[$id])) {
$delete_translations[$id][] = $entity;
}
}
}
if ($delete_entities) {
$storage->delete($delete_entities);
$this->logger('content')->notice('Deleted @count @entity_type items.', [
'@count' => count($delete_entities),
'@entity_type' => $this->entityTypeId,
]);
}
if ($delete_translations) {
$count = 0;
/** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */
foreach ($delete_translations as $id => $translations) {
$entity = $entities[$id]->getUntranslated();
foreach ($translations as $translation) {
$entity->removeTranslation($translation->language()->getId());
}
$entity->save();
$count += count($translations);
}
if ($count) {
$total_count += $count;
$this->logger('content')->notice('Deleted @count @entity_type translations.', [
'@count' => $count,
'@entity_type' => $this->entityTypeId,
]);
}
}
if ($total_count) {
drupal_set_message($this->formatPlural($total_count, 'Deleted 1 item.', 'Deleted @count items.'));
}
$this->tempStore->delete($this->currentUser->id());
$form_state->setRedirectUrl($this->getCancelUrl());
}
}
<?php
/**
* @file
* Contains \Drupal\entity\Plugin\Action\DeleteAction.
*/
namespace Drupal\entity\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Redirects to an entity deletion form.
*
* @Action(
* id = "entity_delete_action",
* label = @Translation("Delete entity"),
* deriver = "Drupal\entity\Plugin\Action\Derivative\DeleteActionDeriver",
* )
*/
class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface {
/**
* The tempstore object.
*
* @var \Drupal\user\SharedTempStore
*/
protected $tempStore;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new DeleteAction 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\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param 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('entity_delete_multiple_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('user.private_tempstore'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
$selection = [];
foreach ($entities as $entity) {
$langcode = $entity->language()->getId();
$selection[$entity->id()][$langcode] = $langcode;
}
$this->tempStore->set($this->currentUser->id(), $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);
}
}
<?php
/**
* @file
* Contains \Drupal\entity\Plugin\Action\Derivative\DeleteActionDeriver.
*/
namespace Drupal\entity\Plugin\Action\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a delete action for each content entity type.
*/
class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new DeleteActionDeriver object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static($container->get('entity_type.manager'));
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
if (empty($this->derivatives)) {
$definitions = [];
foreach ($this->getParticipatingEntityTypes() as $entity_type_id => $entity_type) {
$definition = $base_plugin_definition;
$definition['label'] = t('Delete @entity_type', ['@entity_type' => $entity_type->getLowercaseLabel()]);
$definition['type'] = $entity_type_id;
$definition['confirm_form_route_name'] = 'entity.' . $entity_type_id . '.delete_multiple_form';
$definitions[$entity_type_id] = $definition;
}
$this->derivatives = $definitions;
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
/**
* Gets a list of participating entity types.
*
* The list consists of all content entity types with a delete-multiple-form
* link template.
*
* @return \Drupal\Core\Entity\EntityTypeInterface[]
* The participating entity types, keyed by entity type id.
*/
protected function getParticipatingEntityTypes() {
$entity_types = $this->entityTypeManager->getDefinitions();
$entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
return $entity_type->isSubclassOf(ContentEntityInterface::class) && $entity_type->hasLinkTemplate('delete-multiple-form');
});
return $entity_types;
}
}
<?php
/**
* @file
* Contains \Drupal\entity\Routing\DeleteMultipleRouteProvider.
*/
namespace Drupal\entity\Routing;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides the HTML route for deleting multiple entities.
*/
class DeleteMultipleRouteProvider implements EntityRouteProviderInterface {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$routes = new RouteCollection();
if ($route = $this->deleteMultipleFormRoute($entity_type)) {
$routes->add('entity.' . $entity_type->id() . '.delete_multiple_form', $route);
}
return $routes;
}
/**
* 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 deleteMultipleFormRoute(EntityTypeInterface $entity_type) {
if ($entity_type->hasLinkTemplate('delete-multiple-form')) {
$route = new Route($entity_type->getLinkTemplate('delete-multiple-form'));
$route->setDefault('_form', '\Drupal\entity\Form\DeleteMultiple');
$route->setDefault('entity_type_id', $entity_type->id());
$route->setRequirement('_permission', $entity_type->getAdminPermission());
return $route;
}
}
}
entity_module_test.entity_test_enhanced_bundle.*:
type: config_entity
label: 'Entity test with enhancments - Bundle'
label: 'Entity test with enhancements - Bundle'
mapping:
id:
type: string
......
entity.entity_test_enhanced.collection:
path: '/entity_test_enhanced'
defaults:
_entity_list: 'entity_test_enhanced'
_title: 'Entity test with enhancements'
requirements:
_permission: 'administer entity_test_enhanced'
......@@ -30,7 +30,9 @@ use Drupal\entity\Revision\RevisionableContentEntityBase;
* "html" = "\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* "revision" = "\Drupal\entity\Routing\RevisionRouteProvider",
* "create" = "\Drupal\entity\Routing\CreateHtmlRouteProvider",
* "delete-multiple" = "\Drupal\entity\Routing\DeleteMultipleRouteProvider",
* },
* "list_builder" = "\Drupal\Core\Entity\EntityListBuilder",
* },
* base_table = "entity_test_enhanced",
* data_table = "entity_test_enhanced_field_data",
......@@ -49,6 +51,8 @@ use Drupal\entity\Revision\RevisionableContentEntityBase;
* "add-page" = "/entity_test_enhanced/add",
* "add-form" = "/entity_test_enhanced/add/{type}",
* "canonical" = "/entity_test_enhanced/{entity_test_enhanced}",
* "collection" = "/entity_test_enhanced",
* "delete-multiple-form" = "/entity_test_enhanced/delete",
* "revision" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/view",
* "revision-revert-form" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/revert",
* "version-history" = "/entity_test_enhanced/{entity_test_enhanced}/revisions",
......
<?php
/**
* @file
* Contains \Drupal\Tests\entity\Functional\DeleteMultipleFormTest.
*/
namespace Drupal\Tests\entity\Functional;
use Drupal\entity_module_test\Entity\EnhancedEntity;
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
use Drupal\simpletest\BrowserTestBase;
/**
* Tests the delete multiple confirmation form.
*
* @group entity
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class DeleteMultipleFormTest extends BrowserTestBase {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface;
*/
protected $account;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['entity_module_test', 'user', 'entity'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
EnhancedEntityBundle::create([
'id' => 'default',
'label' => 'Default',
])->save();
$this->account = $this->drupalCreateUser(['administer entity_test_enhanced']);
$this->drupalLogin($this->account);
}
/**
* Tests the add page.
*/
public function testForm() {
$entities = [];
$selection = [];
for ($i = 0; $i < 2; $i++) {
$entity = EnhancedEntity::create([
'type' => 'default',
]);
$entity->save();
$entities[$entity->id()] = $entity;
$langcode = $entity->language()->getId();
$selection[$entity->id()][$langcode] = $langcode;
}
// Add the selection to the tempstore just like DeleteAction would.
$tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
$tempstore->set($this->account->id(), $selection);
$this->drupalGet('/entity_test_enhanced/delete');
$assert = $this->assertSession();
$assert->statusCodeEquals(200);
$assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these items?');
$delete_button = $this->getSession()->getPage()->findButton('Delete');
$delete_button->click();
$assert = $this->assertSession();
$assert->addressEquals('/entity_test_enhanced');
$assert->responseContains('Deleted 2 items.');
\Drupal::entityTypeManager()->getStorage('entity_test_enhanced')->resetCache();
$remaining_entities = EnhancedEntity::loadMultiple(array_keys($selection));
$this->assertEmpty($remaining_entities);
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\entity\Kernel\DeleteActionTest.
*/
namespace Drupal\Tests\entity\Kernel;
use Drupal\entity\Plugin\Action\DeleteAction;
use Drupal\entity_module_test\Entity\EnhancedEntity;
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
use Drupal\system\Entity\Action;
use Drupal\user\Entity\User;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the delete entity action.
*/
class DeleteActionTest extends KernelTestBase {
/**
* The current user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
public static $modules = ['action', 'node', 'entity_module_test', 'entity',
'user', 'system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_enhanced');
$this->installSchema('system', ['key_value_expire', 'sequences']);
$bundle = EnhancedEntityBundle::create([
'id' => 'default',
'label' => 'Default',
]);
$bundle->save();
$this->user = User::create([
'name' => 'username',
'status' => 1,
]);
$this->user->save();
\Drupal::service('current_user')->setAccount($this->user);
}
public function testAction() {
/** @var \Drupal\system\ActionConfigEntityInterface $action */
$action = Action::create([
'id' => 'enhanced_entity_delete_action',
'label' => 'Delete enhanced entity',
'plugin' => 'entity_delete_action:entity_test_enhanced',
]);
$status = $action->save();
$this->assertEquals(SAVED_NEW, $status);
$this->assertInstanceOf(DeleteAction::class, $action->getPlugin());
$entities = [];
for ($i = 0; $i < 2; $i++) {
$entity = EnhancedEntity::create([
'type' => 'default',
]);
$entity->save();
$entities[$entity->id()] = $entity;
}
$action->execute($entities);
// Confirm that the entity ids and langcodes are now in the tempstore.
$tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
$selection = $tempstore->get($this->user->id());
$this->assertEquals(array_keys($entities), array_keys($selection));
$this->assertEquals([['en' => 'en'], ['en' => 'en']], array_values($selection));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment