diff --git a/composer.json b/composer.json index 30e32b85a7003a8a43db8475194b946c3fbe097c..880282bb7a100069b3f5341feb0c5c4c676e1726 100644 --- a/composer.json +++ b/composer.json @@ -174,7 +174,7 @@ "drupal/views_ajax_history": "1.6", "drupal/views_autocomplete_filters": "1.3", "drupal/views_bootstrap": "3.6", - "drupal/views_bulk_operations": "3.13", + "drupal/views_bulk_operations": "4.1.2", "drupal/views_fieldsets": "^3.4", "drupal/views_infinite_scroll": "1.9", "drupal/webform": "^6", diff --git a/composer.lock b/composer.lock index e4accdd3d049efdc07d4e75a9d08de35cf193197..2128b21a1febc65998cefb914bc2ac453d7cdec4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4d692bfd0abd7ce37d05150a17a6ccc3", + "content-hash": "ca74f702b216b1e9478fd9448cc02a06", "packages": [ { "name": "alchemy/zippy", @@ -7836,20 +7836,20 @@ }, { "name": "drupal/views_bulk_operations", - "version": "3.13.0", + "version": "4.1.2", "source": { "type": "git", "url": "https://git.drupalcode.org/project/views_bulk_operations.git", - "reference": "8.x-3.13" + "reference": "4.1.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.13.zip", - "reference": "8.x-3.13", - "shasum": "70583d08b91be3b5e008f571589425c2176eb73b" + "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-4.1.2.zip", + "reference": "4.1.2", + "shasum": "1cfa10f755240cae46de8d8684272a9a8907a730" }, "require": { - "drupal/core": "^8.8 || ^9" + "drupal/core": "^9" }, "require-dev": { "drush/drush": "^10" @@ -7860,8 +7860,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-3.13", - "datestamp": "1619697066", + "version": "4.1.2", + "datestamp": "1647943086", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -12942,16 +12942,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v4.4.39", + "version": "v4.4.42", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4e9215a8b533802ba84a3cc5bd3c43103e7a6dc3" + "reference": "be5a04618e5d44e71d013f177df80d3ec4b192a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4e9215a8b533802ba84a3cc5bd3c43103e7a6dc3", - "reference": "4e9215a8b533802ba84a3cc5bd3c43103e7a6dc3", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/be5a04618e5d44e71d013f177df80d3ec4b192a0", + "reference": "be5a04618e5d44e71d013f177df80d3ec4b192a0", "shasum": "" }, "require": { @@ -12996,7 +12996,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v4.4.39" + "source": "https://github.com/symfony/dom-crawler/tree/v4.4.42" }, "funding": [ { @@ -13012,7 +13012,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T10:38:15+00:00" + "time": "2022-04-30T18:34:00+00:00" }, { "name": "symfony/error-handler", diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 600b6f84a6b99ba97aa7216f41fefb739be2350a..4146e7a5c84a49184c12d9e828ba0edfbda89811 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -8147,21 +8147,21 @@ }, { "name": "drupal/views_bulk_operations", - "version": "3.13.0", - "version_normalized": "3.13.0.0", + "version": "4.1.2", + "version_normalized": "4.1.2.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/views_bulk_operations.git", - "reference": "8.x-3.13" + "reference": "4.1.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.13.zip", - "reference": "8.x-3.13", - "shasum": "70583d08b91be3b5e008f571589425c2176eb73b" + "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-4.1.2.zip", + "reference": "4.1.2", + "shasum": "1cfa10f755240cae46de8d8684272a9a8907a730" }, "require": { - "drupal/core": "^8.8 || ^9" + "drupal/core": "^9" }, "require-dev": { "drush/drush": "^10" @@ -8172,8 +8172,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-3.13", - "datestamp": "1619697066", + "version": "4.1.2", + "datestamp": "1647943086", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -13309,17 +13309,17 @@ }, { "name": "symfony/dom-crawler", - "version": "v4.4.39", - "version_normalized": "4.4.39.0", + "version": "v4.4.42", + "version_normalized": "4.4.42.0", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4e9215a8b533802ba84a3cc5bd3c43103e7a6dc3" + "reference": "be5a04618e5d44e71d013f177df80d3ec4b192a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4e9215a8b533802ba84a3cc5bd3c43103e7a6dc3", - "reference": "4e9215a8b533802ba84a3cc5bd3c43103e7a6dc3", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/be5a04618e5d44e71d013f177df80d3ec4b192a0", + "reference": "be5a04618e5d44e71d013f177df80d3ec4b192a0", "shasum": "" }, "require": { @@ -13338,7 +13338,7 @@ "suggest": { "symfony/css-selector": "" }, - "time": "2022-02-25T10:38:15+00:00", + "time": "2022-04-30T18:34:00+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -13366,7 +13366,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v4.4.39" + "source": "https://github.com/symfony/dom-crawler/tree/v4.4.42" }, "funding": [ { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 32754976359ce7329f86ea1f336db475339ff121..1bc9123907fc932e1a5bb1b58b1769f5b2d52284 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'cf6e62cb8884f75da3b28fac12b8ce2c6e1fc77b', + 'reference' => '041c3df39d99c77a37adea73f38c4380bad4c507', 'name' => 'osu-asc-webservices/d8-upstream', 'dev' => true, ), @@ -1811,12 +1811,12 @@ 'dev_requirement' => false, ), 'drupal/views_bulk_operations' => array( - 'pretty_version' => '3.13.0', - 'version' => '3.13.0.0', + 'pretty_version' => '4.1.2', + 'version' => '4.1.2.0', 'type' => 'drupal-module', 'install_path' => __DIR__ . '/../../web/modules/views_bulk_operations', 'aliases' => array(), - 'reference' => '8.x-3.13', + 'reference' => '4.1.2', 'dev_requirement' => false, ), 'drupal/views_fieldsets' => array( @@ -2101,7 +2101,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'cf6e62cb8884f75da3b28fac12b8ce2c6e1fc77b', + 'reference' => '041c3df39d99c77a37adea73f38c4380bad4c507', 'dev_requirement' => false, ), 'pantheon-systems/quicksilver-pushback' => array( @@ -2658,12 +2658,12 @@ 'dev_requirement' => false, ), 'symfony/dom-crawler' => array( - 'pretty_version' => 'v4.4.39', - 'version' => '4.4.39.0', + 'pretty_version' => 'v4.4.42', + 'version' => '4.4.42.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dom-crawler', 'aliases' => array(), - 'reference' => '4e9215a8b533802ba84a3cc5bd3c43103e7a6dc3', + 'reference' => 'be5a04618e5d44e71d013f177df80d3ec4b192a0', 'dev_requirement' => false, ), 'symfony/error-handler' => array( diff --git a/vendor/symfony/dom-crawler/Crawler.php b/vendor/symfony/dom-crawler/Crawler.php index 462b6b1129d9e6d46ecc03a44419de97391f7274..4f89eec75a74bce314d9b890f4697b1926b25f6f 100644 --- a/vendor/symfony/dom-crawler/Crawler.php +++ b/vendor/symfony/dom-crawler/Crawler.php @@ -1214,11 +1214,11 @@ private function convertToHtmlEntities(string $htmlContent, string $charset = 'U set_error_handler(function () { throw new \Exception(); }); try { - return mb_encode_numericentity($htmlContent, [0x80, 0xFFFF, 0, 0xFFFF], $charset); + return mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], $charset); } catch (\Exception|\ValueError $e) { try { $htmlContent = iconv($charset, 'UTF-8', $htmlContent); - $htmlContent = mb_encode_numericentity($htmlContent, [0x80, 0xFFFF, 0, 0xFFFF], 'UTF-8'); + $htmlContent = mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); } catch (\Exception|\ValueError $e) { } diff --git a/web/modules/views_bulk_operations/composer.json b/web/modules/views_bulk_operations/composer.json index 0c9001044841b059804b9eb9a14ae5ed0788db30..09322768a917a23aa4426d305e64f25ce00b31a3 100644 --- a/web/modules/views_bulk_operations/composer.json +++ b/web/modules/views_bulk_operations/composer.json @@ -17,7 +17,7 @@ "license": "GPL-2.0-or-later", "minimum-stability": "dev", "require": { - "drupal/core": "^8.8 || ^9" + "drupal/core": "^9" }, "require-dev": { "drush/drush": "^10" diff --git a/web/modules/views_bulk_operations/config/schema/views_bulk_operations.data_types.schema.yml b/web/modules/views_bulk_operations/config/schema/views_bulk_operations.data_types.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..806b2924f7ba9c4412c9314661ad88e918350de8 --- /dev/null +++ b/web/modules/views_bulk_operations/config/schema/views_bulk_operations.data_types.schema.yml @@ -0,0 +1,9 @@ +views_bulk_operations_action_config: + type: mapping + mapping: + add_confirmation: + type: boolean + label: 'Should a confirmation step be added?' + label_override: + type: string + label: 'Action label override' diff --git a/web/modules/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml b/web/modules/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml index 97cd68b497ff4f10ba85acd6f9086ff4a966f1ed..d433d32803fbdc34556ac84c3fcac84c6a1c32a2 100644 --- a/web/modules/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml +++ b/web/modules/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml @@ -33,5 +33,9 @@ views.field.views_bulk_operations_bulk_form: type: string label: 'Action plugin ID' preconfiguration: - label: 'Preliminary configuration array for the plugin' - type: ignore + label: 'Configuration array for the plugin' + type: views_bulk_operations.action_config.[%parent.action_id] + +views_bulk_operations.action_config.*: + type: views_bulk_operations_action_config + label: 'Default' diff --git a/web/modules/views_bulk_operations/drush.services.yml b/web/modules/views_bulk_operations/drush.services.yml index 3d629c0fafacef52f7a20533d59a1a6923ef23c6..093032c6e3c4e75d4bf30ae6a6bf10791ed1d993 100644 --- a/web/modules/views_bulk_operations/drush.services.yml +++ b/web/modules/views_bulk_operations/drush.services.yml @@ -3,6 +3,7 @@ services: class: \Drupal\views_bulk_operations\Commands\ViewsBulkOperationsCommands arguments: - '@current_user' + - '@entity_type.manager' - '@views_bulk_operations.data' - '@plugin.manager.views_bulk_operations_action' tags: diff --git a/web/modules/views_bulk_operations/js/adminUi.js b/web/modules/views_bulk_operations/js/adminUi.js index e7dba636957d7deec58bbcfdc69fec1292ad99b2..356d0b56d616b068c3747cd40aaa60b01d82c6dc 100644 --- a/web/modules/views_bulk_operations/js/adminUi.js +++ b/web/modules/views_bulk_operations/js/adminUi.js @@ -12,18 +12,18 @@ */ Drupal.behaviors.views_bulk_operations = { attach: function (context, settings) { - $('.views-bulk-operations-ui').once('views-bulk-operations-ui').each(Drupal.viewsBulkOperationsUi); + once('views-bulk-operations-ui', '.views-bulk-operations-ui', context).forEach(Drupal.viewsBulkOperationsUi); } }; /** * Callback used in {@link Drupal.behaviors.views_bulk_operations}. */ - Drupal.viewsBulkOperationsUi = function () { - var uiElement = $(this); + Drupal.viewsBulkOperationsUi = function (element) { + var $uiElement = $(element); // Select / deselect all functionality. - var actionsElementWrapper = uiElement.find('details.vbo-actions-widget > .details-wrapper'); + var actionsElementWrapper = $uiElement.find('details.vbo-actions-widget > .details-wrapper'); if (actionsElementWrapper.length) { var checked = false; var allHandle = $('<a href="#" class="vbo-all-switch">' + Drupal.t('Select / deselect all') + '</a>'); diff --git a/web/modules/views_bulk_operations/js/frontUi.js b/web/modules/views_bulk_operations/js/frontUi.js index c48e4428c077001e1eeea77a14a05450ef38ce87..7e604cb2daf0fc4ba5262229fa90cba3769294ff 100644 --- a/web/modules/views_bulk_operations/js/frontUi.js +++ b/web/modules/views_bulk_operations/js/frontUi.js @@ -12,7 +12,7 @@ */ Drupal.behaviors.views_bulk_operations = { attach: function (context, settings) { - $('.vbo-view-form').once('vbo-init').each(Drupal.viewsBulkOperationsFrontUi); + once('vbo-init', '.vbo-view-form', context).forEach(Drupal.viewsBulkOperationsFrontUi); } }; @@ -23,7 +23,7 @@ view_id: '', display_id: '', list: {}, - $placeholder: null, + $summary: null, /** * Bind event handlers to an element. @@ -85,7 +85,7 @@ op = state ? 'add' : 'remove'; } - var $placeholder = this.$placeholder; + var $summary = this.$summary; var $selectionInfo = this.$selectionInfo; var target_uri = drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + 'views-bulk-operations/ajax/' + this.view_id + '/' + this.display_id; @@ -96,8 +96,8 @@ op: op }, success: function (data) { - $selectionInfo.html($(data.selection_info).html()); - $placeholder.text(data.count); + $selectionInfo.html(data.selection_info); + $summary.text(Drupal.formatPlural(data.count, 'Selected 1 item in this view', 'Selected @count items in this view')); } }); } @@ -107,8 +107,8 @@ /** * Callback used in {@link Drupal.behaviors.views_bulk_operations}. */ - Drupal.viewsBulkOperationsFrontUi = function () { - var $vboForm = $(this); + Drupal.viewsBulkOperationsFrontUi = function (element) { + var $vboForm = $(element); var $viewsTables = $('.vbo-table', $vboForm); var $primarySelectAll = $('.vbo-select-all', $vboForm); var tableSelectAll = []; @@ -116,7 +116,7 @@ // When grouping is enabled, there can be multiple tables. if ($viewsTables.length) { $viewsTables.each(function (index) { - tableSelectAll[index] = $(this).find('.select-all input').first(); + tableSelectAll[index] = $vboForm.find('.select-all input').first(); }); var $tableSelectAll = $(tableSelectAll); } @@ -126,7 +126,7 @@ if ($multiSelectElement.length) { Drupal.viewsBulkOperationsSelection.$selectionInfo = $multiSelectElement.find('.vbo-info-list-wrapper').first(); - Drupal.viewsBulkOperationsSelection.$placeholder = $multiSelectElement.find('.placeholder').first(); + Drupal.viewsBulkOperationsSelection.$summary = $multiSelectElement.find('summary').first(); Drupal.viewsBulkOperationsSelection.view_id = $multiSelectElement.attr('data-view-id'); Drupal.viewsBulkOperationsSelection.display_id = $multiSelectElement.attr('data-display-id'); Drupal.viewsBulkOperationsSelection.vbo_form = $vboForm; 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 ab281d2a00890a55f3aba14aa773d405ccc6201e..ed1d4dcfe5cdb900769a8af5723a622edd3e6c54 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 2021-04-29 -version: '8.x-3.13' +# Information added by Drupal.org packaging script on 2022-03-19 +version: '4.1.2' project: 'views_bulk_operations' -datestamp: 1619697069 +datestamp: 1647723470 diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/src/EventSubscriber/ActionsPermissionsEventSubscriber.php b/web/modules/views_bulk_operations/modules/actions_permissions/src/EventSubscriber/ActionsPermissionsEventSubscriber.php index 695e9942f90f3bc21d318801e9daee1ab150d7c9..93167b98e3707eea7a86ee3b30bacf2719a27065 100644 --- a/web/modules/views_bulk_operations/modules/actions_permissions/src/EventSubscriber/ActionsPermissionsEventSubscriber.php +++ b/web/modules/views_bulk_operations/modules/actions_permissions/src/EventSubscriber/ActionsPermissionsEventSubscriber.php @@ -3,7 +3,7 @@ namespace Drupal\actions_permissions\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\EventDispatcher\Event; +use Drupal\Component\EventDispatcher\Event; use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager; /** @@ -21,14 +21,17 @@ class ActionsPermissionsEventSubscriber implements EventSubscriberInterface { * {@inheritdoc} */ public static function getSubscribedEvents() { - $events[ViewsBulkOperationsActionManager::ALTER_ACTIONS_EVENT][] = ['alterActions', static::PRIORITY]; + $events[ViewsBulkOperationsActionManager::ALTER_ACTIONS_EVENT][] = [ + 'alterActions', + static::PRIORITY, + ]; return $events; } /** * Alter the actions' definitions. * - * @var \Symfony\Component\EventDispatcher\Event $event + * @var \Drupal\Component\EventDispatcher\Event $event * The event to respond to. */ public function alterActions(Event $event) { diff --git a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/config/schema/views_bulk_operations_example.schema.yml b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/config/schema/views_bulk_operations_example.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..667347fea7ff525f3a7cff5e3a10d76633551048 --- /dev/null +++ b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/config/schema/views_bulk_operations_example.schema.yml @@ -0,0 +1,7 @@ +views_bulk_operations.action_config.views_bulk_operations_example: + type: views_bulk_operations_action_config + label: 'Example preliminary configuration' + mapping: + example_preconfig_setting: + type: string + label: 'Example setting' 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 560f130c23e1a40498caaea77951dacf106ce00c..0bf99a30b70d75af94a55afd2e348d16df16a4c1 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 2021-04-29 -version: '8.x-3.13' +# Information added by Drupal.org packaging script on 2022-03-19 +version: '4.1.2' project: 'views_bulk_operations' -datestamp: 1619697069 +datestamp: 1647723470 diff --git a/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php index 3a37707aa82295f79e9f55c7d0c9a6fd277a2a09..8f9f98d76ad88d64ac04b50dc20311b67ebcf51a 100644 --- a/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php +++ b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php @@ -16,6 +16,8 @@ */ abstract class ViewsBulkOperationsActionBase extends ActionBase implements ViewsBulkOperationsActionInterface, ConfigurableInterface { + use ViewsBulkOperationsActionCompletedTrait; + /** * Action context. * diff --git a/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionCompletedTrait.php b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionCompletedTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..2d5f43fa5061a37ff5f18f1e9b171d78ec12da8a --- /dev/null +++ b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionCompletedTrait.php @@ -0,0 +1,59 @@ +<?php + +namespace Drupal\views_bulk_operations\Action; + +use Symfony\Component\HttpFoundation\RedirectResponse; + +/** + * Defines action completion logic. + */ +trait ViewsBulkOperationsActionCompletedTrait { + + /** + * Set message function wrapper. + * + * @see \Drupal\Core\Messenger\MessengerInterface + */ + public static function message($message = NULL, $type = 'status', $repeat = TRUE) { + \Drupal::messenger()->addMessage($message, $type, $repeat); + } + + /** + * Translation function wrapper. + * + * @see \Drupal\Core\StringTranslation\TranslationInterface:translate() + */ + public static function translate($string, array $args = [], array $options = []) { + return \Drupal::translation()->translate($string, $args, $options); + } + + /** + * Batch finished callback. + * + * @param bool $success + * Was the process successful? + * @param array $results + * Batch process results array. + * @param array $operations + * Performed operations array. + */ + public static function finished($success, array $results, array $operations): ?RedirectResponse { + if ($success) { + $operations = array_count_values($results['operations']); + $details = []; + foreach ($operations as $op => $count) { + $details[] = $op . ' (' . $count . ')'; + } + $message = static::translate('Action processing results: @operations.', [ + '@operations' => implode(', ', $details), + ]); + static::message($message); + } + else { + $message = static::translate('Finished with an error.'); + static::message($message, 'error'); + } + return NULL; + } + +} diff --git a/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionInterface.php b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionInterface.php index d6c8824a4ed1fce71e01ccccbe9e3311fe4e7a80..296108da839e143ba8eea622a60beb92a6d90c96 100644 --- a/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionInterface.php +++ b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionInterface.php @@ -2,12 +2,14 @@ namespace Drupal\views_bulk_operations\Action; +use Drupal\Core\Action\ActionInterface; use Drupal\views\ViewExecutable; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Defines Views Bulk Operations action interface. */ -interface ViewsBulkOperationsActionInterface { +interface ViewsBulkOperationsActionInterface extends ActionInterface { /** * Set action context. @@ -44,4 +46,21 @@ public function setView(ViewExecutable $view); */ public function executeMultiple(array $objects); + /** + * Action batch execution finished callback. + * + * Used to set finished message, redirect or execute some final logic. + * + * @param bool $success + * Was the process successful? + * @param array $results + * Batch process results array. + * @param array $operations + * Performed operations array. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse|null + * Bach redirect response or NULL. + */ + public static function finished($success, array $results, array $operations): ?RedirectResponse; + } diff --git a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php index 523557289501aebfa954dcd49b7a54aa0b97d00d..0ca556790e4e89b06bd4986e83bdd61370331aee 100644 --- a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php +++ b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php @@ -2,11 +2,12 @@ namespace Drupal\views_bulk_operations\Commands; +use Consolidation\OutputFormatters\StructuredData\RowsOfFields; use Drush\Commands\DrushCommands; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface; use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager; -use Drupal\user\Entity\User; use Drupal\views\Views; use Drupal\views_bulk_operations\ViewsBulkOperationsBatch; @@ -22,6 +23,13 @@ class ViewsBulkOperationsCommands extends DrushCommands { */ protected $currentUser; + /** + * The user storage. + * + * @var \Drupal\user\UserStorageInterface + */ + protected $userStorage; + /** * Object that gets the current view data. * @@ -41,6 +49,8 @@ class ViewsBulkOperationsCommands extends DrushCommands { * * @param \Drupal\Core\Session\AccountInterface $currentUser * The current user object. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager. * @param \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface $viewData * VBO View data service. * @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager @@ -48,10 +58,12 @@ class ViewsBulkOperationsCommands extends DrushCommands { */ public function __construct( AccountInterface $currentUser, + EntityTypeManagerInterface $entityTypeManager, ViewsbulkOperationsViewDataInterface $viewData, ViewsBulkOperationsActionManager $actionManager ) { $this->currentUser = $currentUser; + $this->userStorage = $entityTypeManager->getStorage('user'); $this->viewData = $viewData; $this->actionManager = $actionManager; } @@ -71,7 +83,7 @@ public function __construct( * @return string * The summary message. * - * @command views-bulk-operations:execute + * @command views:bulk-operations:execute * * @option display-id * ID of the display to use. @@ -86,7 +98,7 @@ public function __construct( * @option user-id * The ID of the user account used for performing the operation. * - * @usage drush views-bulk-operations:execute some_view some_action + * @usage drush views:bulk-operations:execute some_view some_action * Execute some action on some view. * @usage drush vbo-execute some_view some_action --args=arg1/arg2 --batch-size=50 * Execute some action on some view with arg1 and arg2 as @@ -94,7 +106,7 @@ public function __construct( * @usage drush vbo-exec some_view some_action --configuration="key1=value1&key2=value2" * Execute some action on some view with the specified action configuration. * - * @aliases vbo-execute, vbo-exec + * @aliases vbo-execute, vbo-exec, views-bulk-operations:execute */ public function vboExecute( $view_id, @@ -108,9 +120,8 @@ public function vboExecute( 'user-id' => 1, ] ) { - if (empty($view_id) || empty($action_id)) { - throw new \Exception($this->t('You must specify the view ID and the action ID parameters.')); + throw new \Exception('You must specify the view ID and the action ID parameters.'); } $this->timer($options['verbose']); @@ -147,19 +158,20 @@ public function vboExecute( // We set the clear_on_exposed parameter to true, otherwise with empty // selection exposed filters are not taken into account. 'clear_on_exposed' => TRUE, + 'exclude_mode' => FALSE, ]; - // Login as superuser, as drush 9 doesn't support the - // --user parameter. - $account = User::load($options['user-id']); + // Login as the provided user, as drush 9+ doesn't support the + // --user parameter. Default: user 1. + $account = $this->userStorage->load($options['user-id']); $this->currentUser->setAccount($account); // Initialize the view to check if parameters are correct. if (!$view = Views::getView($vbo_data['view_id'])) { - throw new \Exception($this->t('Incorrect view ID provided.')); + throw new \Exception('Incorrect view ID provided.'); } if (!$view->setDisplay($vbo_data['display_id'])) { - throw new \Exception($this->t('Incorrect view display ID provided.')); + throw new \Exception('Incorrect view display ID provided.'); } if (!empty($vbo_data['arguments'])) { $view->setArguments($vbo_data['arguments']); @@ -225,12 +237,56 @@ public function vboExecute( // Display debug information. if ($options['verbose']) { $this->timer($options['verbose'], 'execute'); - $this->logger->info($this->t('Initialization time: @time ms.', ['@time' => $this->timer($options['verbose'], 'init')])); - $this->logger->info($this->t('Entity list generation time: @time ms.', ['@time' => $this->timer($options['verbose'], 'list')])); - $this->logger->info($this->t('Execution time: @time ms.', ['@time' => $this->timer($options['verbose'], 'execute')])); + $this->logger->info($this->t('Initialization time: @time ms.', [ + '@time' => $this->timer($options['verbose'], 'init'), + ])); + $this->logger->info($this->t('Entity list generation time: @time ms.', [ + '@time' => $this->timer($options['verbose'], 'list'), + ])); + $this->logger->info($this->t('Execution time: @time ms.', [ + '@time' => $this->timer($options['verbose'], 'execute'), + ])); + } + + return $this->t('Action processing results: @results.', [ + '@results' => implode(', ', $details), + ]); + } + + /** + * List available actions for a view. + * + * @return string + * The summary message. + * + * @command views:bulk-operations:list + * + * @table-style default + * @field-labels + * id: ID + * label: Label + * entity_type_id: Entity type ID + * @default-fields id,label,entity_type_id + * + * @usage drush views:bulk-operations:list some_view some_action + * Execute some action on some view. + * @usage drush vbo-list + * List all available actions info. + * + * @aliases vbo-list + */ + public function vboList($options = ['format' => 'table']) { + $rows = []; + $actions = $this->actionManager->getDefinitions(['nocache' => TRUE]); + foreach ($actions as $id => $definition) { + $rows[] = [ + 'id' => $id, + 'label' => $definition['label'], + 'entity_type_id' => $definition['type'] ? $definition['type'] : dt('(any)'), + ]; } - return $this->t('Action processing results: @results.', ['@results' => implode(', ', $details)]); + return new RowsOfFields($rows); } /** diff --git a/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php b/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php index ee15c35561f2e4092a289ef3750834ed484e3771..7a56d0cc0cffe71ca518b29e0aa950377fab2bcf 100644 --- a/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php +++ b/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php @@ -38,7 +38,10 @@ public function __construct(ViewsBulkOperationsViewDataInterface $viewData) { * {@inheritdoc} */ public static function getSubscribedEvents() { - $events[ViewsBulkOperationsEvent::NAME][] = ['provideViewData', self::PRIORITY]; + $events[ViewsBulkOperationsEvent::NAME][] = [ + 'provideViewData', + self::PRIORITY, + ]; return $events; } diff --git a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php index a5469c4ba3646b5ffec1de694c913ce4efb7d431..ec66bdb5c00cccb95f19a02b9c0fee8b2ff53b9f 100644 --- a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php +++ b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php @@ -82,9 +82,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $view_id $form_data = $this->getFormData($view_id, $display_id); - // TODO: display an error msg, redirect back. if (!isset($form_data['action_id'])) { - return; + return [ + '#markup' => $this->t('No items selected. Go back and try again.'), + ]; } $form['#title'] = $this->t('Configure "%action" action applied to the selection', ['%action' => $form_data['action_label']]); diff --git a/web/modules/views_bulk_operations/src/Form/ConfirmAction.php b/web/modules/views_bulk_operations/src/Form/ConfirmAction.php index 8c5c742fb2635e6aa07b473b82cf54af5041c36e..93b1be427cde64e5742ffe48b789c9d691697667 100644 --- a/web/modules/views_bulk_operations/src/Form/ConfirmAction.php +++ b/web/modules/views_bulk_operations/src/Form/ConfirmAction.php @@ -82,7 +82,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $view_id $form_data = $this->getFormData($view_id, $display_id); - // TODO: display an error msg, redirect back. + // @todo Display an error msg, redirect back. if (!isset($form_data['action_id'])) { return; } diff --git a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php index 91fea73670c73aeeac0b1454480a68d4891844f3..725f011b0ff686f96f1a5db22f95d9a634f7af3d 100644 --- a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php +++ b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php @@ -66,7 +66,7 @@ protected function addListData(&$form_data) { $form_data['entity_labels'] = $this->actionProcessor->getLabels($modified_form_data); } else { - $form_data['selected_count'] = $form_data['total_results']; + $form_data['selected_count'] = $form_data['total_results'] ?? 0; } } @@ -83,7 +83,6 @@ 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('No items selected.'); } /** @@ -126,12 +125,12 @@ protected function getListRenderable(array $form_data) { '#wrapper_attributes' => ['class' => ['more']], ]; } + $renderable['#title'] = $this->getSelectionInfoTitle($form_data); } elseif (!empty($form_data['exclude_mode'])) { - $renderable['#empty'] = $this->t('All items'); + $renderable['#empty'] = $this->t('Action will be executed on all items in the view.'); } - $renderable['#title'] = $this->getSelectionInfoTitle($form_data); $renderable['#wrapper_attributes'] = ['class' => ['vbo-info-list-wrapper']]; return $renderable; @@ -147,6 +146,8 @@ protected function getListRenderable(array $form_data) { * The entity to calculate a bulk form key for. * @param mixed $base_field_value * The value of the base field for this view result. + * @param mixed $row_index + * Index of view result. * * @return string * The bulk form key representing the entity id, language and revision (if @@ -154,7 +155,7 @@ protected function getListRenderable(array $form_data) { * * @see self::loadEntityFromBulkFormKey() */ - public static function calculateEntityBulkFormKey(EntityInterface $entity, $base_field_value) { + public static function calculateEntityBulkFormKey(EntityInterface $entity, $base_field_value, $row_index) { // We don't really need the entity ID or type ID, since only the // base field value and language are used to select rows, but // other modules may need those values. @@ -163,6 +164,7 @@ public static function calculateEntityBulkFormKey(EntityInterface $entity, $base $entity->language()->getId(), $entity->getEntityTypeId(), $entity->id(), + $row_index, ]; // An entity ID could be an arbitrary string (although they are typically 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 f133287f73ead593447e3b7112a12d2a384a0fc6..1b082cee5a4856f9c89c7ba2482d4e916b545e9f 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 @@ -637,7 +637,8 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { if ($entity = $this->getEntity($row)) { $bulk_form_keys[$row_index] = self::calculateEntityBulkFormKey( $entity, - $row->{$base_field} + $row->{$base_field}, + $row_index ); $entity_labels[$row_index] = $entity->label(); } @@ -649,7 +650,7 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { // displayed, but not when the form is being built before submission // (data is subject to change - new entities added or deleted after // the form display). TODO: consider using $form_state->set() instead. - if (empty($form_state->getUserInput())) { + if (empty($form_state->getUserInput()['op'])) { $this->updateTempstoreData($bulk_form_keys); } else { @@ -780,9 +781,10 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { $form['header'][$this->options['id']]['multipage'] = [ '#type' => 'details', '#open' => FALSE, - '#title' => $this->t('Selected %count items in this view', [ - '%count' => $count, - ]), + '#title' => $this->formatPlural($count, + 'Selected 1 item in this view', + 'Selected @count items in this view' + ), '#attributes' => [ // Add view_id and display_id to be available for // js multipage selector functionality. @@ -935,17 +937,12 @@ public function viewsFormSubmit(array &$form, FormStateInterface $form_state) { } // Update exclude mode setting. - if ($form_state->getValue('select_all') && !empty($this->tempStoreData['list'])) { - $this->tempStoreData['exclude_mode'] = TRUE; - } - else { - $this->tempStoreData['exclude_mode'] = FALSE; - } + $this->tempStoreData['exclude_mode'] = !empty($select_all); // Routing - determine redirect route. // // Set default redirection due to issue #2952498. - // TODO: remove the next line when core cause is eliminated. + // @todo remove the next line when core cause is eliminated. $redirect_route = 'views_bulk_operations.execute_batch'; if ($this->options['form_step'] && $configurable) { diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php index 1a0bdb3752909c4a4b1ebbd8ef269ece17de45a4..c3f69a63561035b6967a20e3f5457c32a67f6dc6 100644 --- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php +++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php @@ -2,13 +2,13 @@ namespace Drupal\views_bulk_operations\Service; +use Drupal\Component\EventDispatcher\Event; +use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Core\Action\ActionManager; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Symfony\Component\EventDispatcher\Event; -use Drupal\Component\Plugin\Exception\PluginNotFoundException; /** * Defines Views Bulk Operations action manager. @@ -119,7 +119,11 @@ protected function findDefinitions() { } // If this plugin was provided by a module that does not exist, remove the // plugin definition. - if (isset($plugin_definition['provider']) && !in_array($plugin_definition['provider'], ['core', 'component']) && !$this->providerExists($plugin_definition['provider'])) { + if ( + isset($plugin_definition['provider']) && + !in_array($plugin_definition['provider'], ['core', 'component']) && + !$this->providerExists($plugin_definition['provider']) + ) { unset($definitions[$plugin_id]); } } @@ -212,7 +216,8 @@ protected function alterDefinitions(&$definitions) { $event = new Event(); $event->alterParameters = $this->alterParameters; $event->definitions = &$definitions; - $this->eventDispatcher->dispatch(static::ALTER_ACTIONS_EVENT, $event); + + $this->eventDispatcher->dispatch($event, static::ALTER_ACTIONS_EVENT); // Include the expected behaviour (hook system) to avoid security issues. parent::alterDefinitions($definitions); diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php index c40294a84a2bb3793607f56732ed42b5a775cdbb..eb350cddb12aacf516edceb78ba69d394b27e0b5 100644 --- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php +++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php @@ -2,12 +2,14 @@ namespace Drupal\views_bulk_operations\Service; +use Drupal\Core\Access\AccessResultReasonInterface; use Drupal\views\Views; use Drupal\Core\Session\AccountProxyInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Entity\EntityInterface; use Drupal\views_bulk_operations\ViewsBulkOperationsBatch; +use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionInterface; /** * Defines VBO action processor. @@ -118,7 +120,7 @@ public function __construct( /** * {@inheritdoc} */ - public function initialize(array $view_data, $view = NULL) { + public function initialize(array $view_data, $view = NULL): void { // It may happen that the service was already initialized // in this request (e.g. multiple Batch API operation calls). @@ -159,7 +161,7 @@ public function initialize(array $view_data, $view = NULL) { * @param mixed $view * The current view object or NULL. */ - protected function setView($view = NULL) { + protected function setView($view = NULL): void { if (!is_null($view)) { $this->view = $view; } @@ -210,15 +212,15 @@ public function getPageList($page) { $this->view->setExposedInput(['_views_bulk_operations_override' => TRUE]); } + $base_field = $this->view->storage->get('base_field'); + // In some cases we may encounter nondeterministic behaviour in // db queries with sorts allowing different order of results. // To fix this we're removing all sorts and setting one sorting // rule by the view base id field. - $sorts = $this->view->getHandlers('sort'); - foreach ($sorts as $id => $sort) { + foreach (array_keys($this->view->getHandlers('sort')) as $id) { $this->view->setHandler($this->bulkFormData['display_id'], 'sort', $id, NULL); } - $base_field = $this->view->storage->get('base_field'); $this->view->setHandler($this->bulkFormData['display_id'], 'sort', $base_field, [ 'id' => $base_field, 'table' => $this->view->storage->get('base_table'), @@ -226,7 +228,7 @@ public function getPageList($page) { 'order' => 'ASC', 'relationship' => 'none', 'group_type' => 'group', - 'exposed' => 'FALSE', + 'exposed' => FALSE, 'plugin_id' => 'standard', ]); @@ -245,7 +247,6 @@ public function getPageList($page) { $this->moduleHandler->invokeAll('views_pre_execute', [$this->view]); $this->view->query->execute($this->view); - $base_field = $this->view->storage->get('base_field'); foreach ($this->view->result as $row) { $entity = $this->viewDataService->getEntity($row); @@ -352,6 +353,13 @@ public function populateQueue(array $data, array &$context = []) { // query. Give those modules the opportunity to alter the query again. $this->view->query->alter($this->view); + // Use a different pager ID so we don't break the real pager. + // @todo Check if we can use something else to set this value. + $pager = $this->view->getPager(); + if (array_key_exists('id', $pager->options)) { + $pager->options['id'] += (1000 + $this->view->getItemsPerPage()); + } + // Execute the view. $this->moduleHandler->invokeAll('views_pre_execute', [$this->view]); $this->view->query->execute($this->view); @@ -449,8 +457,21 @@ public function process() { // Check access. foreach ($this->queue as $delta => $entity) { - if (!$this->action->access($entity, $this->currentUser)) { - $output[] = $this->t('Access denied'); + $accessResult = $this->action->access($entity, $this->currentUser, TRUE); + if ($accessResult->isAllowed() === FALSE) { + $message = $this->t('Access denied'); + + // If we're given a reason why access was denied, display it. + if ($accessResult instanceof AccessResultReasonInterface) { + $reason = $accessResult->getReason(); + if (!empty($reason)) { + $message = $this->t('Access denied: @reason', [ + '@reason' => $accessResult->getReason(), + ]); + } + } + + $output[] = $message; unset($this->queue[$delta]); } } @@ -473,10 +494,21 @@ public function process() { * {@inheritdoc} */ public function executeProcessing(array &$data, $view = NULL) { - if ($data['exclude_mode'] && empty($data['exclude_list'])) { + if (empty($data['prepopulated']) && $data['exclude_mode'] && empty($data['exclude_list'])) { $data['exclude_list'] = $data['list']; $data['list'] = []; } + + // Get action finished callable. + $definition = $this->actionManager->getDefinition($data['action_id']); + if (in_array(ViewsBulkOperationsActionInterface::class, class_implements($definition['class']), TRUE)) { + $data['finished_callback'] = [$definition['class']]; + } + else { + $data['finished_callback'] = [ViewsBulkOperationsBatch::class]; + } + $data['finished_callback'][] = 'finished'; + if ($data['batch']) { $batch = ViewsBulkOperationsBatch::getBatch($data); batch_set($batch); @@ -495,7 +527,7 @@ public function executeProcessing(array &$data, $view = NULL) { foreach ($batch_results as $result) { $results['operations'][] = (string) $result; } - ViewsBulkOperationsBatch::finished(TRUE, $results, []); + $data['finished_callback'](TRUE, $results, []); } } diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php index 0b71658de9fc5d3e94ee7df8b3ffc571f86c6182..93f4ccc8ee855574315aebdd846fb29f9400c20c 100644 --- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php +++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php @@ -49,7 +49,7 @@ class ViewsBulkOperationsViewData implements ViewsBulkOperationsViewDataInterfac * * @var array */ - protected $data; + protected $data = []; /** * Entity type ids returned by this view. @@ -91,7 +91,9 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, $relation // Get view entity types and results fetcher callable. $event = new ViewsBulkOperationsEvent($this->getViewProvider(), $this->getData(), $view); - $this->eventDispatcher->dispatch(ViewsBulkOperationsEvent::NAME, $event); + + $this->eventDispatcher->dispatch($event, ViewsBulkOperationsEvent::NAME); + $this->entityTypeIds = $event->getEntityTypeIds(); $this->entityGetter = $event->getEntityGetter(); } @@ -110,18 +112,21 @@ public function getEntityTypeIds() { * Part of views data that refers to the current view. */ protected function getData() { - if (!$this->data) { + if (!empty($this->relationship) && $this->relationship != 'none') { + $relationship = $this->displayHandler->getOption('relationships')[$this->relationship]; + $table_data = $viewsData->get($relationship['table']); + $key = $table_data[$relationship['field']]['relationship']['base']; + } + else { + $key = $this->view->storage->get('base_table'); + } + + if (!array_key_exists($key, $this->data)) { $viewsData = Views::viewsData(); - if (!empty($this->relationship) && $this->relationship != 'none') { - $relationship = $this->displayHandler->getOption('relationships')[$this->relationship]; - $table_data = $viewsData->get($relationship['table']); - $this->data = $viewsData->get($table_data[$relationship['field']]['relationship']['base']); - } - else { - $this->data = $viewsData->get($this->view->storage->get('base_table')); - } + $this->data[$key] = $viewsData->get($key); } - return $this->data; + + return $this->data[$key]; } /** @@ -220,7 +225,7 @@ public function getTotalResults($clear_on_exposed = FALSE) { $total_results = $view->total_rows; } - if (!empty($pager_options) && !empty($pager_options['id'])) { + if (!empty($pager_options) && isset($pager_options['id'])) { $this->pagerManager->createPager($pager_options['total_items'], $pager_options['items_per_page'], $pager_options['id']); } diff --git a/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php b/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php index 97356a16fa3cac36b3a57cfb060fb4e2ef666d6f..005b860ed5477be83398a75a6f712676d813d07f 100644 --- a/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php +++ b/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php @@ -3,30 +3,14 @@ namespace Drupal\views_bulk_operations; use Drupal\Core\Url; -use Symfony\Component\HttpFoundation\RedirectResponse; +use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionCompletedTrait; /** * Defines module Batch API methods. */ class ViewsBulkOperationsBatch { - /** - * Translation function wrapper. - * - * @see \Drupal\Core\StringTranslation\TranslationInterface:translate() - */ - public static function t($string, array $args = [], array $options = []) { - return \Drupal::translation()->translate($string, $args, $options); - } - - /** - * Set message function wrapper. - * - * @see \Drupal\Core\Messenger\MessengerInterface - */ - public static function message($message = NULL, $type = 'status', $repeat = TRUE) { - \Drupal::messenger()->addMessage($message, $type, $repeat); - } + use ViewsBulkOperationsActionCompletedTrait; /** * Gets the list of entities to process. @@ -65,7 +49,7 @@ public static function getList(array $data, array &$context) { if ($context['sandbox']['page'] <= $context['sandbox']['npages']) { $context['finished'] = 0; $context['finished'] = $context['sandbox']['processed'] / $context['sandbox']['total']; - $context['message'] = static::t('Prepared @count of @total entities for processing.', [ + $context['message'] = static::translate('Prepared @count of @total entities for processing.', [ '@count' => $context['sandbox']['processed'], '@total' => $context['sandbox']['total'], ]); @@ -134,44 +118,13 @@ public static function operation(array $data, array &$context) { $context['finished'] = 0; $context['finished'] = $context['sandbox']['processed'] / $context['sandbox']['total']; - $context['message'] = static::t('Processed @count of @total entities.', [ + $context['message'] = static::translate('Processed @count of @total entities.', [ '@count' => $context['sandbox']['processed'], '@total' => $context['sandbox']['total'], ]); } } - /** - * Batch finished callback. - * - * @param bool $success - * Was the process successful? - * @param array $results - * Batch process results array. - * @param array $operations - * Performed operations array. - */ - public static function finished($success, array $results, array $operations) { - if ($success) { - $operations = array_count_values($results['operations']); - $details = []; - foreach ($operations as $op => $count) { - $details[] = $op . ' (' . $count . ')'; - } - $message = static::t('Action processing results: @operations.', [ - '@operations' => implode(', ', $details), - ]); - static::message($message); - if (isset($results['redirect_url'])) { - return new RedirectResponse($results['redirect_url']->setAbsolute()->toString()); - } - } - else { - $message = static::t('Finished with an error.'); - static::message($message, 'error'); - } - } - /** * Batch builder function. * @@ -192,14 +145,14 @@ public static function getBatch(array &$view_data) { ]); $batch = [ - 'title' => static::t('Prepopulating entity list for processing.'), + 'title' => static::translate('Prepopulating entity list for processing.'), 'operations' => [ [ [$current_class, 'getList'], [$view_data], ], ], - 'progress_message' => static::t('Prepopulating, estimated time left: @estimate, elapsed: @elapsed.'), + 'progress_message' => static::translate('Prepopulating, estimated time left: @estimate, elapsed: @elapsed.'), 'finished' => [$current_class, 'saveList'], ]; } @@ -207,15 +160,15 @@ public static function getBatch(array &$view_data) { // Execute action. else { $batch = [ - 'title' => static::t('Performing @operation on selected entities.', ['@operation' => $view_data['action_label']]), + 'title' => static::translate('Performing @operation on selected entities.', ['@operation' => $view_data['action_label']]), 'operations' => [ [ [$current_class, 'operation'], [$view_data], ], ], - 'progress_message' => static::t('Processing, estimated time left: @estimate, elapsed: @elapsed.'), - 'finished' => [$current_class, 'finished'], + 'progress_message' => static::translate('Processing, estimated time left: @estimate, elapsed: @elapsed.'), + 'finished' => $view_data['finished_callback'], ]; } diff --git a/web/modules/views_bulk_operations/src/ViewsBulkOperationsEvent.php b/web/modules/views_bulk_operations/src/ViewsBulkOperationsEvent.php index 44ad3e522c685f59211d6c947b09e57f5b768e88..595786ad45f3709dd55ae3fc8bbac86680952284 100644 --- a/web/modules/views_bulk_operations/src/ViewsBulkOperationsEvent.php +++ b/web/modules/views_bulk_operations/src/ViewsBulkOperationsEvent.php @@ -2,7 +2,7 @@ namespace Drupal\views_bulk_operations; -use Symfony\Component\EventDispatcher\Event; +use Drupal\Component\EventDispatcher\Event; use Drupal\views\ViewExecutable; /** 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 67241da11baf697cb2ca36564c2418d18bc5a86c..753a67df8e7d5364069d23be86011bfe63be49ea 100644 --- a/web/modules/views_bulk_operations/tests/src/Functional/DrushCommandsTest.php +++ b/web/modules/views_bulk_operations/tests/src/Functional/DrushCommandsTest.php @@ -24,7 +24,7 @@ class DrushCommandsTest extends BrowserTestBase { * * @var array */ - public static $modules = [ + protected static $modules = [ 'node', 'views', 'views_bulk_operations', @@ -34,7 +34,7 @@ class DrushCommandsTest extends BrowserTestBase { /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); // Create some nodes for testing. @@ -61,14 +61,19 @@ protected function setUp() { * Tests the VBO Drush command. */ public function testDrushCommand() { + $arguments = [ + 'views_bulk_operations_test', + 'views_bulk_operations_simple_test_action', + ]; + // Basic test. - $this->drush('vbo-exec', ['views_bulk_operations_test', 'views_bulk_operations_simple_test_action']); + $this->drush('vbo-exec', $arguments); 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']); + $this->drush('vbo-exec', $arguments, ['exposed' => 'sticky=1']); for ($i = 0; $i < self::TEST_NODE_COUNT; $i++) { $test_string = "Test action (preconfig: , label: Title $i)"; if ($i % 2) { 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 4a981ef31cd8cdcfe5b1eb636cb209a44a23b5ee..47d1a6e3a7bacb6b8bfc7f19a6a2fbe683136dc6 100644 --- a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php +++ b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php @@ -2,8 +2,6 @@ namespace Drupal\Tests\views_bulk_operations\Functional; -use Drupal\Component\Render\FormattableMarkup; - /** * @coversDefaultClass \Drupal\views_bulk_operations\Plugin\views\field\ViewsBulkOperationsBulkForm * @group views_bulk_operations @@ -27,8 +25,7 @@ public function testViewsBulkOperationsBulkFormSimple() { // the correct label. for ($i = 0; $i < 4; $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()); + $assertSession->fieldExists($checkbox_selector); } // The advanced action should not be shown on the form - no permission. @@ -48,15 +45,10 @@ public function testViewsBulkOperationsBulkFormSimple() { $preconfig_setting = $configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['selected_actions'][0]['preconfiguration']['preconfig']; foreach ($selected as $index) { - $assertSession->pageTextContains( - sprintf('Test action (preconfig: %s, label: %s)', - $preconfig_setting, - $this->testNodes[$index]->label() - ), - sprintf('Action has been executed on node "%s".', - $this->testNodes[$index]->label() - ) - ); + $assertSession->pageTextContains(sprintf('Test action (preconfig: %s, label: %s)', + $preconfig_setting, + $this->testNodes[$index]->label() + )); } // Test the select all functionality. @@ -67,10 +59,7 @@ public function testViewsBulkOperationsBulkFormSimple() { $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), - sprintf('Action has been executed on %d nodes.', self::TEST_NODE_COUNT) - ); + $assertSession->pageTextContains(sprintf('Action processing results: Test (%d).', self::TEST_NODE_COUNT)); } @@ -85,7 +74,10 @@ public function testViewsBulkOperationsBulkFormAdvanced() { // 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', 'execute advanced test action']); + $admin_user = $this->drupalCreateUser([ + 'edit any page content', + 'execute advanced test action', + ]); $this->drupalLogin($admin_user); // First execute the simple action to test @@ -94,10 +86,7 @@ public function testViewsBulkOperationsBulkFormAdvanced() { $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)), - sprintf('Action has been executed on %d nodes.', count($selected)) - ); + $assertSession->pageTextContains(sprintf('Action processing results: Test (%d).', count($selected))); // Execute the advanced test action. $selected = [0, 1, 3]; @@ -106,7 +95,7 @@ public function testViewsBulkOperationsBulkFormAdvanced() { // Check if the configuration form is open and contains the // test_config field. - $assertSession->fieldExists('edit-test-config', NULL, 'The configuration field appears.'); + $assertSession->fieldExists('edit-test-config'); // Check if the configuration form contains selected entity labels. // NOTE: The view pager has an offset set on this view, so checkbox @@ -119,11 +108,11 @@ public function testViewsBulkOperationsBulkFormAdvanced() { $edit = [ 'test_config' => $config_value, ]; - $this->drupalPostForm(NULL, $edit, t('Apply')); + $this->submitForm($edit, t('Apply')); // Execute action by posting the confirmation form // (also tests if the submit button exists on the page). - $this->drupalPostForm(NULL, [], t('Execute action')); + $this->submitForm([], t('Execute action')); // If all went well and Batch API did its job, // the next page should display results. @@ -151,15 +140,13 @@ public function testViewsBulkOperationsBulkFormAdvanced() { foreach ([0, 2] as $index) { $edit["views_bulk_operations_bulk_form[$index]"] = TRUE; } - $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); - $this->drupalPostForm(NULL, ['test_config' => 'unpublish'], t('Apply')); - $this->drupalPostForm(NULL, [], t('Execute action')); + $this->submitForm($edit, t('Apply to selected items')); + $this->submitForm(['test_config' => 'unpublish'], t('Apply')); + $this->submitForm([], t('Execute action')); // Again, take offset into account (-1), also take 2 excluded // rows into account (-2). - $assertSession->pageTextContains( - sprintf('Action processing results: Test (%d).', (count($this->testNodes) - 3)), - sprintf('Action has been executed on all %d nodes.', (count($this->testNodes) - 3)) - ); + // Also, check if the custom completed message appears. + $assertSession->pageTextContains(sprintf('Custom processing message: Test (%d).', (count($this->testNodes) - 3))); $this->assertNotEmpty((count($this->cssSelect('table.vbo-table tbody tr')) === 2), "The view shows only excluded results."); } @@ -232,7 +219,7 @@ public function testViewsBulkOperationsBulkFormPassing() { } $this->drupalGet('views-bulk-operations-test-advanced', $options); - $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + $this->submitForm($edit, t('Apply to selected items')); // On batch-enabled processes check if provided context data is correct. if ($case['batch']) { @@ -251,7 +238,7 @@ public function testViewsBulkOperationsBulkFormPassing() { 'Processed %s of %s.', $processed, $total - ), 'The correct processed info message appears.'); + )); } } diff --git a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsFunctionalTestBase.php b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsFunctionalTestBase.php index eb7fd6f615a9b125112873ef6d0954ee1b2df8fd..ce01362df83086101f4dac4b4ad0d0f6035b54bf 100644 --- a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsFunctionalTestBase.php +++ b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsFunctionalTestBase.php @@ -22,7 +22,7 @@ abstract class ViewsBulkOperationsFunctionalTestBase extends BrowserTestBase { * * @var array */ - public static $modules = [ + protected static $modules = [ 'node', 'views', 'views_bulk_operations', @@ -32,7 +32,7 @@ abstract class ViewsBulkOperationsFunctionalTestBase extends BrowserTestBase { /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); // Create some nodes for testing. @@ -56,7 +56,7 @@ protected function setUp() { } /** - * Helper function that gets configuration for a selected view. + * Helper function that executes en operation. * * @param string|null $path * The path of the View page that includes VBO. @@ -71,7 +71,10 @@ protected function executeAction($path, TranslatableMarkup $button_text, array $ foreach ($selection as $index) { $data["views_bulk_operations_bulk_form[$index]"] = TRUE; } - $this->drupalPostForm($path, $data, $button_text); + if ($path !== NULL) { + $this->drupalGet($path); + } + $this->submitForm($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 6468565a67d40101ca94b9d0e054993d2fcdc1db..60f20ebdd86c02e76a20f9f7762fdd0fae19e431 100644 --- a/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php +++ b/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php @@ -64,7 +64,7 @@ class ViewsBulkOperationsBulkFormTest extends WebDriverTestBase { * * @var array */ - public static $modules = [ + protected static $modules = [ 'node', 'views', 'views_bulk_operations', @@ -74,13 +74,13 @@ class ViewsBulkOperationsBulkFormTest extends WebDriverTestBase { /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); // Create some nodes for testing. $this->drupalCreateContentType(['type' => 'page']); for ($i = 0; $i <= self::TEST_NODE_COUNT; $i++) { - $node = $this->drupalCreateNode([ + $this->drupalCreateNode([ 'type' => 'page', 'title' => 'Title ' . $i, ]); @@ -96,13 +96,20 @@ protected function setUp() { $this->assertSession = $this->assertSession(); $this->page = $this->getSession()->getPage(); + $testViewConfig = \Drupal::service('config.factory')->getEditable('views.view.' . self::TEST_VIEW_ID); + // Get useful config data from the test view. - $config_data = \Drupal::service('config.factory')->get('views.view.' . self::TEST_VIEW_ID)->getRawData(); + $config_data = $testViewConfig->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'], ]; + // Enable AJAX on the view. + $config_data['display']['default']['display_options']['use_ajax'] = TRUE; + $testViewConfig->setData($config_data); + $testViewConfig->save(); + $this->drupalGet('/' . $this->testViewParams['path']); } @@ -119,16 +126,17 @@ public function testViewsBulkOperationsAjaxUi() { // 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); + $this->page->checkField('views_bulk_operations_bulk_form[' . $selected_index . ']'); } // Go to the next page and select some more. $this->page->clickLink('Go to next page'); + $this->assertSession->assertWaitOnAjaxRequest(); 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); + $this->page->checkField('views_bulk_operations_bulk_form[' . $selected_index . ']'); } // Execute test operation. @@ -157,7 +165,7 @@ 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); + $this->page->checkField('views_bulk_operations_bulk_form[' . $selected_index . ']'); } // Insert nodes. diff --git a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsActionProcessorTest.php b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsActionProcessorTest.php index 7f250106df2c24454288d76dd6c155d68d447739..d185ee97d73078fa2a92b05e14d47e80faa9bc9b 100644 --- a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsActionProcessorTest.php +++ b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsActionProcessorTest.php @@ -11,7 +11,7 @@ class ViewsBulkOperationsActionProcessorTest extends ViewsBulkOperationsKernelTe /** * {@inheritdoc} */ - public function setUp() { + public function setUp(): void { parent::setUp(); $this->createTestNodes([ diff --git a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsDataServiceTest.php b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsDataServiceTest.php index 1a004a32b1d45d9f162240ad1f8ee65e7498762f..ccb681b5622a905fbd677e62efcede7f66bdd919 100644 --- a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsDataServiceTest.php +++ b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsDataServiceTest.php @@ -13,7 +13,7 @@ class ViewsBulkOperationsDataServiceTest extends ViewsBulkOperationsKernelTestBa /** * {@inheritdoc} */ - public function setUp() { + public function setUp(): void { parent::setUp(); $this->createTestNodes([ diff --git a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php index 032e3a45cc72c81adcda62fc8d57a4682d906922..7fbce1057b175fa77c0a227830277bf791bc5229 100644 --- a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php +++ b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php @@ -61,7 +61,7 @@ abstract class ViewsBulkOperationsKernelTestBase extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = [ + protected static $modules = [ 'user', 'node', 'field', @@ -79,14 +79,13 @@ abstract class ViewsBulkOperationsKernelTestBase extends KernelTestBase { /** * {@inheritdoc} */ - public function setUp() { + public function setUp(): void { parent::setUp(); $this->installEntitySchema('user'); $this->installEntitySchema('node'); $this->installSchema('node', 'node_access'); $this->installSchema('system', 'sequences'); - $this->installSchema('system', 'key_value_expire'); $user = User::create(); $user->setPassword('password'); diff --git a/web/modules/views_bulk_operations/tests/src/Unit/TestViewsBulkOperationsBatch.php b/web/modules/views_bulk_operations/tests/src/Unit/TestViewsBulkOperationsBatch.php index cbf30569b30c26822ab91755c749dfc8cad6f599..0d933ed428fc715e175a6b3a6371daeb48c5760b 100644 --- a/web/modules/views_bulk_operations/tests/src/Unit/TestViewsBulkOperationsBatch.php +++ b/web/modules/views_bulk_operations/tests/src/Unit/TestViewsBulkOperationsBatch.php @@ -12,7 +12,7 @@ class TestViewsBulkOperationsBatch extends ViewsBulkOperationsBatch { /** * Override t method. */ - public static function t($string, array $args = [], array $options = []) { + public static function translate($string, array $args = [], array $options = []) { return strtr($string, $args); } diff --git a/web/modules/views_bulk_operations/tests/src/Unit/ViewsBulkOperationsBatchTest.php b/web/modules/views_bulk_operations/tests/src/Unit/ViewsBulkOperationsBatchTest.php index 3ba5a30b802a27a6f0ef8629262a4385fb2aa91a..1d71b44193355a2aed4f7767266e0b327125ca67 100644 --- a/web/modules/views_bulk_operations/tests/src/Unit/ViewsBulkOperationsBatchTest.php +++ b/web/modules/views_bulk_operations/tests/src/Unit/ViewsBulkOperationsBatchTest.php @@ -17,12 +17,12 @@ class ViewsBulkOperationsBatchTest extends UnitTestCase { * * @var array */ - public static $modules = ['node']; + protected static $modules = ['node']; /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->container = new ContainerBuilder(); @@ -67,6 +67,7 @@ public function testGetBatch() { 'list' => [[0, 'en', 'node', 1]], 'some_data' => [], 'action_label' => '', + 'finished_callback' => [TestViewsBulkOperationsBatch::class, 'finished'], ]; $batch = TestViewsBulkOperationsBatch::getBatch($data); $this->assertArrayHasKey('title', $batch); @@ -128,6 +129,7 @@ public function testOperation() { 'display_id' => 'test_display', 'batch_size' => $batch_size, 'list' => [], + 'finished_callback' => [TestViewsBulkOperationsBatch::class, 'finished'], ]; $context = [ 'sandbox' => [ 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 911613c93e247ab54a2761e9d0dc17530eb54406..5723efcc51269db5c705fc4e4e41caa225b02990 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 @@ -175,10 +175,12 @@ display: action_id: views_bulk_operations_simple_test_action preconfiguration: label_override: 'Simple test action' + add_confirmation: false preconfig: 'Test setting' - action_id: views_bulk_operations_advanced_test_action preconfiguration: + add_confirmation: false preconfig: 'Test setting' plugin_id: views_bulk_operations_bulk_form filters: diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test_advanced.yml b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test_advanced.yml index f19c26356332f537a69a7af12ab87a70acfa0338..56fee531564f2c0aef0e29675fa10b1a661988b4 100644 --- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test_advanced.yml +++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/install/views.view.views_bulk_operations_test_advanced.yml @@ -173,10 +173,12 @@ display: action_id: views_bulk_operations_simple_test_action preconfiguration: label_override: 'Simple test action' + add_confirmation: false preconfig: 'Test setting' 1: action_id: views_bulk_operations_advanced_test_action preconfiguration: + add_confirmation: false test_preconfig: 'Test setting' 2: action_id: views_bulk_operations_passing_test_action diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/schema/views_bulk_operations_test.schema.yml b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/schema/views_bulk_operations_test.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..a9b1711e02210adb0554fa8d6c16839151ac6390 --- /dev/null +++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/config/schema/views_bulk_operations_test.schema.yml @@ -0,0 +1,27 @@ +# Since 4.x preliminary configuration schema should be defined for actions +# that have additional configuration. +views_bulk_operations.action_config.views_bulk_operations_advanced_test_action: + type: views_bulk_operations_action_config + label: 'Test preliminary configuration 1' + mapping: + # This key is in the action configuration form, so it can be set in + # the UI. + test_preconfig: + type: string + label: 'Another configuration item' + # This key is not in the action configuration form but is set directly + # in the view config so needs to be included here as well. + # @todo remove this from tests or add that config form field to avoid + # confusion. + preconfig: + type: string + label: 'Preconfig' + +views_bulk_operations.action_config.views_bulk_operations_simple_test_action: + type: views_bulk_operations_action_config + label: 'Test preliminary configuration 1' + mapping: + preconfig: + type: string + label: 'Preconfig' + diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsAdvancedTestAction.php b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsAdvancedTestAction.php index ef16c633ced18a4dd2c2d074627778ad6cb6b905..88872565023719cd40071d82c78edf7753d5270c 100644 --- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsAdvancedTestAction.php +++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsAdvancedTestAction.php @@ -9,6 +9,7 @@ use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Session\AccountInterface; use Drupal\views\ViewExecutable; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Action for test purposes only. @@ -83,7 +84,7 @@ public function buildPreConfigurationForm(array $element, array $values, FormSta */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form['test_config'] = [ - '#title' => t('Config'), + '#title' => $this->t('Config'), '#type' => 'textfield', '#default_value' => $form_state->getValue('config'), ]; @@ -97,4 +98,22 @@ public function access($object, AccountInterface $account = NULL, $return_as_obj return $object->access('update', $account, $return_as_object); } + /** + * {@inheritdoc} + */ + public static function finished($success, array $results, array $operations): ?RedirectResponse { + // Let's return a bit different message. We don't except faliures + // in tests as well so no need to check for a success. + $operations = array_count_values($results['operations']); + $details = []; + foreach ($operations as $op => $count) { + $details[] = $op . ' (' . $count . ')'; + } + $message = static::translate('Custom processing message: @operations.', [ + '@operations' => implode(', ', $details), + ]); + static::message($message); + return NULL; + } + } 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 e7aad18fb5e05ce00e2486681e9db2380f379b99..04444fe3243c683fcf8b2b4c30d81a159f6f688a 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 2021-04-29 -version: '8.x-3.13' +# Information added by Drupal.org packaging script on 2022-03-19 +version: '4.1.2' project: 'views_bulk_operations' -datestamp: 1619697069 +datestamp: 1647723470 diff --git a/web/modules/views_bulk_operations/views_bulk_operations.drush.inc b/web/modules/views_bulk_operations/views_bulk_operations.drush.inc deleted file mode 100644 index 8ac8bd17600bca0fbf10920e1312110187a7043d..0000000000000000000000000000000000000000 --- a/web/modules/views_bulk_operations/views_bulk_operations.drush.inc +++ /dev/null @@ -1,207 +0,0 @@ -<?php - -/** - * @file - * Contains code providing drush commands functionality. - */ - -use Drupal\views\Views; -use Drupal\views_bulk_operations\ViewsBulkOperationsBatch; - -/** - * Implements hook_drush_command(). - */ -function views_bulk_operations_drush_command() { - return [ - 'views-bulk-operations-execute' => [ - 'description' => 'Execute an action on all results of the given view.', - 'aliases' => ['vbo-execute', 'vbo-exec'], - 'arguments' => [ - 'view_id' => 'The ID of the view to use', - 'action_id' => 'The ID of the action to execute', - ], - 'options' => [ - 'display-id' => 'ID of the display to use (default: default)', - 'args' => 'View arguments (slash is a delimiter, default: none)', - 'exposed' => 'Exposed filters (query string format)', - 'batch-size' => 'Processing batch size (default: 100)', - 'config' => 'Action configuration (query string format)', - 'debug' => 'Include additional debugging information.', - ], - 'examples' => [ - 'drush vbo-execute some_view some_action --user=1' => 'Execute some action on some view as the superuser.', - 'drush vbo-execute some_view some_action --args=arg1/arg2 --batch-size=50' => 'Execute some action on some view with arg1 and arg2 as view arguments and 50 entities processed per batch.', - 'drush vbo-execute some_view some_action --config="key1=value1&key2=value2"' => 'Execute some action on some view with action configuration set.', - ], - ], - ]; -} - -/** - * Helper function to set / get timer. - * - * @param bool $debug - * Should the function do anything at all? - * @param string $id - * ID of a specific timer span. - * - * @return mixed - * NULL or value of a specific timer if set. - */ -function _views_bulk_operations_timer($debug = TRUE, $id = NULL) { - if (!$debug) { - return; - } - - static $timers = []; - - if (!isset($id)) { - $timers['start'] = microtime(TRUE); - } - else { - if (isset($timers[$id])) { - end($timers); - do { - if (key($timers) === $id) { - return round((current($timers) - prev($timers)) * 1000, 3); - } - else { - $result = prev($timers); - } - } while ($result); - } - else { - $timers[$id] = microtime(TRUE); - } - } -} - -/** - * The vbo-exec command execution function. - * - * @param string $view_id - * The ID of the view to use. - * @param string $action_id - * The ID of the action to execute. - */ -function drush_views_bulk_operations_execute($view_id, $action_id) { - - $debug = drush_get_option('debug', FALSE); - _views_bulk_operations_timer($debug); - - // Prepare parameters. - $arguments = drush_get_option('args', FALSE); - if ($arguments) { - $arguments = explode('/', $arguments); - } - - $qs_config = [ - 'config' => [], - 'exposed' => [], - ]; - foreach ($qs_config as $name => $value) { - $config_data = drush_get_option($name, []); - if (!empty($config_data)) { - parse_str($config_data, $qs_config[$name]); - } - } - - $vbo_data = [ - 'list' => [], - 'view_id' => $view_id, - 'display_id' => drush_get_option('display-id', 'default'), - 'action_id' => $action_id, - 'preconfiguration' => $qs_config['config'], - 'batch' => TRUE, - 'arguments' => $arguments, - 'exposed_input' => $qs_config['exposed'], - 'batch_size' => drush_get_option('batch-size', 100), - 'relationship_id' => 'none', - ]; - - // Initialize the view to check if parameters are correct. - if (!$view = Views::getView($vbo_data['view_id'])) { - drush_set_error('Incorrect view ID provided.'); - return; - } - if (!$view->setDisplay($vbo_data['display_id'])) { - drush_set_error('Incorrect view display ID provided.'); - return; - } - if (!empty($vbo_data['arguments'])) { - $view->setArguments($vbo_data['arguments']); - } - if (!empty($vbo_data['exposed_input'])) { - $view->setExposedInput($vbo_data['exposed_input']); - } - - // We need total rows count for proper progress message display. - $view->get_total_rows = TRUE; - $view->execute(); - - // Get relationship ID if VBO field exists. - $vbo_data['relationship_id'] = 'none'; - foreach ($view->field as $field) { - if ($field->options['id'] === 'views_bulk_operations_bulk_form') { - $vbo_data['relationship_id'] = $field->options['relationship']; - } - } - - // Get total rows count. - $viewDataService = \Drupal::service('views_bulk_operations.data'); - $viewDataService->init($view, $view->getDisplay(), $vbo_data['relationship_id']); - $vbo_data['total_results'] = $viewDataService->getTotalResults(); - - // Get action definition and check if action ID is correct. - try { - $action_definition = \Drupal::service('plugin.manager.views_bulk_operations_action')->getDefinition($action_id); - } - catch (\Exception $e) { - drush_set_error($e->getMessage()); - return; - } - $vbo_data['action_label'] = (string) $action_definition['label']; - - _views_bulk_operations_timer($debug, 'init'); - - // Populate entity list. - $context = []; - do { - $context['finished'] = 1; - $context['message'] = ''; - ViewsBulkOperationsBatch::getList($vbo_data, $context); - if (!empty($context['message'])) { - drush_log($context['message'], 'ok'); - } - } while ($context['finished'] < 1); - $vbo_data = $context['results']; - - _views_bulk_operations_timer($debug, 'list'); - - // Execute the selected action. - $context = []; - do { - $context['finished'] = 1; - $context['message'] = ''; - ViewsBulkOperationsBatch::operation($vbo_data, $context); - if (!empty($context['message'])) { - drush_log($context['message'], 'ok'); - } - } while ($context['finished'] < 1); - - // Output a summary message. - $operations = array_count_values($context['results']['operations']); - $details = []; - foreach ($operations as $op => $count) { - $details[] = $op . ' (' . $count . ')'; - } - drush_log(dt('Action processing results: @results.', ['@results' => implode(', ', $details)]), 'ok'); - - // Display debug information. - if ($debug) { - _views_bulk_operations_timer($debug, 'execute'); - drush_print(sprintf('Initialization time: %d ms.', _views_bulk_operations_timer($debug, 'init'))); - drush_print(sprintf('Entity list generation time: %d ms.', _views_bulk_operations_timer($debug, 'list'))); - drush_print(sprintf('Execution time: %d ms.', _views_bulk_operations_timer($debug, 'execute'))); - } -} 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 2860f5e129ede5ec959d4296877339cc0a8602b4..f088cd9513669f2188ade4a817e577a354f84cad 100644 --- a/web/modules/views_bulk_operations/views_bulk_operations.info.yml +++ b/web/modules/views_bulk_operations/views_bulk_operations.info.yml @@ -2,11 +2,11 @@ type: module name: 'Views Bulk Operations' description: 'Adds an ability to perform bulk operations on selected entities from view results.' package: 'Views' -core_version_requirement: ^8.8 || ^9 +core_version_requirement: ^9 dependencies: - drupal:views -# Information added by Drupal.org packaging script on 2021-04-29 -version: '8.x-3.13' +# Information added by Drupal.org packaging script on 2022-03-19 +version: '4.1.2' project: 'views_bulk_operations' -datestamp: 1619697069 +datestamp: 1647723470 diff --git a/web/modules/views_bulk_operations/views_bulk_operations.libraries.yml b/web/modules/views_bulk_operations/views_bulk_operations.libraries.yml index bed306fba03904e50c448fe7a3bafc52aaf913b8..8bdbbf52028950ffea0f02a80acd8c245bb7409f 100644 --- a/web/modules/views_bulk_operations/views_bulk_operations.libraries.yml +++ b/web/modules/views_bulk_operations/views_bulk_operations.libraries.yml @@ -8,7 +8,7 @@ frontUi: dependencies: - core/drupal - core/jquery - - core/jquery.once + - core/once adminUi: version: 1.0 @@ -17,4 +17,4 @@ adminUi: dependencies: - core/drupal - core/jquery - - core/jquery.once + - core/once