Skip to content
Snippets Groups Projects
Commit 3434b008 authored by Andrei Mateescu's avatar Andrei Mateescu
Browse files

Add a UI for viewing deleted entities.

parent 68581687
No related branches found
No related tags found
No related merge requests found
<?php
namespace Drupal\trash\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\trash\TrashManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Defines a controller to list deleted entities.
*/
class TrashController extends ControllerBase implements ContainerInjectionInterface {
/**
* The trash manager.
*
* @var \Drupal\trash\TrashManagerInterface
*/
protected $trashManager;
/**
* Constructs a TrashController object.
*
* @param \Drupal\trash\TrashManagerInterface $trash_manager
* The trash manager.
*/
public function __construct(TrashManagerInterface $trash_manager) {
$this->trashManager = $trash_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('trash.manager')
);
}
/**
* Provides the trash listing page for any entity type.
*
* @param string|null $entity_type_id
* The ID of the entity type to render.
*
* @return array
* A render array.
*/
public function listing(string $entity_type_id = NULL) : array {
$enabled_entity_types = $this->trashManager->getEnabledEntityTypes();
if (empty($enabled_entity_types)) {
throw new NotFoundHttpException();
}
$entity_type_id = $entity_type_id ?: reset($enabled_entity_types);
if (!in_array($entity_type_id, $enabled_entity_types, TRUE)) {
throw new NotFoundHttpException();
}
$build = $this->trashManager->executeInTrashContext('inactive', function () use ($entity_type_id) {
return $this->entityTypeManager()->getListBuilder($entity_type_id)->render();
});
$build['#cache']['tags'][] = 'config:trash.settings';
return $build;
}
}
...@@ -9,26 +9,13 @@ namespace Drupal\trash\Entity\Query; ...@@ -9,26 +9,13 @@ namespace Drupal\trash\Entity\Query;
*/ */
trait QueryTrait { trait QueryTrait {
/**
* @todo Do we want to use the metadata instead to store this information?
*
* @var boolean
*/
protected $isDeleted = FALSE;
/**
* @see \Drupal\multiversion\Entity\Query\QueryInterface::isDeleted()
*/
public function isDeleted() { public function isDeleted() {
$this->isDeleted = TRUE; $this->addMetaData('trash', 'inactive');
return $this; return $this;
} }
/**
* @see \Drupal\multiversion\Entity\Query\QueryInterface::isNotDeleted()
*/
public function isNotDeleted() { public function isNotDeleted() {
$this->isDeleted = FALSE; $this->addMetaData('trash', 'active');
return $this; return $this;
} }
...@@ -38,7 +25,9 @@ trait QueryTrait { ...@@ -38,7 +25,9 @@ trait QueryTrait {
public function prepare() { public function prepare() {
parent::prepare(); parent::prepare();
if (!\Drupal::service('trash.manager')->isEntityTypeEnabled($this->entityType)) { /** @var \Drupal\trash\TrashManagerInterface $trash_manager */
$trash_manager = \Drupal::service('trash.manager');
if (!$trash_manager->shouldAlterQueries() || !$trash_manager->isEntityTypeEnabled($this->entityType)) {
return $this; return $this;
} }
...@@ -58,10 +47,14 @@ trait QueryTrait { ...@@ -58,10 +47,14 @@ trait QueryTrait {
} }
} }
if (!$this->getMetaData('trash')) {
$this->addMetaData('trash', $trash_manager->getTrashContext());
}
$show_deleted = $this->getMetaData('trash') === 'inactive';
if (!$skip_deleted_filter) { if (!$skip_deleted_filter) {
// @todo Do we need support for listing deleted and not deleted entities if ($show_deleted) {
// at the same time?
if ($this->isDeleted) {
$this->exists($deleted_key); $this->exists($deleted_key);
} }
else { else {
......
<?php
namespace Drupal\trash\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\trash\TrashManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local task definitions for trash-enabled entity types.
*/
class TrashLocalTasks extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The trash manager.
*
* @var \Drupal\trash\TrashManagerInterface
*/
protected $trashManager;
/**
* Creates a TrashLocalTasks object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\trash\TrashManagerInterface $trash_manager
* The trash manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, TrashManagerInterface $trash_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->trashManager = $trash_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('trash.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
$enabled_entity_types = $this->trashManager->getEnabledEntityTypes();
if (!$enabled_entity_types) {
return $this->derivatives;
}
$default_entity_type = reset($enabled_entity_types);
foreach ($enabled_entity_types as $entity_type_id) {
$this->derivatives[$entity_type_id] = $base_plugin_definition;
$this->derivatives[$entity_type_id]['title'] = $this->entityTypeManager->getDefinition($entity_type_id)->getCollectionLabel();
$this->derivatives[$entity_type_id]['route_parameters'] = ['entity_type_id' => $entity_type_id];
// @todo Look into why the cache tag is not taken into account when
// enabling or disabling an entity type for Trash.
$this->derivatives[$entity_type_id]['cache_tags'] = ['config:trash.settings'];
// Default task.
if ($default_entity_type === $entity_type_id) {
$this->derivatives[$entity_type_id]['route_name'] = $base_plugin_definition['parent_id'];
// Emulate default logic because without the base plugin id we can't
// change the base_route.
$this->derivatives[$entity_type_id]['weight'] = -10;
unset($this->derivatives[$entity_type_id]['route_parameters']);
}
}
return $this->derivatives;
}
}
...@@ -35,6 +35,13 @@ class TrashManager implements TrashManagerInterface { ...@@ -35,6 +35,13 @@ class TrashManager implements TrashManagerInterface {
*/ */
protected $configFactory; protected $configFactory;
/**
* One of 'active', 'inactive' or 'ignore'.
*
* @var string
*/
protected $trashContext = 'active';
/** /**
* Constructs a new TrashManager. * Constructs a new TrashManager.
* *
...@@ -66,6 +73,13 @@ class TrashManager implements TrashManagerInterface { ...@@ -66,6 +73,13 @@ class TrashManager implements TrashManagerInterface {
return in_array($entity_type->id(), $enabled_entity_types, TRUE); return in_array($entity_type->id(), $enabled_entity_types, TRUE);
} }
/**
* {@inheritdoc}
*/
public function getEnabledEntityTypes() : array {
return $this->configFactory->get('trash.settings')->get('enabled_entity_types') ?? [];
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -105,4 +119,32 @@ class TrashManager implements TrashManagerInterface { ...@@ -105,4 +119,32 @@ class TrashManager implements TrashManagerInterface {
} }
} }
/**
* {@inheritdoc}
*/
public function shouldAlterQueries() : bool {
$trash_context = $this->trashContext ?? 'active';
return $trash_context !== 'ignore';
}
/**
* {@inheritdoc}
*/
public function getTrashContext() : string {
return $this->trashContext ?? 'active';
}
/**
* {@inheritdoc}
*/
public function executeInTrashContext($context, callable $function) {
assert(in_array($context, ['active', 'inactive', 'ignore'], TRUE));
$this->trashContext = $context;
$result = $function();
unset($this->trashContext);
return $result;
}
} }
...@@ -29,6 +29,13 @@ interface TrashManagerInterface { ...@@ -29,6 +29,13 @@ interface TrashManagerInterface {
*/ */
public function isEntityTypeEnabled(EntityTypeInterface $entity_type) : bool; public function isEntityTypeEnabled(EntityTypeInterface $entity_type) : bool;
/**
* Gets the IDs of all entity types which can use the Trash.
*
* @return array
*/
public function getEnabledEntityTypes() : array;
/** /**
* Enables Trash integration for an entity type. * Enables Trash integration for an entity type.
* *
...@@ -49,4 +56,33 @@ interface TrashManagerInterface { ...@@ -49,4 +56,33 @@ interface TrashManagerInterface {
*/ */
public function disableEntityType(EntityTypeInterface $entity_type); public function disableEntityType(EntityTypeInterface $entity_type);
/**
* Determines whether entity and views queries should be altered.
*
* @return bool
* TRUE whether Trash should alter entity and views queries.
*/
public function shouldAlterQueries() : bool;
/**
* Determines the current trash context.
*
* @return string
* One of 'active', 'inactive' or 'ignore'.
*/
public function getTrashContext() : string;
/**
* Executes the given callback function in a specific trash context.
*
* @param string $context
* One of 'active', 'inactive' or 'ignore'.
* @param callable $function
* The callback to be executed.
*
* @return mixed
* The callable's return value.
*/
public function executeInTrashContext($context, callable $function);
} }
...@@ -29,11 +29,11 @@ class ViewsQueryAlter implements ContainerInjectionInterface { ...@@ -29,11 +29,11 @@ class ViewsQueryAlter implements ContainerInjectionInterface {
protected $entityFieldManager; protected $entityFieldManager;
/** /**
* The workspace manager service. * The trash manager service.
* *
* @var \Drupal\workspaces\WorkspaceManagerInterface * @var \Drupal\trash\TrashManagerInterface
*/ */
protected $workspaceManager; protected $trashManager;
/** /**
* The views data. * The views data.
...@@ -49,12 +49,15 @@ class ViewsQueryAlter implements ContainerInjectionInterface { ...@@ -49,12 +49,15 @@ class ViewsQueryAlter implements ContainerInjectionInterface {
* The entity type manager service. * The entity type manager service.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager. * The entity field manager.
* @param \Drupal\trash\TrashManagerInterface $trash_manager
* The trash manager.
* @param \Drupal\views\ViewsData $views_data * @param \Drupal\views\ViewsData $views_data
* The views data. * The views data.
*/ */
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ViewsData $views_data) { public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, TrashManagerInterface $trash_manager, ViewsData $views_data) {
$this->entityTypeManager = $entity_type_manager; $this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager; $this->entityFieldManager = $entity_field_manager;
$this->trashManager = $trash_manager;
$this->viewsData = $views_data; $this->viewsData = $views_data;
} }
...@@ -65,6 +68,7 @@ class ViewsQueryAlter implements ContainerInjectionInterface { ...@@ -65,6 +68,7 @@ class ViewsQueryAlter implements ContainerInjectionInterface {
return new static( return new static(
$container->get('entity_type.manager'), $container->get('entity_type.manager'),
$container->get('entity_field.manager'), $container->get('entity_field.manager'),
$container->get('trash.manager'),
$container->get('views.views_data') $container->get('views.views_data')
); );
} }
...@@ -76,7 +80,7 @@ class ViewsQueryAlter implements ContainerInjectionInterface { ...@@ -76,7 +80,7 @@ class ViewsQueryAlter implements ContainerInjectionInterface {
*/ */
public function alterQuery(QueryPluginBase $query) { public function alterQuery(QueryPluginBase $query) {
// Don't alter any non-sql views queries. // Don't alter any non-sql views queries.
if (!$query instanceof Sql) { if (!$query instanceof Sql || !$this->trashManager->shouldAlterQueries()) {
return; return;
} }
...@@ -94,7 +98,7 @@ class ViewsQueryAlter implements ContainerInjectionInterface { ...@@ -94,7 +98,7 @@ class ViewsQueryAlter implements ContainerInjectionInterface {
$entity_type_definitions = $this->entityTypeManager->getDefinitions(); $entity_type_definitions = $this->entityTypeManager->getDefinitions();
foreach ($entity_type_ids as $entity_type_id) { foreach ($entity_type_ids as $entity_type_id) {
if (\Drupal::service('trash.manager')->isEntityTypeEnabled($entity_type_definitions[$entity_type_id])) { if ($this->trashManager->isEntityTypeEnabled($entity_type_definitions[$entity_type_id])) {
$this->alterQueryForEntityType($query, $entity_type_definitions[$entity_type_id]); $this->alterQueryForEntityType($query, $entity_type_definitions[$entity_type_id]);
} }
} }
......
name: Trash name: Trash
type: module type: module
description: 'Provides the ability to soft-delete content entities.' description: 'Provides the ability to soft-delete content entities.'
core_version_requirement: ^8.8 || ^9 core_version_requirement: ^9
configure: trash.settings.form
trash.admin_content_trash:
title: 'Trash'
route_name: trash.admin_content_trash
base_route: system.admin_content
weight: 50
cache_tags:
- config:trash.settings
trash.admin_content_trash_entity_type:
title: 'Trash'
route_name: trash.admin_content_trash_entity_type
parent_id: trash.admin_content_trash
deriver: \Drupal\trash\Plugin\Derivative\TrashLocalTasks
administer trash: administer trash:
title: 'Administer trash' title: 'Administer trash'
description: 'Allows a user to configure which entity types can use the Trash functionality.' description: 'Allows a user to configure which entity types can use the Trash.'
access trash:
title: 'View deleted content'
description: 'Allows a user to view deleted content from the Trash.'
...@@ -5,3 +5,19 @@ trash.settings.form: ...@@ -5,3 +5,19 @@ trash.settings.form:
_title: 'Trash' _title: 'Trash'
requirements: requirements:
_permission: 'administer trash' _permission: 'administer trash'
trash.admin_content_trash:
path: '/admin/content/trash'
defaults:
_controller: '\Drupal\trash\Controller\TrashController::listing'
_title: 'Trash'
requirements:
_permission: 'access trash'
trash.admin_content_trash_entity_type:
path: '/admin/content/trash/{entity_type_id}'
defaults:
_controller: '\Drupal\trash\Controller\TrashController::listing'
_title: 'Trash'
requirements:
_permission: 'access trash'
...@@ -9,11 +9,14 @@ services: ...@@ -9,11 +9,14 @@ services:
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
# @todo Add pgsql entity query service and run tests with that db type.
trash.entity.query.sql: trash.entity.query.sql:
class: Drupal\trash\Entity\Query\Sql\QueryFactory class: Drupal\trash\Entity\Query\Sql\QueryFactory
public: false public: false
decorates: entity.query.sql decorates: entity.query.sql
arguments: ['@database'] arguments: ['@database']
tags:
- { name: backend_overridable }
trash.entity_type.manager: trash.entity_type.manager:
class: Drupal\trash\TrashEntityTypeManager class: Drupal\trash\TrashEntityTypeManager
......
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