From 259b4e24684fdf77ed5ee3d9b48b75e72273a29e Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Wed, 21 Nov 2012 09:48:44 -0800
Subject: [PATCH] Issue #1828410 by dawehner, damiankloip, tim.plunkett,
 stevector: Added Provide a bulk_form() element for actions.

---
 core/modules/action/action.views.inc          |  44 ++++++
 .../action/Plugin/views/field/BulkForm.php    | 127 +++++++++++++++
 .../lib/Drupal/action/Tests/BulkFormTest.php  |  83 ++++++++++
 .../action_bulk_test/action_bulk_test.info    |   8 +
 .../action_bulk_test/action_bulk_test.module  |   1 +
 .../config/views.view.test_bulk_form.yml      | 149 ++++++++++++++++++
 .../Drupal/views/Plugin/views/HandlerBase.php |  27 ++++
 core/modules/views/views.module               |  23 +--
 8 files changed, 453 insertions(+), 9 deletions(-)
 create mode 100644 core/modules/action/action.views.inc
 create mode 100644 core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php
 create mode 100644 core/modules/action/lib/Drupal/action/Tests/BulkFormTest.php
 create mode 100644 core/modules/action/tests/action_bulk_test/action_bulk_test.info
 create mode 100644 core/modules/action/tests/action_bulk_test/action_bulk_test.module
 create mode 100644 core/modules/action/tests/action_bulk_test/config/views.view.test_bulk_form.yml

