From c3bf8d3f08d44e5fc453d340d1410c08c0a599c5 Mon Sep 17 00:00:00 2001
From: florenttorregrosa <florenttorregrosa@2388214.no-reply.drupal.org>
Date: Fri, 3 Feb 2017 10:44:18 +0100
Subject: [PATCH] Issue #2839351 by Grimreaper: VBO for taxonomy term

---
 .../system.action.taxonomy_term_delete_action.yml  |   9 +
 .../taxonomy/config/schema/taxonomy.schema.yml     |   4 +
 .../config/schema/taxonomy.views.schema.yml        |   4 +
 .../src/Form/TaxonomyTermDeleteMultiple.php        | 147 +++++++++++++++
 .../src/Plugin/Action/DeleteTaxonomyTerm.php       |  93 ++++++++++
 .../Plugin/views/field/TaxonomyTermBulkForm.php    |  21 +++
 core/modules/taxonomy/src/TermViewsData.php        |   8 +
 .../taxonomy/src/Tests/Views/TaxonomyVboTest.php   |  68 +++++++
 core/modules/taxonomy/taxonomy.routing.yml         |   7 +
 .../test_views/views.view.taxonomy_vbo_test.yml    | 203 +++++++++++++++++++++
 10 files changed, 564 insertions(+)
 create mode 100644 core/modules/taxonomy/config/install/system.action.taxonomy_term_delete_action.yml
 create mode 100644 core/modules/taxonomy/src/Form/TaxonomyTermDeleteMultiple.php
 create mode 100644 core/modules/taxonomy/src/Plugin/Action/DeleteTaxonomyTerm.php
 create mode 100644 core/modules/taxonomy/src/Plugin/views/field/TaxonomyTermBulkForm.php
 create mode 100644 core/modules/taxonomy/src/Tests/Views/TaxonomyVboTest.php
 create mode 100644 core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.taxonomy_vbo_test.yml

diff --git a/core/modules/taxonomy/config/install/system.action.taxonomy_term_delete_action.yml b/core/modules/taxonomy/config/install/system.action.taxonomy_term_delete_action.yml
new file mode 100644
index 0000000..cbb2780
--- /dev/null
+++ b/core/modules/taxonomy/config/install/system.action.taxonomy_term_delete_action.yml
@@ -0,0 +1,9 @@
+id: taxonomy_term_delete_action
+label: 'Delete taxonomy term'
+status: true
+langcode: en
+type: taxonomy_term
+plugin: taxonomy_term_delete_action
+dependencies:
+  module:
+    - taxonomy
diff --git a/core/modules/taxonomy/config/schema/taxonomy.schema.yml b/core/modules/taxonomy/config/schema/taxonomy.schema.yml
index efa08e1..f0a92ae 100644
--- a/core/modules/taxonomy/config/schema/taxonomy.schema.yml
+++ b/core/modules/taxonomy/config/schema/taxonomy.schema.yml
@@ -37,3 +37,7 @@ taxonomy.vocabulary.*:
 field.formatter.settings.entity_reference_rss_category:
   type: mapping
   label: 'Taxonomy format settings'
+
+action.configuration.taxonomy_term_delete_action:
+  type: action_configuration_default
+  label: 'Delete taxonomy term configuration'
diff --git a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml
index 9ec9758..dc54f89 100644
--- a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml
+++ b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml
@@ -93,6 +93,10 @@ views.field.term_name:
       type: boolean
       label: 'Convert spaces in term names to hyphens'
 
+views.field.taxonomy_term_bulk_form:
+  type: views_field_bulk_form
+  label: 'Taxonomy term bulk form'
+
 views.field.taxonomy_index_tid:
   type: views_field
   label: 'Taxonomy language'
