From 71d76a46ef967245ac1a2f0671048ee7fe33c759 Mon Sep 17 00:00:00 2001
From: "lee.5151" <lee.5151@osu.edu>
Date: Mon, 8 Jul 2024 13:32:44 -0400
Subject: [PATCH] Upgrading drupal/userprotect (1.2.0 => 1.3.0)

---
 composer.json                                 |   5 +-
 composer.lock                                 |  18 +--
 vendor/composer/installed.json                |  21 ++--
 vendor/composer/installed.php                 |  10 +-
 web/modules/userprotect/.gitlab-ci.yml        | 112 ++++++++++++++++++
 web/modules/userprotect/PATCHES.txt           |   7 --
 web/modules/userprotect/README.md             |   4 +-
 web/modules/userprotect/phpstan.neon          |   9 ++
 .../Controller/ProtectionRuleListBuilder.php  |   2 +-
 .../userprotect/src/Entity/ProtectionRule.php |  16 ++-
 .../src/Entity/ProtectionRuleInterface.php    |   2 +-
 .../src/Form/ProtectionRuleAddForm.php        |   3 +-
 .../src/Form/ProtectionRuleFormBase.php       |  56 +++++++--
 .../src/Plugin/UserProtection/Mail.php        |   2 +-
 .../UserProtection/UserProtectionBase.php     |   1 +
 .../UserProtectionInterface.php               |   2 +-
 .../userprotect_test.info.yml                 |   6 +-
 .../RoleDelegationIntegrationTest.php         |  38 ++++--
 .../Functional/UnsavedUserFieldAccessTest.php |   2 -
 .../UserProtectionPermissionsTest.php         |   9 +-
 .../src/Functional/UserProtectionTest.php     |   7 +-
 .../Kernel/Entity/ProtectionRuleUnitTest.php  |   6 +-
 .../UserProtectionBaseUnitTest.php            |   2 +-
 .../tests/src/Unit/UserProtectUnitTest.php    |   2 +-
 web/modules/userprotect/userprotect.info.yml  |  10 +-
 web/modules/userprotect/userprotect.install   |  16 ++-
 web/modules/userprotect/userprotect.module    |   2 +-
 .../userprotect/userprotect.permissions.yml   |   4 +-
 28 files changed, 275 insertions(+), 99 deletions(-)
 create mode 100644 web/modules/userprotect/.gitlab-ci.yml
 delete mode 100644 web/modules/userprotect/PATCHES.txt
 create mode 100644 web/modules/userprotect/phpstan.neon

diff --git a/composer.json b/composer.json
index 3d3388d6d2..f07ac33cba 100644
--- a/composer.json
+++ b/composer.json
@@ -156,7 +156,7 @@
         "drupal/twig_field_value": "^2.0",
         "drupal/twig_tweak": "3.3.0",
         "drupal/ultimate_cron": "^2.0@alpha",
-        "drupal/userprotect": "1.2",
+        "drupal/userprotect": "1.3",
         "drupal/video_embed_field": "2.5",
         "drupal/view_unpublished": "1.2",
         "drupal/views_ajax_history": "1.7",
@@ -302,9 +302,6 @@
                 "Fix Empty Link": "patches/accessibility-fix-empty-link.patch",
                 "3384469": "https://www.drupal.org/files/issues/2023-09-12/social_media_links-change_twitter_to_x-3384469-7.patch"
             },
-            "drupal/userprotect": {
-                "3349663": "https://www.drupal.org/files/issues/2023-07-28/3349663-8.patch"
-            },
             "drupal/views_ajax_history": {
                 "3028400": "https://www.drupal.org/files/issues/2019-05-16/wrong-url-with-infinit-scroll-3028400-1.patch"
             }