diff --git a/core/modules/action/action.views.inc b/core/modules/action/action.views.inc
new file mode 100644
index 000000000000..847096bbbb93
--- /dev/null
+++ b/core/modules/action/action.views.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Provides views data and handlers for action.module.
+ *
+ * @ingroup views_module_handlers
+ */
+
+/**
+ * Implements hook_views_data().
+ *
+ * @todo hook_views_data() is used instead of hook_views_data_alter(), because
+ *   the alter hook doesn't load the *.views.inc automatically.
+ */
+function action_views_data() {
+  $data['views']['action_bulk_form'] = array(
+    'title' => t('Actions bulk form'),
+    'help' => t('Add a form element that lets you apply actions to multiple items.'),
+    'field' => array(
+      'id' => 'action_bulk_form',
+    ),
+  );
+
+  return $data;
+}
+
+/**
+ * Implements hook_views_form_substitutions().
+ */
+function action_views_form_substitutions() {
+  // Views check_plains the column label, so by doing it matches for the
+  // replacement.
+  $select_all_placeholder = check_plain('<!--action-bulk-form-select-all-->');
+  $select_all = array(
+    '#type' => 'checkbox',
+    '#default_value' => FALSE,
+    '#attributes' => array('class' => array('action-table-select-all')),
+  );
+
+  return array(
+    $select_all_placeholder => drupal_render($select_all),
+  );
+}
diff --git a/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php b/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php
new file mode 100644
index 000000000000..08d6c856e56a
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\action\Plugin\views\field\BulkForm.
+ */
+
+namespace Drupal\action\Plugin\views\field;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\views\Plugin\views\field\FieldPluginBase;
+
+/**
+ * Defines a simple bulk operation form element.
+ *
+ * @Plugin(
+ *   id = "action_bulk_form",
+ *   module = "action"
+ * )
+ */
+class BulkForm extends FieldPluginBase {
+
+  /**
+   * Overrides \Drupal\views\Plugin\views\Plugin\field\FieldPluginBase::render().
+   */
+  public function render($values) {
+    return '<!--form-item-' . $this->options['id'] . '--' . $this->view->row_index . '-->';
+  }
+
+  /**
+   * Overrides \Drupal\views\Plugin\views\Plugin\field\FieldPluginBase::pre_render().
+   */
+  public function pre_render(&$values) {
+    parent::pre_render($values);
+
+    // If the view is using a table style, provide a placeholder for a
+    // "select all" checkbox.
+    if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof \Drupal\views\Plugin\views\style\Table) {
+      // Add the tableselect css classes.
+      $this->options['element_label_class'] .= 'select-all';
+      // Hide the actual label of the field on the table header.
+      $this->options['label'] = '';
+    }
+  }
+
+  /**
+   * Implements \Drupal\views\Plugin\views\Plugin\field\FieldPluginBase::views_form().
+   */
+  public function views_form(&$form, &$form_state) {
+    // Add the tableselect javascript.
+    $form['#attached']['library'][] = array('system', 'drupal.tableselect');
+
+    // Render checkboxes for all rows.
+    foreach ($this->view->result as $row_index => $row) {
+      $entity_id = $this->get_value($row);
+
+      $form[$this->options['id']][$row_index] = array(
+        '#type' => 'checkbox',
+        '#default_value' => FALSE,
+      );
+    }
+
+    $form[$this->options['id']]['#tree'] = TRUE;
+
+    // Get all available actions.
+    $actions = action_get_all_actions();
+    $entity_type = $this->getEntityType();
+    // Filter actions by the entity type and build options for the form.
+    $actions = array_filter($actions, function($action) use ($entity_type) {
+      return $action['type'] == $entity_type && empty($action['configurable']);
+    });
+    $options = array_map(function($action) {
+      return $action['label'];
+    }, $actions);
+
+    $form['action'] = array(
+      '#type' => 'select',
+      '#title' => t('Action'),
+      '#options' => $options,
+      '#description' => t('Select the action you want to execute on the content entitites.'),
+    );
+
+    // Move the submit button beside the selection.
+    $form['actions']['#weight'] = 1;
+
+      // Replace the text with Update.
+    $form['actions']['submit']['#value'] = t('Update');
+
+    // Put the submit button both at the top and bottom.
+    $form['actions_bottom'] = $form['actions'];
+    $form['actions_bottom']['#weight'] = 100;
+  }
+
+  /**
+   * Implements \Drupal\views\Plugin\views\Plugin\field\FieldPluginBase::views_form_submit().
+   */
+  public function views_form_submit(&$form, &$form_state) {
+    if ($form_state['step'] == 'views_form_views_form') {
+      $action = $form_state['values']['action'];
+      $action = action_load($action);
+      $count = 0;
+
+      // Filter only selected checkboxes.
+      $selected = array_filter($form_state['values'][$this->options['id']]);
+
+      if (!empty($selected)) {
+        foreach (array_keys($selected) as $row_index) {
+          $entity = $this->get_entity($this->view->result[$row_index]);
+          actions_do($action->aid, $entity);
+          $entity->save();
+          $count++;
+        }
+      }
+
+      if ($count) {
+        drupal_set_message(t('%action action performed on %count item(s).', array('%action' => $action->label, '%count' => $count)));
+      }
+    }
+  }
+
+  /**
+   * Overrides \Drupal\views\Plugin\views\Plugin\field\FieldPluginBase::query().
+   */
+  public function query() {
+  }
+
+}
diff --git a/core/modules/action/lib/Drupal/action/Tests/BulkFormTest.php b/core/modules/action/lib/Drupal/action/Tests/BulkFormTest.php
new file mode 100644
index 000000000000..8891ed42dd6b
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/Tests/BulkFormTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\action\Tests\BulkFormTest.
+ */
+
+namespace Drupal\action\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the views bulk form test.
+ *
+ * @see \Drupal\action\Plugin\views\field\BulkForm
+ */
+class BulkFormTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('action_bulk_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Bulk form',
+      'description' => 'Tests the views bulk form test.',
+      'group' => 'Action',
+    );
+  }
+
+  /**
+   * Tests the bulk form.
+   */
+  public function testBulkForm() {
+    $nodes = array();
+    for ($i = 0; $i < 10; $i++) {
+      $nodes[] = $this->drupalCreateNode(array('sticky' => FALSE));
+    }
+
+    $this->drupalGet('test_bulk_form');
+
+    $this->assertFieldById('edit-action', NULL, 'The action select field appears.');
+
+    // Make sure a checkbox appears on all rows.
+    $edit = array();
+    for ($i = 0; $i < 10; $i++) {
+      $this->assertFieldById('edit-bulk-form-' . $i, NULL, format_string('The checkbox on row @row appears.', array('@row' => $i)));
+      $edit["bulk_form[$i]"] = TRUE;
+    }
+
+    // Set all nodes to sticky and check that.
+    $edit += array('action' => 'node_make_sticky_action');
+    $this->drupalPost(NULL, $edit, t('Update'));
+
+    foreach ($nodes as $node) {
+      $changed_node = node_load($node->id());
+      $this->assertTrue($changed_node->sticky, format_string('Node @nid got marked as sticky.', array('@nid' => $node->id())));
+    }
+
+    $this->assertText('Make content sticky action performed on 10 item(s).');
+
+    // Unpublish just one node.
+    $node = node_load($nodes[0]->id());
+    $this->assertTrue($node->status, 'The node is published.');
+
+    $edit = array('bulk_form[0]' => TRUE, 'action' => 'node_unpublish_action');
+    $this->drupalPost(NULL, $edit, t('Update'));
+
+    $this->assertText('Unpublish content action performed on 1 item(s).');
+
+    // Load the node again.
+    $node = node_load($node->id(), TRUE);
+    $this->assertFalse($node->status, 'A single node has been unpublished.');
+
+    // The second node should still be published.
+    $node = node_load($nodes[1]->id(), TRUE);
+    $this->assertTrue($node->status, 'An unchecked node is still published.');
+  }
+
+}
diff --git a/core/modules/action/tests/action_bulk_test/action_bulk_test.info b/core/modules/action/tests/action_bulk_test/action_bulk_test.info
new file mode 100644
index 000000000000..36983703e305
--- /dev/null
+++ b/core/modules/action/tests/action_bulk_test/action_bulk_test.info
@@ -0,0 +1,8 @@
+name = Action bulk form test
+description = Support module for action bulk form testing.
+package = Testing
+version = VERSION
+core = 8.x
+hidden = TRUE
+dependencies[] = action
+dependencies[] = views
diff --git a/core/modules/action/tests/action_bulk_test/action_bulk_test.module b/core/modules/action/tests/action_bulk_test/action_bulk_test.module
new file mode 100644
index 000000000000..b3d9bbc7f371
--- /dev/null
+++ b/core/modules/action/tests/action_bulk_test/action_bulk_test.module
@@ -0,0 +1 @@
+<?php
diff --git a/core/modules/action/tests/action_bulk_test/config/views.view.test_bulk_form.yml b/core/modules/action/tests/action_bulk_test/config/views.view.test_bulk_form.yml
new file mode 100644
index 000000000000..ea8f70126639
--- /dev/null
+++ b/core/modules/action/tests/action_bulk_test/config/views.view.test_bulk_form.yml
@@ -0,0 +1,149 @@
+base_table: node
+name: test_bulk_form
+description: ''
+tag: ''
+human_name: form
+core: 8.x
+api_version: '3.0'
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: ''
+    display_options:
+      access:
+        type: perm
+      cache:
+        type: none
+      query:
+        type: views_query
+      exposed_form:
+        type: basic
+      pager:
+        type: full
+        options:
+          items_per_page: '10'
+      style:
+        type: table
+        options:
+          grouping: {  }
+          row_class: ''
+          default_row_class: '1'
+          row_class_special: '1'
+          override: '1'
+          sticky: '0'
+          summary: ''
+          columns:
+            title: title
+            action_bulk_form: action_bulk_form
+          info:
+            title:
+              sortable: '0'
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: '0'
+              responsive: ''
+            bulk_form:
+              align: ''
+              separator: ''
+              empty_column: '0'
+              responsive: ''
+          default: '-1'
+          empty_table: '0'
+      row:
+        type: fields
+      fields:
+        title:
+          id: title
+          table: node
+          field: title
+          label: ''
+          alter:
+            alter_text: '0'
+            make_link: '0'
+            absolute: '0'
+            trim: '0'
+            word_boundary: '0'
+            ellipsis: '0'
+            strip_tags: '0'
+            html: '0'
+          hide_empty: '0'
+          empty_zero: '0'
+          link_to_node: '1'
+        bulk_form:
+          id: action_bulk_form
+          table: views
+          field: action_bulk_form
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: 'Bulk form'
+          exclude: '0'
+          alter:
+            alter_text: '0'
+            text: ''
+            make_link: '0'
+            path: ''
+            absolute: '0'
+            external: '0'
+            replace_spaces: '0'
+            path_case: none
+            trim_whitespace: '0'
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: '0'
+            max_length: ''
+            word_boundary: '1'
+            ellipsis: '1'
+            more_link: '0'
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: '0'
+            trim: '0'
+            preserve_tags: ''
+            html: '0'
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: '1'
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_empty: '0'
+          empty_zero: '0'
+          hide_alter_empty: '1'
+      filters:
+        status:
+          value: '1'
+          table: node
+          field: status
+          id: status
+          expose:
+            operator: '0'
+          group: '1'
+      sorts:
+        created:
+          id: created
+          table: node
+          field: created
+          order: DESC
+      title: form
+  page_1:
+    display_plugin: page
+    id: page_1
+    display_title: Page
+    position: ''
+    display_options:
+      path: test_bulk_form
+base_field: nid
+disabled: '0'
+module: views
+langcode: und
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php
index 0b897ea3f9b4..deaf56d761cf 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php
@@ -813,6 +813,33 @@ public static function getTableJoin($table, $base_table) {
     }
   }
 
