diff --git a/composer.json b/composer.json index 16d38b0d150532a17623cba908af0f949d6cf4f8..347eb1dd44c819b23496d43c73f79e672734ebcf 100644 --- a/composer.json +++ b/composer.json @@ -184,7 +184,7 @@ "drupal/views_ajax_history": "1.5", "drupal/views_autocomplete_filters": "1.3", "drupal/views_bootstrap": "3.1", - "drupal/views_bulk_operations": "3.10", + "drupal/views_bulk_operations": "3.11", "drupal/views_fieldsets": "3.3", "drupal/views_infinite_scroll": "1.7", "drupal/views_slideshow": "4.4", diff --git a/composer.lock b/composer.lock index 24c83e01f3237d1d6c70dd813c2339f864bbb508..1569962c9e32ee65c617f65df893f52851f8a129 100644 --- a/composer.lock +++ b/composer.lock @@ -8521,17 +8521,17 @@ }, { "name": "drupal/views_bulk_operations", - "version": "3.10.0", + "version": "3.11.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/views_bulk_operations.git", - "reference": "8.x-3.10" + "reference": "8.x-3.11" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.10.zip", - "reference": "8.x-3.10", - "shasum": "e346c2a72fc9a1ae8af418e6a02076f52c0fcc7b" + "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.11.zip", + "reference": "8.x-3.11", + "shasum": "14519811dac1661957066a0de6c455228e634ce7" }, "require": { "drupal/core": "^8.8 || ^9" @@ -8545,8 +8545,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-3.10", - "datestamp": "1608795018", + "version": "8.x-3.11", + "datestamp": "1615996407", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" diff --git a/web/modules/views_bulk_operations/js/frontUi.js b/web/modules/views_bulk_operations/js/frontUi.js index 82013da155ed4deef854abd508b30ae0153846f8..c48e4428c077001e1eeea77a14a05450ef38ce87 100644 --- a/web/modules/views_bulk_operations/js/frontUi.js +++ b/web/modules/views_bulk_operations/js/frontUi.js @@ -86,7 +86,9 @@ } var $placeholder = this.$placeholder; + var $selectionInfo = this.$selectionInfo; var target_uri = drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + 'views-bulk-operations/ajax/' + this.view_id + '/' + this.display_id; + $.ajax(target_uri, { method: 'POST', data: { @@ -94,6 +96,7 @@ op: op }, success: function (data) { + $selectionInfo.html($(data.selection_info).html()); $placeholder.text(data.count); } }); @@ -122,6 +125,7 @@ var $multiSelectElement = $vboForm.find('.vbo-multipage-selector').first(); if ($multiSelectElement.length) { + Drupal.viewsBulkOperationsSelection.$selectionInfo = $multiSelectElement.find('.vbo-info-list-wrapper').first(); Drupal.viewsBulkOperationsSelection.$placeholder = $multiSelectElement.find('.placeholder').first(); Drupal.viewsBulkOperationsSelection.view_id = $multiSelectElement.attr('data-view-id'); Drupal.viewsBulkOperationsSelection.display_id = $multiSelectElement.attr('data-display-id'); @@ -162,9 +166,22 @@ if ($primarySelectAll.length) { $primarySelectAll.on('change', function (event) { var value = this.checked; + // Select / deselect all checkboxes in the view. + // If there are table select all elements, use that. + if (tableSelectAll.length) { + tableSelectAll.forEach(function (element) { + if (element.get(0).checked != value) { + element.click(); + } + }); + } + + // Also handle checkboxes that may still have different values. $vboForm.find('.views-field-views-bulk-operations-bulk-form input[type="checkbox"]').each(function () { - this.checked = value; + if (this.checked != value) { + $(this).click(); + } }); // Clear the selection information if exists. diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml index 3b5675c624840a62212724f42cff8e6be452c8b1..27a68f8f1f00ad357f9e86edc97dd949d0d3fbe4 100644 --- a/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml +++ b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml @@ -6,7 +6,7 @@ core_version_requirement: ^8 || ^9 dependencies: - drupal:views_bulk_operations -# Information added by Drupal.org packaging script on 2020-12-24 -version: '8.x-3.10' +# Information added by Drupal.org packaging script on 2021-03-17 +version: '8.x-3.11' project: 'views_bulk_operations' -datestamp: 1608795021 +datestamp: 1615996410 diff --git a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml index 2d2965100fc775258329b7ff2eae5911bf5a18de..7b4dfeb407847317bb465e299f4e1607013ef79c 100644 --- a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml +++ b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml @@ -6,7 +6,7 @@ core_version_requirement: ^8 || ^9 dependencies: - drupal:views_bulk_operations -# Information added by Drupal.org packaging script on 2020-12-24 -version: '8.x-3.10' +# Information added by Drupal.org packaging script on 2021-03-17 +version: '8.x-3.11' project: 'views_bulk_operations' -datestamp: 1608795021 +datestamp: 1615996410 diff --git a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php index 643ccdce938d0e85a69ad76d625d3b81173c8d2a..523557289501aebfa954dcd49b7a54aa0b97d00d 100644 --- a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php +++ b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php @@ -103,7 +103,7 @@ public function vboExecute( 'display-id' => 'default', 'args' => '', 'exposed' => '', - 'batch-size' => 100, + 'batch-size' => 10, 'configuration' => '', 'user-id' => 1, ] @@ -144,6 +144,9 @@ public function vboExecute( 'exposed_input' => $options['exposed'], 'batch_size' => $options['batch-size'], 'relationship_id' => 'none', + // We set the clear_on_exposed parameter to true, otherwise with empty + // selection exposed filters are not taken into account. + 'clear_on_exposed' => TRUE, ]; // Login as superuser, as drush 9 doesn't support the @@ -179,7 +182,7 @@ public function vboExecute( // Get total rows count. $this->viewData->init($view, $view->getDisplay(), $vbo_data['relationship_id']); - $vbo_data['total_results'] = $this->viewData->getTotalResults(); + $vbo_data['total_results'] = $this->viewData->getTotalResults($vbo_data['clear_on_exposed']); // Get action definition and check if action ID is correct. $action_definition = $this->actionManager->getDefinition($action_id); diff --git a/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php b/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php index 3020241cd3ce49f64417b89084239b5d1c6d34ce..ef00fe3599dcf1b5c6c501116918a16977cb3013 100644 --- a/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php +++ b/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php @@ -8,6 +8,7 @@ use Drupal\views_bulk_operations\Form\ViewsBulkOperationsFormTrait; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface; +use Drupal\Core\Render\RendererInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -34,6 +35,13 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI */ protected $actionProcessor; + /** + * The Renderer service object. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + /** * Constructs a new controller object. * @@ -41,13 +49,17 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI * Private temporary storage factory. * @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface $actionProcessor * Views Bulk Operations action processor. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The Renderer service object. */ public function __construct( PrivateTempStoreFactory $tempStoreFactory, - ViewsBulkOperationsActionProcessorInterface $actionProcessor + ViewsBulkOperationsActionProcessorInterface $actionProcessor, + RendererInterface $renderer ) { $this->tempStoreFactory = $tempStoreFactory; $this->actionProcessor = $actionProcessor; + $this->renderer = $renderer; } /** @@ -56,7 +68,8 @@ public function __construct( public static function create(ContainerInterface $container) { return new static( $container->get('tempstore.private'), - $container->get('views_bulk_operations.processor') + $container->get('views_bulk_operations.processor'), + $container->get('renderer') ); } @@ -95,8 +108,9 @@ public function execute($view_id, $display_id) { * The request object. */ public function updateSelection($view_id, $display_id, Request $request) { - $view_data = $this->getTempstoreData($view_id, $display_id); - if (empty($view_data)) { + $response = []; + $tempstore_data = $this->getTempstoreData($view_id, $display_id); + if (empty($tempstore_data)) { throw new NotFoundHttpException(); } @@ -104,7 +118,7 @@ public function updateSelection($view_id, $display_id, Request $request) { $op = $request->request->get('op', 'check'); // Reverse operation when in exclude mode. - if (!empty($view_data['exclude_mode'])) { + if (!empty($tempstore_data['exclude_mode'])) { if ($op === 'add') { $op = 'remove'; } @@ -116,37 +130,43 @@ public function updateSelection($view_id, $display_id, Request $request) { switch ($op) { case 'add': foreach ($list as $bulkFormKey) { - if (!isset($view_data['list'][$bulkFormKey])) { - $view_data['list'][$bulkFormKey] = $this->getListItem($bulkFormKey); + if (!isset($tempstore_data['list'][$bulkFormKey])) { + $tempstore_data['list'][$bulkFormKey] = $this->getListItem($bulkFormKey); } } break; case 'remove': foreach ($list as $bulkFormKey) { - if (isset($view_data['list'][$bulkFormKey])) { - unset($view_data['list'][$bulkFormKey]); + if (isset($tempstore_data['list'][$bulkFormKey])) { + unset($tempstore_data['list'][$bulkFormKey]); } } break; case 'method_include': - unset($view_data['exclude_mode']); - $view_data['list'] = []; + unset($tempstore_data['exclude_mode']); + $tempstore_data['list'] = []; break; case 'method_exclude': - $view_data['exclude_mode'] = TRUE; - $view_data['list'] = []; + $tempstore_data['exclude_mode'] = TRUE; + $tempstore_data['list'] = []; break; } - $this->setTempstoreData($view_data); + $this->setTempstoreData($tempstore_data); + + $count = empty($tempstore_data['exclude_mode']) ? count($tempstore_data['list']) : $tempstore_data['total_results'] - count($tempstore_data['list']); - $count = empty($view_data['exclude_mode']) ? count($view_data['list']) : $view_data['total_results'] - count($view_data['list']); + $selection_info_renderable = $this->getMultipageList($tempstore_data); + $response_data = [ + 'count' => $count, + 'selection_info' => $this->renderer->renderRoot($selection_info_renderable), + ]; $response = new AjaxResponse(); - $response->setData(['count' => $count]); + $response->setData($response_data); return $response; } diff --git a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php index a5d4972b2f3def700dd5b6dd98de650413e445d7..a5469c4ba3646b5ffec1de694c913ce4efb7d431 100644 --- a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php +++ b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php @@ -143,12 +143,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $form_data['configuration'] = $form_state->getValues(); } - $definition = $this->actionManager->getDefinition($form_data['action_id']); - if (!empty($definition['confirm_form_route_name'])) { + if (!empty($form_data['confirm_route'])) { // Update tempStore data. $this->setTempstoreData($form_data, $form_data['view_id'], $form_data['display_id']); // Go to the confirm route. - $form_state->setRedirect($definition['confirm_form_route_name'], [ + $form_state->setRedirect($form_data['confirm_route'], [ 'view_id' => $form_data['view_id'], 'display_id' => $form_data['display_id'], ]); diff --git a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php index 5fe4260896908a5cfb0f4c21daac2267df09ae2a..01bfbbe3ed713e10a9210c7cb4eb673d57fe6ea5 100644 --- a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php +++ b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php @@ -70,6 +70,37 @@ protected function addListData(&$form_data) { } } + /** + * Get the selection info title. + * + * @param array $tempstore_data + * VBO tempstore data array. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The selection info title. + */ + protected function getSelectionInfoTitle(array $tempstore_data) { + if (!empty($tempstore_data['list'])) { + return empty($tempstore_data['exclude_mode']) ? $this->t('Items selected:') : $this->t('Selected all items except:'); + } + return $this->t(''); + } + + /** + * Build the selection info element. + * + * @param array $tempstore_data + * VBO tempstore data array. + * + * @return array + * Renderable array of the item list. + */ + protected function getMultipageList(array $tempstore_data) { + $this->addListData($tempstore_data); + $list = $this->getListRenderable($tempstore_data); + return $list; + } + /** * Build selected entities list renderable. * @@ -80,11 +111,12 @@ protected function addListData(&$form_data) { * Renderable list array. */ protected function getListRenderable(array $form_data) { + $renderable = [ + '#theme' => 'item_list', + '#items' => $form_data['entity_labels'], + '#empty' => $this->t(''), + ]; if (!empty($form_data['entity_labels'])) { - $renderable = [ - '#theme' => 'item_list', - '#items' => $form_data['entity_labels'], - ]; $more = count($form_data['list']) - count($form_data['entity_labels']); if ($more > 0) { $renderable['#items'][] = [ @@ -95,19 +127,11 @@ protected function getListRenderable(array $form_data) { ]; } } - else { - $renderable = [ - '#type' => 'item', - '#markup' => $this->t('All view results'), - ]; - } - if (!empty($form_data['exclude_mode'])) { - $renderable['#title'] = $this->t('Selected @count entities - all in the view except:', ['@count' => $form_data['selected_count']]); - } - else { - $renderable['#title'] = $this->t('Selected @count entities:', ['@count' => $form_data['selected_count']]); + elseif (!empty($form_data['exclude_mode'])) { + $renderable['#empty'] = $this->t('All items'); } + $renderable['#title'] = $this->getSelectionInfoTitle($form_data); $renderable['#wrapper_attributes'] = ['class' => ['vbo-info-list-wrapper']]; return $renderable; @@ -149,7 +173,7 @@ public static function calculateEntityBulkFormKey(EntityInterface $entity, $base } /** - * Get an entity list item from a bulk form key and label. + * Get an entity list item from a bulk form key. * * @param string $bulkFormKey * A bulk form key. diff --git a/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php b/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php index 1f1f00d7bd68f8b880d478ee7c366727e6ae79c0..defcf2144720e5f3ff8f7a2e33f9bc97988bc619 100644 --- a/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php +++ b/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php @@ -215,8 +215,9 @@ protected function updateTempstoreData(array $bulk_form_keys = NULL) { 'batch' => $this->options['batch'], 'batch_size' => $this->options['batch'] ? $this->options['batch_size'] : 0, 'total_results' => $this->viewData->getTotalResults($this->options['clear_on_exposed']), + 'relationship_id' => $this->options['relationship'], 'arguments' => $this->view->args, - 'exposed_input' => $this->view->getExposedInput(), + 'exposed_input' => $this->getExposedInput(), ]; // Add bulk form keys when the form is displayed. @@ -224,12 +225,6 @@ protected function updateTempstoreData(array $bulk_form_keys = NULL) { $variable['bulk_form_keys'] = $bulk_form_keys; } - // A fix to account for empty initial exposed input vs the default values - // difference. - if (empty($variable['exposed_input'])) { - $variable['exposed_input'] = $this->view->exposed_raw_input; - } - // Set redirect URL taking destination into account. $request = $this->requestStack->getCurrentRequest(); $destination = $request->query->get('destination'); @@ -287,6 +282,7 @@ protected function updateTempstoreData(array $bulk_form_keys = NULL) { if ($variable[$trigger] !== $this->tempStoreData[$trigger]) { $this->tempStoreData[$trigger] = $variable[$trigger]; $this->tempStoreData['list'] = []; + $this->tempStoreData['exclude_mode'] = FALSE; } unset($variable[$trigger]); $update = TRUE; @@ -306,6 +302,33 @@ protected function updateTempstoreData(array $bulk_form_keys = NULL) { } + /** + * Gets exposed input values from the view. + * + * @param array $exposed_input + * Current values of exposed input. + * + * @return array + * Exposed input sorted by filter names. + */ + protected function getExposedInput(array $exposed_input = []) { + if (empty($exposed_input)) { + // To avoid unnecessary reset of selection, we apply default values. We do + // that, because default values can be provided or not in the request, and + // it doesn't change results. + $exposed_input = array_merge($this->view->getExposedInput(), $this->view->exposed_raw_input); + } + // Sort values to avoid problems when comparing old and current exposed + // input. + ksort($exposed_input); + foreach ($exposed_input as $name => $value) { + if (is_array($value)) { + $exposed_input[$name] = $this->getExposedInput($value); + } + } + return $exposed_input; + } + /** * Gets the current user. * @@ -364,6 +387,7 @@ protected function defineOptions() { $options['clear_on_exposed'] = ['default' => TRUE]; $options['action_title'] = ['default' => $this->t('Action')]; $options['selected_actions'] = ['default' => []]; + $options['force_selection_info'] = ['default' => FALSE]; return $options; } @@ -423,6 +447,13 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#default_value' => $this->options['clear_on_exposed'], ]; + $form['force_selection_info'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Always show selection info'), + '#default_value' => $this->options['force_selection_info'], + '#description' => $this->t('Should the selection info fieldset be shown above the view even if there is only one page of results and full selection can be seen in the view itself?'), + ]; + $form['action_title'] = [ '#type' => 'textfield', '#title' => $this->t('Action title'), @@ -487,6 +518,16 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#default_value' => isset($selected_actions_data[$id]['preconfiguration']['label_override']) ? $selected_actions_data[$id]['preconfiguration']['label_override'] : '', ]; + // Also allow to force a default confirmation step for actoins that don't + // have it implemented. + if (empty($action['confirm_form_route_name'])) { + $form['selected_actions'][$delta]['preconfiguration']['add_confirmation'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Add confirmation step'), + '#default_value' => isset($selected_actions_data[$id]['preconfiguration']['add_confirmation']) ? $selected_actions_data[$id]['preconfiguration']['add_confirmation'] : FALSE, + ]; + } + // Load preconfiguration form if available. if (method_exists($action['class'], 'buildPreConfigurationForm')) { if (!isset($selected_actions_data[$id]['preconfiguration'])) { @@ -588,15 +629,18 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { $action_options = $this->getBulkOptions(); if (!empty($this->view->result) && !empty($action_options)) { - // Calculate bulk form keys for all rows. + // Calculate bulk form keys and get labels for all rows. $bulk_form_keys = []; + $entity_labels = []; $base_field = $this->view->storage->get('base_field'); foreach ($this->view->result as $row_index => $row) { - $entity = $this->getEntity($row); - $bulk_form_keys[$row_index] = self::calculateEntityBulkFormKey( - $entity, - $row->{$base_field} - ); + if ($entity = $this->getEntity($row)) { + $bulk_form_keys[$row_index] = self::calculateEntityBulkFormKey( + $entity, + $row->{$base_field} + ); + $entity_labels[$row_index] = $entity->label(); + } } // Update and fetch tempstore data to be available from this point @@ -624,8 +668,7 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { // Render checkboxes for all rows. $page_selected = []; - foreach ($this->view->result as $row_index => $row) { - $bulk_form_key = $bulk_form_keys[$row_index]; + foreach ($bulk_form_keys as $row_index => $bulk_form_key) { $checked = isset($this->tempStoreData['list'][$bulk_form_key]); if (!empty($this->tempStoreData['exclude_mode'])) { $checked = !$checked; @@ -636,7 +679,7 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { } $form[$this->options['id']][$row_index] = [ '#type' => 'checkbox', - '#title' => $entity->label(), + '#title' => $entity_labels[$row_index], '#title_display' => 'invisible', '#default_value' => $checked, '#return_value' => $bulk_form_key, @@ -711,23 +754,28 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { } } - $display_select_all = ( - !$this->options['clear_on_exposed'] && - !empty($this->view->getExposedInput()) - ) || - ( - isset($pagerData) && - ( - $pagerData['more'] || - $pagerData['current'] > 0 - ) - ); - // Selection info: displayed if exposed filters are set and selection // is not cleared when they change or "select all" element display - // conditions are met. - if ($display_select_all) { + // conditions are met. Also displayed by default if VBO field has such + // configuration set. + if ($this->options['force_selection_info']) { + $display_select_all = TRUE; + } + else { + $display_select_all = ( + !$this->options['clear_on_exposed'] && + !empty($this->view->getExposedInput()) + ) || + ( + isset($pagerData) && + ( + $pagerData['more'] || + $pagerData['current'] > 0 + ) + ); + } + if ($display_select_all) { $count = empty($this->tempStoreData['exclude_mode']) ? count($this->tempStoreData['list']) : $this->tempStoreData['total_results'] - count($this->tempStoreData['list']); $form['header'][$this->options['id']]['multipage'] = [ '#type' => 'details', @@ -741,27 +789,11 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { 'data-view-id' => $this->tempStoreData['view_id'], 'data-display-id' => $this->tempStoreData['display_id'], 'class' => ['vbo-multipage-selector'], - 'name' => 'somename', ], ]; - // Display a list of items selected on other pages. - if ($count > count($page_selected)) { - $form_data = $this->tempStoreData; - $form_data['list'] = []; - $form_data['relationship_id'] = $this->options['relationship']; - foreach ($this->tempStoreData['list'] as $bulk_form_key => $item) { - if (!in_array($bulk_form_key, $page_selected)) { - $form_data['list'][$bulk_form_key] = $item; - } - } - $this->addListData($form_data); - $form['header'][$this->options['id']]['multipage']['list'] = $this->getListRenderable($form_data); - $form['header'][$this->options['id']]['multipage']['list']['#title'] = empty($this->tempStoreData['exclude_mode']) ? $this->t('Items selected on other pages:') : $this->t('Items excluded on other pages:'); - $form['header'][$this->options['id']]['multipage']['list']['#empty'] = $this->t('No selection'); - - } - + // Get selection info elements. + $form['header'][$this->options['id']]['multipage']['list'] = $this->getMultipageList($this->tempStoreData); $form['header'][$this->options['id']]['multipage']['clear'] = [ '#type' => 'submit', '#value' => $this->t('Clear'), @@ -862,6 +894,10 @@ public function viewsFormSubmit(array &$form, FormStateInterface $form_state) { $this->tempStoreData['relationship_id'] = $this->options['relationship']; $this->tempStoreData['preconfiguration'] = isset($action_config['preconfiguration']) ? $action_config['preconfiguration'] : []; $this->tempStoreData['clear_on_exposed'] = $this->options['clear_on_exposed']; + $this->tempStoreData['confirm_route'] = $action['confirm_form_route_name']; + if (empty($this->tempStoreData['confirm_route']) && !empty($action_config['preconfiguration']['add_confirmation'])) { + $this->tempStoreData['confirm_route'] = 'views_bulk_operations.confirm'; + } $configurable = $this->isActionConfigurable($action); @@ -915,13 +951,8 @@ public function viewsFormSubmit(array &$form, FormStateInterface $form_state) { if ($this->options['form_step'] && $configurable) { $redirect_route = 'views_bulk_operations.execute_configurable'; } - elseif ($this->options['batch']) { - if (!empty($action['confirm_form_route_name'])) { - $redirect_route = $action['confirm_form_route_name']; - } - } - elseif (!empty($action['confirm_form_route_name'])) { - $redirect_route = $action['confirm_form_route_name']; + elseif (!empty($this->tempStoreData['confirm_route'])) { + $redirect_route = $this->tempStoreData['confirm_route']; } // Redirect if needed. diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php index 00db7e5153e28cc0165d5dd0fa85ec6bb5783566..1a0bdb3752909c4a4b1ebbd8ef269ece17de45a4 100644 --- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php +++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php @@ -79,8 +79,6 @@ protected function findDefinitions() { $entity_type_definitions = $this->entityTypeManager->getDefinitions(); foreach ($definitions as $plugin_id => &$definition) { - $this->processDefinition($definition, $plugin_id); - // We only allow actions of existing entity type and empty // type meaning it's applicable to all entity types. if ( @@ -96,11 +94,21 @@ protected function findDefinitions() { // Filter definitions that are incompatible due to applied core // configuration form workaround (using confirm_form_route for config // forms and using action execute() method for purposes other than - // actual action execution). Luckily, core also has useful actions - // without the workaround, like node_assign_owner_action. - if (!in_array('Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionInterface', class_implements($definition['class'])) && (!empty($definition['confirm_form_route_name']))) { - unset($definitions[$plugin_id]); + // actual action execution). Also filter out actions that don't implement + // ViewsBulkOperationsActionInterface and have empty type as this + // shouldn't be the case in core. Luckily, core also has useful actions + // without the workaround, like node_assign_owner_action or + // comment_unpublish_by_keyword_action. + if (!in_array('Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionInterface', class_implements($definition['class']))) { + if ( + !empty($definition['confirm_form_route_name']) || + empty($definition['type']) + ) { + unset($definitions[$plugin_id]); + } } + + $this->processDefinition($definition, $plugin_id); } $this->alterDefinitions($definitions); foreach ($definitions as $plugin_id => $plugin_definition) { diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php index a3466f4b340387cc65ca05074d98751e6b06cee2..00f2e3f6e2c771c62df2317b69c021080eb7ca9a 100644 --- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php +++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php @@ -243,6 +243,10 @@ public function getEntityDefault(ResultRow $row, $relationship_id, ViewExecutabl throw new \Exception('Unexpected view result row structure.'); } + if (empty($entity)) { + return; + } + if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) { // Try to find a field alias for the langcode. diff --git a/web/modules/views_bulk_operations/tests/src/Functional/DrushCommandsTest.php b/web/modules/views_bulk_operations/tests/src/Functional/DrushCommandsTest.php index 91a86b3b59ef8bb6b57e950609e9e113f09d13f1..67241da11baf697cb2ca36564c2418d18bc5a86c 100644 --- a/web/modules/views_bulk_operations/tests/src/Functional/DrushCommandsTest.php +++ b/web/modules/views_bulk_operations/tests/src/Functional/DrushCommandsTest.php @@ -49,7 +49,7 @@ protected function setUp() { $this->testNodes[] = $this->drupalCreateNode([ 'type' => 'page', 'title' => 'Title ' . $i, - 'sticky' => FALSE, + 'sticky' => $i % 2 ? TRUE : FALSE, 'created' => $time, 'changed' => $time, ]); @@ -61,9 +61,23 @@ protected function setUp() { * Tests the VBO Drush command. */ public function testDrushCommand() { + // Basic test. $this->drush('vbo-exec', ['views_bulk_operations_test', 'views_bulk_operations_simple_test_action']); - $this->assertStringContainsString('Test action (preconfig: , label: Title 0)', $this->getErrorOutput()); - $this->assertStringContainsString('Test action (preconfig: , label: Title 14)', $this->getErrorOutput()); + for ($i = 0; $i < self::TEST_NODE_COUNT; $i++) { + $this->assertStringContainsString("Test action (preconfig: , label: Title $i)", $this->getErrorOutput()); + } + + // Exposed filters test. + $this->drush('vbo-exec', ['views_bulk_operations_test', 'views_bulk_operations_simple_test_action'], ['exposed' => 'sticky=1']); + for ($i = 0; $i < self::TEST_NODE_COUNT; $i++) { + $test_string = "Test action (preconfig: , label: Title $i)"; + if ($i % 2) { + $this->assertStringContainsString($test_string, $this->getErrorOutput()); + } + else { + $this->assertStringNotContainsString($test_string, $this->getErrorOutput()); + } + } } } diff --git a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php index 3c700d685a5ff52159e1102c423f8da2b755ff0f..4a981ef31cd8cdcfe5b1eb636cb209a44a23b5ee 100644 --- a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php +++ b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php @@ -3,58 +3,12 @@ namespace Drupal\Tests\views_bulk_operations\Functional; use Drupal\Component\Render\FormattableMarkup; -use Drupal\Tests\BrowserTestBase; /** * @coversDefaultClass \Drupal\views_bulk_operations\Plugin\views\field\ViewsBulkOperationsBulkForm * @group views_bulk_operations */ -class ViewsBulkOperationsBulkFormTest extends BrowserTestBase { - - const TEST_NODE_COUNT = 15; - - /** - * {@inheritdoc} - */ - protected $defaultTheme = 'stable'; - - /** - * Modules to install. - * - * @var array - */ - public static $modules = [ - 'node', - 'views', - 'views_bulk_operations', - 'views_bulk_operations_test', - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - - // Create some nodes for testing. - $this->drupalCreateContentType(['type' => 'page']); - - $this->testNodes = []; - $time = $this->container->get('datetime.time')->getRequestTime(); - for ($i = 0; $i < self::TEST_NODE_COUNT; $i++) { - // Ensure nodes are sorted in the same order they are inserted in the - // array. - $time -= $i; - $this->testNodes[] = $this->drupalCreateNode([ - 'type' => 'page', - 'title' => 'Title ' . $i, - 'sticky' => FALSE, - 'created' => $time, - 'changed' => $time, - ]); - } - - } +class ViewsBulkOperationsBulkFormTest extends ViewsBulkOperationsFunctionalTestBase { /** * Tests the VBO bulk form with simple test action. @@ -69,10 +23,12 @@ public function testViewsBulkOperationsBulkFormSimple() { $first_form_element = $this->xpath('//form/div[1][@id = :id]', [':id' => 'edit-header']); $this->assertNotEmpty($first_form_element, 'The views form edit header appears first.'); - // Make sure a checkbox appears on all rows. - $edit = []; + // Make sure a checkbox appears on all rows and every checkbox has + // the correct label. for ($i = 0; $i < 4; $i++) { - $assertSession->fieldExists('edit-views-bulk-operations-bulk-form-' . $i, NULL, new FormattableMarkup('The checkbox on row @row appears.', ['@row' => $i])); + $checkbox_selector = 'edit-views-bulk-operations-bulk-form-' . $i; + $assertSession->fieldExists($checkbox_selector, NULL, new FormattableMarkup('The checkbox on row @row appears.', ['@row' => $i])); + $assertSession->elementTextContains('css', "label[for=$checkbox_selector]", $this->testNodes[$i]->label()); } // The advanced action should not be shown on the form - no permission. @@ -84,14 +40,8 @@ public function testViewsBulkOperationsBulkFormSimple() { $this->drupalLogin($admin_user); // Execute the simple test action. - $edit = []; $selected = [0, 2, 3]; - foreach ($selected as $index) { - $edit["views_bulk_operations_bulk_form[$index]"] = TRUE; - } - - // Tests: actions as buttons, label override. - $this->drupalPostForm('views-bulk-operations-test', $edit, t('Simple test action')); + $this->executeAction('views-bulk-operations-test', t('Simple test action'), $selected); $testViewConfig = \Drupal::service('config.factory')->get('views.view.views_bulk_operations_test'); $configData = $testViewConfig->getRawData(); @@ -110,17 +60,12 @@ public function testViewsBulkOperationsBulkFormSimple() { } // Test the select all functionality. - $edit = [ - 'select_all' => 1, - ]; // With the exclude mode, we also have to select all rows of the // view, otherwise those will be treated as excluded. In the UI // this is handled by JS. - foreach ([0, 1, 2, 3] as $index) { - $edit["views_bulk_operations_bulk_form[$index]"] = TRUE; - } - - $this->drupalPostForm(NULL, $edit, t('Simple test action')); + $selected = [0, 1, 2, 3]; + $data = ['select_all' => 1]; + $this->executeAction(NULL, t('Simple test action'), $selected, $data); $assertSession->pageTextContains( sprintf('Action processing results: Test (%d).', self::TEST_NODE_COUNT), @@ -145,14 +90,9 @@ public function testViewsBulkOperationsBulkFormAdvanced() { // First execute the simple action to test // the ViewsBulkOperationsController class. - $edit = [ - 'action' => 0, - ]; $selected = [0, 2]; - foreach ($selected as $index) { - $edit["views_bulk_operations_bulk_form[$index]"] = TRUE; - } - $this->drupalPostForm('views-bulk-operations-test-advanced', $edit, t('Apply to selected items')); + $data = ['action' => 0]; + $this->executeAction('views-bulk-operations-test-advanced', t('Apply to selected items'), $selected, $data); $assertSession->pageTextContains( sprintf('Action processing results: Test (%d).', count($selected)), @@ -160,14 +100,9 @@ public function testViewsBulkOperationsBulkFormAdvanced() { ); // Execute the advanced test action. - $edit = [ - 'action' => 1, - ]; $selected = [0, 1, 3]; - foreach ($selected as $index) { - $edit["views_bulk_operations_bulk_form[$index]"] = TRUE; - } - $this->drupalPostForm('views-bulk-operations-test-advanced', $edit, t('Apply to selected items')); + $data = ['action' => 1]; + $this->executeAction('views-bulk-operations-test-advanced', t('Apply to selected items'), $selected, $data); // Check if the configuration form is open and contains the // test_config field. @@ -238,7 +173,7 @@ public function testViewsBulkOperationsBulkFormPassing() { $assertSession = $this->assertSession(); - // Log in as a user with 'administer content' permission + // Log in as a user with 'bypass node access' permission // to have access to perform the test operation. $admin_user = $this->drupalCreateUser(['bypass node access']); $this->drupalLogin($admin_user); @@ -326,4 +261,32 @@ public function testViewsBulkOperationsBulkFormPassing() { } + /** + * Test core action - specific configuration. + */ + public function testActionCorePreconfig() { + $assertSession = $this->assertSession(); + + $testViewConfig = \Drupal::service('config.factory')->getEditable('views.view.views_bulk_operations_test'); + $configData = $testViewConfig->getRawData(); + $preconfig = &$configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['selected_actions'][0]['preconfiguration']; + $preconfig['add_confirmation'] = TRUE; + $testViewConfig->setData($configData); + $testViewConfig->save(); + + $this->drupalGet('views-bulk-operations-test'); + + // Log in as a user with 'edit any page content' permission + // to have access to perform the test operation. + $admin_user = $this->drupalCreateUser(['edit any page content']); + $this->drupalLogin($admin_user); + + // Check if we're on the confirmation form and if the overridden label + // is displayed. + $selection = [0, 2, 3]; + $label = $preconfig['label_override']; + $this->executeAction('views-bulk-operations-test', t('Simple test action'), $selection); + $assertSession->pageTextContains(sprintf('Are you sure you wish to perform "%s" action on %d entities?', $label, count($selection))); + } + } diff --git a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsFunctionalTestBase.php b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsFunctionalTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..eb7fd6f615a9b125112873ef6d0954ee1b2df8fd --- /dev/null +++ b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsFunctionalTestBase.php @@ -0,0 +1,77 @@ +<?php + +namespace Drupal\Tests\views_bulk_operations\Functional; + +use Drupal\Tests\BrowserTestBase; +use Drupal\Core\StringTranslation\TranslatableMarkup; + +/** + * Base class for VBO browser tests. + */ +abstract class ViewsBulkOperationsFunctionalTestBase extends BrowserTestBase { + + const TEST_NODE_COUNT = 15; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stable'; + + /** + * Modules to install. + * + * @var array + */ + public static $modules = [ + 'node', + 'views', + 'views_bulk_operations', + 'views_bulk_operations_test', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create some nodes for testing. + $this->drupalCreateContentType(['type' => 'page']); + + $this->testNodes = []; + $time = $this->container->get('datetime.time')->getRequestTime(); + for ($i = 0; $i < static::TEST_NODE_COUNT; $i++) { + // Ensure nodes are sorted in the same order they are inserted in the + // array. + $time -= $i; + $this->testNodes[] = $this->drupalCreateNode([ + 'type' => 'page', + 'title' => 'Title ' . $i, + 'sticky' => FALSE, + 'created' => $time, + 'changed' => $time, + ]); + } + + } + + /** + * Helper function that gets configuration for a selected view. + * + * @param string|null $path + * The path of the View page that includes VBO. + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $button_text + * The form submit button text. + * @param int[] $selection + * The selected items' indexes. + * @param array $data + * Additional parameters for the submitted form. + */ + protected function executeAction($path, TranslatableMarkup $button_text, array $selection = [], array $data = []) { + foreach ($selection as $index) { + $data["views_bulk_operations_bulk_form[$index]"] = TRUE; + } + $this->drupalPostForm($path, $data, $button_text); + } + +} diff --git a/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php b/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php index 7263470640aa5648322ffbd247c2d5d95206d348..6468565a67d40101ca94b9d0e054993d2fcdc1db 100644 --- a/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php +++ b/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php @@ -15,6 +15,8 @@ class ViewsBulkOperationsBulkFormTest extends WebDriverTestBase { const TEST_NODE_COUNT = 15; + const TEST_VIEW_ID = 'views_bulk_operations_test'; + /** * {@inheritdoc} */ @@ -43,6 +45,20 @@ class ViewsBulkOperationsBulkFormTest extends WebDriverTestBase { */ protected $selectedIndexes = []; + /** + * Test nodes. + * + * @var \Drupal\node\NodeInterface[] + */ + protected $testNodes = []; + + /** + * Test view parameters as in the config. + * + * @var array + */ + protected $testViewParams; + /** * Modules to install. * @@ -63,8 +79,8 @@ protected function setUp() { // Create some nodes for testing. $this->drupalCreateContentType(['type' => 'page']); - for ($i = 1; $i <= self::TEST_NODE_COUNT; $i++) { - $this->drupalCreateNode([ + for ($i = 0; $i <= self::TEST_NODE_COUNT; $i++) { + $node = $this->drupalCreateNode([ 'type' => 'page', 'title' => 'Title ' . $i, ]); @@ -80,31 +96,52 @@ protected function setUp() { $this->assertSession = $this->assertSession(); $this->page = $this->getSession()->getPage(); - $this->drupalGet('/views-bulk-operations-test'); + // Get useful config data from the test view. + $config_data = \Drupal::service('config.factory')->get('views.view.' . self::TEST_VIEW_ID)->getRawData(); + $this->testViewParams = [ + 'items_per_page' => $config_data['display']['default']['display_options']['pager']['options']['items_per_page'], + 'path' => $config_data['display']['page_1']['display_options']['path'], + ]; + $this->drupalGet('/' . $this->testViewParams['path']); + } + + /** + * Tests the VBO bulk form without dynamic insertion. + */ + public function testViewsBulkOperationsAjaxUi() { // Make sure a checkbox appears on all rows and the button exists. - for ($i = 0; $i < 4; $i++) { + $this->assertSession->buttonExists('Simple test action'); + for ($i = 0; $i < $this->testViewParams['items_per_page']; $i++) { $this->assertSession->fieldExists('edit-views-bulk-operations-bulk-form-' . $i); } - $this->assertSession->buttonExists('Simple test action'); - - $this->selectedIndexes = [0, 1, 3]; - foreach ($this->selectedIndexes as $selected_index) { + // Select some items on the first page. + foreach ([0, 1, 3] as $selected_index) { + $this->selectedIndexes[] = $selected_index; $this->page->checkField('edit-views-bulk-operations-bulk-form-' . $selected_index); } - } - - /** - * Tests the VBO bulk form without dynamic insertion. - */ - public function testViewsBulkOperationsWithOutDynamicInsertion() { + // Go to the next page and select some more. + $this->page->clickLink('Go to next page'); + foreach ([1, 2] as $selected_index) { + // This is page one so indexes are incremented by page count and + // checkbox selectors start from 0 again. + $this->selectedIndexes[] = $selected_index + $this->testViewParams['items_per_page']; + $this->page->checkField('edit-views-bulk-operations-bulk-form-' . $selected_index); + } + // Execute test operation. $this->page->pressButton('Simple test action'); - foreach ($this->selectedIndexes as $index) { - $this->assertSession->pageTextContains(sprintf('Test action (preconfig: Test setting, label: Title %s)', self::TEST_NODE_COUNT - $index)); + // Assert if only the selected nodes were processed. + foreach ($this->testNodes as $delta => $node) { + if (in_array($delta, $this->selectedIndexes, TRUE)) { + $this->assertSession->pageTextContains(sprintf('Test action (preconfig: Test setting, label: %s)', $node->label())); + } + else { + $this->assertSession->pageTextNotContains(sprintf('Test action (preconfig: Test setting, label: %s)', $node->label())); + } } $this->assertSession->pageTextContains(sprintf('Action processing results: Test (%s)', count($this->selectedIndexes))); @@ -117,6 +154,12 @@ public function testViewsBulkOperationsWithOutDynamicInsertion() { */ public function testViewsBulkOperationsWithDynamicInsertion() { + $this->selectedIndexes = [0, 1, 3]; + + foreach ($this->selectedIndexes as $selected_index) { + $this->page->checkField('edit-views-bulk-operations-bulk-form-' . $selected_index); + } + // Insert nodes. $nodes = []; for ($i = 100; $i < 100 + self::TEST_NODE_COUNT; $i++) { @@ -132,12 +175,6 @@ public function testViewsBulkOperationsWithDynamicInsertion() { $this->assertSession->pageTextContains(sprintf('Test action (preconfig: Test setting, label: Title %s)', self::TEST_NODE_COUNT - $index)); } $this->assertSession->pageTextContains(sprintf('Action processing results: Test (%s)', count($this->selectedIndexes))); - - // Remove nodes inserted in the middle. - foreach ($nodes as $node) { - $node->delete(); - } - } } diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test.yml b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test.yml index 712b47789d56160130b09f7f54de64ffa8baeb40..96279aa797243c073d39d8da4d123c95bcf2ae78 100644 --- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test.yml +++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test.yml @@ -12,7 +12,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default @@ -170,17 +169,61 @@ display: buttons: true action_title: Action selected_actions: - 0: + - action_id: views_bulk_operations_simple_test_action preconfiguration: label_override: 'Simple test action' preconfig: 'Test setting' - 1: + - action_id: views_bulk_operations_advanced_test_action preconfiguration: preconfig: 'Test setting' plugin_id: views_bulk_operations_bulk_form - filters: { } + filters: + sticky: + id: sticky + table: node_field_data + field: sticky + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Sticky status' + description: '' + use_operator: false + operator: sticky_op + operator_limit_selection: false + operator_list: { } + identifier: sticky + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + role_1: '0' + role_2: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: node + entity_field: sticky + plugin_id: boolean sorts: created: id: created @@ -209,6 +252,7 @@ display: contexts: - 'languages:language_content' - 'languages:language_interface' + - url - url.query_args - 'user.node_grants:view' - user.permissions @@ -226,6 +270,7 @@ display: contexts: - 'languages:language_content' - 'languages:language_interface' + - url - url.query_args - 'user.node_grants:view' - user.permissions diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml index 1d3513193f973e4882c9f1e188fbe7a971d8172b..9f14699ee451bea53e0e887ea8a3dc7c9a582a2c 100644 --- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml +++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml @@ -7,7 +7,7 @@ dependencies: - drupal:views_bulk_operations - drupal:node -# Information added by Drupal.org packaging script on 2020-12-24 -version: '8.x-3.10' +# Information added by Drupal.org packaging script on 2021-03-17 +version: '8.x-3.11' project: 'views_bulk_operations' -datestamp: 1608795021 +datestamp: 1615996410 diff --git a/web/modules/views_bulk_operations/views_bulk_operations.info.yml b/web/modules/views_bulk_operations/views_bulk_operations.info.yml index 9539dbe95dbe7a65e514e269b8d624345a6303cf..94a11698912b2b134bec2327a7f2c196f2a54aae 100644 --- a/web/modules/views_bulk_operations/views_bulk_operations.info.yml +++ b/web/modules/views_bulk_operations/views_bulk_operations.info.yml @@ -6,7 +6,7 @@ core_version_requirement: ^8.8 || ^9 dependencies: - drupal:views -# Information added by Drupal.org packaging script on 2020-12-24 -version: '8.x-3.10' +# Information added by Drupal.org packaging script on 2021-03-17 +version: '8.x-3.11' project: 'views_bulk_operations' -datestamp: 1608795021 +datestamp: 1615996410