diff --git a/composer.lock b/composer.lock
index 95fe59b03c..e6bd337043 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": "51a86303af247daee91a6f15f9f05dea",
+    "content-hash": "409c652f0ac7280b56c8c75279848b14",
     "packages": [
         {
             "name": "algolia/places",
@@ -6225,20 +6225,20 @@
         },
         {
             "name": "drupal/userprotect",
-            "version": "1.2.0",
+            "version": "1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/userprotect.git",
-                "reference": "8.x-1.2"
+                "reference": "8.x-1.3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/userprotect-8.x-1.2.zip",
-                "reference": "8.x-1.2",
-                "shasum": "f3246af48c60b85bb8f240cd98c286e5b38eb33c"
+                "url": "https://ftp.drupal.org/files/projects/userprotect-8.x-1.3.zip",
+                "reference": "8.x-1.3",
+                "shasum": "93a34941254e687e8ca0c0a131336867e426cbf5"
             },
             "require": {
-                "drupal/core": "^8 || ^9 || ^10"
+                "drupal/core": "^8.8 || ^9 || ^10 || ^11"
             },
             "require-dev": {
                 "drupal/role_delegation": "^1.0"
@@ -6246,8 +6246,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.2",
-                    "datestamp": "1670669515",
+                    "version": "8.x-1.3",
+                    "datestamp": "1720107439",
                     "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 15e0f805e4..2035b4d3fc 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -6539,21 +6539,21 @@
         },
         {
             "name": "drupal/userprotect",
-            "version": "1.2.0",
-            "version_normalized": "1.2.0.0",
+            "version": "1.3.0",
+            "version_normalized": "1.3.0.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/userprotect.git",
-                "reference": "8.x-1.2"
+                "reference": "8.x-1.3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/userprotect-8.x-1.2.zip",
-                "reference": "8.x-1.2",
-                "shasum": "f3246af48c60b85bb8f240cd98c286e5b38eb33c"
+                "url": "https://ftp.drupal.org/files/projects/userprotect-8.x-1.3.zip",
+                "reference": "8.x-1.3",
+                "shasum": "93a34941254e687e8ca0c0a131336867e426cbf5"
             },
             "require": {
-                "drupal/core": "^8 || ^9 || ^10"
+                "drupal/core": "^8.8 || ^9 || ^10 || ^11"
             },
             "require-dev": {
                 "drupal/role_delegation": "^1.0"
@@ -6561,15 +6561,12 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.2",
-                    "datestamp": "1670669515",
+                    "version": "8.x-1.3",
+                    "datestamp": "1720107439",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
                     }
-                },
-                "patches_applied": {
-                    "3349663": "https://www.drupal.org/files/issues/2023-07-28/3349663-8.patch"
                 }
             },
             "installation-source": "dist",
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 0a8153074b..bb80bfac7d 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'osu-asc-webservices/d8-upstream',
         'pretty_version' => 'dev-main',
         'version' => 'dev-main',