+  /**
+   * Determines the entity type used by this handler.
+   *
+   * If this handler uses a relationship, the base class of the relationship is
+   * taken into account.
+   *
+   * @return string
+   *   The machine name of the entity type.
+   */
+  public function getEntityType() {
+    // If the user has configured a relationship on the handler take that into
+    // account.
+    if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
+      $views_data = views_fetch_data($this->view->relationship->table);
+    }
+    else {
+      $views_data = views_fetch_data($this->view->storage->get('base_table'));
+    }
+
+    if (isset($views_data['table']['entity type'])) {
+      return $views_data['table']['entity type'];
+    }
+    else {
+      throw new \Exception(format_string('No entity type for field @field on view @view', array('@field' => $this->options['id'], '@view' => $this->view->storage->get('name'))));
+    }
+  }
+
   /**
    * Breaks x,y,z and x+y+z into an array. Numeric only.
    *
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 72068d134a33..6347df67a942 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -1110,9 +1110,15 @@ function views_hook_info() {
   $hooks['views_data'] = array(
     'group' => 'views',
   );
+  $hooks['views_data_alter'] = array(
+    'group' => 'views',
+  );
   $hooks['views_query_substitutions'] = array(
     'group' => 'views',
   );
+  $hooks['views_form_substitutions'] = array(
+    'group' => 'views',
+  );
 
   return $hooks;
 }
@@ -1641,6 +1647,14 @@ function views_form_views_form($form, &$form_state, ViewExecutable $view, $outpu
     '#weight' => 50,
   );
 
+  $form['actions'] = array(
+    '#type' => 'actions',
+  );
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
   $substitutions = array();
   foreach ($view->field as $field_name => $field) {
     $form_element_name = $field_name;
@@ -1696,15 +1710,6 @@ function views_form_views_form($form, &$form_state, ViewExecutable $view, $outpu
     '#type' => 'value',
     '#value' => $substitutions,
   );
-  $form['actions'] = array(
-    '#type' => 'container',
-    '#attributes' => array('class' => array('form-actions')),
-    '#weight' => 100,
-  );
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
 
   return $form;
 }
-- 
GitLab