diff --git a/core/modules/taxonomy/src/Form/TaxonomyTermDeleteMultiple.php b/core/modules/taxonomy/src/Form/TaxonomyTermDeleteMultiple.php
new file mode 100644
index 0000000..188a46a
--- /dev/null
+++ b/core/modules/taxonomy/src/Form/TaxonomyTermDeleteMultiple.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Drupal\taxonomy\Form;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\user\PrivateTempStoreFactory;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a taxonomy term deletion confirmation form.
+ */
+class TaxonomyTermDeleteMultiple extends ConfirmFormBase {
+
+  /**
+   * The array of taxonomy terms to delete.
+   *
+   * @var array
+   */
+  protected $taxonomyTerms = array();
+
+  /**
+   * The tempstore factory.
+   *
+   * @var \Drupal\user\PrivateTempStoreFactory
+   */
+  protected $tempStoreFactory;
+
+  /**
+   * The taxonomy term storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $taxonomyTermStorage;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * Constructs a DeleteMultiple form object.
+   *
+   * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
+   *   The tempstore factory.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   */
+  public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user) {
+    $this->tempStoreFactory = $temp_store_factory;
+    $this->taxonomyTermStorage = $entity_type_manager->getStorage('taxonomy_term');
+    $this->currentUser = $current_user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('user.private_tempstore'),
+      $container->get('entity_type.manager'),
+      $container->get('current_user')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'taxonomy_term_multiple_delete_confirm';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->formatPlural(count($this->taxonomyTerms), '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.taxonomy_vocabulary.collection');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $this->taxonomyTerms = $this->tempStoreFactory->get('taxonomy_term_multiple_delete_confirm')
+      ->get($this->currentUser->id());
+    if (empty($this->taxonomyTerms)) {
+      return new RedirectResponse($this->getCancelUrl()
+        ->setAbsolute()
+        ->toString());
+    }
+
+    $items = [];
+    /** @var \Drupal\Core\Entity\EntityInterface $taxonomy_term */
+    foreach ($this->taxonomyTerms as $taxonomy_term) {
+      $items[] = array(
+        '#markup' => $taxonomy_term->label(),
+      );
+    }
+
+    $form['taxonomy_terms'] = array(
+      '#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->taxonomyTerms)) {
+      $this->taxonomyTermStorage->delete($this->taxonomyTerms);
+      $this->tempStoreFactory->get('taxonomy_term_multiple_delete_confirm')
+        ->delete($this->currentUser->id());
+      $count = count($this->taxonomyTerms);
+      $this->logger('taxonomy_term')
+        ->notice('Deleted @count posts.', array('@count' => $count));
+      drupal_set_message($this->formatPlural($count, 'Deleted 1 taxonomy term.', 'Deleted @count taxonomy terms.'));
+    }
+    $form_state->setRedirect('entity.taxonomy_vocabulary.collection');
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Plugin/Action/DeleteTaxonomyTerm.php b/core/modules/taxonomy/src/Plugin/Action/DeleteTaxonomyTerm.php
new file mode 100644
index 0000000..3cfa973
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/Action/DeleteTaxonomyTerm.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Drupal\taxonomy\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 a taxonomy term deletion form.
+ *
+ * @Action(
+ *   id = "taxonomy_term_delete_action",
+ *   label = @Translation("Delete taxonomy term"),
+ *   type = "taxonomy_term",
+ *   confirm_form_route_name = "taxonomy_term.multiple_delete_confirm"
+ * )
+ */
+class DeleteTaxonomyTerm extends ActionBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The tempstore factory.
+   *
+   * @var \Drupal\user\PrivateTempStoreFactory
+   */
+  protected $tempStoreFactory;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * Constructs a DeleteTaxonomyTerm 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->tempStoreFactory = $temp_store_factory;
+
+    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) {
+    $this->tempStoreFactory->get('taxonomy_term_multiple_delete_confirm')->set($this->currentUser->id(), $entities);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute($object = NULL) {
+    $this->executeMultiple(array($object));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\taxonomy\TermInterface $object */
+    return $object->access('delete', $account, $return_as_object);
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Plugin/views/field/TaxonomyTermBulkForm.php b/core/modules/taxonomy/src/Plugin/views/field/TaxonomyTermBulkForm.php
new file mode 100644
index 0000000..e7b01f4
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/views/field/TaxonomyTermBulkForm.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\taxonomy\Plugin\views\field;
+
+use Drupal\system\Plugin\views\field\BulkForm;
+
+/**
+ * Defines a taxonomy term operations bulk form element.
+ *
+ * @ViewsField("taxonomy_term_bulk_form")
+ */
+class TaxonomyTermBulkForm extends BulkForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function emptySelectedMessage() {
+    return $this->t('No term selected.');
+  }
+
+}
diff --git a/core/modules/taxonomy/src/TermViewsData.php b/core/modules/taxonomy/src/TermViewsData.php
index 89b2b55..ff7f0bc 100644
--- a/core/modules/taxonomy/src/TermViewsData.php
+++ b/core/modules/taxonomy/src/TermViewsData.php
@@ -133,6 +133,14 @@ public function getViewsData() {
       ),
     );
 
