From 82a884823e7e41e060f7b7e649b04b1e96f1e0a1 Mon Sep 17 00:00:00 2001
From: Brian Weaver <weaver.299@osu.edu>
Date: Wed, 5 Feb 2020 13:36:40 -0500
Subject: [PATCH] SECURITY update: views_bulk_operations -> 3.4

---
 composer.json                                 |   2 +-
 composer.lock                                 |  23 +--
 vendor/composer/installed.json                |  20 +--
 web/modules/views_bulk_operations/README.txt  |  11 +-
 .../views_bulk_operations/composer.json       |   4 +-
 .../views_bulk_operations.views.schema.yml    |   2 +-
 .../views_bulk_operations/js/frontUi.js       | 114 ++++---------
 .../actions_permissions.info.yml              |   9 +-
 .../ViewsBulkOperationExampleAction.php       |   2 +-
 .../views_bulk_operations_example.info.yml    |   9 +-
 .../src/Access/ViewsBulkOperationsAccess.php  |   6 +-
 .../Action/ViewsBulkOperationsActionBase.php  |  16 ++
 .../Commands/ViewsBulkOperationsCommands.php  |   4 +-
 .../ViewsBulkOperationsController.php         |  66 ++++---
 .../ViewsBulkOperationsEventSubscriber.php    |   2 +-
 .../src/Form/ConfigureAction.php              |  25 +--
 .../src/Form/ConfirmAction.php                |  17 +-
 .../src/Form/ViewsBulkOperationsFormTrait.php |  76 +++++++--
 .../src/Plugin/Action/CancelUserAction.php    |   6 +-
 .../field/ViewsBulkOperationsBulkForm.php     | 161 ++++++++++++------
 .../ViewsBulkOperationsActionManager.php      |   3 +
 .../ViewsBulkOperationsActionProcessor.php    | 134 ++++++++++-----
 ...BulkOperationsActionProcessorInterface.php |  17 +-
 .../Service/ViewsBulkOperationsViewData.php   |  64 ++++++-
 .../ViewsBulkOperationsViewDataInterface.php  |   5 +-
 .../src/ViewsBulkOperationsBatch.php          |  88 +++++-----
 .../ViewsBulkOperationsBulkFormTest.php       |  42 +++--
 ...ViewsBulkOperationsActionProcessorTest.php |  84 +++++++--
 .../ViewsBulkOperationsKernelTestBase.php     |  12 +-
 .../src/Unit/ViewsBulkOperationsBatchTest.php |   4 +-
 .../ViewsBulkOperationsAdvancedTestAction.php |   4 +-
 .../ViewsBulkOperationsPassTestAction.php     |   6 +-
 .../ViewsBulkOperationsSimpleTestAction.php   |   4 +-
 .../views_bulk_operations_test.info.yml       |   9 +-
 .../views_bulk_operations.info.yml            |  11 +-
 .../views_bulk_operations.services.yml        |   2 +-
 36 files changed, 678 insertions(+), 386 deletions(-)

diff --git a/composer.json b/composer.json
index 1f3ad6bb58..e48d28bf44 100644
--- a/composer.json
+++ b/composer.json
@@ -181,7 +181,7 @@
         "drupal/views_ajax_history": "^1.1",
         "drupal/views_autocomplete_filters": "1.1",
         "drupal/views_bootstrap": "3.1",
-        "drupal/views_bulk_operations": "^2.4",
+        "drupal/views_bulk_operations": "^3.0",
         "drupal/views_fieldsets": "3.3",
         "drupal/views_infinite_scroll": "1.5",
         "drupal/views_slideshow": "4.4",
diff --git a/composer.lock b/composer.lock
index 9390dd2fce..112fa313e6 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": "b50b24db351327a76dc297fa85641300",
+    "content-hash": "860bbfe67c7dcc55be713de1dfd94b59",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -4483,6 +4483,9 @@
                         "status": "not-covered",
                         "message": "Beta releases are not covered by Drupal security advisories."
                     }
+                },
+                "patches_applied": {
+                    "3060223": "https://www.drupal.org/files/issues/2019-10-17/%20entity_clone-corrupted-paragraph-cloning-3060223-5.patch"
                 }
             },
             "notification-url": "https://packages.drupal.org/8/downloads",
