diff --git a/core/modules/action/action.views.inc b/core/modules/action/action.views.inc new file mode 100644 index 00000000000..847096bbbb9 --- /dev/null +++ b/core/modules/action/action.views.inc @@ -0,0 +1,44 @@ + 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(''); + $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 00000000000..08d6c856e56 --- /dev/null +++ b/core/modules/action/lib/Drupal/action/Plugin/views/field/BulkForm.php @@ -0,0 +1,127 @@ +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 00000000000..8891ed42dd6 --- /dev/null +++ b/core/modules/action/lib/Drupal/action/Tests/BulkFormTest.php @@ -0,0 +1,83 @@ + '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 00000000000..36983703e30 --- /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 00000000000..b3d9bbc7f37 --- /dev/null +++ b/core/modules/action/tests/action_bulk_test/action_bulk_test.module @@ -0,0 +1 @@ +2.4-rc0)', ); - variable_set('dependencies', $dependencies); + state()->set('system_test.dependencies', $dependencies); $n = count($dependencies); for ($i = 0; $i < $n; $i++) { $this->drupalGet('admin/modules'); diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module index 4949aecff48..e833ba7b28c 100644 --- a/core/modules/system/tests/modules/system_test/system_test.module +++ b/core/modules/system/tests/modules/system_test/system_test.module @@ -247,11 +247,11 @@ function system_test_exit() { function system_test_system_info_alter(&$info, $file, $type) { // We need a static otherwise the last test will fail to alter common_test. static $test; - if (($dependencies = variable_get('dependencies', array())) || $test) { + if (($dependencies = state()->get('system_test.dependencies')) || $test) { if ($file->name == 'module_test') { $info['hidden'] = FALSE; $info['dependencies'][] = array_shift($dependencies); - variable_set('dependencies', $dependencies); + state()->set('system_test.dependencies', $dependencies); $test = TRUE; } if ($file->name == 'common_test') { 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 0b897ea3f9b..deaf56d761c 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 @@ abstract class HandlerBase extends PluginBase { } } + /** + * 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 72068d134a3..6347df67a94 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; }