+    $data['taxonomy_term_data']['taxonomy_term_bulk_form'] = array(
+      'title' => $this->t('Taxonomy term operations bulk form'),
+      'help' => $this->t('Add a form element that lets you run operations on multiple taxonomy terms.'),
+      'field' => array(
+        'id' => 'taxonomy_term_bulk_form',
+      ),
+    );
+
     $data['taxonomy_index']['table']['group']  = $this->t('Taxonomy term');
 
     $data['taxonomy_index']['table']['join'] = array(
diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyVboTest.php b/core/modules/taxonomy/src/Tests/Views/TaxonomyVboTest.php
new file mode 100644
index 0000000..f512bda
--- /dev/null
+++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyVboTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\taxonomy\Tests\Views;
+
+use Drupal\Core\Url;
+
+/**
+ * Tests VBO actions functionality.
+ *
+ * @group taxonomy
+ */
+class TaxonomyVboTest extends TaxonomyTestBase {
+
+  /**
+   * Views used by this test.
+   *
+   * @var array
+   */
+  public static $testViews = array('taxonomy_vbo_test');
+
+  /**
+   * An admin user.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp($import_test_views = TRUE) {
+    parent::setUp($import_test_views = TRUE);
+
+    $this->adminUser = $this->drupalCreateUser(array('administer taxonomy'));
+  }
+
+  /**
+   * Tests VBO action with a delete action.
+   */
+  function testVboActions() {
+    // Log in with user with right permissions and test listing.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('taxonomy_vbo_test');
+
+    // Delete both taxonomy terms to check bulk form.
+    $edit = array(
+      'action' => 'taxonomy_term_delete_action',
+      'taxonomy_term_bulk_form[0]' => 1,
+      'taxonomy_term_bulk_form[1]' => 1,
+    );
+    $this->drupalPostForm('taxonomy_vbo_test', $edit, t('Apply to selected items'));
+    $this->assertUrl(Url::fromRoute('taxonomy_term.multiple_delete_confirm', array(), array(
+      'query' => array(
+        'destination' => Url::fromRoute('view.taxonomy_vbo_test.page_1')->toString(),
+      ),
+    )));
+    // Test cancel link.
+    $this->assertLinkByHref('taxonomy_vbo_test');
+    $this->clickLink(t('Cancel'));
+    $this->assertUrl('taxonomy_vbo_test');
+    // Test confirm to deletion.
+    $this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
+    $this->assertText('Are you sure you want to delete these items?');
+    $this->drupalPostForm(NULL, array(), t('Delete'));
+    $this->assertText('Deleted 2 taxonomy terms.');
+  }
+
+}
diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml
index 8a3bd1a..7c3c7ce 100644
--- a/core/modules/taxonomy/taxonomy.routing.yml
+++ b/core/modules/taxonomy/taxonomy.routing.yml
@@ -85,3 +85,10 @@ entity.taxonomy_term.canonical:
   requirements:
     _entity_access: 'taxonomy_term.view'
     taxonomy_term: \d+
+
+taxonomy_term.multiple_delete_confirm:
+  path: '/admin/structure/taxonomy/term/delete'
+  defaults:
+    _form: '\Drupal\taxonomy\Form\TaxonomyTermDeleteMultiple'
+  requirements:
+    _permission: 'administer taxonomy'
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.taxonomy_vbo_test.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.taxonomy_vbo_test.yml
new file mode 100644
index 0000000..6df8f35
--- /dev/null
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.taxonomy_vbo_test.yml
@@ -0,0 +1,203 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - taxonomy
+    - user
+id: taxonomy_vbo_test
+label: taxonomy_vbo_test
+module: views
+description: ''
+tag: ''
+base_table: taxonomy_term_field_data
+base_field: tid
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'access content'
+      cache:
+        type: tag
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: mini
+        options:
+          items_per_page: 10
+          offset: 0
+          id: 0
+          total_pages: null
+          expose:
+            items_per_page: false
+            items_per_page_label: 'Items per page'
+            items_per_page_options: '5, 10, 25, 50'
+            items_per_page_options_all: false
+            items_per_page_options_all_label: '- All -'
+            offset: false
+            offset_label: Offset
+          tags:
+            previous: ‹‹
+            next: ››
+      style:
+        type: table
+      row:
+        type: fields
+      fields:
+        taxonomy_term_bulk_form:
+          id: taxonomy_term_bulk_form
+          table: taxonomy_term_data
+          field: taxonomy_term_bulk_form
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: 'Taxonomy term operations bulk form'
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          entity_type: taxonomy_term
+          plugin_id: taxonomy_term_bulk_form
+        name:
+          id: name
+          table: taxonomy_term_field_data
+          field: name
+          entity_type: taxonomy_term
+          entity_field: name
+          alter:
+            alter_text: false
+            make_link: false
+            absolute: false
+            trim: false
+            word_boundary: false
+            ellipsis: false
+            strip_tags: false
+            html: false
+          hide_empty: false
+          empty_zero: false
+          type: string
+          settings:
+            link_to_entity: true
+          plugin_id: term_name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Name
+          exclude: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_alter_empty: true
+          click_sort_column: value
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          convert_spaces: false
+      filters: {  }
+      sorts: {  }
+      title: 'taxonomy_vbo_test'
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships: {  }
+      arguments: {  }
+      display_extenders: {  }
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url.query_args
+        - user.permissions
+      tags: {  }
+  page_1:
+    display_plugin: page
+    id: page_1
+    display_title: Page
+    position: 1
+    display_options:
+      display_extenders: {  }
+      path: taxonomy_vbo_test
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url.query_args
+        - user.permissions
+      tags: {  }
-- 
1.9.1