@@ -8134,29 +8137,29 @@
         },
         {
             "name": "drupal/views_bulk_operations",
-            "version": "2.5.0",
+            "version": "3.4.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/views_bulk_operations.git",
-                "reference": "8.x-2.5"
+                "reference": "8.x-3.4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-2.5.zip",
-                "reference": "8.x-2.5",
-                "shasum": "3ec848b4f235720d05cb9713ea80a86415b6909f"
+                "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.4.zip",
+                "reference": "8.x-3.4",
+                "shasum": "549eb149f82fbf30e975155a14cd7a0d4653dfe9"
             },
             "require": {
-                "drupal/core": "^8.4"
+                "drupal/core": "~8.5"
             },
             "type": "drupal-module",
             "extra": {
                 "branch-alias": {
-                    "dev-2.x": "2.x-dev"
+                    "dev-3.x": "3.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-2.5",
-                    "datestamp": "1550740384",
+                    "version": "8.x-3.4",
+                    "datestamp": "1580924754",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 7ac7ec2fac..a8e23e0a60 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -8395,30 +8395,30 @@
     },
     {
         "name": "drupal/views_bulk_operations",
-        "version": "2.5.0",
-        "version_normalized": "2.5.0.0",
+        "version": "3.4.0",
+        "version_normalized": "3.4.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/views_bulk_operations.git",
-            "reference": "8.x-2.5"
+            "reference": "8.x-3.4"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-2.5.zip",
-            "reference": "8.x-2.5",
-            "shasum": "3ec848b4f235720d05cb9713ea80a86415b6909f"
+            "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.4.zip",
+            "reference": "8.x-3.4",
+            "shasum": "549eb149f82fbf30e975155a14cd7a0d4653dfe9"
         },
         "require": {
-            "drupal/core": "^8.4"
+            "drupal/core": "~8.5"
         },
         "type": "drupal-module",
         "extra": {
             "branch-alias": {
-                "dev-2.x": "2.x-dev"
+                "dev-3.x": "3.x-dev"
             },
             "drupal": {
-                "version": "8.x-2.5",
-                "datestamp": "1550740384",
+                "version": "8.x-3.4",
+                "datestamp": "1580924754",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
diff --git a/web/modules/views_bulk_operations/README.txt b/web/modules/views_bulk_operations/README.txt
index 69a18df9f7..4c0e820c3c 100644
--- a/web/modules/views_bulk_operations/README.txt
+++ b/web/modules/views_bulk_operations/README.txt
@@ -23,7 +23,7 @@ Creating custom actions
 -----------------------
 
 Example that covers different possibilities is available in
-modules/views_bulk_operatios_example/.
+modules/views_bulk_operations_example/.
 
 In a module, create an action plugin (check the included example module,
 test actions in /tests/views_bulk_operations_test/src/Plugin/Action
@@ -41,9 +41,12 @@ Available annotation parameters:
     no confirmation step (default: empty string).
   - requirements: an array of requirements an action must meet
     to be displayed on the action selection form. At the moment
-    only one possible requirement is supported: '_permission', if
-    the current user has that permission, the action execution will
-    be possible.
+    two possible requirements are supported:
+    - '_permission', if the current user has that permission, the action
+      execution will be possible.
+    - '_custom_access', checks if the output of `customAccess` method from
+      the action class is TRUE (default implementation included in
+      ViewsBulkOperationsActionBase).
 
 
 Additional notes
diff --git a/web/modules/views_bulk_operations/composer.json b/web/modules/views_bulk_operations/composer.json
index 174bebe312..1affe7a543 100644
--- a/web/modules/views_bulk_operations/composer.json
+++ b/web/modules/views_bulk_operations/composer.json
@@ -15,7 +15,9 @@
   },
   "license": "GPL-2.0+",
   "minimum-stability": "dev",
-  "require": {},
+  "require": {
+    "drupal/core": "~8.5"
+  },
   "extra": {
     "drush": {
       "services": {
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 0fae1cbc73..febcb5480c 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
@@ -25,4 +25,4 @@ views.field.views_bulk_operations_bulk_form:
       label: 'Preliminary configuration array'
     clear_on_exposed:
       type: boolean
-      label: 'Clear on exposed'
+      label: 'Clear selection when exposed filters change'
diff --git a/web/modules/views_bulk_operations/js/frontUi.js b/web/modules/views_bulk_operations/js/frontUi.js
index 254c8a6557..bf89987d4b 100644
--- a/web/modules/views_bulk_operations/js/frontUi.js
+++ b/web/modules/views_bulk_operations/js/frontUi.js
@@ -58,6 +58,7 @@
      * Perform an AJAX request to update selection.
      *
      * @param {bool} state
+     * @param {mixed} index
      * @param {string} value
      */
     update: function (state, index, value) {
@@ -65,14 +66,24 @@
         value = null;
       }
       if (this.view_id.length && this.display_id.length) {
-        var list = {};
-        if (value && value != 'on') {
-          list[value] = this.list[index][value];
+        // TODO: prevent form submission when ajaxing.
+
+        var list = {}, op = '';
+        if (index === 'selection_method_change') {
+          var op = state ? 'method_include' : 'method_exclude';
+          if (!state) {
+            list = this.list[index];
+          }
         }
         else {
-          list = this.list[index];
+          if (value && value != 'on') {
+            list[value] = this.list[index][value];
+          }
+          else {
+            list = this.list[index];
+          }
+          op = state ? 'remove' : 'add';
         }
-        var op = state ? 'remove' : 'add';
 
         var $placeholder = this.$placeholder;
         var target_uri = '/' + drupalSettings.path.pathPrefix + 'views-bulk-operations/ajax/' + this.view_id + '/' + this.display_id;
@@ -83,9 +94,7 @@
             op: op
           },
           success: function (data) {
-            var count = parseInt($placeholder.text());
-            count += data.change;
-            $placeholder.text(count);
+            $placeholder.text(data.count);
           }
         });
       }
@@ -109,13 +118,14 @@
       var $tableSelectAll = $(tableSelectAll);
     }
 
-    // Add AJAX functionality to table checkboxes.
+    // Add AJAX functionality to row selector checkboxes.
     var $multiSelectElement = $vboForm.find('.vbo-multipage-selector').first();
     if ($multiSelectElement.length) {
 
       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');
+      Drupal.viewsBulkOperationsSelection.vbo_form = $vboForm;
 
       // Get the list of all checkbox values and add AJAX callback.
       Drupal.viewsBulkOperationsSelection.list = [];
@@ -135,7 +145,7 @@
         $contentWrapper.find('.views-field-views-bulk-operations-bulk-form input[type="checkbox"]').each(function () {
           var value = $(this).val();
           if (value != 'on') {
-            Drupal.viewsBulkOperationsSelection.list[index][value] = $(this).parent().find('label').first().text();
+            Drupal.viewsBulkOperationsSelection.list[index][value] = value;
             Drupal.viewsBulkOperationsSelection.bindEventHandlers($(this), index);
           }
         });
@@ -149,85 +159,19 @@
 
     // Initialize all selector if the primary select all and
     // view table elements exist.
-    if ($primarySelectAll.length && $viewsTables.length) {
-      var strings = {
-        selectAll: $('label', $primarySelectAll.parent()).html(),
-        selectRegular: Drupal.t('Select only items on this page')
-      };
-
-      $primarySelectAll.parent().hide();
-
-      if ($viewsTables.length == 1) {
-        var colspan = $('thead th', $viewsTables.first()).length;
-        var $allSelector = $('<tr class="views-table-row-vbo-select-all even" style="display: none"><td colspan="' + colspan + '"><div><input type="submit" class="form-submit" value="' + strings.selectAll + '"></div></td></tr>');
-        $('tbody', $viewsTables.first()).prepend($allSelector);
-      }
-      else {
-        var $allSelector = $('<div class="views-table-row-vbo-select-all" style="display: none"><div><input type="submit" class="form-submit" value="' + strings.selectAll + '"></div></div>');
-        $($viewsTables.first()).before($allSelector);
-      }
-
-      if ($primarySelectAll.is(':checked')) {
-        $('input', $allSelector).val(strings.selectRegular);
-        $allSelector.show();
-      }
-      else {
-        var show_all_selector = true;
-        $tableSelectAll.each(function () {
-          if (!$(this).is(':checked')) {
-            show_all_selector = false;
-          }
+    if ($primarySelectAll.length) {
+      $primarySelectAll.on('change', function (event) {
+        var value = this.checked;
+        // Select / deselect all checkboxes in the view.
+        $vboForm.find('.views-field-views-bulk-operations-bulk-form input[type="checkbox"]').each(function () {
+          this.checked = value;
         });
-        if (show_all_selector) {
-          $allSelector.show();
-        }
-      }
 
-      $('input', $allSelector).on('click', function (event) {
-        event.preventDefault();
-        if ($primarySelectAll.is(':checked')) {
-          $multiSelectElement.show('fast');
-          $primarySelectAll.prop('checked', false);
-          $allSelector.removeClass('all-selected');
-          $(this).val(strings.selectAll);
-        }
-        else {
-          $multiSelectElement.hide('fast');
-          $primarySelectAll.prop('checked', true);
-          $allSelector.addClass('all-selected');
-          $(this).val(strings.selectRegular);
-        }
       });
 
-      $(tableSelectAll).each(function () {
-        $(this).on('change', function (event) {
-          var show_all_selector = true;
-          $tableSelectAll.each(function () {
-            if (!$(this).is(':checked')) {
-              show_all_selector = false;
-            }
-          });
-          if (show_all_selector) {
-            $allSelector.show();
-          }
-          else {
-            $allSelector.hide();
-            if ($primarySelectAll.is(':checked')) {
-              $('input', $allSelector).trigger('click');
-            }
-          }
-        });
-      });
-    }
-    else {
-      $primarySelectAll.first().on('change', function (event) {
-        if (this.checked) {
-          $multiSelectElement.hide('fast');
-        }
-        else {
-          $multiSelectElement.show('fast');
-        }
-      });
+      if ($multiSelectElement.length) {
+        Drupal.viewsBulkOperationsSelection.bindEventHandlers($primarySelectAll, 'selection_method_change');
+      }
     }
   };
 
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 06947af3dd..aa8d26da2a 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
@@ -2,12 +2,11 @@ type: module
 name: 'Actions Permissions'
 description: 'Adds access permissions on all actions allowing admins to restrict access on a per-role basis.'
 package: 'Views Bulk Operations'
-# core: 8.x
+core: 8.x
 dependencies:
   - drupal:views_bulk_operations
 
-# Information added by Drupal.org packaging script on 2019-02-21
-version: '8.x-2.5'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2020-02-04
+version: '8.x-3.4'
 project: 'views_bulk_operations'
-datestamp: 1550740388
+datestamp: 1580807961
diff --git a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php
index d4262e5f67..6d9e813d7d 100644
--- a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php
+++ b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php
@@ -40,7 +40,7 @@ public function execute($entity = NULL) {
 
     // Do some processing..
     // ...
-    drupal_set_message($entity->label());
+    $this->messenger()->addMessage($entity->label() . ' - ' . $entity->language()->getId() . ' - ' . $entity->id());
     return sprintf('Example action (configuration: %s)', print_r($this->configuration, TRUE));
   }
 
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 c8c57af493..9fca589a28 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
@@ -2,12 +2,11 @@ type: module
 name: 'Views Bulk Operations example'
 description: 'Defines an example action with all possible options.'
 package: 'Examples'
-# core: 8.x
+core: 8.x
 dependencies:
   - drupal:views_bulk_operations
 
-# Information added by Drupal.org packaging script on 2019-02-21
-version: '8.x-2.5'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2020-02-04
+version: '8.x-3.4'
 project: 'views_bulk_operations'
-datestamp: 1550740388
+datestamp: 1580807961
diff --git a/web/modules/views_bulk_operations/src/Access/ViewsBulkOperationsAccess.php b/web/modules/views_bulk_operations/src/Access/ViewsBulkOperationsAccess.php
index 9f78a983be..b6e29a524f 100644
--- a/web/modules/views_bulk_operations/src/Access/ViewsBulkOperationsAccess.php
+++ b/web/modules/views_bulk_operations/src/Access/ViewsBulkOperationsAccess.php
@@ -3,7 +3,7 @@
 namespace Drupal\views_bulk_operations\Access;
 
 use Drupal\Core\Routing\Access\AccessInterface;
-use Drupal\user\PrivateTempStoreFactory;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Routing\RouteMatch;
 use Drupal\Core\Access\AccessResult;
@@ -15,9 +15,9 @@
 class ViewsBulkOperationsAccess implements AccessInterface {
 
   /**
-   * Temporary user storage object.
+   * The tempstore service.
    *
-   * @var \Drupal\user\PrivateTempStoreFactory
+   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
    */
   protected $tempStoreFactory;
 
diff --git a/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php
index 2209fad10f..6a1922a883 100644
--- a/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php
+++ b/web/modules/views_bulk_operations/src/Action/ViewsBulkOperationsActionBase.php
@@ -6,6 +6,7 @@
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\ViewExecutable;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Views Bulk Operations action plugin base.
@@ -130,4 +131,19 @@ public function calculateDependencies() {
     return [];
   }
 
+  /**
+   * Default custom access callback.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user the access check needs to be preformed against.
+   * @param \Drupal\views\ViewExecutable $view
+   *   The View Bulk Operations view data.
+   *
+   * @return bool
+   *   Has access.
+   */
+  public static function customAccess(AccountInterface $account, ViewExecutable $view) {
+    return TRUE;
+  }
+
 }
diff --git a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php
index 9c9d2f14bf..b03c244932 100644
--- a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php
+++ b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php
@@ -25,7 +25,7 @@ class ViewsBulkOperationsCommands extends DrushCommands {
   /**
    * Object that gets the current view data.
    *
-   * @var \Drupal\views_bulk_operations\ViewsbulkOperationsViewDataInterface
+   * @var \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface
    */
   protected $viewData;
 
@@ -41,7 +41,7 @@ class ViewsBulkOperationsCommands extends DrushCommands {
    *
    * @param \Drupal\Core\Session\AccountInterface $currentUser
    *   The current user object.
-   * @param \Drupal\views_bulk_operations\ViewsbulkOperationsViewDataInterface $viewData
+   * @param \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface $viewData
    *   VBO View data service.
    * @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
    *   VBO Action manager service.
diff --git a/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php b/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php
index 8f87b06298..3020241cd3 100644
--- a/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php
+++ b/web/modules/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php
@@ -4,10 +4,10 @@
 
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
 use Drupal\views_bulk_operations\Form\ViewsBulkOperationsFormTrait;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
-use Drupal\user\PrivateTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -21,9 +21,9 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI
   use ViewsBulkOperationsFormTrait;
 
   /**
-   * User private temporary storage factory.
+   * The tempstore service.
    *
-   * @var \Drupal\user\PrivateTempStoreFactory
+   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
    */
   protected $tempStoreFactory;
 
@@ -37,8 +37,8 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI
   /**
    * Constructs a new controller object.
    *
-   * @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
-   *   User private temporary storage factory.
+   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStoreFactory
+   *   Private temporary storage factory.
    * @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface $actionProcessor
    *   Views Bulk Operations action processor.
    */
@@ -55,7 +55,7 @@ public function __construct(
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('user.private_tempstore'),
+      $container->get('tempstore.private'),
       $container->get('views_bulk_operations.processor')
     );
   }
@@ -102,29 +102,51 @@ public function updateSelection($view_id, $display_id, Request $request) {
 
     $list = $request->request->get('list');
 
-    $op = $request->request->get('op', 'add');
-    $change = 0;
-
-    if ($op === 'add') {
-      foreach ($list as $bulkFormKey => $label) {
-        if (!isset($view_data['list'][$bulkFormKey])) {
-          $view_data['list'][$bulkFormKey] = $this->getListItem($bulkFormKey, $label);
-          $change++;
-        }
+    $op = $request->request->get('op', 'check');
+    // Reverse operation when in exclude mode.
+    if (!empty($view_data['exclude_mode'])) {
+      if ($op === 'add') {
+        $op = 'remove';
+      }
+      elseif ($op === 'remove') {
+        $op = 'add';
       }
     }
-    elseif ($op === 'remove') {
-      foreach ($list as $bulkFormKey => $label) {
-        if (isset($view_data['list'][$bulkFormKey])) {
-          unset($view_data['list'][$bulkFormKey]);
-          $change--;
+
+    switch ($op) {
+      case 'add':
+        foreach ($list as $bulkFormKey) {
+          if (!isset($view_data['list'][$bulkFormKey])) {
+            $view_data['list'][$bulkFormKey] = $this->getListItem($bulkFormKey);
+          }
         }
-      }
+        break;
+
+      case 'remove':
+        foreach ($list as $bulkFormKey) {
+          if (isset($view_data['list'][$bulkFormKey])) {
+            unset($view_data['list'][$bulkFormKey]);
+          }
+        }
+        break;
+
+      case 'method_include':
+        unset($view_data['exclude_mode']);
+        $view_data['list'] = [];
+        break;
+
+      case 'method_exclude':
+        $view_data['exclude_mode'] = TRUE;
+        $view_data['list'] = [];
+        break;
     }
+
     $this->setTempstoreData($view_data);
 
+    $count = empty($view_data['exclude_mode']) ? count($view_data['list']) : $view_data['total_results'] - count($view_data['list']);
+
     $response = new AjaxResponse();
-    $response->setData(['change' => $change]);
+    $response->setData(['count' => $count]);
     return $response;
   }
 
diff --git a/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php b/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php
index 6ff2892254..ee15c35561 100644
--- a/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php
+++ b/web/modules/views_bulk_operations/src/EventSubscriber/ViewsBulkOperationsEventSubscriber.php
@@ -20,7 +20,7 @@ class ViewsBulkOperationsEventSubscriber implements EventSubscriberInterface {
   /**
    * Object that gets the current view data.
    *
-   * @var \Drupal\views_bulk_operations\ViewsBulkOperationsViewDataInterface
+   * @var \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface
    */
   protected $viewData;
 
diff --git a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php
index ba769346ac..e4bce65029 100644
--- a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php
+++ b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php
@@ -5,7 +5,7 @@
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\user\PrivateTempStoreFactory;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
 use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
 use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
 
@@ -17,9 +17,9 @@ class ConfigureAction extends FormBase {
   use ViewsBulkOperationsFormTrait;
 
   /**
-   * User private temporary storage factory.
+   * The tempstore service.
    *
-   * @var \Drupal\user\PrivateTempStoreFactory
+   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
    */
   protected $tempStoreFactory;
 
@@ -40,7 +40,7 @@ class ConfigureAction extends FormBase {
   /**
    * Constructor.
    *
-   * @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
+   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStoreFactory
    *   User private temporary storage factory.
    * @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
    *   Extended action manager object.
@@ -62,7 +62,7 @@ public function __construct(
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('user.private_tempstore'),
+      $container->get('tempstore.private'),
       $container->get('plugin.manager.views_bulk_operations_action'),
       $container->get('views_bulk_operations.processor')
     );
@@ -89,20 +89,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $view_id
 
     $form['#title'] = $this->t('Configure "%action" action applied to the selection', ['%action' => $form_data['action_label']]);
 
-    $selection = [];
-    if (!empty($form_data['entity_labels'])) {
-      $form['list'] = [
-        '#theme' => 'item_list',
-        '#items' => $form_data['entity_labels'],
-      ];
-    }
-    else {
-      $form['list'] = [
-        '#type' => 'item',
-        '#markup' => $this->t('All view results'),
-      ];
-    }
-    $form['list']['#title'] = $this->t('Selected @count entities:', ['@count' => $form_data['selected_count']]);
+    $form['list'] = $this->getListRenderable($form_data);
 
     // :D Make sure the submit button is at the bottom of the form
     // and is editale from the action buildConfigurationForm method.
diff --git a/web/modules/views_bulk_operations/src/Form/ConfirmAction.php b/web/modules/views_bulk_operations/src/Form/ConfirmAction.php
index dda696db2a..8c5c742fb2 100644
--- a/web/modules/views_bulk_operations/src/Form/ConfirmAction.php
+++ b/web/modules/views_bulk_operations/src/Form/ConfirmAction.php
@@ -5,7 +5,7 @@
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\user\PrivateTempStoreFactory;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
 use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
 use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
 
@@ -17,9 +17,9 @@ class ConfirmAction extends FormBase {
   use ViewsBulkOperationsFormTrait;
 
   /**
-   * User private temporary storage factory.
+   * The tempstore service.
    *
-   * @var \Drupal\user\PrivateTempStoreFactory
+   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
    */
   protected $tempStoreFactory;
 
@@ -40,7 +40,7 @@ class ConfirmAction extends FormBase {
   /**
    * Constructor.
    *
-   * @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
+   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStoreFactory
    *   User private temporary storage factory.
    * @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
    *   Extended action manager object.
@@ -62,7 +62,7 @@ public function __construct(
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('user.private_tempstore'),
+      $container->get('tempstore.private'),
       $container->get('plugin.manager.views_bulk_operations_action'),
       $container->get('views_bulk_operations.processor')
     );
@@ -87,12 +87,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $view_id
       return;
     }
 
-    if (!empty($form_data['entity_labels'])) {
-      $form['list'] = [
-        '#theme' => 'item_list',
-        '#items' => $form_data['entity_labels'],
-      ];
-    }
+    $form['list'] = $this->getListRenderable($form_data);
 
     $form['#title'] = $this->formatPlural(
       $form_data['selected_count'],
diff --git a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php
index fba98fc607..9d83f90c12 100644
--- a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php
+++ b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php
@@ -4,11 +4,13 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Messenger\MessengerTrait;
 
 /**
  * Defines common methods for Views Bulk Operations forms.
  */
 trait ViewsBulkOperationsFormTrait {
+  use MessengerTrait;
 
   /**
    * The tempstore object associated with the current view.
@@ -41,22 +43,72 @@ protected function getFormData($view_id, $display_id) {
     $form_data = $this->getTempstoreData($view_id, $display_id);
 
     // Get data needed for selected entities list.
+    $this->addListData($form_data);
+
+    return $form_data;
+  }
+
+  /**
+   * Add data needed for entity list rendering.
+   */
+  protected function addListData(&$form_data) {
+    $form_data['entity_labels'] = [];
     if (!empty($form_data['list'])) {
-      $form_data['entity_labels'] = [];
-      $form_data['selected_count'] = 0;
-      foreach ($form_data['list'] as $item) {
-        $form_data['selected_count']++;
-        $form_data['entity_labels'][] = $item[4];
+      $form_data['selected_count'] = count($form_data['list']);
+      if (!empty($form_data['exclude_mode'])) {
+        $form_data['selected_count'] = $form_data['total_results'] - $form_data['selected_count'];
       }
+
+      // In case of exclude mode we still get excluded labels
+      // so we temporarily switch off exclude mode.
+      $modified_form_data = $form_data;
+      $modified_form_data['exclude_mode'] = FALSE;
+      $form_data['entity_labels'] = $this->actionProcessor->getLabels($modified_form_data);
     }
-    elseif ($form_data['total_results']) {
+    else {
       $form_data['selected_count'] = $form_data['total_results'];
     }
+  }
+
+  /**
+   * Build selected entities list renderable.
+   *
+   * @param array $form_data
+   *   Data needed for this form.
+   *
+   * @return array
+   *   Renderable list array.
+   */
+  protected function getListRenderable(array $form_data) {
+    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'][] = [
+          '#children' => $this->t('..plus @count more..', [
+            '@count' => $more,
+          ]),
+          '#wrapper_attributes' => ['class' => ['more']],
+        ];
+      }
+    }
     else {
-      $form_data['selected_count'] = (string) $this->t('all');
+      $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']]);
     }
 
-    return $form_data;
+    return $renderable;
   }
 
   /**
@@ -99,16 +151,12 @@ public static function calculateEntityBulkFormKey(EntityInterface $entity, $base
    *
    * @param string $bulkFormKey
    *   A bulk form key.
-   * @param mixed $label
-   *   Entity label, string or
-   *   \Drupal\Core\StringTranslation\TranslatableMarkup.
    *
    * @return array
    *   Entity list item.
    */
-  protected function getListItem($bulkFormKey, $label) {
+  protected function getListItem($bulkFormKey) {
     $item = json_decode(base64_decode($bulkFormKey));
-    $item[] = $label;
     return $item;
   }
 
@@ -190,7 +238,7 @@ protected function addCancelButton(array &$form) {
    */
   public function cancelForm(array &$form, FormStateInterface $form_state) {
     $form_data = $form_state->get('views_bulk_operations');
-    drupal_set_message($this->t('Canceled "%action".', ['%action' => $form_data['action_label']]));
+    $this->messenger()->addMessage($this->t('Canceled "%action".', ['%action' => $form_data['action_label']]));
     $form_state->setRedirectUrl($form_data['redirect_url']);
   }
 
diff --git a/web/modules/views_bulk_operations/src/Plugin/Action/CancelUserAction.php b/web/modules/views_bulk_operations/src/Plugin/Action/CancelUserAction.php
index 1e73ffc30b..bd39fe729e 100644
--- a/web/modules/views_bulk_operations/src/Plugin/Action/CancelUserAction.php
+++ b/web/modules/views_bulk_operations/src/Plugin/Action/CancelUserAction.php
@@ -92,12 +92,12 @@ public static function create(ContainerInterface $container, array $configuratio
    */
   public function execute($account = NULL) {
     if ($account->id() === $this->currentUser->id() && (empty($this->context['list']) || count($this->context['list'] > 1))) {
-      drupal_set_message($this->t('The current user account cannot be canceled in a batch operation. Select your account only or cancel it from your account page.'), 'error');
+      $this->messenger()->addError($this->t('The current user account cannot be canceled in a batch operation. Select your account only or cancel it from your account page.'));
     }
     elseif (intval($account->id()) === 1) {
-      drupal_set_message($this->t('The user 1 account (%label) cannot be canceled.', [
+      $this->messenger()->addError($this->t('The user 1 account (%label) cannot be canceled.', [
         '%label' => $account->label(),
-      ]), 'error');
+      ]));
     }
     else {
       // Allow other modules to act.
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 13087d299e..95e72beb5b 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
@@ -6,6 +6,7 @@
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RedirectDestinationTrait;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
 use Drupal\views\Plugin\views\field\UncacheableFieldHandlerTrait;
@@ -17,7 +18,6 @@
 use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
 use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
 use Drupal\views_bulk_operations\Form\ViewsBulkOperationsFormTrait;
-use Drupal\user\PrivateTempStoreFactory;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Drupal\Core\Url;
@@ -38,7 +38,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
   /**
    * Object that gets the current view data.
    *
-   * @var \Drupal\views_bulk_operations\ViewsbulkOperationsViewDataInterface
+   * @var \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface
    */
   protected $viewData;
 
@@ -57,9 +57,9 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
   protected $actionProcessor;
 
   /**
-   * User private temporary storage factory.
+   * The tempstore service.
    *
-   * @var \Drupal\user\PrivateTempStoreFactory
+   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
    */
   protected $tempStoreFactory;
 
@@ -116,7 +116,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
    *   Extended action manager object.
    * @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface $actionProcessor
    *   Views Bulk Operations action processor.
-   * @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
+   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStoreFactory
    *   User private temporary storage factory.
    * @param \Drupal\Core\Session\AccountInterface $currentUser
    *   The current user object.
@@ -155,7 +155,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $container->get('views_bulk_operations.data'),
       $container->get('plugin.manager.views_bulk_operations_action'),
       $container->get('views_bulk_operations.processor'),
-      $container->get('user.private_tempstore'),
+      $container->get('tempstore.private'),
       $container->get('current_user'),
       $container->get('request_stack')
     );
@@ -210,11 +210,33 @@ protected function updateTempstoreData() {
     $variable = [
       'batch' => $this->options['batch'],
       'batch_size' => $this->options['batch'] ? $this->options['batch_size'] : 0,
-      'total_results' => $this->viewData->getTotalResults(),
+      'total_results' => $this->viewData->getTotalResults($this->options['clear_on_exposed']),
       'arguments' => $this->view->args,
-      'redirect_url' => Url::createFromRequest(clone $this->requestStack->getCurrentRequest()),
       'exposed_input' => $this->view->getExposedInput(),
     ];
+
+    // 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');
+    if ($destination) {
+      $request->query->remove('destination');
+      unset($variable['exposed_input']['destination']);
+      if (strpos($destination, '/') !== 0) {
+        $destination = '/' . $destination;
+      }
+      $variable['redirect_url'] = Url::fromUserInput($destination, []);
+    }
+    else {
+      $variable['redirect_url'] = Url::createFromRequest(clone $this->requestStack->getCurrentRequest());
+    }
+
+    // Set exposed filters values to be kept after action execution.
     $query = $variable['redirect_url']->getOption('query');
     if (!$query) {
       $query = [];
@@ -226,11 +248,12 @@ protected function updateTempstoreData() {
     if (!is_array($this->tempStoreData)) {
       $this->tempStoreData = [];
 
-      // Add constant parameters.
+      // Add initial values.
       $this->tempStoreData += [
         'view_id' => $this->view->id(),
         'display_id' => $this->view->current_display,
         'list' => [],
+        'exclude_mode' => FALSE,
       ];
 
       // Add variable parameters.
@@ -388,6 +411,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     $form['clear_on_exposed'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Clear selection when exposed filters change.'),
+      '#description' => $this->t('With this enabled, selection will be cleared everey time exposed filters are changed, select all will select all rows with exposed filters applied and view total count will take exposed filters into account. When disabled, select all selects all results in the view with empty exposed filters and one can change exposed filters while selecting rows without the selection being lost.'),
       '#default_value' => $this->options['clear_on_exposed'],
     ];
 
@@ -567,6 +591,10 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
         );
 
         $checked = isset($this->tempStoreData['list'][$bulk_form_key]);
+        if (!empty($this->tempStoreData['exclude_mode'])) {
+          $checked = !$checked;
+        }
+
         if ($checked) {
           $page_selected[] = $bulk_form_key;
         }
@@ -638,17 +666,29 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
         }
       }
 
-      $display_select_all = isset($pagerData) && ($pagerData['more'] || $pagerData['current'] > 0);
+      $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 ((!$this->options['clear_on_exposed'] && !empty($this->view->getExposedInput())) || $display_select_all) {
+      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',
           '#open' => FALSE,
           '#title' => $this->t('Selected %count items in this view', [
-            '%count' => count($this->tempStoreData['list']),
+            '%count' => $count,
           ]),
           '#attributes' => [
             // Add view_id and display_id to be available for
@@ -661,35 +701,39 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
         ];
 
         // Display a list of items selected on other pages.
-        $form['header'][$this->options['id']]['multipage']['list'] = [
-          '#theme' => 'item_list',
-          '#title' => $this->t('Items selected on other pages:'),
-          '#items' => [],
-          '#empty' => $this->t('No selection'),
-        ];
-        if (count($this->tempStoreData['list']) > count($page_selected)) {
+        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['header'][$this->options['id']]['multipage']['list']['#items'][] = $item[4];
+              $form_data['list'][$bulk_form_key] = $item;
             }
           }
-          $form['header'][$this->options['id']]['multipage']['clear'] = [
-            '#type' => 'submit',
-            '#value' => $this->t('Clear'),
-            '#submit' => [[$this, 'clearSelection']],
-            '#limit_validation_errors' => [],
-          ];
+          $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');
+
         }
+
+        $form['header'][$this->options['id']]['multipage']['clear'] = [
+          '#type' => 'submit',
+          '#value' => $this->t('Clear'),
+          '#submit' => [[$this, 'clearSelection']],
+          '#limit_validation_errors' => [],
+        ];
       }
 
-      // Select all results checkbox.
-      if ($display_select_all) {
+      // Select all results checkbox. Always display on non-table displays.
+      if ($display_select_all || !($this->view->style_plugin instanceof Table)) {
         $form['header'][$this->options['id']]['select_all'] = [
           '#type' => 'checkbox',
-          '#title' => $this->t('Select all@count results in this view', [
-            '@count' => $this->tempStoreData['total_results'] ? ' ' . $this->tempStoreData['total_results'] : '',
+          '#title' => $this->t('Select / deselect all results in this view (all pages, @count total)', [
+            '@count' => $this->tempStoreData['total_results'],
           ]),
           '#attributes' => ['class' => ['vbo-select-all']],
+          '#default_value' => !empty($this->tempStoreData['exclude_mode']),
         ];
       }
 
@@ -734,6 +778,11 @@ protected function getBulkOptions() {
           continue;
         }
 
+        // Check custom access, if defined.
+        if (!empty($definition['requirements']['_custom_access']) && !$definition['class']::customAccess($this->currentUser, $this->view)) {
+          continue;
+        }
+
         // Override label if applicable.
         if (!empty($this->options['preconfiguration'][$id]['label_override'])) {
           $this->bulkOptions[$id] = $this->options['preconfiguration'][$id]['label_override'];
@@ -766,23 +815,7 @@ public function viewsFormSubmit(array &$form, FormStateInterface $form_state) {
       $this->tempStoreData['action_label'] = empty($this->options['preconfiguration'][$action_id]['label_override']) ? (string) $action['label'] : $this->options['preconfiguration'][$action_id]['label_override'];
       $this->tempStoreData['relationship_id'] = $this->options['relationship'];
       $this->tempStoreData['preconfiguration'] = isset($this->options['preconfiguration'][$action_id]) ? $this->options['preconfiguration'][$action_id] : [];
-
-      if (!$form_state->getValue('select_all')) {
-
-        // Update list data with the current form selection.
-        foreach ($form_state->getValue($this->options['id']) as $row_index => $bulkFormKey) {
-          if ($bulkFormKey) {
-            $this->tempStoreData['list'][$bulkFormKey] = $this->getListItem($bulkFormKey, $form[$this->options['id']][$row_index]['#title']);
-          }
-          else {
-            unset($this->tempStoreData['list'][$form[$this->options['id']][$row_index]['#return_value']]);
-          }
-        }
-      }
-      else {
-        // Unset the list completely.
-        $this->tempStoreData['list'] = [];
-      }
+      $this->tempStoreData['clear_on_exposed'] = $this->options['clear_on_exposed'];
 
       $configurable = $this->isActionConfigurable($action);
 
@@ -799,8 +832,40 @@ public function viewsFormSubmit(array &$form, FormStateInterface $form_state) {
         }
       }
 
-      // Routing - determine redirect route.
+      // Update list data with the current page selection.
+      if ($form_state->getValue('select_all')) {
+        foreach ($form_state->getValue($this->options['id']) as $row_index => $bulkFormKey) {
+          if ($bulkFormKey) {
+            unset($this->tempStoreData['list'][$bulkFormKey]);
+          }
+          else {
+            $row_bulk_form_key = $form[$this->options['id']][$row_index]['#return_value'];
+            $this->tempStoreData['list'][$row_bulk_form_key] = $this->getListItem($row_bulk_form_key);
+          }
+        }
+      }
+      else {
+        foreach ($form_state->getValue($this->options['id']) as $row_index => $bulkFormKey) {
+          if ($bulkFormKey) {
+            $this->tempStoreData['list'][$bulkFormKey] = $this->getListItem($bulkFormKey);
+          }
+          else {
+            $row_bulk_form_key = $form[$this->options['id']][$row_index]['#return_value'];
+            unset($this->tempStoreData['list'][$row_bulk_form_key]);
+          }
+        }
+      }
+
+      // 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;
+      }
 
+      // Routing - determine redirect route.
+      //
       // Set default redirection due to issue #2952498.
       // TODO: remove the next line when core cause is eliminated.
       $redirect_route = 'views_bulk_operations.execute_batch';
diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php
index 39fa43e004..4c60e10489 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php
@@ -178,6 +178,9 @@ protected function alterDefinitions(&$definitions) {
     $event->alterParameters = $this->alterParameters;
     $event->definitions = &$definitions;
     $this->eventDispatcher->dispatch(static::ALTER_ACTIONS_EVENT, $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 cb9d7cb328..481ee4c781 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php
@@ -16,6 +16,11 @@ class ViewsBulkOperationsActionProcessor implements ViewsBulkOperationsActionPro
 
   use StringTranslationTrait;
 
+  /**
+   * Maximum number of labels fetched for informational purposes.
+   */
+  const MAX_LIST_COUNT = 50;
+
   /**
    * View data provider service.
    *
@@ -51,6 +56,13 @@ class ViewsBulkOperationsActionProcessor implements ViewsBulkOperationsActionPro
    */
   protected $initialized = FALSE;
 
+  /**
+   * Are we operating in exclude mode?
+   *
+   * @var bool
+   */
+  protected $excludeMode = FALSE;
+
   /**
    * The processed action object.
    *
@@ -115,18 +127,22 @@ public function initialize(array $view_data, $view = NULL) {
       $this->queue = [];
     }
 
-    if (!isset($view_data['configuration'])) {
-      $view_data['configuration'] = [];
-    }
-    if (!empty($view_data['preconfiguration'])) {
-      $view_data['configuration'] += $view_data['preconfiguration'];
-    }
+    $this->excludeMode = $view_data['exclude_mode'];
 
-    // Initialize action object.
-    $this->action = $this->actionManager->createInstance($view_data['action_id'], $view_data['configuration']);
+    if (isset($view_data['action_id'])) {
+      if (!isset($view_data['configuration'])) {
+        $view_data['configuration'] = [];
+      }
+      if (!empty($view_data['preconfiguration'])) {
+        $view_data['configuration'] += $view_data['preconfiguration'];
+      }
+
+      // Initialize action object.
+      $this->action = $this->actionManager->createInstance($view_data['action_id'], $view_data['configuration']);
 
-    // Set action context.
-    $this->setActionContext($view_data);
+      // Set action context.
+      $this->setActionContext($view_data);
+    }
 
     // Set entire view data as object parameter for future reference.
     $this->bulkFormData = $view_data;
@@ -158,6 +174,26 @@ protected function setView($view = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabels(array $view_data) {
+    $this->initialize($view_data);
+
+    // We don't want to load too many entities here due to performance reasons.
+    if (count($view_data['list']) > self::MAX_LIST_COUNT) {
+      $view_data['list'] = array_slice($view_data['list'], 0, self::MAX_LIST_COUNT);
+    }
+
+    $this->populateQueue($view_data);
+
+    $labels = [];
+    foreach ($this->queue as $entity) {
+      $labels[] = $entity->label();
+    }
+    return $labels;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -167,9 +203,13 @@ public function getPageList($page) {
     $this->viewDataService->init($this->view, $this->view->getDisplay(), $this->bulkFormData['relationship_id']);
 
     // Set exposed filters and pager parameters.
-    if (!empty($this->bulkFormData['exposed_input'])) {
+    if (!empty($this->bulkFormData['clear_on_exposed']) && !empty($this->bulkFormData['exposed_input'])) {
       $this->view->setExposedInput($this->bulkFormData['exposed_input']);
     }
+    else {
+      $this->view->setExposedInput(['_views_bulk_operations_override' => TRUE]);
+    }
+
     $this->view->setItemsPerPage($this->bulkFormData['batch_size']);
     $this->view->setCurrentPage($page);
     $this->view->build();
@@ -189,13 +229,25 @@ public function getPageList($page) {
     foreach ($this->view->result as $row) {
       $entity = $this->viewDataService->getEntity($row);
 
-      // We don't need entity label here.
-      $list[] = [
-        $row->{$base_field},
-        $entity->language()->getId(),
-        $entity->getEntityTypeId(),
-        $entity->id(),
-      ];
+      $exclude = FALSE;
+      if ($this->excludeMode) {
+        // Filter out excluded results basing on base field ID and language.
+        foreach ($this->bulkFormData['exclude_list'] as $key => $item) {
+          if ($row->{$base_field} === $item[0] && $entity->language()->getId() === $item[1]) {
+            $exclude = TRUE;
+            break;
+          }
+        }
+      }
+
+      if (!$exclude) {
+        $list[] = [
+          $row->{$base_field},
+          $entity->language()->getId(),
+          $entity->getEntityTypeId(),
+          $entity->id(),
+        ];
+      }
     }
 
     return $list;
@@ -204,10 +256,14 @@ public function getPageList($page) {
   /**
    * {@inheritdoc}
    */
-  public function populateQueue(array $list, array &$context = []) {
+  public function populateQueue(array $data, array &$context = []) {
+    $list = $data['list'];
+    $base_field = $this->view->storage->get('base_field');
+    $this->queue = [];
+
     // Determine batch size and offset.
     if (!empty($context)) {
-      $batch_size = $this->bulkFormData['batch_size'];
+      $batch_size = $data['batch_size'];
       if (!isset($context['sandbox']['current_batch'])) {
         $context['sandbox']['current_batch'] = 0;
       }
@@ -227,18 +283,11 @@ public function populateQueue(array $list, array &$context = []) {
       $batch_list = $list;
     }
 
-    $base_field_values = [];
-    foreach ($batch_list as $item) {
-      $base_field_values[] = $item[0];
-    }
-    if (empty($base_field_values)) {
-      return 0;
-    }
-
     $this->view->setItemsPerPage(0);
     $this->view->setCurrentPage(0);
     $this->view->setOffset(0);
     $this->view->initHandlers();
+    $this->view->setExposedInput(['_views_bulk_operations_override' => TRUE]);
 
     // Remove all exposed filters so we don't have any default filter
     // values that could make the actual selection out of range.
@@ -254,13 +303,21 @@ public function populateQueue(array $list, array &$context = []) {
     $this->view->build();
 
     // Modify the view query: determine and apply the base field condition.
-    $base_field = $this->view->storage->get('base_field');
+    $base_field_values = [];
+    foreach ($batch_list as $item) {
+      $base_field_values[] = $item[0];
+    }
+    if (empty($base_field_values)) {
+      return 0;
+    }
+
     if (isset($this->view->query->fields[$base_field])) {
       $base_field_alias = $this->view->query->fields[$base_field]['table'] . '.' . $this->view->query->fields[$base_field]['alias'];
     }
     else {
       $base_field_alias = $base_field;
     }
+
     $this->view->query->addWhere(0, $base_field_alias, $base_field_values, 'IN');
 
     // Rebuild the view query.
@@ -272,6 +329,7 @@ public function populateQueue(array $list, array &$context = []) {
 
     // Get entities.
     $this->viewDataService->init($this->view, $this->view->getDisplay(), $this->bulkFormData['relationship_id']);
+
     foreach ($this->view->result as $row_index => $row) {
       // This may return rows for all possible languages.
       // Check if the current language is on the list.
@@ -294,7 +352,7 @@ public function populateQueue(array $list, array &$context = []) {
     if (!empty($context)) {
       if (!isset($context['sandbox']['total'])) {
         if (empty($list)) {
-          $context['sandbox']['total'] = $this->viewDataService->getTotalResults();
+          $context['sandbox']['total'] = $this->viewDataService->getTotalResults($data['clear_on_exposed']);
         }
         else {
           $context['sandbox']['total'] = count($list);
@@ -390,21 +448,21 @@ public function process() {
    * {@inheritdoc}
    */
   public function executeProcessing(array &$data, $view = NULL) {
+    if ($data['exclude_mode'] && empty($data['exclude_list'])) {
+      $data['exclude_list'] = $data['list'];
+      $data['list'] = [];
+    }
     if ($data['batch']) {
       $batch = ViewsBulkOperationsBatch::getBatch($data);
       batch_set($batch);
     }
     else {
-      $list = $data['list'];
-
       // Populate and process queue.
-      if (!$this->initialized) {
-        $this->initialize($data, $view);
-      }
-      if (empty($list)) {
-        $list = $this->getPageList(0);
+      $this->initialize($data, $view);
+      if (empty($data['list'])) {
+        $data['list'] = $this->getPageList(0);
       }
-      if ($this->populateQueue($list)) {
+      if ($this->populateQueue($data)) {
         $batch_results = $this->process();
       }
 
diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessorInterface.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessorInterface.php
index 219cc11fb5..f3312cb421 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessorInterface.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessorInterface.php
@@ -17,6 +17,17 @@ interface ViewsBulkOperationsActionProcessorInterface {
    */
   public function initialize(array $view_data, $view = NULL);
 
+  /**
+   * Get the current processing entity queue.
+   *
+   * @param array $view_data
+   *   Data concerning the view that will be processed.
+   *
+   * @return array
+   *   Array of entity labels.
+   */
+  public function getLabels(array $view_data);
+
   /**
    * Get full list of items from a specific view page.
    *
@@ -31,12 +42,12 @@ public function getPageList($page);
   /**
    * Populate entity queue for processing.
    *
-   * @param array $list
-   *   Array of selected view results.
+   * @param array $data
+   *   Data concerning the view that will be processed.
    * @param array $context
    *   Batch API context.
    */
-  public function populateQueue(array $list, array &$context = []);
+  public function populateQueue(array $data, array &$context = []);
 
   /**
    * Process results.
diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php
index bc41f82081..f9f69b43ef 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php
@@ -159,19 +159,73 @@ public function getEntity(ResultRow $row) {
     }
   }
 
+  /**
+   * Helper function that restores pager data.
+   *
+   * Pager data is stored in global variables and changed every
+   * time the view is executed, even if in a new object instance
+   * so we need to save and restore the original values.
+   */
+  protected function fixPagerData() {
+    static $values;
+    if (!isset($values)) {
+      foreach (['pager_page_array', 'pager_total', 'pager_total_items'] as $key) {
+        if (isset($GLOBALS[$key])) {
+          $values[$key] = $GLOBALS[$key];
+        }
+      }
+    }
+    elseif (!empty($values)) {
+      foreach ($values as $key => $value) {
+        $GLOBALS[$key] = $value;
+      }
+      unset($values);
+    }
+  }
+
   /**
    * Get the total count of results on all pages.
    *
+   * @param bool $clear_on_exposed
+   *   Are we clearing selection on exposed filters change?
+   *
    * @return int
    *   The total number of results this view displays.
    */
-  public function getTotalResults() {
+  public function getTotalResults($clear_on_exposed = FALSE) {
     $total_results = NULL;
-    if (!empty($this->view->pager->total_items)) {
-      $total_results = $this->view->pager->total_items;
+
+    if (!$clear_on_exposed && !empty($this->view->getExposedInput())) {
+      // Execute the view without exposed input set.
+      $view = Views::getView($this->view->id());
+      $view->setDisplay($this->view->current_display);
+      // If there are any arguments, pass them through.
+      if (!empty($this->view->args)) {
+        $view->setArguments($this->view->args);
+      }
+      $view->get_total_rows = TRUE;
+
+      // We have to set exposed input to some value here, empty
+      // value will be overwritten with query params by Views so
+      // setting an empty array wouldn't work.
+      $view->setExposedInput(['_views_bulk_operations_override' => TRUE]);
+    }
+    else {
+      $view = $this->view;
+    }
+
+    $this->fixPagerData();
+
+    // Execute the view if not already executed.
+    $view->execute();
+
+    $this->fixPagerData();
+
+    if (!empty($view->pager->total_items)) {
+      $total_results = $view->pager->total_items;
     }
-    elseif (!empty($this->view->total_rows)) {
-      $total_results = $this->view->total_rows;
+    elseif (!empty($view->total_rows)) {
+      $total_results = $view->total_rows;
     }
 
     return $total_results;
diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewDataInterface.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewDataInterface.php
index ed41b78514..16195546ca 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewDataInterface.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewDataInterface.php
@@ -61,10 +61,13 @@ public function getEntity(ResultRow $row);
   /**
    * Get the total count of results on all pages.
    *
+   * @param bool $clear_on_exposed
+   *   Are we clearing selection on exposed filters change?
+   *
    * @return int
    *   The total number of results this view displays.
    */
-  public function getTotalResults();
+  public function getTotalResults($clear_on_exposed);
 
   /**
    * The default entity getter function.
diff --git a/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php b/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php
index f43f7ebc8a..b809ebf464 100644
--- a/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php
+++ b/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php
@@ -3,6 +3,7 @@
 namespace Drupal\views_bulk_operations;
 
 use Drupal\Core\Url;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Defines module Batch API methods.
@@ -21,10 +22,10 @@ public static function t($string, array $args = [], array $options = []) {
   /**
    * Set message function wrapper.
    *
-   * @see \drupal_set_message()
+   * @see \Drupal\Core\Messenger\MessengerInterface
    */
   public static function message($message = NULL, $type = 'status', $repeat = TRUE) {
-    drupal_set_message($message, $type, $repeat);
+    \Drupal::messenger()->addMessage($message, $type, $repeat);
   }
 
   /**
@@ -42,6 +43,8 @@ public static function getList(array $data, array &$context) {
     if (empty($context['sandbox'])) {
       $context['sandbox']['processed'] = 0;
       $context['sandbox']['page'] = 0;
+      $context['sandbox']['total'] = $data['exclude_mode'] ? $data['total_results'] - count($data['exclude_list']) : $data['total_results'];
+      $context['sandbox']['npages'] = ceil($data['total_results'] / $data['batch_size']);
       $context['results'] = $data;
     }
 
@@ -52,29 +55,20 @@ public static function getList(array $data, array &$context) {
     $list = $actionProcessor->getPageList($context['sandbox']['page']);
     $count = count($list);
 
-    if ($count) {
-      foreach ($list as $item) {
-        $context['results']['list'][] = $item;
-      }
+    foreach ($list as $item) {
+      $context['results']['list'][] = $item;
+    }
 
-      $context['sandbox']['page']++;
-      $context['sandbox']['processed'] += $count;
+    $context['sandbox']['page']++;
+    $context['sandbox']['processed'] += $count;
 
-      // There may be cases where we don't know the total number of
-      // results (e.g. mini pager with a search_api view)
+    if ($context['sandbox']['page'] <= $context['sandbox']['npages']) {
       $context['finished'] = 0;
-      if ($data['total_results']) {
-        $context['finished'] = $context['sandbox']['processed'] / $data['total_results'];
-        $context['message'] = static::t('Prepared @count of @total entities for processing.', [
-          '@count' => $context['sandbox']['processed'],
-          '@total' => $data['total_results'],
-        ]);
-      }
-      else {
-        $context['message'] = static::t('Prepared @count entities for processing.', [
-          '@count' => $context['sandbox']['processed'],
-        ]);
-      }
+      $context['finished'] = $context['sandbox']['processed'] / $context['sandbox']['total'];
+      $context['message'] = static::t('Prepared @count of @total entities for processing.', [
+        '@count' => $context['sandbox']['processed'],
+        '@total' => $context['sandbox']['total'],
+      ]);
     }
 
   }
@@ -93,7 +87,7 @@ public static function saveList($success, array $results, array $operations) {
     if ($success) {
       $results['redirect_url'] = $results['redirect_after_processing'];
       unset($results['redirect_after_processing']);
-      $tempstore_factory = \Drupal::service('user.private_tempstore');
+      $tempstore_factory = \Drupal::service('tempstore.private');
       $current_user = \Drupal::service('current_user');
       $tempstore_name = 'views_bulk_operations_' . $results['view_id'] . '_' . $results['display_id'];
       $results['prepopulated'] = TRUE;
@@ -114,6 +108,8 @@ public static function operation(array $data, array &$context) {
     if (empty($context['sandbox'])) {
       $context['sandbox']['processed'] = 0;
       $context['results']['operations'] = [];
+      $context['sandbox']['page'] = 0;
+      $context['sandbox']['npages'] = ceil($data['total_results'] / $data['batch_size']);
     }
 
     // Get entities to process.
@@ -121,34 +117,27 @@ public static function operation(array $data, array &$context) {
     $actionProcessor->initialize($data);
 
     // Do the processing.
-    $count = $actionProcessor->populateQueue($data['list'], $context);
-    if ($count) {
-      $batch_results = $actionProcessor->process();
-      if (!empty($batch_results)) {
-        // Convert translatable markup to strings in order to allow
-        // correct operation of array_count_values function.
-        foreach ($batch_results as $result) {
-          $context['results']['operations'][] = (string) $result;
-        }
+    $count = $actionProcessor->populateQueue($data, $context);
+
+    $batch_results = $actionProcessor->process();
+    if (!empty($batch_results)) {
+      // Convert translatable markup to strings in order to allow
+      // correct operation of array_count_values function.
+      foreach ($batch_results as $result) {
+        $context['results']['operations'][] = (string) $result;
       }
-      $context['sandbox']['processed'] += $count;
+    }
+    $context['sandbox']['processed'] += $count;
+    $context['sandbox']['page']++;
 
+    if ($context['sandbox']['page'] <= $context['sandbox']['npages']) {
       $context['finished'] = 0;
-      // There may be cases where we don't know the total number of
-      // results (probably all of them were already eliminated but
-      // leaving this code just in case).
-      if ($context['sandbox']['total']) {
-        $context['finished'] = $context['sandbox']['processed'] / $context['sandbox']['total'];
-        $context['message'] = static::t('Processed @count of @total entities.', [
-          '@count' => $context['sandbox']['processed'],
-          '@total' => $context['sandbox']['total'],
-        ]);
-      }
-      else {
-        $context['message'] = static::t('Processed @count entities.', [
-          '@count' => $context['sandbox']['processed'],
-        ]);
-      }
+
+      $context['finished'] = $context['sandbox']['processed'] / $context['sandbox']['total'];
+      $context['message'] = static::t('Processed @count of @total entities.', [
+        '@count' => $context['sandbox']['processed'],
+        '@total' => $context['sandbox']['total'],
+      ]);
     }
   }
 
@@ -173,6 +162,9 @@ public static function finished($success, array $results, array $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.');
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 74c1170eb7..0a83927e30 100644
--- a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php
+++ b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\views_bulk_operations\Functional;
 
+use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Tests\BrowserTestBase;
 
 /**
@@ -10,6 +11,8 @@
  */
 class ViewsBulkOperationsBulkFormTest extends BrowserTestBase {
 
+  const TEST_NODE_COUNT = 15;
+
   /**
    * Modules to install.
    *
@@ -33,7 +36,7 @@ protected function setUp() {
 
     $this->testNodes = [];
     $time = $this->container->get('datetime.time')->getRequestTime();
-    for ($i = 0; $i < 15; $i++) {
+    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;
@@ -105,7 +108,7 @@ public function testViewsBulkOperationsBulkFormSimple() {
     // Make sure a checkbox appears on all rows.
     $edit = [];
     for ($i = 0; $i < 4; $i++) {
-      $assertSession->fieldExists('edit-views-bulk-operations-bulk-form-' . $i, NULL, format_string('The checkbox on row @row appears.', ['@row' => $i]));
+      $assertSession->fieldExists('edit-views-bulk-operations-bulk-form-' . $i, NULL, new FormattableMarkup('The checkbox on row @row appears.', ['@row' => $i]));
     }
 
     // The advanced action should not be shown on the form - no permission.
@@ -146,11 +149,18 @@ public function testViewsBulkOperationsBulkFormSimple() {
     $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'));
 
     $assertSession->pageTextContains(
-      sprintf('Action processing results: Test (%d).', count($this->testNodes)),
-      sprintf('Action has been executed on %d nodes.', count($this->testNodes))
+      sprintf('Action processing results: Test (%d).', self::TEST_NODE_COUNT),
+      sprintf('Action has been executed on %d nodes.', self::TEST_NODE_COUNT)
     );
 
   }
@@ -232,21 +242,26 @@ public function testViewsBulkOperationsBulkFormAdvanced() {
       ));
     }
 
-    // Test the select all functionality with batching and entity
+    // Test the exclude functionality with batching and entity
     // property changes affecting view query results.
     $edit = [
       'action' => 'views_bulk_operations_advanced_test_action',
       'select_all' => 1,
     ];
+    // Let's leave two checkboxes unchecked to test the exclude mode.
+    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'));
-    // Again, take offset into account (-1).
+    // 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) - 1)),
-      sprintf('Action has been executed on all %d nodes.', (count($this->testNodes) - 1))
+      sprintf('Action processing results: Test (%d).', (count($this->testNodes) - 3)),
+      sprintf('Action has been executed on all %d nodes.', (count($this->testNodes) - 3))
     );
-    $this->assertTrue(empty($this->cssSelect('table.views-table tr')), t("The view doesn't show any results."));
+    $this->assertTrue((count($this->cssSelect('table.views-table tbody tr')) === 2), t("The view shows only excluded results."));
   }
 
   /**
@@ -280,9 +295,10 @@ public function testViewsBulkOperationsBulkFormPassing() {
 
     $testViewConfig = \Drupal::service('config.factory')->getEditable('views.view.views_bulk_operations_test_advanced');
     $configData = $testViewConfig->getRawData();
-    $configData['display']['default']['display_options']['pager']['options']['items_per_page'] = 5;
+    $items_per_page = 5;
 
     foreach ($cases as $case) {
+      $items_per_page++;
 
       // Populate form values.
       $edit = [
@@ -295,10 +311,14 @@ public function testViewsBulkOperationsBulkFormPassing() {
       }
       else {
         $edit['select_all'] = 1;
+        // So we don't cause exclude mode.
+        for ($i = 0; $i < $items_per_page; $i++) {
+          $edit["views_bulk_operations_bulk_form[$i]"] = TRUE;
+        }
       }
 
       // Update test view configuration.
-      $configData['display']['default']['display_options']['pager']['options']['items_per_page']++;
+      $configData['display']['default']['display_options']['pager']['options']['items_per_page'] = $items_per_page;
       $configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['batch'] = $case['batch'];
       if (isset($case['batch_size'])) {
         $configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['batch_size'] = $case['batch_size'];
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 a62a062335..66b9969443 100644
--- a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsActionProcessorTest.php
+++ b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsActionProcessorTest.php
@@ -23,6 +23,48 @@ public function setUp() {
     ]);
   }
 
+  /**
+   * Helper function to assert if node statuses have expected values.
+   *
+   * @param array $list
+   *   VBO processing list.
+   * @param bool $exclude
+   *   Exclude mode enabled?
+   */
+  protected function assertNodeStatuses(array $list, $exclude = FALSE) {
+    $nodeStorage = $this->container->get('entity_type.manager')->getStorage('node');
+
+    $expected = [
+      $exclude ? NodeInterface::PUBLISHED : NodeInterface::NOT_PUBLISHED,
+      $exclude ? NodeInterface::NOT_PUBLISHED : NodeInterface::PUBLISHED,
+    ];
+
+    $statuses = [];
+
+    foreach ($this->testNodesData as $id => $lang_data) {
+      $node = $nodeStorage->load($id);
+      $statuses[$id] = intval($node->status->value);
+
+      // Reset node status.
+      $node->status->value = 1;
+      $node->save();
+    }
+
+    foreach ($statuses as $id => $status) {
+      $asserted = FALSE;
+      foreach ($list as $item) {
+        if ($item[3] == $id) {
+          $this->assertEquals($expected[0], $status);
+          $asserted = TRUE;
+          break;
+        }
+      }
+      if (!$asserted) {
+        $this->assertEquals($expected[1], $status);
+      }
+    }
+  }
+
   /**
    * Tests general functionality of ViewsBulkOperationsActionProcessor.
    *
@@ -66,24 +108,36 @@ public function testViewsbulkOperationsActionProcessor() {
     // Execute the action.
     $results = $this->executeAction($vbo_data);
 
-    $nodeStorage = $this->container->get('entity_type.manager')->getStorage('node');
+    $this->assertNodeStatuses($vbo_data['list']);
+  }
 
-    $statuses = [];
+  /**
+   * Tests exclude mode of ViewsBulkOperationsActionProcessor.
+   *
+   * @covers ::getPageList
+   * @covers ::populateQueue
+   * @covers ::process
+   * @covers ::initialize
+   */
+  public function testViewsbulkOperationsActionProcessorExclude() {
+    $vbo_data = [
+      'view_id' => 'views_bulk_operations_test',
+      'action_id' => 'views_bulk_operations_advanced_test_action',
+      'exclude_mode' => TRUE,
+      'preconfiguration' => [
+        'test_preconfig' => 'test',
+        'test_config' => 'unpublish',
+      ],
+    ];
 
-    foreach ($this->testNodesData as $id => $lang_data) {
-      $node = $nodeStorage->load($id);
-      $statuses[$id] = intval($node->status->value);
-    }
+    // Get list of rows to process from different view pages.
+    $selection = [1, 2, 4, 18];
+    $vbo_data['list'] = $this->getResultsList($vbo_data, $selection);
 
-    foreach ($statuses as $id => $status) {
-      foreach ($vbo_data['list'] as $item) {
-        if ($item[3] == $id) {
-          $this->assertEquals(NodeInterface::NOT_PUBLISHED, $status);
-          break 2;
-        }
-      }
-      $this->assertEquals(NodeInterface::PUBLISHED, $status);
-    }
+    // Execute the action.
+    $results = $this->executeAction($vbo_data);
+
+    $this->assertNodeStatuses($vbo_data['list'], $vbo_data['exclude_mode']);
   }
 
 }
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 8dcf355154..6c80cedd5d 100644
--- a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php
+++ b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php
@@ -3,7 +3,7 @@
 namespace Drupal\Tests\views_bulk_operations\Kernel;
 
 use Drupal\KernelTests\KernelTestBase;
-use Drupal\simpletest\NodeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
 use Drupal\node\Entity\NodeType;
 use Drupal\user\Entity\User;
 use Drupal\views\Views;
@@ -32,6 +32,8 @@ abstract class ViewsBulkOperationsKernelTestBase extends KernelTestBase {
     'exposed_input' => [],
     'batch_size' => 10,
     'relationship_id' => 'none',
+    'exclude_mode' => FALSE,
+    'clear_on_exposed' => FALSE,
   ];
 
   /**
@@ -252,7 +254,7 @@ protected function executeAction(array $vbo_data) {
 
     // Get total rows count.
     $this->vboDataService->init($view, $view->getDisplay(), $vbo_data['relationship_id']);
-    $vbo_data['total_results'] = $this->vboDataService->getTotalResults();
+    $vbo_data['total_results'] = $this->vboDataService->getTotalResults($vbo_data['clear_on_exposed']);
 
     // Get action definition and check if action ID is correct.
     $action_definition = $this->container->get('plugin.manager.views_bulk_operations_action')->getDefinition($vbo_data['action_id']);
@@ -260,6 +262,12 @@ protected function executeAction(array $vbo_data) {
       $vbo_data['action_label'] = (string) $action_definition['label'];
     }
 
+    // Account for eclude mode.
+    if ($vbo_data['exclude_mode']) {
+      $vbo_data['exclude_list'] = $vbo_data['list'];
+      $vbo_data['list'] = [];
+    }
+
     // Populate entity list if empty.
     if (empty($vbo_data['list'])) {
       $context = [];
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 5c8f16eebe..286a4c42a5 100644
--- a/web/modules/views_bulk_operations/tests/src/Unit/ViewsBulkOperationsBatchTest.php
+++ b/web/modules/views_bulk_operations/tests/src/Unit/ViewsBulkOperationsBatchTest.php
@@ -94,7 +94,7 @@ public function testOperation() {
       ->with('test_view')
       ->will($this->returnValue($view));
 
-    $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $entity_manager = $this->createMock('Drupal\Core\Entity\EntityManagerInterface');
     $entity_manager->expects($this->any())
       ->method('getStorage')
       ->with('view')
@@ -133,6 +133,8 @@ public function testOperation() {
       'sandbox' => [
         'processed' => 0,
         'total' => $entities_count,
+        'page' => 0,
+        'npages' => ceil($entities_count / $batch_size),
       ],
     ];
 
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 b567026880..47bf354995 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
@@ -2,6 +2,7 @@
 
 namespace Drupal\views_bulk_operations_test\Plugin\Action;
 
+use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
 use Drupal\views_bulk_operations\Action\ViewsBulkOperationsPreconfigurationInterface;
 use Drupal\Core\Form\FormStateInterface;
@@ -23,6 +24,7 @@
  * )
  */
 class ViewsBulkOperationsAdvancedTestAction extends ViewsBulkOperationsActionBase implements ViewsBulkOperationsPreconfigurationInterface, PluginFormInterface {
+  use MessengerTrait;
 
   /**
    * {@inheritdoc}
@@ -38,7 +40,7 @@ public function execute($entity = NULL) {
       throw new \Exception('Context array empty in action object.');
     }
 
-    drupal_set_message(sprintf('Test action (preconfig: %s, config: %s, label: %s)',
+    $this->messenger()->addMessage(sprintf('Test action (preconfig: %s, config: %s, label: %s)',
       $this->configuration['test_preconfig'],
       $this->configuration['test_config'],
       $entity->label()
diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsPassTestAction.php b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsPassTestAction.php
index c2c0eecb5a..068256abab 100644
--- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsPassTestAction.php
+++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsPassTestAction.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\views_bulk_operations_test\Plugin\Action;
 
+use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
 use Drupal\Core\Session\AccountInterface;
 
@@ -15,13 +16,14 @@
  * )
  */
 class ViewsBulkOperationsPassTestAction extends ViewsBulkOperationsActionBase {
+  use MessengerTrait;
 
   /**
    * {@inheritdoc}
    */
   public function executeMultiple(array $nodes) {
     if (!empty($this->context['sandbox'])) {
-      drupal_set_message(sprintf(
+      $this->messenger()->addMessage(sprintf(
         'Processed %s of %s.',
         $this->context['sandbox']['processed'],
         $this->context['sandbox']['total']
@@ -49,7 +51,7 @@ public function executeMultiple(array $nodes) {
     // On last batch display message if passed rows match.
     if ($processed + $batch_size >= $total) {
       if (empty($this->context['sandbox']['result_pass_error'])) {
-        drupal_set_message('Passed view results match the entity queue.');
+        $this->messenger()->addMessage('Passed view results match the entity queue.');
       }
     }
   }
diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsSimpleTestAction.php b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsSimpleTestAction.php
index a8e0d33886..8a00c1f9ec 100644
--- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsSimpleTestAction.php
+++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/src/Plugin/Action/ViewsBulkOperationsSimpleTestAction.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\views_bulk_operations_test\Plugin\Action;
 
+use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
 use Drupal\Core\Session\AccountInterface;
 
@@ -15,12 +16,13 @@
  * )
  */
 class ViewsBulkOperationsSimpleTestAction extends ViewsBulkOperationsActionBase {
+  use MessengerTrait;
 
   /**
    * {@inheritdoc}
    */
   public function execute($entity = NULL) {
-    drupal_set_message(sprintf('Test action (preconfig: %s, label: %s)',
+    $this->messenger()->addMessage(sprintf('Test action (preconfig: %s, label: %s)',
       $this->configuration['preconfig'],
       $entity->label()
     ));
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 a8b750f94a..dbbc32fae9 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
@@ -2,13 +2,12 @@ name: 'Views Bulk Operations test'
 type: module
 description: 'Support module for testing Views Bulk Operations.'
 package: Testing
-# core: 8.x
+core: 8.x
 dependencies:
   - drupal:views_bulk_operations
   - drupal:node
 
-# Information added by Drupal.org packaging script on 2019-02-21
-version: '8.x-2.5'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2020-02-04
+version: '8.x-3.4'
 project: 'views_bulk_operations'
-datestamp: 1550740388
+datestamp: 1580807961
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 06015357a1..7f895d1016 100644
--- a/web/modules/views_bulk_operations/views_bulk_operations.info.yml
+++ b/web/modules/views_bulk_operations/views_bulk_operations.info.yml
@@ -2,12 +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: 8.x
+core: 8.x
 dependencies:
-  - drupal:views (>=8.4)
+  - drupal:views (>=8.5)
 
-# Information added by Drupal.org packaging script on 2019-02-21
-version: '8.x-2.5'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2020-02-04
+version: '8.x-3.4'
 project: 'views_bulk_operations'
-datestamp: 1550740388
+datestamp: 1580807961
diff --git a/web/modules/views_bulk_operations/views_bulk_operations.services.yml b/web/modules/views_bulk_operations/views_bulk_operations.services.yml
index ef82b7ab8c..56e68de2b0 100644
--- a/web/modules/views_bulk_operations/views_bulk_operations.services.yml
+++ b/web/modules/views_bulk_operations/views_bulk_operations.services.yml
@@ -10,7 +10,7 @@ services:
     arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@event_dispatcher']
   views_bulk_operations.access:
     class: Drupal\views_bulk_operations\Access\ViewsBulkOperationsAccess
-    arguments: ['@user.private_tempstore']
+    arguments: ['@tempstore.private']
     tags:
       - { name: access_check, applies_to: _views_bulk_operation_access }
   views_bulk_operations.view_data_provider:
-- 
GitLab