-        'reference' => '360e7e8f7704019deb9720c254e5336a29f0b32f',
+        'reference' => '54b63c0a26a39ec94e78c8026022d278e441a10b',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -1097,9 +1097,9 @@
             'dev_requirement' => false,
         ),
         'drupal/userprotect' => array(
-            'pretty_version' => '1.2.0',
-            'version' => '1.2.0.0',
-            'reference' => '8.x-1.2',
+            'pretty_version' => '1.3.0',
+            'version' => '1.3.0.0',
+            'reference' => '8.x-1.3',
             'type' => 'drupal-module',
             'install_path' => __DIR__ . '/../../web/modules/userprotect',
             'aliases' => array(),
@@ -1519,7 +1519,7 @@
         'osu-asc-webservices/d8-upstream' => array(
             'pretty_version' => 'dev-main',
             'version' => 'dev-main',
-            'reference' => '360e7e8f7704019deb9720c254e5336a29f0b32f',
+            'reference' => '54b63c0a26a39ec94e78c8026022d278e441a10b',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
diff --git a/web/modules/userprotect/.gitlab-ci.yml b/web/modules/userprotect/.gitlab-ci.yml
new file mode 100644
index 0000000000..7e69d7d880
--- /dev/null
+++ b/web/modules/userprotect/.gitlab-ci.yml
@@ -0,0 +1,112 @@
+################
+# DrupalCI GitLabCI template
+#
+# Gitlab-ci.yml to replicate DrupalCI testing for Contrib
+#
+# With thanks to:
+#   * The GitLab Acceleration Initiative participants
+#   * DrupalSpoons
+################
+
+################
+# Guidelines
+#
+# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification. It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained.
+#
+# However, you can modify this template if you have additional needs for your project.
+################
+
+################
+# Includes
+#
+# Additional configuration can be provided through includes.
+# One advantage of include files is that if they are updated upstream, the changes affect all pipelines using that include.
+#
+# Includes can be overridden by re-declaring anything provided in an include, here in gitlab-ci.yml
+# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values
+################
+
+include:
+  ################
+  # DrupalCI includes:
+  # As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically.
+  # View these include files at https://git.drupalcode.org/project/gitlab_templates/
+  ################
+  - project: $_GITLAB_TEMPLATES_REPO
+    # "ref" value can be:
+    # - Recommended (default) - `ref: $_GITLAB_TEMPLATES_REF` - The Drupal Association will update this value to the recommended tag for contrib.
+    # - Latest - `ref: main` - Get the latest additions and bug fixes as they are merged into the templates.
+    # - Minor or Major latests - `ref: 1.x-latest` or `ref: 1.0.x-latest` - Get the latest additions within a minor (mostly bugfixes) or major (bugs and new features).
+    # - Fixed tag - `ref: 1.0.1` - Set the value to a known tag. This will not get any updates.
+    # If you change the default value of ref, you should set the _CURL_TEMPLATES_REF variable in the variables section to be the same as set here.
+    ref: $_GITLAB_TEMPLATES_REF
+    file:
+      - "/includes/include.drupalci.main.yml"
+      # For Drupal 7, remove the above line and uncomment the below.
+      # - "/includes/include.drupalci.main-d7.yml"
+      - "/includes/include.drupalci.variables.yml"
+      - "/includes/include.drupalci.workflows.yml"
+#
+################
+# Pipeline configuration variables
+#
+# These are the variables provided to the Run Pipeline form that a user may want to override.
+#
+# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/main/includes/include.drupalci.variables.yml
+################
+# variables:
+#   SKIP_ESLINT: '1'
+#   OPT_IN_TEST_NEXT_MAJOR: '1'
+#   _CURL_TEMPLATES_REF: 'main'
+variables:
+  _CSPELL_WORDS: 'Chriz, Koppen, Youri'
+  OPT_IN_TEST_PREVIOUS_MINOR: '1'
+  OPT_IN_TEST_NEXT_MINOR: '1'
+  OPT_IN_TEST_PREVIOUS_MAJOR: '1'
+  OPT_IN_TEST_NEXT_MAJOR: '1'
+
+###################################################################################
+#
+#                                        *
+#                                       /(
+#                                      ((((,
+#                                    /(((((((
+#                                   ((((((((((*
+#                                ,(((((((((((((((
+#                              ,(((((((((((((((((((
+#                            ((((((((((((((((((((((((*
+#                         *(((((((((((((((((((((((((((((
+#                       ((((((((((((((((((((((((((((((((((*
+#                    *((((((((((((((((((  .((((((((((((((((((
+#                  ((((((((((((((((((.       /(((((((((((((((((*
+#                /(((((((((((((((((            .(((((((((((((((((,
+#             ,((((((((((((((((((                 ((((((((((((((((((
+#           .((((((((((((((((((((                   .(((((((((((((((((
+#          (((((((((((((((((((((((                     ((((((((((((((((/
+#        (((((((((((((((((((((((((((/                    ,(((((((((((((((*
+#      .((((((((((((((/  /(((((((((((((.                   ,(((((((((((((((
+#     *((((((((((((((      ,(((((((((((((/                   *((((((((((((((.
+#    ((((((((((((((,          /(((((((((((((.                  ((((((((((((((,
+#   (((((((((((((/              ,(((((((((((((*                 ,(((((((((((((,
+#  *(((((((((((((                .(((((((((((((((                ,(((((((((((((
+#  ((((((((((((/                /((((((((((((((((((.              ,((((((((((((/
+# (((((((((((((              *(((((((((((((((((((((((*             *((((((((((((
+# (((((((((((((            ,(((((((((((((..(((((((((((((           *((((((((((((
+# ((((((((((((,          /((((((((((((*      /((((((((((((/         ((((((((((((
+# (((((((((((((        /((((((((((((/          (((((((((((((*       ((((((((((((
+# (((((((((((((/     /((((((((((((               ,((((((((((((,    *((((((((((((
+#  ((((((((((((((  *(((((((((((/                   *((((((((((((.  ((((((((((((/
+#  *((((((((((((((((((((((((((,                      /(((((((((((((((((((((((((
+#   (((((((((((((((((((((((((                         ((((((((((((((((((((((((,
+#   .(((((((((((((((((((((((/                         ,(((((((((((((((((((((((
+#     ((((((((((((((((((((((/                         ,(((((((((((((((((((((/
+#      *(((((((((((((((((((((                         (((((((((((((((((((((,
+#       ,(((((((((((((((((((((,                      ((((((((((((((((((((/
+#         ,(((((((((((((((((((((*                  /((((((((((((((((((((
+#            ((((((((((((((((((((((,           ,/((((((((((((((((((((,
+#              ,(((((((((((((((((((((((((((((((((((((((((((((((((((
+#                 .(((((((((((((((((((((((((((((((((((((((((((((
+#                     .((((((((((((((((((((((((((((((((((((,.
+#                          .,(((((((((((((((((((((((((.
+#
+###################################################################################
diff --git a/web/modules/userprotect/PATCHES.txt b/web/modules/userprotect/PATCHES.txt
deleted file mode 100644
index c12a3f215b..0000000000
--- a/web/modules/userprotect/PATCHES.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
-Patches applied to this directory:
-
-3349663
-Source: https://www.drupal.org/files/issues/2023-07-28/3349663-8.patch
-
-
diff --git a/web/modules/userprotect/README.md b/web/modules/userprotect/README.md
index a23eb49bbc..a2cbfc1a91 100644
--- a/web/modules/userprotect/README.md
+++ b/web/modules/userprotect/README.md
@@ -18,7 +18,7 @@ protections can be specific to a user, or applied to all users in a role.
 The following protections are supported:
 
  * Username
- * E-mail address
+ * Email address
  * Password
  * Status
  * Roles
@@ -66,7 +66,7 @@ There are two exceptions in which a configured protection rule does not apply:
  * The specified user is the current logged in user.
    Protection rules don't count for the user itself. Instead, there are
    permissions available to prevent an user from editing its own account,
-   username, e-mail address, or password.
+   username, email address, or password.
 
 Protected fields will be disabled or hidden on the form at user/X/edit. The edit
 and delete operations are protected by controlling entity access for the
diff --git a/web/modules/userprotect/phpstan.neon b/web/modules/userprotect/phpstan.neon
new file mode 100644
index 0000000000..c4819cfb0b
--- /dev/null
+++ b/web/modules/userprotect/phpstan.neon
@@ -0,0 +1,9 @@
+includes:
+  - phar://phpstan.phar/conf/bleedingEdge.neon
+
+parameters:
+  level: 1
+  reportUnmatchedIgnoredErrors: false
+  ignoreErrors:
+    # new static() is a best practice in Drupal, so we cannot fix that.
+    - "#^Unsafe usage of new static#"
diff --git a/web/modules/userprotect/src/Controller/ProtectionRuleListBuilder.php b/web/modules/userprotect/src/Controller/ProtectionRuleListBuilder.php
index 487918a560..4a815c7bc6 100644
--- a/web/modules/userprotect/src/Controller/ProtectionRuleListBuilder.php
+++ b/web/modules/userprotect/src/Controller/ProtectionRuleListBuilder.php
@@ -2,10 +2,10 @@
 
 namespace Drupal\userprotect\Controller;
 
-use Drupal\userprotect\Entity\ProtectionRuleInterface;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\userprotect\Entity\ProtectionRuleInterface;
 
 /**
  * Provides a listing of protection rules.
diff --git a/web/modules/userprotect/src/Entity/ProtectionRule.php b/web/modules/userprotect/src/Entity/ProtectionRule.php
index 0fcbaa34f3..d6b7693bbb 100644
--- a/web/modules/userprotect/src/Entity/ProtectionRule.php
+++ b/web/modules/userprotect/src/Entity/ProtectionRule.php
@@ -5,12 +5,14 @@
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
-use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
 use Drupal\user\UserInterface;
-use Drupal\userprotect\UserProtect;
 use Drupal\userprotect\Plugin\UserProtection\UserProtectionPluginCollection;
+use Drupal\userprotect\UserProtect;
 
 /**
  * Defines the Protection rule entity.
@@ -286,7 +288,9 @@ public static function postLoad(EntityStorageInterface $storage_controller, arra
     foreach ($entities as $entity) {
       $permission = $entity->getPermissionName();
       if ($permission) {
-        $roles = array_keys(user_role_names(FALSE, $permission));
+        // Find out which roles may bypass the given protection rule, by
+        // filtering the roles on the bypass permission name for this rule.
+        $roles = array_keys(array_filter(Role::loadMultiple(), fn(RoleInterface $role) => $role->hasPermission($permission)));
         $entity->setBypassRoles($roles);
       }
     }
@@ -302,7 +306,7 @@ public function postSave(EntityStorageInterface $storage_controller, $update = T
     $roles = $this->getBypassRoles();
     $permission = $this->getPermissionName();
     if ($roles && $permission) {
-      foreach (user_roles() as $rid => $name) {
+      foreach (Role::loadMultiple() as $rid => $name) {
         $enabled = in_array($rid, $roles, TRUE);
         user_role_change_permissions($rid, [$permission => $enabled]);
       }
@@ -344,9 +348,9 @@ public function hasProtection($protection) {
    * {@inheritdoc}
    */
   public function isProtected(UserInterface $user, $op, AccountInterface $account) {
-    // First check if this protection rule is applyable to the given user.
+    // First check if this protection rule is applicable to the given user.
     if (!$this->appliesTo($user)) {
-      // Not applyable. The operation is not protected by this rule.
+      // Not applicable. The operation is not protected by this rule.
       return FALSE;
     }
 
diff --git a/web/modules/userprotect/src/Entity/ProtectionRuleInterface.php b/web/modules/userprotect/src/Entity/ProtectionRuleInterface.php
index 90ab7da2bb..89970f57f7 100644
--- a/web/modules/userprotect/src/Entity/ProtectionRuleInterface.php
+++ b/web/modules/userprotect/src/Entity/ProtectionRuleInterface.php
@@ -36,7 +36,7 @@ public function setProtectedEntityTypeId($entity_type_id);
   /**
    * Gets the protected entity.
    *
-   * @return EntityInterface
+   * @return \Drupal\Core\Entity\EntityInterface|null
    *   The loaded entity, if found.
    *   NULL otherwise.
    */
diff --git a/web/modules/userprotect/src/Form/ProtectionRuleAddForm.php b/web/modules/userprotect/src/Form/ProtectionRuleAddForm.php
index 44a6fb3777..676823dc72 100644
--- a/web/modules/userprotect/src/Form/ProtectionRuleAddForm.php
+++ b/web/modules/userprotect/src/Form/ProtectionRuleAddForm.php
@@ -43,8 +43,9 @@ public function buildForm(array $form, FormStateInterface $form_state, $protecte
    * {@inheritdoc}
    */
   public function save(array $form, FormStateInterface $form_state) {
-    parent::save($form, $form_state);
+    $result = parent::save($form, $form_state);
     $this->messenger->addMessage($this->t('Added protection rule %name.', ['%name' => $this->entity->label()]));
+    return $result;
   }
 
 }
diff --git a/web/modules/userprotect/src/Form/ProtectionRuleFormBase.php b/web/modules/userprotect/src/Form/ProtectionRuleFormBase.php
index 48498bd4cd..a73e116ae7 100644
--- a/web/modules/userprotect/src/Form/ProtectionRuleFormBase.php
+++ b/web/modules/userprotect/src/Form/ProtectionRuleFormBase.php
@@ -3,10 +3,12 @@
 namespace Drupal\userprotect\Form;
 
 use Drupal\Core\Entity\EntityForm;
-use Drupal\user\UserStorageInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+use Drupal\user\UserStorageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -45,11 +47,7 @@ abstract class ProtectionRuleFormBase extends EntityForm {
    * @param \Drupal\Core\Messenger\MessengerInterface $messenger
    *   The messenger service.
    */
-  public function __construct(
-    EntityStorageInterface $protection_rule_storage,
-    UserStorageInterface $user_storage,
-    MessengerInterface $messenger
-  ) {
+  public function __construct(EntityStorageInterface $protection_rule_storage, UserStorageInterface $user_storage, MessengerInterface $messenger) {
     $this->protectionRuleStorage = $protection_rule_storage;
     $this->userStorage = $user_storage;
     $this->messenger = $messenger;
@@ -71,8 +69,10 @@ public static function create(ContainerInterface $container) {
    */
   protected function getBypassMessage($permission, $permission_label) {
     $message = $this->t('Users with the permission "%permission".', ['%permission' => $permission_label]);
-    $roles = user_role_names(FALSE, $permission);
-    if (count($roles)) {
+    $roles = $this->getRoleNames([
+      'permission' => $permission,
+    ]);
+    if (count($roles) > 0) {
       $message .= '<br /><div class="description">' . $this->t('Currently the following roles have this permission:  %roles.', ['%roles' => implode(', ', $roles)]) . '</div>';
     }
     else {
@@ -171,7 +171,11 @@ public function form(array $form, FormStateInterface $form_state) {
         break;
 
       case 'user_role':
-        $entities = array_map('Drupal\Component\Utility\Html::escape', user_role_names(TRUE));
+        // Compose a list of roles that can be protected, but exclude the
+        // anonymous role from this list.
+        $entities = array_map('Drupal\Component\Utility\Html::escape', $this->getRoleNames([
+          'anonymous' => FALSE,
+        ]));
         $form['entity_id'] = [
           '#type' => 'select',
           '#title' => $this->t('Role'),
@@ -205,7 +209,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#default_value' => $enabled_protections,
     ];
 
-    $roles = array_map('Drupal\Component\Utility\Html::escape', user_role_names());
+    $roles = array_map('Drupal\Component\Utility\Html::escape', $this->getRoleNames());
     $form['bypass_roles'] = [
       '#type' => 'checkboxes',
       '#title' => $this->t('Bypass for roles'),
@@ -251,8 +255,38 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function save(array $form, FormStateInterface $form_state) {
-    $this->entity->save();
+    $result = $this->entity->save();
     $form_state->setRedirect('userprotect.rule_list');
+    return $result;
+  }
+
+  /**
+   * Returns a list of role names.
+   *
+   * @param array $options
+   *   (optional) An associative array with additional options. The following
+   *   keys can be used:
+   *   - permission: (string) filter role names by permission.
+   *   - anonymous: (bool) Whether or not to include the anonymous role.
+   *     Defaults to true.
+   *
+   * @return string[]
+   *   A list of role names.
+   */
+  protected function getRoleNames(array $options = []): array {
+    $options += [
+      'permission' => NULL,
+      'anonymous' => TRUE,
+    ];
+    $roles = Role::loadMultiple();
+    if (!$options['anonymous']) {
+      unset($roles[RoleInterface::ANONYMOUS_ID]);
+    }
+    if (isset($options['permission'])) {
+      $roles = array_filter($roles, fn(RoleInterface $role) => $role->hasPermission($options['permission']));
+    }
+
+    return array_map(fn(RoleInterface $role) => $role->label(), $roles);
   }
 
 }
diff --git a/web/modules/userprotect/src/Plugin/UserProtection/Mail.php b/web/modules/userprotect/src/Plugin/UserProtection/Mail.php
index 837346ead9..8ab5be9513 100644
--- a/web/modules/userprotect/src/Plugin/UserProtection/Mail.php
+++ b/web/modules/userprotect/src/Plugin/UserProtection/Mail.php
@@ -9,7 +9,7 @@
  *
  * @UserProtection(
  *   id = "user_mail",
- *   label = @Translation("E-mail address"),
+ *   label = @Translation("Email address"),
  *   weight = -9
  * )
  */
diff --git a/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionBase.php b/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionBase.php
index ddc1f72765..8f980bdec3 100644
--- a/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionBase.php
+++ b/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionBase.php
@@ -105,6 +105,7 @@ public function isProtected(UserInterface $user, $op, AccountInterface $account)
     if ($op == $this->getPluginId()) {
       return TRUE;
     }
+    return FALSE;
   }
 
   /**
diff --git a/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionInterface.php b/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionInterface.php
index 8fa2e0992b..f9c0e673d3 100644
--- a/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionInterface.php
+++ b/web/modules/userprotect/src/Plugin/UserProtection/UserProtectionInterface.php
@@ -2,9 +2,9 @@
 
 namespace Drupal\userprotect\Plugin\UserProtection;
 
-use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Plugin\ConfigurableInterface;
 use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\user\UserInterface;
diff --git a/web/modules/userprotect/tests/modules/userprotect_test/userprotect_test.info.yml b/web/modules/userprotect/tests/modules/userprotect_test/userprotect_test.info.yml
index 9278665a46..fa6b0ac456 100644
--- a/web/modules/userprotect/tests/modules/userprotect_test/userprotect_test.info.yml
+++ b/web/modules/userprotect/tests/modules/userprotect_test/userprotect_test.info.yml
@@ -5,7 +5,7 @@ package: Testing
 dependencies:
   - userprotect:userprotect
 
-# Information added by Drupal.org packaging script on 2022-12-10
-version: '8.x-1.2'
+# Information added by Drupal.org packaging script on 2024-07-04
+version: '8.x-1.3'
 project: 'userprotect'
-datestamp: 1670669517
+datestamp: 1720107442
diff --git a/web/modules/userprotect/tests/src/Functional/RoleDelegation/RoleDelegationIntegrationTest.php b/web/modules/userprotect/tests/src/Functional/RoleDelegation/RoleDelegationIntegrationTest.php
index 3110ba24b5..55e10f37d8 100644
--- a/web/modules/userprotect/tests/src/Functional/RoleDelegation/RoleDelegationIntegrationTest.php
+++ b/web/modules/userprotect/tests/src/Functional/RoleDelegation/RoleDelegationIntegrationTest.php
@@ -2,7 +2,10 @@
 
 namespace Drupal\Tests\userprotect\Functional\RoleDelegation;
 
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Tests\BrowserTestBase;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
 use Drupal\userprotect\Entity\ProtectionRule;
 
 /**
@@ -167,9 +170,19 @@ public function testUserEditPage() {
    * Test that user protect rules are also enabled on /user/%user/roles.
    */
   public function testRolesPage() {
-    // Ensure that an anonymous user cannot access teh user protect settings
+    // Since Role Delegation 8.x-1.3, users with the permission
+    // "administer users" cannot access the roles page.
+    $this->getFirstRole($this->roleDelegatedAdminUser)
+      ->revokePermission('administer users')
+      ->save();
+    $this->getFirstRole($this->regularRolesAdminUser)
+      ->revokePermission('administer users')
+      ->save();
+
+    // Ensure that an anonymous user cannot access the user protect settings
     // page.
     $this->drupalGet('admin/config/people/userprotect/manage/protect_admin_role');
+    $this->assertSession()->statusCodeEquals(403);
 
     // Login as the delegated admin user. This user has permission to assign
     // roles 1 and 2 to users.
@@ -194,16 +207,21 @@ public function testRolesPage() {
 
     $this->drupalGet(sprintf('/user/%s/roles', $this->adminUser->id()));
     $this->assertSession()->statusCodeEquals(403);
+  }
 
-    // Login as an user with the admin role. This user has all privileges.
-    $this->drupalLogin($this->adminUser);
-
-    // Ensure the admin user can access the roles edit page of all users.
-    $this->drupalGet(sprintf('/user/%s/roles', $this->roleDelegatedAdminUser->id()));
-    $this->assertSession()->statusCodeEquals(200);
-
-    $this->drupalGet(sprintf('/user/%s/roles', $this->adminUser->id()));
-    $this->assertSession()->statusCodeEquals(200);
+  /**
+   * Returns the first role of the given user.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The account for which to load the first role.
+   *
+   * @return \Drupal\user\RoleInterface
+   *   The role of the given user.
+   */
+  protected function getFirstRole(AccountInterface $account): RoleInterface {
+    $role_ids = $account->getRoles(TRUE);
+    $rid = current($role_ids);
+    return Role::load($rid);
   }
 
 }
diff --git a/web/modules/userprotect/tests/src/Functional/UnsavedUserFieldAccessTest.php b/web/modules/userprotect/tests/src/Functional/UnsavedUserFieldAccessTest.php
index b210e839b6..ad51d69cc0 100644
--- a/web/modules/userprotect/tests/src/Functional/UnsavedUserFieldAccessTest.php
+++ b/web/modules/userprotect/tests/src/Functional/UnsavedUserFieldAccessTest.php
@@ -78,7 +78,6 @@ public function testNameAccessForUnsavedUser() {
 
     // The logged in user should have the privileges to edit the unsaved user's
     // name.
-    $this->assertTrue($unsavedUserEntity->isAnonymous(), 'Unsaved user is considered anonymous when userprotect is installed.');
     $this->assertTrue($unsavedUserEntity->get('name')->access('edit'), 'Logged in user is allowed to edit name field when userprotect is installed.');
 
     // Uninstall userprotect and verify that logged in user has privileges to
@@ -91,7 +90,6 @@ public function testNameAccessForUnsavedUser() {
     $module_handler = $this->container->get('module_handler');
 
     $this->assertFalse($module_handler->moduleExists('userprotect'), 'Userprotect uninstalled successfully.');
-    $this->assertTrue($unsavedUserEntity->isAnonymous(), 'Unsaved user is considered anonymous when userprotect is uninstalled.');
     $this->assertTrue($unsavedUserEntity->get('name')->access('edit'), 'Logged in user is allowed to edit name field when userprotect is uninstalled.');
   }
 
diff --git a/web/modules/userprotect/tests/src/Functional/UserProtectionPermissionsTest.php b/web/modules/userprotect/tests/src/Functional/UserProtectionPermissionsTest.php
index 56a3e05af3..6d9c40cce3 100644
--- a/web/modules/userprotect/tests/src/Functional/UserProtectionPermissionsTest.php
+++ b/web/modules/userprotect/tests/src/Functional/UserProtectionPermissionsTest.php
@@ -8,7 +8,7 @@
  * Tests if "change own" User Protect permissions are respected.
  *
  * The test includes coverage for the following permissions:
- * - Change own e-mail (userprotect.mail.edit);
+ * - Change own email (userprotect.mail.edit);
  * - Change own password (userprotect.pass.edit);
  * - Change own account (userprotect.account.edit).
  *
@@ -77,7 +77,12 @@ public function testNoEditOwnMail() {
     $this->drupalLogin($account);
 
     $this->drupalGet('user/' . $account->id() . '/edit');
-    $this->assertSession()->fieldDisabled('mail');
+    if (version_compare(\Drupal::VERSION, '10.1.0', '>=')) {
+      $this->assertSession()->fieldNotExists('mail');
+    }
+    else {
+      $this->assertSession()->fieldDisabled('mail');
+    }
   }
 
   /**
diff --git a/web/modules/userprotect/tests/src/Functional/UserProtectionTest.php b/web/modules/userprotect/tests/src/Functional/UserProtectionTest.php
index 286f4fa5aa..96a5c1b1aa 100644
--- a/web/modules/userprotect/tests/src/Functional/UserProtectionTest.php
+++ b/web/modules/userprotect/tests/src/Functional/UserProtectionTest.php
@@ -48,7 +48,12 @@ public function testMailProtection() {
     $protected_account = $this->createProtectedUser(['user_mail']);
 
     $this->drupalGet('user/' . $protected_account->id() . '/edit');
-    $this->assertSession()->fieldDisabled('mail');
+    if (version_compare(\Drupal::VERSION, '10.1.0', '>=')) {
+      $this->assertSession()->fieldNotExists('mail');
+    }
+    else {
+      $this->assertSession()->fieldDisabled('mail');
+    }
   }
 
   /**
diff --git a/web/modules/userprotect/tests/src/Kernel/Entity/ProtectionRuleUnitTest.php b/web/modules/userprotect/tests/src/Kernel/Entity/ProtectionRuleUnitTest.php
index f5e7160d6f..011a9609fb 100644
--- a/web/modules/userprotect/tests/src/Kernel/Entity/ProtectionRuleUnitTest.php
+++ b/web/modules/userprotect/tests/src/Kernel/Entity/ProtectionRuleUnitTest.php
@@ -2,14 +2,14 @@
 
 namespace Drupal\Tests\userprotect\Kernel\Entity;
 
-use Drupal\user\Entity\User;
 use Drupal\Core\Session\UserSession;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\user\Entity\User;
 use Drupal\userprotect\Entity\ProtectionRule;
 use Drupal\userprotect\Entity\ProtectionRuleInterface;
 use Drupal\userprotect\Plugin\UserProtection\UserProtectionInterface;
-use Drupal\userprotect\UserProtect;
 use Drupal\userprotect\Plugin\UserProtection\UserProtectionPluginCollection;
-use Drupal\KernelTests\KernelTestBase;
+use Drupal\userprotect\UserProtect;
 
 /**
  * Various unit tests for protection rules.
diff --git a/web/modules/userprotect/tests/src/Unit/Plugin/UserProtection/UserProtectionBaseUnitTest.php b/web/modules/userprotect/tests/src/Unit/Plugin/UserProtection/UserProtectionBaseUnitTest.php
index 6f95d8efbd..c07df15974 100644
--- a/web/modules/userprotect/tests/src/Unit/Plugin/UserProtection/UserProtectionBaseUnitTest.php
+++ b/web/modules/userprotect/tests/src/Unit/Plugin/UserProtection/UserProtectionBaseUnitTest.php
@@ -56,7 +56,7 @@ public function setUp(): void {
       ->getMock();
     $this->plugin->expects($this->any())
       ->method('t')
-      ->will($this->returnArgument(0));
+      ->willReturnArgument(0);
   }
 
   /**
diff --git a/web/modules/userprotect/tests/src/Unit/UserProtectUnitTest.php b/web/modules/userprotect/tests/src/Unit/UserProtectUnitTest.php
index c871a628e7..2a83ef0a91 100644
--- a/web/modules/userprotect/tests/src/Unit/UserProtectUnitTest.php
+++ b/web/modules/userprotect/tests/src/Unit/UserProtectUnitTest.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\Tests\userprotect\Unit;
 
-use Drupal\userprotect\UserProtect;
 use Drupal\Tests\UnitTestCase;
+use Drupal\userprotect\UserProtect;
 use Symfony\Component\DependencyInjection\Container;
 
 /**
diff --git a/web/modules/userprotect/userprotect.info.yml b/web/modules/userprotect/userprotect.info.yml
index bf495861a2..fc4fbe7c24 100644
--- a/web/modules/userprotect/userprotect.info.yml
+++ b/web/modules/userprotect/userprotect.info.yml
@@ -1,14 +1,12 @@
 name: User Protect
 type: module
 description: 'Allows admins to protect users from being edited or cancelled, on a per-user basis.'
-core: 8.x
-core_version_requirement: ^8 || ^9 || ^10
+core_version_requirement: ^8.8 || ^9 || ^10 || ^11
 configure: userprotect.rule_list
 dependencies:
-  - drupal:system (>=8.7.0)
   - drupal:user
 
-# Information added by Drupal.org packaging script on 2022-12-10
-version: '8.x-1.2'
+# Information added by Drupal.org packaging script on 2024-07-04
+version: '8.x-1.3'
 project: 'userprotect'
-datestamp: 1670669517
+datestamp: 1720107442
diff --git a/web/modules/userprotect/userprotect.install b/web/modules/userprotect/userprotect.install
index f85f169c91..882138691a 100644
--- a/web/modules/userprotect/userprotect.install
+++ b/web/modules/userprotect/userprotect.install
@@ -5,7 +5,8 @@
  * Install, update and uninstall functions for the userprotect module.
  */
 
-use Drupal\Core\Session\AccountInterface;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
 
 /**
  * Implements hook_install().
@@ -16,9 +17,12 @@ function userprotect_install($is_syncing) {
   if ($is_syncing) {
     return;
   }
-  $role = \Drupal::entityTypeManager()->getStorage('user_role')->load(AccountInterface::AUTHENTICATED_ROLE);
-  $role->grantPermission('userprotect.mail.edit');
-  $role->grantPermission('userprotect.pass.edit');
-  $role->grantPermission('userprotect.account.edit');
-  $role->save();
+
+  $role = Role::load(RoleInterface::AUTHENTICATED_ID);
+  if ($role instanceof RoleInterface) {
+    $role->grantPermission('userprotect.mail.edit');
+    $role->grantPermission('userprotect.pass.edit');
+    $role->grantPermission('userprotect.account.edit');
+    $role->trustData()->save();
+  }
 }
diff --git a/web/modules/userprotect/userprotect.module b/web/modules/userprotect/userprotect.module
index 6609ce7210..d62b99b9c0 100644
--- a/web/modules/userprotect/userprotect.module
+++ b/web/modules/userprotect/userprotect.module
@@ -38,7 +38,7 @@ function userprotect_user_access(UserInterface $entity, $op, AccountInterface $a
   // Check if the account has the permission "userprotect.bypass_all".
   // If so, all protections rules should be ignored.
   if (!$account->hasPermission('userprotect.bypass_all')) {
-    // Users editing their own accounts have the permissions for e-mail
+    // Users editing their own accounts have the permissions for email
     // and password determined by the role-based setting in the userprotect
     // section at admin/config/people/permissions. This is done for consistency
     // with the way core handles the self-editing of usernames.
diff --git a/web/modules/userprotect/userprotect.permissions.yml b/web/modules/userprotect/userprotect.permissions.yml
index 44a3e950d7..f732c4fcfd 100644
--- a/web/modules/userprotect/userprotect.permissions.yml
+++ b/web/modules/userprotect/userprotect.permissions.yml
@@ -1,6 +1,6 @@
 userprotect.mail.edit:
-  title: 'Change own e-mail'
-  description: 'Allow users to edit their own e-mail address.'
+  title: 'Change own email'
+  description: 'Allow users to edit their own email address.'
 userprotect.pass.edit:
   title: 'Change own password'
   description: 'Allow users to edit their own password.'
-- 
GitLab