Move CSV export action logic to a confirmation form.

This commit is contained in:
Michael Stenta 2024-01-22 15:39:47 -05:00
parent 60d2357fbc
commit 4e22c5fd83
5 changed files with 369 additions and 84 deletions

View File

@ -0,0 +1,27 @@
<?php
/**
* @file
* The farmOS Export CSV module.
*/
use Drupal\farm_export_csv\Form\EntityCsvActionForm;
use Drupal\farm_export_csv\Routing\EntityCsvActionRouteProvider;
/**
* Implements hook_entity_type_build().
*/
function farm_export_csv_entity_type_build(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
// Enable the entity CSV export action on assets and logs.
foreach (['asset', 'log'] as $entity_type) {
if (!empty($entity_types[$entity_type])) {
$route_providers = $entity_types[$entity_type]->getRouteProviderClasses();
$route_providers['csv'] = EntityCsvActionRouteProvider::class;
$entity_types[$entity_type]->setHandlerClass('route_provider', $route_providers);
$entity_types[$entity_type]->setLinkTemplate('csv-action-form', '/' . $entity_type . '/csv');
$entity_types[$entity_type]->setFormClass('csv-action-form', EntityCsvActionForm::class);
}
}
}

View File

@ -0,0 +1,276 @@
<?php
namespace Drupal\farm_export_csv\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Drupal\file\FileRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Provides an entity CSV action form.
*
* @see \Drupal\farm_export_csv\Plugin\Action\EntityCsv
* @see \Drupal\Core\Entity\Form\DeleteMultipleForm
*/
class EntityCsvActionForm extends ConfirmFormBase {
/**
* The tempstore factory.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The serializer service.
*
* @var \Symfony\Component\Serializer\SerializerInterface
*/
protected $serializer;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The default file scheme.
*
* @var string
*/
protected $defaultFileScheme;
/**
* The file repository service.
*
* @var \Drupal\file\FileRepositoryInterface
*/
protected $fileRepository;
/**
* The file URL generator.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The entities to export.
*
* @var \Drupal\Core\Entity\EntityInterface[]
*/
protected $entities;
/**
* Constructs an EntityCsvActionForm form object.
*
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The serializer service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\file\FileRepositoryInterface $file_repository
* The file repository service.
* @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
* The file URL generator.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, SerializerInterface $serializer, FileSystemInterface $file_system, ConfigFactoryInterface $config_factory, FileRepositoryInterface $file_repository, FileUrlGeneratorInterface $file_url_generator, AccountInterface $user) {
$this->tempStore = $temp_store_factory->get('entity_csv_confirm');
$this->entityTypeManager = $entity_type_manager;
$this->serializer = $serializer;
$this->fileSystem = $file_system;
$this->defaultFileScheme = $config_factory->get('system.file')->get('default_scheme') ?? 'public';
$this->fileRepository = $file_repository;
$this->fileUrlGenerator = $file_url_generator;
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('tempstore.private'),
$container->get('entity_type.manager'),
$container->get('serializer'),
$container->get('file_system'),
$container->get('config.factory'),
$container->get('file.repository'),
$container->get('file_url_generator'),
$container->get('current_user'),
);
}
/**
* {@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 . '_csv_action_confirm_form';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->formatPlural(count($this->entities), 'Are you sure you want to export a CSV of this @item?', 'Are you sure you want to export a CSV of these @items?', [
'@item' => $this->entityType->getSingularLabel(),
'@items' => $this->entityType->getPluralLabel(),
]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
if ($this->entityType->hasLinkTemplate('collection')) {
return new Url('entity.' . $this->entityType->id() . '.collection');
}
else {
return new Url('<front>');
}
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return '';
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Export');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
// If we don't have an entity type or list of entities, redirect.
$this->entityType = $this->entityTypeManager->getDefinition($entity_type_id);
$this->entities = $this->tempStore->get($this->user->id() . ':' . $entity_type_id);
if (empty($entity_type_id) || empty($this->entities)) {
return new RedirectResponse($this->getCancelUrl()
->setAbsolute()
->toString());
}
// Delegate to the parent method.
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Filter out entities the user doesn't have access to.
$inaccessible_entities = [];
$accessible_entities = [];
foreach ($this->entities as $entity) {
if (!$entity->access('view', $this->currentUser())) {
$inaccessible_entities[] = $entity;
continue;
}
$accessible_entities[] = $entity;
}
// Serialize the entities with the csv format.
$output = $this->serializer->serialize($accessible_entities, 'csv');
// Prepare the file directory.
$directory = $this->defaultFileScheme . '://csv';
$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
// Create the file.
$filename = 'csv_export-' . date('c') . '.csv';
$destination = "$directory/$filename";
try {
$file = $this->fileRepository->writeData($output, $destination);
}
// If file creation failed, bail with a warning.
catch (\Exception $e) {
$this->messenger()->addWarning($this->t('Could not create file.'));
return;
}
// Make the file temporary.
$file->status = 0;
$file->save();
// Add warning message for inaccessible entities.
if (!empty($inaccessible_entities)) {
$inaccessible_count = count($inaccessible_entities);
$this->messenger()->addWarning($this->formatPlural($inaccessible_count, 'Could not export @count @item because you do not have the necessary permissions.', 'Could not export @count @items because you do not have the necessary permissions.', [
'@item' => $this->entityType->getSingularLabel(),
'@items' => $this->entityType->getPluralLabel(),
]));
}
// Add confirmation message.
if (count($accessible_entities)) {
$this->messenger()->addStatus($this->formatPlural(count($accessible_entities), 'Exported @count @item.', 'Exported @count @items', [
'@item' => $this->entityType->getSingularLabel(),
'@items' => $this->entityType->getPluralLabel(),
]));
}
// Show a link to the file.
$url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
$this->messenger()->addMessage($this->t('CSV file created: <a href=":url">%filename</a>', [
':url' => $url,
'%filename' => $file->label(),
]));
$this->tempStore->delete($this->currentUser()->id() . ':' . $this->entityType->id());
$form_state->setRedirectUrl($this->getCancelUrl());
}
}

View File

@ -22,6 +22,7 @@ class EntityCsvDeriver extends EntityActionDeriverBase {
$definition = $base_plugin_definition;
$definition['type'] = $entity_type_id;
$definition['label'] = $this->t('Export @entity_type CSV', ['@entity_type' => $entity_type->getSingularLabel()]);
$definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.csv_form';
$definitions[$entity_type_id] = $definition;
}
$this->derivatives = $definitions;

View File

@ -3,14 +3,10 @@
namespace Drupal\farm_export_csv\Plugin\Action;
use Drupal\Core\Action\Plugin\Action\EntityActionBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\file\FileRepositoryInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Action that exports a CSV file of entities.
@ -24,39 +20,18 @@ use Symfony\Component\Serializer\SerializerInterface;
class EntityCsv extends EntityActionBase {
/**
* The serializer service.
* The tempstore object.
*
* @var \Symfony\Component\Serializer\SerializerInterface
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $serializer;
protected $tempStore;
/**
* The file system service.
* The current user.
*
* @var \Drupal\Core\File\FileSystemInterface
* @var \Drupal\Core\Session\AccountInterface
*/
protected $fileSystem;
/**
* The default file scheme.
*
* @var string
*/
protected $defaultFileScheme;
/**
* The file repository service.
*
* @var \Drupal\file\FileRepositoryInterface
*/
protected $fileRepository;
/**
* The file URL generator.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
protected $currentUser;
/**
* Constructs a new EntityCsv object.
@ -69,24 +44,15 @@ class EntityCsv extends EntityActionBase {
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The serializer service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\file\FileRepositoryInterface $file_repository
* The file repository service.
* @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
* The file URL generator.
* @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, EntityTypeManagerInterface $entity_type_manager, SerializerInterface $serializer, FileSystemInterface $file_system, ConfigFactoryInterface $config_factory, FileRepositoryInterface $file_repository, FileUrlGeneratorInterface $file_url_generator) {
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);
$this->serializer = $serializer;
$this->fileSystem = $file_system;
$this->defaultFileScheme = $config_factory->get('system.file')->get('default_scheme') ?? 'public';
$this->fileRepository = $file_repository;
$this->fileUrlGenerator = $file_url_generator;
$this->tempStore = $temp_store_factory->get('entity_csv_confirm');
$this->currentUser = $current_user;
}
/**
@ -98,11 +64,8 @@ class EntityCsv extends EntityActionBase {
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('serializer'),
$container->get('file_system'),
$container->get('config.factory'),
$container->get('file.repository'),
$container->get('file_url_generator'),
$container->get('tempstore.private'),
$container->get('current_user'),
);
}
@ -110,37 +73,8 @@ class EntityCsv extends EntityActionBase {
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
// Serialize the entities.
$output = $this->serializer->serialize($entities, 'csv');
// Prepare the file directory.
$directory = $this->defaultFileScheme . '://csv';
$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
// Create the file.
$filename = 'csv_export-' . date('c') . '.csv';
$destination = "$directory/$filename";
try {
$file = $this->fileRepository->writeData($output, $destination);
}
// If file creation failed, bail with a warning.
catch (\Exception $e) {
$this->messenger()->addWarning($this->t('Could not create file.'));
return;
}
// Make the file temporary.
$file->status = 0;
$file->save();
// Show a link to the file.
$url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
$this->messenger()->addMessage($this->t('CSV file created: <a href=":url">%filename</a>', [
':url' => $url,
'%filename' => $file->label(),
]));
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
$this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $entities);
}
/**

View File

@ -0,0 +1,47 @@
<?php
namespace Drupal\farm_export_csv\Routing;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides routes for the entity CSV export action.
*/
class EntityCsvActionRouteProvider implements EntityRouteProviderInterface {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$collection = new RouteCollection();
$entity_type_id = $entity_type->id();
if ($route = $this->getEntityCsvFormRoute($entity_type)) {
$collection->add("entity.$entity_type_id.csv_form", $route);
}
return $collection;
}
/**
* Gets the entity CSV export 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 getEntityCsvFormRoute(EntityTypeInterface $entity_type) {
if ($entity_type->hasLinkTemplate('csv-action-form')) {
$route = new Route($entity_type->getLinkTemplate('csv-action-form'));
$route->setDefault('_form', $entity_type->getFormClass('csv-action-form'));
$route->setDefault('entity_type_id', $entity_type->id());
$route->setRequirement('_user_is_logged_in', 'TRUE');
return $route;
}
}
}