diff --git a/composer.json b/composer.json
index 8e3d7ce915f6b6eb38a84e521b612376b7d4048e..505dc002c2a8f2f6a6ff1d9dfaa8676b206046e9 100644
--- a/composer.json
+++ b/composer.json
@@ -155,7 +155,7 @@
         "drupal/recaptcha_v3": "^1.4",
         "drupal/redirect": "1.8",
         "drupal/roleassign": "1.0.0-beta1",
-        "drupal/scheduler": "^2.0",
+        "drupal/scheduler": "1.4",
         "drupal/simple_gmap": "3.0.1",
         "drupal/simple_instagram_feed": "^3.11",
         "drupal/simple_sitemap": "3.11",
diff --git a/composer.lock b/composer.lock
index ec239c9605ae4bdad21989082da3efaba2715e6a..85ec6cc49b5279a0750d3a30cdb8867444a069cf 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": "e23a8b84339c35e81cf6a3b51761d8cc",
+    "content-hash": "e2f1e64fc0f9e9f2324cb800fd3f83a7",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -6757,35 +6757,34 @@
         },
         {
             "name": "drupal/scheduler",
-            "version": "2.0.0-rc8",
+            "version": "1.4.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/scheduler.git",
-                "reference": "2.0.0-rc8"
+                "reference": "8.x-1.4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/scheduler-2.0.0-rc8.zip",
-                "reference": "2.0.0-rc8",
-                "shasum": "1aa120cd855100fba33aeedb46ce55d8a1acd448"
+                "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.4.zip",
+                "reference": "8.x-1.4",
+                "shasum": "5b2203e4688e5d3ac67d0780605809c92c6ece70"
             },
             "require": {
-                "drupal/core": "^8 || ^9 || ^10"
+                "drupal/core": "^8 || ^9"
             },
             "require-dev": {
-                "drupal/commerce": "^2.0",
-                "drupal/devel_generate": ">=4",
+                "drupal/devel_generate": "^2.0 || 4.x-dev",
                 "drupal/rules": "^3",
-                "drush/drush": ">=9"
+                "drush/drush": "^9.0 || ^10"
             },
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "2.0.0-rc8",
-                    "datestamp": "1668951102",
+                    "version": "8.x-1.4",
+                    "datestamp": "1654699160",
                     "security-coverage": {
-                        "status": "not-covered",
-                        "message": "RC releases are not covered by Drupal security advisories."
+                        "status": "covered",
+                        "message": "Covered by Drupal's security advisory policy"
                     }
                 },
                 "drush": {
@@ -8398,25 +8397,24 @@
         },
         {
             "name": "egulias/email-validator",
-            "version": "3.2.1",
+            "version": "3.2.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/egulias/EmailValidator.git",
-                "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715"
+                "reference": "b531a2311709443320c786feb4519cfaf94af796"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/f88dcf4b14af14a98ad96b14b2b317969eab6715",
-                "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715",
+                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b531a2311709443320c786feb4519cfaf94af796",
+                "reference": "b531a2311709443320c786feb4519cfaf94af796",
                 "shasum": ""
             },
             "require": {
-                "doctrine/lexer": "^1.2",
+                "doctrine/lexer": "^1.2|^2",
                 "php": ">=7.2",
                 "symfony/polyfill-intl-idn": "^1.15"
             },
             "require-dev": {
-                "php-coveralls/php-coveralls": "^2.2",
                 "phpunit/phpunit": "^8.5.8|^9.3.3",
                 "vimeo/psalm": "^4"
             },
@@ -8454,7 +8452,7 @@
             ],
             "support": {
                 "issues": "https://github.com/egulias/EmailValidator/issues",
-                "source": "https://github.com/egulias/EmailValidator/tree/3.2.1"
+                "source": "https://github.com/egulias/EmailValidator/tree/3.2.5"
             },
             "funding": [
                 {
@@ -8462,7 +8460,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-06-18T20:57:19+00:00"
+            "time": "2023-01-02T17:26:14+00:00"
         },
         {
             "name": "enshrined/svg-sanitize",
@@ -15682,16 +15680,16 @@
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v5.4.14",
+            "version": "v5.4.17",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "6894d06145fefebd9a4c7272baa026a1c394a430"
+                "reference": "ad74890513d07060255df2575703daf971de92c7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6894d06145fefebd9a4c7272baa026a1c394a430",
-                "reference": "6894d06145fefebd9a4c7272baa026a1c394a430",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ad74890513d07060255df2575703daf971de92c7",
+                "reference": "ad74890513d07060255df2575703daf971de92c7",
                 "shasum": ""
             },
             "require": {
@@ -15751,7 +15749,7 @@
                 "dump"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v5.4.14"
+                "source": "https://github.com/symfony/var-dumper/tree/v5.4.17"
             },
             "funding": [
                 {
@@ -15767,20 +15765,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-10-07T08:01:20+00:00"
+            "time": "2022-12-22T10:31:03+00:00"
         },
         {
             "name": "symfony/var-exporter",
-            "version": "v5.4.10",
+            "version": "v5.4.17",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-exporter.git",
-                "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340"
+                "reference": "2adac0a9b55f9fb40b983b790509581dc3db0fff"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8fc03ee75eeece3d9be1ef47d26d79bea1afb340",
-                "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/2adac0a9b55f9fb40b983b790509581dc3db0fff",
+                "reference": "2adac0a9b55f9fb40b983b790509581dc3db0fff",
                 "shasum": ""
             },
             "require": {
@@ -15824,7 +15822,7 @@
                 "serialize"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-exporter/tree/v5.4.10"
+                "source": "https://github.com/symfony/var-exporter/tree/v5.4.17"
             },
             "funding": [
                 {
@@ -15840,7 +15838,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-05-27T12:56:18+00:00"
+            "time": "2022-12-22T10:10:04+00:00"
         },
         {
             "name": "symfony/yaml",
@@ -15975,16 +15973,16 @@
         },
         {
             "name": "twig/twig",
-            "version": "v2.15.3",
+            "version": "v2.15.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/twigphp/Twig.git",
-                "reference": "ab402673db8746cb3a4c46f3869d6253699f614a"
+                "reference": "3e059001d6d597dd50ea7c74dd2464b4adea48d3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/twigphp/Twig/zipball/ab402673db8746cb3a4c46f3869d6253699f614a",
-                "reference": "ab402673db8746cb3a4c46f3869d6253699f614a",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/3e059001d6d597dd50ea7c74dd2464b4adea48d3",
+                "reference": "3e059001d6d597dd50ea7c74dd2464b4adea48d3",
                 "shasum": ""
             },
             "require": {
@@ -16039,7 +16037,7 @@
             ],
             "support": {
                 "issues": "https://github.com/twigphp/Twig/issues",
-                "source": "https://github.com/twigphp/Twig/tree/v2.15.3"
+                "source": "https://github.com/twigphp/Twig/tree/v2.15.4"
             },
             "funding": [
                 {
@@ -16051,7 +16049,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-09-28T08:40:08+00:00"
+            "time": "2022-12-27T12:26:20+00:00"
         },
         {
             "name": "typo3/phar-stream-wrapper",
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index 69156ac9f9202183ddf918685b8e85bada8cb213..7f3992dc8e7abd507e892abff92f8a1a01b1c85f 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -2284,8 +2284,6 @@
     'Drupal\\Core\\Language\\LanguageManager' => $baseDir . '/web/core/lib/Drupal/Core/Language/LanguageManager.php',
     'Drupal\\Core\\Language\\LanguageManagerInterface' => $baseDir . '/web/core/lib/Drupal/Core/Language/LanguageManagerInterface.php',
     'Drupal\\Core\\Layout\\Annotation\\Layout' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Annotation/Layout.php',
-    'Drupal\\Core\\Layout\\Icon\\IconBuilderInterface' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php',
-    'Drupal\\Core\\Layout\\Icon\\SvgIconBuilder' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php',
     'Drupal\\Core\\Layout\\LayoutDefault' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutDefault.php',
     'Drupal\\Core\\Layout\\LayoutDefinition' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutDefinition.php',
     'Drupal\\Core\\Layout\\LayoutInterface' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutInterface.php',
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 8144af166e94a67462a3be4fe3e31748530ca50f..dc27cc7768d43d611825e74239d891ad839dd737 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -3105,8 +3105,6 @@ class ComposerStaticInit5c689ffcd54b9e495ed983fdce09b530
         'Drupal\\Core\\Language\\LanguageManager' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Language/LanguageManager.php',
         'Drupal\\Core\\Language\\LanguageManagerInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Language/LanguageManagerInterface.php',
         'Drupal\\Core\\Layout\\Annotation\\Layout' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Annotation/Layout.php',
-        'Drupal\\Core\\Layout\\Icon\\IconBuilderInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php',
-        'Drupal\\Core\\Layout\\Icon\\SvgIconBuilder' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php',
         'Drupal\\Core\\Layout\\LayoutDefault' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutDefault.php',
         'Drupal\\Core\\Layout\\LayoutDefinition' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutDefinition.php',
         'Drupal\\Core\\Layout\\LayoutInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutInterface.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 8004751fc0d6ebbae5223bd66b4a01bc57da6e07..0037016ab3062b58fa1816a65397d6e3c616871d 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -7006,36 +7006,35 @@
         },
         {
             "name": "drupal/scheduler",
-            "version": "2.0.0-rc8",
-            "version_normalized": "2.0.0.0-RC8",
+            "version": "1.4.0",
+            "version_normalized": "1.4.0.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/scheduler.git",
-                "reference": "2.0.0-rc8"
+                "reference": "8.x-1.4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/scheduler-2.0.0-rc8.zip",
-                "reference": "2.0.0-rc8",
-                "shasum": "1aa120cd855100fba33aeedb46ce55d8a1acd448"
+                "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.4.zip",
+                "reference": "8.x-1.4",
+                "shasum": "5b2203e4688e5d3ac67d0780605809c92c6ece70"
             },
             "require": {
-                "drupal/core": "^8 || ^9 || ^10"
+                "drupal/core": "^8 || ^9"
             },
             "require-dev": {
-                "drupal/commerce": "^2.0",
-                "drupal/devel_generate": ">=4",
+                "drupal/devel_generate": "^2.0 || 4.x-dev",
                 "drupal/rules": "^3",
-                "drush/drush": ">=9"
+                "drush/drush": "^9.0 || ^10"
             },
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "2.0.0-rc8",
-                    "datestamp": "1668951102",
+                    "version": "8.x-1.4",
+                    "datestamp": "1654699160",
                     "security-coverage": {
-                        "status": "not-covered",
-                        "message": "RC releases are not covered by Drupal security advisories."
+                        "status": "covered",
+                        "message": "Covered by Drupal's security advisory policy"
                     }
                 },
                 "drush": {
@@ -8731,33 +8730,32 @@
         },
         {
             "name": "egulias/email-validator",
-            "version": "3.2.1",
-            "version_normalized": "3.2.1.0",
+            "version": "3.2.5",
+            "version_normalized": "3.2.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/egulias/EmailValidator.git",
-                "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715"
+                "reference": "b531a2311709443320c786feb4519cfaf94af796"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/f88dcf4b14af14a98ad96b14b2b317969eab6715",
-                "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715",
+                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b531a2311709443320c786feb4519cfaf94af796",
+                "reference": "b531a2311709443320c786feb4519cfaf94af796",
                 "shasum": ""
             },
             "require": {
-                "doctrine/lexer": "^1.2",
+                "doctrine/lexer": "^1.2|^2",
                 "php": ">=7.2",
                 "symfony/polyfill-intl-idn": "^1.15"
             },
             "require-dev": {
-                "php-coveralls/php-coveralls": "^2.2",
                 "phpunit/phpunit": "^8.5.8|^9.3.3",
                 "vimeo/psalm": "^4"
             },
             "suggest": {
                 "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
             },
-            "time": "2022-06-18T20:57:19+00:00",
+            "time": "2023-01-02T17:26:14+00:00",
             "type": "library",
             "extra": {
                 "branch-alias": {
@@ -8790,7 +8788,7 @@
             ],
             "support": {
                 "issues": "https://github.com/egulias/EmailValidator/issues",
-                "source": "https://github.com/egulias/EmailValidator/tree/3.2.1"
+                "source": "https://github.com/egulias/EmailValidator/tree/3.2.5"
             },
             "funding": [
                 {
@@ -16290,17 +16288,17 @@
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v5.4.14",
-            "version_normalized": "5.4.14.0",
+            "version": "v5.4.17",
+            "version_normalized": "5.4.17.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "6894d06145fefebd9a4c7272baa026a1c394a430"
+                "reference": "ad74890513d07060255df2575703daf971de92c7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6894d06145fefebd9a4c7272baa026a1c394a430",
-                "reference": "6894d06145fefebd9a4c7272baa026a1c394a430",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ad74890513d07060255df2575703daf971de92c7",
+                "reference": "ad74890513d07060255df2575703daf971de92c7",
                 "shasum": ""
             },
             "require": {
@@ -16324,7 +16322,7 @@
                 "ext-intl": "To show region name in time zone dump",
                 "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
             },
-            "time": "2022-10-07T08:01:20+00:00",
+            "time": "2022-12-22T10:31:03+00:00",
             "bin": [
                 "Resources/bin/var-dump-server"
             ],
@@ -16362,7 +16360,7 @@
                 "dump"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v5.4.14"
+                "source": "https://github.com/symfony/var-dumper/tree/v5.4.17"
             },
             "funding": [
                 {
@@ -16382,17 +16380,17 @@
         },
         {
             "name": "symfony/var-exporter",
-            "version": "v5.4.10",
-            "version_normalized": "5.4.10.0",
+            "version": "v5.4.17",
+            "version_normalized": "5.4.17.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-exporter.git",
-                "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340"
+                "reference": "2adac0a9b55f9fb40b983b790509581dc3db0fff"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8fc03ee75eeece3d9be1ef47d26d79bea1afb340",
-                "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/2adac0a9b55f9fb40b983b790509581dc3db0fff",
+                "reference": "2adac0a9b55f9fb40b983b790509581dc3db0fff",
                 "shasum": ""
             },
             "require": {
@@ -16402,7 +16400,7 @@
             "require-dev": {
                 "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0"
             },
-            "time": "2022-05-27T12:56:18+00:00",
+            "time": "2022-12-22T10:10:04+00:00",
             "type": "library",
             "installation-source": "dist",
             "autoload": {
@@ -16438,7 +16436,7 @@
                 "serialize"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-exporter/tree/v5.4.10"
+                "source": "https://github.com/symfony/var-exporter/tree/v5.4.17"
             },
             "funding": [
                 {
@@ -16591,17 +16589,17 @@
         },
         {
             "name": "twig/twig",
-            "version": "v2.15.3",
-            "version_normalized": "2.15.3.0",
+            "version": "v2.15.4",
+            "version_normalized": "2.15.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/twigphp/Twig.git",
-                "reference": "ab402673db8746cb3a4c46f3869d6253699f614a"
+                "reference": "3e059001d6d597dd50ea7c74dd2464b4adea48d3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/twigphp/Twig/zipball/ab402673db8746cb3a4c46f3869d6253699f614a",
-                "reference": "ab402673db8746cb3a4c46f3869d6253699f614a",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/3e059001d6d597dd50ea7c74dd2464b4adea48d3",
+                "reference": "3e059001d6d597dd50ea7c74dd2464b4adea48d3",
                 "shasum": ""
             },
             "require": {
@@ -16614,7 +16612,7 @@
                 "psr/container": "^1.0",
                 "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
             },
-            "time": "2022-09-28T08:40:08+00:00",
+            "time": "2022-12-27T12:26:20+00:00",
             "type": "library",
             "extra": {
                 "branch-alias": {
@@ -16658,7 +16656,7 @@
             ],
             "support": {
                 "issues": "https://github.com/twigphp/Twig/issues",
-                "source": "https://github.com/twigphp/Twig/tree/v2.15.3"
+                "source": "https://github.com/twigphp/Twig/tree/v2.15.4"
             },
             "funding": [
                 {
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index bc301827c230e0d273dc14b01624b95ab88fa1b3..658e311cf4cfb9f3af3740d3ed789e6f9377c730 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'osu-asc-webservices/d8-upstream',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '722d6bd27e6db99d8b8598dc703ac17a5254bd61',
+        'reference' => '71716e30031a5a6991c8171b4d9249782f371ae2',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -1145,9 +1145,9 @@
             'dev_requirement' => false,
         ),
         'drupal/scheduler' => array(
-            'pretty_version' => '2.0.0-rc8',
-            'version' => '2.0.0.0-RC8',
-            'reference' => '2.0.0-rc8',
+            'pretty_version' => '1.4.0',
+            'version' => '1.4.0.0',
+            'reference' => '8.x-1.4',
             'type' => 'drupal-module',
             'install_path' => __DIR__ . '/../../web/modules/scheduler',
             'aliases' => array(),
@@ -1379,9 +1379,9 @@
             'dev_requirement' => false,
         ),
         'egulias/email-validator' => array(
-            'pretty_version' => '3.2.1',
-            'version' => '3.2.1.0',
-            'reference' => 'f88dcf4b14af14a98ad96b14b2b317969eab6715',
+            'pretty_version' => '3.2.5',
+            'version' => '3.2.5.0',
+            'reference' => 'b531a2311709443320c786feb4519cfaf94af796',
             'type' => 'library',
             'install_path' => __DIR__ . '/../egulias/email-validator',
             'aliases' => array(),
@@ -1594,7 +1594,7 @@
         'osu-asc-webservices/d8-upstream' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '722d6bd27e6db99d8b8598dc703ac17a5254bd61',
+            'reference' => '71716e30031a5a6991c8171b4d9249782f371ae2',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
@@ -2478,18 +2478,18 @@
             'dev_requirement' => false,
         ),
         'symfony/var-dumper' => array(
-            'pretty_version' => 'v5.4.14',
-            'version' => '5.4.14.0',
-            'reference' => '6894d06145fefebd9a4c7272baa026a1c394a430',
+            'pretty_version' => 'v5.4.17',
+            'version' => '5.4.17.0',
+            'reference' => 'ad74890513d07060255df2575703daf971de92c7',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/var-dumper',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'symfony/var-exporter' => array(
-            'pretty_version' => 'v5.4.10',
-            'version' => '5.4.10.0',
-            'reference' => '8fc03ee75eeece3d9be1ef47d26d79bea1afb340',
+            'pretty_version' => 'v5.4.17',
+            'version' => '5.4.17.0',
+            'reference' => '2adac0a9b55f9fb40b983b790509581dc3db0fff',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/var-exporter',
             'aliases' => array(),
@@ -2514,9 +2514,9 @@
             'dev_requirement' => false,
         ),
         'twig/twig' => array(
-            'pretty_version' => 'v2.15.3',
-            'version' => '2.15.3.0',
-            'reference' => 'ab402673db8746cb3a4c46f3869d6253699f614a',
+            'pretty_version' => 'v2.15.4',
+            'version' => '2.15.4.0',
+            'reference' => '3e059001d6d597dd50ea7c74dd2464b4adea48d3',
             'type' => 'library',
             'install_path' => __DIR__ . '/../twig/twig',
             'aliases' => array(),
diff --git a/vendor/egulias/email-validator/CHANGELOG.md b/vendor/egulias/email-validator/CHANGELOG.md
index 83fd62e02410f57ef6918ae7a5f641a0197d7c4e..539917f5356d7e04a5916f7f1edd4ef108146037 100644
--- a/vendor/egulias/email-validator/CHANGELOG.md
+++ b/vendor/egulias/email-validator/CHANGELOG.md
@@ -5,7 +5,7 @@
 * Access to local part and domain part from EmailParser
 * Validations outside of the scope of the RFC will be considered "extra" validations, thus opening the door for adding new; will live in their own folder "extra" (as requested in #248, #195, #183). 
 
-## Breacking changes
+## Breaking changes
 
 * PHP version upgraded to match Symfony's (as of 12/2020).
 * DNSCheckValidation now fails for missing MX records. While the RFC argues that the existence of only A records to be valid, starting in v3 they will be considered invalid.
diff --git a/vendor/egulias/email-validator/LICENSE b/vendor/egulias/email-validator/LICENSE
index 1f0f2678a0e2069e2d31685d3480e744e62ec2a4..307440d45d3f530c1e5b14978b08421bf7a8579e 100644
--- a/vendor/egulias/email-validator/LICENSE
+++ b/vendor/egulias/email-validator/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2021 Eduardo Gulias Davis
+Copyright (c) 2013-2022 Eduardo Gulias Davis
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/egulias/email-validator/composer.json b/vendor/egulias/email-validator/composer.json
index d598d1b62239f54078061dac97617f7320132f0a..6a273e2f3e7c1b1f092cbf1caec5eed5fdea1763 100644
--- a/vendor/egulias/email-validator/composer.json
+++ b/vendor/egulias/email-validator/composer.json
@@ -14,11 +14,10 @@
   },
   "require": {
     "php": ">=7.2",
-    "doctrine/lexer": "^1.2",
+    "doctrine/lexer": "^1.2|^2",
     "symfony/polyfill-intl-idn": "^1.15"
   },
   "require-dev": {
-    "php-coveralls/php-coveralls": "^2.2",
     "phpunit/phpunit": "^8.5.8|^9.3.3",
     "vimeo/psalm": "^4"
   },
diff --git a/vendor/egulias/email-validator/composer.lock b/vendor/egulias/email-validator/composer.lock
deleted file mode 100644
index 4b918d1310183878cf0626dddca1e450632060eb..0000000000000000000000000000000000000000
--- a/vendor/egulias/email-validator/composer.lock
+++ /dev/null
@@ -1,5028 +0,0 @@
-{
-    "_readme": [
-        "This file locks the dependencies of your project to a known state",
-        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
-        "This file is @generated automatically"
-    ],
-    "content-hash": "a77d36b64bc1213fecf4d4f92d759c3b",
-    "packages": [
-        {
-            "name": "doctrine/lexer",
-            "version": "1.2.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/lexer.git",
-                "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229",
-                "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1 || ^8.0"
-            },
-            "require-dev": {
-                "doctrine/coding-standard": "^9.0",
-                "phpstan/phpstan": "^1.3",
-                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
-                "vimeo/psalm": "^4.11"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
-                {
-                    "name": "Roman Borschel",
-                    "email": "roman@code-factory.org"
-                },
-                {
-                    "name": "Johannes Schmitt",
-                    "email": "schmittjoh@gmail.com"
-                }
-            ],
-            "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
-            "homepage": "https://www.doctrine-project.org/projects/lexer.html",
-            "keywords": [
-                "annotations",
-                "docblock",
-                "lexer",
-                "parser",
-                "php"
-            ],
-            "support": {
-                "issues": "https://github.com/doctrine/lexer/issues",
-                "source": "https://github.com/doctrine/lexer/tree/1.2.3"
-            },
-            "funding": [
-                {
-                    "url": "https://www.doctrine-project.org/sponsorship.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://www.patreon.com/phpdoctrine",
-                    "type": "patreon"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-02-28T11:07:21+00:00"
-        },
-        {
-            "name": "symfony/polyfill-intl-idn",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-idn.git",
-                "reference": "749045c69efb97c70d25d7463abba812e91f3a44"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44",
-                "reference": "749045c69efb97c70d25d7463abba812e91f3a44",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1",
-                "symfony/polyfill-intl-normalizer": "^1.10",
-                "symfony/polyfill-php72": "^1.10"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Laurent Bassin",
-                    "email": "laurent@bassin.info"
-                },
-                {
-                    "name": "Trevor Rowbotham",
-                    "email": "trevor.rowbotham@pm.me"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "idn",
-                "intl",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-09-14T14:02:44+00:00"
-        },
-        {
-            "name": "symfony/polyfill-intl-normalizer",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
-                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
-                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
-                },
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for intl's Normalizer class and related functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "intl",
-                "normalizer",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-02-19T12:13:01+00:00"
-        },
-        {
-            "name": "symfony/polyfill-php72",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php72.git",
-                "reference": "9a142215a36a3888e30d0a9eeea9766764e96976"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976",
-                "reference": "9a142215a36a3888e30d0a9eeea9766764e96976",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php72\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-05-27T09:17:38+00:00"
-        }
-    ],
-    "packages-dev": [
-        {
-            "name": "amphp/amp",
-            "version": "v2.6.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/amphp/amp.git",
-                "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb",
-                "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "amphp/php-cs-fixer-config": "dev-master",
-                "amphp/phpunit-util": "^1",
-                "ext-json": "*",
-                "jetbrains/phpstorm-stubs": "^2019.3",
-                "phpunit/phpunit": "^7 | ^8 | ^9",
-                "psalm/phar": "^3.11@dev",
-                "react/promise": "^2"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.x-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "lib/functions.php",
-                    "lib/Internal/functions.php"
-                ],
-                "psr-4": {
-                    "Amp\\": "lib"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Daniel Lowrey",
-                    "email": "rdlowrey@php.net"
-                },
-                {
-                    "name": "Aaron Piotrowski",
-                    "email": "aaron@trowski.com"
-                },
-                {
-                    "name": "Bob Weinand",
-                    "email": "bobwei9@hotmail.com"
-                },
-                {
-                    "name": "Niklas Keller",
-                    "email": "me@kelunik.com"
-                }
-            ],
-            "description": "A non-blocking concurrency framework for PHP applications.",
-            "homepage": "https://amphp.org/amp",
-            "keywords": [
-                "async",
-                "asynchronous",
-                "awaitable",
-                "concurrency",
-                "event",
-                "event-loop",
-                "future",
-                "non-blocking",
-                "promise"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.org/amphp",
-                "issues": "https://github.com/amphp/amp/issues",
-                "source": "https://github.com/amphp/amp/tree/v2.6.2"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/amphp",
-                    "type": "github"
-                }
-            ],
-            "time": "2022-02-20T17:52:18+00:00"
-        },
-        {
-            "name": "amphp/byte-stream",
-            "version": "v1.8.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/amphp/byte-stream.git",
-                "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd",
-                "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd",
-                "shasum": ""
-            },
-            "require": {
-                "amphp/amp": "^2",
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "amphp/php-cs-fixer-config": "dev-master",
-                "amphp/phpunit-util": "^1.4",
-                "friendsofphp/php-cs-fixer": "^2.3",
-                "jetbrains/phpstorm-stubs": "^2019.3",
-                "phpunit/phpunit": "^6 || ^7 || ^8",
-                "psalm/phar": "^3.11.4"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "lib/functions.php"
-                ],
-                "psr-4": {
-                    "Amp\\ByteStream\\": "lib"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Aaron Piotrowski",
-                    "email": "aaron@trowski.com"
-                },
-                {
-                    "name": "Niklas Keller",
-                    "email": "me@kelunik.com"
-                }
-            ],
-            "description": "A stream abstraction to make working with non-blocking I/O simple.",
-            "homepage": "http://amphp.org/byte-stream",
-            "keywords": [
-                "amp",
-                "amphp",
-                "async",
-                "io",
-                "non-blocking",
-                "stream"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.org/amphp",
-                "issues": "https://github.com/amphp/byte-stream/issues",
-                "source": "https://github.com/amphp/byte-stream/tree/v1.8.1"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/amphp",
-                    "type": "github"
-                }
-            ],
-            "time": "2021-03-30T17:13:30+00:00"
-        },
-        {
-            "name": "composer/package-versions-deprecated",
-            "version": "1.11.99.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/composer/package-versions-deprecated.git",
-                "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d",
-                "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d",
-                "shasum": ""
-            },
-            "require": {
-                "composer-plugin-api": "^1.1.0 || ^2.0",
-                "php": "^7 || ^8"
-            },
-            "replace": {
-                "ocramius/package-versions": "1.11.99"
-            },
-            "require-dev": {
-                "composer/composer": "^1.9.3 || ^2.0@dev",
-                "ext-zip": "^1.13",
-                "phpunit/phpunit": "^6.5 || ^7"
-            },
-            "type": "composer-plugin",
-            "extra": {
-                "class": "PackageVersions\\Installer",
-                "branch-alias": {
-                    "dev-master": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "PackageVersions\\": "src/PackageVersions"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Marco Pivetta",
-                    "email": "ocramius@gmail.com"
-                },
-                {
-                    "name": "Jordi Boggiano",
-                    "email": "j.boggiano@seld.be"
-                }
-            ],
-            "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
-            "support": {
-                "issues": "https://github.com/composer/package-versions-deprecated/issues",
-                "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5"
-            },
-            "funding": [
-                {
-                    "url": "https://packagist.com",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/composer",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-01-17T14:14:24+00:00"
-        },
-        {
-            "name": "composer/pcre",
-            "version": "3.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/composer/pcre.git",
-                "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd",
-                "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.4 || ^8.0"
-            },
-            "require-dev": {
-                "phpstan/phpstan": "^1.3",
-                "phpstan/phpstan-strict-rules": "^1.1",
-                "symfony/phpunit-bridge": "^5"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "3.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Composer\\Pcre\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Jordi Boggiano",
-                    "email": "j.boggiano@seld.be",
-                    "homepage": "http://seld.be"
-                }
-            ],
-            "description": "PCRE wrapping library that offers type-safe preg_* replacements.",
-            "keywords": [
-                "PCRE",
-                "preg",
-                "regex",
-                "regular expression"
-            ],
-            "support": {
-                "issues": "https://github.com/composer/pcre/issues",
-                "source": "https://github.com/composer/pcre/tree/3.0.0"
-            },
-            "funding": [
-                {
-                    "url": "https://packagist.com",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/composer",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-02-25T20:21:48+00:00"
-        },
-        {
-            "name": "composer/semver",
-            "version": "3.3.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/composer/semver.git",
-                "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9",
-                "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.3.2 || ^7.0 || ^8.0"
-            },
-            "require-dev": {
-                "phpstan/phpstan": "^1.4",
-                "symfony/phpunit-bridge": "^4.2 || ^5"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "3.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Composer\\Semver\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nils Adermann",
-                    "email": "naderman@naderman.de",
-                    "homepage": "http://www.naderman.de"
-                },
-                {
-                    "name": "Jordi Boggiano",
-                    "email": "j.boggiano@seld.be",
-                    "homepage": "http://seld.be"
-                },
-                {
-                    "name": "Rob Bast",
-                    "email": "rob.bast@gmail.com",
-                    "homepage": "http://robbast.nl"
-                }
-            ],
-            "description": "Semver library that offers utilities, version constraint parsing and validation.",
-            "keywords": [
-                "semantic",
-                "semver",
-                "validation",
-                "versioning"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.org/composer",
-                "issues": "https://github.com/composer/semver/issues",
-                "source": "https://github.com/composer/semver/tree/3.3.2"
-            },
-            "funding": [
-                {
-                    "url": "https://packagist.com",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/composer",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-04-01T19:23:25+00:00"
-        },
-        {
-            "name": "composer/xdebug-handler",
-            "version": "3.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "ced299686f41dce890debac69273b47ffe98a40c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c",
-                "reference": "ced299686f41dce890debac69273b47ffe98a40c",
-                "shasum": ""
-            },
-            "require": {
-                "composer/pcre": "^1 || ^2 || ^3",
-                "php": "^7.2.5 || ^8.0",
-                "psr/log": "^1 || ^2 || ^3"
-            },
-            "require-dev": {
-                "phpstan/phpstan": "^1.0",
-                "phpstan/phpstan-strict-rules": "^1.1",
-                "symfony/phpunit-bridge": "^6.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Composer\\XdebugHandler\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "John Stevenson",
-                    "email": "john-stevenson@blueyonder.co.uk"
-                }
-            ],
-            "description": "Restarts a process without Xdebug.",
-            "keywords": [
-                "Xdebug",
-                "performance"
-            ],
-            "support": {
-                "irc": "irc://irc.freenode.org/composer",
-                "issues": "https://github.com/composer/xdebug-handler/issues",
-                "source": "https://github.com/composer/xdebug-handler/tree/3.0.3"
-            },
-            "funding": [
-                {
-                    "url": "https://packagist.com",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/composer",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-02-25T21:32:43+00:00"
-        },
-        {
-            "name": "dnoegel/php-xdg-base-dir",
-            "version": "v0.1.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/dnoegel/php-xdg-base-dir.git",
-                "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
-                "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.2"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "XdgBaseDir\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "description": "implementation of xdg base directory specification for php",
-            "support": {
-                "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues",
-                "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1"
-            },
-            "time": "2019-12-04T15:06:13+00:00"
-        },
-        {
-            "name": "doctrine/instantiator",
-            "version": "1.4.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/instantiator.git",
-                "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
-                "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1 || ^8.0"
-            },
-            "require-dev": {
-                "doctrine/coding-standard": "^9",
-                "ext-pdo": "*",
-                "ext-phar": "*",
-                "phpbench/phpbench": "^0.16 || ^1",
-                "phpstan/phpstan": "^1.4",
-                "phpstan/phpstan-phpunit": "^1",
-                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
-                "vimeo/psalm": "^4.22"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Marco Pivetta",
-                    "email": "ocramius@gmail.com",
-                    "homepage": "https://ocramius.github.io/"
-                }
-            ],
-            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
-            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
-            "keywords": [
-                "constructor",
-                "instantiate"
-            ],
-            "support": {
-                "issues": "https://github.com/doctrine/instantiator/issues",
-                "source": "https://github.com/doctrine/instantiator/tree/1.4.1"
-            },
-            "funding": [
-                {
-                    "url": "https://www.doctrine-project.org/sponsorship.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://www.patreon.com/phpdoctrine",
-                    "type": "patreon"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-03-03T08:28:38+00:00"
-        },
-        {
-            "name": "felixfbecker/advanced-json-rpc",
-            "version": "v3.2.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git",
-                "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447",
-                "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447",
-                "shasum": ""
-            },
-            "require": {
-                "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
-                "php": "^7.1 || ^8.0",
-                "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^7.0 || ^8.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "AdvancedJsonRpc\\": "lib/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "ISC"
-            ],
-            "authors": [
-                {
-                    "name": "Felix Becker",
-                    "email": "felix.b@outlook.com"
-                }
-            ],
-            "description": "A more advanced JSONRPC implementation",
-            "support": {
-                "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues",
-                "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1"
-            },
-            "time": "2021-06-11T22:34:44+00:00"
-        },
-        {
-            "name": "felixfbecker/language-server-protocol",
-            "version": "v1.5.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/felixfbecker/php-language-server-protocol.git",
-                "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842",
-                "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "phpstan/phpstan": "*",
-                "squizlabs/php_codesniffer": "^3.1",
-                "vimeo/psalm": "^4.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "LanguageServerProtocol\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "ISC"
-            ],
-            "authors": [
-                {
-                    "name": "Felix Becker",
-                    "email": "felix.b@outlook.com"
-                }
-            ],
-            "description": "PHP classes for the Language Server Protocol",
-            "keywords": [
-                "language",
-                "microsoft",
-                "php",
-                "server"
-            ],
-            "support": {
-                "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues",
-                "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2"
-            },
-            "time": "2022-03-02T22:36:06+00:00"
-        },
-        {
-            "name": "guzzlehttp/guzzle",
-            "version": "7.4.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/74a8602c6faec9ef74b7a9391ac82c5e65b1cdab",
-                "reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "guzzlehttp/promises": "^1.5",
-                "guzzlehttp/psr7": "^1.8.3 || ^2.1",
-                "php": "^7.2.5 || ^8.0",
-                "psr/http-client": "^1.0",
-                "symfony/deprecation-contracts": "^2.2 || ^3.0"
-            },
-            "provide": {
-                "psr/http-client-implementation": "1.0"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.4.1",
-                "ext-curl": "*",
-                "php-http/client-integration-tests": "^3.0",
-                "phpunit/phpunit": "^8.5.5 || ^9.3.5",
-                "psr/log": "^1.1 || ^2.0 || ^3.0"
-            },
-            "suggest": {
-                "ext-curl": "Required for CURL handler support",
-                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
-                "psr/log": "Required for using the Log middleware"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "7.4-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "src/functions_include.php"
-                ],
-                "psr-4": {
-                    "GuzzleHttp\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                },
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Jeremy Lindblom",
-                    "email": "jeremeamia@gmail.com",
-                    "homepage": "https://github.com/jeremeamia"
-                },
-                {
-                    "name": "George Mponos",
-                    "email": "gmponos@gmail.com",
-                    "homepage": "https://github.com/gmponos"
-                },
-                {
-                    "name": "Tobias Nyholm",
-                    "email": "tobias.nyholm@gmail.com",
-                    "homepage": "https://github.com/Nyholm"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com",
-                    "homepage": "https://github.com/sagikazarmark"
-                },
-                {
-                    "name": "Tobias Schultze",
-                    "email": "webmaster@tubo-world.de",
-                    "homepage": "https://github.com/Tobion"
-                }
-            ],
-            "description": "Guzzle is a PHP HTTP client library",
-            "keywords": [
-                "client",
-                "curl",
-                "framework",
-                "http",
-                "http client",
-                "psr-18",
-                "psr-7",
-                "rest",
-                "web service"
-            ],
-            "support": {
-                "issues": "https://github.com/guzzle/guzzle/issues",
-                "source": "https://github.com/guzzle/guzzle/tree/7.4.3"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://github.com/Nyholm",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-05-25T13:24:33+00:00"
-        },
-        {
-            "name": "guzzlehttp/promises",
-            "version": "1.5.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/promises.git",
-                "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
-                "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.5"
-            },
-            "require-dev": {
-                "symfony/phpunit-bridge": "^4.4 || ^5.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.5-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "src/functions_include.php"
-                ],
-                "psr-4": {
-                    "GuzzleHttp\\Promise\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                },
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Tobias Nyholm",
-                    "email": "tobias.nyholm@gmail.com",
-                    "homepage": "https://github.com/Nyholm"
-                },
-                {
-                    "name": "Tobias Schultze",
-                    "email": "webmaster@tubo-world.de",
-                    "homepage": "https://github.com/Tobion"
-                }
-            ],
-            "description": "Guzzle promises library",
-            "keywords": [
-                "promise"
-            ],
-            "support": {
-                "issues": "https://github.com/guzzle/promises/issues",
-                "source": "https://github.com/guzzle/promises/tree/1.5.1"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://github.com/Nyholm",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-10-22T20:56:57+00:00"
-        },
-        {
-            "name": "guzzlehttp/psr7",
-            "version": "2.2.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/psr7.git",
-                "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2",
-                "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2.5 || ^8.0",
-                "psr/http-factory": "^1.0",
-                "psr/http-message": "^1.0",
-                "ralouphie/getallheaders": "^3.0"
-            },
-            "provide": {
-                "psr/http-factory-implementation": "1.0",
-                "psr/http-message-implementation": "1.0"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.4.1",
-                "http-interop/http-factory-tests": "^0.9",
-                "phpunit/phpunit": "^8.5.8 || ^9.3.10"
-            },
-            "suggest": {
-                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.2-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\Psr7\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                },
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "George Mponos",
-                    "email": "gmponos@gmail.com",
-                    "homepage": "https://github.com/gmponos"
-                },
-                {
-                    "name": "Tobias Nyholm",
-                    "email": "tobias.nyholm@gmail.com",
-                    "homepage": "https://github.com/Nyholm"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com",
-                    "homepage": "https://github.com/sagikazarmark"
-                },
-                {
-                    "name": "Tobias Schultze",
-                    "email": "webmaster@tubo-world.de",
-                    "homepage": "https://github.com/Tobion"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com",
-                    "homepage": "https://sagikazarmark.hu"
-                }
-            ],
-            "description": "PSR-7 message implementation that also provides common utility methods",
-            "keywords": [
-                "http",
-                "message",
-                "psr-7",
-                "request",
-                "response",
-                "stream",
-                "uri",
-                "url"
-            ],
-            "support": {
-                "issues": "https://github.com/guzzle/psr7/issues",
-                "source": "https://github.com/guzzle/psr7/tree/2.2.1"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://github.com/Nyholm",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-03-20T21:55:58+00:00"
-        },
-        {
-            "name": "myclabs/deep-copy",
-            "version": "1.11.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
-                "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1 || ^8.0"
-            },
-            "conflict": {
-                "doctrine/collections": "<1.6.8",
-                "doctrine/common": "<2.13.3 || >=3,<3.2.2"
-            },
-            "require-dev": {
-                "doctrine/collections": "^1.6.8",
-                "doctrine/common": "^2.13.3 || ^3.2.2",
-                "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "src/DeepCopy/deep_copy.php"
-                ],
-                "psr-4": {
-                    "DeepCopy\\": "src/DeepCopy/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "description": "Create deep copies (clones) of your objects",
-            "keywords": [
-                "clone",
-                "copy",
-                "duplicate",
-                "object",
-                "object graph"
-            ],
-            "support": {
-                "issues": "https://github.com/myclabs/DeepCopy/issues",
-                "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
-            },
-            "funding": [
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-03-03T13:19:32+00:00"
-        },
-        {
-            "name": "netresearch/jsonmapper",
-            "version": "v4.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/cweiske/jsonmapper.git",
-                "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
-                "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-pcre": "*",
-                "ext-reflection": "*",
-                "ext-spl": "*",
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0",
-                "squizlabs/php_codesniffer": "~3.5"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-0": {
-                    "JsonMapper": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "OSL-3.0"
-            ],
-            "authors": [
-                {
-                    "name": "Christian Weiske",
-                    "email": "cweiske@cweiske.de",
-                    "homepage": "http://github.com/cweiske/jsonmapper/",
-                    "role": "Developer"
-                }
-            ],
-            "description": "Map nested JSON structures onto PHP classes",
-            "support": {
-                "email": "cweiske@cweiske.de",
-                "issues": "https://github.com/cweiske/jsonmapper/issues",
-                "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0"
-            },
-            "time": "2020-12-01T19:48:11+00:00"
-        },
-        {
-            "name": "nikic/php-parser",
-            "version": "v4.13.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/nikic/PHP-Parser.git",
-                "reference": "210577fe3cf7badcc5814d99455df46564f3c077"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
-                "reference": "210577fe3cf7badcc5814d99455df46564f3c077",
-                "shasum": ""
-            },
-            "require": {
-                "ext-tokenizer": "*",
-                "php": ">=7.0"
-            },
-            "require-dev": {
-                "ircmaxell/php-yacc": "^0.0.7",
-                "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
-            },
-            "bin": [
-                "bin/php-parse"
-            ],
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.9-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "PhpParser\\": "lib/PhpParser"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Nikita Popov"
-                }
-            ],
-            "description": "A PHP parser written in PHP",
-            "keywords": [
-                "parser",
-                "php"
-            ],
-            "support": {
-                "issues": "https://github.com/nikic/PHP-Parser/issues",
-                "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
-            },
-            "time": "2021-11-30T19:35:32+00:00"
-        },
-        {
-            "name": "openlss/lib-array2xml",
-            "version": "1.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/nullivex/lib-array2xml.git",
-                "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90",
-                "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.2"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-0": {
-                    "LSS": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "Bryan Tong",
-                    "email": "bryan@nullivex.com",
-                    "homepage": "https://www.nullivex.com"
-                },
-                {
-                    "name": "Tony Butler",
-                    "email": "spudz76@gmail.com",
-                    "homepage": "https://www.nullivex.com"
-                }
-            ],
-            "description": "Array2XML conversion library credit to lalit.org",
-            "homepage": "https://www.nullivex.com",
-            "keywords": [
-                "array",
-                "array conversion",
-                "xml",
-                "xml conversion"
-            ],
-            "support": {
-                "issues": "https://github.com/nullivex/lib-array2xml/issues",
-                "source": "https://github.com/nullivex/lib-array2xml/tree/master"
-            },
-            "time": "2019-03-29T20:06:56+00:00"
-        },
-        {
-            "name": "phar-io/manifest",
-            "version": "2.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phar-io/manifest.git",
-                "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
-                "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
-                "shasum": ""
-            },
-            "require": {
-                "ext-dom": "*",
-                "ext-phar": "*",
-                "ext-xmlwriter": "*",
-                "phar-io/version": "^3.0.1",
-                "php": "^7.2 || ^8.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0.x-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
-                },
-                {
-                    "name": "Sebastian Heuer",
-                    "email": "sebastian@phpeople.de",
-                    "role": "Developer"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "Developer"
-                }
-            ],
-            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
-            "support": {
-                "issues": "https://github.com/phar-io/manifest/issues",
-                "source": "https://github.com/phar-io/manifest/tree/2.0.3"
-            },
-            "time": "2021-07-20T11:28:43+00:00"
-        },
-        {
-            "name": "phar-io/version",
-            "version": "3.2.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phar-io/version.git",
-                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
-                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2 || ^8.0"
-            },
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
-                },
-                {
-                    "name": "Sebastian Heuer",
-                    "email": "sebastian@phpeople.de",
-                    "role": "Developer"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "Developer"
-                }
-            ],
-            "description": "Library for handling version information and constraints",
-            "support": {
-                "issues": "https://github.com/phar-io/version/issues",
-                "source": "https://github.com/phar-io/version/tree/3.2.1"
-            },
-            "time": "2022-02-21T01:04:05+00:00"
-        },
-        {
-            "name": "php-coveralls/php-coveralls",
-            "version": "v2.5.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-coveralls/php-coveralls.git",
-                "reference": "007e13afdcdba2cd0efcc5f72c3b7efb356a8bd4"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/007e13afdcdba2cd0efcc5f72c3b7efb356a8bd4",
-                "reference": "007e13afdcdba2cd0efcc5f72c3b7efb356a8bd4",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-simplexml": "*",
-                "guzzlehttp/guzzle": "^6.0 || ^7.0",
-                "php": "^5.5 || ^7.0 || ^8.0",
-                "psr/log": "^1.0 || ^2.0",
-                "symfony/config": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
-                "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
-                "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
-                "symfony/yaml": "^2.0.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
-                "sanmai/phpunit-legacy-adapter": "^6.1 || ^8.0"
-            },
-            "suggest": {
-                "symfony/http-kernel": "Allows Symfony integration"
-            },
-            "bin": [
-                "bin/php-coveralls"
-            ],
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "PhpCoveralls\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Kitamura Satoshi",
-                    "email": "with.no.parachute@gmail.com",
-                    "homepage": "https://www.facebook.com/satooshi.jp",
-                    "role": "Original creator"
-                },
-                {
-                    "name": "Takashi Matsuo",
-                    "email": "tmatsuo@google.com"
-                },
-                {
-                    "name": "Google Inc"
-                },
-                {
-                    "name": "Dariusz Ruminski",
-                    "email": "dariusz.ruminski@gmail.com",
-                    "homepage": "https://github.com/keradus"
-                },
-                {
-                    "name": "Contributors",
-                    "homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors"
-                }
-            ],
-            "description": "PHP client library for Coveralls API",
-            "homepage": "https://github.com/php-coveralls/php-coveralls",
-            "keywords": [
-                "ci",
-                "coverage",
-                "github",
-                "test"
-            ],
-            "support": {
-                "issues": "https://github.com/php-coveralls/php-coveralls/issues",
-                "source": "https://github.com/php-coveralls/php-coveralls/tree/v2.5.2"
-            },
-            "time": "2021-12-06T17:05:08+00:00"
-        },
-        {
-            "name": "phpdocumentor/reflection-common",
-            "version": "2.2.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
-                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
-                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2 || ^8.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-2.x": "2.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Jaap van Otterdijk",
-                    "email": "opensource@ijaap.nl"
-                }
-            ],
-            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
-            "homepage": "http://www.phpdoc.org",
-            "keywords": [
-                "FQSEN",
-                "phpDocumentor",
-                "phpdoc",
-                "reflection",
-                "static analysis"
-            ],
-            "support": {
-                "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
-            },
-            "time": "2020-06-27T09:03:43+00:00"
-        },
-        {
-            "name": "phpdocumentor/reflection-docblock",
-            "version": "5.3.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
-                "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
-                "shasum": ""
-            },
-            "require": {
-                "ext-filter": "*",
-                "php": "^7.2 || ^8.0",
-                "phpdocumentor/reflection-common": "^2.2",
-                "phpdocumentor/type-resolver": "^1.3",
-                "webmozart/assert": "^1.9.1"
-            },
-            "require-dev": {
-                "mockery/mockery": "~1.3.2",
-                "psalm/phar": "^4.8"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "5.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Mike van Riel",
-                    "email": "me@mikevanriel.com"
-                },
-                {
-                    "name": "Jaap van Otterdijk",
-                    "email": "account@ijaap.nl"
-                }
-            ],
-            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "support": {
-                "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
-            },
-            "time": "2021-10-19T17:43:47+00:00"
-        },
-        {
-            "name": "phpdocumentor/type-resolver",
-            "version": "1.6.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "77a32518733312af16a44300404e945338981de3"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
-                "reference": "77a32518733312af16a44300404e945338981de3",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2 || ^8.0",
-                "phpdocumentor/reflection-common": "^2.0"
-            },
-            "require-dev": {
-                "ext-tokenizer": "*",
-                "psalm/phar": "^4.8"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-1.x": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Mike van Riel",
-                    "email": "me@mikevanriel.com"
-                }
-            ],
-            "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
-            "support": {
-                "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
-                "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
-            },
-            "time": "2022-03-15T21:29:03+00:00"
-        },
-        {
-            "name": "phpspec/prophecy",
-            "version": "v1.15.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
-                "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/instantiator": "^1.2",
-                "php": "^7.2 || ~8.0, <8.2",
-                "phpdocumentor/reflection-docblock": "^5.2",
-                "sebastian/comparator": "^3.0 || ^4.0",
-                "sebastian/recursion-context": "^3.0 || ^4.0"
-            },
-            "require-dev": {
-                "phpspec/phpspec": "^6.0 || ^7.0",
-                "phpunit/phpunit": "^8.0 || ^9.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Prophecy\\": "src/Prophecy"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
-                },
-                {
-                    "name": "Marcello Duarte",
-                    "email": "marcello.duarte@gmail.com"
-                }
-            ],
-            "description": "Highly opinionated mocking framework for PHP 5.3+",
-            "homepage": "https://github.com/phpspec/prophecy",
-            "keywords": [
-                "Double",
-                "Dummy",
-                "fake",
-                "mock",
-                "spy",
-                "stub"
-            ],
-            "support": {
-                "issues": "https://github.com/phpspec/prophecy/issues",
-                "source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
-            },
-            "time": "2021-12-08T12:19:24+00:00"
-        },
-        {
-            "name": "phpunit/php-code-coverage",
-            "version": "9.2.15",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
-                "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
-                "shasum": ""
-            },
-            "require": {
-                "ext-dom": "*",
-                "ext-libxml": "*",
-                "ext-xmlwriter": "*",
-                "nikic/php-parser": "^4.13.0",
-                "php": ">=7.3",
-                "phpunit/php-file-iterator": "^3.0.3",
-                "phpunit/php-text-template": "^2.0.2",
-                "sebastian/code-unit-reverse-lookup": "^2.0.2",
-                "sebastian/complexity": "^2.0",
-                "sebastian/environment": "^5.1.2",
-                "sebastian/lines-of-code": "^1.0.3",
-                "sebastian/version": "^3.0.1",
-                "theseer/tokenizer": "^1.2.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "suggest": {
-                "ext-pcov": "*",
-                "ext-xdebug": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "9.2-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
-            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
-            "keywords": [
-                "coverage",
-                "testing",
-                "xunit"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2022-03-07T09:28:20+00:00"
-        },
-        {
-            "name": "phpunit/php-file-iterator",
-            "version": "3.0.6",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
-                "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
-            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
-            "keywords": [
-                "filesystem",
-                "iterator"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
-                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2021-12-02T12:48:52+00:00"
-        },
-        {
-            "name": "phpunit/php-invoker",
-            "version": "3.1.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-invoker.git",
-                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
-                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "ext-pcntl": "*",
-                "phpunit/phpunit": "^9.3"
-            },
-            "suggest": {
-                "ext-pcntl": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.1-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Invoke callables with a timeout",
-            "homepage": "https://github.com/sebastianbergmann/php-invoker/",
-            "keywords": [
-                "process"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
-                "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-09-28T05:58:55+00:00"
-        },
-        {
-            "name": "phpunit/php-text-template",
-            "version": "2.0.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-text-template.git",
-                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
-                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Simple template engine.",
-            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
-            "keywords": [
-                "template"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
-                "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T05:33:50+00:00"
-        },
-        {
-            "name": "phpunit/php-timer",
-            "version": "5.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
-                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "5.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Utility class for timing",
-            "homepage": "https://github.com/sebastianbergmann/php-timer/",
-            "keywords": [
-                "timer"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
-                "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T13:16:10+00:00"
-        },
-        {
-            "name": "phpunit/phpunit",
-            "version": "9.5.20",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba",
-                "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/instantiator": "^1.3.1",
-                "ext-dom": "*",
-                "ext-json": "*",
-                "ext-libxml": "*",
-                "ext-mbstring": "*",
-                "ext-xml": "*",
-                "ext-xmlwriter": "*",
-                "myclabs/deep-copy": "^1.10.1",
-                "phar-io/manifest": "^2.0.3",
-                "phar-io/version": "^3.0.2",
-                "php": ">=7.3",
-                "phpspec/prophecy": "^1.12.1",
-                "phpunit/php-code-coverage": "^9.2.13",
-                "phpunit/php-file-iterator": "^3.0.5",
-                "phpunit/php-invoker": "^3.1.1",
-                "phpunit/php-text-template": "^2.0.3",
-                "phpunit/php-timer": "^5.0.2",
-                "sebastian/cli-parser": "^1.0.1",
-                "sebastian/code-unit": "^1.0.6",
-                "sebastian/comparator": "^4.0.5",
-                "sebastian/diff": "^4.0.3",
-                "sebastian/environment": "^5.1.3",
-                "sebastian/exporter": "^4.0.3",
-                "sebastian/global-state": "^5.0.1",
-                "sebastian/object-enumerator": "^4.0.3",
-                "sebastian/resource-operations": "^3.0.3",
-                "sebastian/type": "^3.0",
-                "sebastian/version": "^3.0.2"
-            },
-            "require-dev": {
-                "ext-pdo": "*",
-                "phpspec/prophecy-phpunit": "^2.0.1"
-            },
-            "suggest": {
-                "ext-soap": "*",
-                "ext-xdebug": "*"
-            },
-            "bin": [
-                "phpunit"
-            ],
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "9.5-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "src/Framework/Assert/Functions.php"
-                ],
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "The PHP Unit Testing framework.",
-            "homepage": "https://phpunit.de/",
-            "keywords": [
-                "phpunit",
-                "testing",
-                "xunit"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20"
-            },
-            "funding": [
-                {
-                    "url": "https://phpunit.de/sponsors.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2022-04-01T12:37:26+00:00"
-        },
-        {
-            "name": "psr/container",
-            "version": "1.1.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/container.git",
-                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
-                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.4.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Container\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "https://www.php-fig.org/"
-                }
-            ],
-            "description": "Common Container Interface (PHP FIG PSR-11)",
-            "homepage": "https://github.com/php-fig/container",
-            "keywords": [
-                "PSR-11",
-                "container",
-                "container-interface",
-                "container-interop",
-                "psr"
-            ],
-            "support": {
-                "issues": "https://github.com/php-fig/container/issues",
-                "source": "https://github.com/php-fig/container/tree/1.1.2"
-            },
-            "time": "2021-11-05T16:50:12+00:00"
-        },
-        {
-            "name": "psr/http-client",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/http-client.git",
-                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
-                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.0 || ^8.0",
-                "psr/http-message": "^1.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Http\\Client\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for HTTP clients",
-            "homepage": "https://github.com/php-fig/http-client",
-            "keywords": [
-                "http",
-                "http-client",
-                "psr",
-                "psr-18"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/http-client/tree/master"
-            },
-            "time": "2020-06-29T06:28:15+00:00"
-        },
-        {
-            "name": "psr/http-factory",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/http-factory.git",
-                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
-                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.0.0",
-                "psr/http-message": "^1.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Http\\Message\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interfaces for PSR-7 HTTP message factories",
-            "keywords": [
-                "factory",
-                "http",
-                "message",
-                "psr",
-                "psr-17",
-                "psr-7",
-                "request",
-                "response"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/http-factory/tree/master"
-            },
-            "time": "2019-04-30T12:38:16+00:00"
-        },
-        {
-            "name": "psr/http-message",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/http-message.git",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Http\\Message\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for HTTP messages",
-            "homepage": "https://github.com/php-fig/http-message",
-            "keywords": [
-                "http",
-                "http-message",
-                "psr",
-                "psr-7",
-                "request",
-                "response"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/http-message/tree/master"
-            },
-            "time": "2016-08-06T14:39:51+00:00"
-        },
-        {
-            "name": "psr/log",
-            "version": "1.1.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/log.git",
-                "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
-                "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Log\\": "Psr/Log/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "https://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for logging libraries",
-            "homepage": "https://github.com/php-fig/log",
-            "keywords": [
-                "log",
-                "psr",
-                "psr-3"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/log/tree/1.1.4"
-            },
-            "time": "2021-05-03T11:20:27+00:00"
-        },
-        {
-            "name": "ralouphie/getallheaders",
-            "version": "3.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/ralouphie/getallheaders.git",
-                "reference": "120b605dfeb996808c31b6477290a714d356e822"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
-                "reference": "120b605dfeb996808c31b6477290a714d356e822",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.6"
-            },
-            "require-dev": {
-                "php-coveralls/php-coveralls": "^2.1",
-                "phpunit/phpunit": "^5 || ^6.5"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "src/getallheaders.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ralph Khattar",
-                    "email": "ralph.khattar@gmail.com"
-                }
-            ],
-            "description": "A polyfill for getallheaders.",
-            "support": {
-                "issues": "https://github.com/ralouphie/getallheaders/issues",
-                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
-            },
-            "time": "2019-03-08T08:55:37+00:00"
-        },
-        {
-            "name": "sebastian/cli-parser",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/cli-parser.git",
-                "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
-                "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Library for parsing CLI options",
-            "homepage": "https://github.com/sebastianbergmann/cli-parser",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
-                "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-09-28T06:08:49+00:00"
-        },
-        {
-            "name": "sebastian/code-unit",
-            "version": "1.0.8",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/code-unit.git",
-                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
-                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Collection of value objects that represent the PHP code units",
-            "homepage": "https://github.com/sebastianbergmann/code-unit",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/code-unit/issues",
-                "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T13:08:54+00:00"
-        },
-        {
-            "name": "sebastian/code-unit-reverse-lookup",
-            "version": "2.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
-                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
-                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Looks up which function or method a line of code belongs to",
-            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
-                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-09-28T05:30:19+00:00"
-        },
-        {
-            "name": "sebastian/comparator",
-            "version": "4.0.6",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "55f4261989e546dc112258c7a75935a81a7ce382"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
-                "reference": "55f4261989e546dc112258c7a75935a81a7ce382",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3",
-                "sebastian/diff": "^4.0",
-                "sebastian/exporter": "^4.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Volker Dusch",
-                    "email": "github@wallbash.com"
-                },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@2bepublished.at"
-                }
-            ],
-            "description": "Provides the functionality to compare PHP values for equality",
-            "homepage": "https://github.com/sebastianbergmann/comparator",
-            "keywords": [
-                "comparator",
-                "compare",
-                "equality"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/comparator/issues",
-                "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T15:49:45+00:00"
-        },
-        {
-            "name": "sebastian/complexity",
-            "version": "2.0.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/complexity.git",
-                "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
-                "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
-                "shasum": ""
-            },
-            "require": {
-                "nikic/php-parser": "^4.7",
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Library for calculating the complexity of PHP code units",
-            "homepage": "https://github.com/sebastianbergmann/complexity",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/complexity/issues",
-                "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T15:52:27+00:00"
-        },
-        {
-            "name": "sebastian/diff",
-            "version": "4.0.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
-                "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3",
-                "symfony/process": "^4.2 || ^5"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Kore Nordmann",
-                    "email": "mail@kore-nordmann.de"
-                }
-            ],
-            "description": "Diff implementation",
-            "homepage": "https://github.com/sebastianbergmann/diff",
-            "keywords": [
-                "diff",
-                "udiff",
-                "unidiff",
-                "unified diff"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/diff/issues",
-                "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T13:10:38+00:00"
-        },
-        {
-            "name": "sebastian/environment",
-            "version": "5.1.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7",
-                "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "suggest": {
-                "ext-posix": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "5.1-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Provides functionality to handle HHVM/PHP environments",
-            "homepage": "http://www.github.com/sebastianbergmann/environment",
-            "keywords": [
-                "Xdebug",
-                "environment",
-                "hhvm"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/environment/issues",
-                "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2022-04-03T09:37:03+00:00"
-        },
-        {
-            "name": "sebastian/exporter",
-            "version": "4.0.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
-                "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3",
-                "sebastian/recursion-context": "^4.0"
-            },
-            "require-dev": {
-                "ext-mbstring": "*",
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Volker Dusch",
-                    "email": "github@wallbash.com"
-                },
-                {
-                    "name": "Adam Harvey",
-                    "email": "aharvey@php.net"
-                },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@gmail.com"
-                }
-            ],
-            "description": "Provides the functionality to export PHP variables for visualization",
-            "homepage": "https://www.github.com/sebastianbergmann/exporter",
-            "keywords": [
-                "export",
-                "exporter"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/exporter/issues",
-                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2021-11-11T14:18:36+00:00"
-        },
-        {
-            "name": "sebastian/global-state",
-            "version": "5.0.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
-                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3",
-                "sebastian/object-reflector": "^2.0",
-                "sebastian/recursion-context": "^4.0"
-            },
-            "require-dev": {
-                "ext-dom": "*",
-                "phpunit/phpunit": "^9.3"
-            },
-            "suggest": {
-                "ext-uopz": "*"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "5.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Snapshotting of global state",
-            "homepage": "http://www.github.com/sebastianbergmann/global-state",
-            "keywords": [
-                "global state"
-            ],
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2022-02-14T08:28:10+00:00"
-        },
-        {
-            "name": "sebastian/lines-of-code",
-            "version": "1.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/lines-of-code.git",
-                "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
-                "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
-                "shasum": ""
-            },
-            "require": {
-                "nikic/php-parser": "^4.6",
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Library for counting the lines of code in PHP source code",
-            "homepage": "https://github.com/sebastianbergmann/lines-of-code",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
-                "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-11-28T06:42:11+00:00"
-        },
-        {
-            "name": "sebastian/object-enumerator",
-            "version": "4.0.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
-                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
-                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3",
-                "sebastian/object-reflector": "^2.0",
-                "sebastian/recursion-context": "^4.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
-            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
-                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T13:12:34+00:00"
-        },
-        {
-            "name": "sebastian/object-reflector",
-            "version": "2.0.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/object-reflector.git",
-                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
-                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Allows reflection of object attributes, including inherited and non-public ones",
-            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
-                "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T13:14:26+00:00"
-        },
-        {
-            "name": "sebastian/recursion-context",
-            "version": "4.0.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/recursion-context.git",
-                "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
-                "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Adam Harvey",
-                    "email": "aharvey@php.net"
-                }
-            ],
-            "description": "Provides functionality to recursively process PHP variables",
-            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
-                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-10-26T13:17:30+00:00"
-        },
-        {
-            "name": "sebastian/resource-operations",
-            "version": "3.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/resource-operations.git",
-                "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
-                "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                }
-            ],
-            "description": "Provides a list of PHP built-in functions that operate on resources",
-            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
-                "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-09-28T06:45:17+00:00"
-        },
-        {
-            "name": "sebastian/type",
-            "version": "3.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/type.git",
-                "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
-                "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.5"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Collection of value objects that represent the types of the PHP type system",
-            "homepage": "https://github.com/sebastianbergmann/type",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/type/issues",
-                "source": "https://github.com/sebastianbergmann/type/tree/3.0.0"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2022-03-15T09:54:48+00:00"
-        },
-        {
-            "name": "sebastian/version",
-            "version": "3.0.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/version.git",
-                "reference": "c6c1022351a901512170118436c764e473f6de8c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
-                "reference": "c6c1022351a901512170118436c764e473f6de8c",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
-            "homepage": "https://github.com/sebastianbergmann/version",
-            "support": {
-                "issues": "https://github.com/sebastianbergmann/version/issues",
-                "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-09-28T06:39:44+00:00"
-        },
-        {
-            "name": "symfony/config",
-            "version": "v5.4.9",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/config.git",
-                "reference": "8f551fe22672ac7ab2c95fe46d899f960ed4d979"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/8f551fe22672ac7ab2c95fe46d899f960ed4d979",
-                "reference": "8f551fe22672ac7ab2c95fe46d899f960ed4d979",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2.5",
-                "symfony/deprecation-contracts": "^2.1|^3",
-                "symfony/filesystem": "^4.4|^5.0|^6.0",
-                "symfony/polyfill-ctype": "~1.8",
-                "symfony/polyfill-php80": "^1.16",
-                "symfony/polyfill-php81": "^1.22"
-            },
-            "conflict": {
-                "symfony/finder": "<4.4"
-            },
-            "require-dev": {
-                "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
-                "symfony/finder": "^4.4|^5.0|^6.0",
-                "symfony/messenger": "^4.4|^5.0|^6.0",
-                "symfony/service-contracts": "^1.1|^2|^3",
-                "symfony/yaml": "^4.4|^5.0|^6.0"
-            },
-            "suggest": {
-                "symfony/yaml": "To use the yaml reference dumper"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Config\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/config/tree/v5.4.9"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-05-17T10:39:36+00:00"
-        },
-        {
-            "name": "symfony/console",
-            "version": "v5.4.9",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/console.git",
-                "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb",
-                "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2.5",
-                "symfony/deprecation-contracts": "^2.1|^3",
-                "symfony/polyfill-mbstring": "~1.0",
-                "symfony/polyfill-php73": "^1.9",
-                "symfony/polyfill-php80": "^1.16",
-                "symfony/service-contracts": "^1.1|^2|^3",
-                "symfony/string": "^5.1|^6.0"
-            },
-            "conflict": {
-                "psr/log": ">=3",
-                "symfony/dependency-injection": "<4.4",
-                "symfony/dotenv": "<5.1",
-                "symfony/event-dispatcher": "<4.4",
-                "symfony/lock": "<4.4",
-                "symfony/process": "<4.4"
-            },
-            "provide": {
-                "psr/log-implementation": "1.0|2.0"
-            },
-            "require-dev": {
-                "psr/log": "^1|^2",
-                "symfony/config": "^4.4|^5.0|^6.0",
-                "symfony/dependency-injection": "^4.4|^5.0|^6.0",
-                "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
-                "symfony/lock": "^4.4|^5.0|^6.0",
-                "symfony/process": "^4.4|^5.0|^6.0",
-                "symfony/var-dumper": "^4.4|^5.0|^6.0"
-            },
-            "suggest": {
-                "psr/log": "For using the console logger",
-                "symfony/event-dispatcher": "",
-                "symfony/lock": "",
-                "symfony/process": ""
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Console\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Eases the creation of beautiful and testable command line interfaces",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "cli",
-                "command line",
-                "console",
-                "terminal"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/console/tree/v5.4.9"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-05-18T06:17:34+00:00"
-        },
-        {
-            "name": "symfony/deprecation-contracts",
-            "version": "v2.5.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/deprecation-contracts.git",
-                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
-                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "2.5-dev"
-                },
-                "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "function.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "A generic function and convention to trigger deprecation notices",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.1"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-01-02T09:53:40+00:00"
-        },
-        {
-            "name": "symfony/filesystem",
-            "version": "v5.4.9",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/filesystem.git",
-                "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/36a017fa4cce1eff1b8e8129ff53513abcef05ba",
-                "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2.5",
-                "symfony/polyfill-ctype": "~1.8",
-                "symfony/polyfill-mbstring": "~1.8",
-                "symfony/polyfill-php80": "^1.16"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Filesystem\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Provides basic utilities for the filesystem",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/filesystem/tree/v5.4.9"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-05-20T13:55:35+00:00"
-        },
-        {
-            "name": "symfony/polyfill-ctype",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "30885182c981ab175d4d034db0f6f469898070ab"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
-                "reference": "30885182c981ab175d4d034db0f6f469898070ab",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "provide": {
-                "ext-ctype": "*"
-            },
-            "suggest": {
-                "ext-ctype": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Gert de Pagter",
-                    "email": "BackEndTea@gmail.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for ctype functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "ctype",
-                "polyfill",
-                "portable"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-10-20T20:35:02+00:00"
-        },
-        {
-            "name": "symfony/polyfill-intl-grapheme",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
-                "reference": "81b86b50cf841a64252b439e738e97f4a34e2783"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783",
-                "reference": "81b86b50cf841a64252b439e738e97f4a34e2783",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for intl's grapheme_* functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "grapheme",
-                "intl",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-11-23T21:10:46+00:00"
-        },
-        {
-            "name": "symfony/polyfill-mbstring",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
-                "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "provide": {
-                "ext-mbstring": "*"
-            },
-            "suggest": {
-                "ext-mbstring": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for the Mbstring extension",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "mbstring",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-11-30T18:21:41+00:00"
-        },
-        {
-            "name": "symfony/polyfill-php73",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php73.git",
-                "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5",
-                "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php73\\": ""
-                },
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-06-05T21:20:04+00:00"
-        },
-        {
-            "name": "symfony/polyfill-php80",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
-                "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php80\\": ""
-                },
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ion Bazan",
-                    "email": "ion.bazan@gmail.com"
-                },
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-03-04T08:16:47+00:00"
-        },
-        {
-            "name": "symfony/polyfill-php81",
-            "version": "v1.25.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php81.git",
-                "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
-                "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php81\\": ""
-                },
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-09-13T13:58:11+00:00"
-        },
-        {
-            "name": "symfony/service-contracts",
-            "version": "v2.5.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/service-contracts.git",
-                "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c",
-                "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2.5",
-                "psr/container": "^1.1",
-                "symfony/deprecation-contracts": "^2.1|^3"
-            },
-            "conflict": {
-                "ext-psr": "<1.1|>=2"
-            },
-            "suggest": {
-                "symfony/service-implementation": ""
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "2.5-dev"
-                },
-                "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Contracts\\Service\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Generic abstractions related to writing services",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "abstractions",
-                "contracts",
-                "decoupling",
-                "interfaces",
-                "interoperability",
-                "standards"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/service-contracts/tree/v2.5.1"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-03-13T20:07:29+00:00"
-        },
-        {
-            "name": "symfony/stopwatch",
-            "version": "v5.4.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/stopwatch.git",
-                "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4d04b5c24f3c9a1a168a131f6cbe297155bc0d30",
-                "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2.5",
-                "symfony/service-contracts": "^1|^2|^3"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Stopwatch\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Provides a way to profile code",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/stopwatch/tree/v5.4.5"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-02-18T16:06:09+00:00"
-        },
-        {
-            "name": "symfony/string",
-            "version": "v5.4.9",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/string.git",
-                "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/985e6a9703ef5ce32ba617c9c7d97873bb7b2a99",
-                "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2.5",
-                "symfony/polyfill-ctype": "~1.8",
-                "symfony/polyfill-intl-grapheme": "~1.0",
-                "symfony/polyfill-intl-normalizer": "~1.0",
-                "symfony/polyfill-mbstring": "~1.0",
-                "symfony/polyfill-php80": "~1.15"
-            },
-            "conflict": {
-                "symfony/translation-contracts": ">=3.0"
-            },
-            "require-dev": {
-                "symfony/error-handler": "^4.4|^5.0|^6.0",
-                "symfony/http-client": "^4.4|^5.0|^6.0",
-                "symfony/translation-contracts": "^1.1|^2",
-                "symfony/var-exporter": "^4.4|^5.0|^6.0"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "Resources/functions.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Component\\String\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "grapheme",
-                "i18n",
-                "string",
-                "unicode",
-                "utf-8",
-                "utf8"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/string/tree/v5.4.9"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-04-19T10:40:37+00:00"
-        },
-        {
-            "name": "symfony/yaml",
-            "version": "v5.4.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/yaml.git",
-                "reference": "e80f87d2c9495966768310fc531b487ce64237a2"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/e80f87d2c9495966768310fc531b487ce64237a2",
-                "reference": "e80f87d2c9495966768310fc531b487ce64237a2",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2.5",
-                "symfony/deprecation-contracts": "^2.1|^3",
-                "symfony/polyfill-ctype": "^1.8"
-            },
-            "conflict": {
-                "symfony/console": "<5.3"
-            },
-            "require-dev": {
-                "symfony/console": "^5.3|^6.0"
-            },
-            "suggest": {
-                "symfony/console": "For validating YAML files using the lint command"
-            },
-            "bin": [
-                "Resources/bin/yaml-lint"
-            ],
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Yaml\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Loads and dumps YAML files",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/yaml/tree/v5.4.3"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-01-26T16:32:32+00:00"
-        },
-        {
-            "name": "theseer/tokenizer",
-            "version": "1.2.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/theseer/tokenizer.git",
-                "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
-                "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
-                "shasum": ""
-            },
-            "require": {
-                "ext-dom": "*",
-                "ext-tokenizer": "*",
-                "ext-xmlwriter": "*",
-                "php": "^7.2 || ^8.0"
-            },
-            "type": "library",
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
-                }
-            ],
-            "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
-            "support": {
-                "issues": "https://github.com/theseer/tokenizer/issues",
-                "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/theseer",
-                    "type": "github"
-                }
-            ],
-            "time": "2021-07-28T10:34:58+00:00"
-        },
-        {
-            "name": "vimeo/psalm",
-            "version": "4.23.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/vimeo/psalm.git",
-                "reference": "f1fe6ff483bf325c803df9f510d09a03fd796f88"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/vimeo/psalm/zipball/f1fe6ff483bf325c803df9f510d09a03fd796f88",
-                "reference": "f1fe6ff483bf325c803df9f510d09a03fd796f88",
-                "shasum": ""
-            },
-            "require": {
-                "amphp/amp": "^2.4.2",
-                "amphp/byte-stream": "^1.5",
-                "composer/package-versions-deprecated": "^1.8.0",
-                "composer/semver": "^1.4 || ^2.0 || ^3.0",
-                "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0",
-                "dnoegel/php-xdg-base-dir": "^0.1.1",
-                "ext-ctype": "*",
-                "ext-dom": "*",
-                "ext-json": "*",
-                "ext-libxml": "*",
-                "ext-mbstring": "*",
-                "ext-simplexml": "*",
-                "ext-tokenizer": "*",
-                "felixfbecker/advanced-json-rpc": "^3.0.3",
-                "felixfbecker/language-server-protocol": "^1.5",
-                "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
-                "nikic/php-parser": "^4.13",
-                "openlss/lib-array2xml": "^1.0",
-                "php": "^7.1|^8",
-                "sebastian/diff": "^3.0 || ^4.0",
-                "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0",
-                "symfony/polyfill-php80": "^1.25",
-                "webmozart/path-util": "^2.3"
-            },
-            "provide": {
-                "psalm/psalm": "self.version"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.2",
-                "brianium/paratest": "^4.0||^6.0",
-                "ext-curl": "*",
-                "php-parallel-lint/php-parallel-lint": "^1.2",
-                "phpdocumentor/reflection-docblock": "^5",
-                "phpmyadmin/sql-parser": "5.1.0||dev-master",
-                "phpspec/prophecy": ">=1.9.0",
-                "phpunit/phpunit": "^9.0",
-                "psalm/plugin-phpunit": "^0.16",
-                "slevomat/coding-standard": "^7.0",
-                "squizlabs/php_codesniffer": "^3.5",
-                "symfony/process": "^4.3 || ^5.0 || ^6.0",
-                "weirdan/prophecy-shim": "^1.0 || ^2.0"
-            },
-            "suggest": {
-                "ext-curl": "In order to send data to shepherd",
-                "ext-igbinary": "^2.0.5 is required, used to serialize caching data"
-            },
-            "bin": [
-                "psalm",
-                "psalm-language-server",
-                "psalm-plugin",
-                "psalm-refactor",
-                "psalter"
-            ],
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.x-dev",
-                    "dev-3.x": "3.x-dev",
-                    "dev-2.x": "2.x-dev",
-                    "dev-1.x": "1.x-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "src/functions.php",
-                    "src/spl_object_id.php"
-                ],
-                "psr-4": {
-                    "Psalm\\": "src/Psalm/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Matthew Brown"
-                }
-            ],
-            "description": "A static analysis tool for finding errors in PHP applications",
-            "keywords": [
-                "code",
-                "inspection",
-                "php"
-            ],
-            "support": {
-                "issues": "https://github.com/vimeo/psalm/issues",
-                "source": "https://github.com/vimeo/psalm/tree/4.23.0"
-            },
-            "time": "2022-04-28T17:35:49+00:00"
-        },
-        {
-            "name": "webmozart/assert",
-            "version": "1.10.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/webmozarts/assert.git",
-                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
-                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2 || ^8.0",
-                "symfony/polyfill-ctype": "^1.8"
-            },
-            "conflict": {
-                "phpstan/phpstan": "<0.12.20",
-                "vimeo/psalm": "<4.6.1 || 4.6.2"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^8.5.13"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.10-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Webmozart\\Assert\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@gmail.com"
-                }
-            ],
-            "description": "Assertions to validate method input/output with nice error messages.",
-            "keywords": [
-                "assert",
-                "check",
-                "validate"
-            ],
-            "support": {
-                "issues": "https://github.com/webmozarts/assert/issues",
-                "source": "https://github.com/webmozarts/assert/tree/1.10.0"
-            },
-            "time": "2021-03-09T10:59:23+00:00"
-        },
-        {
-            "name": "webmozart/path-util",
-            "version": "2.3.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/webmozart/path-util.git",
-                "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725",
-                "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3",
-                "webmozart/assert": "~1.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.6",
-                "sebastian/version": "^1.0.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.3-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Webmozart\\PathUtil\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@gmail.com"
-                }
-            ],
-            "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.",
-            "support": {
-                "issues": "https://github.com/webmozart/path-util/issues",
-                "source": "https://github.com/webmozart/path-util/tree/2.3.0"
-            },
-            "abandoned": "symfony/filesystem",
-            "time": "2015-12-17T08:42:14+00:00"
-        }
-    ],
-    "aliases": [],
-    "minimum-stability": "stable",
-    "stability-flags": [],
-    "prefer-stable": false,
-    "prefer-lowest": false,
-    "platform": {
-        "php": ">=7.2"
-    },
-    "platform-dev": [],
-    "plugin-api-version": "2.0.0"
-}
diff --git a/vendor/egulias/email-validator/src/EmailLexer.php b/vendor/egulias/email-validator/src/EmailLexer.php
index 7e660b67c6d3425399d45058742d6ea25b1da9e9..6add6bd2b9e39e5fc28dc04cd3aab32e14b2796c 100644
--- a/vendor/egulias/email-validator/src/EmailLexer.php
+++ b/vendor/egulias/email-validator/src/EmailLexer.php
@@ -3,58 +3,62 @@
 namespace Egulias\EmailValidator;
 
 use Doctrine\Common\Lexer\AbstractLexer;
+use Doctrine\Common\Lexer\Token;
 
+/**
+ * @extends AbstractLexer<int, string>
+ */
 class EmailLexer extends AbstractLexer
 {
     //ASCII values
-    const S_EMPTY            = null;
-    const C_NUL              = 0;
-    const S_HTAB             = 9;
-    const S_LF               = 10;
-    const S_CR               = 13;
-    const S_SP               = 32;
-    const EXCLAMATION        = 33;
-    const S_DQUOTE           = 34;
-    const NUMBER_SIGN        = 35;
-    const DOLLAR             = 36;
-    const PERCENTAGE         = 37;
-    const AMPERSAND          = 38;
-    const S_SQUOTE           = 39;
-    const S_OPENPARENTHESIS  = 40;
-    const S_CLOSEPARENTHESIS = 41;
-    const ASTERISK           = 42;
-    const S_PLUS             = 43;
-    const S_COMMA            = 44;
-    const S_HYPHEN           = 45;
-    const S_DOT              = 46;
-    const S_SLASH            = 47;
-    const S_COLON            = 58;
-    const S_SEMICOLON        = 59;
-    const S_LOWERTHAN        = 60;
-    const S_EQUAL            = 61;
-    const S_GREATERTHAN      = 62;
-    const QUESTIONMARK       = 63;
-    const S_AT               = 64;
-    const S_OPENBRACKET      = 91;
-    const S_BACKSLASH        = 92;
-    const S_CLOSEBRACKET     = 93;
-    const CARET              = 94;
-    const S_UNDERSCORE       = 95;
-    const S_BACKTICK         = 96;
-    const S_OPENCURLYBRACES  = 123;
-    const S_PIPE             = 124;
-    const S_CLOSECURLYBRACES = 125;
-    const S_TILDE            = 126;
-    const C_DEL              = 127;
-    const INVERT_QUESTIONMARK= 168;
-    const INVERT_EXCLAMATION = 173;
-    const GENERIC            = 300;
-    const S_IPV6TAG          = 301;
-    const INVALID            = 302;
-    const CRLF               = 1310;
-    const S_DOUBLECOLON      = 5858;
-    const ASCII_INVALID_FROM = 127;
-    const ASCII_INVALID_TO   = 199;
+    public const S_EMPTY            = null;
+    public const C_NUL              = 0;
+    public const S_HTAB             = 9;
+    public const S_LF               = 10;
+    public const S_CR               = 13;
+    public const S_SP               = 32;
+    public const EXCLAMATION        = 33;
+    public const S_DQUOTE           = 34;
+    public const NUMBER_SIGN        = 35;
+    public const DOLLAR             = 36;
+    public const PERCENTAGE         = 37;
+    public const AMPERSAND          = 38;
+    public const S_SQUOTE           = 39;
+    public const S_OPENPARENTHESIS  = 40;
+    public const S_CLOSEPARENTHESIS = 41;
+    public const ASTERISK           = 42;
+    public const S_PLUS             = 43;
+    public const S_COMMA            = 44;
+    public const S_HYPHEN           = 45;
+    public const S_DOT              = 46;
+    public const S_SLASH            = 47;
+    public const S_COLON            = 58;
+    public const S_SEMICOLON        = 59;
+    public const S_LOWERTHAN        = 60;
+    public const S_EQUAL            = 61;
+    public const S_GREATERTHAN      = 62;
+    public const QUESTIONMARK       = 63;
+    public const S_AT               = 64;
+    public const S_OPENBRACKET      = 91;
+    public const S_BACKSLASH        = 92;
+    public const S_CLOSEBRACKET     = 93;
+    public const CARET              = 94;
+    public const S_UNDERSCORE       = 95;
+    public const S_BACKTICK         = 96;
+    public const S_OPENCURLYBRACES  = 123;
+    public const S_PIPE             = 124;
+    public const S_CLOSECURLYBRACES = 125;
+    public const S_TILDE            = 126;
+    public const C_DEL              = 127;
+    public const INVERT_QUESTIONMARK= 168;
+    public const INVERT_EXCLAMATION = 173;
+    public const GENERIC            = 300;
+    public const S_IPV6TAG          = 301;
+    public const INVALID            = 302;
+    public const CRLF               = 1310;
+    public const S_DOUBLECOLON      = 5858;
+    public const ASCII_INVALID_FROM = 127;
+    public const ASCII_INVALID_TO   = 199;
 
     /**
      * US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
@@ -107,11 +111,11 @@ class EmailLexer extends AbstractLexer
         '¡'    => self::INVERT_EXCLAMATION,
     ];
 
-    const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu";
+    public const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu";
 
-    const VALID_UTF8_REGEX = '/\p{Cc}+/u';
+    public const VALID_UTF8_REGEX = '/\p{Cc}+/u';
 
-    const CATCHABLE_PATTERNS = [
+    public const CATCHABLE_PATTERNS = [
         '[a-zA-Z]+[46]?', //ASCII and domain literal
         '[^\x00-\x7F]',  //UTF-8
         '[0-9]+',
@@ -121,11 +125,11 @@ class EmailLexer extends AbstractLexer
         '.',
     ];
 
-    const NON_CATCHABLE_PATTERNS = [
+    public const NON_CATCHABLE_PATTERNS = [
         '[\xA0-\xff]+',
     ];
 
-    const MODIFIERS = 'iu';
+    public const MODIFIERS = 'iu';
 
     /** @var bool */
     protected $hasInvalidTokens = false;
@@ -140,18 +144,20 @@ class EmailLexer extends AbstractLexer
     /**
      * The last matched/seen token.
      *
-     * @var array
+     * @var array|Token
      *
      * @psalm-suppress NonInvariantDocblockPropertyType
-     * @psalm-var array{value:string, type:null|int, position:int}
-     * @psalm-suppress NonInvariantDocblockPropertyType
+     * @psalm-var array{value:string, type:null|int, position:int}|Token<int, string>
      */
     public $token;
 
     /**
      * The next token in the input.
      *
-     * @var array{position: int, type: int|null|string, value: int|string}|null
+     * @var array|Token|null
+     *
+     * @psalm-suppress NonInvariantDocblockPropertyType
+     * @psalm-var array{position: int, type: int|null|string, value: int|string}|Token<int, string>|null
      */
     public $lookahead;
 
@@ -210,7 +216,9 @@ public function moveNext() : bool
             $this->accumulator .= $this->token['value'];
         }
 
-        $this->previous = $this->token;
+        $this->previous = $this->token instanceof Token
+            ? ['value' => $this->token->value, 'type' => $this->token->type, 'position' => $this->token->position]
+            : $this->token;
         
         if($this->lookahead === null) {
             $this->lookahead = self::$nullToken;
diff --git a/vendor/egulias/email-validator/src/EmailParser.php b/vendor/egulias/email-validator/src/EmailParser.php
index c78f74a98e14143f60c817cefcb6d6ddf91e335a..352eae4a03243f4eefe8d703097be17e98df5c36 100644
--- a/vendor/egulias/email-validator/src/EmailParser.php
+++ b/vendor/egulias/email-validator/src/EmailParser.php
@@ -2,7 +2,6 @@
 
 namespace Egulias\EmailValidator;
 
-use Egulias\EmailValidator\EmailLexer;
 use Egulias\EmailValidator\Result\Result;
 use Egulias\EmailValidator\Parser\LocalPart;
 use Egulias\EmailValidator\Parser\DomainPart;
@@ -13,7 +12,7 @@
 
 class EmailParser extends Parser
 {
-    const EMAIL_MAX_LENGTH = 254;
+    public const EMAIL_MAX_LENGTH = 254;
 
     /**
      * @var string
diff --git a/vendor/egulias/email-validator/src/MessageIDParser.php b/vendor/egulias/email-validator/src/MessageIDParser.php
index fe58b9f421c56e02b961e2fd1bfb2ee6832c25ac..b0b6720f8cbcdebcee28a4c6377f00a7f071e7a0 100644
--- a/vendor/egulias/email-validator/src/MessageIDParser.php
+++ b/vendor/egulias/email-validator/src/MessageIDParser.php
@@ -2,7 +2,6 @@
 
 namespace Egulias\EmailValidator;
 
-use Egulias\EmailValidator\Parser;
 use Egulias\EmailValidator\Result\Result;
 use Egulias\EmailValidator\Parser\IDLeftPart;
 use Egulias\EmailValidator\Parser\IDRightPart;
@@ -14,7 +13,7 @@
 class MessageIDParser extends Parser
 {
 
-    const EMAILID_MAX_LENGTH = 254;
+    public const EMAILID_MAX_LENGTH = 254;
 
     /**
      * @var string
@@ -89,4 +88,4 @@ private function addLongEmailWarning(string $localPart, string $parsedDomainPart
             $this->warnings[EmailTooLong::CODE] = new EmailTooLong();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/Comment.php b/vendor/egulias/email-validator/src/Parser/Comment.php
index ffa61281f8548f229b4e9e66a8dd4a7e4b9553ab..d6f3032fc3c66924854aad7ded9f7463cf6c1398 100644
--- a/vendor/egulias/email-validator/src/Parser/Comment.php
+++ b/vendor/egulias/email-validator/src/Parser/Comment.php
@@ -59,7 +59,8 @@ public function parse() : Result
 
         if($this->openedParenthesis >= 1) {
             return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']);
-        } else if ($this->openedParenthesis < 0) {
+        }
+        if ($this->openedParenthesis < 0) {
             return new InvalidEmail(new UnOpenedComment(), $this->lexer->token['value']);
         }
 
@@ -100,4 +101,4 @@ private function noClosingParenthesis() : bool
             return true;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php b/vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php
index c388efd65c0482f8efd84011e9b859521c95ce72..410032fea9dfd4330ce511e02731dfd375642a9f 100644
--- a/vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php
+++ b/vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php
@@ -15,4 +15,4 @@ public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool;
     public function endOfLoopValidations(EmailLexer $lexer) : Result;
 
     public function getWarnings() : array;
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/CommentStrategy/DomainComment.php b/vendor/egulias/email-validator/src/Parser/CommentStrategy/DomainComment.php
index b34ce29ad834439e3fd7be7d1a025b62c88b5964..cbbe3f201b0fe1dc3c79924e1090b74c375c4c3e 100644
--- a/vendor/egulias/email-validator/src/Parser/CommentStrategy/DomainComment.php
+++ b/vendor/egulias/email-validator/src/Parser/CommentStrategy/DomainComment.php
@@ -34,4 +34,4 @@ public function getWarnings(): array
     {
         return [];
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php b/vendor/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php
index 73bc7b2bc3c63cb23173c28780b75f1bab4a1648..e72319a215013ed93927fb217dbb4c9d849bce55 100644
--- a/vendor/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php
+++ b/vendor/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php
@@ -34,4 +34,4 @@ public function getWarnings(): array
     {
         return $this->warnings;
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/DomainLiteral.php b/vendor/egulias/email-validator/src/Parser/DomainLiteral.php
index 4e5b8bf7862ec34e5b58567566062215cbf9bd5f..e6d66d0bd80fc88cfcead5571648a0b0fc87ea1e 100644
--- a/vendor/egulias/email-validator/src/Parser/DomainLiteral.php
+++ b/vendor/egulias/email-validator/src/Parser/DomainLiteral.php
@@ -22,9 +22,9 @@
 
 class DomainLiteral extends PartParser
 {
-    const IPV4_REGEX = '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
+    public const IPV4_REGEX = '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
 
-    const OBSOLETE_WARNINGS = [
+    public const OBSOLETE_WARNINGS = [
         EmailLexer::INVALID,
         EmailLexer::C_DEL,
         EmailLexer::S_LF,
@@ -208,4 +208,4 @@ private function addTagWarnings() : void
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/DomainPart.php b/vendor/egulias/email-validator/src/Parser/DomainPart.php
index f1717409668a179ea83a0be6d0df17737b884734..e1f5b3bef3e0606b1ccf1f3e62006e7e4756b97d 100644
--- a/vendor/egulias/email-validator/src/Parser/DomainPart.php
+++ b/vendor/egulias/email-validator/src/Parser/DomainPart.php
@@ -2,6 +2,7 @@
 
 namespace Egulias\EmailValidator\Parser;
 
+use Doctrine\Common\Lexer\Token;
 use Egulias\EmailValidator\EmailLexer;
 use Egulias\EmailValidator\Warning\TLD;
 use Egulias\EmailValidator\Result\Result;
@@ -24,8 +25,8 @@
 
 class DomainPart extends PartParser
 {
-    const DOMAIN_MAX_LENGTH = 253;
-    const LABEL_MAX_LENGTH = 63;
+    public const DOMAIN_MAX_LENGTH = 253;
+    public const LABEL_MAX_LENGTH = 63;
 
     /**
      * @var string
@@ -212,7 +213,10 @@ protected function doParseDomainPart() : Result
         return new ValidEmail();
     }
 
-    private function checkNotAllowedChars(array $token) : Result
+    /**
+     * @psalm-param array|Token<int, string> $token
+     */
+    private function checkNotAllowedChars($token) : Result
     {
         $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
         if (isset($notAllowed[$token['type']])) {
diff --git a/vendor/egulias/email-validator/src/Parser/DoubleQuote.php b/vendor/egulias/email-validator/src/Parser/DoubleQuote.php
index 8b83273782fc61efe6c4042144dfb5e44d3ec5ef..b32e2b6b23afc612cd434f9b1bc7539d7cb0f410 100644
--- a/vendor/egulias/email-validator/src/Parser/DoubleQuote.php
+++ b/vendor/egulias/email-validator/src/Parser/DoubleQuote.php
@@ -2,7 +2,6 @@
 namespace Egulias\EmailValidator\Parser;
 
 use Egulias\EmailValidator\EmailLexer;
-use Egulias\EmailValidator\Parser\Parser;
 use Egulias\EmailValidator\Result\ValidEmail;
 use Egulias\EmailValidator\Result\InvalidEmail;
 use Egulias\EmailValidator\Warning\CFWSWithFWS;
@@ -85,4 +84,4 @@ protected function checkDQUOTE() : Result
         return new ValidEmail();
     }
 
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/FoldingWhiteSpace.php b/vendor/egulias/email-validator/src/Parser/FoldingWhiteSpace.php
index da17d434282ebb9f94374262ba4b7e0619678d2d..fff6ec3ef5b54e2948b0d4c9810ad4505686c298 100644
--- a/vendor/egulias/email-validator/src/Parser/FoldingWhiteSpace.php
+++ b/vendor/egulias/email-validator/src/Parser/FoldingWhiteSpace.php
@@ -15,7 +15,7 @@
 
 class  FoldingWhiteSpace extends PartParser
 {
-    const FWS_TYPES = [
+    public const FWS_TYPES = [
         EmailLexer::S_SP,
         EmailLexer::S_HTAB,
         EmailLexer::S_CR,
@@ -83,4 +83,4 @@ protected function isFWS() : bool
 
         return in_array($this->lexer->token['type'], self::FWS_TYPES);
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/IDLeftPart.php b/vendor/egulias/email-validator/src/Parser/IDLeftPart.php
index abb4982b72c65c92dfbe1e78928658e107b41ea0..5fd9cdf9929d06aba5e9d5dda33f8fcb8f6f871d 100644
--- a/vendor/egulias/email-validator/src/Parser/IDLeftPart.php
+++ b/vendor/egulias/email-validator/src/Parser/IDLeftPart.php
@@ -3,7 +3,6 @@
 namespace Egulias\EmailValidator\Parser;
 
 use Egulias\EmailValidator\Result\Result;
-use Egulias\EmailValidator\Parser\LocalPart;
 use Egulias\EmailValidator\Result\InvalidEmail;
 use Egulias\EmailValidator\Result\Reason\CommentsInIDRight;
 
@@ -13,4 +12,4 @@ protected function parseComments(): Result
     {
        return new InvalidEmail(new CommentsInIDRight(), $this->lexer->token['value']);
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/IDRightPart.php b/vendor/egulias/email-validator/src/Parser/IDRightPart.php
index 243256274faa37f1f3478056aacdcd349c1d0692..1a1268f2dfb006aaefe9facf39ce55c35b8a95b1 100644
--- a/vendor/egulias/email-validator/src/Parser/IDRightPart.php
+++ b/vendor/egulias/email-validator/src/Parser/IDRightPart.php
@@ -26,4 +26,4 @@ protected function validateTokens(bool $hasComments) : Result
         }
         return new ValidEmail();
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/LocalPart.php b/vendor/egulias/email-validator/src/Parser/LocalPart.php
index 90f01a5292d21e12b42109a000eb5333554939b5..154521b27f869fcbe1ff13182edf9f19ff22c3bc 100644
--- a/vendor/egulias/email-validator/src/Parser/LocalPart.php
+++ b/vendor/egulias/email-validator/src/Parser/LocalPart.php
@@ -15,7 +15,7 @@
 
 class LocalPart extends PartParser
 {
-    const INVALID_TOKENS = [
+    public const INVALID_TOKENS = [
         EmailLexer::S_COMMA => EmailLexer::S_COMMA,
         EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
         EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
@@ -162,4 +162,4 @@ private function validateEscaping() : Result
 
         return new ValidEmail();
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Parser/PartParser.php b/vendor/egulias/email-validator/src/Parser/PartParser.php
index fd65fc56028ad9e14a97fa5a1bc2ccd0071ecb3d..a75a172acb42ed3c4ec542d00cd5cccaf24a47ea 100644
--- a/vendor/egulias/email-validator/src/Parser/PartParser.php
+++ b/vendor/egulias/email-validator/src/Parser/PartParser.php
@@ -60,4 +60,4 @@ protected function escaped() : bool
             &&
             $this->lexer->token['type'] !== EmailLexer::GENERIC;
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/InvalidEmail.php b/vendor/egulias/email-validator/src/Result/InvalidEmail.php
index 3d85e154aec378ad33ce60b3896560e0fb48f868..180f4d890438d54416aea492b08772beba2eeca0 100644
--- a/vendor/egulias/email-validator/src/Result/InvalidEmail.php
+++ b/vendor/egulias/email-validator/src/Result/InvalidEmail.php
@@ -6,7 +6,7 @@
 
 class InvalidEmail implements Result
 {
-    private  $token;
+    private $token;
     /**
      * @var Reason
      */
@@ -43,4 +43,4 @@ public function reason() : Reason
         return $this->reason;
     }
 
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php b/vendor/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php
index 76015a2d9e99dadb75fcf96ca16c8b0d8fd3a878..96e22842f7d9c2887c05380b6cf8621c7b95d3aa 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return 'ATEXT found after CFWS';
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/CRLFAtTheEnd.php b/vendor/egulias/email-validator/src/Result/Reason/CRLFAtTheEnd.php
index a0b66e71f6c79310eb35f1cadf26b82e2b4c3bd0..185b9ebc7b8e67796c78273a3edc17bee15cd59c 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/CRLFAtTheEnd.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/CRLFAtTheEnd.php
@@ -4,8 +4,8 @@
 
 class CRLFAtTheEnd implements Reason
 {
-    const CODE = 149;
-    const REASON = "CRLF at the end";
+    public const CODE = 149;
+    public const REASON = "CRLF at the end";
 
     public function code() : int
     {
diff --git a/vendor/egulias/email-validator/src/Result/Reason/CharNotAllowed.php b/vendor/egulias/email-validator/src/Result/Reason/CharNotAllowed.php
index 45999b3629891c6cd45130b534aeea0e16753910..7840cc76e12416dc05eb3f08d1e5b08e6a631569 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/CharNotAllowed.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/CharNotAllowed.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return "Character not allowed";
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/CommaInDomain.php b/vendor/egulias/email-validator/src/Result/Reason/CommaInDomain.php
index 93d3b5615964983c19367c0cd0b79fc2d4e66714..055f145764e07a4e4cfccb0e2c9fce3f00193919 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/CommaInDomain.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/CommaInDomain.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return "Comma ',' is not allowed in domain part";
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/CommentsInIDRight.php b/vendor/egulias/email-validator/src/Result/Reason/CommentsInIDRight.php
index 43a7cf7b3227a746602061d6bc4cfb2d1e7b63b4..6567a1102843bfbbef3d81dfb1bd476a30a3b90e 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/CommentsInIDRight.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/CommentsInIDRight.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return 'Comments are not allowed in IDRight for message-id';
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/DetailedReason.php b/vendor/egulias/email-validator/src/Result/Reason/DetailedReason.php
index 17519873396cd715a29f8e3d1e041d384423a9c5..8541c924b9fb5340ba3acb026d40d75e6ef15723 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/DetailedReason.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/DetailedReason.php
@@ -10,4 +10,4 @@ public function __construct(string $details)
     {
         $this->detailedDescription = $details;
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php b/vendor/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php
index 55f44bbafde323ede2a8b249899033c8e73ceb51..bcaefb68a1eb8e9609b969942d93d049b27306c4 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return 'Domain accepts no mail (Null MX, RFC7505)';
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/ExceptionFound.php b/vendor/egulias/email-validator/src/Result/Reason/ExceptionFound.php
index 8b1135d06d3fc148c24c81647d0507d0e0419e29..ffed86c3a6bc12ece0eab11427a37534e68f7c84 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/ExceptionFound.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/ExceptionFound.php
@@ -23,4 +23,4 @@ public function description() : string
     {
         return $this->exception->getMessage();
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php b/vendor/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php
index 7deffcaffe40328318bfb50a60199432e2dec2f4..525e7acadbe18847868d3a7ac42a683781a18595 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return "Closing bracket ']' for domain literal not found";
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/LocalOrReservedDomain.php b/vendor/egulias/email-validator/src/Result/Reason/LocalOrReservedDomain.php
index bc7c5d5a3df6ce9cd734fef57de586fd38607d07..c464767bf854dae37c892d7d0a4de7bb3d777a96 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/LocalOrReservedDomain.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/LocalOrReservedDomain.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return 'Local, mDNS or reserved domain (RFC2606, RFC6762)';
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/NoDNSRecord.php b/vendor/egulias/email-validator/src/Result/Reason/NoDNSRecord.php
index e217d02c95fdd0e5cead30533a365f50b8d810cc..2c966c2800ce95f0598f653bdb35ef819b0a21e6 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/NoDNSRecord.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/NoDNSRecord.php
@@ -13,4 +13,4 @@ public function description() : string
     {
         return 'No MX or A DSN record was found for this email';
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/Reason.php b/vendor/egulias/email-validator/src/Result/Reason/Reason.php
index e6810b9365c4edace4e5879f45ef647915ffa862..786eb9b4cb9d3e774545fca93d83892782e28160 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/Reason.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/Reason.php
@@ -13,4 +13,4 @@ public function code() : int;
      * Short description of the result, human readable.
      */
     public function description() : string;
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/UnOpenedComment.php b/vendor/egulias/email-validator/src/Result/Reason/UnOpenedComment.php
index c76a5844511ea6fbabd651ac7e57ef4c21207fde..b0a4316825f8fe2a5c077e11b7e03b4f50121584 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/UnOpenedComment.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/UnOpenedComment.php
@@ -13,4 +13,4 @@ public function description(): string
     {
         return 'Missing opening comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2';
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Reason/UnusualElements.php b/vendor/egulias/email-validator/src/Result/Reason/UnusualElements.php
index 03873dc09ebac4004d57d041d3a62caac5310fe7..861f3bcb879dd06207c77e6f7f9d009c07c4937f 100644
--- a/vendor/egulias/email-validator/src/Result/Reason/UnusualElements.php
+++ b/vendor/egulias/email-validator/src/Result/Reason/UnusualElements.php
@@ -23,4 +23,4 @@ public function description() : string
     {
         return 'Unusual element found, wourld render invalid in majority of cases. Element found: ' . $this->element;
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/Result.php b/vendor/egulias/email-validator/src/Result/Result.php
index 1e16bccacc9a279d6d774ae6d8293926ab9fdf4f..fd13e6c6877d0d71bdee6e71a3e644259a12aab0 100644
--- a/vendor/egulias/email-validator/src/Result/Result.php
+++ b/vendor/egulias/email-validator/src/Result/Result.php
@@ -24,4 +24,4 @@ public function description() : string;
      * Code for user land to act upon.
      */
     public function code() : int;
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/SpoofEmail.php b/vendor/egulias/email-validator/src/Result/SpoofEmail.php
index 9f010de11444a306ee903ac5e2d8e3f2c8e15a62..0ad449b71dc7e91f25961e4a380ad7d1e9ec33e3 100644
--- a/vendor/egulias/email-validator/src/Result/SpoofEmail.php
+++ b/vendor/egulias/email-validator/src/Result/SpoofEmail.php
@@ -1,7 +1,6 @@
 <?php
 namespace Egulias\EmailValidator\Result;
 
-use Egulias\EmailValidator\Result\InvalidEmail;
 use Egulias\EmailValidator\Result\Reason\SpoofEmail as ReasonSpoofEmail;
 
 class SpoofEmail extends InvalidEmail
@@ -11,4 +10,4 @@ public function __construct()
         $this->reason = new ReasonSpoofEmail();
         parent::__construct($this->reason, '');
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Result/ValidEmail.php b/vendor/egulias/email-validator/src/Result/ValidEmail.php
index 4f3693a9247ac26c81d020bcee5c7bc7e3c6907e..fdc882fac6d94251ba06ed0fdf3fc9f72fd823cf 100644
--- a/vendor/egulias/email-validator/src/Result/ValidEmail.php
+++ b/vendor/egulias/email-validator/src/Result/ValidEmail.php
@@ -24,4 +24,4 @@ public function code(): int
         return 0;
     }
 
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Validation/DNSCheckValidation.php b/vendor/egulias/email-validator/src/Validation/DNSCheckValidation.php
index 4e0041b50bdd9a02cb2e0e23e5f918e133cf6ea1..4882b80443492b8b1e49b2d0325ff29791e3515a 100644
--- a/vendor/egulias/email-validator/src/Validation/DNSCheckValidation.php
+++ b/vendor/egulias/email-validator/src/Validation/DNSCheckValidation.php
@@ -2,7 +2,6 @@
 
 namespace Egulias\EmailValidator\Validation;
 
-use Egulias\EmailValidator\Validation\DNSGetRecordWrapper;
 use Egulias\EmailValidator\EmailLexer;
 use Egulias\EmailValidator\Result\InvalidEmail;
 use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail;
@@ -22,7 +21,7 @@ class DNSCheckValidation implements EmailValidation
      * Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
      * mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
      */
-    const RESERVED_DNS_TOP_LEVEL_NAMES = [
+    public const RESERVED_DNS_TOP_LEVEL_NAMES = [
         // Reserved Top Level DNS Names
         'test',
         'example',
@@ -61,7 +60,7 @@ class DNSCheckValidation implements EmailValidation
      */
     private $dnsGetRecord;
 
-    public function __construct(DNSGetRecordWrapper $dnsGetRecord = null)
+    public function __construct(?DNSGetRecordWrapper $dnsGetRecord = null)
     {
         if (!function_exists('idn_to_ascii')) {
             throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
@@ -189,4 +188,4 @@ private function validateMxRecord($dnsRecord) : bool
 
         return true;
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Validation/DNSGetRecordWrapper.php b/vendor/egulias/email-validator/src/Validation/DNSGetRecordWrapper.php
index ec6fe4b6158fb594f09fb8017f7096b5a54b9dd6..f493c5746c11ebdf8a54e9d9877554819dd3343b 100644
--- a/vendor/egulias/email-validator/src/Validation/DNSGetRecordWrapper.php
+++ b/vendor/egulias/email-validator/src/Validation/DNSGetRecordWrapper.php
@@ -25,4 +25,4 @@ static function (int $errorLevel, string $errorMessage): ?bool {
             restore_error_handler();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Validation/DNSRecords.php b/vendor/egulias/email-validator/src/Validation/DNSRecords.php
index afda213cf5087aebcc8f320fd47d95f3673774f9..b026a2df9363468cb0398a856208eac5a6a68bfc 100644
--- a/vendor/egulias/email-validator/src/Validation/DNSRecords.php
+++ b/vendor/egulias/email-validator/src/Validation/DNSRecords.php
@@ -32,4 +32,4 @@ public function withError() : bool
     }
 
 
-}
\ No newline at end of file
+}
diff --git a/vendor/egulias/email-validator/src/Validation/Exception/EmptyValidationList.php b/vendor/egulias/email-validator/src/Validation/Exception/EmptyValidationList.php
index ee7c41aa0333b95362d15ae16ccf1fb13e0a454b..4a031b163ff4173d91e7240ffaf2650f18df76d2 100644
--- a/vendor/egulias/email-validator/src/Validation/Exception/EmptyValidationList.php
+++ b/vendor/egulias/email-validator/src/Validation/Exception/EmptyValidationList.php
@@ -9,7 +9,7 @@ class EmptyValidationList extends \InvalidArgumentException
     /**
     * @param int $code
     */
-    public function __construct($code = 0, Exception $previous = null)
+    public function __construct($code = 0, ?Exception $previous = null)
     {
         parent::__construct("Empty validation list is not allowed", $code, $previous);
     }
diff --git a/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php b/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php
index 90d4f0d0e46c0915d45379e711b20a8611cae3ff..abafe752afc4df859e5e452d54bc65d75190d509 100644
--- a/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php
+++ b/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php
@@ -13,13 +13,13 @@ class MultipleValidationWithAnd implements EmailValidation
      * If one of validations fails, the remaining validations will be skipped.
      * This means MultipleErrors will only contain a single error, the first found.
      */
-    const STOP_ON_ERROR = 0;
+    public const STOP_ON_ERROR = 0;
 
     /**
      * All of validations will be invoked even if one of them got failure.
      * So MultipleErrors will contain all causes.
      */
-    const ALLOW_ALL_ERRORS = 1;
+    public const ALLOW_ALL_ERRORS = 1;
 
     /**
      * @var EmailValidation[]
diff --git a/vendor/egulias/email-validator/src/Warning/AddressLiteral.php b/vendor/egulias/email-validator/src/Warning/AddressLiteral.php
index 77e70f7f554d17701fff70bcf07cc56abe009bd1..474ff0e75ced24026ef197120ed10f2d003e1ba9 100644
--- a/vendor/egulias/email-validator/src/Warning/AddressLiteral.php
+++ b/vendor/egulias/email-validator/src/Warning/AddressLiteral.php
@@ -4,7 +4,7 @@
 
 class AddressLiteral extends Warning
 {
-    const CODE = 12;
+    public const CODE = 12;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php b/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php
index be43bbe6f622be1390d8f7db8b1382cf8fd4dbaf..8bac12b1ea96a9f4887e9b26b2ae39b3668dcafd 100644
--- a/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php
+++ b/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php
@@ -4,7 +4,7 @@
 
 class CFWSNearAt extends Warning
 {
-    const CODE = 49;
+    public const CODE = 49;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php b/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php
index dea3450ec8ee7b714e41d06fcb2f767065e4a80b..ba57601c877ce369a77bd55b70487eff0a05fff9 100644
--- a/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php
+++ b/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php
@@ -4,7 +4,7 @@
 
 class CFWSWithFWS extends Warning
 {
-    const CODE = 18;
+    public const CODE = 18;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/Comment.php b/vendor/egulias/email-validator/src/Warning/Comment.php
index 704c2908b9a7d4f3f7cffde6871b5713ebd756a0..6508295e338f9fddac2051079f176982d4e69a3d 100644
--- a/vendor/egulias/email-validator/src/Warning/Comment.php
+++ b/vendor/egulias/email-validator/src/Warning/Comment.php
@@ -4,7 +4,7 @@
 
 class Comment extends Warning
 {
-    const CODE = 17;
+    public const CODE = 17;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php b/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php
index ad43bd7c97001dd3f7ad6aa712c591d114c6e21e..a2578076e740e267d0cb015d806399a7578474bc 100644
--- a/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php
+++ b/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php
@@ -4,7 +4,7 @@
 
 class DeprecatedComment extends Warning
 {
-    const CODE = 37;
+    public const CODE = 37;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/DomainLiteral.php b/vendor/egulias/email-validator/src/Warning/DomainLiteral.php
index 6f36b5e293d720a819546002ba4b93577ae2918d..034388c47bca576cf79b1825f41f761f53523d51 100644
--- a/vendor/egulias/email-validator/src/Warning/DomainLiteral.php
+++ b/vendor/egulias/email-validator/src/Warning/DomainLiteral.php
@@ -4,7 +4,7 @@
 
 class DomainLiteral extends Warning
 {
-    const CODE = 70;
+    public const CODE = 70;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/EmailTooLong.php b/vendor/egulias/email-validator/src/Warning/EmailTooLong.php
index 497309dbb0f038afa12db6eca839816d7063ebeb..d25ad123453deeb222312db464bc5e5935999887 100644
--- a/vendor/egulias/email-validator/src/Warning/EmailTooLong.php
+++ b/vendor/egulias/email-validator/src/Warning/EmailTooLong.php
@@ -6,7 +6,7 @@
 
 class EmailTooLong extends Warning
 {
-    const CODE = 66;
+    public const CODE = 66;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php b/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php
index ba2fcc01b83c7b36d19588cc1dfab842b83b54c4..3ecd5bcca6eaef49fde953c9398256406502cad6 100644
--- a/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php
+++ b/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php
@@ -4,7 +4,7 @@
 
 class IPV6BadChar extends Warning
 {
-    const CODE = 74;
+    public const CODE = 74;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php b/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php
index 41afa78c63f1212f9c058c845d3643adc6510b72..3f0c2f2df118cd6292a82f2a2bc5f9b829119e29 100644
--- a/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php
+++ b/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php
@@ -4,7 +4,7 @@
 
 class IPV6ColonEnd extends Warning
 {
-    const CODE = 77;
+    public const CODE = 77;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php b/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php
index 1bf754e30cd206254de8b4c5cc4f5e1f4bcc0e60..742fb3bd12034cb67def8b29cf4e57bac7145a38 100644
--- a/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php
+++ b/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php
@@ -4,7 +4,7 @@
 
 class IPV6ColonStart extends Warning
 {
-    const CODE = 76;
+    public const CODE = 76;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php b/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php
index d752caaa19d954de81ea397b4b326605bea214e3..59c3037a50a3b5119bc66b454fc8697a0af46fd7 100644
--- a/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php
+++ b/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php
@@ -4,7 +4,7 @@
 
 class IPV6Deprecated extends Warning
 {
-    const CODE = 13;
+    public const CODE = 13;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php b/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php
index 4f823949962cee50b7b6225f6f0698adb604678b..d40660264fbf18513f65fc41182a302322adf2bb 100644
--- a/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php
+++ b/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php
@@ -4,7 +4,7 @@
 
 class IPV6DoubleColon extends Warning
 {
-    const CODE = 73;
+    public const CODE = 73;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php b/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php
index a59d317f28745710d61b7da44a2883234ac7bf8c..551bc3af11758f834ec02b30a60b7a9cf7009019 100644
--- a/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php
+++ b/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php
@@ -4,7 +4,7 @@
 
 class IPV6GroupCount extends Warning
 {
-    const CODE = 72;
+    public const CODE = 72;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php b/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php
index 936274c13eea0156633cd152f90e5b5938062481..7f8a410a8a8df2a191a772bbe9a85fa756fc5688 100644
--- a/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php
+++ b/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php
@@ -4,7 +4,7 @@
 
 class IPV6MaxGroups extends Warning
 {
-    const CODE = 75;
+    public const CODE = 75;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/LocalTooLong.php b/vendor/egulias/email-validator/src/Warning/LocalTooLong.php
index 0d08d8b35c612f12b01b646e69c2785a71843e10..b46b874c0caec15ef7143d7c93b61c1a0e146e3a 100644
--- a/vendor/egulias/email-validator/src/Warning/LocalTooLong.php
+++ b/vendor/egulias/email-validator/src/Warning/LocalTooLong.php
@@ -4,8 +4,8 @@
 
 class LocalTooLong extends Warning
 {
-    const CODE = 64;
-    const LOCAL_PART_LENGTH = 64;
+    public const CODE = 64;
+    public const LOCAL_PART_LENGTH = 64;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php b/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php
index b3c21a1f3bf7b90042474fd509d8bf12d06bc9ef..bddb96be7bf368d636a12419667679de519dc241 100644
--- a/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php
+++ b/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php
@@ -4,7 +4,7 @@
 
 class NoDNSMXRecord extends Warning
 {
-    const CODE = 6;
+    public const CODE = 6;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php b/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php
index 10f19e33409ab4fe38e7357ceef87d18aa0f5d27..412fd499e52b16b1ab4d747b8d197e8bd0945e61 100644
--- a/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php
+++ b/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php
@@ -4,7 +4,7 @@
 
 class ObsoleteDTEXT extends Warning
 {
-    const CODE = 71;
+    public const CODE = 71;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/QuotedPart.php b/vendor/egulias/email-validator/src/Warning/QuotedPart.php
index 36a4265a515f9b442df57c4e2385dddea5d3fec9..afa1f9ea24d10a66e4dfb5f2fd0f13c007ed19e7 100644
--- a/vendor/egulias/email-validator/src/Warning/QuotedPart.php
+++ b/vendor/egulias/email-validator/src/Warning/QuotedPart.php
@@ -4,7 +4,7 @@
 
 class QuotedPart extends Warning
 {
-    const CODE = 36;
+    public const CODE = 36;
 
     /**
      * @param scalar $prevToken
diff --git a/vendor/egulias/email-validator/src/Warning/QuotedString.php b/vendor/egulias/email-validator/src/Warning/QuotedString.php
index 817e4e84b07968c25ecf6736b038d8b9af7edbcb..c152ec2e23c331d083fbc1ce39107a32de7eafa0 100644
--- a/vendor/egulias/email-validator/src/Warning/QuotedString.php
+++ b/vendor/egulias/email-validator/src/Warning/QuotedString.php
@@ -4,7 +4,7 @@
 
 class QuotedString extends Warning
 {
-    const CODE = 11;
+    public const CODE = 11;
 
     /**
      * @param scalar $prevToken
diff --git a/vendor/egulias/email-validator/src/Warning/TLD.php b/vendor/egulias/email-validator/src/Warning/TLD.php
index 2338b9f442a5cd2f5f1f45dc71ad11bbf2c455fc..10cec28164309e7ce8e9a5f96967f4bd577c1efe 100644
--- a/vendor/egulias/email-validator/src/Warning/TLD.php
+++ b/vendor/egulias/email-validator/src/Warning/TLD.php
@@ -4,7 +4,7 @@
 
 class TLD extends Warning
 {
-    const CODE = 9;
+    public const CODE = 9;
 
     public function __construct()
     {
diff --git a/vendor/egulias/email-validator/src/Warning/Warning.php b/vendor/egulias/email-validator/src/Warning/Warning.php
index a0158a2cd4183d8442c44b1ff576a29d21eed070..8b39d64485c24f34d1c02506680d7730b0a8d066 100644
--- a/vendor/egulias/email-validator/src/Warning/Warning.php
+++ b/vendor/egulias/email-validator/src/Warning/Warning.php
@@ -4,7 +4,7 @@
 
 abstract class Warning
 {
-    const CODE = 0;
+    public const CODE = 0;
 
     /**
      * @var string
diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php
index 53f4461d0df80daf77ffc3e381203b9e158a0774..890f5310637602c646bbda5a6b8cb31baa1fe625 100644
--- a/vendor/symfony/var-dumper/Caster/Caster.php
+++ b/vendor/symfony/var-dumper/Caster/Caster.php
@@ -47,7 +47,7 @@ public static function castObject(object $obj, string $class, bool $hasDebugInfo
         if ($hasDebugInfo) {
             try {
                 $debugInfo = $obj->__debugInfo();
-            } catch (\Exception $e) {
+            } catch (\Throwable $e) {
                 // ignore failing __debugInfo()
                 $hasDebugInfo = false;
             }
diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
index 274ee0d98f7dadd6e33cd25282c15d7b02a021e4..5c644053ad136cdf17016dbabda696ce37c9cbbc 100644
--- a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
+++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
@@ -197,7 +197,7 @@ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, arra
         self::addMap($a, $c, [
             'returnsReference' => 'returnsReference',
             'returnType' => 'getReturnType',
-            'class' => 'getClosureScopeClass',
+            'class' => \PHP_VERSION_ID >= 80111 ? 'getClosureCalledClass' : 'getClosureScopeClass',
             'this' => 'getClosureThis',
         ]);
 
diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php
index a034dddb989b458bfb9e6f7552f742daf93d6e31..f4e5746f15e471dc04fc3b78ceaba200e4a2f7bf 100644
--- a/vendor/symfony/var-exporter/Internal/Exporter.php
+++ b/vendor/symfony/var-exporter/Internal/Exporter.php
@@ -149,6 +149,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
                 }
                 if (null !== $sleep) {
                     if (!isset($sleep[$n]) || ($i && $c !== $class)) {
+                        unset($arrayValue[$name]);
                         continue;
                     }
                     $sleep[$n] = false;
@@ -164,6 +165,9 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
                     }
                 }
             }
+            if (method_exists($class, '__unserialize')) {
+                $properties = $arrayValue;
+            }
 
             prepare_value:
             $objectsPool[$value] = [$id = \count($objectsPool)];
diff --git a/vendor/twig/twig/.github/dependabot.yml b/vendor/twig/twig/.github/dependabot.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5ace4600a1f26e6892982f3e2f069ebfab108d87
--- /dev/null
+++ b/vendor/twig/twig/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/vendor/twig/twig/.github/workflows/ci.yml b/vendor/twig/twig/.github/workflows/ci.yml
index 88aebc164d73372231838fc614fd6783e7dc745b..3f49259ca9add1a5def67fd26988482db52fec41 100644
--- a/vendor/twig/twig/.github/workflows/ci.yml
+++ b/vendor/twig/twig/.github/workflows/ci.yml
@@ -29,11 +29,12 @@ jobs:
                     - '7.4'
                     - '8.0'
                     - '8.1'
+                    - '8.2'
                 experimental: [false]
 
         steps:
             - name: "Checkout code"
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
 
             - name: "Install PHP with extensions"
               uses: shivammathur/setup-php@v2
@@ -74,6 +75,7 @@ jobs:
                     - '7.4'
                     - '8.0'
                     - '8.1'
+                    - '8.2'
                 extension:
                     - 'extra/cssinliner-extra'
                     - 'extra/html-extra'
@@ -86,7 +88,7 @@ jobs:
 
         steps:
             - name: "Checkout code"
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
 
             - name: "Install PHP with extensions"
               uses: shivammathur/setup-php@v2
@@ -131,7 +133,7 @@ jobs:
 
         steps:
             - name: "Checkout code"
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
 
             - name: "Install PHP with extensions"
               uses: shivammathur/setup-php@v2
diff --git a/vendor/twig/twig/.github/workflows/documentation.yml b/vendor/twig/twig/.github/workflows/documentation.yml
index ee83b588749cbe1088dff59cb47f92c7e4de0e65..f2f46fc6d6e41df54589a73596e698d843e2afa4 100644
--- a/vendor/twig/twig/.github/workflows/documentation.yml
+++ b/vendor/twig/twig/.github/workflows/documentation.yml
@@ -18,22 +18,22 @@ jobs:
 
         steps:
             -   name: "Checkout code"
-                uses: actions/checkout@v2
+                uses: actions/checkout@v3
 
             -   name: "Set-up PHP"
                 uses: shivammathur/setup-php@v2
                 with:
-                    php-version: 8.1
+                    php-version: 8.2
                     coverage: none
                     tools: "composer:v2"
 
             -   name: Get composer cache directory
                 id: composercache
                 working-directory: doc/_build
-                run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+                run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
 
             -   name: Cache dependencies
-                uses: actions/cache@v2
+                uses: actions/cache@v3
                 with:
                     path: ${{ steps.composercache.outputs.dir }}
                     key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
@@ -54,7 +54,7 @@ jobs:
 
         steps:
             - name: "Checkout code"
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
 
             - name: "Run DOCtor-RST"
               uses: docker://oskarstark/doctor-rst
diff --git a/vendor/twig/twig/CHANGELOG b/vendor/twig/twig/CHANGELOG
index 51712223b6f26d0b3f356084b8e62b0840a00cf3..db87b236046a3eaedc4e80018141c8d0acdc3c87 100644
--- a/vendor/twig/twig/CHANGELOG
+++ b/vendor/twig/twig/CHANGELOG
@@ -1,3 +1,9 @@
+# 2.15.4 (2022-12-27)
+
+ * Fix optimizing closures callbacks
+ * Add a better exception when getting an undefined constant via `constant`
+ * Fix `if` nodes when outside of a block and with an empty body
+
 # 2.15.3 (2022-09-28)
 
  * Fix a security issue on filesystem loader (possibility to load a template outside a configured directory)
diff --git a/vendor/twig/twig/src/Environment.php b/vendor/twig/twig/src/Environment.php
index 13ce511f4fe44054e3345488bfb5ccf4d54d1d56..96f725106f39d64aa89fe3a60c1cc12ff04f5ee9 100644
--- a/vendor/twig/twig/src/Environment.php
+++ b/vendor/twig/twig/src/Environment.php
@@ -38,11 +38,11 @@
  */
 class Environment
 {
-    public const VERSION = '2.15.3';
-    public const VERSION_ID = 21503;
+    public const VERSION = '2.15.4';
+    public const VERSION_ID = 21504;
     public const MAJOR_VERSION = 2;
     public const MINOR_VERSION = 15;
-    public const RELEASE_VERSION = 3;
+    public const RELEASE_VERSION = 4;
     public const EXTRA_VERSION = '';
 
     private $charset;
diff --git a/vendor/twig/twig/src/Extension/CoreExtension.php b/vendor/twig/twig/src/Extension/CoreExtension.php
index 6ac36108137e8eef6380cc3a86d58c5a5272ded6..5c4087ec217f7f042ab04165f332fa81b2e25766 100644
--- a/vendor/twig/twig/src/Extension/CoreExtension.php
+++ b/vendor/twig/twig/src/Extension/CoreExtension.php
@@ -1329,6 +1329,10 @@ function twig_constant($constant, $object = null)
         $constant = \get_class($object).'::'.$constant;
     }
 
+    if (!\defined($constant)) {
+        throw new RuntimeError(sprintf('Constant "%s" is undefined.', $constant));
+    }
+
     return \constant($constant);
 }
 
diff --git a/vendor/twig/twig/src/Node/Expression/CallExpression.php b/vendor/twig/twig/src/Node/Expression/CallExpression.php
index 79e9defdf0fb2821b8911dbcbda5d7696a891369..aeb38c42f8983400309a72f786c0ff58285c9f05 100644
--- a/vendor/twig/twig/src/Node/Expression/CallExpression.php
+++ b/vendor/twig/twig/src/Node/Expression/CallExpression.php
@@ -304,7 +304,9 @@ private function reflectCallable($callable)
         if ($object = $r->getClosureThis()) {
             $callable = [$object, $r->name];
             $callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name;
-        } elseif ($class = $r->getClosureScopeClass()) {
+        } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
+            $callableName = $class->name.'::'.$r->name;
+        } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
             $callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
         } else {
             $callable = $callableName = $r->name;
diff --git a/vendor/twig/twig/src/Node/IfNode.php b/vendor/twig/twig/src/Node/IfNode.php
index 8ba23ddb60a5081298aeb94ed004aa06f94d5341..e74ca523b03158f8afc77ebb086c037d70343d7f 100644
--- a/vendor/twig/twig/src/Node/IfNode.php
+++ b/vendor/twig/twig/src/Node/IfNode.php
@@ -50,8 +50,11 @@ public function compile(Compiler $compiler)
                 ->subcompile($this->getNode('tests')->getNode($i))
                 ->raw(") {\n")
                 ->indent()
-                ->subcompile($this->getNode('tests')->getNode($i + 1))
             ;
+            // The node might not exists if the content is empty
+            if ($this->getNode('tests')->hasNode($i + 1)) {
+                $compiler->subcompile($this->getNode('tests')->getNode($i + 1));
+            }
         }
 
         if ($this->hasNode('else')) {
diff --git a/web/modules/scheduler/.eslintignore b/web/modules/scheduler/.eslintignore
deleted file mode 100644
index a75123a85918ebe5af37836409061755df66a71d..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/.eslintignore
+++ /dev/null
@@ -1,7 +0,0 @@
-# These files fail eslint standards. In due course they will be fixed and then
-# removed from this ignore file.
-# See https://www.drupal.org/project/scheduler/issues/3314451
-drupalci.yml
-js/scheduler_default_time.js
-js/scheduler_default_time_8x.js
-js/scheduler_vertical_tabs.js
\ No newline at end of file
diff --git a/web/modules/scheduler/.eslintrc.json b/web/modules/scheduler/.eslintrc.json
deleted file mode 100644
index 009abb96c91fbe07fbc2fe924112bca6de473a61..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/.eslintrc.json
+++ /dev/null
@@ -1,7 +0,0 @@
-// This file does not strictly need to exist in contrib, because eslint config
-// files placed futher up in the directory structure will be merged and
-// inherited. The top-level .eslintrc.json extends ./core/.eslintrc.json.
-// Drupalci eslint does not have a 'sniff all files' argument, but having this
-// in a patch or MR will cause all files to be checked.
-// See https://www.drupal.org/project/scheduler/issues/3314451
-{}
diff --git a/web/modules/scheduler/.prettierignore b/web/modules/scheduler/.prettierignore
deleted file mode 100644
index 47a89b805332bc0a733f0adca9901ab75b16e645..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/.prettierignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# Drupal ./core/.prettierignore has *.yml so contrib can do likewise.
-*.yml
diff --git a/web/modules/scheduler/.prettierrc.json b/web/modules/scheduler/.prettierrc.json
deleted file mode 100644
index 7156dade30ff3b4024d03f3eac6ab3cafff1b402..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/.prettierrc.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "_comment": "Duplicate what is in core/.prettierrc.json",
-  "printWidth": 80,
-  "semi": true,
-  "singleQuote": true,
-  "trailingComma": "all"
-}
diff --git a/web/modules/scheduler/.travis.yml b/web/modules/scheduler/.travis.yml
index af1c8de9b508b3a9e646bbf71c6c3b9faf2399a5..623d2f8b3133122c93822a846bf074bae4684c92 100644
--- a/web/modules/scheduler/.travis.yml
+++ b/web/modules/scheduler/.travis.yml
@@ -17,40 +17,29 @@ env:
 jobs:
   fast_finish: true
   include:
-    - php: 7.4
+    - php: 7.2
+      env: DRUPAL_CORE=8.9.x
+    - php: 7.3
       env:
-        - DRUPAL_CORE=9.5.x
-        # Run the Node and Product tests, with Rules included.
-        - NODE=YES
-        - PRODUCT=YES
-        - RULES=YES
-        # ---- Remaining self deprecation notices (0)
-        # ---- Other deprecation notices (0)
-        - DEPRECATIONS=0
-    - php: 8.1
+        - DRUPAL_CORE=9.0.x
+        # --- Remaining self deprecation notices (0)
+        # --- Remaining direct deprecation notices (0)
+    - php: 7.4
       env:
-        - DRUPAL_CORE=9.5.x
-        - MEDIA=YES
-        - TAXONOMY=YES
-        # ---- Unsilenced deprecation notices (6)
-        #    6 strlen(): Passing null to parameter #1 ($string) is deprecated (SchedulerDrushTest)
-        # ---- Remaining self deprecation notices (0)
-        # ---- Other deprecation notices (0)
-        - DEPRECATIONS=6
-    - php: 8.1
+        - DRUPAL_CORE=9.1.x
+        # --- Remaining self deprecation notices (180)
+        # 141 EventDispatcher (in Rules module)
+        #  27 Drupal\Core\Plugin\ContextAwarePluginBase (in Rules module)
+        #  12 StatementWrapper::fetchColumn
+        # --- Remaining direct deprecation notices (0)
+        - DEPRECATIONS=180
+    - php: 7.4
       env:
-        # Run the Node tests only.
-        - DRUPAL_CORE=10.0.x
-        - NODE=YES
-        # ---- Remaining self deprecation notices (4)
-        #    2 Behat\Mink\Element\ElementInterface::getText() might add "string"
-        #    2 Behat\Mink\Element\ElementInterface::waitFor()" might add "mixed"
-        # ---- Remaining direct deprecation notices (3)
-        #    1 PHPUnit\TextUI\DefaultResultPrinter class is considered internal
-        #    2 Drupal\Tests\Listeners\DrupalListener
-        # ---- Other deprecation notices (2)
-        #    2 PHPUnit\Framework\TestCase::addWarning() method is considered internal
-        - DEPRECATIONS=9
+        # Have a second 9.1 run which excludes the Rules tests.
+        - DRUPAL_CORE=9.1.x
+        # --- Remaining self deprecation notices (0)
+        # --- Remaining direct deprecation notices (0)
+        - RULES=NO
 
 # Be sure to cache composer downloads.
 cache:
@@ -59,8 +48,13 @@ cache:
 
 before_script:
   # At job start-up Composer is installed at 1.8.4 then self-update is run. From
-  # 24 October 2020 this bumped the version to Composer 2.
+  # 24 October 2020 this bumped the version to Composer 2. Drupal Core 8.8 has
+  # plugins that only run with composer-plugin-api ^1.0 so revert to Composer 1.
+  - if [ "$DRUPAL_CORE" == "8.8.x" ]; then
+      travis_retry composer self-update --1;
+    fi
   - composer --version
+
   - echo $MODULE
 
   # Remove Xdebug as we don't need it and it causes
@@ -68,8 +62,8 @@ before_script:
   # We also don't care if that file exists or not on PHP 7.
   - phpenv config-rm xdebug.ini || true
 
-  # Navigate up out of $TRAVIS_BUILD_DIR to prevent blown stack on recursive module lookup.
-  - pwd
+  # Navigate out of module directory to prevent blown stack by recursive module
+  # lookup.
   - cd ..
 
   # Create database.
@@ -88,11 +82,10 @@ before_script:
   - mkdir $DRUPAL_ROOT/modules/$MODULE
   - cp -R $TRAVIS_BUILD_DIR/* $DRUPAL_ROOT/modules/$MODULE/
 
-  # Get the latest dev versions of some of the test dependency modules.
-  - travis_retry git clone --branch 8.x-1.x --depth 1 https://git.drupalcode.org/project/workbench_moderation.git modules/workbench_moderation
-  - travis_retry git clone --branch 8.x-1.x --depth 1 https://git.drupalcode.org/project/workbench_moderation_actions.git modules/workbench_moderation_actions
+  # Get the latest dev versions of the test dependency modules.
+  - travis_retry git clone --branch 4.x     --depth 1 https://git.drupalcode.org/project/devel.git modules/devel
   - |
-    if [ "$RULES" == "YES" ]; then
+    if [ "$RULES" != "NO" ]; then
       echo "Installing Rules and Typed Data ..."
       travis_retry git clone --branch 8.x-3.x --depth 1 https://git.drupalcode.org/project/rules.git modules/rules
       travis_retry git clone --branch 8.x-1.x --depth 1 https://git.drupalcode.org/project/typed_data.git modules/typed_data
@@ -101,18 +94,13 @@ before_script:
   # Install the site dependencies via Composer.
   - travis_retry composer install
 
-  # Install the other testing dependencies via Composer.
-  - travis_retry composer require drupal/devel:"^4 || ^5"
-  - travis_retry composer require drush/drush:"^9 || ^10 || ^11"
-  - travis_retry composer require drupal/commerce
+  # Install drush. COMPOSER_MEMORY_LIMIT=-1 fixes the 'Allowed memory exhausted'
+  # problem when running at core 8.8.
+  - COMPOSER_MEMORY_LIMIT=-1 travis_retry composer require drush/drush:"^9.0 || ^10.0"
 
   # Coder is already installed as part of composer install. We just need to set
-  # the installed_paths to pick up the Drupal standards. This is only for Coder
-  # up to version 8.3.13. From 8.3.14 onwards this is done at install time.
-  - |
-    if [[ "$DRUPAL_CORE" == "8.9.x" || "$DRUPAL_CORE" == "9.2.x" || "$DRUPAL_CORE" == "9.3.x" ]]; then
-      $DRUPAL_ROOT/vendor/bin/phpcs --config-set installed_paths $DRUPAL_ROOT/vendor/drupal/coder/coder_sniffer
-    fi
+  # the installed_paths to pick up the Drupal standards.
+  - $DRUPAL_ROOT/vendor/bin/phpcs --config-set installed_paths $DRUPAL_ROOT/vendor/drupal/coder/coder_sniffer
 
   # Start a web server on port 8888, run in the background.
   - php -S localhost:8888 &
@@ -125,40 +113,18 @@ before_script:
   - echo $SYMFONY_DEPRECATIONS_HELPER
 
 script:
-  - echo "NODE=$NODE MEDIA=$MEDIA PRODUCT=$PRODUCT TAXONOMY=$TAXONOMY RULES=$RULES"
-  # By default the specific entity type tests will be excluded unless explicitly
-  # included via a YES variable value.
-  - EXCLUDE=()
-  - if [ "$NODE" != "YES" ]; then EXCLUDE+=('node|HooksLegacy|Multilingual|WorkbenchModeration|Migrate'); fi
-  - if [ "$MEDIA" != "YES" ]; then EXCLUDE+=('media'); fi
-  - if [ "$PRODUCT" != "YES" ]; then EXCLUDE+=('product'); fi
-  - if [ "$TAXONOMY" != "YES" ]; then EXCLUDE+=('taxonomy'); fi
-  - if [ "$RULES" != "YES" ]; then EXCLUDE+=('rules'); fi
-  - if [ "$DRUPAL_CORE" == "10.0.x" ]; then EXCLUDE+=('HooksLegacy|WorkbenchModeration'); fi
-  - EXCLUDE=${EXCLUDE[@]}     # create a space delimited string from array
-
   # Run the PHPUnit tests.
   - cd $DRUPAL_ROOT
-  - |
-    if [ "$EXCLUDE" != "" ]; then
-      echo "Running tests excluding $EXCLUDE ..."
-      export FILTER="/^((?!(${EXCLUDE// /|})).)*$/i"   # use parameter expansion to substitute spaces with |
-      echo "FILTER=$FILTER"
-      ./vendor/bin/phpunit -c ./core/phpunit.xml.dist --debug ./modules/$MODULE/ --filter "$FILTER"
+  - if [ "$RULES" == "NO" ]; then
+      ./vendor/bin/phpunit -c ./core/phpunit.xml.dist --debug ./modules/$MODULE/tests/ --filter '/^((?!rules).)*$/i';
     else
-      echo "Running all tests ..."
-      ./vendor/bin/phpunit -c ./core/phpunit.xml.dist --debug ./modules/$MODULE/
+      ./vendor/bin/phpunit -c ./core/phpunit.xml.dist --debug ./modules/$MODULE/tests/;
     fi
-  # Display the parameters again at the end of the test run.
-  - echo "NODE=$NODE MEDIA=$MEDIA PRODUCT=$PRODUCT TAXONOMY=$TAXONOMY RULES=$RULES"
-  - echo "EXCLUDE=$EXCLUDE"
 
   # Check for coding standards. First show the versions.
   - composer show drupal/coder | egrep 'name |vers'
   - composer show squizlabs/php_codesniffer | egrep 'name |vers'
   - $DRUPAL_ROOT/vendor/bin/phpcs --version
-  - $DRUPAL_ROOT/vendor/bin/phpcs --config-show installed_paths
-
 
   # Change into $MODULE directory to avoid having to add --standard=$DRUPAL_ROOT/modules/$MODULE/phpcs.xml.dist
   - cd $DRUPAL_ROOT/modules/$MODULE
@@ -168,4 +134,4 @@ script:
   - $DRUPAL_ROOT/vendor/bin/phpcs -e
 
   # Show the violations in detail, plus summary and source report.
-  - $DRUPAL_ROOT/vendor/bin/phpcs . --report-full --report-summary --report-source -s;
+  - $DRUPAL_ROOT/vendor/bin/phpcs . --report-full --report-summary --report-source -s
diff --git a/web/modules/scheduler/.tugboat/config.yml b/web/modules/scheduler/.tugboat/config.yml
index 811f7e86d75f5fcad1e2834d7de9549043d1d9a8..f648f4e3eb43f1bc67ca903ab33b44f970ef7a6c 100644
--- a/web/modules/scheduler/.tugboat/config.yml
+++ b/web/modules/scheduler/.tugboat/config.yml
@@ -1,96 +1,51 @@
 services:
   php:
-    image: q0rban/tugboat-drupal:10.0
+    image: q0rban/tugboat-drupal:9.0
     default: true
     http: false
     depends: mysql
     commands:
-      init: |
-        # Install the bcmath extension, required for commerce_product
-        docker-php-ext-install bcmath
-        # JPEG support is not included by default, but it is needed when generating content.
-        docker-php-ext-configure gd --enable-gd --with-jpeg
-        docker-php-ext-install gd
-
       update: |
         set -eux
-
         # Check out a branch using the unique Tugboat ID for this repository, to
         # ensure we don't clobber an existing branch.
         git checkout -b $TUGBOAT_REPO_ID
-
         # Composer is hungry. You need a Tugboat project with a pretty sizeable
         # chunk of memory.
         export COMPOSER_MEMORY_LIMIT=-1
-
         # This is an environment variable we added in the Dockerfile that
         # provides the path to Drupal composer root (not the web root).
         cd $DRUPAL_COMPOSER_ROOT
-
         # We configure the Drupal project to use the checkout of the module as a
         # Composer package repository.
         composer config repositories.tugboat vcs $TUGBOAT_ROOT
-
         # Now we can require this module, specifing the branch name we created
         # above that uses the $TUGBOAT_REPO_ID environment variable.
-        composer require drupal/scheduler:dev-$TUGBOAT_REPO_ID
-
+        composer require drupal/scheduler:dev-$TUGBOAT_REPO_ID --dev
+        composer require drupal/devel_generate
         # Install Drupal on the site.
         vendor/bin/drush \
           --yes \
           --db-url=mysql://tugboat:tugboat@mysql:3306/tugboat \
-          --site-name="${TUGBOAT_PREVIEW_NAME}" \
+          --site-name="Scheduler issue ${TUGBOAT_PREVIEW_NAME}" \
           --account-pass=admin \
           site:install standard
-
-        # Show site status and GD image support status.
-        vendor/bin/drush status-report
-        vendor/bin/drush php:eval 'phpinfo()' | grep GD
-        vendor/bin/drush php:eval 'print "imagepng() " . (function_exists("imagepng") ? "--yes\n" : "--no\n"); '
-        vendor/bin/drush php:eval 'print "imagejpeg() " . (function_exists("imagejpeg") ? "--yes\n" : "--no\n"); '
-        vendor/bin/drush php:eval 'print_r(gd_info());'
-
-        # Commerce 2.29 needs inline_entity_form which does not have a stable
-        # version as at March 2022. Therefore get that package here allowing RC
-        # to avoid the "does not match your minimum-stability" problem.
-        composer require drupal/inline_entity_form ^1.0@RC
-        composer require drupal/commerce
-
-        # Get other useful modules.
-        composer require drupal/devel_generate
-
-        # These modules are not compatible with Drupal 10 (yet) so only get them
-        # when running Drupal 9.
-        vendor/bin/drush core:status | awk "NR==1{print \$2\$3\$4}"
-        vendor/bin/drush core:status | awk "NR==1{print \$2\$3\$4}" | \
-          grep version:9 && composer require drupal/module_filter drupal/workbench_moderation \
-          drupal/workbench_moderation_actions:1.x-dev drupal/admin_toolbar
-
         # Set up the files directory permissions.
         mkdir -p $DRUPAL_DOCROOT/sites/default/files
         chgrp -R www-data $DRUPAL_DOCROOT/sites/default/files
         chmod 2775 $DRUPAL_DOCROOT/sites/default/files
         chmod -R g+w $DRUPAL_DOCROOT/sites/default/files
-
         # Enable modules.
-        vendor/bin/drush --yes pm:enable scheduler devel devel_generate media commerce_product
-
-        # Enable modules that are only available at Drupal 9.
-        vendor/bin/drush core:status | awk "NR==1{print \$2\$3\$4}" | \
-          grep version:9 && vendor/bin/drush --yes pm:enable module_filter admin_toolbar
-
-        # Show versions.
+        vendor/bin/drush --yes pm:enable scheduler devel devel_generate
         composer show drupal/scheduler | egrep 'name |vers'
         vendor/bin/drush pml | grep scheduler
         composer show drupal/devel | egrep 'name |vers'
         vendor/bin/drush pml | grep devel
 
-        # Scheduler general settings.
-        vendor/bin/drush -y config-set scheduler.settings allow_date_only 1
-        vendor/bin/drush -y config-set scheduler.settings default_time '22:06:00'
+        # Scheduler settings
+        vendor/bin/drush config-set scheduler.settings allow_date_only 1 -y
+        vendor/bin/drush config-set scheduler.settings default_time '22:06:00' -y
         vendor/bin/drush config-get scheduler.settings
-
-        # Scheduler content settings.
         vendor/bin/drush -y config-set node.type.article third_party_settings.scheduler.publish_enable 1
         vendor/bin/drush -y config-set node.type.article third_party_settings.scheduler.unpublish_enable 1
         vendor/bin/drush -y config-set node.type.article third_party_settings.scheduler.expand_fieldset 'always'
@@ -98,93 +53,35 @@ services:
         vendor/bin/drush -y config-set node.type.article third_party_settings.scheduler.publish_past_date 'schedule'
         vendor/bin/drush config-get node.type.article third_party_settings
 
-        # Scheduler media settings.
-        vendor/bin/drush -y config-set media.type.image third_party_settings.scheduler.publish_enable 1
-        vendor/bin/drush -y config-set media.type.image third_party_settings.scheduler.unpublish_enable 1
-        vendor/bin/drush -y config-set media.type.image third_party_settings.scheduler.expand_fieldset 'always'
-        vendor/bin/drush -y config-set media.type.image third_party_settings.scheduler.fields_display_mode 'fieldset'
-        vendor/bin/drush -y config-set media.type.image third_party_settings.scheduler.publish_past_date 'schedule'
-        vendor/bin/drush config-get media.type.image third_party_settings
-
-        # Scheduler commerce product settings.
-        vendor/bin/drush -y config-set commerce_product.commerce_product_type.default third_party_settings.scheduler.publish_enable 1
-        vendor/bin/drush -y config-set commerce_product.commerce_product_type.default third_party_settings.scheduler.unpublish_enable 1
-        vendor/bin/drush -y config-set commerce_product.commerce_product_type.default third_party_settings.scheduler.publish_past_date 'schedule'
-        vendor/bin/drush -y config-set commerce_product.commerce_product_type.default third_party_settings.scheduler.expand_fieldset 'always'
-        vendor/bin/drush -y config-set commerce_product.commerce_product_type.default third_party_settings.scheduler.fields_display_mode 'fieldset'
-        vendor/bin/drush config-get commerce_product.commerce_product_type.default third_party_settings
-
-        # Media settings.
-        vendor/bin/drush -y config-set media.settings standalone_url 1
-        vendor/bin/drush config-get media.settings
-        vendor/bin/drush -y config-set field.field.media.image.field_media_image required 0
-        vendor/bin/drush config-get field.field.media.image.field_media_image
-
-        # Create roles for each of the scheduler user permissions.
-        vendor/bin/drush role-create 'my_content_editor' 'Content Editor'
-        vendor/bin/drush role-add-perm 'my_content_editor' 'schedule publishing of nodes'
+        # Create roles for each of the two scheduler user permissions.
+        vendor/bin/drush role-create 'content_editor' 'Content Editor'
+        vendor/bin/drush role-add-perm 'content_editor' 'schedule publishing of nodes'
         vendor/bin/drush role-create 'content_viewer' 'Content Viewer'
         vendor/bin/drush role-add-perm 'content_viewer' 'view scheduled content'
-        vendor/bin/drush role-create 'media_editor' 'Media Editor'
-        vendor/bin/drush role-add-perm 'media_editor' 'schedule publishing of media'
-        vendor/bin/drush role-create 'media_viewer' 'Media Viewer'
-        vendor/bin/drush role-add-perm 'media_viewer' 'view scheduled media'
-        vendor/bin/drush role-create 'product_editor' 'Product Editor'
-        vendor/bin/drush role-add-perm 'product_editor' 'schedule publishing of commerce_product'
-        vendor/bin/drush role-create 'product_viewer' 'Product Viewer'
-        vendor/bin/drush role-add-perm 'product_viewer' 'view scheduled commerce_product'
-
-        # Add some permissions for all authenticated users.
-        vendor/bin/drush role-add-perm 'authenticated' \
-          "create article content, edit any article content, delete any article content, \
-           access content overview, view own unpublished content, switch users"
-        vendor/bin/drush role-add-perm 'authenticated' \
-          "create media, update any media, delete any media, access media overview, view own unpublished media"
-        vendor/bin/drush role-add-perm 'authenticated' \
-          "create default commerce_product, update any default commerce_product, \
-           delete any default commerce_product, access commerce_product overview, \
-           view own unpublished commerce_product, administer commerce_store"
+        # Add permissions for all users.
+        vendor/bin/drush role-add-perm 'authenticated' "create article content, edit any article content, delete any article content, access content overview, view own unpublished content, switch users"
 
-        # Create users and give them roles.
-        vendor/bin/drush user-create 'Eddy content editor'
-        vendor/bin/drush user-add-role 'my_content_editor' 'Eddy content editor'
-        vendor/bin/drush user-create 'Vera content viewer'
-        vendor/bin/drush user-add-role 'content_viewer' 'Vera content viewer'
-        vendor/bin/drush user-create 'Madeline media editor'
-        vendor/bin/drush user-add-role 'media_editor' 'Madeline media editor'
-        vendor/bin/drush user-create 'Marvin media viewer'
-        vendor/bin/drush user-add-role 'media_viewer' 'Marvin media viewer'
-        vendor/bin/drush user-create 'Prodie product editor'
-        vendor/bin/drush user-add-role 'product_editor' 'Prodie product editor'
-        vendor/bin/drush user-create 'Proctor product viewer'
-        vendor/bin/drush user-add-role 'product_viewer' 'Proctor product viewer'
+        # Create users.
+        vendor/bin/drush user-create 'Eddy the editor'
+        vendor/bin/drush user-add-role 'content_editor' 'Eddy the editor'
+        vendor/bin/drush user-create 'Vera the viewer'
+        vendor/bin/drush user-add-role 'content_viewer' 'Vera the viewer'
 
         # Generate content.
-        vendor/bin/drush devel-generate-content 3 --bundles=article --authors=1,2 --verbose
-        vendor/bin/drush devel-generate-content 3 --bundles=page --authors=1,2 --verbose
-        vendor/bin/drush devel-generate-media 3 --media-types=document --verbose
-        vendor/bin/drush devel-generate-media 3 --media-types=image --verbose
+        vendor/bin/drush devel-generate-content 5 --bundles=article --authors=1,2 --verbose
 
         # @todo Place the 'Switch users' block in first sidebar.
-        # @todo Add 'content overview' and 'media overview' to tools menu.
-        # @todo Create a store for products. Then create some products.
-        # @todo Generate a vocabulary and some terms. Create users for taxonomy.
-        # @todo page entity type should not be promoted to front page.
-        # @todo Create a menu of links to the pages?
 
       build: |
         set -eux
-
         # Delete and re-check out this branch in case this is built from a Base Preview.
         git branch -D $TUGBOAT_REPO_ID && git checkout -b $TUGBOAT_REPO_ID || true
         export COMPOSER_MEMORY_LIMIT=-1
         cd $DRUPAL_COMPOSER_ROOT
         composer install --optimize-autoloader
-
         # Update this module, including all dependencies.
         composer update drupal/scheduler --with-all-dependencies
         vendor/bin/drush --yes updb
         vendor/bin/drush cache:rebuild
-
   mysql:
     image: tugboatqa/mariadb
diff --git a/web/modules/scheduler/README.md b/web/modules/scheduler/README.md
index 94b6961fbb2451403332588face26fc3ac6f6453..0e65c3ad0c90fcfbab705c094b3f1b79ff41c9ae 100644
--- a/web/modules/scheduler/README.md
+++ b/web/modules/scheduler/README.md
@@ -2,20 +2,18 @@
 
 [![Build Status](https://travis-ci.org/jonathan1055/scheduler.svg?branch=8.x-1.x)](https://travis-ci.org/jonathan1055/scheduler)
 
-Scheduler gives website editors the ability to schedule content to be published
+Scheduler gives content editors the ability to schedule nodes to be published
 and unpublished at specified dates and times in the future.
 
-Scheduler provides hooks and events for third-party modules to interact with the
-process during content editing and during cron publishing and unpublishing.
+Scheduler provides hooks and events for third-party modules to interact with
+the processing during node edit and during cron publishing and unpublishing.
 
-A plugin system allows support for any Drupal entity type that has the concept
-of a 'published' status. As at Scheduler version 2.0 Node content, Media
-entities, Commerce Products and Taxonomy Terms are supported.
+For a fuller description of the module, visit the [project page on Drupal.org](https://drupal.org/project/scheduler)
 
 ## Requirements
 
- * Scheduler uses the following Drupal 8 Core components: Actions, Datetime,
-   Field, Node, Text, Filter, User, System, Views.
+ * Scheduler uses the following Drupal 8 Core components:
+     Actions, Datetime, Field, Node, Text, Filter, User, System, Views.
 
  * There are no special requirements outside core.
 
@@ -36,65 +34,44 @@ entities, Commerce Products and Taxonomy Terms are supported.
      If you use core Content Moderation then you should also install this
      sub-module, contributed by the folks at [Thunder](https://www.drupal.org/thunder)
 
- * [Media](https://www.drupal.org/docs/8/core/modules/media):
-     Core media items can be scheduled for publishing and unpublishing.
-
- * [Commerce](https://www.drupal.org/project/commerce):
-     Commerce products can be scheduled for publishing and unpublishing.
-
- * [Taxonomy](https://www.drupal.org/docs/8/core/modules/taxonomy):
-     Core taxonomy terms can be scheduled for publishing and unpublishing.
-
 ## Installation
 
  * Install as you would normally install a contributed Drupal module. See:
      https://drupal.org/documentation/install/modules-themes/modules-8
      for further information.
 
- * The [Scheduler project page on Drupal.org](https://drupal.org/project/scheduler)
-   has information regarding versions and Core compatibility.
-
 ## Configuration
 
  * Configure user permissions via url /admin/people/permissions#module-scheduler
    or Administration » People » Permissions
 
-   - "Schedule publishing and unpublishing of {type}"
-
-     Users with this permission can enter dates and times for publishing and/or
-     unpublishing, when editing content of types which are Scheduler-enabled.
-
-   - "View scheduled {type}"
+   - View scheduled content list
 
      Users can always see their own scheduled content, via a tab on their user
      page. This permissions grants additional authority to see the full list of
      scheduled content by any author, providing the user also has the core
-     permission 'access content overview' or the equivalent for other entity
-     types.
+     permission 'access content overview'.
+
+   - Schedule content publication
 
-   - "Administer scheduler"
+     Users with this permission can enter dates and times for publishing and/or
+     unpublishing, when editing nodes of types which are Scheduler-enabled.
+
+   - Administer scheduler
 
      This permission allows the user to alter all Scheduler settings. It should
      therefore only be given to trusted admin roles.
 
  * Configure the Scheduler global options via /admin/config/content/scheduler
-   or Administration » Configuration » Content Authoring » Scheduler
-
-   - Basic settings: allow a date only and set a default time.
+   or Administration » Configuration » Content Authoring
 
-   - Lightweight Cron: This gives sites admins the granularity to run
-     Scheduler's functions only on more frequent crontab jobs than the full
-     Drupal cron run.
+   - Basic settings for date format, allowing date only, setting default time.
 
- * Configure the Scheduler settings per entity type:
-   - Administration » Structure » Content Types » Edit
-   - Administration » Structure » Media Types » Edit
-   - Administration » Commerce » Configuration » Product Types » Edit
-   - Administration » Structure » Taxonomy » Vocabulary » Edit
+   - Lightweight Cron, which gives sites admins the granularity to run
+     Scheduler's functions only, on more frequent crontab jobs.
 
- * The system status report at /admin/reports/status has a Scheduler Timecheck
-   section, giving details of the server time, default site time and current
-   user time.
+ * Configure the Scheduler settings per content type via /admin/structure/types
+     or Administration » Structure » Content Types » Edit
 
 ## Troubleshooting
 
diff --git a/web/modules/scheduler/composer.json b/web/modules/scheduler/composer.json
index d835ab3c546c324fcf0dcf6f8f4050688ae22b0e..7bddcde92320c726d97a61a45351b94c5d229463 100644
--- a/web/modules/scheduler/composer.json
+++ b/web/modules/scheduler/composer.json
@@ -6,9 +6,8 @@
     "homepage": "https://drupal.org/project/scheduler",
     "require-dev": {
         "drupal/rules": "^3",
-        "drush/drush": ">=9",
-        "drupal/devel_generate": ">=4",
-        "drupal/commerce": "^2.0"
+        "drush/drush": "^9.0 || ^10",
+        "drupal/devel_generate": "^2.0 || 4.x-dev"
     },
     "repositories": {
         "drupal": {
diff --git a/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml b/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml
index 12f37aef61689093e8dec7435942db99945dad2e..342784a5c1d91bdd8e7fc31ab39e9150ae95993a 100644
--- a/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml
+++ b/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml
@@ -1,31 +1,22 @@
 langcode: en
 status: true
 dependencies:
-  config:
-    - system.menu.admin
   module:
     - node
+    - scheduler
     - user
-  enforced:
-    module:
-      - scheduler
 id: scheduler_scheduled_content
-label: 'Scheduled Content'
+label: 'Scheduled content'
 module: views
 description: 'Find and manage scheduled content.'
 tag: ''
 base_table: node_field_revision
 base_field: vid
 display:
-  # ----------------------------------------------------------------------------
-  # Default display
-  # ----------------------------------------------------------------------------
   default:
     display_options:
       access:
-        type: perm
-        options:
-          perm: 'view scheduled content'
+        type: scheduler
       cache:
         type: tag
         options: {  }
@@ -994,20 +985,17 @@ display:
         - 'languages:language_interface'
         - url
         - url.query_args
+        - user
         - 'user.node_grants:view'
-        - user.permissions
       max-age: 0
       tags: {  }
-  # ----------------------------------------------------------------------------
-  # Overview
-  # ----------------------------------------------------------------------------
   overview:
     display_options:
       path: admin/content/scheduled
       menu:
         type: normal
-        title: 'Scheduled Content'
-        description: 'Content that is scheduled for publishing or unpublishing'
+        title: Scheduled
+        description: 'Content scheduled for publishing and unpublishing'
         expanded: false
         parent: system.admin_content
         weight: -10
@@ -1032,13 +1020,10 @@ display:
         - 'languages:language_interface'
         - url
         - url.query_args
+        - user
         - 'user.node_grants:view'
-        - user.permissions
       max-age: 0
       tags: {  }
-  # ----------------------------------------------------------------------------
-  # User page
-  # ----------------------------------------------------------------------------
   user_page:
     display_options:
       path: user/%user/scheduled
@@ -1046,7 +1031,7 @@ display:
         type: tab
         title: Scheduled
         description: ''
-        parent: user.page
+        parent: system.admin_content
         weight: -10
         context: '0'
         menu_name: admin
@@ -1062,7 +1047,7 @@ display:
         filters: true
         filter_groups: true
         arguments: false
-        access: false
+        access: true
         empty: false
       arguments:
         uid:
@@ -1119,13 +1104,8 @@ display:
           tokenize: true
           content: 'No scheduled content for user {{ arguments.uid }}'
           plugin_id: text_custom
-      access:
-        type: perm
-        options:
-          perm: 'access content'
-      display_comment: 'Access to the user view is controlled via a custom RouteSubscriber. The high-level "view published content" permission is added to satisfy the security_review module'
     display_plugin: page
-    display_title: User
+    display_title: 'User profile tab'
     id: user_page
     position: 2
     cache_metadata:
@@ -1134,7 +1114,7 @@ display:
         - 'languages:language_interface'
         - url
         - url.query_args
+        - user
         - 'user.node_grants:view'
-        - user.permissions
       max-age: 0
       tags: {  }
diff --git a/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_commerce_product.yml b/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_commerce_product.yml
deleted file mode 100644
index 375725bf75398a624e97696b1692022e02271b43..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_commerce_product.yml
+++ /dev/null
@@ -1,968 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - system.menu.admin
-  enforced:
-    module:
-      - scheduler
-  module:
-    - commerce
-    - commerce_product
-    - commerce_store
-    - user
-id: scheduler_scheduled_commerce_product
-label: 'Scheduled Products'
-module: views
-description: 'Find and manage scheduled commerce products.'
-tag: ''
-base_table: commerce_product_field_data
-base_field: product_id
-display:
-  # ----------------------------------------------------------------------------
-  # Default display
-  # ----------------------------------------------------------------------------
-  default:
-    display_plugin: default
-    id: default
-    display_title: Master
-    position: 0
-    display_options:
-      access:
-        type: perm
-        options:
-          perm: 'view scheduled commerce_product'
-      cache:
-        type: tag
-        options: {  }
-      query:
-        type: views_query
-        options:
-          disable_sql_rewrite: false
-          distinct: true
-          replica: false
-          query_comment: ''
-          query_tags: {  }
-      exposed_form:
-        type: basic
-        options:
-          submit_button: Filter
-          reset_button: true
-          reset_button_label: Reset
-          exposed_sorts_label: 'Sort by'
-          expose_sort_order: true
-          sort_asc_label: Asc
-          sort_desc_label: Desc
-      pager:
-        type: full
-        options:
-          items_per_page: 50
-          offset: 0
-          id: 0
-          total_pages: null
-          expose:
-            items_per_page: false
-            items_per_page_label: 'Items per page'
-            items_per_page_options: '5, 10, 25, 50'
-            items_per_page_options_all: false
-            items_per_page_options_all_label: '- All -'
-            offset: false
-            offset_label: Offset
-          tags:
-            previous: '‹ previous'
-            next: 'next ›'
-            first: '« first'
-            last: 'last »'
-      style:
-        type: table
-        options:
-          grouping:
-            -
-              field: stores_target_id
-              rendered: true
-              rendered_strip: false
-          row_class: ''
-          default_row_class: true
-          override: true
-          sticky: true
-          caption: ''
-          summary: ''
-          description: ''
-          columns:
-            commerce_product_bulk_form: commerce_product_bulk_form
-            title: title
-            type: type
-            status: status
-            publish_on: publish_on
-            unpublish_on: unpublish_on
-            operations: operations
-            stores_target_id: stores_target_id
-          info:
-            commerce_product_bulk_form:
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            title:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            type:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            status:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            publish_on:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            unpublish_on:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            operations:
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            stores_target_id:
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-          default: publish_on
-          empty_table: true
-      row:
-        type: fields
-      fields:
-        commerce_product_bulk_form:
-          id: commerce_product_bulk_form
-          table: commerce_product
-          field: commerce_product_bulk_form
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Bulk update'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: false
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          action_title: Action
-          include_exclude: exclude
-          selected_actions: {  }
-          entity_type: commerce_product
-          plugin_id: bulk_form
-        title:
-          id: title
-          table: commerce_product_field_data
-          field: title
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Title'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: false
-            ellipsis: false
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: string
-          settings:
-            link_to_entity: true
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: commerce_product
-          entity_field: title
-          plugin_id: field
-        type:
-          id: type
-          table: commerce_product_field_data
-          field: type
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Type
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: target_id
-          type: entity_reference_label
-          settings:
-            link: true
-          group_column: target_id
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          hide_single_bundle: true
-          entity_type: commerce_product
-          entity_field: type
-          plugin_id: commerce_entity_bundle
-        status:
-          id: status
-          table: commerce_product_field_data
-          field: status
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Status
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: boolean
-          settings:
-            format: custom
-            format_custom_true: Published
-            format_custom_false: Unpublished
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: commerce_product
-          entity_field: status
-          plugin_id: field
-        publish_on:
-          id: publish_on
-          table: commerce_product_field_data
-          field: publish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Publish on'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: timestamp
-          settings:
-            date_format: short
-            custom_date_format: ''
-            timezone: ''
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: commerce_product
-          entity_field: publish_on
-          plugin_id: field
-        unpublish_on:
-          id: unpublish_on
-          table: commerce_product_field_data
-          field: unpublish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Unpublish on'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: timestamp
-          settings:
-            date_format: short
-            custom_date_format: ''
-            timezone: ''
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: commerce_product
-          entity_field: unpublish_on
-          plugin_id: field
-        operations:
-          id: operations
-          table: commerce_product
-          field: operations
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Operations
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          destination: false
-          entity_type: commerce_product
-          plugin_id: entity_operations
-        stores_target_id:
-          id: stores_target_id
-          table: commerce_product__stores
-          field: stores_target_id
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Stores
-          exclude: true
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: target_id
-          type: entity_reference_label
-          settings:
-            link: true
-          group_column: target_id
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: commerce_product
-          entity_field: stores
-          plugin_id: field
-      filters:
-        type:
-          id: type
-          table: commerce_product_field_data
-          field: type
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: in
-          value: {  }
-          group: 1
-          exposed: true
-          expose:
-            operator_id: type_op
-            label: 'Type'
-            description: ''
-            use_operator: false
-            operator: type_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: type
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
-            reduce: false
-            hide_single_bundle: true
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: commerce_product
-          entity_field: type
-          plugin_id: commerce_entity_bundle
-        title:
-          id: title
-          table: commerce_product_field_data
-          field: title
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: contains
-          value: ''
-          group: 1
-          exposed: true
-          expose:
-            operator_id: title_op
-            label: 'Title'
-            description: ''
-            use_operator: false
-            operator: title_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: title
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
-            placeholder: ''
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: commerce_product
-          entity_field: title
-          plugin_id: string
-        status:
-          id: status
-          table: commerce_product_field_data
-          field: status
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: '='
-          value: '0'
-          group: 1
-          exposed: true
-          expose:
-            operator_id: ''
-            label: Status
-            description: ''
-            use_operator: false
-            operator: status_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: status
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
-          is_grouped: true
-          group_info:
-            label: 'Published status'
-            description: ''
-            identifier: status
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items:
-              1:
-                title: Published
-                operator: '='
-                value: '1'
-              2:
-                title: Unpublished
-                operator: '='
-                value: '0'
-          entity_type: commerce_product
-          entity_field: status
-          plugin_id: boolean
-        publish_on:
-          id: publish_on
-          table: commerce_product_field_data
-          field: publish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: 'not empty'
-          value:
-            min: ''
-            max: ''
-            value: ''
-            type: date
-          group: 2
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            placeholder: ''
-            min_placeholder: ''
-            max_placeholder: ''
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: commerce_product
-          entity_field: publish_on
-          plugin_id: date
-        unpublish_on:
-          id: unpublish_on
-          table: commerce_product_field_data
-          field: unpublish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: 'not empty'
-          value:
-            min: ''
-            max: ''
-            value: ''
-            type: date
-          group: 2
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            placeholder: null
-            min_placeholder: null
-            max_placeholder: null
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: commerce_product
-          entity_field: unpublish_on
-          plugin_id: date
-      sorts: {  }
-      header: {  }
-      footer: {  }
-      empty:
-        area_text_custom:
-          id: area_text_custom
-          table: views
-          field: area_text_custom
-          relationship: none
-          group_type: group
-          admin_label: ''
-          empty: true
-          tokenize: false
-          content: 'No scheduled products.'
-          plugin_id: text_custom
-      arguments: {  }
-      display_extenders: {  }
-      filter_groups:
-        operator: AND
-        groups:
-          1: AND
-          2: OR
-      title: 'Scheduled Products'
-      relationships:
-        stores_target_id:
-          id: stores_target_id
-          table: commerce_product__stores
-          field: stores_target_id
-          relationship: none
-          group_type: group
-          admin_label: Store
-          required: false
-          entity_type: commerce_product
-          entity_field: stores
-          plugin_id: standard
-      group_by: true
-    cache_metadata:
-      max-age: 0
-      contexts:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
-        - url.query_args
-        - user.permissions
-      tags: {  }
-  # ----------------------------------------------------------------------------
-  # Overview
-  # ----------------------------------------------------------------------------
-  overview:
-    display_plugin: page
-    id: overview
-    display_title: 'Products Overview'
-    position: 1
-    display_options:
-      display_extenders: {  }
-      path: admin/commerce/products/scheduled
-      display_description: 'Overview of all scheduled products, via main commerce product page'
-      menu:
-        type: normal
-        title: 'Scheduled Products'
-        description: 'Commerce products that are scheduled for publishing or unpublishing'
-        expanded: false
-        parent: entity.commerce_product.collection
-        weight: 0
-        context: '0'
-        menu_name: admin
-      tab_options:
-        type: none
-        title: ''
-        description: ''
-        weight: 0
-    cache_metadata:
-      max-age: 0
-      contexts:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
-        - url.query_args
-        - user.permissions
-      tags: {  }
diff --git a/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_media.yml b/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_media.yml
deleted file mode 100644
index d55e27c33312bea1d30e57006cd511d4d118ec29..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_media.yml
+++ /dev/null
@@ -1,1097 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - system.menu.admin
-  module:
-    - media
-    - user
-  enforced:
-    module:
-      - scheduler
-id: scheduler_scheduled_media
-label: 'Scheduled Media'
-module: views
-description: 'Find and manage scheduled media.'
-tag: ''
-base_table: media_field_revision
-base_field: vid
-display:
-  # ----------------------------------------------------------------------------
-  # Default display
-  # ----------------------------------------------------------------------------
-  default:
-    display_plugin: default
-    id: default
-    display_title: Master
-    position: 0
-    display_options:
-      access:
-        type: perm
-        options:
-          perm: 'view scheduled media'
-      cache:
-        type: tag
-        options: {  }
-      query:
-        type: views_query
-        options:
-          disable_sql_rewrite: false
-          distinct: false
-          replica: false
-          query_comment: ''
-          query_tags: {  }
-      exposed_form:
-        type: basic
-        options:
-          submit_button: Filter
-          reset_button: true
-          reset_button_label: Reset
-          exposed_sorts_label: 'Sort by'
-          expose_sort_order: true
-          sort_asc_label: Asc
-          sort_desc_label: Desc
-      pager:
-        type: full
-        options:
-          items_per_page: 50
-          offset: 0
-          id: 0
-          total_pages: null
-          expose:
-            items_per_page: false
-            items_per_page_label: 'Items per page'
-            items_per_page_options: '5, 10, 25, 50'
-            items_per_page_options_all: false
-            items_per_page_options_all_label: '- All -'
-            offset: false
-            offset_label: Offset
-          tags:
-            previous: '‹ previous'
-            next: 'next ›'
-            first: '« first'
-            last: 'last »'
-      style:
-        type: table
-        options:
-          grouping: {  }
-          row_class: ''
-          default_row_class: true
-          override: true
-          sticky: true
-          caption: ''
-          summary: ''
-          description: ''
-          columns:
-            media_bulk_form: media_bulk_form
-            name: name
-            bundle: bundle
-            uid: uid
-            status: status
-            publish_on: publish_on
-            unpublish_on: unpublish_on
-            operations: operations
-          info:
-            media_bulk_form:
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            name:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            bundle:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            uid:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            status:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            publish_on:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            unpublish_on:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            operations:
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-          default: publish_on
-          empty_table: true
-      row:
-        type: fields
-      fields:
-        media_bulk_form:
-          id: media_bulk_form
-          table: media
-          field: media_bulk_form
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Bulk update'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: false
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          action_title: Action
-          include_exclude: exclude
-          selected_actions: {  }
-          entity_type: media
-          plugin_id: bulk_form
-        name:
-          id: name
-          table: media_field_revision
-          field: name
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Media Name'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: false
-            ellipsis: false
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: string
-          settings:
-            link_to_entity: true
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: media
-          entity_field: name
-          plugin_id: field
-        bundle:
-          id: bundle
-          table: media_field_data
-          field: bundle
-          relationship: mid
-          group_type: group
-          admin_label: ''
-          label: 'Media type'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: target_id
-          type: entity_reference_label
-          settings:
-            link: true
-          group_column: target_id
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: media
-          entity_field: bundle
-          plugin_id: field
-        uid:
-          id: uid
-          table: media_field_revision
-          field: uid
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Author
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: target_id
-          type: entity_reference_label
-          settings:
-            link: true
-          group_column: target_id
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: media
-          entity_field: uid
-          plugin_id: field
-        status:
-          id: status
-          table: media_field_revision
-          field: status
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Status
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: boolean
-          settings:
-            format: custom
-            format_custom_true: Published
-            format_custom_false: Unpublished
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: media
-          entity_field: status
-          plugin_id: field
-        publish_on:
-          id: publish_on
-          table: media_field_revision
-          field: publish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Publish on'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: timestamp
-          settings:
-            date_format: short
-            custom_date_format: ''
-            timezone: ''
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: media
-          entity_field: publish_on
-          plugin_id: field
-        unpublish_on:
-          id: unpublish_on
-          table: media_field_revision
-          field: unpublish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Unpublish on'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: timestamp
-          settings:
-            date_format: short
-            custom_date_format: ''
-            timezone: ''
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: media
-          entity_field: unpublish_on
-          plugin_id: field
-        operations:
-          id: operations
-          table: media_revision
-          field: operations
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Operations
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          destination: false
-          entity_type: media
-          plugin_id: entity_operations
-      filters:
-        latest_revision:
-          id: latest_revision
-          table: media_revision
-          field: latest_revision
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: '='
-          value: ''
-          group: 1
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: media
-          plugin_id: latest_revision
-        name:
-          id: name
-          table: media_field_revision
-          field: name
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: contains
-          value: ''
-          group: 1
-          exposed: true
-          expose:
-            operator_id: name_op
-            label: 'Media name'
-            description: ''
-            use_operator: false
-            operator: name_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: name
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
-            placeholder: ''
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: media
-          entity_field: name
-          plugin_id: string
-        bundle:
-          id: bundle
-          table: media_field_data
-          field: bundle
-          relationship: mid
-          group_type: group
-          admin_label: ''
-          operator: in
-          value: {  }
-          group: 1
-          exposed: true
-          expose:
-            operator_id: bundle_op
-            label: Type
-            description: ''
-            use_operator: false
-            operator: bundle_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: bundle
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
-            reduce: false
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: media
-          entity_field: bundle
-          plugin_id: bundle
-        status:
-          id: status
-          table: media_field_revision
-          field: status
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: '='
-          value: ''
-          group: 1
-          exposed: true
-          expose:
-            operator_id: ''
-            label: Status
-            description: ''
-            use_operator: false
-            operator: status_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: status
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
-          is_grouped: true
-          group_info:
-            label: 'Published status'
-            description: ''
-            identifier: status
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items:
-              1:
-                title: Published
-                operator: '='
-                value: '1'
-              2:
-                title: Unpublished
-                operator: '='
-                value: '0'
-          entity_type: media
-          entity_field: status
-          plugin_id: boolean
-        langcode:
-          id: langcode
-          table: media_field_revision
-          field: langcode
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: in
-          value: {  }
-          group: 1
-          exposed: true
-          expose:
-            operator_id: langcode_op
-            label: Language
-            description: ''
-            use_operator: false
-            operator: langcode_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: langcode
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
-            reduce: false
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: media
-          entity_field: langcode
-          plugin_id: language
-        publish_on:
-          id: publish_on
-          table: media_field_revision
-          field: publish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: 'not empty'
-          value:
-            min: ''
-            max: ''
-            value: ''
-            type: date
-          group: 2
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            placeholder: ''
-            min_placeholder: ''
-            max_placeholder: ''
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: media
-          entity_field: publish_on
-          plugin_id: date
-        unpublish_on:
-          id: unpublish_on
-          table: media_field_revision
-          field: unpublish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: 'not empty'
-          value:
-            min: ''
-            max: ''
-            value: ''
-            type: date
-          group: 2
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            placeholder: null
-            min_placeholder: null
-            max_placeholder: null
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: media
-          entity_field: unpublish_on
-          plugin_id: date
-      sorts: {  }
-      title: 'Scheduled Media'
-      header: {  }
-      footer: {  }
-      empty:
-        area_text_custom:
-          id: area_text_custom
-          table: views
-          field: area_text_custom
-          relationship: none
-          group_type: group
-          admin_label: ''
-          empty: true
-          tokenize: false
-          content: 'No scheduled media.'
-          plugin_id: text_custom
-      arguments: {  }
-      relationships:
-        mid:
-          id: mid
-          table: media_field_revision
-          field: mid
-          relationship: none
-          group_type: group
-          admin_label: 'Media Field'
-          required: false
-          entity_type: media
-          entity_field: mid
-          plugin_id: standard
-      display_extenders: {  }
-      filter_groups:
-        operator: AND
-        groups:
-          1: AND
-          2: OR
-    cache_metadata:
-      max-age: 0
-      contexts:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
-        - url.query_args
-        - user.permissions
-      tags: {  }
-  # ----------------------------------------------------------------------------
-  # Overview
-  # ----------------------------------------------------------------------------
-  overview:
-    display_plugin: page
-    id: overview
-    display_title: 'Media Overview'
-    position: 1
-    display_options:
-      display_extenders: {  }
-      path: admin/content/media/scheduled
-      display_description: 'Overview of all scheduled media, via main admin content page'
-      menu:
-        type: normal
-        title: 'Scheduled Media'
-        description: 'Media items that are scheduled for publishing or unpublishing'
-        expanded: false
-        parent: 'system.admin_content'
-        weight: 0
-        context: '0'
-        menu_name: admin
-    cache_metadata:
-      max-age: 0
-      contexts:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
-        - url.query_args
-        - user.permissions
-      tags: {  }
-  # ----------------------------------------------------------------------------
-  # User page
-  # ----------------------------------------------------------------------------
-  user_page:
-    display_plugin: page
-    id: user_page
-    display_title: User
-    position: 2
-    display_options:
-      display_extenders: {  }
-      display_description: 'Scheduled media on user profile, showing just that user''s scheduled media'
-      path: user/%user/scheduled_media
-      menu:
-        type: tab
-        title: 'Scheduled Media'
-        description: 'Scheduled Media by this user'
-        expanded: false
-        parent: user.page
-        weight: -1
-        context: '0'
-        menu_name: account
-      empty:
-        area_text_custom:
-          id: area_text_custom
-          table: views
-          field: area_text_custom
-          relationship: none
-          group_type: group
-          admin_label: ''
-          empty: true
-          tokenize: true
-          content: 'No scheduled media for user {{ arguments.uid }}'
-          plugin_id: text_custom
-      defaults:
-        empty: false
-        arguments: false
-        access: false
-      arguments:
-        uid:
-          id: uid
-          table: media_field_revision
-          field: uid
-          entity_type: media
-          entity_field: uid
-          plugin_id: numeric
-      access:
-        type: perm
-        options:
-          perm: 'view media'
-      display_comment: 'Access to the user view is controlled via a custom RouteSubscriber. The high-level "view media" permission is added to satisfy the security_review module'
-    cache_metadata:
-      max-age: 0
-      contexts:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
-        - url.query_args
-        - user.permissions
-      tags: {  }
diff --git a/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_taxonomy_term.yml b/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_taxonomy_term.yml
deleted file mode 100644
index 0042da07957a1e922a8af0fc22e86ed321d01ea4..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/config/optional/views.view.scheduler_scheduled_taxonomy_term.yml
+++ /dev/null
@@ -1,866 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - system.menu.admin
-  enforced:
-    module:
-      - scheduler
-  module:
-    - taxonomy
-    - user
-id: scheduler_scheduled_taxonomy_term
-label: 'Scheduled Taxonomy Terms'
-module: views
-description: 'Find and manage scheduled taxonomy terms.'
-tag: ''
-base_table: taxonomy_term_field_data
-base_field: tid
-display:
-  # ----------------------------------------------------------------------------
-  # Default display
-  # ----------------------------------------------------------------------------
-  default:
-    display_plugin: default
-    id: default
-    display_title: Master
-    position: 0
-    display_options:
-      access:
-        type: perm
-        options:
-          perm: 'view scheduled taxonomy_term'
-      cache:
-        type: tag
-        options: {  }
-      query:
-        type: views_query
-        options:
-          disable_sql_rewrite: false
-          distinct: false
-          replica: false
-          query_comment: ''
-          query_tags: {  }
-      exposed_form:
-        type: basic
-        options:
-          submit_button: Filter
-          reset_button: true
-          reset_button_label: Reset
-          exposed_sorts_label: 'Sort by'
-          expose_sort_order: true
-          sort_asc_label: Asc
-          sort_desc_label: Desc
-      pager:
-        type: full
-        options:
-          items_per_page: 50
-          offset: 0
-          id: 0
-          total_pages: null
-          expose:
-            items_per_page: false
-            items_per_page_label: 'Items per page'
-            items_per_page_options: '5, 10, 25, 50'
-            items_per_page_options_all: false
-            items_per_page_options_all_label: '- All -'
-            offset: false
-            offset_label: Offset
-          tags:
-            previous: '‹ previous'
-            next: 'next ›'
-            first: '« first'
-            last: 'last »'
-      style:
-        type: table
-        options:
-          grouping: {  }
-          row_class: ''
-          default_row_class: true
-          override: true
-          sticky: true
-          caption: ''
-          summary: ''
-          description: ''
-          columns:
-            taxonomy_term_bulk_form: taxonomy_term_bulk_form
-            name: name
-            vid: vid
-            status: status
-            publish_on: publish_on
-            unpublish_on: unpublish_on
-          info:
-            taxonomy_term_bulk_form:
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            name:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            vid:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            status:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            publish_on:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-            unpublish_on:
-              sortable: true
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
-          default: publish_on
-          empty_table: true
-      row:
-        type: fields
-        options:
-          inline: {  }
-          separator: ''
-          hide_empty: false
-          default_field_elements: true
-      fields:
-        taxonomy_term_bulk_form:
-          id: taxonomy_term_bulk_form
-          table: taxonomy_term_data
-          field: taxonomy_term_bulk_form
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Bulk update'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: false
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          action_title: Action
-          include_exclude: exclude
-          selected_actions: {  }
-          entity_type: taxonomy_term
-          plugin_id: bulk_form
-        name:
-          id: name
-          table: taxonomy_term_field_data
-          field: name
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Term
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: false
-            ellipsis: false
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: string
-          settings:
-            link_to_entity: true
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          convert_spaces: false
-          entity_type: taxonomy_term
-          entity_field: name
-          plugin_id: term_name
-        vid:
-          id: vid
-          table: taxonomy_term_field_data
-          field: vid
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Vocabulary
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: target_id
-          type: entity_reference_label
-          settings:
-            link: true
-          group_column: target_id
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: taxonomy_term
-          entity_field: vid
-          plugin_id: field
-        status:
-          id: status
-          table: taxonomy_term_field_data
-          field: status
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Status
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: boolean
-          settings:
-            format: custom
-            format_custom_true: Published
-            format_custom_false: Unpublished
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: taxonomy_term
-          entity_field: status
-          plugin_id: field
-        publish_on:
-          id: publish_on
-          table: taxonomy_term_field_data
-          field: publish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Publish on'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: timestamp
-          settings:
-            date_format: short
-            custom_date_format: ''
-            timezone: ''
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: taxonomy_term
-          entity_field: publish_on
-          plugin_id: field
-        unpublish_on:
-          id: unpublish_on
-          table: taxonomy_term_field_data
-          field: unpublish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: 'Unpublish on'
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          click_sort_column: value
-          type: timestamp
-          settings:
-            date_format: short
-            custom_date_format: ''
-            timezone: ''
-          group_column: value
-          group_columns: {  }
-          group_rows: true
-          delta_limit: 0
-          delta_offset: 0
-          delta_reversed: false
-          delta_first_last: false
-          multi_type: separator
-          separator: ', '
-          field_api_classes: false
-          entity_type: taxonomy_term
-          entity_field: unpublish_on
-          plugin_id: field
-        operations:
-          id: operations
-          table: taxonomy_term_data
-          field: operations
-          relationship: none
-          group_type: group
-          admin_label: ''
-          label: Operations
-          exclude: false
-          alter:
-            alter_text: false
-            text: ''
-            make_link: false
-            path: ''
-            absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: 0
-            word_boundary: true
-            ellipsis: true
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
-            strip_tags: false
-            trim: false
-            preserve_tags: ''
-            html: false
-          element_type: ''
-          element_class: ''
-          element_label_type: ''
-          element_label_class: ''
-          element_label_colon: true
-          element_wrapper_type: ''
-          element_wrapper_class: ''
-          element_default_classes: true
-          empty: ''
-          hide_empty: false
-          empty_zero: false
-          hide_alter_empty: true
-          destination: false
-          entity_type: taxonomy_term
-          plugin_id: entity_operations
-      filters:
-        name:
-          id: name
-          table: taxonomy_term_field_data
-          field: name
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: contains
-          value: ''
-          group: 1
-          exposed: true
-          expose:
-            operator_id: name_op
-            label: 'Term name'
-            description: ''
-            use_operator: false
-            operator: name_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: name
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            placeholder: ''
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: taxonomy_term
-          entity_field: name
-          plugin_id: string
-        vid:
-          id: vid
-          table: taxonomy_term_field_data
-          field: vid
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: in
-          value: {  }
-          group: 1
-          exposed: true
-          expose:
-            operator_id: vid_op
-            label: Vocabulary
-            description: ''
-            use_operator: false
-            operator: vid_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: vid
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            reduce: false
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: taxonomy_term
-          entity_field: vid
-          plugin_id: bundle
-        status:
-          id: status
-          table: taxonomy_term_field_data
-          field: status
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: '='
-          value: ''
-          group: 1
-          exposed: true
-          expose:
-            operator_id: ''
-            label: Status
-            description: ''
-            use_operator: false
-            operator: status_op
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: status
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: true
-          group_info:
-            label: 'Published status'
-            description: ''
-            identifier: status
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items:
-              1:
-                title: Published
-                operator: '='
-                value: '1'
-              2:
-                title: Unpublished
-                operator: '='
-                value: '0'
-          entity_type: taxonomy_term
-          entity_field: status
-          plugin_id: boolean
-        publish_on:
-          id: publish_on
-          table: taxonomy_term_field_revision
-          field: publish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: 'not empty'
-          value:
-            min: ''
-            max: ''
-            value: ''
-            type: date
-          group: 2
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            placeholder: null
-            min_placeholder: null
-            max_placeholder: null
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: taxonomy_term
-          entity_field: publish_on
-          plugin_id: date
-        unpublish_on:
-          id: unpublish_on
-          table: taxonomy_term_field_revision
-          field: unpublish_on
-          relationship: none
-          group_type: group
-          admin_label: ''
-          operator: 'not empty'
-          value:
-            min: ''
-            max: ''
-            value: ''
-            type: date
-          group: 2
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-            placeholder: ''
-            min_placeholder: ''
-            max_placeholder: ''
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
-          entity_type: taxonomy_term
-          entity_field: unpublish_on
-          plugin_id: date
-      sorts: {  }
-      header: {  }
-      footer: {  }
-      empty:
-        area_text_custom:
-          id: area_text_custom
-          table: views
-          field: area_text_custom
-          relationship: none
-          group_type: group
-          admin_label: ''
-          empty: true
-          tokenize: false
-          content: 'No scheduled taxonomy terms'
-          plugin_id: text_custom
-      relationships: {  }
-      arguments: {  }
-      display_extenders: {  }
-      filter_groups:
-        operator: AND
-        groups:
-          1: AND
-          2: OR
-      title: 'Scheduled Taxonomy Terms'
-    cache_metadata:
-      max-age: 0
-      contexts:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
-        - url.query_args
-        - user.permissions
-      tags: {  }
-  # ----------------------------------------------------------------------------
-  # Overview
-  # ----------------------------------------------------------------------------
-  overview:
-    display_plugin: page
-    id: overview
-    display_title: 'Taxonomy Terms'
-    position: 1
-    display_options:
-      display_extenders: {  }
-      display_description: 'Overview of all scheduled terms, via main admin taxonomy page'
-      path: admin/structure/taxonomy/scheduled
-      menu:
-        type: normal
-        title: 'Scheduled Taxonomy Terms'
-        description: 'Taxonomy Terms that are scheduled for publishing or unpublishing'
-        expanded: false
-        parent: entity.taxonomy_vocabulary.collection
-        weight: 0
-        context: '0'
-        menu_name: admin
-    cache_metadata:
-      max-age: 0
-      contexts:
-        - 'languages:language_content'
-        - 'languages:language_interface'
-        - url
-        - url.query_args
-        - user.permissions
-      tags: {  }
diff --git a/web/modules/scheduler/config/schema/scheduler.schema.yml b/web/modules/scheduler/config/schema/scheduler.schema.yml
index b1e1ab97ec84e2e17f778540a0a2153937fdfe65..ed5386d9cb63596f38be20c3839a800a333794f7 100644
--- a/web/modules/scheduler/config/schema/scheduler.schema.yml
+++ b/web/modules/scheduler/config/schema/scheduler.schema.yml
@@ -18,43 +18,43 @@ scheduler.settings:
       label: 'Date part of the full format'
     default_expand_fieldset:
       type: string
-      label: 'Default value for entity type setting expand_fieldset'
+      label: 'Default value for nodetype setting expand_fieldset'
     default_fields_display_mode:
       type: string
-      label: 'Default value for entity type setting fields_display_mode'
+      label: 'Default value for nodetype setting fields_display_mode'
     default_publish_enable:
       type: boolean
-      label: 'Default value for entity type setting publish_enable'
+      label: 'Default value for nodetype setting publish_enable'
     default_publish_past_date:
       type: string
-      label: 'Default value for entity type setting publish_past_date'
+      label: 'Default value for nodetype setting publish_past_date'
     default_publish_past_date_created:
       type: boolean
-      label: 'Default value for entity type setting publish_past_date_created'
+      label: 'Default value for nodetype setting publish_past_date_created'
     default_publish_required:
       type: boolean
-      label: 'Default value for entity type setting publish_required'
+      label: 'Default value for nodetype setting publish_required'
     default_publish_revision:
       type: boolean
-      label: 'Default value for entity type setting publish_revision'
+      label: 'Default value for nodetype setting publish_revision'
     default_publish_touch:
       type: boolean
-      label: 'Default value for entity type setting publish_touch'
+      label: 'Default value for nodetype setting publish_touch'
     default_show_message_after_update:
       type: boolean
-      label: 'Default value for entity type setting show_message_after_update'
+      label: 'Default value for nodetype setting show_message_after_update'
     default_time:
       type: string
       label: 'Default Scheduling Time. This is used with the option to allow date only'
     default_unpublish_enable:
       type: boolean
-      label: 'Default value for entity type setting unpublish_enable'
+      label: 'Default value for nodetype setting unpublish_enable'
     default_unpublish_required:
       type: boolean
-      label: 'Default value for entity type setting unpublish_required'
+      label: 'Default value for nodetype setting unpublish_required'
     default_unpublish_revision:
       type: boolean
-      label: 'Default value for entity type setting unpublish_revision'
+      label: 'Default value for nodetype setting unpublish_revision'
     hide_seconds:
       type: boolean
       label: 'Hide the seconds on the input control when entering a time'
@@ -71,25 +71,25 @@ scheduler.settings:
       type: string
       label: 'Time part of the full format'
 
-node.type.*.third_party.scheduler: &third_party_settings_alias
+node.type.*.third_party.scheduler:
   type: mapping
-  label: 'Scheduler entity type settings'
+  label: 'Scheduler content type settings'
   mapping:
     expand_fieldset:
       type: string
-      label: 'Conditions under which to expand the date input fieldset or vertical tab ("when_required" or "always")'
+      label: 'Conditions under which to expand the date input fieldset or vertical tab'
     fields_display_mode:
       type: string
-      label: 'The way the scheduling fields are displayed in the edit form ("vertical_tab" or "fieldset")'
+      label: 'The way the scheduling fields are displayed in the node form'
     publish_enable:
       type: boolean
       label: 'Enable scheduled publishing'
     publish_past_date:
       type: string
-      label: 'Action to be taken for publication dates in the past ("error", "publish" or "schedule")'
+      label: 'Action to be taken for publication dates in the past'
     publish_past_date_created:
       type: boolean
-      label: 'Change entity creation date for past dates to avoid "changed" being earlier than "created"'
+      label: 'Change content creation date for past dates to avoid "changed" being earlier than "created"'
     publish_required:
       type: boolean
       label: 'Require scheduled publishing'
@@ -98,10 +98,10 @@ node.type.*.third_party.scheduler: &third_party_settings_alias
       label: 'Create a new revision on publishing'
     publish_touch:
       type: boolean
-      label: 'Change entity creation time to match the scheduled publish time'
+      label: 'Change content creation time to match the scheduled publish time'
     show_message_after_update:
       type: boolean
-      label: 'Show a message after updating an entity which is scheduled'
+      label: 'Show a message after updating content which is scheduled'
     unpublish_enable:
       type: boolean
       label: 'Enable scheduled unpublishing'
@@ -111,15 +111,3 @@ node.type.*.third_party.scheduler: &third_party_settings_alias
     unpublish_revision:
       type: boolean
       label: 'Create a new revision on unpublishing'
-
-# Use the saved alias to repeat the same schema for media.type and
-# commerce_product.commerce_product_type. Adding a separate comment betweeen the
-# two definitions below causes a "cannot parse" error.
-media.type.*.third_party.scheduler:
-  *third_party_settings_alias
-
-commerce_product.commerce_product_type.*.third_party.scheduler:
-  *third_party_settings_alias
-
-taxonomy.vocabulary.*.third_party.scheduler:
-  *third_party_settings_alias
diff --git a/web/modules/scheduler/config/schema/workbench_moderation_actions_fix.schema.yml b/web/modules/scheduler/config/schema/workbench_moderation_actions_fix.schema.yml
deleted file mode 100644
index 0469c74d98a63fefb59ca25e25c9acf88f5f3da3..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/config/schema/workbench_moderation_actions_fix.schema.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-# Temporary fix for workbench_moderation_actions
-# See https://www.drupal.org/project/workbench_moderation_actions/issues/3281948 
-action.configuration.state_change:*:
-  type: action_configuration_default
-  label: 'State Change configuration'
diff --git a/web/modules/scheduler/css/styling.css b/web/modules/scheduler/css/styling.css
index a36281c564d56d7df8920de2ef749dddf7a3f396..f5d53eb510ce020c6378bfb32541b16635912b9e 100644
--- a/web/modules/scheduler/css/styling.css
+++ b/web/modules/scheduler/css/styling.css
@@ -2,11 +2,3 @@
 .scheduler-admin-form > .dropbutton-wrapper {
   width: 30%;
 }
-
-/* Add default background to dropbutton items in Claro theme. This should only
-   affect the non-link group title items, and avoid them being transparent.
-   See https://www.drupal.org/project/scheduler/issues/3316719
-   and https://www.drupal.org/project/drupal/issues/3317323 */
-.scheduler-admin-form .dropbutton__item:first-of-type ~ .dropbutton__item {
-  background-color: #f2f4f5;
-}
diff --git a/web/modules/scheduler/drupalci.yml b/web/modules/scheduler/drupalci.yml
index 394613c021b953d37e2c5ebd771dedad40d20d58..7940044000ac8ba53862c00813f0f78a8fb4bc8e 100644
--- a/web/modules/scheduler/drupalci.yml
+++ b/web/modules/scheduler/drupalci.yml
@@ -4,41 +4,18 @@ build:
     validate_codebase:
       phplint:
       container_composer:
-      container_command:
-        commands:
-      host_command:
-        commands:
-          # Apply patch from https://www.drupal.org/project/drupalci_testbot/issues/3251817
-          # @todo Remove this when Drupal 9.4.9 and 9.5.0 and 10.0.0 have been released.
-          - "cd ${SOURCE_DIR} && sudo -u www-data curl https://www.drupal.org/files/issues/2021-11-30/3251817-4.run-tests-with-multiple-groups.patch | sudo -u www-data patch -p1 --verbose"
       csslint:
       eslint:
       phpcs:
+      # Static analysis for uses of @deprecated code.
       phpstan:
         halt-on-fail: false
     testing:
-      container_command:
-        commands:
-          # Rule 3.0-alpha7 is not compatible with PHP8.1 but the dev version has been fixed.
-          - "cd ${SOURCE_DIR} && sudo -u www-data composer require drupal/rules:3.x-dev"
-          # Get workbench moderation modules when testing with Drupal 9.
-          # Use * because only the dev version of WBMA is compatible with D9.
-          - 'drush core:status | awk "NR==1{print \$2\$3\$4}"'
-          - 'drush core:status | awk "NR==1{print \$2\$3\$4}" | grep version:9 && sudo -u www-data composer require drupal/workbench_moderation drupal/workbench_moderation_actions:*'
-          # Show the eslint version
-          - "${SOURCE_DIR}/core/node_modules/.bin/eslint --version"
-      run_tests.functional:
+      run_tests.standard:
         types: 'PHPUnit-Functional'
-        testgroups: '--all'
-        # The groups are 'scheduler,scheduler_api,scheduler_rules_integration'
-        suppress-deprecations: false
-      run_tests.kernel:
-        types: 'PHPUnit-Kernel'
-        testgroups: 'scheduler_kernel'
-        suppress-deprecations: false
+        suppress-deprecations: true
       run_tests.js:
         types: 'PHPUnit-FunctionalJavascript'
-        testgroups: 'scheduler_js'
-        suppress-deprecations: false
+        suppress-deprecations: true
         concurrency: 1
         halt-on-fail: false
diff --git a/web/modules/scheduler/js/scheduler_default_time.js b/web/modules/scheduler/js/scheduler_default_time.js
index c1b90e3a7b4bf125b4cc57fb29a12eec255dfeec..cc82a7cd04ef0871e95a27bba1d1fb1234964d1c 100644
--- a/web/modules/scheduler/js/scheduler_default_time.js
+++ b/web/modules/scheduler/js/scheduler_default_time.js
@@ -3,14 +3,14 @@
  * JQuery to set default time for Scheduler DateTime Widget.
  */
 
-(function ($, drupalSettings, once) {
+(function ($, drupalSettings) {
 
   'use strict';
 
   /**
    * Provide default time if schedulerDefaultTime is set.
    *
-   * schedulerDefaultTime is defined in _scheduler_entity_form_alter when the
+   * schedulerDefaultTime is defined in scheduler_form_node_form_alter when the
    * user is allowed to enter just a date. The values need to be pre-filled here
    * to avoid the browser validation 'please fill in this field' pop-up error
    * which is produced before the date widget valueCallback() can set the value.
@@ -18,13 +18,7 @@
    */
   Drupal.behaviors.setSchedulerDefaultTime = {
     attach: function (context) {
-
-      // Drupal.behaviors are called many times per page. Using .once() adds the
-      // class onto the matched DOM element and uses this to prevent it running
-      // on subsequent calls.
-      const $default_time = once('default-time-done', '#edit-scheduler-settings', context);
-
-      if ($default_time.length && typeof drupalSettings.schedulerDefaultTime !== "undefined") {
+      if (typeof drupalSettings.schedulerDefaultTime !== "undefined") {
         var operations = ["publish", "unpublish"];
         operations.forEach(function (value) {
           var element = $("input#edit-" + value + "-on-0-value-time", context);
@@ -49,4 +43,4 @@
 
     }
   };
-})(jQuery, drupalSettings, once);
+})(jQuery, drupalSettings);
diff --git a/web/modules/scheduler/js/scheduler_default_time_8x.js b/web/modules/scheduler/js/scheduler_default_time_8x.js
deleted file mode 100644
index 7e2363f1e2f87eb09169dcf55c84ab6d4fed16ea..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/js/scheduler_default_time_8x.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * @file
- * JQuery to set default time for Scheduler DateTime Widget.
- *
- * This is a legacy version to maintain compatibility with Drupal 8.9.
- */
-
-(function ($, drupalSettings) {
-
-  'use strict';
-
-  /**
-   * Provide default time if schedulerDefaultTime is set.
-   *
-   * schedulerDefaultTime is defined in _scheduler_entity_form_alter when the
-   * user is allowed to enter just a date. The values need to be pre-filled here
-   * to avoid the browser validation 'please fill in this field' pop-up error
-   * which is produced before the date widget valueCallback() can set the value.
-   * @see https://www.drupal.org/project/scheduler/issues/2913829
-   */
-  Drupal.behaviors.setSchedulerDefaultTime = {
-    attach: function (context) {
-
-      // Drupal.behaviors are called many times per page. Using .once() adds the
-      // class onto the matched DOM element and uses this to prevent it running
-      // on subsequent calls.
-      const $default_time = $(context).find('#edit-scheduler-settings').once('default-time-done');
-
-      if ($default_time.length && typeof drupalSettings.schedulerDefaultTime !== "undefined") {
-        var operations = ["publish", "unpublish"];
-        operations.forEach(function (value) {
-          var element = $("input#edit-" + value + "-on-0-value-time", context);
-          // Only set the time when there is no value and the field is required.
-          if (element.val() === "" && element.prop("required")) {
-            element.val(drupalSettings.schedulerDefaultTime);
-          }
-        });
-      }
-
-      // Also use this jQuery behaviors function to set any pre-existing time
-      // values with seconds removed if those drupalSettings values exist. This
-      // is required by some browsers to make the seconds hidden.
-      if (typeof drupalSettings.schedulerHideSecondsPublishOn !== "undefined") {
-        var element = $("input#edit-publish-on-0-value-time", context);
-        element.val(drupalSettings.schedulerHideSecondsPublishOn);
-      }
-      if (typeof drupalSettings.schedulerHideSecondsUnpublishOn !== "undefined") {
-        var element = $("input#edit-unpublish-on-0-value-time", context);
-        element.val(drupalSettings.schedulerHideSecondsUnpublishOn);
-      }
-
-    }
-  };
-})(jQuery, drupalSettings);
diff --git a/web/modules/scheduler/migrations/d7_scheduler_settings.yml b/web/modules/scheduler/migrations/d7_scheduler_settings.yml
deleted file mode 100644
index 6371cc80fab4d6f9982543642ec0f2ac385c567f..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/migrations/d7_scheduler_settings.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-id: d7_scheduler_settings
-label: Scheduler configuration
-migration_tags:
-  - Drupal 7
-  - Configuration
-source:
-  plugin: variable
-  variables:
-    - scheduler_allow_date_only
-    - scheduler_default_time
-    - scheduler_date_format
-  source_module: scheduler
-process:
-  allow_date_only:
-    plugin: default_value
-    default_value: false
-    source: scheduler_allow_date_only
-  default_time:
-    plugin: default_value
-    default_value: '00:00:00'
-    source: scheduler_default_time
-  hide_seconds:
-    plugin: scheduler_hide_seconds
-    default_value: false
-    source: scheduler_date_format
-destination:
-  plugin: config
-  config_name: scheduler.settings
diff --git a/web/modules/scheduler/migrations/state/scheduler.migrate_drupal.yml b/web/modules/scheduler/migrations/state/scheduler.migrate_drupal.yml
deleted file mode 100644
index 58d04cb5d3a0bedf813447b43396895d9fcdcfb0..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/migrations/state/scheduler.migrate_drupal.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-finished:
-  7:
-    scheduler: scheduler
diff --git a/web/modules/scheduler/phpcs.xml.dist b/web/modules/scheduler/phpcs.xml.dist
index 89c0152cd92ef272b91d863fe186ad1b02dfd953..1a86e6e31d18431fe300263d64a33f34fd54d15c 100644
--- a/web/modules/scheduler/phpcs.xml.dist
+++ b/web/modules/scheduler/phpcs.xml.dist
@@ -3,11 +3,6 @@
   <description>Default PHP CodeSniffer configuration for Scheduler.</description>
   <file>.</file>
 
-  <!-- Temporary fix until drupal.org testbot script is changed. This is also
-    compatible with running phpcs locally, and on Travis, so can be committed.
-    See https://www.drupal.org/project/drupalci_testbot/issues/3283978 -->
-  <config name="installed_paths" value="../../drupal/coder/coder_sniffer/,../../sirbrillig/phpcs-variable-analysis/,../../slevomat/coding-standard/"/>
-
   <!-- Initially include all Drupal and DrupalPractice sniffs. -->
   <rule ref="Drupal"/>
   <rule ref="DrupalPractice"/>
@@ -29,13 +24,6 @@
     <exclude name="Drupal.Commenting.InlineComment.SpacingAfter"/>
   </rule>
 
-  <!-- This rule is disabled in Coder 8.3.10, but undefined variables will -->
-  <!-- be reported when using earlier versions. Hence re-enable the rule so -->
-  <!-- we do not get surprises when testing with other versions. -->
-  <rule ref="DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable">
-    <severity>5</severity>
-  </rule>
-
   <!-- Ignore all files that match these patterns. By default the full file -->
   <!-- path is checked, unless type=relative is used. There is an implied * -->
   <!-- wildcard at each end and periods and slashes must be escaped using \ -->
diff --git a/web/modules/scheduler/plugins/content_types/scheduler_form_pane.inc b/web/modules/scheduler/plugins/content_types/scheduler_form_pane.inc
index 9e6e97804337eec5a6e72c974a59245e16f283dd..29721877a1bd3ac45b054fd91d9eb94275f6ee9f 100644
--- a/web/modules/scheduler/plugins/content_types/scheduler_form_pane.inc
+++ b/web/modules/scheduler/plugins/content_types/scheduler_form_pane.inc
@@ -18,7 +18,7 @@
   'edit form' => 'scheduler_form_pane_node_form_menu_content_type_edit_form',
   'render callback' => 'scheduler_form_pane_content_type_render',
   'title' => t('Node form scheduler'),
-  'icon' => \Drupal::service('extension.list.module')->getPath('ctools') . '/plugins/content_types/node_form/icon_node_form.png',
+  'icon' => drupal_get_path('module', 'ctools') . '/plugins/content_types/node_form/icon_node_form.png',
   'description' => t('Scheduler date options on the Node form.'),
   'required context' => new ctools_context_required(t('Form'), 'node_form'),
   'category' => t('Form'),
diff --git a/web/modules/scheduler/scheduler.admin.inc b/web/modules/scheduler/scheduler.admin.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a1b9b0f8d4e2b69319536a8a350f709a5b5f9ffd
--- /dev/null
+++ b/web/modules/scheduler/scheduler.admin.inc
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Administration forms for the Scheduler module.
+ */
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\node\NodeTypeInterface;
+
+/**
+ * Helper function for the real hook_form_node_type_form_alter().
+ *
+ * @see scheduler_form_node_type_form_alter()
+ */
+function _scheduler_form_node_type_form_alter(array &$form, FormStateInterface $form_state) {
+  $config = \Drupal::config('scheduler.settings');
+
+  /** @var \Drupal\node\NodeTypeInterface $type */
+  $type = $form_state->getFormObject()->getEntity();
+
+  $form['#attached']['library'][] = 'scheduler/admin';
+  $form['#attached']['library'][] = 'scheduler/vertical-tabs';
+
+  $form['scheduler'] = [
+    '#type' => 'details',
+    '#title' => t('Scheduler'),
+    '#weight' => 35,
+    '#group' => 'additional_settings',
+  ];
+
+  // Publishing options.
+  $form['scheduler']['publish'] = [
+    '#type' => 'details',
+    '#title' => t('Publishing'),
+    '#weight' => 1,
+    '#group' => 'scheduler',
+    '#open' => TRUE,
+  ];
+  $form['scheduler']['publish']['scheduler_publish_enable'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Enable scheduled publishing for this content type'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable')),
+  ];
+  $form['scheduler']['publish']['scheduler_publish_touch'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Change content creation time to match the scheduled publish time'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch')),
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+  $form['scheduler']['publish']['scheduler_publish_required'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Require scheduled publishing'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_required', $config->get('default_publish_required')),
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+  $form['scheduler']['publish']['scheduler_publish_revision'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Create a new revision on publishing'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_revision', $config->get('default_publish_revision')),
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+  $form['scheduler']['publish']['advanced'] = [
+    '#type' => 'details',
+    '#title' => t('Advanced options'),
+    '#open' => FALSE,
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+  $form['scheduler']['publish']['advanced']['scheduler_publish_past_date'] = [
+    '#type' => 'radios',
+    '#title' => t('Action to be taken for publication dates in the past'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_past_date', $config->get('default_publish_past_date')),
+    '#options' => [
+      'error' => t('Display an error message - do not allow dates in the past'),
+      'publish' => t('Publish the content immediately after saving'),
+      'schedule' => t('Schedule the content for publication on the next cron run'),
+    ],
+  ];
+  $form['scheduler']['publish']['advanced']['scheduler_publish_past_date_created'] = [
+    '#type' => 'checkbox',
+    '#title' => t("Change content creation time to match the published time for dates before the content was created"),
+    '#description' => t("The created time will only be altered when the scheduled publishing time is earlier than the existing content creation time"),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_past_date_created', $config->get('default_publish_past_date_created')),
+    // This option is not relevant if the full 'change creation time' option is
+    // selected, or when past dates are not allowed. Hence only show it when
+    // the main option is not checked and the past dates option is not 'error'.
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_publish_touch"]' => ['checked' => FALSE],
+        ':input[name="scheduler_publish_past_date"]' => ['!value' => 'error'],
+      ],
+    ],
+  ];
+
+  // Unpublishing options.
+  $form['scheduler']['unpublish'] = [
+    '#type' => 'details',
+    '#title' => t('Unpublishing'),
+    '#weight' => 2,
+    '#group' => 'scheduler',
+    '#open' => TRUE,
+  ];
+  $form['scheduler']['unpublish']['scheduler_unpublish_enable'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Enable scheduled unpublishing for this content type'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable')),
+  ];
+  $form['scheduler']['unpublish']['scheduler_unpublish_required'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Require scheduled unpublishing'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'unpublish_required', $config->get('default_unpublish_required')),
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_unpublish_enable"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+  $form['scheduler']['unpublish']['scheduler_unpublish_revision'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Create a new revision on unpublishing'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'unpublish_revision', $config->get('default_unpublish_revision')),
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_unpublish_enable"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+
+  // The 'node_edit_layout' fieldset contains options to alter the layout of
+  // node edit pages.
+  $form['scheduler']['node_edit_layout'] = [
+    '#type' => 'details',
+    '#title' => t('Node edit page'),
+    '#weight' => 3,
+    '#group' => 'scheduler',
+    // The #states processing only caters for AND and does not do OR. So to set
+    // the state to visible if either of the boxes are ticked we use the fact
+    // that logical 'X = A or B' is equivalent to 'not X = not A and not B'.
+    '#states' => [
+      '!visible' => [
+        ':input[name="scheduler_publish_enable"]' => ['!checked' => TRUE],
+        ':input[name="scheduler_unpublish_enable"]' => ['!checked' => TRUE],
+      ],
+    ],
+  ];
+  $form['scheduler']['node_edit_layout']['scheduler_fields_display_mode'] = [
+    '#type' => 'radios',
+    '#title' => t('Display scheduling options as'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'fields_display_mode', $config->get('default_fields_display_mode')),
+    '#options' => [
+      'vertical_tab' => t('Vertical tab'),
+      'fieldset' => t('Separate fieldset'),
+    ],
+    '#description' => t('Use this option to specify how the scheduling options will be displayed when editing a node.'),
+  ];
+  $form['scheduler']['node_edit_layout']['scheduler_expand_fieldset'] = [
+    '#type' => 'radios',
+    '#title' => t('Expand fieldset or vertical tab'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'expand_fieldset', $config->get('default_expand_fieldset')),
+    '#options' => [
+      'when_required' => t('Expand only when a scheduled date exists or when a date is required'),
+      'always' => t('Always open the fieldset or vertical tab'),
+    ],
+  ];
+  $form['scheduler']['node_edit_layout']['scheduler_show_message_after_update'] = [
+    '#type' => 'checkbox',
+    '#prefix' => '<strong>' . t('Show message') . '</strong>',
+    '#title' => t('Show a confirmation message when scheduled content is saved'),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'show_message_after_update', $config->get('default_show_message_after_update')),
+  ];
+
+  $form['#entity_builders'][] = 'scheduler_form_node_type_form_builder';
+}
+
+/**
+ * Entity builder for the node type form with scheduler options.
+ */
+function scheduler_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) {
+  $type->setThirdPartySetting('scheduler', 'expand_fieldset', $form_state->getValue('scheduler_expand_fieldset'));
+  $type->setThirdPartySetting('scheduler', 'fields_display_mode', $form_state->getValue('scheduler_fields_display_mode'));
+  $type->setThirdPartySetting('scheduler', 'publish_enable', $form_state->getValue('scheduler_publish_enable'));
+  $type->setThirdPartySetting('scheduler', 'publish_past_date', $form_state->getValue('scheduler_publish_past_date'));
+  $type->setThirdPartySetting('scheduler', 'publish_past_date_created', $form_state->getValue('scheduler_publish_past_date_created'));
+  $type->setThirdPartySetting('scheduler', 'publish_required', $form_state->getValue('scheduler_publish_required'));
+  $type->setThirdPartySetting('scheduler', 'publish_revision', $form_state->getValue('scheduler_publish_revision'));
+  $type->setThirdPartySetting('scheduler', 'publish_touch', $form_state->getValue('scheduler_publish_touch'));
+  $type->setThirdPartySetting('scheduler', 'show_message_after_update', $form_state->getValue('scheduler_show_message_after_update'));
+  $type->setThirdPartySetting('scheduler', 'unpublish_enable', $form_state->getValue('scheduler_unpublish_enable'));
+  $type->setThirdPartySetting('scheduler', 'unpublish_required', $form_state->getValue('scheduler_unpublish_required'));
+  $type->setThirdPartySetting('scheduler', 'unpublish_revision', $form_state->getValue('scheduler_unpublish_revision'));
+}
diff --git a/web/modules/scheduler/scheduler.api.php b/web/modules/scheduler/scheduler.api.php
index 5ecec2f69bd406eb6418c9c4590178a940ee0389..3304947102555660676f30bf2ab9ea009b3c6d2f 100644
--- a/web/modules/scheduler/scheduler.api.php
+++ b/web/modules/scheduler/scheduler.api.php
@@ -3,12 +3,6 @@
 /**
  * @file
  * API documentation for the Scheduler module.
- *
- * Each of these hook functions has a general version which is invoked for all
- * entity types, and a specific variant with _{type}_ in the name, invoked when
- * processing that specific entity type.
- *
- * phpcs:disable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable
  */
 
 /**
@@ -17,326 +11,229 @@
  */
 
 /**
- * Hook function to add entity ids to the list being processed.
+ * Hook function to add node ids to the list being processed.
  *
- * This hook allows modules to add more entity ids into the list being processed
+ * This hook allows modules to add more node ids into the list being processed
  * in the current cron run. It is invoked during cron runs only. This function
  * is retained for backwards compatibility but is superceded by the more
- * flexible hook_scheduler_list_alter().
+ * flexible hook_scheduler_nid_list_alter().
  *
- * @param string $process
- *   The process being done - 'publish' or 'unpublish'.
- * @param string $entityTypeId
- *   The type of the entity being processed, for example 'node' or 'media'.
+ * @param string $action
+ *   The action being done to the node - 'publish' or 'unpublish'.
  *
  * @return array
- *   Array of ids to add to the existing list to be processed. Duplicates are
- *   removed when all hooks have been invoked.
+ *   Array of node ids to add to the existing list of nodes to be processed.
  */
-function hook_scheduler_list($process, $entityTypeId) {
-  $ids = [];
-  // Do some processing to add ids to the $ids array.
-  return $ids;
+function hook_scheduler_nid_list($action) {
+  $nids = [];
+  // Do some processing to add new node ids into $nids.
+  return $nids;
 }
 
 /**
- * Entity-type specific version of hook_scheduler_list().
+ * Hook function to manipulate the list of nodes being processed.
  *
- * The parameters and return value match the general variant of this hook. The
- * $entityTypeId parameter is included for ease and consistency, but is not
- * strictly necessary as it will always match the TYPE in the function name.
- */
-function hook_scheduler_TYPE_list($process, $entityTypeId) {
-}
-
-/**
- * Hook function to manipulate the list of entity ids being processed.
+ * This hook allows modules to add or remove node ids from the list being
+ * processed in the current cron run. It is invoked during cron runs only. It
+ * can do everything that hook_scheduler_nid_list() does, plus more.
  *
- * This hook allows modules to add or remove entity ids from the list being
- * processed in the current cron run. It is invoked during cron runs only.
+ * @param array $nids
+ *   An array of node ids being processed.
+ * @param string $action
+ *   The action being done to the node - 'publish' or 'unpublish'.
  *
- * @param array $ids
- *   The array of entity ids being processed.
- * @param string $process
- *   The process being done - 'publish' or 'unpublish'.
- * @param string $entityTypeId
- *   The type of the entity being processed, for example 'node' or 'media'.
- */
-function hook_scheduler_list_alter(array &$ids, $process, $entityTypeId) {
-  if ($process == 'publish' && $some_condition) {
-    // Set a publish_on date and add the id.
-    $entity->set('publish_on', \Drupal::time()->getRequestTime())->save();
-    $ids[] = $id;
-  }
-  if ($process == 'unpublish' && $some_other_condition) {
-    // Remove the id.
-    $ids = array_diff($ids, [$id]);
-  }
-  // No return is necessary because $ids is passed by reference. Duplicates are
-  // removed when all hooks have been invoked.
-}
-
-/**
- * Entity-type specific version of hook_scheduler_list_alter().
- *
- * The parameters match the general variant of this hook.
+ * @return array
+ *   The full array of node ids to process, adjusted as required.
  */
-function hook_scheduler_TYPE_list_alter(array &$ids, $process, $entityTypeId) {
+function hook_scheduler_nid_list_alter(array &$nids, $action) {
+  // Do some processing to add or remove node ids.
+  return $nids;
 }
 
 /**
- * Hook function to deny publishing of an entity.
+ * Hook function to deny or allow a node to be published.
  *
- * This hook gives modules the ability to prevent publication of an entity. The
- * entity may be scheduled, and an attempt to publish it will be made during the
- * first cron run after the publishing time. If any implementation of this hook
- * function returns FALSE the entity will not be published. Attempts to publish
- * will continue on each subsequent cron run, and the entity will be published
- * when no hook prevents it.
+ * This hook gives modules the ability to prevent publication of a node at the
+ * scheduled time. The node may be scheduled, and an attempt to publish it will
+ * be made during the first cron run after the publishing time. If this hook
+ * returns FALSE the node will not be published. Attempts at publishing will
+ * continue on each subsequent cron run until this hook returns TRUE.
  *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The scheduled entity that is about to be published.
+ * @param \Drupal\node\NodeInterface $node
+ *   The scheduled node that is about to be published.
  *
- * @return bool|null
- *   FALSE if the entity should not be published. TRUE or NULL will not affect
- *   the outcome.
+ * @return bool
+ *   TRUE if the node can be published, FALSE if it should not be published.
  */
-function hook_scheduler_publishing_allowed(EntityInterface $entity) {
-  // Do some logic here ...
-  $allowed = !empty($entity->field_approved->value);
-  // If publication is denied then inform the user why. This message will be
-  // displayed during entity edit and save.
-  if (!$allowed) {
-    \Drupal::messenger()->addMessage(t('The content will only be published after approval.'), 'status', FALSE);
-    // If the time is in the past it means that the action has been prevented,
-    // so write a dblog message to show this.
-    if ($entity->publish_on->value <= \Drupal::time()->getRequestTime()) {
-      if ($entity->id() && $entity->hasLinkTemplate('canonical')) {
-        $link = $entity->toLink(t('View'))->toString();
-      }
-      \Drupal::logger('scheduler_api_test')->warning('Publishing of "%title" is prevented until approved.', [
-        '%title' => $entity->label(),
-        'link' => $link ?? NULL,
-      ]);
+function hook_scheduler_allow_publishing(NodeInterface $node) {
+  // If there is no 'approved' field do nothing to change the result.
+  if (!isset($node->field_approved)) {
+    $allowed = TRUE;
+  }
+  else {
+    // Prevent publication of nodes that do not have the 'Approved for
+    // publication by the CEO' checkbox ticked.
+    $allowed = !empty($node->field_approved->value);
+
+    // If publication is denied then inform the user why. This message will be
+    // displayed during node edit and save.
+    if (!$allowed) {
+      \Drupal::messenger()->addMessage(t('The content will only be published after approval by the CEO.'), 'status', FALSE);
     }
   }
-  return $allowed;
-}
 
-/**
- * Entity-type specific version of hook_scheduler_publishing_allowed().
- *
- * The parameters and return match the general variant of this hook.
- */
-function hook_scheduler_TYPE_publishing_allowed(EntityInterface $entity) {
+  return $allowed;
 }
 
 /**
- * Hook function to deny unpublishing of an entity.
+ * Hook function to deny or allow a node to be unpublished.
  *
- * This hook gives modules the ability to prevent unpublication of an entity.
- * The entity may be scheduled, and an attempt to unpublish it will be made
- * during the first cron run after the unpublishing time. If any implementation
- * of this hook function returns FALSE the entity will not be unpublished.
- * Attempts to unpublish will continue on each subsequent cron run, and the
- * entity will be unpublished when no hook prevents it.
+ * This hook gives modules the ability to prevent unpblication of a node at the
+ * scheduled time. The node may be scheduled, and an attempt to unpublish it
+ * will be made during the first cron run after the unpublishing time. If this
+ * hook returns FALSE the node will not be unpublished. Attempts at unpublishing
+ * will continue on each subsequent cron run until this hook returns TRUE.
  *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The scheduled entity that is about to be unpublished.
+ * @param \Drupal\node\NodeInterface $node
+ *   The scheduled node that is about to be unpublished.
  *
- * @return bool|null
- *   FALSE if the entity should not be unpublished. TRUE or NULL will not affect
- *   the outcome.
+ * @return bool
+ *   TRUE if the node can be unpublished, FALSE if it should not be unpublished.
  */
-function hook_scheduler_unpublishing_allowed(EntityInterface $entity) {
+function hook_scheduler_allow_unpublishing(NodeInterface $node) {
   $allowed = TRUE;
-  // Prevent unpublication of competitions if not all prizes have been claimed.
-  if ($entity->getEntityTypeId() == 'competition' && $items = $entity->field_competition_prizes->getValue()) {
+
+  // Prevent unpublication of competition entries if not all prizes have been
+  // claimed.
+  if ($node->getType() == 'competition' && $items = $node->field_competition_prizes->getValue()) {
     $allowed = (bool) count($items);
 
     // If unpublication is denied then inform the user why. This message will be
-    // displayed during entity edit and save.
+    // displayed during node edit and save.
     if (!$allowed) {
-      \Drupal::messenger()->addMessage(t('The competition will only be unpublished after all prizes have been claimed.'));
+      \Drupal::messenger()->addMessage(t('The competition will only be unpublished after all prizes have been claimed by the winners.'));
     }
   }
-  return $allowed;
-}
 
-/**
- * Entity-type specific version of hook_scheduler_unpublishing_allowed().
- *
- * The parameters and return match the general variant of this hook.
- */
-function hook_scheduler_TYPE_unpublishing_allowed(EntityInterface $entity) {
+  return $allowed;
 }
 
 /**
  * Hook function to hide the Publish On field.
  *
- * This hook is called from scheduler_form_alter() when adding or editing an
- * entity. It gives modules the ability to hide the scheduler publish_on input
- * field so that a date may not be entered or changed. Note that it does not
- * give the ability to force the field to be displayed, as that could override a
- * more significant setting. It can only be used to hide the field.
+ * This hook is called from scheduler_form_node_form_alter(). It gives modules
+ * the ability to hide the scheduler publish_on input field on the node edit
+ * form. Note that it does not give the ability to force the field to be
+ * displayed, as that could override a more significant setting. It can only be
+ * used to hide the field.
  *
  * This hook was introduced for scheduler_content_moderation_integration.
- * See https://www.drupal.org/project/scheduler/issues/2798689
  *
  * @param array $form
  *   An associative array containing the structure of the form, as used in
  *   hook_form_alter().
  * @param \Drupal\Core\Form\FormStateInterface $form_state
  *   The current state of the form, as used in hook_form_alter().
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object being added or edited.
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being editted.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
  *
  * @return bool
  *   TRUE to hide the publish_on field.
  *   FALSE or NULL to leave the setting unchanged.
  */
-function hook_scheduler_hide_publish_date(array $form, FormStateInterface $form_state, EntityInterface $entity) {
-  if ($some_condition) {
-    return TRUE;
-  }
-}
-
-/**
- * Entity-type specific version of hook_scheduler_hide_publish_date().
- *
- * The parameters and return match the general variant of this hook.
- */
-function hook_scheduler_TYPE_hide_publish_date(array $form, FormStateInterface $form_state, EntityInterface $entity) {
+function hook_scheduler_hide_publish_on_field(array $form, FormStateInterface $form_state, NodeInterface $node) {
+  return FALSE;
 }
 
 /**
  * Hook function to hide the Unpublish On field.
  *
- * This hook is called from scheduler_form_alter() when adding or editing an
- * entity. It gives modules the ability to hide the scheduler unpublish_on input
- * field so that a date may not be entered or changed. Note that it does not
- * give the ability to force the field to be displayed, as that could override a
- * more significant setting. It can only be used to hide the field.
+ * This hook is called from scheduler_form_node_form_alter(). It gives modules
+ * the ability to hide the scheduler unpublish_on input field on the node edit
+ * form. Note that it does not give the ability to force the field to be
+ * displayed, as that could override a more significant setting. It can only be
+ * used to hide the field.
  *
  * This hook was introduced for scheduler_content_moderation_integration.
- * See https://www.drupal.org/project/scheduler/issues/2798689
  *
  * @param array $form
  *   An associative array containing the structure of the form, as used in
  *   hook_form_alter().
  * @param \Drupal\Core\Form\FormStateInterface $form_state
  *   The current state of the form, as used in hook_form_alter().
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object being added or edited.
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being editted.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
  *
  * @return bool
  *   TRUE to hide the unpublish_on field.
  *   FALSE or NULL to leave the setting unchanged.
  */
-function hook_scheduler_hide_unpublish_date(array $form, FormStateInterface $form_state, EntityInterface $entity) {
-  if ($some_condition) {
-    return TRUE;
-  }
+function hook_scheduler_hide_unpublish_on_field(array $form, FormStateInterface $form_state, NodeInterface $node) {
+  return FALSE;
 }
 
 /**
- * Entity-type specific version of hook_scheduler_hide_unpublish_date().
+ * Hook function to process the publish action for a node.
  *
- * The parameters and return match the general variant of this hook.
- */
-function hook_scheduler_TYPE_hide_unpublish_date(array $form, FormStateInterface $form_state, EntityInterface $entity) {
-}
-
-/**
- * Hook function to process the publish action for an entity.
- *
- * This hook is called from schedulerManger::publish() and allows other modules
- * to process the publish action on the entity during a cron run. That module
+ * This hook is called from schedulerManger::publish() and allows oher modules
+ * to process the publish action on a node during a cron run. The other module
  * may require different functionality to be executed instead of the default
- * publish action. If all of the invoked hook functions return 0 then Scheduler
- * will process the entity using the default publish action, just as if no hook
- * functions had been called.
+ * publish process. If none of the invoked hook functions return a TRUE value
+ * then Scheduler will process the node using the default publish action, just
+ * as if no other hooks had been called.
  *
  * This hook was introduced for scheduler_content_moderation_integration.
- * See https://www.drupal.org/project/scheduler/issues/2798689
  *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The scheduled entity that is about to be published.
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being published.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
  *
  * @return int
- *   1 if this function has published the entity or performed other such action
+ *   1 if this function has published the node or performed other such action
  *     meaning that Scheduler should NOT process the default publish action.
  *   0 if nothing has been done and Scheduler should process the default publish
  *     action just as if this hook function did not exist.
  *   -1 if an error has occurred and Scheduler should abandon processing this
- *     entity with no further action and move on to the next one.
+ *     node with no further action and move on to the next one.
  */
-function hook_scheduler_publish_process(EntityInterface $entity) {
-  if ($big_problem) {
-    // Throw an exception here.
-    return -1;
-  }
-  if ($some_condition) {
-    // Do the publish processing here on the $entity.
-    $entity->setSomeValue();
-    return 1;
-  }
+function hook_scheduler_publish_action(NodeInterface $node) {
   return 0;
 }
 
 /**
- * Entity-type specific version of hook_scheduler_publish_process().
+ * Hook function to process the unpublish action for a node.
  *
- * The parameters and return match the general variant of this hook.
- */
-function hook_scheduler_TYPE_publish_process(EntityInterface $entity) {
-}
-
-/**
- * Hook function to process the unpublish action for an entity.
- *
- * This hook is called from schedulerManger::unpublish() and allows other
- * modules to process the unpublish action on the entity during a cron run. That
- * module may require different functionality to be executed instead of the
- * default unpublish action. If all of the invoked hook functions return 0 then
- * Scheduler will process the entity using the default unpublish action, just as
- * if no hook functions had been called.
+ * This hook is called from schedulerManger::unpublish() and allows oher modules
+ * to process the unpublish action on a node during a cron run. The other module
+ * may require different functionality to be executed instead of the default
+ * unpublish process. If none of the invoked hook functions return a TRUE value
+ * then Scheduler will process the node using the default unpublish action, just
+ * as if no other hooks had been called.
  *
  * This hook was introduced for scheduler_content_moderation_integration.
- * See https://www.drupal.org/project/scheduler/issues/2798689
  *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The scheduled entity that is about to be unpublished.
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being unpublished.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
  *
  * @return int
- *   1 if this function has unpublished the entity or performed other actions
- *     meaning that Scheduler should NOT process the default unpublish action.
- *   0 if nothing has been done and Scheduler should process the default
- *     unpublish action just as if this hook function did not exist.
+ *   1 if this function has published the node or performed other such action
+ *     meaning that Scheduler should NOT process the default publish action.
+ *   0 if nothing has been done and Scheduler should process the default publish
+ *     action just as if this hook function did not exist.
  *   -1 if an error has occurred and Scheduler should abandon processing this
- *     entity with no further action and move on to the next one.
+ *     node with no further action and move on to the next one.
  */
-function hook_scheduler_unpublish_process(EntityInterface $entity) {
-  if ($big_problem) {
-    // Throw an exception here.
-    return -1;
-  }
-  if ($some_condition) {
-    // Do the unpublish processing here on the $entity.
-    $entity->setSomeValue();
-    return 1;
-  }
+function hook_scheduler_unpublish_action(NodeInterface $node) {
   return 0;
 }
 
-/**
- * Entity-type specific version of hook_scheduler_unpublish_process().
- *
- * The parameters and return match the general variant of this hook.
- */
-function hook_scheduler_TYPE_unpublish_process(EntityInterface $entity) {
-}
-
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/web/modules/scheduler/scheduler.drush.inc b/web/modules/scheduler/scheduler.drush.inc
new file mode 100644
index 0000000000000000000000000000000000000000..09ebbfe3d8d0f006c9943e7564c98d3d02156fd0
--- /dev/null
+++ b/web/modules/scheduler/scheduler.drush.inc
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Drush commands for Scheduler.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function scheduler_drush_command() {
+  $items = [];
+
+  $items['scheduler-cron'] = [
+    'description' => 'Lightweight cron to process scheduler tasks.',
+    'core' => ['8+'],
+    'aliases' => ['sch-cron'],
+    'category' => 'scheduler',
+    'options' => [
+      'nomsg' => 'to avoid the "cron completed" message being written to the terminal.',
+    ],
+  ];
+
+  return $items;
+}
+
+/**
+ * Run lightweight scheduler cron.
+ */
+function drush_scheduler_cron() {
+  \Drupal::service('scheduler.manager')->runLightweightCron();
+  $nomsg = drush_get_option('nomsg', NULL);
+  $nomsg ? NULL : \Drupal::messenger()->addMessage(t('Scheduler lightweight cron completed'));
+}
diff --git a/web/modules/scheduler/scheduler.info.yml b/web/modules/scheduler/scheduler.info.yml
index bf40ce1cf7d2608304539205c2fd0861f6678b97..571cfe6dc7fd8a5280b40aa1603c722079c3f6cb 100644
--- a/web/modules/scheduler/scheduler.info.yml
+++ b/web/modules/scheduler/scheduler.info.yml
@@ -1,10 +1,11 @@
 name: Scheduler
 type: module
-description: 'Publish and unpublish content and entities automatically on specified dates and times.'
-core_version_requirement: ^8 || ^9 || ^10
+description: 'Publish and unpublish content automatically on specified dates and times.'
+core: 8.x
+core_version_requirement: ^8 || ^9
 configure: scheduler.admin_form
 dependencies:
-  - drupal:system (>= 8.8.3)
+  - drupal:system (>= 8.5)
   - drupal:datetime
   - drupal:field
   - drupal:node
@@ -12,15 +13,12 @@ dependencies:
 test_dependencies:
   - rules:rules
   - devel:devel_generate
-  - drupal:media
-  - commerce:commerce
-  - drupal:taxonomy
 libraries:
-  - admin-css
+  - scheduler/admin
   - vertical-tabs
   - default-time
 
-# Information added by Drupal.org packaging script on 2022-11-20
-version: '2.0.0-rc8'
+# Information added by Drupal.org packaging script on 2021-07-19
+version: '8.x-1.4'
 project: 'scheduler'
-datestamp: 1668951020
+datestamp: 1626701769
diff --git a/web/modules/scheduler/scheduler.install b/web/modules/scheduler/scheduler.install
index 060feca6fea7b5c4acf446fdcd9c3557a73890b9..3af878b117f08e8bd75d7039523a0bcbb2a9426f 100644
--- a/web/modules/scheduler/scheduler.install
+++ b/web/modules/scheduler/scheduler.install
@@ -82,12 +82,8 @@ function scheduler_install() {
  * Implements hook_uninstall().
  */
 function scheduler_uninstall() {
-  // This should not be necessary but incase the entity db tables or config have
-  // got out-of-step with the Scheduler plugins make sure all is up to date so
-  // that uninstalling will run OK.
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-  $scheduler_manager->entityUpdate();
-  $scheduler_manager->entityRevert();
+  // Delete the scheduled content view.
+  \Drupal::configFactory()->getEditable('views.view.scheduler_scheduled_content')->delete();
 }
 
 /**
@@ -131,134 +127,3 @@ function scheduler_update_8102() {
     return t('The "Scheduled" tab is now a "Scheduled content" sub-task under the "Content" tab');
   }
 }
-
-/**
- * Add date fields to any newly supported entity types.
- */
-function scheduler_update_8201() {
-  // If modules that have scheduler plugin support are already installed when
-  // Scheduler is then upgraded to a version which includes the entity plugins,
-  // this update function will add the missing db fields.
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-  if ($result = $scheduler_manager->entityUpdate()) {
-    return t('Added Scheduler date fields to the following entity types: %updated.', [
-      '%updated' => implode(', ', $result),
-    ]);
-  }
-  else {
-    return t('No database fields had to be added.');
-  }
-}
-
-/**
- * Refresh views for supported entity types.
- */
-function scheduler_update_8202() {
-  // The scheduled content view needs to be refreshed from source when upgrading
-  // to the entity plugin version of Scheduler. If the media or commerce modules
-  // are already enabled this will also load those new views from source.
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-  if ($result = $scheduler_manager->viewsUpdate(['node', 'media', 'commerce_product'])) {
-    return t('Updated views: %updated.', ['%updated' => implode(', ', $result)]);
-  }
-  else {
-    return t('No views require updating.');
-  }
-}
-
-/**
- * Update entity fields and scheduled view for Taxonomy Terms.
- */
-function scheduler_update_8203() {
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-  $output = [];
-  if ($result = $scheduler_manager->entityUpdate()) {
-    $output[] = t('Added Scheduler date fields to the following entity types: %updated.', [
-      '%updated' => implode(', ', $result),
-    ]);
-  }
-  if ($result = $scheduler_manager->viewsUpdate(['taxonomy_term'])) {
-    $output[] = t('Updated views: %updated.', ['%updated' => implode(', ', $result)]);
-  }
-  return $output ? implode('<br>', $output) : t('Nothing requires updating for Taxonomy Terms.');
-}
-
-/**
- * Update Rules actions and conditions to use 'entity' context.
- */
-function scheduler_update_8204() {
-  // The entity context names need to be 'entity' for all entity types, not
-  // 'node', 'media', 'commerce_product' or 'taxonomy_term'. This is for PHP8
-  // compatibility, fixing "Unknown named parameter in call_user_func_array()"
-  // See https://www.drupal.org/project/scheduler/issues/3276637
-  $rules = \Drupal::configFactory()->listAll('rules.reaction');
-  $rules_updated = [];
-  foreach ($rules as $config_id) {
-    $rule = \Drupal::configFactory()->getEditable($config_id);
-    $changed = FALSE;
-
-    // The expression array has 'conditions' and 'actions' elements which have
-    // the same structure, so can be fixed using the same loop process.
-    $expression = $rule->get('expression');
-    foreach (['condition_id' => 'conditions', 'action_id' => 'actions'] as $idx => $group) {
-      foreach ($expression[$group][$group] as $key => $cond_act) {
-        if (substr($cond_act[$idx], 0, 10) == 'scheduler_' && !empty($cond_act['context_mapping'])) {
-          foreach ($cond_act['context_mapping'] as $name => $value) {
-            if (in_array($name, ['node', 'media', 'commerce_product', 'taxonomy_term'])) {
-              // Replace the node/media/commerce_product key with 'entity'.
-              unset($expression[$group][$group][$key]['context_mapping'][$name]);
-              $expression[$group][$group][$key]['context_mapping']['entity'] = $value;
-              // Only add the rule label once.
-              $changed ?: $rules_updated[] = $rule->get('label');
-              $changed = TRUE;
-            }
-          }
-        }
-      }
-    }
-
-    // Replace the config value with the updated expression array.
-    if ($changed) {
-      $rule->set('expression', $expression);
-      $rule->save();
-    }
-  }
-
-  $output = empty($rules_updated) ? t('No reaction rules required updating with entity context.') :
-    \Drupal::translation()->formatPlural(count($rules_updated), '1 reaction rule updated with entity context', '@count reaction rules updated with entity context')
-    . '<br>' . implode('<br>', $rules_updated);
-  return $output;
-}
-
-/**
- * Add date fields to any newly supported entity types, specifically Paragraphs.
- */
-function scheduler_update_8205() {
-  return scheduler_update_8201();
-}
-
-/**
- * Remove Scheduler fields and third_party_settings for Paragraph entity types.
- */
-function scheduler_update_8207() {
-  // update_8206 had a typo, therefore replaced with update_8207.
-  // Check the module is enabled, to avoid 'Unknown entity type' message.
-  if (\Drupal::moduleHandler()->moduleExists('paragraphs') && ($result = \Drupal::service('scheduler.manager')->entityRevert(['paragraph']))) {
-    return t('%updated.', ['%updated' => implode(', ', $result)]);
-  }
-  else {
-    return t('No update required.');
-  }
-}
-
-/**
- * Show/hide entity form fields to match Scheduling enabled/disabled settings.
- */
-function scheduler_update_8208() {
-  if ($result = \Drupal::service('scheduler.manager')->resetFormDisplayFields()) {
-    return implode('</li><li>', $result);
-  }
-  else {
-    return t('No update required.');
-  }
-}
diff --git a/web/modules/scheduler/scheduler.libraries.yml b/web/modules/scheduler/scheduler.libraries.yml
index d0e9fb9b754560e34df99e94d9b3edaf95e4130e..861dca8351a70be51c012a4ce432135522b1b9f1 100644
--- a/web/modules/scheduler/scheduler.libraries.yml
+++ b/web/modules/scheduler/scheduler.libraries.yml
@@ -2,20 +2,10 @@ vertical-tabs:
   js:
     js/scheduler_vertical_tabs.js: {}
   dependencies:
-    - core/jquery
-    - core/drupal.ajax
+      - core/jquery
+      - core/drupal.ajax
 default-time:
   js:
     js/scheduler_default_time.js: {}
   dependencies:
     - core/jquery
-    - core/once
-default-time-8x:
-  js:
-    js/scheduler_default_time_8x.js: {}
-  dependencies:
-    - core/jquery
-admin-css:
-  css:
-    component:
-      css/styling.css: { }
diff --git a/web/modules/scheduler/scheduler.links.task.yml b/web/modules/scheduler/scheduler.links.task.yml
index df2debf193cff399a7b1e59e459819c6e75210c7..d1afd7e9aecf9051dc3ab30143e20b549dd3db43 100644
--- a/web/modules/scheduler/scheduler.links.task.yml
+++ b/web/modules/scheduler/scheduler.links.task.yml
@@ -10,8 +10,18 @@ scheduler.cron_tab:
   weight: 10
   base_route: scheduler.admin_form
 
-# Modules must not add hardcoded local tasks that depend on configuration, such
-# as a view which could be disabled. Therefore we use a deriver to allow
-# conditional logic to check for the views.
-scheduler.local_tasks:
-  deriver: 'Drupal\scheduler\Plugin\Derivative\DynamicLocalTasks'
+content_moderation.content:
+  # Use content_moderation.content which is the same key as is used in the core
+  # Content Moderation module. If that modules is enabled this avoids two
+  # 'Overview' links. If https://www.drupal.org/project/drupal/issues/3199682
+  # gets committed then this route could be removed from here.
+  title: 'Overview'
+  route_name: system.admin_content
+  parent_id: system.admin_content
+
+scheduler.scheduled_content:
+  title: 'Scheduled content'
+  route_name: view.scheduler_scheduled_content.overview
+  parent_id: system.admin_content
+  # Overview seems to have weight 0 and moderated content is weight 1.
+  weight: 5
diff --git a/web/modules/scheduler/scheduler.module b/web/modules/scheduler/scheduler.module
index 2ae2be069418906ca18c24742e23036e2ac73a82..ff698b6dc543132637c82d0fc5600837f6bfeb8d 100644
--- a/web/modules/scheduler/scheduler.module
+++ b/web/modules/scheduler/scheduler.module
@@ -2,27 +2,20 @@
 
 /**
  * @file
- * Scheduler publishes and unpublishes entities on dates specified by the user.
+ * Scheduler publishes and unpublishes nodes on dates specified by the user.
  */
 
-use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Component\Utility\Xss;
-use Drupal\Core\Action\Plugin\Action\UnpublishAction;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
-use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
-use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
-use Drupal\migrate\Exception\RequirementsException;
-use Drupal\migrate\Plugin\MigrateSourceInterface;
-use Drupal\migrate\Plugin\MigrationInterface;
-use Drupal\migrate\Row;
-use Drupal\workbench_moderation\Plugin\Action\ModerationOptOutPublishNode;
-use Drupal\workbench_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
+use Drupal\node\Entity\NodeType;
+use Drupal\scheduler\SchedulerEvent;
+use Drupal\scheduler\SchedulerEvents;
 
 /**
  * Implements hook_help().
@@ -32,13 +25,18 @@ function scheduler_help($route_name, RouteMatchInterface $route_match) {
   switch ($route_name) {
     case 'help.page.scheduler':
       $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Scheduler module provides the functionality for automatic publishing and unpublishing of entities, such and nodes and media items, at specified future dates.') . '</p>';
+      $output .= '<p>' . t('The Scheduler module provides the functionality for automatic publishing and unpublishing of nodes at specified future dates.') . '</p>';
       $output .= '<p>' . t('You can read more in the <a href="@readme">readme</a> file or our <a href="@project">project page on Drupal.org</a>.', [
-        '@readme' => $GLOBALS['base_url'] . '/' . \Drupal::service('extension.list.module')->getPath('scheduler') . '/README.md',
+        '@readme' => $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'scheduler') . '/README.md',
         '@project' => 'https://drupal.org/project/scheduler',
       ]) . '</p>';
       break;
 
+    case 'scheduler.admin_form':
+      $output = '<p>' . t('Most of the Scheduler options are set for each different content type, and are accessed via the <a href="@link">admin content type</a> list.', ['@link' => Url::fromRoute('entity.node_type.collection')->toString()]) . '</br>';
+      $output .= t('The options and settings below are common to all content types.') . '</p>';
+      break;
+
     case 'scheduler.cron_form':
       $base_url = $GLOBALS['base_url'];
       $access_key = \Drupal::config('scheduler.settings')->get('lightweight_cron_access_key');
@@ -56,104 +54,54 @@ function scheduler_help($route_name, RouteMatchInterface $route_match) {
 }
 
 /**
- * Implements hook_form_alter().
+ * Implements hook_form_FORM_ID_alter() for node_type_form().
  */
-function scheduler_form_alter(&$form, FormStateInterface $form_state, $form_id) {
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-
-  if (in_array($form_id, $scheduler_manager->getEntityFormIds())) {
-    /** @var \Drupal\Core\Entity\EntityInterface $entity */
-    $entity = $form_state->getFormObject()->getEntity();
-    _scheduler_entity_form_alter($form, $form_state, $form_id, $entity);
-  }
-  elseif (in_array($form_id, $scheduler_manager->getEntityTypeFormIds())) {
-    _scheduler_entity_type_form_alter($form, $form_state, $form_id);
-  }
-  elseif ($entityTypeId = array_search($form_id, $scheduler_manager->getDevelGenerateFormIds())) {
-    // Devel Generate forms are different from the other types above. There is
-    // only one form id per entity type, but also no direct way to get the
-    // entity from the form. Hence we add the entityTypeId as a key in the array
-    // of returned possible form ids, and pass that on to the helper function.
-    _scheduler_devel_generate_form_alter($form, $form_state, $form_id, $entityTypeId);
-  }
-  elseif (in_array($form_id, ['media_library_add_form_oembed', 'media_library_add_form_upload'])) {
-    if (isset($form['media'])) {
-      $media = $form_state->get('media');
-      // Call the entity form alter function for each of the new media items
-      // being uploaded.
-      foreach ($media as $key => $entity) {
-        _scheduler_entity_form_alter($form['media'][$key]['fields'], $form_state, $form_id, $entity);
-      }
-    }
-  }
+function scheduler_form_node_type_form_alter(array &$form, FormStateInterface $form_state) {
+  // Load the real code only when needed.
+  module_load_include('inc', 'scheduler', 'scheduler.admin');
+  _scheduler_form_node_type_form_alter($form, $form_state);
 }
 
 /**
- * Form alter handling for entity forms - add and edit.
+ * Implements hook_form_FORM_ID_alter() for node_form().
  */
-function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $form_id, $entity) {
-  // When a content type, such as a page, has an entity reference field linked
-  // to media items the Media Library module allows new media to be uploaded and
-  // inserted when adding/editing the page. The media upload form is not an
-  // 'entity' form and does not have a getFormDisplay() method. Hence we cannot
-  // properly amend the form in this scenario and the best we can do is remove
-  // the Scheduler fields for safety. The Scheduler issue discussing this is
-  // https://www.drupal.org/project/scheduler/issues/2916730
-  if (!method_exists($form_state->getFormObject(), 'getFormDisplay')) {
-    unset($form['publish_on']);
-    unset($form['unpublish_on']);
-    return;
-  }
-
-  // Get the form display object. If this does not exist because the form is
-  // prevented from displaying, such as in Commerce Add Product before any store
-  // has been created, we can not (and do not need to) do anything, so exit.
-  $param = ($form_id == 'media_library_add_form_upload') ? $entity : $form_state;
-  if (!$display = $form_state->getFormObject()->getFormDisplay($param)) {
-    return;
-  }
-
+function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state) {
   $config = \Drupal::config('scheduler.settings');
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-  $entityTypeId = $entity->getEntityTypeId();
-
-  $publishing_enabled = $scheduler_manager->getThirdPartySetting($entity, 'publish_enable', $config->get('default_publish_enable'));
-  $unpublishing_enabled = $scheduler_manager->getThirdPartySetting($entity, 'unpublish_enable', $config->get('default_unpublish_enable'));
-
-  // If neither publishing nor unpublishing are enabled then there is nothing to
-  // do so remove the fields from the form and exit early.
-  if (!$publishing_enabled && !$unpublishing_enabled) {
-    unset($form['publish_on']);
-    unset($form['unpublish_on']);
-    return;
-  }
+  /** @var \Drupal\node\NodeTypeInterface $type */
+  $type = $form_state->getFormObject()->getEntity()->type->entity;
+  $publishing_enabled = $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'));
+  $unpublishing_enabled = $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'));
 
   // Determine if the scheduler fields have been set to hidden (disabled).
+  $display = $form_state->getFormObject()->getFormDisplay($form_state);
   $publishing_displayed = !empty($display->getComponent('publish_on'));
   $unpublishing_displayed = !empty($display->getComponent('unpublish_on'));
 
-  // Invoke all implementations of hook_scheduler_hide_publish_date() and
-  // hook_scheduler_{type}_hide_publish_date() to allow other modules to hide
-  // the field on the entity edit form.
+  /** @var \Drupal\node\NodeInterface $node */
+  $node = $form_state->getFormObject()->getEntity();
+
+  // Invoke all implementations of hook_scheduler_hide_publish_on_field() to
+  // allow other modules to hide the field on the node edit form.
   if ($publishing_enabled && $publishing_displayed) {
-    $hook_implementations = $scheduler_manager->getHookImplementations('hide_publish_date', $entity);
-    foreach ($hook_implementations as $function) {
-      $publishing_displayed = ($function($form, $form_state, $entity) !== TRUE) && $publishing_displayed;
+    $hook = 'scheduler_hide_publish_on_field';
+    foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+      $function = $module . '_' . $hook;
+      $publishing_displayed = ($function($form, $form_state, $node) !== TRUE) && $publishing_displayed;
     }
   }
-  // Invoke all implementations of hook_scheduler_hide_unpublish_date() and
-  // hook_scheduler_{type}_hide_unpublish_date() to allow other modules to hide
-  // the field on the entity edit form.
+  // Invoke all implementations of hook_scheduler_hide_unpublish_on_field() to
+  // allow other modules to hide the field on the node edit form.
   if ($unpublishing_enabled && $unpublishing_displayed) {
-    $hook_implementations = $scheduler_manager->getHookImplementations('hide_unpublish_date', $entity);
-    foreach ($hook_implementations as $function) {
-      $unpublishing_displayed = ($function($form, $form_state, $entity) !== TRUE) && $unpublishing_displayed;
+    $hook = 'scheduler_hide_unpublish_on_field';
+    foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+      $function = $module . '_' . $hook;
+      $unpublishing_displayed = ($function($form, $form_state, $node) !== TRUE) && $unpublishing_displayed;
     }
   }
 
   // If both publishing and unpublishing are either not enabled or are hidden
-  // for this entity type then the only thing to do is remove the fields from
-  // the form, then exit.
+  // for this node type then the only thing to do is remove the fields from the
+  // form, then exit.
   if ((!$publishing_enabled || !$publishing_displayed) && (!$unpublishing_enabled || !$unpublishing_displayed)) {
     unset($form['publish_on']);
     unset($form['unpublish_on']);
@@ -163,34 +111,28 @@ function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $f
   $allow_date_only = $config->get('allow_date_only');
 
   // A publish_on date is required if the content type option is set and the
-  // entity is being created or it is currently not published but has a
+  // node is being created or it is currently not published but has a
   // scheduled publishing date.
   $publishing_required = $publishing_enabled
-    && $scheduler_manager->getThirdPartySetting($entity, 'publish_required', $config->get('default_publish_required'))
-    && ($entity->isNew() || (!$entity->isPublished() && !empty($entity->publish_on->value)));
+    && $type->getThirdPartySetting('scheduler', 'publish_required', $config->get('default_publish_required'))
+    && ($node->isNew() || (!$node->isPublished() && !empty($node->publish_on->value)));
 
   // An unpublish_on date is required if the content type option is set and the
-  // entity is being created or the current status is published or the entity is
+  // node is being created or the current status is published or the node is
   // scheduled to be published.
   $unpublishing_required = $unpublishing_enabled
-    && $scheduler_manager->getThirdPartySetting($entity, 'unpublish_required', $config->get('default_unpublish_required'))
-    && ($entity->isNew() || $entity->isPublished() || !empty($entity->publish_on->value));
+    && $type->getThirdPartySetting('scheduler', 'unpublish_required', $config->get('default_unpublish_required'))
+    && ($node->isNew() || $node->isPublished() || !empty($node->publish_on->value));
 
   // Create a 'details' field group to wrap the scheduling fields, and expand it
   // if publishing or unpublishing is required, if a date already exists or the
   // fieldset is configured to be always expanded.
-  $has_data = isset($entity->publish_on->value) || isset($entity->unpublish_on->value);
-  $always_expand = $scheduler_manager->getThirdPartySetting($entity, 'expand_fieldset', $config->get('default_expand_fieldset')) === 'always';
+  $has_data = isset($node->publish_on->value) || isset($node->unpublish_on->value);
+  $always_expand = $type->getThirdPartySetting('scheduler', 'expand_fieldset', $config->get('default_expand_fieldset')) === 'always';
   $expand_details = $publishing_required || $unpublishing_required || $has_data || $always_expand;
 
-  // Create the group for the fields. The array key has to be distinct when more
-  // than one $entity appears in the form, for example in Media Library uploads.
-  // Keep the first key the same as before, without any suffix, as this is used
-  // in drupal.behaviors javascript and could be used by third-party modules.
-  static $group_number;
-  $group_number += 1;
-  $scheduler_field_group = ($group_number == 1) ? 'scheduler_settings' : "scheduler_settings_{$group_number}";
-  $form[$scheduler_field_group] = [
+  // Create the group for the fields.
+  $form['scheduler_settings'] = [
     '#type' => 'details',
     '#title' => t('Scheduling options'),
     '#open' => $expand_details,
@@ -200,22 +142,18 @@ function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $f
   ];
 
   // Attach the fields to group.
-  $form['publish_on']['#group'] = $form['unpublish_on']['#group'] = $scheduler_field_group;
+  $form['unpublish_on']['#group'] = 'scheduler_settings';
+  $form['publish_on']['#group'] = 'scheduler_settings';
 
   // Show the field group as a vertical tab if this option is enabled.
-  $use_vertical_tabs = $scheduler_manager->getThirdPartySetting($entity, 'fields_display_mode', $config->get('default_fields_display_mode')) === 'vertical_tab';
+  $use_vertical_tabs = $type->getThirdPartySetting('scheduler', 'fields_display_mode', $config->get('default_fields_display_mode')) === 'vertical_tab';
   if ($use_vertical_tabs) {
-    $form[$scheduler_field_group]['#group'] = 'advanced';
+    $form['scheduler_settings']['#group'] = 'advanced';
 
     // Attach the javascript for the vertical tabs.
-    $form[$scheduler_field_group]['#attached']['library'][] = 'scheduler/vertical-tabs';
+    $form['scheduler_settings']['#attached']['library'][] = 'scheduler/vertical-tabs';
   }
 
-  // The 'once' library was moved from jQuery into core at 9.2. The original js
-  // library file is kept to maintain compatibility with Drupal 8.9.
-  // @see https://www.drupal.org/project/scheduler/issues/3314158
-  $default_time_library = version_compare(\Drupal::VERSION, '9.2', '>=') ? 'scheduler/default-time' : 'scheduler/default-time-8x';
-
   // Define the descriptions depending on whether the time can be skipped.
   $descriptions = [];
   if ($allow_date_only) {
@@ -229,8 +167,8 @@ function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $f
     // Use javascript to pre-fill the time parts if the dates are required.
     // See js/scheduler_default_time.js for more details.
     if ($publishing_required || $unpublishing_required) {
-      $form[$scheduler_field_group]['#attached']['library'][] = $default_time_library;
-      $form[$scheduler_field_group]['#attached']['drupalSettings']['schedulerDefaultTime'] = $config->get('default_time');
+      $form['scheduler_settings']['#attached']['library'][] = 'scheduler/default-time';
+      $form['scheduler_settings']['#attached']['drupalSettings']['schedulerDefaultTime'] = $config->get('default_time');
     }
   }
   else {
@@ -259,28 +197,25 @@ function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $f
   // When hiding the seconds on time input, we need to remove the seconds from
   // the form value, as some browsers HTML5 rendering still show the seconds.
   // We can use the same jQuery drupal behaviors file as for default time.
-  // This functionality is not covered by tests.
   if ($config->get('hide_seconds')) {
     // If there is a publish_on time, then use jQuery to remove the seconds.
-    if (isset($entity->publish_on->value)) {
-      $form[$scheduler_field_group]['#attached']['library'][] = $default_time_library;
-      $form[$scheduler_field_group]['#attached']['drupalSettings']['schedulerHideSecondsPublishOn'] = date('H:i', $entity->publish_on->value);
+    if (isset($node->publish_on->value)) {
+      $form['scheduler_settings']['#attached']['library'][] = 'scheduler/default-time';
+      $form['scheduler_settings']['#attached']['drupalSettings']['schedulerHideSecondsPublishOn'] = date('H:i', $node->publish_on->value);
     }
     // Likewise for the unpublish_on time.
-    if (isset($entity->unpublish_on->value)) {
-      $form[$scheduler_field_group]['#attached']['library'][] = $default_time_library;
-      $form[$scheduler_field_group]['#attached']['drupalSettings']['schedulerHideSecondsUnpublishOn'] = date('H:i', $entity->unpublish_on->value);
+    if (isset($node->unpublish_on->value)) {
+      $form['scheduler_settings']['#attached']['library'][] = 'scheduler/default-time';
+      $form['scheduler_settings']['#attached']['drupalSettings']['schedulerHideSecondsUnpublishOn'] = date('H:i', $node->unpublish_on->value);
     }
   }
 
-  // Check the permission for entering scheduled dates.
-  $permission = $scheduler_manager->permissionName($entityTypeId, 'schedule');
-  if (!\Drupal::currentUser()->hasPermission($permission)) {
+  if (!\Drupal::currentUser()->hasPermission('schedule publishing of nodes')) {
     // Do not show the scheduler fields for users who do not have permission.
-    // Setting #access to FALSE for the group fieldset is enough to hide the
+    // Setting #access to FALSE for 'scheduler_settings' is enough to hide the
     // fields. Setting FALSE for the individual fields is necessary to keep any
     // existing scheduled dates preserved and remain unchanged on saving.
-    $form[$scheduler_field_group]['#access'] = FALSE;
+    $form['scheduler_settings']['#access'] = FALSE;
     $form['publish_on']['#access'] = FALSE;
     $form['unpublish_on']['#access'] = FALSE;
 
@@ -293,10 +228,8 @@ function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $f
     $form['unpublish_on']['widget'][0]['value']['#required'] = FALSE;
   }
 
-  // Check which widget is set for the scheduler fields, and give a warning and
-  // provide a hint and link for how to fix it. Allow third-party modules to
-  // provide their own custom widget, we are only interested in checking that it
-  // has not reverted back to the core 'datetime_timestamp' widget.
+  // Check which widget type is set for the scheduler fields, and give a warning
+  // if the wrong one has been set and provide a hint and link to fix it.
   $pluginDefinitions = $display->get('pluginManager')->getDefinitions();
   $fields_to_check = [];
   if ($publishing_enabled && $publishing_displayed) {
@@ -308,15 +241,12 @@ function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $f
   $correct_widget_id = 'datetime_timestamp_no_default';
   foreach ($fields_to_check as $field) {
     $actual_widget_id = $display->getComponent($field)['type'];
-    if ($actual_widget_id == 'datetime_timestamp') {
-      $link = \Drupal::moduleHandler()->moduleExists('field_ui') ?
-        Url::fromRoute("entity.entity_form_display.$entityTypeId.default", ["{$entityTypeId}_type" => $entity->bundle()])->toString()
-        : '#';
-      \Drupal::messenger()->addMessage(t('The widget for field %field is incorrectly set to %wrong. This should be changed to %correct by an admin user via the <a href="@link">Field UI form display</a> :not_available', [
+    if ($actual_widget_id != $correct_widget_id) {
+      \Drupal::messenger()->addMessage(t('The widget for field %field is incorrectly set to %wrong. This should be changed to %correct by an admin user via Field UI <a href="@link">content type form display</a> :not_available', [
         '%field' => (string) $form[$field]['widget']['#title'],
         '%correct' => (string) $pluginDefinitions[$correct_widget_id]['label'],
         '%wrong' => (string) $pluginDefinitions[$actual_widget_id]['label'],
-        '@link' => $link,
+        '@link' => \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('entity.entity_form_display.node.default', ['node_type' => $type->get('type')])->toString() : '#',
         ':not_available' => \Drupal::moduleHandler()->moduleExists('field_ui') ? '' : ('(' . t('not available') . ')'),
       ]), 'warning', FALSE);
     }
@@ -324,360 +254,46 @@ function _scheduler_entity_form_alter(&$form, FormStateInterface $form_state, $f
 }
 
 /**
- * Form alter handling for entity type forms.
+ * Implements hook_form_FORM_ID_alter() for devel_generate_form_content.
  */
-function _scheduler_entity_type_form_alter(&$form, FormStateInterface $form_state, $form_id) {
-  $config = \Drupal::config('scheduler.settings');
+function scheduler_form_devel_generate_form_content_alter(array &$form, FormStateInterface $form_state) {
+  // Add an extra column to the node_types table to show which type are enabled
+  // for scheduled publishing and unpublishing.
+  $publishing_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types('publish'));
+  $unpublishing_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types('unpublish'));
 
-  /** @var \Drupal\Core\Entity\EntityTypeInterface $type */
-  $type = $form_state->getFormObject()->getEntity();
+  $form['node_types']['#header']['scheduler'] = t('Scheduler settings');
 
-  /** @var Drupal\Core\Entity\ContentEntityTypeInterface $contentEntityType */
-  $contentEntityType = \Drupal::entityTypeManager()->getDefinition($type->getEntityType()->getBundleOf());
-
-  /** @var \Drupal\Core\Entity\ContentEntityInterface $contentEntity */
-  $contentEntity = \Drupal::entityTypeManager()->getStorage($contentEntityType->id())->create([$contentEntityType->getKey('bundle') => 'scaffold']);
-
-  $params = [
-    '@type' => $type->label() ?? '',
-    '%type' => strtolower($type->label() ?? ''),
-    '@singular' => $contentEntityType->getSingularLabel(),
-    '@plural' => $contentEntityType->getPluralLabel(),
-  ];
-
-  $form['#attached']['library'][] = 'scheduler/vertical-tabs';
-
-  $form['scheduler'] = [
-    '#type' => 'details',
-    '#title' => t('Scheduler'),
-    '#weight' => 35,
-    '#group' => 'additional_settings',
-  ];
-
-  // Publishing options.
-  $form['scheduler']['publish'] = [
-    '#type' => 'details',
-    '#title' => t('Publishing'),
-    '#weight' => 1,
-    '#group' => 'scheduler',
-    '#open' => TRUE,
-  ];
-  $form['scheduler']['publish']['scheduler_publish_enable'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Enable scheduled publishing for %type @plural', $params),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable')),
-  ];
-  $form['scheduler']['publish']['scheduler_publish_touch'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Change %type creation time to match the scheduled publish time', $params),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch')),
-    '#states' => [
-      'visible' => [
-        ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
-      ],
-    ],
-  ];
-
-  // Entity types that do not implement the 'getCreatedTime' method should have
-  // the option set to FALSE and the field disabled and hidden.
-  if (!method_exists($contentEntity, 'getCreatedTime')) {
-    $form['scheduler']['publish']['scheduler_publish_touch']['#disabled'] = TRUE;
-    $form['scheduler']['publish']['scheduler_publish_touch']['#description'] = t('The entity type does not support the change of creation time.');
-    $form['scheduler']['publish']['scheduler_publish_touch']['#default_value'] = FALSE;
-    $form['scheduler']['publish']['scheduler_publish_touch']['#access'] = FALSE;
-  }
-
-  $form['scheduler']['publish']['scheduler_publish_required'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Require scheduled publishing'),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_required', $config->get('default_publish_required')),
-    '#states' => [
-      'visible' => [
-        ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
-      ],
-    ],
-  ];
-  if ($contentEntityType->isRevisionable()) {
-    $form['scheduler']['publish']['scheduler_publish_revision'] = [
-      '#type' => 'checkbox',
-      '#title' => t('Create a new revision on publishing'),
-      '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_revision', $config->get('default_publish_revision')),
-      '#states' => [
-        'visible' => [
-          ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
-        ],
-      ],
-    ];
-  }
-  $form['scheduler']['publish']['advanced'] = [
-    '#type' => 'details',
-    '#title' => t('Advanced options'),
-    '#open' => FALSE,
-    '#states' => [
-      'visible' => [
-        ':input[name="scheduler_publish_enable"]' => ['checked' => TRUE],
-      ],
-    ],
-  ];
-  $form['scheduler']['publish']['advanced']['scheduler_publish_past_date'] = [
-    '#type' => 'radios',
-    '#title' => t('Action to be taken for publication dates in the past'),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_past_date', $config->get('default_publish_past_date')),
-    '#options' => [
-      'error' => t('Display an error message - do not allow dates in the past'),
-      'publish' => t('Publish the %type @singular immediately after saving', $params),
-      'schedule' => t('Schedule the %type @singular for publication on the next cron run', $params),
-    ],
-  ];
-  $form['scheduler']['publish']['advanced']['scheduler_publish_past_date_created'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Change %type creation time to match the published time, for dates before the %type was created', $params),
-    '#description' => t("The created time will only be altered when the scheduled publishing time is earlier than the existing creation time"),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_past_date_created', $config->get('default_publish_past_date_created')),
-    // This option is not relevant if the full 'change creation time' option is
-    // selected, or when past dates are not allowed. Hence only show it when
-    // the main option is not checked and the past dates option is not 'error'.
-    '#states' => [
-      'visible' => [
-        ':input[name="scheduler_publish_touch"]' => ['checked' => FALSE],
-        ':input[name="scheduler_publish_past_date"]' => ['!value' => 'error'],
-      ],
-    ],
-  ];
-
-  // Entity types that do not implement the 'getCreatedTime' method should have
-  // the option set to FALSE and the field disabled. It will be hidden due to
-  // the #states setting above, as scheduler_publish_touch has #access = FALSE.
-  if (!method_exists($contentEntity, 'getCreatedTime')) {
-    $form['scheduler']['publish']['advanced']['scheduler_publish_past_date_created']['#disabled'] = TRUE;
-    $form['scheduler']['publish']['advanced']['scheduler_publish_past_date_created']['#description'] = t('The entity type does not support the change of creation time.');
-    $form['scheduler']['publish']['advanced']['scheduler_publish_past_date_created']['#default_value'] = FALSE;
-  }
-
-  // Unpublishing options.
-  $form['scheduler']['unpublish'] = [
-    '#type' => 'details',
-    '#title' => t('Unpublishing'),
-    '#weight' => 2,
-    '#group' => 'scheduler',
-    '#open' => TRUE,
-  ];
-  $form['scheduler']['unpublish']['scheduler_unpublish_enable'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Enable scheduled unpublishing for %type @plural', $params),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable')),
-  ];
-  $form['scheduler']['unpublish']['scheduler_unpublish_required'] = [
-    '#type' => 'checkbox',
-    '#title' => t('Require scheduled unpublishing'),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'unpublish_required', $config->get('default_unpublish_required')),
-    '#states' => [
-      'visible' => [
-        ':input[name="scheduler_unpublish_enable"]' => ['checked' => TRUE],
-      ],
-    ],
-  ];
-  if ($contentEntityType->isRevisionable()) {
-    $form['scheduler']['unpublish']['scheduler_unpublish_revision'] = [
-      '#type' => 'checkbox',
-      '#title' => t('Create a new revision on unpublishing'),
-      '#default_value' => $type->getThirdPartySetting('scheduler', 'unpublish_revision', $config->get('default_unpublish_revision')),
-      '#states' => [
-        'visible' => [
-          ':input[name="scheduler_unpublish_enable"]' => ['checked' => TRUE],
-        ],
-      ],
-    ];
-  }
-
-  // The 'entity_edit_layout' fieldset contains options to alter the layout of
-  // entity edit pages.
-  $form['scheduler']['entity_edit_layout'] = [
-    '#type' => 'details',
-    '#title' => t('@type edit page', $params),
-    '#weight' => 3,
-    '#group' => 'scheduler',
-    // The #states processing only caters for AND and does not do OR. So to set
-    // the state to visible if either of the boxes are ticked we use the fact
-    // that logical 'X = A or B' is equivalent to 'not X = not A and not B'.
-    '#states' => [
-      '!visible' => [
-        ':input[name="scheduler_publish_enable"]' => ['!checked' => TRUE],
-        ':input[name="scheduler_unpublish_enable"]' => ['!checked' => TRUE],
-      ],
-    ],
-  ];
-  $form['scheduler']['entity_edit_layout']['scheduler_fields_display_mode'] = [
-    '#type' => 'radios',
-    '#title' => t('Display scheduling date input fields in'),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'fields_display_mode', $config->get('default_fields_display_mode')),
-    '#options' => [
-      'vertical_tab' => t('Vertical tab'),
-      'fieldset' => t('Separate fieldset'),
-    ],
-    '#description' => t('Use this option to specify how the scheduler fields are displayed when editing %type @plural', $params),
-  ];
-  $form['scheduler']['entity_edit_layout']['scheduler_expand_fieldset'] = [
-    '#type' => 'radios',
-    '#title' => t('Expand fieldset or vertical tab'),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'expand_fieldset', $config->get('default_expand_fieldset')),
-    '#options' => [
-      'when_required' => t('Expand only when a scheduled date exists or when a date is required'),
-      'always' => t('Always open the fieldset or vertical tab'),
-    ],
-  ];
-  $form['scheduler']['entity_edit_layout']['scheduler_show_message_after_update'] = [
-    '#type' => 'checkbox',
-    '#prefix' => '<strong>' . t('Show message') . '</strong>',
-    '#title' => t('Show a confirmation message when a scheduled %type @singular is saved', $params),
-    '#default_value' => $type->getThirdPartySetting('scheduler', 'show_message_after_update', $config->get('default_show_message_after_update')),
-  ];
-
-  $form['#entity_builders'][] = '_scheduler_form_entity_type_form_builder';
-
-  // Add a custom submit handler to adjust the fields in the form displays.
-  $form['actions']['submit']['#submit'][] = '_scheduler_form_entity_type_submit';
-}
-
-/**
- * Entity builder for the entity type form with scheduler options.
- */
-function _scheduler_form_entity_type_form_builder($entity_type, $type, &$form, FormStateInterface $form_state) {
-  $type->setThirdPartySetting('scheduler', 'expand_fieldset', $form_state->getValue('scheduler_expand_fieldset'));
-  $type->setThirdPartySetting('scheduler', 'fields_display_mode', $form_state->getValue('scheduler_fields_display_mode'));
-  $type->setThirdPartySetting('scheduler', 'publish_enable', $form_state->getValue('scheduler_publish_enable'));
-  $type->setThirdPartySetting('scheduler', 'publish_past_date', $form_state->getValue('scheduler_publish_past_date'));
-  $type->setThirdPartySetting('scheduler', 'publish_past_date_created', $form_state->getValue('scheduler_publish_past_date_created'));
-  $type->setThirdPartySetting('scheduler', 'publish_required', $form_state->getValue('scheduler_publish_required'));
-  $type->setThirdPartySetting('scheduler', 'publish_revision', $form_state->getValue('scheduler_publish_revision'));
-  $type->setThirdPartySetting('scheduler', 'publish_touch', $form_state->getValue('scheduler_publish_touch'));
-  $type->setThirdPartySetting('scheduler', 'show_message_after_update', $form_state->getValue('scheduler_show_message_after_update'));
-  $type->setThirdPartySetting('scheduler', 'unpublish_enable', $form_state->getValue('scheduler_unpublish_enable'));
-  $type->setThirdPartySetting('scheduler', 'unpublish_required', $form_state->getValue('scheduler_unpublish_required'));
-  $type->setThirdPartySetting('scheduler', 'unpublish_revision', $form_state->getValue('scheduler_unpublish_revision'));
-}
-
-/**
- * Entity type form submit handler.
- */
-function _scheduler_form_entity_type_submit($form, FormStateInterface $form_state) {
-  // Get the entity type id (node, media, taxonomy_term, etc.)
-  $entity_type_id = $form_state->getFormObject()->getEntity()->getEntityType()->getBundleOf();
-  // Get the entity bundle id (page, article, image, etc.)
-  $bundle_id = $form_state->getFormObject()->getEntity()->id();
-
-  /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
-  $display_repository = \Drupal::service('entity_display.repository');
-
-  // Get all active display modes. getFormModes() returns the additional modes
-  // then add the default.
-  $all_display_modes = array_keys($display_repository->getFormModes($entity_type_id));
-  $all_display_modes[] = $display_repository::DEFAULT_DISPLAY_MODE;
-
-  $supported_display_modes = \Drupal::service('scheduler.manager')->getPlugin($entity_type_id)->entityFormDisplayModes();
-
-  // Each of the active form display modes may need to be adjusted to add or
-  // remove the scheduler fields depending on the 'enable' setting.
-  foreach ($all_display_modes as $display_mode) {
-    $form_display = $display_repository->getFormDisplay($entity_type_id, $bundle_id, $display_mode);
-
-    // If this bundle is not enabled for scheduled publishing or the form
-    // display mode is not supported then make sure the publish_on field is
-    // disabled in the form display.
-    if (!$form_state->getValue('scheduler_publish_enable') || !in_array($display_mode, $supported_display_modes)) {
-      $form_display->removeComponent('publish_on')->save();
+  foreach (array_keys($form['node_types']['#options']) as $type) {
+    $items = [];
+    if (in_array($type, $publishing_enabled_types)) {
+      $items[] = t('Enabled for publishing');
     }
-    // @todo Find a more robust way to detect that the checkbox was off before.
-    elseif (!$form['scheduler']['publish']['scheduler_publish_enable']['#default_value']) {
-      // If the entity bundle is now enabled for scheduled publishing but was
-      // not enabled before then set the publish_on field to be displayed and
-      // set the widget type to 'datetime_timestamp_no_default'. Only do this
-      // when the checkbox has been changed, because the widget could be altered
-      // or the field moved by a subsequent process or manually by admin.
-      $form_display->setComponent('scheduler_settings', ['weight' => 50])
-        ->setComponent('publish_on', ['type' => 'datetime_timestamp_no_default', 'weight' => 52])->save();
+    if (in_array($type, $unpublishing_enabled_types)) {
+      $items[] = t('Enabled for unpublishing');
     }
-
-    // Do the same for the unpublish_on field.
-    if (!$form_state->getValue('scheduler_unpublish_enable') || !in_array($display_mode, $supported_display_modes)) {
-      $form_display->removeComponent('unpublish_on')->save();
-    }
-    elseif (!$form['scheduler']['unpublish']['scheduler_unpublish_enable']['#default_value']) {
-      $form_display->setComponent('scheduler_settings', ['weight' => 50])
-        ->setComponent('unpublish_on', ['type' => 'datetime_timestamp_no_default', 'weight' => 54])->save();
-    }
-
-    // If the display mode is not supported then also remove the
-    // scheduler_settings group fieldset.
-    if (!in_array($display_mode, $supported_display_modes)) {
-      $form_display->removeComponent('scheduler_settings')->save();
-    }
-  }
-}
-
-/**
- * Form alter handling for Devel Generate forms.
- */
-function _scheduler_devel_generate_form_alter(array &$form, FormStateInterface $form_state, $form_id, $entityTypeId) {
-  // Show which types are enabled for scheduled publishing and unpublishing. If
-  // the form does not have a table but has a selection list instead, then
-  // nothing is added here.
-  $type_table = $entityTypeId . '_types';
-  if (isset($form[$type_table]['#header']) && isset($form[$type_table]['#options'])) {
-    // Add an extra column to the table to show which types are enabled for
-    // scheduled publishing and unpublishing.
-    $scheduler_manager = \Drupal::service('scheduler.manager');
-    $publishing_enabled_types = $scheduler_manager->getEnabledTypes($entityTypeId, 'publish');
-    $unpublishing_enabled_types = $scheduler_manager->getEnabledTypes($entityTypeId, 'unpublish');
-    $form[$type_table]['#header']['scheduler'] = t('Scheduler settings');
-
-    foreach (array_keys($form[$type_table]['#options']) as $type) {
-      $items = [];
-      if (in_array($type, $publishing_enabled_types)) {
-        $items[] = t('Enabled for publishing');
-      }
-      if (in_array($type, $unpublishing_enabled_types)) {
-        $items[] = t('Enabled for unpublishing');
-      }
-      if (empty($items)) {
-        $scheduler_settings = t('None');
-      }
-      else {
-        $scheduler_settings = [
-          'data' => [
-            '#theme' => 'item_list',
-            '#items' => $items,
-          ],
-        ];
-      }
-      $form[$type_table]['#options'][$type]['scheduler'] = $scheduler_settings;
+    if (empty($items)) {
+      $scheduler_settings = t('None');
     }
-  }
-
-  if (!isset($form['time_range'])) {
-    // Add the time range field if it was not added by Devel Generate.
-    $options = [1 => t('Now')];
-    foreach ([3600, 86400, 604800, 2592000, 31536000] as $interval) {
-      $options[$interval] = \Drupal::service('date.formatter')->formatInterval($interval, 1);
+    else {
+      $scheduler_settings = [
+        'data' => [
+          '#theme' => 'item_list',
+          '#items' => $items,
+        ],
+      ];
     }
-    $form['time_range'] = [
-      '#type' => 'select',
-      '#title' => t('How far into the future should the items be scheduled?'),
-      '#description' => t('Scheduled dates will be set randomly within the selected time span.'),
-      '#options' => $options,
-      '#default_value' => 86400,
-    ];
+    $form['node_types']['#options'][$type]['scheduler'] = $scheduler_settings;
   }
 
-  // Add form items to specify what proportion of generated entities should have
-  // a publish-on and/or unpublish-on date assigned. See hook_entity_presave()
-  // for the code that sets these values in the generated entity. Allow for any
-  // previously-executed form_alter to have already set the percentages.
+  // Add form items to specify what proportion of generated nodes should have a
+  // publish-on and unpublish-on date assigned. See hook_node_presave() for the
+  // code which sets the node values.
   $form['scheduler_publishing'] = [
     '#type' => 'number',
     '#title' => t('Publishing date for Scheduler'),
-    '#description' => t('Enter a percentage for randomly selecting Scheduler-enabled entities to be given a publish-on date. Enter 0 for none, 100 for all. The date and time will be random within the range starting at entity creation date, up to a time in the future matching the same span as selected above.'),
-    '#default_value' => $form['scheduler_publishing']['#default_value'] ?? 50,
+    '#description' => t('Enter the percentage of randomly selected Scheduler-enabled nodes to be given a publish-on date. Enter 0 for none, 100 for all. The date and time will be random within the range starting at node creation date, up to a time in the future matching the same span as selected above for node creation date.'),
+    '#default_value' => 50,
     '#required' => TRUE,
     '#min' => 0,
     '#max' => 100,
@@ -685,8 +301,8 @@ function _scheduler_devel_generate_form_alter(array &$form, FormStateInterface $
   $form['scheduler_unpublishing'] = [
     '#type' => 'number',
     '#title' => t('Unpublishing date for Scheduler'),
-    '#description' => t('Enter a percentage for randomly selecting Scheduler-enabled entities to be given an unpublish-on date. Enter 0 for none, 100 for all. The date and time will be random within the range starting at the later of entity creation date and publish-on date, up to a time in the future matching the same span as selected above.'),
-    '#default_value' => $form['scheduler_unpublishing']['#default_value'] ?? 50,
+    '#description' => t('Enter the percentage of randomly selected Scheduler-enabled nodes to be given an unpublish-on date. Enter 0 for none, 100 for all. The date and time will be random within the range starting at the later of node creation date and publish-on date, up to a time in the future matching the same span as selected above for node creation date.'),
+    '#default_value' => 50,
     '#required' => TRUE,
     '#min' => 0,
     '#max' => 100,
@@ -698,54 +314,43 @@ function _scheduler_devel_generate_form_alter(array &$form, FormStateInterface $
  */
 function scheduler_form_language_content_settings_form_alter(array &$form, FormStateInterface $form_state) {
   // Add our validation function for the translation field settings form at
-  // admin/config/regional/content-language
-  // This hook function caters for all entity types, not just nodes.
+  // admin/config/regional/content-language.
   $form['#validate'][] = '_scheduler_translation_validate';
 }
 
 /**
  * Validation handler for language_content_settings_form.
  *
- * For each entity type, if it is translatable and also enabled for Scheduler,
+ * If the content type is translatable and the field is enabled for Scheduler
  * but the translation setting for the publish_on / unpublish_on field does not
  * match the 'published status' field setting then throw a validation error.
  *
  * @see https://www.drupal.org/project/scheduler/issues/2871164
  */
 function _scheduler_translation_validate($form, FormStateInterface $form_state) {
-  $settings = $form_state->getValues()['settings'];
-  /** @var \Drupal\scheduler\SchedulerManager $scheduler_manager */
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-  foreach ($settings as $entity_type => $content_types) {
-    $publishing_enabled_types = $scheduler_manager->getEnabledTypes($entity_type, 'publish');
-    $unpublishing_enabled_types = $scheduler_manager->getEnabledTypes($entity_type, 'unpublish');
-    if (empty($publishing_enabled_types) && empty($publishing_enabled_types)) {
-      continue;
-    }
-
-    $enabled = [];
-    foreach ($content_types as $name => $options) {
-      $enabled['publish_on'] = in_array($name, $publishing_enabled_types);
-      $enabled['unpublish_on'] = in_array($name, $unpublishing_enabled_types);
-      if ($options['translatable'] && ($enabled['publish_on'] || $enabled['unpublish_on'])) {
-        $params = [
-          '@entity' => $form['settings'][$entity_type]['#bundle_label'],
-          '@type' => $form['settings'][$entity_type][$name]['settings']['#label'],
-          '%status' => $form['settings'][$entity_type][$name]['fields']['status']['#label'],
-        ];
-        foreach (['publish_on', 'unpublish_on'] as $var) {
-          $mismatch = $enabled[$var] && ($options['fields'][$var] <> $options['fields']['status']);
-          if ($mismatch) {
-            $params['%scheduler_field'] = $form['settings'][$entity_type][$name]['fields'][$var]['#label'];
-            $message = t("There is a problem with @entity '@type' - The translatable settings for status field '%status' and Scheduler field '%scheduler_field' should match, either both on or both off", $params);
-            $form_state->setErrorByName("settings][$entity_type][$name][fields][status", $message);
-            $form_state->setErrorByName("settings][$entity_type][$name][fields][$var", $message);
-          }
+  $content_types = $form_state->getValues()['settings']['node'];
+  $publishing_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types('publish'));
+  $unpublishing_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types('unpublish'));
+  $enabled = [];
+  foreach ($content_types as $name => $settings) {
+    $enabled['publish_on'] = in_array($name, $publishing_enabled_types);
+    $enabled['unpublish_on'] = in_array($name, $unpublishing_enabled_types);
+    if ($settings['translatable'] && ($enabled['publish_on'] || $enabled['unpublish_on'])) {
+      $params = [
+        '@type' => $form['settings']['node'][$name]['settings']['#label'],
+        '@status' => $form['settings']['node'][$name]['fields']['status']['#label'],
+      ];
+      foreach (['publish_on', 'unpublish_on'] as $var) {
+        $mismatch = $enabled[$var] && ($settings['fields'][$var] <> $settings['fields']['status']);
+        if ($mismatch) {
+          $params['@scheduler_field'] = $form['settings']['node'][$name]['fields'][$var]['#label'];
+          $message = t("Content type '@type' - Translatable settings for status field '@status' and Scheduler field '@scheduler_field' should match, either both on or both off", $params);
+          $form_state->setErrorByName("settings][node][$name][fields][status", $message);
+          $form_state->setErrorByName("settings][node][$name][fields][$var", $message);
         }
       }
     }
   }
-
 }
 
 /**
@@ -753,14 +358,12 @@ function _scheduler_translation_validate($form, FormStateInterface $form_state)
  */
 function scheduler_entity_base_field_info(EntityTypeInterface $entity_type) {
   $fields = [];
-  $entity_types = \Drupal::service('scheduler.manager')->getPluginEntityTypes();
-
-  if (in_array($entity_type->id(), $entity_types)) {
+  if ($entity_type->id() === 'node') {
     $fields['publish_on'] = BaseFieldDefinition::create('timestamp')
       ->setLabel(t('Publish on'))
       ->setDisplayOptions('form', [
         'type' => 'datetime_timestamp_no_default',
-        'region' => 'hidden',
+        'weight' => 30,
       ])
       ->setDisplayConfigurable('form', TRUE)
       ->setTranslatable(TRUE)
@@ -771,34 +374,14 @@ function scheduler_entity_base_field_info(EntityTypeInterface $entity_type) {
       ->setLabel(t('Unpublish on'))
       ->setDisplayOptions('form', [
         'type' => 'datetime_timestamp_no_default',
-        'region' => 'hidden',
+        'weight' => 30,
       ])
       ->setDisplayConfigurable('form', TRUE)
       ->setTranslatable(TRUE)
       ->setRevisionable(TRUE)
       ->addConstraint('SchedulerUnpublishOn');
-  }
 
-  return $fields;
-}
-
-/**
- * Implements hook_action_info_alter().
- */
-function scheduler_action_info_alter(&$definitions) {
-
-  // Workbench Moderation has a bug where the wrong actions are assigned which
-  // causes scheduled publishing of non-moderated content to fail. This fix will
-  // work regardless of the relative weights of the two modules, and will
-  // continue to work even if WBM is fixed before this code is removed.
-  // See https://www.drupal.org/project/workbench_moderation/issues/3238576
-  if (\Drupal::moduleHandler()->moduleExists('workbench_moderation')) {
-    if (isset($definitions['entity:publish_action:node']['class']) && $definitions['entity:publish_action:node']['class'] == ModerationOptOutUnpublishNode::class) {
-      $definitions['entity:publish_action:node']['class'] = ModerationOptOutPublishNode::class;
-    }
-    if (isset($definitions['entity:unpublish_action:node']['class']) && $definitions['entity:unpublish_action:node']['class'] == UnpublishAction::class) {
-      $definitions['entity:unpublish_action:node']['class'] = ModerationOptOutUnpublishNode::class;
-    }
+    return $fields;
   }
 }
 
@@ -809,202 +392,174 @@ function scheduler_views_data_alter(array &$data) {
   // By default the 'is null' and 'is not null' operators are only added to the
   // list of filter options if the view contains a relationship. We want them to
   // be always available for the scheduler date fields.
-  $entity_types = \Drupal::service('scheduler.manager')->getPluginEntityTypes();
-  foreach ($entity_types as $entityTypeId) {
-    // Not every entity that has a plugin will have these tables, so only set
-    // the allow_empty filter if the top-level key exists.
-    if (isset($data["{$entityTypeId}_field_data"])) {
-      $data["{$entityTypeId}_field_data"]['publish_on']['filter']['allow empty'] = TRUE;
-      $data["{$entityTypeId}_field_data"]['unpublish_on']['filter']['allow empty'] = TRUE;
-    }
-    if (isset($data["{$entityTypeId}_field_revision"])) {
-      $data["{$entityTypeId}_field_revision"]['publish_on']['filter']['allow empty'] = TRUE;
-      $data["{$entityTypeId}_field_revision"]['unpublish_on']['filter']['allow empty'] = TRUE;
-    }
-  }
-
-  // Add a relationship from Media Field Revision back to Media Field Data.
-  // @todo This can be removed when the relationship is added to core.
-  // @see https://www.drupal.org/project/drupal/issues/3036192
-  // Replace the existing 'argument' item.
-  $data['media_field_revision']['mid']['argument'] = [
-    'id' => 'media_mid',
-    'numeric' => TRUE,
-  ];
-  // Add a 'relationship' item.
-  $data['media_field_revision']['mid']['relationship'] = [
-    'id' => 'standard',
-    'base' => 'media_field_data',
-    'field' => 'mid',
-    'base field' => 'mid',
-    'title' => t('Media Field Data'),
-    'help' => t('Relationship to access the Media fields that are not on Media Revision.'),
-    'label' => t('Media Field'),
-    'extra' => [
-      [
-        'field' => 'langcode',
-        'left_field' => 'langcode',
-      ],
-    ],
-  ];
+  $data['node_field_data']['publish_on']['filter']['allow empty'] = TRUE;
+  $data['node_field_data']['unpublish_on']['filter']['allow empty'] = TRUE;
+  $data['node_field_revision']['publish_on']['filter']['allow empty'] = TRUE;
+  $data['node_field_revision']['unpublish_on']['filter']['allow empty'] = TRUE;
 }
 
 /**
- * Implements hook_entity_view().
+ * Implements hook_ENTITY_TYPE_view() for node entities.
  */
-function scheduler_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, string $view_mode) {
-  // If the entity is going to be unpublished, then add this information to the
-  // http header for search engines. Only do this when the current page is the
-  // full-page view of the entity.
+function scheduler_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
+  // If the node is going to be unpublished, then add this information to the
+  // header for search engines. Only do this when the current page is the
+  // full-page view of the node.
   // @see https://googleblog.blogspot.be/2007/07/robots-exclusion-protocol-now-with-even.html
-  if ($view_mode == 'full' && isset($entity->unpublish_on->value)) {
-    $unavailable_after = date(DATE_RFC850, $entity->unpublish_on->value);
+  if (!empty($node->unpublish_on->value) && node_is_page($node)) {
+    $unavailable_after = date(DATE_RFC850, $node->unpublish_on->value);
     $build['#attached']['http_header'][] = ['X-Robots-Tag', 'unavailable_after: ' . $unavailable_after];
-
-    // Also add the information as a meta tag in the html head section.
-    $unavailable_meta_tag = [
-      '#tag' => 'meta',
-      '#attributes' => [
-        'name' => 'robots',
-        'content' => 'unavailable_after: ' . $unavailable_after,
-      ],
-    ];
-    // Any value seems to be OK for the second item, but it must not be omitted.
-    $build['#attached']['html_head'][] = [$unavailable_meta_tag, 'robots_unavailable_date'];
   }
 }
 
 /**
- * Implements hook_entity_presave().
+ * Implements hook_ENTITY_TYPE_presave() for node entities.
  */
-function scheduler_entity_presave(EntityInterface $entity) {
+function scheduler_node_presave(EntityInterface $node) {
   $config = \Drupal::config('scheduler.settings');
   $scheduler_manager = \Drupal::service('scheduler.manager');
+  $entity = $node->type->entity;
   $request_time = \Drupal::time()->getRequestTime();
+  $publish_message = FALSE;
+  $unpublish_message = FALSE;
 
-  $publishing_enabled_types = $scheduler_manager->getEnabledTypes($entity->getEntityTypeId(), 'publish');
-  $unpublishing_enabled_types = $scheduler_manager->getEnabledTypes($entity->getEntityTypeId(), 'unpublish');
-  $publishing_enabled = in_array($entity->bundle(), $publishing_enabled_types);
-  $unpublishing_enabled = in_array($entity->bundle(), $unpublishing_enabled_types);
-
-  if (!$publishing_enabled && !$unpublishing_enabled) {
-    // Neither scheduled publishing nor unpublishing are enabled for this
-    // specific bundle/type, so end here.
+  // If there is no entity object or the class is incorrect then stop here. This
+  // should not really happen but it has been observed, so better to be safe.
+  // @see https://www.drupal.org/node/2902512
+  if (is_null($entity) || !get_class($entity) == 'Drupal\node\Entity\NodeType') {
     return;
-  }
+  };
 
-  // If this entity is being created via Devel Generate then set values for the
+  // If this node is being created via Devel Generate then set values for the
   // publish_on and unpublish_on dates as specified in the devel_generate form.
-  if (isset($entity->devel_generate)) {
+  if (isset($node->devel_generate)) {
+    static $publishing_enabled_types;
+    static $unpublishing_enabled_types;
     static $publishing_percent;
     static $unpublishing_percent;
-    static $entity_created;
     static $time_range;
 
-    if (!isset($publishing_percent)) {
+    if (!isset($publishing_enabled_types)) {
+      $publishing_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types('publish'));
+      $unpublishing_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types('unpublish'));
       // The values may not be set if calling via drush, so default to zero.
-      $publishing_percent = @$entity->devel_generate['scheduler_publishing'] ?: 0;
-      $unpublishing_percent = @$entity->devel_generate['scheduler_unpublishing'] ?: 0;
-      $entity_created = isset($entity->created) ? $entity->created->value : $request_time;
-      // Reuse the selected 'creation' time range for our future date span.
-      $time_range = $entity->devel_generate['time_range'];
+      $publishing_percent = @$node->devel_generate['scheduler_publishing'] ?: 0;
+      $unpublishing_percent = @$node->devel_generate['scheduler_unpublishing'] ?: 0;
+      // Reuse the selected 'node creation' time range for our future date span.
+      $time_range = $node->devel_generate['time_range'];
     }
-    if ($publishing_percent && $publishing_enabled) {
+    if ($publishing_percent && in_array($node->getType(), $publishing_enabled_types)) {
       if (rand(1, 100) <= $publishing_percent) {
         // Randomly assign a publish_on value in the range starting with the
         // created date and up to the selected time range in the future.
-        $entity->set('publish_on', rand($entity_created, $request_time + $time_range));
+        $node->set('publish_on', rand($node->created->value + 1, $request_time + $time_range));
       }
     }
-    if ($unpublishing_percent && $unpublishing_enabled) {
+    if ($unpublishing_percent && in_array($node->getType(), $unpublishing_enabled_types)) {
       if (rand(1, 100) <= $unpublishing_percent) {
         // Randomly assign an unpublish_on value in the range from the later of
         // created date/publish_on date up to the time range in the future.
-        $entity->set('unpublish_on', rand(max($entity_created, $entity->publish_on->value), $request_time + $time_range));
+        $node->set('unpublish_on', rand(max($node->created->value, $node->publish_on->value), $request_time + $time_range));
       }
     }
   }
 
-  $publish_message = FALSE;
-  $unpublish_message = FALSE;
-
-  // If the entity type is enabled for scheduled publishing and has a publish_on
+  // If the node type is enabled for scheduled publishing and has a publish_on
   // date then check if publishing is allowed and if the content needs to be
   // published immediately.
-  if ($publishing_enabled && !empty($entity->publish_on->value)) {
-    // Check that other modules allow the action on this entity.
-    $publication_allowed = $scheduler_manager->isAllowed($entity, 'publish');
+  if ($entity->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable')) && !empty($node->publish_on->value)) {
+    // Check that other modules allow the action on this node.
+    $publication_allowed = $scheduler_manager->isAllowed($node, 'publish');
 
-    // Publish the entity immediately if the publication date is in the past.
-    $publish_immediately = $scheduler_manager->getThirdPartySetting($entity, 'publish_past_date', $config->get('default_publish_past_date')) == 'publish';
+    // Publish the node immediately if the publication date is in the past.
+    $publish_immediately = $entity->getThirdPartySetting('scheduler', 'publish_past_date', $config->get('default_publish_past_date')) == 'publish';
 
-    if ($publication_allowed && $publish_immediately && $entity->publish_on->value <= $request_time) {
-      // Trigger the PRE_PUBLISH_IMMEDIATELY event so that modules can react
-      // before the entity has been published.
-      $scheduler_manager->dispatchSchedulerEvent($entity, 'PRE_PUBLISH_IMMEDIATELY');
+    if ($publication_allowed && $publish_immediately && $node->publish_on->value <= $request_time) {
+      // Trigger the PRE_PUBLISH_INMEDIATELY event so that modules can react
+      // before the node has been published.
+      $event = new SchedulerEvent($node);
+      $scheduler_manager->dispatch($event, SchedulerEvents::PRE_PUBLISH_IMMEDIATELY);
+      $node = $event->getNode();
 
       // Set the 'changed' timestamp to match what would have been done had this
       // content been published via cron.
-      if ($entity instanceof EntityChangedInterface) {
-        $entity->setChangedTime($entity->publish_on->value);
-      }
-
+      $node->setChangedTime($node->publish_on->value);
       // If required, set the created date to match published date.
-      if ($scheduler_manager->getThirdPartySetting($entity, 'publish_touch', $config->get('default_publish_touch')) ||
-        ($scheduler_manager->getThirdPartySetting($entity, 'publish_past_date_created', $config->get('default_publish_past_date_created')) && $entity->getCreatedTime() > $entity->publish_on->value)) {
-        $entity->setCreatedTime($entity->publish_on->value);
+      if ($entity->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch')) ||
+      ($node->getCreatedTime() > $node->publish_on->value && $entity->getThirdPartySetting('scheduler', 'publish_past_date_created', $config->get('default_publish_past_date_created')))) {
+        $node->setCreatedTime($node->publish_on->value);
       }
-      $entity->publish_on->value = NULL;
-      $entity->setPublished();
+      $node->publish_on->value = NULL;
+      $node->setPublished();
 
       // Trigger the PUBLISH_IMMEDIATELY event so that modules can react after
-      // the entity has been published.
-      $scheduler_manager->dispatchSchedulerEvent($entity, 'PUBLISH_IMMEDIATELY');
+      // the node has been published.
+      $event = new SchedulerEvent($node);
+      $scheduler_manager->dispatch($event, SchedulerEvents::PUBLISH_IMMEDIATELY);
+      $node = $event->getNode();
     }
     else {
-      // Ensure the entity is unpublished as it will be published by cron later.
-      $entity->setUnpublished();
+      // Ensure the node is unpublished as it will be published by cron later.
+      $node->setUnpublished();
 
-      // Only inform the user that the entity is scheduled if publication has
-      // not been prevented by other modules. Those modules have to display a
+      // Only inform the user that the node is scheduled if publication has not
+      // been prevented by other modules. Those modules have to display a
       // message themselves explaining why publication is denied.
-      $publish_message = ($publication_allowed && $scheduler_manager->getThirdPartySetting($entity, 'show_message_after_update', $config->get('default_show_message_after_update')));
+      $publish_message = ($publication_allowed && $entity->getThirdPartySetting('scheduler', 'show_message_after_update', $config->get('default_show_message_after_update')));
     }
-  } // Entity has a publish_on date.
+  }
 
-  if ($unpublishing_enabled && !empty($entity->unpublish_on->value)) {
+  if ($entity->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable')) && !empty($node->unpublish_on->value)) {
     // Scheduler does not do the same 'immediate' processing for unpublishing.
     // However, the api hook should still be called during presave as there may
     // be messages to be displayed if the unpublishing will be disallowed later.
-    $unpublication_allowed = $scheduler_manager->isAllowed($entity, 'unpublish');
-    $unpublish_message = ($unpublication_allowed && $scheduler_manager->getThirdPartySetting($entity, 'show_message_after_update', $config->get('default_show_message_after_update')));
+    $unpublication_allowed = $scheduler_manager->isAllowed($node, 'unpublish');
+    $unpublish_message = ($unpublication_allowed && $entity->getThirdPartySetting('scheduler', 'show_message_after_update', $config->get('default_show_message_after_update')));
   }
 
   // Give one message, which will include the publish_on date, the unpublish_on
-  // date or both dates. Cannot make the title into a link here when the entity
-  // is being created. But core provides the link in the subsequent message.
+  // date or both dates. Cannot make the title into a link here when the node
+  // is being created. But the node module gives the link in the next message.
   $date_formatter = \Drupal::service('date.formatter');
   if ($publish_message && $unpublish_message) {
     \Drupal::messenger()->addMessage(t('%title is scheduled to be published @publish_time and unpublished @unpublish_time.', [
-      '%title' => $entity->label(),
-      '@publish_time' => $date_formatter->format($entity->publish_on->value, 'long'),
-      '@unpublish_time' => $date_formatter->format($entity->unpublish_on->value, 'long'),
+      '%title' => $node->getTitle(),
+      '@publish_time' => $date_formatter->format($node->publish_on->value, 'long'),
+      '@unpublish_time' => $date_formatter->format($node->unpublish_on->value, 'long'),
     ]), 'status', FALSE);
   }
   elseif ($publish_message) {
     \Drupal::messenger()->addMessage(t('%title is scheduled to be published @publish_time.', [
-      '%title' => $entity->label(),
-      '@publish_time' => $date_formatter->format($entity->publish_on->value, 'long'),
+      '%title' => $node->getTitle(),
+      '@publish_time' => $date_formatter->format($node->publish_on->value, 'long'),
     ]), 'status', FALSE);
   }
   elseif ($unpublish_message) {
     \Drupal::messenger()->addMessage(t('%title is scheduled to be unpublished @unpublish_time.', [
-      '%title' => $entity->label(),
-      '@unpublish_time' => $date_formatter->format($entity->unpublish_on->value, 'long'),
+      '%title' => $node->getTitle(),
+      '@unpublish_time' => $date_formatter->format($node->unpublish_on->value, 'long'),
     ]), 'status', FALSE);
   }
 }
 
+/**
+ * Implements hook_ENTITY_TYPE_insert() for node entities.
+ */
+function scheduler_node_insert(EntityInterface $node) {
+  // Removed RULES code but keep the function. There may be code to add here.
+  // @todo remove this comment when done. JSS Sep 2016.
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_update() for node entities.
+ */
+function scheduler_node_update(EntityInterface $node) {
+  // Removed RULES code but keep the function. There may be code to add here.
+  // This function is currently called by actions SetPublishingDate and
+  // SetUnpublishingDate.
+  // @todo remove this comment when done. JSS Sep 2016.
+}
+
 /**
  * Implements hook_cron().
  */
@@ -1023,21 +578,10 @@ function scheduler_cron() {
   // Scheduler 7.x provided hook_scheduler_api() which has been replaced by
   // event dispatching in 8.x. Display a warning in the log if any of these
   // hooks still exist, so that admins and developers are informed.
-  if (version_compare(\Drupal::VERSION, '9.4', '>=')) {
-    // getImplementations() is deprecated in D9.4, use invokeAllWith().
-    \Drupal::moduleHandler()->invokeAllWith('scheduler_api', function (callable $hook, string $module) {
-      \Drupal::logger('scheduler')->warning('Function %function has not been executed. Implementations of hook_scheduler_api() should be replaced by Scheduler event listeners.', [
-        '%function' => $module . '_scheduler_api',
-      ]);
-    });
-  }
-  else {
-    // Use getImplementations() to maintain compatibility with Drupal 8.9.
-    foreach (Drupal::moduleHandler()->getImplementations('scheduler_api') as $module) {
-      \Drupal::logger('scheduler')->warning('Function %function has not been executed. Implementations of hook_scheduler_api() should be replaced by Scheduler event listeners.', [
-        '%function' => $module . '_scheduler_api',
-      ]);
-    }
+  foreach (Drupal::moduleHandler()->getImplementations('scheduler_api') as $module) {
+    \Drupal::logger('scheduler')->warning('Function %function has not been executed. In Drupal 8, implementations of hook_scheduler_api() should be replaced by Scheduler event listeners.', [
+      '%function' => $module . '_scheduler_api',
+    ]);
   }
 
   // Reset the static scheduler_cron flag.
@@ -1063,56 +607,42 @@ function scheduler_cron_is_running() {
 function scheduler_entity_extra_field_info() {
   $config = \Drupal::config('scheduler.settings');
 
-  $plugins = \Drupal::service('scheduler.manager')->getPlugins();
-
   // Expose the Scheduler group on the 'Manage Form Display' tab when editing a
   // content type. This allows admins to adjust the weight of the group, and it
   // works for vertical tabs and separate fieldsets.
   $fields = [];
-
-  foreach ($plugins as $entityTypeId => $plugin) {
-    $types = $plugin->getTypes();
-    foreach ($types as $type) {
-      $publishing_enabled = $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'));
-      $unpublishing_enabled = $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'));
-
-      if ($publishing_enabled || $unpublishing_enabled) {
-        // Weight 50 puts this below the core fields by default.
-        $fields[$entityTypeId][$type->id()]['form']['scheduler_settings'] = [
-          'label' => t('Scheduler Dates'),
-          'description' => t('Fieldset containing Scheduler Publish-on and Unpublish-on date input fields'),
-          'weight' => 50,
-        ];
-      }
+  foreach (NodeType::loadMultiple() as $type) {
+    $publishing_enabled = $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'));
+    $unpublishing_enabled = $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'));
+
+    if ($publishing_enabled || $unpublishing_enabled) {
+      // Weight 20 puts this below the core fields by default.
+      $fields['node'][$type->get('type')]['form']['scheduler_settings'] = [
+        'label' => t('Scheduler Dates'),
+        'description' => t('Fieldset containing Scheduler Publish-on and Unpublish-on date input fields'),
+        'weight' => 20,
+      ];
     }
   }
-
   return $fields;
 }
 
 /**
- * Implements hook_preprocess().
+ * Prepares variables for node templates.
+ *
+ * Makes the publish_on and unpublish_on data available as theme variables.
+ *
+ * @see template_preprocess_node()
  */
-function scheduler_preprocess(&$variables, $hook) {
-  // For entity types that can be processed by Scheduler add the formatted
-  // publish_on and unpublish_on dates as variables for use in theme templates.
-  $plugins = &drupal_static(__FUNCTION__);
-  if (empty($plugins)) {
-    $plugins = \Drupal::service('scheduler.manager')->getPluginEntityTypes();
+function scheduler_preprocess_node(&$variables) {
+  $date_formatter = \Drupal::service('date.formatter');
+  /** @var \Drupal\node\NodeInterface $node */
+  $node = $variables['node'];
+  if (!empty($node->publish_on->value) && $node->publish_on->value && is_numeric($node->publish_on->value)) {
+    $variables['publish_on'] = $date_formatter->format($node->publish_on->value, 'long');
   }
-  // For $hook = 'node' and 'media' the entity is stored in $variables[$hook].
-  // This is not guaranteed, for example in commerce_product the entity is in
-  // $variables['product_entity']. This could be extracted if there is a need,
-  // but for now just skip if the entity is not immediately available.
-  if (in_array($hook, $plugins) && isset($variables[$hook])) {
-    $date_formatter = \Drupal::service('date.formatter');
-    $entity = $variables[$hook];
-    if (!empty($entity->publish_on->value) && $entity->publish_on->value && is_numeric($entity->publish_on->value)) {
-      $variables['publish_on'] = $date_formatter->format($entity->publish_on->value, 'long');
-    }
-    if (!empty($entity->unpublish_on->value) && $entity->unpublish_on->value && is_numeric($entity->unpublish_on->value)) {
-      $variables['unpublish_on'] = $date_formatter->format($entity->unpublish_on->value, 'long');
-    }
+  if (!empty($node->unpublish_on->value) && $node->unpublish_on->value && is_numeric($node->unpublish_on->value)) {
+    $variables['unpublish_on'] = $date_formatter->format($node->unpublish_on->value, 'long');
   }
 }
 
@@ -1122,39 +652,29 @@ function scheduler_preprocess(&$variables, $hook) {
  * This function exposes publish_on and unpublish_on as mappable targets to the
  * Feeds module.
  *
- * @see https://www.drupal.org/project/feeds
- *
  * @todo Port to Drupal 8.
  *
- * @see https://www.drupal.org/project/scheduler/issues/2651354
+ * @see https://www.drupal.org/node/2651354
  */
 function scheduler_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
-  // The plugins processing has not been tested, because the function has not
-  // been converted to Drupal 8. The processing below will need to be adjusted
-  // depending on whether $entity_type is a string or an object.
-  // See scheduler_entity_extra_field_info() for an example.
-  // May need to use $scheduler_manager->getThirdPartySetting.
-  $plugins = &drupal_static(__FUNCTION__);
-  if (empty($plugins)) {
-    $plugins = \Drupal::service('scheduler.manager')->getPluginEntityTypes();
-  }
-  if (in_array($entity_type, $plugins)) {
-    $config = \Drupal::config('scheduler.settings');
-    // @todo Get the entity object if $entity_type is a string.
+  $config = \Drupal::config('scheduler.settings');
+
+  // Scheduler module only works on nodes.
+  if ($entity_type == 'node') {
     $publishing_enabled = $entity_type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'));
     $unpublishing_enabled = $entity_type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'));
 
     if ($publishing_enabled) {
       $targets['publish_on'] = [
         'name' => t('Scheduler: publish on'),
-        'description' => t('The date when the Scheduler module will publish the content.'),
+        'description' => t('The date when the Scheduler module will publish the node.'),
         'callback' => 'scheduler_feeds_set_target',
       ];
     }
     if ($unpublishing_enabled) {
       $targets['unpublish_on'] = [
         'name' => t('Scheduler: unpublish on'),
-        'description' => t('The date when the Scheduler module will unpublish the content.'),
+        'description' => t('The date when the Scheduler module will unpublish the node.'),
         'callback' => 'scheduler_feeds_set_target',
       ];
     }
@@ -1166,7 +686,7 @@ function scheduler_feeds_processor_targets_alter(&$targets, $entity_type, $bundl
  *
  * @todo Port to Drupal 8.
  *
- * @see https://www.drupal.org/project/scheduler/issues/2651354
+ * @see https://www.drupal.org/node/2651354
  */
 function scheduler_feeds_set_target($source, $entity, $target, $value, $mapping) {
   // We expect a string or integer, but can accomodate an array, by taking the
@@ -1186,242 +706,26 @@ function scheduler_feeds_set_target($source, $entity, $target, $value, $mapping)
     $timestamp = $value;
   }
 
-  // If the timestamp is valid, use it to set the target field in the entity.
-  if (is_numeric($timestamp)) {
+  // If the timestamp is valid then use it to set the target field in the node.
+  if (is_numeric($timestamp) && $timestamp > 0) {
     $entity->$target = $timestamp;
   }
 }
 
 /**
- * Implements hook_modules_installed().
- */
-function scheduler_modules_installed($modules) {
-  /** @var \Drupal\scheduler\SchedulerManager $scheduler_manager */
-  $scheduler_manager = \Drupal::service('scheduler.manager');
-  $scheduler_manager->invalidatePluginCache();
-
-  // If there is a Scheduler plugin for a newly installed module then update
-  // the base tables by adding publish_on and unpublish_on for that entity type,
-  // and load/refresh the scheduled view. Third-party modules can provide
-  // Scheduler plugins for entity types that are not defined by that module, or
-  // that do not have the same id as the module name. Similarly, core modules
-  // define entity types for which Scheduler provides the plugin. Hence we need
-  // to check both the plugin entity type and the provider and if either of
-  // these match a module that is being installed we run the update functions.
-  $matches = [];
-  $plugin_definitions = $scheduler_manager->getPluginDefinitions();
-  foreach ($plugin_definitions as $definition) {
-    // If the plugin entity type, provider or dependency match any of the
-    // modules being installed then add the entity type to the $matches list.
-    if (array_intersect([$definition['entityType'], $definition['provider'], $definition['dependency']], $modules)) {
-      $matches[] = $definition['entityType'];
-    }
-  }
-  if (!empty($matches)) {
-    // Add the database fields.
-    $scheduler_manager->entityUpdate();
-    // Load/refresh the scheduler view.
-    $scheduler_manager->viewsUpdate($matches);
-  }
-}
-
-/**
- * Implements hook_cache_flush().
- */
-function scheduler_cache_flush() {
-  \Drupal::service('scheduler.manager')->invalidatePluginCache();
-}
-
-/**
- * Implements hook_migrate_prepare_row().
- */
-function scheduler_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
-  // getMigrationTags can return null for a custom migration, so skip these.
-  if (is_null($migration->getMigrationTags())) {
-    return;
-  }
-
-  // Process node type third-party-settings.
-  if (in_array('Scheduler Settings', $migration->getMigrationTags())) {
-    $scheduler_config_name = [
-      'scheduler_expand_fieldset_' . $row->getSourceProperty('type'),
-      'scheduler_publish_enable_' . $row->getSourceProperty('type'),
-      'scheduler_publish_past_date_' . $row->getSourceProperty('type'),
-      'scheduler_publish_required_' . $row->getSourceProperty('type'),
-      'scheduler_publish_revision_' . $row->getSourceProperty('type'),
-      'scheduler_publish_touch_' . $row->getSourceProperty('type'),
-      'scheduler_unpublish_enable_' . $row->getSourceProperty('type'),
-      'scheduler_unpublish_required_' . $row->getSourceProperty('type'),
-      'scheduler_unpublish_revision_' . $row->getSourceProperty('type'),
-      'scheduler_use_vertical_tabs_' . $row->getSourceProperty('type'),
-    ];
-    $query = $source->getDatabase()->select('variable', 'v')
-      ->fields('v', ['name', 'value'])
-      ->condition('name', $scheduler_config_name, 'IN');
-    $settings = $query->execute()->fetchAll();
-    $third_party_settings = [];
-    foreach ($settings as $setting) {
-      // Remove 'scheduler_' from the start and '_{type}' from the end to get
-      // the short config name from the full $setting->name.
-      $config_name = str_replace(
-        ['scheduler_', '_' . $row->getSourceProperty('type')],
-        ['', ''],
-        $setting->name
-      );
-      // Two third-party-settings have changed name or values, so convert these.
-      if ($config_name == 'use_vertical_tabs') {
-        if (unserialize($setting->value, ['allowed_classes' => FALSE]) == 1) {
-          $third_party_settings['fields_display_mode'] = 'vertical_tab';
-        }
-        else {
-          $third_party_settings['fields_display_mode'] = 'fieldset';
-        }
-      }
-      elseif ($config_name == 'expand_fieldset') {
-        if (unserialize($setting->value, ['allowed_classes' => FALSE]) == 1) {
-          $third_party_settings['expand_fieldset'] = 'always';
-        }
-        else {
-          $third_party_settings['expand_fieldset'] = 'when_required';
-        }
-      }
-      else {
-        // The remaining eight settings are exactly the same as in Drupal 7.
-        $third_party_settings[$config_name] = unserialize($setting->value, ['allowed_classes' => FALSE]);
-      }
-    }
-    $row->setSourceProperty('scheduler_third_party_settings', $third_party_settings);
-  }
-
-  // Process publish_on and unpublish_on dates.
-  if (in_array('Scheduler Data', $migration->getMigrationTags())) {
-    $database_connection = $source->getDatabase();
-    $result = $database_connection->select('scheduler', 's')
-      ->fields('s', ['publish_on', 'unpublish_on'])
-      ->condition('nid', $row->getSourceProperty('nid'))
-      ->execute()
-      ->fetch();
-    if ($result && $result->publish_on) {
-      $row->setSourceProperty('scheduler_publish_on', $result->publish_on);
-    }
-    if ($result && $result->unpublish_on) {
-      $row->setSourceProperty('scheduler_unpublish_on', $result->unpublish_on);
-    }
-  }
-}
-
-/**
- * Implements hook_migration_plugins_alter().
- */
-function scheduler_migration_plugins_alter(array &$migrations) {
-  $migration_plugin_manager = \Drupal::service('plugin.manager.migration');
-  // Create a stub migration with the variable source plugin.
-  // We cannot use MigrationDeriverTrait::getSourcePlugin() directly, because
-  // in PHP 8.1+ calling trait methods is deprecated.
-  // See \Drupal\migrate\Plugin\MigrationDeriverTrait::getSourcePlugin().
-  try {
-    $stub_migration = $migration_plugin_manager->createStubMigration([
-      'source' => [
-        'ignore_map' => TRUE,
-        'plugin' => 'variable',
-        'variables' => [],
-      ],
-      'idMap' => ['plugin' => 'null'],
-      'destination' => ['plugin' => 'null'],
-    ]);
-    // The 'variables' key in source is required (but can be empty) to avoid the
-    // following getSourcePlugin() failing with unknown index 'variables'.
-    $variable_source = $stub_migration->getSourcePlugin();
-    $variable_source->checkRequirements();
-  }
-  catch (RequirementsException $e) {
-    \Drupal::logger('scheduler')->notice('Scheduler settings cannot be migrated due to RequirementsException: %message, %requirements, line %line in %file', [
-      '%message' => $e->getMessage(),
-      '%requirements' => $e->getRequirementsString(),
-      '%line' => $e->getLine(),
-      '%file' => $e->getFile(),
-    ]);
-    return;
-  }
-  catch (PluginException $e) {
-    // The 'variable' source plugin isn't available because Migrate Drupal
-    // isn't enabled. There is nothing we can do.
-    return;
-  }
-  // Before migrating Scheduler, check if the module is enabled.
-  assert($variable_source instanceof DrupalSqlBase);
-  $scheduler_enabled = !empty($variable_source->getSystemData()['module']['scheduler']['status']);
-  if (!$scheduler_enabled) {
-    return;
-  }
-
-  $node_type_migrations = array_filter(
-    $migrations,
-    function ($definition) {
-      return $definition['id'] === 'd7_node_type';
-    }
-  );
-
-  foreach (array_keys($node_type_migrations) as $plugin_id) {
-    $migrations[$plugin_id]['process']['third_party_settings/scheduler'] = 'scheduler_third_party_settings';
-    $migrations[$plugin_id]['migration_tags'][] = 'Scheduler Settings';
-  }
-
-  $node_migrations = array_filter(
-    $migrations,
-    function ($definition) {
-      return $definition['id'] === 'd7_node_complete' || $definition['id'] === 'd7_node';
-    }
-  );
-
-  foreach (array_keys($node_migrations) as $plugin_id) {
-    $migrations[$plugin_id]['process']['publish_on'] = 'scheduler_publish_on';
-    $migrations[$plugin_id]['process']['unpublish_on'] = 'scheduler_unpublish_on';
-    $migrations[$plugin_id]['migration_tags'][] = 'Scheduler Data';
-  }
-
-}
-
-/**
- * Implements hook_local_tasks_alter().
+ * Returns all content types for which scheduler has been enabled.
+ *
+ * @param string $action
+ *   The action that needs to be checked. Can be 'publish' or 'unpublish'.
+ *
+ * @return \Drupal\node\NodeTypeInterface[]
+ *   Array of NodeTypeInterface objects
  */
-function scheduler_local_tasks_alter(&$local_tasks) {
-  // If the default local tasks for the overviews are also provided by another
-  // module or by Core, then remove the ones added by Scheduler in
-  // src/Plugin/Derivative/DynamicLocalTasks. This is to avoid duplicate links
-  // if this core issue gets committed at some time.
-  // @see https://www.drupal.org/project/drupal/issues/3199682
-
-  // Get the list of routes to check.
-  $routes_to_check = \Drupal::service('scheduler.manager')->getCollectionRoutes();
-
-  // Find all the local tasks with the routes we are searching for and that have
-  // a parent_id. These will be the links that are potential duplicates.
-  $found = [];
-  foreach ($local_tasks as $key => $value) {
-    foreach ($routes_to_check as $route) {
-      if ($value['route_name'] == $route && !empty($value['parent_id'])) {
-        // Save the key of the $local_tasks array in a two level array, keyed on
-        // the route and the module that provided it.
-        $found[$route][$value['provider']] = $key;
-      }
-    }
-  }
-
-  // If there is more than one for any of the routes being checked then remove
-  // the route added by Scheduler.
-  foreach ($found as $route => $data) {
-    if (count($data) > 1) {
-      unset($local_tasks[$data['scheduler']]);
-      unset($data['scheduler']);
-    }
-    // We assume that the duplicates are only caused by Scheduler. Other modules
-    // could be causing more so log this and solve it later if it ever happens.
-    if (count($data) > 1) {
-      \Drupal::logger('scheduler')->warning('Local task route %route contains duplicates in addition to Scheduler. %data', [
-        '%route' => $route,
-        '%data' => print_r($data, TRUE),
-      ]);
-    }
-  }
+function _scheduler_get_scheduler_enabled_node_types($action) {
+  $config = \Drupal::config('scheduler.settings');
+  $node_types = NodeType::loadMultiple();
+  return array_filter($node_types, function ($bundle) use ($action, $config) {
+    /** @var \Drupal\node\NodeTypeInterface $bundle */
+    return $bundle->getThirdPartySetting('scheduler', $action . '_enable', $config->get('default_' . $action . '_enable'));
+  });
 }
diff --git a/web/modules/scheduler/scheduler.permissions.yml b/web/modules/scheduler/scheduler.permissions.yml
index fe7220ab427c37d58ae7122459f823a803c20133..5e36688fa57a312bcd99692fa463a92dcdce9dd2 100644
--- a/web/modules/scheduler/scheduler.permissions.yml
+++ b/web/modules/scheduler/scheduler.permissions.yml
@@ -1,7 +1,9 @@
 'administer scheduler':
   title: 'Administer scheduler'
-  description: 'Configure scheduler - set default times, lightweight cron.'
-# All other permissions are dynamically created for each supported entity type.
-# See src/SchedulerPermissions.php
-permission_callbacks:
-  - Drupal\scheduler\SchedulerPermissions::permissions
+  description: 'Configure scheduler date formats, pop-up calendar, default times, lightweight cron'
+'schedule publishing of nodes':
+  title: 'Schedule content publication'
+  description: 'Allows users to set a start and end time for content publication'
+'view scheduled content':
+  title: 'View scheduled content list'
+  description: 'Allows users to see all content which is scheduled.'
diff --git a/web/modules/scheduler/scheduler.services.yml b/web/modules/scheduler/scheduler.services.yml
index a889b50d9dfec97bc1ee11991fcc09a4f4a8fb3f..b849e4fd1d95403be1321aeb53277f6d92e01d4e 100644
--- a/web/modules/scheduler/scheduler.services.yml
+++ b/web/modules/scheduler/scheduler.services.yml
@@ -9,22 +9,16 @@ services:
       - '@config.factory'
       - '@event_dispatcher'
       - '@datetime.time'
-      - '@entity_field.manager'
-      - '@plugin.manager.scheduler'
   logger.channel.scheduler:
     class: Drupal\Core\Logger\LoggerChannel
     factory: logger.factory:get
     arguments: ['scheduler']
+  access_checker.scheduler_content:
+    class: Drupal\scheduler\Access\ScheduledListAccess
+    arguments: ['@current_route_match']
+    tags:
+      - { name: access_check }
   theme.negotiator.scheduler:
     class: Drupal\scheduler\Theme\SchedulerThemeNegotiator
     tags:
       - { name: theme_negotiator, priority: 10 }
-  plugin.manager.scheduler:
-    class: Drupal\scheduler\SchedulerPluginManager
-    parent: default_plugin_manager
-    arguments:
-      - '@entity_type.manager'
-  scheduler.route_subscriber:
-    class: Drupal\scheduler\Routing\SchedulerRouteSubscriber
-    tags:
-      - { name: event_subscriber }
diff --git a/web/modules/scheduler/scheduler.tokens.inc b/web/modules/scheduler/scheduler.tokens.inc
index c6b19ba3cf4ce58d11d8b96c65ace3f81f2b7597..873e6a349fd4e4b4d07d0a0ba846b392940948fe 100644
--- a/web/modules/scheduler/scheduler.tokens.inc
+++ b/web/modules/scheduler/scheduler.tokens.inc
@@ -11,22 +11,16 @@
  * Implements hook_token_info().
  */
 function scheduler_token_info() {
-
-  $plugin_types = \Drupal::service('scheduler.manager')->getPluginEntityTypes();
-  // Initialise the array to avoid 'variable is undefined' phpcs error.
-  $info = [];
-  foreach ($plugin_types as $type) {
-    $info['tokens'][$type]['scheduler-publish'] = [
-      'name' => t('Publish on date'),
-      'description' => t("The date the %type will be published.", ['%type' => $type]),
-      'type' => 'date',
-    ];
-    $info['tokens'][$type]['scheduler-unpublish'] = [
-      'name' => t('Unpublish on date'),
-      'description' => t("The date the %type will be unpublished.", ['%type' => $type]),
-      'type' => 'date',
-    ];
-  }
+  $info['tokens']['node']['scheduler-publish'] = [
+    'name' => t('Publish on date'),
+    'description' => t("The date the node will be published."),
+    'type' => 'date',
+  ];
+  $info['tokens']['node']['scheduler-unpublish'] = [
+    'name' => t('Unpublish on date'),
+    'description' => t("The date the node will be unpublished."),
+    'type' => 'date',
+  ];
 
   return $info;
 }
@@ -37,36 +31,34 @@ function scheduler_token_info() {
 function scheduler_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
   $token_service = \Drupal::token();
   $date_formatter = \Drupal::service('date.formatter');
-  $language_code = $options['langcode'] ?? NULL;
+  $language_code = isset($options['langcode']) ? $options['langcode'] : NULL;
   $replacements = [];
 
-  $plugin_types = \Drupal::service('scheduler.manager')->getPluginEntityTypes();
-
-  if (in_array($type, $plugin_types) && !empty($data[$type])) {
-    $entity = $data[$type];
+  if ($type == 'node' && !empty($data['node'])) {
+    $node = $data['node'];
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'scheduler-publish':
-          if (isset($entity->publish_on->value)) {
-            $replacements[$original] = $date_formatter->format($entity->publish_on->value, 'medium', '', NULL, $language_code);
+          if (isset($node->publish_on->value)) {
+            $replacements[$original] = $date_formatter->format($node->publish_on->value, 'medium', '', NULL, $language_code);
           }
           break;
 
         case 'scheduler-unpublish':
-          if (isset($entity->unpublish_on->value)) {
-            $replacements[$original] = $date_formatter->format($entity->unpublish_on->value, 'medium', '', NULL, $language_code);
+          if (isset($node->unpublish_on->value)) {
+            $replacements[$original] = $date_formatter->format($node->unpublish_on->value, 'medium', '', NULL, $language_code);
           }
           break;
       }
     }
 
     // Chained token replacement.
-    if (isset($entity->publish_on->value) && $publish_tokens = $token_service->findWithPrefix($tokens, 'scheduler-publish')) {
-      $replacements += $token_service->generate('date', $publish_tokens, ['date' => $entity->publish_on->value], $options, $bubbleable_metadata);
+    if (isset($node->publish_on->value) && $publish_tokens = $token_service->findWithPrefix($tokens, 'scheduler-publish')) {
+      $replacements += $token_service->generate('date', $publish_tokens, ['date' => $node->publish_on->value], $options, $bubbleable_metadata);
     }
-    if (isset($entity->unpublish_on->value) && $unpublish_tokens = $token_service->findWithPrefix($tokens, 'scheduler-unpublish')) {
-      $replacements += $token_service->generate('date', $unpublish_tokens, ['date' => $entity->unpublish_on->value], $options, $bubbleable_metadata);
+    if (isset($node->unpublish_on->value) && $unpublish_tokens = $token_service->findWithPrefix($tokens, 'scheduler-unpublish')) {
+      $replacements += $token_service->generate('date', $unpublish_tokens, ['date' => $node->unpublish_on->value], $options, $bubbleable_metadata);
     }
   }
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/composer.json b/web/modules/scheduler/scheduler_rules_integration/composer.json
index fc4b40235fb14f837052f523ebb68d50b7ed67f1..d2903917172ad52d08c8d3b7afc074ecd383593c 100644
--- a/web/modules/scheduler/scheduler_rules_integration/composer.json
+++ b/web/modules/scheduler/scheduler_rules_integration/composer.json
@@ -5,6 +5,6 @@
     "license": "GPL-2.0-or-later",
     "require": {
         "drupal/rules": "^3",
-        "drupal/scheduler": ">=2"
+        "drupal/scheduler": "^1.0"
     }
 }
diff --git a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
index c7511cc77e87e461ae1a798249cca74f239f4e39..f930768dbc52c8fe5ad871fa2687d818fab1c4a6 100644
--- a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
+++ b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
@@ -1,12 +1,13 @@
 name: Scheduler Rules Integration
 type: module
 description: 'Scheduler sub-module providing conditions, actions and events for use with the Rules module.'
-core_version_requirement: ^8 || ^9 || ^10
+core: 8.x
+core_version_requirement: ^8 || ^9
 dependencies:
   - rules:rules
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2022-11-20
-version: '2.0.0-rc8'
+# Information added by Drupal.org packaging script on 2021-07-19
+version: '8.x-1.4'
 project: 'scheduler'
-datestamp: 1668951020
+datestamp: 1626701769
diff --git a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.module b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.module
index 1e8f38e86cff99241d3ab1c05a5a947c13759d9c..fee05c910807911840e3c3f296a5800d9734aba8 100644
--- a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.module
+++ b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.module
@@ -11,78 +11,64 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\scheduler_rules_integration\Event\ExistingNodeIsScheduledForPublishingEvent;
+use Drupal\scheduler_rules_integration\Event\ExistingNodeIsScheduledForUnpublishingEvent;
+use Drupal\scheduler_rules_integration\Event\NewNodeIsScheduledForPublishingEvent;
+use Drupal\scheduler_rules_integration\Event\NewNodeIsScheduledForUnpublishingEvent;
+use Drupal\scheduler_rules_integration\Event\SchedulerHasPublishedThisNodeEvent;
+use Drupal\scheduler_rules_integration\Event\SchedulerHasUnpublishedThisNodeEvent;
 
 /**
- * Dispatch a Rules Integration event for an entity.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object being processed.
- * @param string $event_id
- *   The internal event id, for example NEW_FOR_PUBLISHING or CRON_PUBLISHED.
+ * Implements hook_ENTITY_TYPE_insert() for node entities.
  */
-function _scheduler_rules_integration_event(EntityInterface $entity, $event_id) {
-  // Derive the fully namespaced event class for the given type of entity. The
-  // entity type id may contain underscores and these need to be converted to
-  // camelCase to match the event class. For example the class for 'node' is
-  // simply RulesNodeEvent, but the class for commerce_product is
-  // RulesCommerceProductEvent.
-  $camelCaseEntityType = str_replace(' ', '', ucwords(str_replace('_', ' ', $entity->getEntityTypeId())));
-  $event_class = "\Drupal\scheduler_rules_integration\Event\Rules{$camelCaseEntityType}Event";
-  $event = new $event_class($entity);
-  $event_name = constant(get_class($event) . "::$event_id");
-  \Drupal::service('scheduler.manager')->dispatch($event, $event_name);
-}
-
-/**
- * Trigger Rules events during cron.
- *
- * This function is called from the main Scheduler module publish() and
- * unpublish() functions in the SchedulerManager class.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object being processed.
- * @param string $action
- *   The action being performed - 'publish' or 'unpublish'.
- */
-function _scheduler_rules_integration_dispatch_cron_event(EntityInterface $entity, $action) {
-  $event_id = strtoupper("CRON_{$action}ED");
-  _scheduler_rules_integration_event($entity, $event_id);
+function scheduler_rules_integration_node_insert(EntityInterface $node) {
+  // Invoke the Rules events to indicate that a new node has been scheduled.
+  $scheduler_manager = \Drupal::service('scheduler.manager');
+  if (!empty($node->publish_on->value)) {
+    // @todo In 7.x we had the dates as parameters. These are available in Rules via node.publish_on.value so maybe we do not need the parms?
+    $event = new NewNodeIsScheduledForPublishingEvent($node);
+    $scheduler_manager->dispatch($event, NewNodeIsScheduledForPublishingEvent::EVENT_NAME);
+  }
+  if (!empty($node->unpublish_on->value)) {
+    $event = new NewNodeIsScheduledForUnpublishingEvent($node);
+    $scheduler_manager->dispatch($event, NewNodeIsScheduledForUnpublishingEvent::EVENT_NAME);
+  }
 }
 
 /**
- * Implements hook_entity_insert().
+ * Implements hook_ENTITY_TYPE_update() for node entities.
  */
-function scheduler_rules_integration_entity_insert(EntityInterface $entity) {
-  // Invoke the Rules events to indicate that a new entity has been scheduled.
+function scheduler_rules_integration_node_update(EntityInterface $node) {
+  // Invoke Rules events to indicate that an existing node has been scheduled.
   $scheduler_manager = \Drupal::service('scheduler.manager');
-  // If this entity type is is not supported by Scheduler then go further.
-  if (!$scheduler_manager->getPlugin($entity->getEntityTypeId())) {
-    return;
-  }
-  if (!empty($entity->publish_on->value)) {
-    _scheduler_rules_integration_event($entity, 'NEW_FOR_PUBLISHING');
+  if (!empty($node->publish_on->value)) {
+    $event = new ExistingNodeIsScheduledForPublishingEvent($node, ['node' => $node]);
+    $scheduler_manager->dispatch($event, ExistingNodeIsScheduledForPublishingEvent::EVENT_NAME);
   }
-  if (!empty($entity->unpublish_on->value)) {
-    _scheduler_rules_integration_event($entity, 'NEW_FOR_UNPUBLISHING');
+  if (!empty($node->unpublish_on->value)) {
+    $event = new ExistingNodeIsScheduledForUnpublishingEvent($node);
+    $scheduler_manager->dispatch($event, ExistingNodeIsScheduledForUnpublishingEvent::EVENT_NAME);
   }
 }
 
 /**
- * Implements hook_entity_update().
+ * Trigger Rules events during cron.
+ *
+ * This function is called from the main Scheduler module publish() and
+ * unpublish() functions in the SchedulerManager class.
  */
-function scheduler_rules_integration_entity_update(EntityInterface $entity) {
+function _scheduler_rules_integration_dispatch_cron_event(EntityInterface $node, $event_type) {
   $scheduler_manager = \Drupal::service('scheduler.manager');
-  // If this entity type is is not supported by Scheduler then go further.
-  if (!$scheduler_manager->getPlugin($entity->getEntityTypeId())) {
-    return;
+  if ($event_type == 'publish') {
+    // Invoke the event to tell Rules that Scheduler has published this node.
+    // @todo 2nd param $publish_on may be needed as the date will no longer be on the node
+    $event = new SchedulerHasPublishedThisNodeEvent($node);
+    $scheduler_manager->dispatch($event, SchedulerHasPublishedThisNodeEvent::EVENT_NAME);
   }
-
-  // Invoke Rules events to indicate that an existing entity has been scheduled.
-  if (!empty($entity->publish_on->value)) {
-    _scheduler_rules_integration_event($entity, 'EXISTING_FOR_PUBLISHING');
-  }
-
-  if (!empty($entity->unpublish_on->value)) {
-    _scheduler_rules_integration_event($entity, 'EXISTING_FOR_UNPUBLISHING');
+  elseif ($event_type == 'unpublish') {
+    // Invoke the event to tell Rules that Scheduler has unpublished this node.
+    // @todo 2nd param $publish_on may be needed as the date will no longer be on the node
+    $event = new SchedulerHasUnpublishedThisNodeEvent($node);
+    $scheduler_manager->dispatch($event, SchedulerHasUnpublishedThisNodeEvent::EVENT_NAME);
   }
 }
diff --git a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules.events.yml b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules.events.yml
index f60ded9ddabb57deb35126e6ff547c3b2c3156d4..faeee74d0d31dcc7b20a12d7316679f9c07f1566 100644
--- a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules.events.yml
+++ b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules.events.yml
@@ -1,16 +1,26 @@
-# Six events dispatched for node entity types.
 scheduler_new_node_is_scheduled_for_publishing_event:
-  label: 'After saving a new content item that is scheduled for publishing'
-  category: 'Content (Scheduler)'
+  label: 'After saving new content that is scheduled for publishing'
+  category: 'Scheduler'
   context_definitions:
     node:
       type: 'entity:node'
       label: 'Scheduled Content Node'
       description: 'The node object representing the scheduled content'
+# These parameters were in the 7.x version. May not be needed, but left here
+# for reference until decision is made.
+# @TODO Add the parameters or remove these commented lines. Sep 2016
+#    publish_on:
+#      type: 'integer'
+#      label: 'Scheduler Publish On date'
+#      description: 'Date and time that the node will be published by Scheduler'
+#    unpublish_on:
+#      type: 'integer'
+#      label: 'Scheduler Unpublish On date'
+#      description: 'Date and time that the node will be unpublished by Scheduler'
 
 scheduler_existing_node_is_scheduled_for_publishing_event:
-  label: 'After updating a content item that is scheduled for publishing'
-  category: 'Content (Scheduler)'
+  label: 'After updating existing content that is scheduled for publishing'
+  category: 'Scheduler'
   context_definitions:
     node:
       type: 'entity:node'
@@ -18,8 +28,8 @@ scheduler_existing_node_is_scheduled_for_publishing_event:
       description: 'The node object representing the scheduled content'
 
 scheduler_new_node_is_scheduled_for_unpublishing_event:
-  label: 'After saving a new content item that is scheduled for unpublishing'
-  category: 'Content (Scheduler)'
+  label: 'After saving new content that is scheduled for unpublishing'
+  category: 'Scheduler'
   context_definitions:
     node:
       type: 'entity:node'
@@ -27,8 +37,8 @@ scheduler_new_node_is_scheduled_for_unpublishing_event:
       description: 'The node object representing the scheduled content'
 
 scheduler_existing_node_is_scheduled_for_unpublishing_event:
-  label: 'After updating a content item that is scheduled for unpublishing'
-  category: 'Content (Scheduler)'
+  label: 'After updating existing content that is scheduled for unpublishing'
+  category: 'Scheduler'
   context_definitions:
     node:
       type: 'entity:node'
@@ -36,8 +46,8 @@ scheduler_existing_node_is_scheduled_for_unpublishing_event:
       description: 'The node object representing the scheduled content'
 
 scheduler_has_published_this_node_event:
-  label: 'After Scheduler has published a content item'
-  category: 'Content (Scheduler)'
+  label: 'After a node has been published by Scheduler'
+  category: 'Scheduler'
   context_definitions:
     node:
       type: 'entity:node'
@@ -45,17 +55,10 @@ scheduler_has_published_this_node_event:
       description: 'The node object representing the scheduled content'
 
 scheduler_has_unpublished_this_node_event:
-  label: 'After Scheduler has unpublished a content item'
-  category: 'Content (Scheduler)'
+  label: 'After a node has been unpublished by Scheduler'
+  category: 'Scheduler'
   context_definitions:
     node:
       type: 'entity:node'
       label: 'Scheduled Content Node'
       description: 'The node object representing the scheduled content'
-
-# Use a deriver to build the corresponding six events for all other entity
-# types that are supported by Scheduler. This will not create any node events,
-# as they need to remain unchanged as above for backwards compatibilty.
-scheduler:
-  deriver: 'Drupal\scheduler_rules_integration\Event\EventDeriver'
-  class: '\Drupal\rules\EventHandler\ConfigurableEventHandlerEntityBundle'
diff --git a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules_defaults.inc b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules_defaults.inc
index 970d9758dcd031fa7335807dc4d20554a5cabb6c..1d86d63ddf3aa06a3a9aee6b29865e6df7718641 100644
--- a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules_defaults.inc
+++ b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.rules_defaults.inc
@@ -14,9 +14,6 @@
  * @todo Convert to 8.x, this is stil 7.x code
  */
 function scheduler_rules_integration_default_rules_configuration() {
-  // Initialise the array to avoid 'variable is undefined' phpcs error.
-  $configs = [];
-
   // Define two reaction rules which will be displayed on the 'Rules' tab. These
   // are initially inactive, but the user can enable them, and then modify the
   // values and/or add more conditions and actions.
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/EventBase.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/EventBase.php
deleted file mode 100644
index 681983098ec50aa37f7432a0bdf37f6cc951779c..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/EventBase.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Event;
-
-// Drupal\Component\EventDispatcher\Event was introduced in Drupal core 9.1 to
-// assist with deprecations and the transition to Symfony 5.
-// @todo Remove this when core 9.1 is the lowest supported version.
-// @see https://www.drupal.org/project/scheduler/issues/3166688
-if (!class_exists('Drupal\Component\EventDispatcher\Event')) {
-  class_alias('Symfony\Component\EventDispatcher\Event', 'Drupal\Component\EventDispatcher\Event');
-}
-
-use Drupal\Component\EventDispatcher\Event;
-
-/**
- * Base class on which all Scheduler Rules Integration events are extended.
- */
-class EventBase extends Event {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/EventDeriver.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/EventDeriver.php
deleted file mode 100644
index 402c0cdacea44cace109987ea4236d15e22f16c3..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/EventDeriver.php
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Event;
-
-use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\Core\StringTranslation\TranslationInterface;
-use Drupal\scheduler\SchedulerManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Derives Rules events for all non-node entities supported by Scheduler.
- *
- * This creates events with names starting with a prefix of "scheduler:" as
- * defined by the property name in scheduler_rules_integration.rules.events.yml,
- * followed by the text in keys of the array $this->derivatives.
- *
- * The processing below is based on code in the Rules module. For an example see
- * src/Plugin/RulesEvent/EntityUpdateDeriver.php. For backwards compatibility
- * the node event names must remain unchnaged, and this is not possible when
- * using this deriver. Hence the node event names stay written out long-hand in
- * scheduler_rules_integration.rules.events.yml.
- */
-class EventDeriver extends DeriverBase implements ContainerDeriverInterface {
-  use StringTranslationTrait;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The scheduler manager.
-   *
-   * @var \Drupal\scheduler\SchedulerManager
-   */
-  protected $schedulerManager;
-
-  /**
-   * Creates a new EventDeriver object.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
-   *   The string translation service.
-   * @param \Drupal\scheduler\SchedulerManager $scheduler_manager
-   *   The scheduler manager.
-   */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, SchedulerManager $scheduler_manager) {
-    $this->entityTypeManager = $entity_type_manager;
-    $this->stringTranslation = $string_translation;
-    $this->schedulerManager = $scheduler_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, $base_plugin_id) {
-    return new static(
-      $container->get('entity_type.manager'),
-      $container->get('string_translation'),
-      $container->get('scheduler.manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    // Get all entity types supported by Scheduler plugins.
-    foreach ($this->schedulerManager->getPluginEntityTypes() as $entity_type_id) {
-      // Node events are the originals, and for backwards-compatibility those
-      // event ids must remain unchanged, which cannot be done with the deriver.
-      // So they remain defined in scheduler_rules_integration.rules.events.yml.
-      if ($entity_type_id == 'node') {
-        continue;
-      }
-      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-
-      // Define the values that are the same for all events of this entity type.
-      $defaults = [
-        'entity_type_id' => $entity_type_id,
-        'category' => $entity_type->getLabel() . ' (' . $this->t('Scheduler') . ')',
-        'context_definitions' => [
-          $entity_type_id => [
-            'type' => "entity:$entity_type_id",
-            'label' => $this->t('The object representing the scheduled @entity_type', ['@entity_type' => $entity_type->getLabel()]),
-          ],
-        ],
-      ];
-
-      // Create six events for this entity type.
-      $this->derivatives["new_{$entity_type_id}_is_scheduled_for_publishing"] = [
-        'label' => $this->t('After saving a new @entity_type that is scheduled for publishing', ['@entity_type' => $entity_type->getSingularLabel()]),
-      ] + $defaults + $base_plugin_definition;
-
-      $this->derivatives["new_{$entity_type_id}_is_scheduled_for_unpublishing"] = [
-        'label' => $this->t('After saving a new @entity_type that is scheduled for unpublishing', ['@entity_type' => $entity_type->getSingularLabel()]),
-      ] + $defaults + $base_plugin_definition;
-
-      $this->derivatives["existing_{$entity_type_id}_is_scheduled_for_publishing"] = [
-        'label' => $this->t('After updating a @entity_type that is scheduled for publishing', ['@entity_type' => $entity_type->getSingularLabel()]),
-      ] + $defaults + $base_plugin_definition;
-
-      $this->derivatives["existing_{$entity_type_id}_is_scheduled_for_unpublishing"] = [
-        'label' => $this->t('After updating a @entity_type that is scheduled for unpublishing', ['@entity_type' => $entity_type->getSingularLabel()]),
-      ] + $defaults + $base_plugin_definition;
-
-      $this->derivatives["{$entity_type_id}_has_been_published_via_cron"] = [
-        'label' => $this->t('After Scheduler has published a @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]),
-      ] + $defaults + $base_plugin_definition;
-
-      $this->derivatives["{$entity_type_id}_has_been_unpublished_via_cron"] = [
-        'label' => $this->t('After Scheduler has unpublished a @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]),
-      ] + $defaults + $base_plugin_definition;
-
-    }
-    return $this->derivatives;
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd0f3d365f0f0d32aec24eb5f97e4c6be85e5d22
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Event;
+
+use Drupal\scheduler\EventBase;
+
+/**
+ * An existing node is scheduled for publishing.
+ *
+ * This event is fired when an existing node is updated/saved and it has a
+ * scheduled publishing date.
+ */
+class ExistingNodeIsScheduledForPublishingEvent extends EventBase {
+
+  const EVENT_NAME = 'scheduler_existing_node_is_scheduled_for_publishing_event';
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..1493bc97d3485a23539b5a0ab961431e66e539de
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Event;
+
+use Drupal\scheduler\EventBase;
+
+/**
+ * An existing node is scheduled for unpublishing.
+ *
+ * This event is fired when an existing node is updated/saved and it has a
+ * scheduled unpublishing date.
+ */
+class ExistingNodeIsScheduledForUnpublishingEvent extends EventBase {
+
+  const EVENT_NAME = 'scheduler_existing_node_is_scheduled_for_unpublishing_event';
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..39acbe31a85606b1e749b08acf319fdee2c82bc8
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Event;
+
+use Drupal\scheduler\EventBase;
+
+/**
+ * A new node is scheduled for publishing.
+ *
+ * This event is fired when a newly created node is saved for the first time
+ * and it has a scheduled publishing date.
+ */
+class NewNodeIsScheduledForPublishingEvent extends EventBase {
+
+  const EVENT_NAME = 'scheduler_new_node_is_scheduled_for_publishing_event';
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd7dc2778eafef68e03b8649877f6e1940a45e64
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Event;
+
+use Drupal\scheduler\EventBase;
+
+/**
+ * A new node is scheduled for unpublishing.
+ *
+ * This event is fired when a newly created node is saved for the first time
+ * and it has a scheduled unpublishing date.
+ */
+class NewNodeIsScheduledForUnpublishingEvent extends EventBase {
+
+  const EVENT_NAME = 'scheduler_new_node_is_scheduled_for_unpublishing_event';
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesCommerceProductEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesCommerceProductEvent.php
deleted file mode 100644
index 64ce4de0b2967749050855827a7b8a48301c43e8..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesCommerceProductEvent.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Event;
-
-use Drupal\commerce_product\Entity\ProductInterface;
-
-/**
- * Class for all Commerce Product events for use in Rules module.
- */
-class RulesCommerceProductEvent extends EventBase {
-
-  /**
-   * Define constants to convert the event identifier into the full event name.
-   *
-   * The final event names here are defined in the event deriver and are
-   * different in format from the event names for node events, as originally
-   * coded long-hand in scheduler_rules_integration.rules.events.yml.
-   * However, the identifiers (CRON_PUBLISHED, NEW_FOR_PUBLISHING, etc) are the
-   * same for all types and this is how the actual event names are retrieved.
-   */
-  const CRON_PUBLISHED = 'scheduler:commerce_product_has_been_published_via_cron';
-  const CRON_UNPUBLISHED = 'scheduler:commerce_product_has_been_unpublished_via_cron';
-  const NEW_FOR_PUBLISHING = 'scheduler:new_commerce_product_is_scheduled_for_publishing';
-  const NEW_FOR_UNPUBLISHING = 'scheduler:new_commerce_product_is_scheduled_for_unpublishing';
-  const EXISTING_FOR_PUBLISHING = 'scheduler:existing_commerce_product_is_scheduled_for_publishing';
-  const EXISTING_FOR_UNPUBLISHING = 'scheduler:existing_commerce_product_is_scheduled_for_unpublishing';
-
-  /**
-   * The commerce product which is being processed.
-   *
-   * This property name could be changed to lowerCamelCase but that would also
-   * require the context_definitions key to be changed to match. This could also
-   * be done, but when editing a rule we get commerceproduct in the drop-downs,
-   * whereas all other usages in the Rules forms have commerce_product. This is
-   * confusing for the admin/developer who has to select from this list when
-   * editing a rule. Therefore keep the property name matching the entity type
-   * id and prevent Coder from reporting the invalid name by disabling this
-   * specific sniff for this file only.
-   *
-   * phpcs:disable Drupal.NamingConventions.ValidVariableName.LowerCamelName
-   *
-   * @var Drupal\commerce_product\Entity\ProductInterface
-   */
-  public $commerce_product;
-
-  /**
-   * Constructs the object.
-   *
-   * @param Drupal\commerce_product\Entity\ProductInterface $commerce_product
-   *   The commerce_product item which is being processed.
-   */
-  public function __construct(ProductInterface $commerce_product) {
-    $this->commerce_product = $commerce_product;
-  }
-
-  /**
-   * Returns the entity which is being processed.
-   */
-  public function getEntity() {
-    // The Rules module requires the entity to be stored in a specifically named
-    // property which will obviously vary according to the entity type being
-    // processed. This generic getEntity() method is not strictly required by
-    // Rules but is added for convenience when manipulating the event entity.
-    return $this->commerce_product;
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesMediaEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesMediaEvent.php
deleted file mode 100644
index a2e4f974bf548840e813880cb023b73e5cac2732..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesMediaEvent.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Event;
-
-use Drupal\media\MediaInterface;
-
-/**
- * Class for all Rules media events.
- */
-class RulesMediaEvent extends EventBase {
-
-  /**
-   * Define constants to convert the event identifier into the full event name.
-   *
-   * The final event names here are defined in the event deriver and are
-   * different in format from the event names for node events, as originally
-   * coded long-hand in scheduler_rules_integration.rules.events.yml.
-   * However, the identifiers (CRON_PUBLISHED, NEW_FOR_PUBLISHING, etc) are the
-   * same for all types and this is how the actual event names are retrieved.
-   */
-  const CRON_PUBLISHED = 'scheduler:media_has_been_published_via_cron';
-  const CRON_UNPUBLISHED = 'scheduler:media_has_been_unpublished_via_cron';
-  const NEW_FOR_PUBLISHING = 'scheduler:new_media_is_scheduled_for_publishing';
-  const NEW_FOR_UNPUBLISHING = 'scheduler:new_media_is_scheduled_for_unpublishing';
-  const EXISTING_FOR_PUBLISHING = 'scheduler:existing_media_is_scheduled_for_publishing';
-  const EXISTING_FOR_UNPUBLISHING = 'scheduler:existing_media_is_scheduled_for_unpublishing';
-
-  /**
-   * The media item which is being processed.
-   *
-   * @var \Drupal\media\MediaInterface
-   */
-  public $media;
-
-  /**
-   * Constructs the object.
-   *
-   * @param \Drupal\media\MediaInterface $media
-   *   The media item which is being processed.
-   */
-  public function __construct(MediaInterface $media) {
-    $this->media = $media;
-  }
-
-  /**
-   * Returns the entity which is being processed.
-   */
-  public function getEntity() {
-    // The Rules module requires the entity to be stored in a specifically named
-    // property which will obviously vary according to the entity type being
-    // processed. This generic getEntity() method is not strictly required by
-    // Rules but is added for convenience when manipulating the event entity.
-    return $this->media;
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesNodeEvent.php
deleted file mode 100644
index dfabe82a55406056a9db4292fea57d83bdeb9bd4..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesNodeEvent.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Event;
-
-use Drupal\node\NodeInterface;
-
-/**
- * Class for all Rules node events.
- */
-class RulesNodeEvent extends EventBase {
-
-  /**
-   * Define constants to convert the event identifier into the full event name.
-   *
-   * To retain backwards compatibility the event names for node events remain as
-   * originally specified in scheduler_rules_integration.rules.events.yml. The
-   * format is different from the new events derived for other entity types.
-   * However, the identifiers (CRON_PUBLISHED, NEW_FOR_PUBLISHING, etc) are the
-   * same for all types and this is how the actual event names are retrieved.
-   */
-  const CRON_PUBLISHED = 'scheduler_has_published_this_node_event';
-  const CRON_UNPUBLISHED = 'scheduler_has_unpublished_this_node_event';
-  const NEW_FOR_PUBLISHING = 'scheduler_new_node_is_scheduled_for_publishing_event';
-  const NEW_FOR_UNPUBLISHING = 'scheduler_new_node_is_scheduled_for_unpublishing_event';
-  const EXISTING_FOR_PUBLISHING = 'scheduler_existing_node_is_scheduled_for_publishing_event';
-  const EXISTING_FOR_UNPUBLISHING = 'scheduler_existing_node_is_scheduled_for_unpublishing_event';
-
-  /**
-   * The node which is being processed.
-   *
-   * @var \Drupal\node\NodeInterface
-   */
-  public $node;
-
-  /**
-   * Constructs the object.
-   *
-   * @param \Drupal\node\NodeInterface $node
-   *   The node which is being processed.
-   */
-  public function __construct(NodeInterface $node) {
-    $this->node = $node;
-  }
-
-  /**
-   * Returns the entity which is being processed.
-   */
-  public function getEntity() {
-    // The Rules module requires the entity to be stored in a specifically named
-    // property which will obviously vary according to the entity type being
-    // processed. This generic getEntity() method is not strictly required by
-    // Rules but is added for convenience when manipulating the event entity.
-    return $this->node;
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesTaxonomyTermEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesTaxonomyTermEvent.php
deleted file mode 100644
index 932b3bafc974c32142a2558310bdd5e50a5656b4..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/RulesTaxonomyTermEvent.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Event;
-
-use Drupal\taxonomy\TermInterface;
-
-/**
- * Class for all Rules taxonomy term events.
- */
-class RulesTaxonomyTermEvent extends EventBase {
-
-  /**
-   * Define constants to convert the event identifier into the full event name.
-   *
-   * The final event names here are defined in the event deriver and are
-   * different in format from the event names for node events, as originally
-   * coded long-hand in scheduler_rules_integration.rules.events.yml.
-   * However, the identifiers (CRON_PUBLISHED, NEW_FOR_PUBLISHING, etc) are the
-   * same for all types and this is how the actual event names are retrieved.
-   */
-  const CRON_PUBLISHED = 'scheduler:taxonomy_term_has_been_published_via_cron';
-  const CRON_UNPUBLISHED = 'scheduler:taxonomy_term_has_been_unpublished_via_cron';
-  const NEW_FOR_PUBLISHING = 'scheduler:new_taxonomy_term_is_scheduled_for_publishing';
-  const NEW_FOR_UNPUBLISHING = 'scheduler:new_taxonomy_term_is_scheduled_for_unpublishing';
-  const EXISTING_FOR_PUBLISHING = 'scheduler:existing_taxonomy_term_is_scheduled_for_publishing';
-  const EXISTING_FOR_UNPUBLISHING = 'scheduler:existing_taxonomy_term_is_scheduled_for_unpublishing';
-
-  /**
-   * The taxonomy term which is being processed.
-   *
-   * This property name could be changed to lowerCamelCase but that would also
-   * require the context_definitions key to be changed to match. This could also
-   * be done, but when editing a rule we get taxonomyterm in the drop-downs,
-   * whereas all other usages in the Rules forms have taxonomy_term. This is
-   * confusing for the admin/developer who has to select from this list when
-   * editing a rule. Therefore keep the property name matching the entity type
-   * id and prevent Coder from reporting the invalid name by disabling this
-   * specific sniff for this file only.
-   *
-   * phpcs:disable Drupal.NamingConventions.ValidVariableName.LowerCamelName
-   *
-   * @var \Drupal\taxonomy\TermInterface
-   */
-  public $taxonomy_term;
-
-  /**
-   * Constructs the object.
-   *
-   * @param \Drupal\taxonomy\TermInterface $taxonomy_term
-   *   The taxonomy term is being processed.
-   */
-  public function __construct(TermInterface $taxonomy_term) {
-    $this->taxonomy_term = $taxonomy_term;
-  }
-
-  /**
-   * Returns the entity which is being processed.
-   */
-  public function getEntity() {
-    // The Rules module requires the entity to be stored in a specifically named
-    // property which will obviously vary according to the entity type being
-    // processed. This generic getEntity() method is not strictly required by
-    // Rules but is added for convenience when manipulating the event entity.
-    return $this->taxonomy_term;
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf4e4678b31c3dea93fd8a61d72c850f1a67d829
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Event;
+
+use Drupal\scheduler\EventBase;
+
+/**
+ * A node is published by Scheduler.
+ *
+ * This event is fired when Scheduler publishes a node via cron.
+ */
+class SchedulerHasPublishedThisNodeEvent extends EventBase {
+
+  const EVENT_NAME = 'scheduler_has_published_this_node_event';
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..c1956aa8355424a15e4cb38b3eac72914eef205d
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Event;
+
+use Drupal\scheduler\EventBase;
+
+/**
+ * A node is unpublished by Scheduler.
+ *
+ * This event is fired when Scheduler unpublishes a node via cron.
+ */
+class SchedulerHasUnpublishedThisNodeEvent extends EventBase {
+
+  const EVENT_NAME = 'scheduler_has_unpublished_this_node_event';
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ConditionDeriver.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ConditionDeriver.php
deleted file mode 100644
index e99dfd5d52a420b99e6c3dc692a6a0133017e22d..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ConditionDeriver.php
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\Condition;
-
-use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\Core\StringTranslation\TranslationInterface;
-use Drupal\rules\Context\ContextDefinition;
-use Drupal\scheduler\SchedulerManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Derives conditions for each supported entity type (except nodes).
- */
-class ConditionDeriver extends DeriverBase implements ContainerDeriverInterface {
-
-  use StringTranslationTrait;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The scheduler manager.
-   *
-   * @var \Drupal\scheduler\SchedulerManager
-   */
-  protected $schedulerManager;
-
-  /**
-   * Creates a new deriver object.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
-   *   The string translation service.
-   * @param \Drupal\scheduler\SchedulerManager $scheduler_manager
-   *   The scheduler manager.
-   */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, SchedulerManager $scheduler_manager) {
-    $this->entityTypeManager = $entity_type_manager;
-    $this->stringTranslation = $string_translation;
-    $this->schedulerManager = $scheduler_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, $base_plugin_id) {
-    return new static(
-      $container->get('entity_type.manager'),
-      $container->get('string_translation'),
-      $container->get('scheduler.manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    // Get all entity types supported by Scheduler plugins.
-    $base_plugin_id = $base_plugin_definition['id'];
-    foreach ($this->schedulerManager->getPluginEntityTypes() as $entity_type_id) {
-      // Node actions are the originals, and for backwards-compatibility those
-      // action ids must remain the same, which can not be done using this
-      // deriver. Hence the node actions are defined in the 'Legacy' classes.
-      if ($entity_type_id == 'node') {
-        continue;
-      }
-      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-
-      // Create a context definition object for the 'entity'. This is common
-      // to all the derivatives.
-      $entity_context_definition = ContextDefinition::create("entity:$entity_type_id")
-        ->setAssignmentRestriction(ContextDefinition::ASSIGNMENT_RESTRICTION_SELECTOR)
-        ->setRequired(TRUE);
-
-      $t_args = [
-        '@entity_type_label' => $entity_type->getLabel(),
-        '@entity_type_singular' => $entity_type->getSingularLabel(),
-      ];
-      // Define the action label, context label and description, depending on
-      // which derivative we are building.
-      switch ($base_plugin_id) {
-        case 'scheduler_publishing_is_enabled':
-          $label = $this->t('@entity_type_label type is enabled for scheduled publishing', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label', $t_args))
-            ->setDescription($this->t('The @entity_type_singular to check for the type being enabled for scheduled publishing.', $t_args));
-          break;
-
-        case 'scheduler_unpublishing_is_enabled':
-          $label = $this->t('@entity_type_label type is enabled for scheduled unpublishing', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label', $t_args))
-            ->setDescription($this->t('The @entity_type_singular to check for the type being enabled for scheduled unpublishing.', $t_args));
-          break;
-
-        case 'scheduler_entity_is_scheduled_for_publishing':
-          $label = $this->t('@entity_type_label is scheduled for publishing', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label', $t_args))
-            ->setDescription($this->t('The @entity_type_singular to check for having a scheduled publishing date.', $t_args));
-          break;
-
-        case 'scheduler_entity_is_scheduled_for_unpublishing':
-          $label = $this->t('@entity_type_label is scheduled for unpublishing', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label', $t_args))
-            ->setDescription($this->t('The @entity_type_singular to check for having a scheduled unpublishing date.', $t_args));
-          break;
-
-        default:
-          $label = 'NOT SET for ' . $base_plugin_id;
-          $entity_context_definition->setLabel($label);
-          break;
-      }
-
-      // Build the basic condition definition with the entity context.
-      $condition_definition = [
-        'label' => $label,
-        'entity_type_id' => $entity_type_id,
-        'category' => $entity_type->getLabel() . ' (' . $this->t('Scheduler') . ')',
-        // The context parameter names have to be consistent across all entity
-        // types (we cannot use $entity_type_id). This avoids PHP8 failing with
-        // 'unknown named parameter' in call_user_func_array()
-        // @see https://www.drupal.org/project/scheduler/issues/3276637
-        'context_definitions' => ['entity' => $entity_context_definition],
-      ];
-
-      // Add the full definition to the derivatives array.
-      $this->derivatives[$entity_type_id] = $condition_definition + $base_plugin_definition;
-    }
-
-    return $this->derivatives;
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyPublishingIsEnabled.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyPublishingIsEnabled.php
deleted file mode 100644
index 7d77d9b6fa17d09e858349e73834941471587c35..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyPublishingIsEnabled.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\Condition\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\Condition\PublishingIsEnabled;
-
-/**
- * Provides a 'Publishing is enabled' condition for nodes only.
- *
- * @Condition(
- *   id = "scheduler_condition_publishing_is_enabled",
- *   label = @Translation("Node type is enabled for scheduled publishing"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node to check for the type being enabled for scheduled publishing."),
- *       assignment_restriction = "selector",
- *     )
- *   }
- * )
- */
-class LegacyPublishingIsEnabled extends PublishingIsEnabled {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyScheduledForPublishing.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyScheduledForPublishing.php
deleted file mode 100644
index d59dd9a96e7601681a4c82af3439956fdbc7b65c..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyScheduledForPublishing.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\Condition\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\Condition\ScheduledForPublishing;
-
-/**
- * Provides 'Node is scheduled for publishing' condition.
- *
- * @Condition(
- *   id = "scheduler_condition_node_scheduled_for_publishing",
- *   label = @Translation("Node is scheduled for publishing"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node to check for having a scheduled publishing date."),
- *       assignment_restriction = "selector",
- *     )
- *   }
- * )
- */
-class LegacyScheduledForPublishing extends ScheduledForPublishing {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyScheduledForUnpublishing.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyScheduledForUnpublishing.php
deleted file mode 100644
index 7cac63db5930835636f5c46750bd27e52ad78077..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyScheduledForUnpublishing.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\Condition\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\Condition\ScheduledForUnpublishing;
-
-/**
- * Provides 'Node is scheduled for unpublishing' condition.
- *
- * @Condition(
- *   id = "scheduler_condition_node_scheduled_for_unpublishing",
- *   label = @Translation("Node is scheduled for unpublishing"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node to check for having a scheduled unpublishing date."),
- *       assignment_restriction = "selector",
- *     )
- *   }
- * )
- */
-class LegacyScheduledForUnpublishing extends ScheduledForUnpublishing {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyUnpublishingIsEnabled.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyUnpublishingIsEnabled.php
deleted file mode 100644
index afb5daab3f7eb33feedf68f1fd994e8cb7b1b546..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/Legacy/LegacyUnpublishingIsEnabled.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\Condition\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\Condition\UnpublishingIsEnabled;
-
-/**
- * Provides 'Unpublishing is enabled' condition for nodes only.
- *
- * @Condition(
- *   id = "scheduler_condition_unpublishing_is_enabled",
- *   label = @Translation("Node type is enabled for scheduled unpublishing"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node to check for the type being enabled for scheduled unpublishing."),
- *       assignment_restriction = "selector",
- *     )
- *   }
- * )
- */
-class LegacyUnpublishingIsEnabled extends UnpublishingIsEnabled {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/NodeIsScheduledForPublishing.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/NodeIsScheduledForPublishing.php
new file mode 100644
index 0000000000000000000000000000000000000000..8f1dfaa5b71a7970e86925e29e755d4f4f7e312d
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/NodeIsScheduledForPublishing.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Plugin\Condition;
+
+use Drupal\rules\Core\RulesConditionBase;
+
+/**
+ * Provides 'Node is scheduled for publishing' condition.
+ *
+ * @Condition(
+ *   id = "scheduler_condition_node_scheduled_for_publishing",
+ *   label = @Translation("Node is scheduled for publishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Scheduled Node"),
+ *       description = @Translation("The node to test for having a scheduled publishing date. Enter 'node' or use data selection.")
+ *     )
+ *   }
+ * )
+ */
+class NodeIsScheduledForPublishing extends RulesConditionBase {
+
+  /**
+   * Determines whether a node is scheduled for publishing.
+   *
+   * @return bool
+   *   TRUE if the node is scheduled for publishing, FALSE if not.
+   */
+  protected function doEvaluate() {
+    $node = $this->getContextValue('node');
+    return !empty($node->publish_on->value);
+  }
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/NodeIsScheduledForUnpublishing.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/NodeIsScheduledForUnpublishing.php
new file mode 100644
index 0000000000000000000000000000000000000000..31460af556ca9560c1e4c9df246018b76e8016a7
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/NodeIsScheduledForUnpublishing.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\scheduler_rules_integration\Plugin\Condition;
+
+use Drupal\rules\Core\RulesConditionBase;
+
+/**
+ * Provides 'Node is scheduled for unpublishing' condition.
+ *
+ * @Condition(
+ *   id = "scheduler_condition_node_scheduled_for_unpublishing",
+ *   label = @Translation("Node is scheduled for unpublishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Scheduled Node"),
+ *       description = @Translation("The node to test for having a scheduled unpublishing date. Enter 'node' or use data selection.")
+ *     )
+ *   }
+ * )
+ */
+class NodeIsScheduledForUnpublishing extends RulesConditionBase {
+
+  /**
+   * Determines whether a node is scheduled for unpublishing.
+   *
+   * @return bool
+   *   TRUE if the node is scheduled for unpublishing, FALSE if not.
+   */
+  protected function doEvaluate() {
+    $node = $this->getContextValue('node');
+    return !empty($node->unpublish_on->value);
+  }
+
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/PublishingIsEnabled.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/PublishingIsEnabled.php
index 8f8a9de79d2681db7bedb81f564678f16d8071d3..dffa941b7dbb07aa986683f0d742d5ed1b02c272 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/PublishingIsEnabled.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/PublishingIsEnabled.php
@@ -2,33 +2,36 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\Condition;
 
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\rules\Core\RulesConditionBase;
 
 /**
- * Provides 'Publishing is enabled for the type of this entity' condition.
+ * Provides a 'Publishing is enabled' condition.
  *
  * @Condition(
- *   id = "scheduler_publishing_is_enabled",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\Condition\ConditionDeriver"
+ *   id = "scheduler_condition_publishing_is_enabled",
+ *   label = @Translation("Node type is enabled for scheduled publishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Scheduled Node"),
+ *       description = @Translation("The node to check for scheduled publishing enabled. Enter 'node' or use data selection.")
+ *     )
+ *   }
  * )
  */
 class PublishingIsEnabled extends RulesConditionBase {
 
   /**
-   * Determines whether scheduled publishing is enabled for this entity type.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be checked.
+   * Determines whether scheduled publishing is enabled for this node type.
    *
    * @return bool
-   *   TRUE if scheduled publishing is enabled for the bundle of this entity
-   *   type.
+   *   TRUE if scheduled publishing is enabled for the content type of this
+   *   node.
    */
-  public function doEvaluate(EntityInterface $entity) {
+  public function evaluate() {
+    $node = $this->getContextValue('node');
     $config = \Drupal::config('scheduler.settings');
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    return ($entity->$bundle_field->entity->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable')));
+    return ($node->type->entity->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable')));
   }
 
 }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ScheduledForPublishing.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ScheduledForPublishing.php
deleted file mode 100644
index 5ae791c8c151dd76a7f61866b93504c4266fc8b1..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ScheduledForPublishing.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\Condition;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\rules\Core\RulesConditionBase;
-
-/**
- * Provides 'Entity is scheduled for publishing' condition.
- *
- * @Condition(
- *   id = "scheduler_entity_is_scheduled_for_publishing",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\Condition\ConditionDeriver"
- * )
- */
-class ScheduledForPublishing extends RulesConditionBase {
-
-  /**
-   * Determines whether an entity is scheduled for publishing.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be checked.
-   *
-   * @return bool
-   *   TRUE if the entity is scheduled for publishing, FALSE if not.
-   */
-  public function doEvaluate(EntityInterface $entity) {
-    return isset($entity->publish_on->value);
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ScheduledForUnpublishing.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ScheduledForUnpublishing.php
deleted file mode 100644
index e047440ca16d6fed10b774f23f4fcfa8b278a70b..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/ScheduledForUnpublishing.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\Condition;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\rules\Core\RulesConditionBase;
-
-/**
- * Provides 'Entity is scheduled for publishing' condition.
- *
- * @Condition(
- *   id = "scheduler_entity_is_scheduled_for_unpublishing",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\Condition\ConditionDeriver"
- * )
- */
-class ScheduledForUnpublishing extends RulesConditionBase {
-
-  /**
-   * Determines whether an entity is scheduled for unpublishing.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be checked.
-   *
-   * @return bool
-   *   TRUE if the entity is scheduled for unpublishing, FALSE if not.
-   */
-  public function doEvaluate(EntityInterface $entity) {
-    return isset($entity->unpublish_on->value);
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/UnpublishingIsEnabled.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/UnpublishingIsEnabled.php
index 01f62ffa48abf590f1618b8ed62e75756d1de224..9e1243e0f59bf969160844d378b1470fac311c30 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/UnpublishingIsEnabled.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/Condition/UnpublishingIsEnabled.php
@@ -2,33 +2,36 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\Condition;
 
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\rules\Core\RulesConditionBase;
 
 /**
- * Provides 'Unpublishing is enabled for the type of this entity' condition.
+ * Provides 'Unpublishing is enabled' condition.
  *
  * @Condition(
- *   id = "scheduler_unpublishing_is_enabled",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\Condition\ConditionDeriver"
+ *   id = "scheduler_condition_unpublishing_is_enabled",
+ *   label = @Translation("Node type is enabled for scheduled unpublishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Scheduled Node"),
+ *       description = @Translation("The node to check for scheduled unpublishing enabled. Enter 'node' or use data selection.")
+ *     )
+ *   }
  * )
  */
 class UnpublishingIsEnabled extends RulesConditionBase {
 
   /**
-   * Determines whether scheduled unpublishing is enabled for this entity type.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be checked.
+   * Determines whether scheduled unpublishing is enabled for this node type.
    *
    * @return bool
-   *   TRUE if scheduled unpublishing is enabled for the bundle of this entity
-   *   type.
+   *   TRUE if scheduled unpublishing is enabled for the content type of this
+   *   node.
    */
-  public function doEvaluate(EntityInterface $entity) {
+  public function evaluate() {
+    $node = $this->getContextValue('node');
     $config = \Drupal::config('scheduler.settings');
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    return ($entity->$bundle_field->entity->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable')));
+    return ($node->type->entity->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable')));
   }
 
 }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyPublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyPublishNow.php
deleted file mode 100644
index 12314d6e05ef6a810e3dc7dc94756e08ebb7c6d2..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyPublishNow.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\RulesAction\PublishNow;
-
-/**
- * Provides a 'Publish the node immediately' action.
- *
- * @RulesAction(
- *   id = "scheduler_publish_now_action",
- *   entity_type_id = "node",
- *   label = @Translation("Publish a content item immediately"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node to be published now"),
- *       assignment_restriction = "selector",
- *     ),
- *   }
- * )
- */
-class LegacyPublishNow extends PublishNow {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyRemovePublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyRemovePublishingDate.php
deleted file mode 100644
index 63d614b40d55452536b5097fc31e2791981a0cb4..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyRemovePublishingDate.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\RulesAction\RemovePublishingDate;
-
-/**
- * Provides a 'Remove date for scheduled publishing' action, for nodes only.
- *
- * @RulesAction(
- *   id = "scheduler_remove_publishing_date_action",
- *   entity_type_id = "node",
- *   label = @Translation("Remove date for publishing a content item"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node from which to remove the scheduled publishing date"),
- *       assignment_restriction = "selector",
- *     ),
- *   }
- * )
- */
-class LegacyRemovePublishingDate extends RemovePublishingDate {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyRemoveUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyRemoveUnpublishingDate.php
deleted file mode 100644
index fb417dac17b086930f3a355c1929907fd9b5b5dd..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyRemoveUnpublishingDate.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\RulesAction\RemoveUnpublishingDate;
-
-/**
- * Provides a 'Remove date for scheduled unpublishing' action for nodes only.
- *
- * @RulesAction(
- *   id = "scheduler_remove_unpublishing_date_action",
- *   entity_type_id = "node",
- *   label = @Translation("Remove date for unpublishing a content item"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node from which to remove the scheduled unpublishing date"),
- *       assignment_restriction = "selector",
- *     ),
- *   }
- * )
- */
-class LegacyRemoveUnpublishingDate extends RemoveUnpublishingDate {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacySetPublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacySetPublishingDate.php
deleted file mode 100644
index 707041c194a0f915cbb62a07d60601f4137c3cc2..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacySetPublishingDate.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\RulesAction\SetPublishingDate;
-
-/**
- * Provides the 'Set date for scheduled unpublishing' action just for nodes.
- *
- * @RulesAction(
- *   id = "scheduler_set_publishing_date_action",
- *   entity_type_id = "node",
- *   label = @Translation("Set date for publishing a content item"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node for scheduling"),
- *       description = @Translation("The node which is to have a scheduled publishing date set"),
- *       assignment_restriction = "selector",
- *     ),
- *     "date" = @ContextDefinition("timestamp",
- *       label = @Translation("The date for publishing"),
- *       description = @Translation("The date when Scheduler will publish the node"),
- *     )
- *   }
- * )
- */
-class LegacySetPublishingDate extends SetPublishingDate {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacySetUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacySetUnpublishingDate.php
deleted file mode 100644
index 9878e1a8402a2a967f813ac0ce1656888458a6e5..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacySetUnpublishingDate.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\RulesAction\SetUnpublishingDate;
-
-/**
- * Provides a 'Set date for scheduled unpublishing' action just for nodes.
- *
- * @RulesAction(
- *   id = "scheduler_set_unpublishing_date_action",
- *   entity_type_id = "node",
- *   label = @Translation("Set date for unpublishing a content item"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node for scheduling"),
- *       description = @Translation("The node which is to have a scheduled unpublishing date set"),
- *       assignment_restriction = "selector",
- *     ),
- *     "date" = @ContextDefinition("timestamp",
- *       label = @Translation("The date for unpublishing"),
- *       description = @Translation("The date when Scheduler will unpublish the node"),
- *     )
- *   }
- * )
- */
-class LegacySetUnpublishingDate extends SetUnpublishingDate {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyUnpublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyUnpublishNow.php
deleted file mode 100644
index dfd4e6486410f8f6a25365905f204edbd68d8732..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/Legacy/LegacyUnpublishNow.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction\Legacy;
-
-use Drupal\scheduler_rules_integration\Plugin\RulesAction\UnpublishNow;
-
-/**
- * Provides an 'Unpublish the node immediately' action.
- *
- * @RulesAction(
- *   id = "scheduler_unpublish_now_action",
- *   entity_type_id = "node",
- *   label = @Translation("Unpublish a content item immediately"),
- *   category = @Translation("Content (Scheduler)"),
- *   context_definitions = {
- *     "entity" = @ContextDefinition("entity:node",
- *       label = @Translation("Node"),
- *       description = @Translation("The node to be unpublished now"),
- *       assignment_restriction = "selector",
- *     ),
- *   }
- * )
- */
-class LegacyUnpublishNow extends UnpublishNow {}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php
index a929988913a83391bb83ddf5cb16561a3653b550..b7e2465b25ded0a5371d8363d8ebd503663a28ce 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php
@@ -2,30 +2,35 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\rules\Core\RulesActionBase;
 
 /**
- * Provides a 'Publish immediately' action.
+ * Provides a 'Publish the node immediately' action.
  *
  * @RulesAction(
- *   id = "scheduler_publish_now",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\RulesAction\SchedulerRulesActionDeriver"
+ *   id = "scheduler_publish_now_action",
+ *   label = @Translation("Publish the content immediately"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node"),
+ *       description = @Translation("The node to be published now"),
+ *     ),
+ *   }
  * )
  */
-class PublishNow extends SchedulerRulesActionBase {
+class PublishNow extends RulesActionBase {
 
   /**
-   * Set the entity status to Published.
+   * Set the node status to Published.
    *
-   * This action is provided by the Rules Module but only for node content, not
-   * Media. There is also a problem with recursion in the Rules action due to
-   * autoSaveContext(). Hence better for Scheduler to provide this action.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be published.
+   * This action should really be provided by Rules or by Core, but it is not
+   * yet done (as of Aug 2016). Scheduler users need this action so we provide
+   * it here. It could be removed later when Rules or Core includes it.
    */
-  public function doExecute(EntityInterface $entity) {
-    $entity->setPublished();
+  public function doExecute() {
+    $node = $this->getContextValue('node');
+    $node->setPublished();
   }
 
 }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php
index f6c7fed0d3979c1dbe1da341c538fee5ce05086e..1ddebd0b621996c888f5c640759cd1f7e36a02d9 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php
@@ -2,35 +2,60 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\rules\Core\RulesActionBase;
 
 /**
  * Provides a 'Remove date for scheduled publishing' action.
  *
  * @RulesAction(
- *   id = "scheduler_remove_publishing_date",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\RulesAction\SchedulerRulesActionDeriver"
+ *   id = "scheduler_remove_publishing_date_action",
+ *   label = @Translation("Remove date for scheduled publishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node"),
+ *       description = @Translation("The node from which to remove the scheduled publishing date"),
+ *     ),
+ *   }
  * )
  */
-class RemovePublishingDate extends SchedulerRulesActionBase {
+class RemovePublishingDate extends RulesActionBase {
 
   /**
-   * Remove the publish_on date from the entity.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity from which to remove the scheduled date.
+   * Remove the publish_on date from the node.
    */
-  public function doExecute(EntityInterface $entity) {
+  public function doExecute() {
+    $node = $this->getContextValue('node');
     $config = \Drupal::config('scheduler.settings');
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    if ($entity->$bundle_field->entity->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'))) {
-      $entity->set('publish_on', NULL);
-      scheduler_entity_presave($entity);
+    if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'))) {
+      $node->set('publish_on', NULL);
+      scheduler_node_presave($node);
+      scheduler_node_update($node);
     }
     else {
       // The action cannot be executed because the content type is not enabled
       // for scheduled publishing.
-      $this->notEnabledWarning($entity, 'publish');
+      $action_label = $this->summary();
+      // @todo Can we get the condition description from the actual condition
+      // object instead of hard-coding it here?
+      $condition = $this->t('Node type is enabled for scheduled publishing');
+      $type_name = node_get_type_label($node);
+      $url = new Url('entity.node_type.edit_form', ['node_type' => $node->getType()]);
+      $arguments = [
+        '%type' => $type_name,
+        '%action_label' => $action_label,
+        '%condition' => $condition,
+        '@url' => $url->toString(),
+      ];
+
+      $link = Link::fromTextAndUrl($this->t('@type settings', ['@type' => $type_name]), $url)->toString();
+      \Drupal::logger('scheduler')->warning('Action "%action_label" is not valid because scheduled publishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled publishing via the %type settings.',
+        $arguments + ['link' => $link]);
+
+      \Drupal::messenger()->addMessage($this->t('Action "%action_label" is not valid because scheduled publishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled publishing via the <a href="@url">%type</a> settings.',
+        $arguments), 'warning', FALSE);
     }
   }
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php
index dce7fad5ca896e9472dcc7c5b58a3810d0fe1a30..a3130d505431a5646454c375809cf2e524605740 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php
@@ -2,35 +2,60 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\rules\Core\RulesActionBase;
 
 /**
  * Provides a 'Remove date for scheduled unpublishing' action.
  *
  * @RulesAction(
- *   id = "scheduler_remove_unpublishing_date",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\RulesAction\SchedulerRulesActionDeriver"
+ *   id = "scheduler_remove_unpublishing_date_action",
+ *   label = @Translation("Remove date for scheduled unpublishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node"),
+ *       description = @Translation("The node from which to remove the scheduled unpublishing date"),
+ *     ),
+ *   }
  * )
  */
-class RemoveUnpublishingDate extends SchedulerRulesActionBase {
+class RemoveUnpublishingDate extends RulesActionBase {
 
   /**
-   * Remove the unpublish_on date from the entity.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity from which to remove the scheduled date.
+   * Remove the unpublish_on date from the node.
    */
-  public function doExecute(EntityInterface $entity) {
+  public function doExecute() {
+    $node = $this->getContextValue('node');
     $config = \Drupal::config('scheduler.settings');
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    if ($entity->$bundle_field->entity->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'))) {
-      $entity->set('unpublish_on', NULL);
-      scheduler_entity_presave($entity);
+    if ($node->type->entity->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'))) {
+      $node->set('unpublish_on', NULL);
+      scheduler_node_presave($node);
+      scheduler_node_update($node);
     }
     else {
       // The action cannot be executed because the content type is not enabled
       // for scheduled unpublishing.
-      $this->notEnabledWarning($entity, 'unpublish');
+      $action_label = $this->summary();
+      // @todo Can we get the condition description from the actual condition
+      // object instead of hard-coding it here?
+      $condition = $this->t('Node type is enabled for scheduled unpublishing');
+      $type_name = node_get_type_label($node);
+      $url = new Url('entity.node_type.edit_form', ['node_type' => $node->getType()]);
+      $arguments = [
+        '%type' => $type_name,
+        '%action_label' => $action_label,
+        '%condition' => $condition,
+        '@url' => $url->toString(),
+      ];
+
+      $link = Link::fromTextAndUrl($this->t('@type settings', ['@type' => $type_name]), $url)->toString();
+      \Drupal::logger('scheduler')->warning('Action "%action_label" is not valid because scheduled unpublishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled unpublishing via the %type settings.',
+        $arguments + ['link' => $link]);
+
+      \Drupal::messenger()->addMessage($this->t('Action "%action_label" is not valid because scheduled unpublishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled unpublishing via the <a href="@url">%type</a> settings.',
+        $arguments), 'warning', FALSE);
     }
   }
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SchedulerRulesActionBase.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SchedulerRulesActionBase.php
deleted file mode 100644
index ef7eebcbc040c8575dc0753f57c1b617235a3bde..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SchedulerRulesActionBase.php
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Link;
-use Drupal\Core\Url;
-use Drupal\rules\Core\RulesActionBase;
-
-/**
- * Provides base class on which all Scheduler Rules actions are built.
- */
-class SchedulerRulesActionBase extends RulesActionBase {
-
-  /**
-   * The entity type id.
-   *
-   * @var string
-   */
-  protected $entityTypeId;
-
-  /**
-   * Constructs a SchedulerRulesActionBase object.
-   *
-   * @param array $configuration
-   *   A configuration array containing information about the plugin instance.
-   * @param string $plugin_id
-   *   The plugin ID for the plugin instance.
-   * @param mixed $plugin_definition
-   *   The plugin implementation definition.
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->entityTypeId = $plugin_definition['entity_type_id'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    return new static(
-      $configuration,
-      $plugin_id,
-      $plugin_definition
-    );
-  }
-
-  /**
-   * Gives a warning when an entity is not enabled for Scheduler.
-   *
-   * This is called from actions that attempt to set or remove a Scheduler date
-   * value when the entity type is not enabled for that process.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity object being processed by the action.
-   * @param string $process
-   *   The process that is not enabled, either 'publish' or 'unpublish'.
-   */
-  public function notEnabledWarning(EntityInterface $entity, string $process) {
-    $action = $this->summary();
-    $activity = ($process == 'publish') ? $this->t('scheduled publishing') : $this->t('scheduled unpublishing');
-    $condition = $this->t('@bundle_label is enabled for @activity', [
-      '@bundle_label' => $entity->getEntityType()->getBundleLabel(),
-      '@activity' => $activity,
-    ]);
-
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    $type_name = $entity->$bundle_field->entity->label();
-    $type_id = $entity->$bundle_field->entity->bundle();
-    $url = new Url("entity.$type_id.edit_form", [$type_id => $entity->bundle()]);
-    $arguments = [
-      '%action' => "'$action'",
-      '@activity' => $activity,
-      '%type' => $type_name,
-      '@group' => $entity->getEntityType()->getPluralLabel(),
-      '%condition' => "'$condition'",
-      '@url' => $url->toString(),
-    ];
-    $link = Link::fromTextAndUrl($this->t('@type settings', ['@type' => $type_name]), $url)->toString();
-    \Drupal::logger('scheduler')->warning('Action %action is not valid because @activity is not enabled for %type @group. Add the condition %condition to your Reaction Rule, or enable @activity via the %type settings.',
-      $arguments + ['link' => $link]);
-
-    \Drupal::messenger()->addMessage($this->t('Action %action is not valid because @activity is not enabled for %type @group. Add the condition %condition to your Reaction Rule, or enable @activity via the <a href="@url">%type</a> settings.',
-      $arguments), 'warning', FALSE);
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SchedulerRulesActionDeriver.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SchedulerRulesActionDeriver.php
deleted file mode 100644
index eb6f43cfbecbf6eabe3e78c0a8205ab42824caa6..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SchedulerRulesActionDeriver.php
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-
-namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
-
-use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\Core\StringTranslation\TranslationInterface;
-use Drupal\rules\Context\ContextDefinition;
-use Drupal\scheduler\SchedulerManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Derives actions for each supported entity type.
- *
- * Based on code from Rules module EntityCreateDeriver.
- */
-class SchedulerRulesActionDeriver extends DeriverBase implements ContainerDeriverInterface {
-
-  use StringTranslationTrait;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The scheduler manager.
-   *
-   * @var \Drupal\scheduler\SchedulerManager
-   */
-  protected $schedulerManager;
-
-  /**
-   * Creates a new deriver object.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
-   *   The string translation service.
-   * @param \Drupal\scheduler\SchedulerManager $scheduler_manager
-   *   The scheduler manager.
-   */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, SchedulerManager $scheduler_manager) {
-    $this->entityTypeManager = $entity_type_manager;
-    $this->stringTranslation = $string_translation;
-    $this->schedulerManager = $scheduler_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, $base_plugin_id) {
-    return new static(
-      $container->get('entity_type.manager'),
-      $container->get('string_translation'),
-      $container->get('scheduler.manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    // Get all entity types supported by Scheduler plugins.
-    $base_plugin_id = $base_plugin_definition['id'];
-    foreach ($this->schedulerManager->getPluginEntityTypes() as $entity_type_id) {
-      // Node actions are the originals, and for backwards-compatibility those
-      // action ids must remain the same, which can not be done using this
-      // deriver. Hence the node actions are defined in the 'Legacy' classes.
-      if ($entity_type_id == 'node') {
-        continue;
-      }
-      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-
-      // Create a context definition object for the 'entity'. This is common
-      // to all the derivatives.
-      $entity_context_definition = ContextDefinition::create("entity:$entity_type_id")
-        ->setAssignmentRestriction(ContextDefinition::ASSIGNMENT_RESTRICTION_SELECTOR)
-        ->setRequired(TRUE);
-
-      $t_args = [
-        '@entity_type_label' => $entity_type->getLabel(),
-        '@entity_type_singular' => $entity_type->getSingularLabel(),
-      ];
-      // Define the action label, context label and description, depending on
-      // which derivative we are building.
-      switch ($base_plugin_id) {
-        case 'scheduler_set_publishing_date':
-          $action_label = $this->t('Set date for publishing a @entity_type_singular', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label for scheduling', $t_args))
-            ->setDescription($this->t('The @entity_type_singular which is to have a scheduled publishing date set', $t_args));
-          // Define a label and description for the date context definition.
-          $date_label = $this->t('Date for publishing');
-          $date_description = $this->t('The date when Scheduler will publish the @entity_type_singular', $t_args);
-          break;
-
-        case 'scheduler_set_unpublishing_date':
-          $action_label = $this->t('Set date for unpublishing a @entity_type_singular', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label for scheduling', $t_args))
-            ->setDescription($this->t('The @entity_type_singular which is to have a scheduled unpublishing date set', $t_args));
-          $date_label = $this->t('Date for unpublishing');
-          $date_description = $this->t('The date when Scheduler will unpublish the @entity_type_singular', $t_args);
-          break;
-
-        case 'scheduler_remove_publishing_date':
-          $action_label = $this->t('Remove date for publishing a @entity_type_singular', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label', $t_args))
-            ->setDescription($this->t('The @entity_type_singular from which to remove the scheduled publishing date', $t_args));
-          break;
-
-        case 'scheduler_remove_unpublishing_date':
-          $action_label = $this->t('Remove date for unpublishing a @entity_type_singular', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label', $t_args))
-            ->setDescription($this->t('The @entity_type_singular from which to remove the scheduled unpublishing date', $t_args));
-          break;
-
-        case 'scheduler_publish_now':
-          $action_label = $this->t('Publish a @entity_type_singular immediately', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label for publishing', $t_args))
-            ->setDescription($this->t('The @entity_type_singular to be published now', $t_args));
-          break;
-
-        case 'scheduler_unpublish_now':
-          $action_label = $this->t('Unpublish a @entity_type_singular immediately', $t_args);
-          $entity_context_definition
-            ->setLabel($this->t('@entity_type_label for unpublishing', $t_args))
-            ->setDescription($this->t('The @entity_type_singular to be unpublished now', $t_args));
-          break;
-
-        default:
-          $action_label = 'NOT SET for ' . $base_plugin_id;
-          $entity_context_definition->setLabel($action_label);
-          break;
-      }
-
-      // Build the basic action definition, with the entity context, which is
-      // common to all six actions.
-      $action_definition = [
-        'label' => $action_label,
-        'entity_type_id' => $entity_type_id,
-        'category' => $entity_type->getLabel() . ' (' . $this->t('Scheduler') . ')',
-        // The context parameter names have to be consistent across all entity
-        // types (we cannot use $entity_type_id). This avoids PHP8 failing with
-        // 'unknown named parameter' in call_user_func_array()
-        // @see https://www.drupal.org/project/scheduler/issues/3276637
-        'context_definitions' => ['entity' => $entity_context_definition],
-      ];
-
-      // For the actions that set a scheduler date add the date as a second
-      // context variable.
-      if ($base_plugin_id == 'scheduler_set_publishing_date' || $base_plugin_id == 'scheduler_set_unpublishing_date') {
-        $date_context_definition = ContextDefinition::create('timestamp')
-          ->setLabel($date_label)
-          ->setDescription($date_description)
-          ->setRequired(TRUE);
-        $action_definition['context_definitions']['date'] = $date_context_definition;
-      }
-
-      // Finally add the full action definition to the derivatives array.
-      $this->derivatives[$entity_type_id] = $action_definition + $base_plugin_definition;
-    }
-
-    return $this->derivatives;
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php
index 8a4b9f665477a088e36eb2bed5e6392b0d82f394..099066e1dcdd5d30f018f0eeb9d516cadd88449e 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php
@@ -2,41 +2,70 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\rules\Core\RulesActionBase;
 
 /**
  * Provides a 'Set date for scheduled publishing' action.
  *
  * @RulesAction(
- *   id = "scheduler_set_publishing_date",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\RulesAction\SchedulerRulesActionDeriver"
+ *   id = "scheduler_set_publishing_date_action",
+ *   label = @Translation("Set date for scheduled publishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node for scheduling"),
+ *       description = @Translation("The node which is to have a scheduled publishing date set"),
+ *     ),
+ *     "date" = @ContextDefinition("timestamp",
+ *       label = @Translation("The date for publishing"),
+ *       description = @Translation("The date when Scheduler will publish the node"),
+ *     )
+ *   }
  * )
  */
-class SetPublishingDate extends SchedulerRulesActionBase {
+class SetPublishingDate extends RulesActionBase {
 
   /**
-   * Set the publish_on date on the entity.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be scheduled for publishing.
-   * @param int $date
-   *   The date for publishing.
+   * Set the publish_on date for the node.
    */
-  public function doExecute(EntityInterface $entity, $date) {
+  public function doExecute() {
+    $node = $this->getContextValue('node');
+    $date = $this->getContextValue('date');
     $config = \Drupal::config('scheduler.settings');
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    if ($entity->$bundle_field->entity->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'))) {
-      $entity->set('publish_on', $date);
-      // When this action is invoked and it operates on the entity being edited
-      // then hook_entity_presave() will be executed automatically. But if this
-      // action is being used to schedule a different entity then we need to
-      // call the functions directly here.
-      scheduler_entity_presave($entity);
+    if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'))) {
+      $node->set('publish_on', $date);
+      // When this action is invoked and it operates on the node being editted
+      // then hook_node_presave() and hook_node_update() will be executed
+      // automatically. But if this action is being used to schedule a different
+      // node then we need to call the functions directly here.
+      scheduler_node_presave($node);
+      scheduler_node_update($node);
     }
     else {
       // The action cannot be executed because the content type is not enabled
       // for scheduled publishing.
-      $this->notEnabledWarning($entity, 'publish');
+      $action_label = $this->summary();
+      // @todo Can we get the condition description from the actual condition
+      // object instead of hard-coding it here?
+      $condition = $this->t('Node type is enabled for scheduled publishing');
+      $type_name = node_get_type_label($node);
+      $url = new Url('entity.node_type.edit_form', ['node_type' => $node->getType()]);
+      $arguments = [
+        '%type' => $type_name,
+        '%action_label' => $action_label,
+        '%condition' => $condition,
+        '@url' => $url->toString(),
+      ];
+
+      $link = Link::fromTextAndUrl($this->t('@type settings', ['@type' => $type_name]), $url)->toString();
+      \Drupal::logger('scheduler')->warning('Action "%action_label" is not valid because scheduled publishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled publishing via the %type settings.',
+        $arguments + ['link' => $link]);
+
+      \Drupal::messenger()->addMessage($this->t('Action "%action_label" is not valid because scheduled publishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled publishing via the <a href="@url">%type</a> settings.',
+        $arguments), 'warning', FALSE);
+
     }
   }
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php
index e0006d25fad0054196d8fff8bdaf8258b857487e..0e00f60d2446f131d799daf42fc7f26aacbeaac1 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php
@@ -2,41 +2,69 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\rules\Core\RulesActionBase;
 
 /**
  * Provides a 'Set date for scheduled unpublishing' action.
  *
  * @RulesAction(
- *   id = "scheduler_set_unpublishing_date",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\RulesAction\SchedulerRulesActionDeriver"
+ *   id = "scheduler_set_unpublishing_date_action",
+ *   label = @Translation("Set date for scheduled unpublishing"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node for scheduling"),
+ *       description = @Translation("The node which is to have a scheduled unpublishing date set"),
+ *     ),
+ *     "date" = @ContextDefinition("timestamp",
+ *       label = @Translation("The date for unpublishing"),
+ *       description = @Translation("The date when Scheduler will unpublish the node"),
+ *     )
+ *   }
  * )
  */
-class SetUnpublishingDate extends SchedulerRulesActionBase {
+class SetUnpublishingDate extends RulesActionBase {
 
   /**
-   * Set the unpublish_on date on the entity.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be scheduled for unpublishing.
-   * @param int $date
-   *   The date for unpublishing.
+   * Set the unpublish_on date for the node.
    */
-  public function doExecute(EntityInterface $entity, $date) {
+  public function doExecute() {
+    $node = $this->getContextValue('node');
+    $date = $this->getContextValue('date');
     $config = \Drupal::config('scheduler.settings');
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    if ($entity->$bundle_field->entity->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'))) {
-      $entity->set('unpublish_on', $date);
-      // When this action is invoked and it operates on the entity being edited
-      // then hook_entity_presave() will be executed automatically. But if this
-      // action is being used to schedule a different entity then we need to
-      // call the functions directly here.
-      scheduler_entity_presave($entity);
+    if ($node->type->entity->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'))) {
+      $node->set('unpublish_on', $date);
+      // When this action is invoked and it operates on the node being editted
+      // then hook_node_presave() and hook_node_update() will be executed
+      // automatically. But if this action is being used to schedule a different
+      // node then we need to call the functions directly here.
+      scheduler_node_presave($node);
+      scheduler_node_update($node);
     }
     else {
       // The action cannot be executed because the content type is not enabled
       // for scheduled unpublishing.
-      $this->notEnabledWarning($entity, 'unpublish');
+      $action_label = $this->summary();
+      // @todo Can we get the condition description from the actual condition
+      // object instead of hard-coding it here?
+      $condition = $this->t('Node type is enabled for scheduled unpublishing');
+      $type_name = node_get_type_label($node);
+      $url = new Url('entity.node_type.edit_form', ['node_type' => $node->getType()]);
+      $arguments = [
+        '%type' => $type_name,
+        '%action_label' => $action_label,
+        '%condition' => $condition,
+        '@url' => $url->toString(),
+      ];
+
+      $link = Link::fromTextAndUrl($this->t('@type settings', ['@type' => $type_name]), $url)->toString();
+      \Drupal::logger('scheduler')->warning('Action "%action_label" is not valid because scheduled unpublishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled unpublishing via the %type settings.',
+        $arguments + ['link' => $link]);
+
+      \Drupal::messenger()->addMessage($this->t('Action "%action_label" is not valid because scheduled unpublishing is not enabled for %type content. Add the condition "%condition" to your Reaction Rule, or enable scheduled unpublishing via the <a href="@url">%type</a> settings.',
+        $arguments), 'warning', FALSE);
     }
   }
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php
index cba53e26183c35b41b8ac5566db5d74a1bed4a5a..f675af4bcb4d14200e15c4067d3d566a9dc98590 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php
@@ -2,30 +2,35 @@
 
 namespace Drupal\scheduler_rules_integration\Plugin\RulesAction;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\rules\Core\RulesActionBase;
 
 /**
- * Provides an 'Unpublish immediately' action.
+ * Provides an 'Unpublish the node immediately' action.
  *
  * @RulesAction(
- *   id = "scheduler_unpublish_now",
- *   deriver = "Drupal\scheduler_rules_integration\Plugin\RulesAction\SchedulerRulesActionDeriver"
+ *   id = "scheduler_unpublish_now_action",
+ *   label = @Translation("Unpublish the content immediately"),
+ *   category = @Translation("Scheduler"),
+ *   context_definitions = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node"),
+ *       description = @Translation("The node to be unpublished now"),
+ *     ),
+ *   }
  * )
  */
-class UnpublishNow extends SchedulerRulesActionBase {
+class UnpublishNow extends RulesActionBase {
 
   /**
-   * Set the entity status to Unpublished.
+   * Set the node status to Unpublished.
    *
-   * This action is provided by the Rules Module but only for node content, not
-   * Media. There is also a problem with recursion in the Rules action due to
-   * autoSaveContext(). Hence better for Scheduler to provide this action.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be unpublished.
+   * This action should really be provided by Rules or by Core, but it is not
+   * yet done (as of Aug 2016). Scheduler users need this action so we provide
+   * it here. It could be removed later when Rules or Core includes it.
    */
-  public function doExecute(EntityInterface $entity) {
-    $entity->setUnpublished();
+  public function doExecute() {
+    $node = $this->getContextValue('node');
+    $node->setUnpublished();
   }
 
 }
diff --git a/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesActionsTest.php b/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesActionsTest.php
deleted file mode 100644
index a1d26b3bf05c5761d73cbb72d83c2978851abfba..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesActionsTest.php
+++ /dev/null
@@ -1,425 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler_rules_integration\Functional;
-
-use Drupal\Core\Logger\RfcLogLevel;
-use Drupal\rules\Context\ContextConfig;
-use Drupal\Tests\scheduler\Functional\SchedulerBrowserTestBase;
-
-/**
- * Tests the six actions that Scheduler provides for use in Rules module.
- *
- * @group scheduler_rules_integration
- */
-class SchedulerRulesActionsTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Additional modules required.
-   *
-   * @var array
-   */
-  protected static $modules = ['scheduler_rules_integration'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $this->rulesStorage = $this->container->get('entity_type.manager')->getStorage('rules_reaction_rule');
-    $this->expressionManager = $this->container->get('plugin.manager.rules_expression');
-    $this->drupalLogin($this->adminUser);
-
-  }
-
-  /**
-   * Tests the actions which set and remove the 'Publish On' date.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testPublishOnActions($entityTypeId, $enabledBundle) {
-    $nonEnabledBundle = $this->entityTypeObject($entityTypeId, 'non-enabled')->id();
-    $titleField = $this->titleField($entityTypeId);
-    $publish_on = $this->requestTime + 1800;
-    $publish_on_formatted = $this->dateFormatter->format($publish_on, 'long');
-
-    // The legacy rules action ids for nodes remain as:
-    // -  scheduler_set_publishing_date_action
-    // -  scheduler_publish_now_action
-    // For all other entity types the new derived action ids are of the form:
-    // -  scheduler_set_publishing_date:{type}
-    // -  scheduler_publish_now:{type}
-    // .
-    $action_suffix = ($entityTypeId == 'node') ? '_action' : ":$entityTypeId";
-    $storage = $this->entityStorageObject($entityTypeId);
-
-    // Create rule 1 to set the publishing date.
-    $rule1 = $this->expressionManager->createRule();
-    $rule1->addCondition('rules_data_comparison',
-        ContextConfig::create()
-          ->map('data', "$entityTypeId.$titleField.value")
-          ->setValue('operation', 'contains')
-          ->setValue('value', 'Trigger Rule 1')
-    );
-    $message1 = 'RULES message 1. Action to set Publish-on date.';
-    $rule1->addAction("scheduler_set_publishing_date$action_suffix",
-      ContextConfig::create()
-        ->map('entity', "$entityTypeId")
-        ->setValue('date', $publish_on)
-      )
-      ->addAction('rules_system_message',
-        ContextConfig::create()
-          ->setValue('message', $message1)
-          ->setValue('type', 'status')
-    );
-    // The event needs to be rules_entity_presave:{type} 'before saving' because
-    // rules_entity_update:{type} 'after save' is too late to set the date.
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule1',
-      'events' => [['event_name' => "rules_entity_presave:$entityTypeId"]],
-      'expression' => $rule1->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create rule 2 to remove the publishing date and publish the entity.
-    $rule2 = $this->expressionManager->createRule();
-    $rule2->addCondition('rules_data_comparison',
-        ContextConfig::create()
-          ->map('data', "$entityTypeId.$titleField.value")
-          ->setValue('operation', 'contains')
-          ->setValue('value', 'Trigger Rule 2')
-    );
-    $message2 = 'RULES message 2. Action to remove Publish-on date and publish immediately.';
-    $rule2->addAction("scheduler_remove_publishing_date$action_suffix",
-      ContextConfig::create()
-        ->map('entity', "$entityTypeId")
-      )
-      ->addAction("scheduler_publish_now$action_suffix",
-        ContextConfig::create()
-          ->map('entity', "$entityTypeId")
-      )
-      ->addAction('rules_system_message',
-        ContextConfig::create()
-          ->setValue('message', $message2)
-          ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule2',
-      'events' => [['event_name' => "rules_entity_presave:$entityTypeId"]],
-      'expression' => $rule2->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    $assert = $this->assertSession();
-
-    // First, create a new scheduler-enabled entity, triggering rule 1.
-    $title = "First - new enabled $enabledBundle - Trigger Rule 1";
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $enabledBundle));
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s', $title, $publish_on_formatted));
-
-    // Check that rule 1 is triggered and rule 2 is not. Check that a publishing
-    // date has been set and the status is now unpublished.
-    $assert->pageTextContains($message1);
-    $assert->pageTextNotContains($message2);
-    $this->assertEquals($entity->publish_on->value, $publish_on, 'Entity should be scheduled for publishing at the correct time');
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing.');
-    $this->assertFalse($entity->isPublished(), 'Entity should be unpublished');
-
-    // Second, edit a pre-existing Scheduler-enabled entity, without triggering
-    // either of the rules.
-    $entity = $this->createEntity($entityTypeId, $enabledBundle, [
-      "$titleField" => "Second - existing enabled $enabledBundle",
-    ]);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit enabled $enabledBundle - but no rules will be triggered"], 'Save');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check that neither of the rules are triggered, no publish and unpublish
-    // dates are set and the status is still published.
-    $assert->pageTextNotContains($message1);
-    $assert->pageTextNotContains($message2);
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing');
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing');
-    $this->assertTrue($entity->isPublished(), 'Entity should remain published');
-
-    // Edit the entity, triggering rule 1.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit enabled $enabledBundle - Trigger Rule 1"], 'Save');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check that rule 1 is triggered and rule 2 is not. Check that a publishing
-    // date has been set and the status is now unpublished.
-    $assert->pageTextContains($message1);
-    $assert->pageTextNotContains($message2);
-    $this->assertEquals($entity->publish_on->value, $publish_on, 'Entity should be scheduled for publishing at the correct time');
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing');
-    $this->assertFalse($entity->isPublished(), 'Entity should be unpublished');
-
-    // Edit the entity, triggering rule 2.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit enabled $enabledBundle - Trigger Rule 2"], 'Save');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check that rule 2 is triggered and rule 1 is not. Check that the
-    // publishing date has been removed and the status is now published.
-    $assert->pageTextNotContains($message1);
-    $assert->pageTextContains($message2);
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing.');
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing.');
-    $this->assertTrue($entity->isPublished(), 'Entity should be published.');
-
-    // Third, create a new entity which is not scheduler-enabled.
-    $title = "Third - new non-enabled $nonEnabledBundle - Trigger Rule 1";
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $nonEnabledBundle));
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    // Check that rule 1 issued a warning message.
-    $assert->pageTextContains('warning message');
-    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
-    // Check that no publishing date is set.
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing');
-    // Check that a log message has been recorded.
-    $log = \Drupal::database()->select('watchdog', 'w')
-      ->condition('type', 'scheduler')
-      ->condition('severity', RfcLogLevel::WARNING)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    $this->assertEquals(1, $log, 'There is 1 watchdog warning message from Scheduler');
-
-    // Fourthly, edit a pre-existing entity which is not enabled for Scheduler,
-    // triggering rule 1.
-    $entity = $this->createEntity($entityTypeId, $nonEnabledBundle, [
-      "$titleField" => "Fourth - existing non-enabled $nonEnabledBundle",
-    ]);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit non-enabled $nonEnabledBundle - Trigger Rule 1"], 'Save');
-    // Check that rule 1 issued a warning message.
-    $assert->pageTextContains('warning message');
-    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
-    // Check that no publishing date is set.
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing.');
-    // Check that a log message has been recorded.
-    $log = \Drupal::database()->select('watchdog', 'w')
-      ->condition('type', 'scheduler')
-      ->condition('severity', RfcLogLevel::WARNING)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    $this->assertEquals(2, $log, 'There are now 2 watchdog warning messages from Scheduler');
-
-    // Edit the entity again, triggering rule 2.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit non-enabled $nonEnabledBundle - Trigger Rule 2"], 'Save');
-    // Check that rule 2 issued a warning message.
-    $assert->pageTextContains('warning message');
-    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
-    // Check that a second log message has been recorded.
-    $log = \Drupal::database()->select('watchdog', 'w')
-      ->condition('type', 'scheduler')
-      ->condition('severity', RfcLogLevel::WARNING)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    $this->assertEquals(3, $log, 'There are now 3 watchdog warning messages from Scheduler');
-    $this->drupalGet('admin/reports/dblog');
-  }
-
-  /**
-   * Tests the actions which set and remove the 'Unpublish On' date.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testUnpublishOnActions($entityTypeId, $enabledBundle) {
-    $nonEnabledBundle = $this->entityTypeObject($entityTypeId, 'non-enabled')->id();
-    $titleField = $this->titleField($entityTypeId);
-    $unpublish_on = $this->requestTime + 2400;
-    $unpublish_on_formatted = $this->dateFormatter->format($unpublish_on, 'long');
-
-    // The legacy rules action ids for nodes remain as:
-    // -  scheduler_set_unpublishing_date_action
-    // -  scheduler_unpublish_now_action
-    // For all other entity types the new derived action ids are of the form:
-    // -  scheduler_set_unpublishing_date:{type}
-    // -  scheduler_unpublish_now:{type}
-    // .
-    $action_suffix = ($entityTypeId == 'node') ? '_action' : ":$entityTypeId";
-    $storage = $this->entityStorageObject($entityTypeId);
-
-    // Create rule 3 to set the unpublishing date.
-    $rule3 = $this->expressionManager->createRule();
-    $rule3->addCondition('rules_data_comparison',
-        ContextConfig::create()
-          ->map('data', "$entityTypeId.$titleField.value")
-          ->setValue('operation', 'contains')
-          ->setValue('value', 'Trigger Rule 3')
-    );
-    $message3 = 'RULES message 3. Action to set Unpublish-on date.';
-    $rule3->addAction("scheduler_set_unpublishing_date$action_suffix",
-      ContextConfig::create()
-        ->map('entity', "$entityTypeId")
-        ->setValue('date', $unpublish_on)
-      )
-      ->addAction('rules_system_message',
-        ContextConfig::create()
-          ->setValue('message', $message3)
-          ->setValue('type', 'status')
-    );
-    // The event needs to be rules_entity_presave:{type} 'before saving' because
-    // rules_entity_update:{type} 'after save' is too late to set the date.
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule3',
-      'events' => [['event_name' => "rules_entity_presave:$entityTypeId"]],
-      'expression' => $rule3->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create rule 4 to remove the unpublishing date and unpublish the entity.
-    $rule4 = $this->expressionManager->createRule();
-    $rule4->addCondition('rules_data_comparison',
-        ContextConfig::create()
-          ->map('data', "$entityTypeId.$titleField.value")
-          ->setValue('operation', 'contains')
-          ->setValue('value', 'Trigger Rule 4')
-    );
-    $message4 = "RULES message 4. Action to remove Unpublish-on date and unpublish the $entityTypeId immediately.";
-    $rule4->addAction("scheduler_remove_unpublishing_date$action_suffix",
-      ContextConfig::create()
-        ->map('entity', "$entityTypeId")
-      )
-      ->addAction("scheduler_unpublish_now$action_suffix",
-        ContextConfig::create()
-          ->map('entity', "$entityTypeId")
-      )
-      ->addAction('rules_system_message',
-        ContextConfig::create()
-          ->setValue('message', $message4)
-          ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule4',
-      'events' => [['event_name' => "rules_entity_presave:$entityTypeId"]],
-      'expression' => $rule4->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    $assert = $this->assertSession();
-
-    // First, create a new scheduler-enabled entity, triggering rule 3.
-    $title = "First - new enabled $enabledBundle - Trigger Rule 3";
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $enabledBundle));
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be unpublished %s', $title, $unpublish_on_formatted));
-
-    // Check that rule 3 is triggered and rule 4 is not. Check that a publishing
-    // date has been set and the status is now unpublished.
-    $assert->pageTextContains($message3);
-    $assert->pageTextNotContains($message4);
-    $this->assertEquals($entity->unpublish_on->value, $unpublish_on, 'Entity should be scheduled for unpublishing at the correct time');
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing.');
-    $this->assertTrue($entity->isPublished(), 'Entity should be published');
-
-    // Second, edit a pre-existing Scheduler-enabled entity, without triggering
-    // either of the rules.
-    $entity = $this->createEntity($entityTypeId, $enabledBundle, [
-      "$titleField" => "Second - existing enabled $enabledBundle",
-    ]);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit enabled $enabledBundle - but no rules will be triggered"], 'Save');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check that neither of the rules are triggered, no publish and unpublish
-    // dates are set and the status is still published.
-    $assert->pageTextNotContains($message3);
-    $assert->pageTextNotContains($message4);
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing');
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing');
-    $this->assertTrue($entity->isPublished(), 'Entity should remain published');
-
-    // Edit the entity, triggering rule 3.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit enabled $enabledBundle - Trigger Rule 3"], 'Save');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check that rule 3 is triggered and rule 4 is not. Check that an
-    // unpublishing date has been set and the status is still published.
-    $assert->pageTextContains($message3);
-    $assert->pageTextNotContains($message4);
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing');
-    $this->assertEquals($entity->unpublish_on->value, $unpublish_on, 'Entity should be scheduled for unpublishing at the correct time');
-    $this->assertTrue($entity->isPublished(), 'Entity is still published');
-
-    // Edit the entity, triggering rule 4.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit enabled $enabledBundle - Trigger Rule 4"], 'Save');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check that rule 4 is triggered and rule 3 is not. Check that the
-    // unpublishing date has been removed and the status is now unpublished.
-    $assert->pageTextNotContains($message3);
-    $assert->pageTextContains($message4);
-    $this->assertEmpty($entity->publish_on->value, 'Entity should not be scheduled for publishing.');
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing.');
-    $this->assertFalse($entity->isPublished(), 'Entity should be unpublished.');
-
-    // Third, create a new entity which is not scheduler-enabled.
-    $title = "Third - new non-enabled $nonEnabledBundle - Trigger Rule 3";
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $nonEnabledBundle));
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    // Check that rule 3 issued a warning message.
-    $assert->pageTextContains('warning message');
-    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
-    // Check that no publishing date is set.
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing');
-    // Check that a log message has been recorded.
-    $log = \Drupal::database()->select('watchdog', 'w')
-      ->condition('type', 'scheduler')
-      ->condition('severity', RfcLogLevel::WARNING)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    $this->assertEquals(1, $log, 'There is 1 watchdog warning message from Scheduler');
-
-    // Fourthly, edit a pre-existing entity which is not enabled for Scheduler,
-    // triggering rule 3.
-    $entity = $this->createEntity($entityTypeId, $nonEnabledBundle, [
-      "$titleField" => "Fourth - existing non-enabled $nonEnabledBundle",
-    ]);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit non-enabled $nonEnabledBundle - Trigger Rule 3"], 'Save');
-    // Check that rule 3 issued a warning message.
-    $assert->pageTextContains('warning message');
-    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
-    // Check that no unpublishing date is set.
-    $this->assertEmpty($entity->unpublish_on->value, 'Entity should not be scheduled for unpublishing.');
-    // Check that a log message has been recorded.
-    $log = \Drupal::database()->select('watchdog', 'w')
-      ->condition('type', 'scheduler')
-      ->condition('severity', RfcLogLevel::WARNING)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    $this->assertEquals(2, $log, 'There are now 2 watchdog warning messages from Scheduler');
-
-    // Edit the entity again, triggering rule 4.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => "Edit non-enabled $nonEnabledBundle - Trigger Rule 4"], 'Save');
-    // Check that rule 4 issued a warning message.
-    $assert->pageTextContains('warning message');
-    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
-    // Check that a second log message has been recorded.
-    $log = \Drupal::database()->select('watchdog', 'w')
-      ->condition('type', 'scheduler')
-      ->condition('severity', RfcLogLevel::WARNING)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    $this->assertEquals(3, $log, 'There are now 3 watchdog warning messages from Scheduler');
-    $this->drupalGet('admin/reports/dblog');
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesConditionsTest.php b/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesConditionsTest.php
deleted file mode 100644
index f2141bfcfca298dca5bded3173da7b5409c485e7..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesConditionsTest.php
+++ /dev/null
@@ -1,292 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler_rules_integration\Functional;
-
-use Drupal\rules\Context\ContextConfig;
-use Drupal\Tests\scheduler\Functional\SchedulerBrowserTestBase;
-
-/**
- * Tests the four conditions that Scheduler provides for use in Rules module.
- *
- * @group scheduler_rules_integration
- */
-class SchedulerRulesConditionsTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Additional modules required.
-   *
-   * @var array
-   */
-  protected static $modules = ['scheduler_rules_integration'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    $this->rulesStorage = $this->container->get('entity_type.manager')->getStorage('rules_reaction_rule');
-    $this->expressionManager = $this->container->get('plugin.manager.rules_expression');
-  }
-
-  /**
-   * Tests the conditions for whether an entity type is enabled for Scheduler.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testEntityTypeEnabledConditions($entityTypeId, $bundle) {
-
-    // The legacy rules condition ids for nodes remain as:
-    // -  scheduler_condition_publishing_is_enabled
-    // -  scheduler_condition_unpublishing_is_enabled
-    // For all other entity types the new derived condition ids are of the form:
-    // -  scheduler_publishing_is_enabled:{type}
-    // -  scheduler_unpublishing_is_enabled:{type}
-    // .
-    $condition_prefix = ($entityTypeId == 'node') ? 'scheduler_condition_' : 'scheduler_';
-    $condition_suffix = ($entityTypeId == 'node') ? '' : ":$entityTypeId";
-    $entityType = $this->entityTypeObject($entityTypeId, $bundle);
-    $assert = $this->assertSession();
-
-    // Create a reaction rule to display a message when viewing an entity of a
-    // type that is enabled for scheduled publishing.
-    // "viewing content" actually means "viewing PUBLISHED content".
-    $rule1 = $this->expressionManager->createRule();
-    $rule1->addCondition("{$condition_prefix}publishing_is_enabled{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")
-    );
-    $message1 = 'RULES message 1. This entity type is enabled for scheduled publishing.';
-    $rule1->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message1)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule1',
-      'events' => [['event_name' => "rules_entity_view:$entityTypeId"]],
-      'expression' => $rule1->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create a reaction rule to display a message when viewing an entity of a
-    // type that is enabled for scheduled unpublishing.
-    $rule2 = $this->expressionManager->createRule();
-    $rule2->addCondition("{$condition_prefix}unpublishing_is_enabled{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")
-    );
-    $message2 = 'RULES message 2. This entity type is enabled for scheduled unpublishing.';
-    $rule2->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message2)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule2',
-      'events' => [['event_name' => "rules_entity_view:$entityTypeId"]],
-      'expression' => $rule2->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create a reaction rule to display a message when viewing an entity of a
-    // type that is NOT enabled for scheduled publishing.
-    $rule3 = $this->expressionManager->createRule();
-    $rule3->addCondition("{$condition_prefix}publishing_is_enabled{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")->negateResult()
-    );
-    $message3 = 'RULES message 3. This entity type is not enabled for scheduled publishing.';
-    $rule3->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message3)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule3',
-      'events' => [['event_name' => "rules_entity_view:$entityTypeId"]],
-      'expression' => $rule3->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create a reaction rule to display a message when viewing an entity of a
-    // type that is NOT enabled for scheduled unpublishing.
-    $rule4 = $this->expressionManager->createRule();
-    $rule4->addCondition("{$condition_prefix}unpublishing_is_enabled{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")->negateResult()
-    );
-    $message4 = 'RULES message 4. This entity type is not enabled for scheduled unpublishing.';
-    $rule4->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message4)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule4',
-      'events' => [['event_name' => "rules_entity_view:$entityTypeId"]],
-      'expression' => $rule4->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create a published entity.
-    $entity = $this->createEntity($entityTypeId, $bundle, [
-      'title' => "Enabled Conditions - $entityTypeId $bundle",
-      'status' => TRUE,
-    ]);
-
-    // View the entity and check the default position - that the entity type is
-    // enabled for both publishing and unpublishing.
-    $this->drupalGet($entity->toUrl());
-    $assert->pageTextContains($message1);
-    $assert->pageTextContains($message2);
-    $assert->pageTextNotContains($message3);
-    $assert->pageTextNotContains($message4);
-
-    // Turn off scheduled publishing for the entity type and check the rules.
-    $entityType->setThirdPartySetting('scheduler', 'publish_enable', FALSE)->save();
-    drupal_flush_all_caches();
-    $this->drupalGet($entity->toUrl());
-    $assert->pageTextNotContains($message1);
-    $assert->pageTextContains($message2);
-    $assert->pageTextContains($message3);
-    $assert->pageTextNotContains($message4);
-
-    // Turn off scheduled unpublishing for the entity type and the check again.
-    $entityType->setThirdPartySetting('scheduler', 'unpublish_enable', FALSE)->save();
-    drupal_flush_all_caches();
-    $this->drupalGet($entity->toUrl());
-    $assert->pageTextNotContains($message1);
-    $assert->pageTextNotContains($message2);
-    $assert->pageTextContains($message3);
-    $assert->pageTextContains($message4);
-
-  }
-
-  /**
-   * Tests the conditions for whether an entity is scheduled.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testEntityIsScheduledConditions($entityTypeId, $bundle) {
-    // The legacy rules condition ids for nodes remain as:
-    // -  scheduler_condition_node_scheduled_for_publishing
-    // -  scheduler_condition_node_scheduled_for_unpublishing
-    // For all other entity types the new derived condition ids are of the form:
-    // -  scheduler_entity_is_scheduled_for_publishing:{type}
-    // -  scheduler_entity_is_scheduled_for_unpublishing:{type}
-    // .
-    $condition_prefix = ($entityTypeId == 'node') ? 'scheduler_condition_node_' : 'scheduler_entity_is_';
-    $condition_suffix = ($entityTypeId == 'node') ? '' : ":$entityTypeId";
-    $assert = $this->assertSession();
-
-    // Create a reaction rule to display a message when an entity is updated and
-    // is not scheduled for publishing.
-    $rule5 = $this->expressionManager->createRule();
-    $rule5->addCondition("{$condition_prefix}scheduled_for_publishing{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")->negateResult()
-    );
-    $message5 = "RULES message 5. This $entityTypeId is not scheduled for publishing.";
-    $rule5->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message5)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule5',
-      'events' => [['event_name' => "rules_entity_update:$entityTypeId"]],
-      'expression' => $rule5->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create a reaction rule to display a message when an entity is updated and
-    // is not scheduled for unpublishing.
-    $rule6 = $this->expressionManager->createRule();
-    $rule6->addCondition("{$condition_prefix}scheduled_for_unpublishing{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")->negateResult()
-    );
-    $message6 = "RULES message 6. This $entityTypeId is not scheduled for unpublishing.";
-    $rule6->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message6)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule6',
-      'events' => [['event_name' => "rules_entity_update:$entityTypeId"]],
-      'expression' => $rule6->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create a reaction rule to display a message when an entity is updated and
-    // is scheduled for publishing.
-    $rule7 = $this->expressionManager->createRule();
-    $rule7->addCondition("{$condition_prefix}scheduled_for_publishing{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")
-    );
-    $message7 = "RULES message 7. This $entityTypeId is scheduled for publishing.";
-    $rule7->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message7)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule7',
-      'events' => [['event_name' => "rules_entity_update:$entityTypeId"]],
-      'expression' => $rule7->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    // Create a reaction rule to display a message when an entity is updated and
-    // is scheduled for unpublishing.
-    $rule8 = $this->expressionManager->createRule();
-    $rule8->addCondition("{$condition_prefix}scheduled_for_unpublishing{$condition_suffix}",
-      ContextConfig::create()->map('entity', "$entityTypeId")
-    );
-    $message8 = "RULES message 8. This $entityTypeId is scheduled for unpublishing.";
-    $rule8->addAction('rules_system_message', ContextConfig::create()
-      ->setValue('message', $message8)
-      ->setValue('type', 'status')
-      );
-    $config_entity = $this->rulesStorage->create([
-      'id' => 'rule8',
-      'events' => [['event_name' => "rules_entity_update:$entityTypeId"]],
-      'expression' => $rule8->getConfiguration(),
-    ]);
-    $config_entity->save();
-
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create a published entity.
-    $entity = $this->createEntity($entityTypeId, $bundle, [
-      'title' => "Scheduled Conditions - $entityTypeId $bundle",
-      'uid' => $this->schedulerUser->id(),
-      'status' => TRUE,
-    ]);
-
-    // Edit the entity but do not enter any scheduling dates, and check that
-    // only messages 5 and 6 are shown.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
-    $assert->pageTextContains($message5);
-    $assert->pageTextContains($message6);
-    $assert->pageTextNotContains($message7);
-    $assert->pageTextNotContains($message8);
-
-    // Edit the entity, set a publish_on date, and check that message 5 is now
-    // not shown and we get message 7 instead.
-    $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', $this->requestTime)),
-    ];
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
-    $assert->pageTextNotContains($message5);
-    $assert->pageTextContains($message6);
-    $assert->pageTextContains($message7);
-    $assert->pageTextNotContains($message8);
-
-    // Edit the entity again, set an unpublish_on date, and check that message 6
-    // is now not shown and we get message 8 instead.
-    $edit = [
-      'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', $this->requestTime)),
-      'unpublish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', $this->requestTime)),
-    ];
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
-    $assert->pageTextNotContains($message5);
-    $assert->pageTextNotContains($message6);
-    $assert->pageTextContains($message7);
-    $assert->pageTextContains($message8);
-
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesEventsTest.php b/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesEventsTest.php
deleted file mode 100644
index 62e1666c9997669759b50464857461b2a19fb880..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_rules_integration/tests/src/Functional/SchedulerRulesEventsTest.php
+++ /dev/null
@@ -1,246 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler_rules_integration\Functional;
-
-use Drupal\rules\Context\ContextConfig;
-use Drupal\Tests\scheduler\Functional\SchedulerBrowserTestBase;
-
-/**
- * Tests the six events that Scheduler provides for use in Rules module.
- *
- * phpcs:set Drupal.Arrays.Array lineLimit 140
- *
- * @group scheduler_rules_integration
- */
-class SchedulerRulesEventsTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Additional modules required.
-   *
-   * @var array
-   */
-  protected static $modules = ['scheduler_rules_integration'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $this->rulesStorage = $this->container->get('entity_type.manager')->getStorage('rules_reaction_rule');
-    $this->expressionManager = $this->container->get('plugin.manager.rules_expression');
-
-    // Create a reaction rule to display a system message for each of the six
-    // events that Scheduler triggers, for each entity type. The array of data
-    // contains the event name and the text to display.
-    // These rules are all active throughout all of the tests, which makes the
-    // tests stronger, because it will show not only that the correct events are
-    // triggered in the right places, but also that they are not triggered in
-    // the wrong places.
-    $rule_data = [
-      // The first six events are the originals, only dispatched for Nodes.
-      1 => ['scheduler_new_node_is_scheduled_for_publishing_event', 'A new node is created and is scheduled for publishing.'],
-      2 => ['scheduler_existing_node_is_scheduled_for_publishing_event', 'An existing node is saved and is scheduled for publishing.'],
-      3 => ['scheduler_has_published_this_node_event', 'Scheduler has published this node during cron.'],
-      4 => ['scheduler_new_node_is_scheduled_for_unpublishing_event', 'A new node is created and is scheduled for unpublishing.'],
-      5 => ['scheduler_existing_node_is_scheduled_for_unpublishing_event', 'An existing node is saved and is scheduled for unpublishing.'],
-      6 => ['scheduler_has_unpublished_this_node_event', 'Scheduler has unpublished this node during cron.'],
-      // These six events are dispatched only for Media entities.
-      7 => ['scheduler:new_media_is_scheduled_for_publishing', 'A new media item is created and scheduled for publishing.'],
-      8 => ['scheduler:existing_media_is_scheduled_for_publishing', 'An existing media item is saved and scheduled for publishing.'],
-      9 => ['scheduler:media_has_been_published_via_cron', 'Scheduler has published this media item during cron.'],
-      10 => ['scheduler:new_media_is_scheduled_for_unpublishing', 'A new media item is created and scheduled for unpublishing.'],
-      11 => ['scheduler:existing_media_is_scheduled_for_unpublishing', 'An existing media item is saved and scheduled for unpublishing.'],
-      12 => ['scheduler:media_has_been_unpublished_via_cron', 'Scheduler has unpublished this media item during cron.'],
-      // These six events are dispatched only for Commerce Product entities.
-      13 => ['scheduler:new_commerce_product_is_scheduled_for_publishing', 'A new product is created and scheduled for publishing.'],
-      14 => ['scheduler:existing_commerce_product_is_scheduled_for_publishing', 'An existing product is scheduled for publishing.'],
-      15 => ['scheduler:commerce_product_has_been_published_via_cron', 'Scheduler has published this product during cron.'],
-      16 => ['scheduler:new_commerce_product_is_scheduled_for_unpublishing', 'A new product is created and scheduled for unpublishing.'],
-      17 => ['scheduler:existing_commerce_product_is_scheduled_for_unpublishing', 'An existing product is scheduled for unpublishing.'],
-      18 => ['scheduler:commerce_product_has_been_unpublished_via_cron', 'Scheduler has unpublished this product during cron.'],
-      // These six events are dispatched only for Taxonomy Term entities.
-      19 => ['scheduler:new_taxonomy_term_is_scheduled_for_publishing', 'A new taxonomy term is created and scheduled for publishing.'],
-      20 => ['scheduler:existing_taxonomy_term_is_scheduled_for_publishing', 'An existing taxonomy term is scheduled for publishing.'],
-      21 => ['scheduler:taxonomy_term_has_been_published_via_cron', 'Scheduler has published this taxonomy term during cron.'],
-      22 => ['scheduler:new_taxonomy_term_is_scheduled_for_unpublishing', 'A new taxonomy term is created and scheduled for unpublishing.'],
-      23 => ['scheduler:existing_taxonomy_term_is_scheduled_for_unpublishing', 'An existing taxonomy term is scheduled for unpublishing.'],
-      24 => ['scheduler:taxonomy_term_has_been_unpublished_via_cron', 'Scheduler has unpublished this taxonomy term during cron.'],
-    ];
-
-    $rule = [];
-    foreach ($rule_data as $i => [$event_name, $description]) {
-      $rule[$i] = $this->expressionManager->createRule();
-      $this->message[$i] = 'RULES message ' . $i . '. ' . $description;
-      $rule[$i]->addAction('rules_system_message', ContextConfig::create()
-        ->setValue('message', $this->message[$i])
-        ->setValue('type', 'status')
-        );
-      $config_entity = $this->rulesStorage->create([
-        'id' => 'rule' . $i,
-        'events' => [['event_name' => $event_name]],
-        'expression' => $rule[$i]->getConfiguration(),
-      ]);
-      $config_entity->save();
-    }
-
-    $this->drupalLogin($this->schedulerUser);
-  }
-
-  /**
-   * Check the presence or absence of expected message texts on the page.
-   *
-   * @param string $entityTypeId
-   *   The entity type being tested.
-   * @param array $expectedMessages
-   *   The ids of the messages that should be showing on the current page. All
-   *   other messsages should not be displayed.
-   */
-  public function checkMessages(string $entityTypeId = NULL, array $expectedMessages = []) {
-    // Add the required entity offset to each message id in the expected array.
-    $offset = ['node' => 0, 'media' => 6, 'commerce_product' => 12, 'taxonomy_term' => 18];
-    array_walk($expectedMessages, function (&$item) use ($offset, $entityTypeId) {
-      $item = $item + $offset[$entityTypeId];
-    });
-
-    // Check that all the expected messages are shown.
-    foreach ($expectedMessages as $i) {
-      $this->assertSession()->pageTextContains($this->message[$i]);
-    }
-
-    // Check that none of the other messages are shown.
-    $notExpecting = array_diff(array_keys($this->message), $expectedMessages);
-    foreach ($notExpecting as $i) {
-      $this->assertSession()->pageTextNotContains($this->message[$i]);
-    }
-  }
-
-  /**
-   * Tests that no events are triggered when there are no scheduling dates.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testRulesEventsNone($entityTypeId, $bundle) {
-    // Add and save an entity without any scheduled dates and check that no
-    // events are triggered.
-    $titleField = $this->titleField($entityTypeId);
-    $title = 'A. Create with no dates';
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
-    $this->checkMessages();
-
-    // Edit the entity and check that no events are triggered.
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => 'B. Edit with no dates'], 'Save');
-    $this->checkMessages();
-  }
-
-  /**
-   * Tests the three events related to publishing an entity.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testRulesEventsPublish($entityTypeId, $bundle) {
-    // Allow dates in the past.
-    $this->entityTypeObject($entityTypeId, $bundle)
-      ->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
-
-    // Create an entity with a publish-on date, and check that only event 1 is
-    // triggered.
-    $titleField = $this->titleField($entityTypeId);
-    $title = 'C. Create with publish-on date';
-    $edit = [
-      "{$titleField}[0][value]" => $title,
-      'publish_on[0][value][date]' => date('Y-m-d', time() - 60),
-      'publish_on[0][value][time]' => date('H:i:s', time() - 60),
-    ];
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->submitForm($edit, 'Save');
-    $this->checkMessages($entityTypeId, [1]);
-
-    // Edit the entity and check that only event 2 is triggered.
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => 'D. Edit with publish-on date'], 'Save');
-    $this->checkMessages($entityTypeId, [2]);
-
-    // Run cron and check that only event 3 is triggered.
-    $this->cronRun();
-    $this->drupalGet($entity->toUrl());
-    $this->checkMessages($entityTypeId, [3]);
-  }
-
-  /**
-   * Tests the three events related to unpublishing an entity.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testRulesEventsUnpublish($entityTypeId, $bundle) {
-    // Create an entity with an unpublish-on date, and check that only event 4
-    // is triggered.
-    $titleField = $this->titleField($entityTypeId);
-    $title = 'E. Create with unpublish-on date';
-    $edit = [
-      "{$titleField}[0][value]" => $title,
-      'unpublish_on[0][value][date]' => date('Y-m-d', time() + 5),
-      'unpublish_on[0][value][time]' => date('H:i:s', time() + 5),
-    ];
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->submitForm($edit, 'Save');
-    $this->checkMessages($entityTypeId, [4]);
-
-    // Edit the entity and check that only event 5 is triggered.
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => 'F. Edit with unpublish-on date'], 'Save');
-    $this->checkMessages($entityTypeId, [5]);
-
-    // Delay to ensure that the dates are in the past so that the entity will be
-    // processed during cron, and check that only event 6 is triggered.
-    sleep(6);
-    $this->cronRun();
-    $this->drupalGet($entity->toUrl());
-    $this->checkMessages($entityTypeId, [6]);
-  }
-
-  /**
-   * Tests all six events related to publishing and unpublishing an entity.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testRulesEventsBoth($entityTypeId, $bundle) {
-    // Allow dates in the past.
-    $this->entityTypeObject($entityTypeId, $bundle)
-      ->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
-
-    // Create an entity with both publish-on and unpublish-on dates, and check
-    // that both event 1 and event 4 are triggered.
-    $titleField = $this->titleField($entityTypeId);
-    $title = 'G. Create with both dates';
-    $edit = [
-      "{$titleField}[0][value]" => $title,
-      'publish_on[0][value][date]' => date('Y-m-d', time() - 60),
-      'publish_on[0][value][time]' => date('H:i:s', time() - 60),
-      'unpublish_on[0][value][date]' => date('Y-m-d', time() + 5),
-      'unpublish_on[0][value][time]' => date('H:i:s', time() + 5),
-    ];
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->submitForm($edit, 'Save');
-    $this->checkMessages($entityTypeId, [1, 4]);
-
-    // Edit the entity and check that only events 2 and 5 are triggered.
-    $entity = $this->getEntityByTitle($entityTypeId, $title);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => 'H. Edit with both dates'], 'Save');
-    $this->checkMessages($entityTypeId, [2, 5]);
-
-    // Delay to ensure that the dates are in the past so that the entity will be
-    // processed during cron, and assert that events 3, 5 and 6 are triggered.
-    sleep(6);
-    $this->cronRun();
-    $this->drupalGet($entity->toUrl());
-    $this->checkMessages($entityTypeId, [3, 5, 6]);
-  }
-
-}
diff --git a/web/modules/scheduler/src/Access/ScheduledListAccess.php b/web/modules/scheduler/src/Access/ScheduledListAccess.php
new file mode 100644
index 0000000000000000000000000000000000000000..a63d0e77f0424147005f36e8095fc68af7316704
--- /dev/null
+++ b/web/modules/scheduler/src/Access/ScheduledListAccess.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\scheduler\Access;
+
+use Drupal\Core\Access\AccessCheckInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Checks access for displaying the scheduler list of scheduled nodes.
+ */
+class ScheduledListAccess implements AccessCheckInterface {
+
+  /**
+   * The current route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * Constructs a ScheduledListAccess object.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match.
+   */
+  public function __construct(RouteMatchInterface $route_match) {
+    $this->routeMatch = $route_match;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Route $route) {
+    return $route->hasRequirement('_access_scheduler_content');
+  }
+
+  /**
+   * Determine if the $account has access to the scheduled content list.
+   *
+   * The result will vary depending on whether the page being viewed is the user
+   * profile page or the scheduled content admin overview.
+   */
+  public function access(AccountInterface $account) {
+    // When viewing a user profile page routeMatch->getRawParameter('user')
+    // returns the user's id. If not on a user page it returns NULL silently.
+    $viewing_own_tab = $this->routeMatch->getRawParameter('user') == $account->id();
+
+    // Users with 'schedule publishing of nodes' can see their own scheduled
+    // content via a tab on their user page. Users with 'view scheduled content'
+    // will be able to access the 'scheduled' tab for any user, and also access
+    // the scheduled content overview page.
+    $allowed = $account->hasPermission('view scheduled content')
+      || ($viewing_own_tab && $account->hasPermission('schedule publishing of nodes'));
+    return $allowed ? AccessResult::allowed() : AccessResult::forbidden();
+  }
+
+}
diff --git a/web/modules/scheduler/src/Access/SchedulerRouteAccess.php b/web/modules/scheduler/src/Access/SchedulerRouteAccess.php
deleted file mode 100644
index 31f6d63a672a982a5196ecfdf5231ce27cdcda23..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Access/SchedulerRouteAccess.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Access;
-
-use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\user\Entity\User;
-
-/**
- * Sets access for specific scheduler views routes.
- */
-class SchedulerRouteAccess {
-
-  /**
-   * Provides custom access checks for the scheduled views on the user page.
-   *
-   * A user is given access if either of the following conditions are met:
-   * - they are viewing their own page and they have the permission to schedule
-   * content or view scheduled content of the required type.
-   * - they are viewing another user's page and they have permission to view
-   * user profiles and view scheduled content, and the user they are viewing has
-   * permission to schedule content or view scheduled content.
-   *
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The currently logged in account.
-   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
-   *   The current route match.
-   */
-  public function access(AccountInterface $account, RouteMatchInterface $route_match) {
-    $user_being_viewed = $route_match->getParameter('user');
-    $viewing_own_page = $user_being_viewed == $account->id();
-
-    // getUserPageViewRoutes() returns an array of user page view routes, keyed
-    // on the entity id. Use this to get the entity id.
-    $scheduler_manager = \Drupal::service('scheduler.manager');
-    $entityTypeId = array_search($route_match->getRouteName(), $scheduler_manager->getUserPageViewRoutes());
-    $viewing_permission_name = $scheduler_manager->permissionName($entityTypeId, 'view');
-    $scheduling_permission_name = $scheduler_manager->permissionName($entityTypeId, 'schedule');
-
-    if ($viewing_own_page && ($account->hasPermission($viewing_permission_name) || $account->hasPermission($scheduling_permission_name))) {
-      return AccessResult::allowed();
-    }
-    if (!$viewing_own_page && $account->hasPermission($viewing_permission_name) && $account->hasPermission('access user profiles')) {
-      $other_user = User::load($user_being_viewed);
-      if ($other_user && ($other_user->hasPermission($viewing_permission_name) || $other_user->hasPermission($scheduling_permission_name))) {
-        return AccessResult::allowed();
-      }
-    }
-    return AccessResult::forbidden();
-  }
-
-}
diff --git a/web/modules/scheduler/src/Annotation/SchedulerPlugin.php b/web/modules/scheduler/src/Annotation/SchedulerPlugin.php
deleted file mode 100644
index ce0fd2b09480792e7fa49e339c96721817524448..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Annotation/SchedulerPlugin.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Annotation;
-
-use Drupal\Component\Annotation\Plugin;
-
-/**
- * Annotation class for scheduler entity plugins.
- *
- * @package Drupal\scheduler\Annotation
- *
- * @Annotation
- */
-class SchedulerPlugin extends Plugin {
-
-  /**
-   * The internal id / machine name of the plugin.
-   *
-   * @var string
-   */
-  public $id;
-
-  /**
-   * The readable name of the plugin.
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   *
-   * @ingroup plugin_translatable
-   */
-  public $label;
-
-  /**
-   * Description of plugin.
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   *
-   * @ingroup plugin_translatable
-   */
-  public $description;
-
-  /**
-   * The entity type.
-   *
-   * @var string
-   */
-  public $entityType;
-
-  /**
-   * The name of the type/bundle field.
-   *
-   * @var string
-   */
-  public $typeFieldName;
-
-  /**
-   * Module name that plugin requires.
-   *
-   * @var string
-   */
-  public $dependency;
-
-  /**
-   * The Form ID of the devel generate form (optional).
-   *
-   * @var string
-   */
-  public $develGenerateForm = '';
-
-  /**
-   * The route of the collection overview page.
-   *
-   * The default is entity.{$entityType}.collection so this property only needs
-   * to be specified if that route is not the correct one.
-   *
-   * @var string
-   */
-  public $collectionRoute;
-
-  /**
-   * The route of the scheduled view on the user profile page (optional).
-   *
-   * @var string
-   */
-  public $userViewRoute = '';
-
-  /**
-   * The event class for Scheduler events relating to activity on the entity.
-   *
-   * This is optional, and if not specified, will default to the standard class
-   *   \Drupal\scheduler\Event\Scheduler{EntityType}Events
-   * The class must be in UpperCamelCase with no underscores, so if entityType
-   * contains underscores then this property must be specified. The convention
-   * in this case is to convert each word to upper case and remove underscores.
-   *
-   * @var string
-   */
-  public $schedulerEventClass;
-
-  /**
-   * The name of the publish action for the entity type (optional).
-   *
-   * This is used when the action name does not match the default pattern of
-   * {entity type id}_publish_action.
-   *
-   * @var string
-   */
-  public $publishAction;
-
-  /**
-   * The name of the unpublish action for the entity type (optional).
-   *
-   * This is used when the action name does not match the default pattern of
-   * {entity type id}_unpublish_action.
-   *
-   * @var string
-   */
-  public $unpublishAction;
-
-}
diff --git a/web/modules/scheduler/src/Commands/SchedulerCommands.php b/web/modules/scheduler/src/Commands/SchedulerCommands.php
index 83e2a5c5403391fc1a63b26a98cc7d071b5719f0..0d73d2b720f262fb19e20420f83d09935d88cdd2 100644
--- a/web/modules/scheduler/src/Commands/SchedulerCommands.php
+++ b/web/modules/scheduler/src/Commands/SchedulerCommands.php
@@ -5,7 +5,6 @@
 use Drupal\Core\Messenger\MessengerInterface;
 use Drupal\scheduler\SchedulerManager;
 use Drush\Commands\DrushCommands;
-use Drush\Utils\StringUtils;
 
 /**
  * Drush 9 Scheduler commands for Drupal Core 8.4+.
@@ -62,35 +61,4 @@ public function cron(array $options = ['nomsg' => NULL, 'nolog' => NULL]) {
     $options['nomsg'] ? NULL : $this->messenger->addMessage(dt('Scheduler lightweight cron completed.'));
   }
 
-  /**
-   * Entity Update - add Scheduler fields for entities covered by plugins.
-   *
-   * Use the standard drush parameter -q for quiet mode (no terminal output).
-   *
-   * @command scheduler:entity-update
-   * @aliases sch-ent-upd, sch-upd, scheduler-entity-update
-   */
-  public function entityUpdate() {
-    $result = $this->schedulerManager->entityUpdate();
-    $updated = $result ? implode(', ', $result) : dt('nothing to update');
-    $this->messenger->addMessage(dt('Scheduler entity update - @updated', ['@updated' => $updated]));
-  }
-
-  /**
-   * Entity Revert - remove Scheduler fields and third-party-settings.
-   *
-   * Use the standard drush parameter -q for quiet mode (no terminal output).
-   *
-   * @option types A comma-delimited list of entity type ids. Default is all
-   *    entity types that need reverting.
-   *
-   * @command scheduler:entity-revert
-   * @aliases sch-ent-rev, sch-rev, scheduler-entity-revert
-   */
-  public function entityRevert(array $options = ['types' => '']) {
-    $result = $this->schedulerManager->entityRevert(StringUtils::csvToArray($options['types']));
-    $reverted = $result ? implode(', ', $result) : dt('nothing to do');
-    $this->messenger->addMessage(dt('Scheduler entity revert - @reverted', ['@reverted' => $reverted]));
-  }
-
 }
diff --git a/web/modules/scheduler/src/Event/SchedulerCommerceProductEvents.php b/web/modules/scheduler/src/Event/SchedulerCommerceProductEvents.php
deleted file mode 100644
index 28e53f8b61db8f5e60c2ba2951edcbb0127f5e59..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Event/SchedulerCommerceProductEvents.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Event;
-
-/**
- * Lists the six events dispatched by Scheduler for Commerce Product entities.
- */
-final class SchedulerCommerceProductEvents {
-
-  /**
-   * The event triggered after a commerce product is published immediately.
-   *
-   * This event allows modules to react after an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH_IMMEDIATELY = 'scheduler.commerce_product_publish_immediately';
-
-  /**
-   * The event triggered after a commerce product is published by cron.
-   *
-   * This event allows modules to react after an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH = 'scheduler.commerce_product_publish';
-
-  /**
-   * The event triggered before a commerce product is published immediately.
-   *
-   * This event allows modules to react before an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH_IMMEDIATELY = 'scheduler.commerce_product_pre_publish_immediately';
-
-  /**
-   * The event triggered before a commerce product is published by cron.
-   *
-   * This event allows modules to react before an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH = 'scheduler.commerce_product_pre_publish';
-
-  /**
-   * The event triggered before a commerce product is unpublished by cron.
-   *
-   * This event allows modules to react before an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_UNPUBLISH = 'scheduler.commerce_product_pre_unpublish';
-
-  /**
-   * The event triggered after a commerce product is unpublished by cron.
-   *
-   * This event allows modules to react after an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const UNPUBLISH = 'scheduler.commerce_product_unpublish';
-
-}
diff --git a/web/modules/scheduler/src/Event/SchedulerEvent.php b/web/modules/scheduler/src/Event/SchedulerEvent.php
deleted file mode 100644
index e9e7fa2c6ac19436d4cd741015ea1f9b942de4f5..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Event/SchedulerEvent.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Event;
-
-use Drupal\Core\Entity\EntityInterface;
-
-/**
- * Wraps a scheduler event for event listeners.
- */
-class SchedulerEvent extends EventBase {
-
-  /**
-   * Gets the entity object.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface
-   *   The entity object that caused the event to fire.
-   */
-  public function getEntity() {
-    return $this->entity;
-  }
-
-  /**
-   * Sets the entity object.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity object that caused the event to fire.
-   */
-  public function setEntity(EntityInterface $entity) {
-    $this->entity = $entity;
-  }
-
-  /**
-   * Gets the node object (same as the entity object).
-   *
-   * This method is retained for backwards compatibility because implementations
-   * of the event subscriber functions may be using $event->getNode().
-   *
-   * @return \Drupal\Core\Entity\EntityInterface
-   *   The entity object that caused the event to fire.
-   */
-  public function getNode() {
-    return $this->entity;
-  }
-
-  /**
-   * Sets the node object (same as the entity object).
-   *
-   * This method is retained for backwards compatibility because implementations
-   * of the event subscriber functions may be using $event->setNode().
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity object that caused the event to fire.
-   */
-  public function setNode(EntityInterface $entity) {
-    $this->entity = $entity;
-  }
-
-}
diff --git a/web/modules/scheduler/src/Event/SchedulerMediaEvents.php b/web/modules/scheduler/src/Event/SchedulerMediaEvents.php
deleted file mode 100644
index 397291120f47978e0668e856aa7c73cb227b941d..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Event/SchedulerMediaEvents.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Event;
-
-/**
- * Lists the six events dispatched by Scheduler relating to Media entities.
- */
-final class SchedulerMediaEvents {
-
-  /**
-   * The event triggered after a media item is published immediately.
-   *
-   * This event allows modules to react after an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH_IMMEDIATELY = 'scheduler.media_publish_immediately';
-
-  /**
-   * The event triggered after a media item is published by cron.
-   *
-   * This event allows modules to react after an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH = 'scheduler.media_publish';
-
-  /**
-   * The event triggered before a media item is published immediately.
-   *
-   * This event allows modules to react before an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH_IMMEDIATELY = 'scheduler.media_pre_publish_immediately';
-
-  /**
-   * The event triggered before a media item is published by cron.
-   *
-   * This event allows modules to react before an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH = 'scheduler.media_pre_publish';
-
-  /**
-   * The event triggered before a media item is unpublished by cron.
-   *
-   * This event allows modules to react before an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_UNPUBLISH = 'scheduler.media_pre_unpublish';
-
-  /**
-   * The event triggered after a media item is unpublished by cron.
-   *
-   * This event allows modules to react after an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const UNPUBLISH = 'scheduler.media_unpublish';
-
-}
diff --git a/web/modules/scheduler/src/Event/SchedulerNodeEvents.php b/web/modules/scheduler/src/Event/SchedulerNodeEvents.php
deleted file mode 100644
index 1480f26452523d55c0470054c746c2accc7fc263..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Event/SchedulerNodeEvents.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Event;
-
-/**
- * Lists the six events dispatched by Scheduler relating to Node entities.
- *
- * The event names here are the original six, when only nodes were supported.
- * See SchedulerTaxonomyTermEvents for the generic naming convention to follow
- * for any new entity plugin implementations.
- */
-final class SchedulerNodeEvents {
-
-  /**
-   * The event triggered after a node is published immediately.
-   *
-   * This event allows modules to react after an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH_IMMEDIATELY = 'scheduler.publish_immediately';
-
-  /**
-   * The event triggered after a node is published by cron.
-   *
-   * This event allows modules to react after an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH = 'scheduler.publish';
-
-  /**
-   * The event triggered before a node is published immediately.
-   *
-   * This event allows modules to react before an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH_IMMEDIATELY = 'scheduler.pre_publish_immediately';
-
-  /**
-   * The event triggered before a node is published by cron.
-   *
-   * This event allows modules to react before an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH = 'scheduler.pre_publish';
-
-  /**
-   * The event triggered before a node is unpublished by cron.
-   *
-   * This event allows modules to react before an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_UNPUBLISH = 'scheduler.pre_unpublish';
-
-  /**
-   * The event triggered after a node is unpublished by cron.
-   *
-   * This event allows modules to react after an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const UNPUBLISH = 'scheduler.unpublish';
-
-}
diff --git a/web/modules/scheduler/src/Event/SchedulerTaxonomyTermEvents.php b/web/modules/scheduler/src/Event/SchedulerTaxonomyTermEvents.php
deleted file mode 100644
index 962121b1ea26f966d400adecd1deff0e1cea1d8e..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Event/SchedulerTaxonomyTermEvents.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Event;
-
-/**
- * Lists the six events dispatched by Scheduler for Taxonomy Term entities.
- */
-final class SchedulerTaxonomyTermEvents {
-
-  /**
-   * The event triggered after a taxonomy term is published immediately.
-   *
-   * This event allows modules to react after an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH_IMMEDIATELY = 'scheduler.taxonomy_term_publish_immediately';
-
-  /**
-   * The event triggered after a taxonomy term is published by cron.
-   *
-   * This event allows modules to react after an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PUBLISH = 'scheduler.taxonomy_term_publish';
-
-  /**
-   * The event triggered before a taxonomy term is published immediately.
-   *
-   * This event allows modules to react before an entity is published
-   * immediately when being saved after editing. The event listener method
-   * receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH_IMMEDIATELY = 'scheduler.taxonomy_term_pre_publish_immediately';
-
-  /**
-   * The event triggered before a taxonomy term is published by cron.
-   *
-   * This event allows modules to react before an entity is published by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_PUBLISH = 'scheduler.taxonomy_term_pre_publish';
-
-  /**
-   * The event triggered before a taxonomy term is unpublished by cron.
-   *
-   * This event allows modules to react before an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const PRE_UNPUBLISH = 'scheduler.taxonomy_term_pre_unpublish';
-
-  /**
-   * The event triggered after a taxonomy term is unpublished by cron.
-   *
-   * This event allows modules to react after an entity is unpublished by Cron.
-   * The event listener receives a \Drupal\Core\Entity\EntityInterface instance.
-   *
-   * @Event
-   *
-   * @see \Drupal\scheduler\Event\SchedulerEvent
-   *
-   * @var string
-   */
-  const UNPUBLISH = 'scheduler.taxonomy_term_unpublish';
-
-}
diff --git a/web/modules/scheduler/src/Event/EventBase.php b/web/modules/scheduler/src/EventBase.php
similarity index 64%
rename from web/modules/scheduler/src/Event/EventBase.php
rename to web/modules/scheduler/src/EventBase.php
index ac4dd766b2accbd3fcaf1cd32bf0592ee17bf2cd..f5193edebeab66ed8abb55d2a5bcf15cadfcd4a8 100644
--- a/web/modules/scheduler/src/Event/EventBase.php
+++ b/web/modules/scheduler/src/EventBase.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\scheduler\Event;
+namespace Drupal\scheduler;
 
 // Drupal\Component\EventDispatcher\Event was introduced in Drupal core 9.1 to
 // assist with deprecations and the transition to Symfony 5.
@@ -11,7 +11,7 @@ class_alias('Symfony\Component\EventDispatcher\Event', 'Drupal\Component\EventDi
 }
 
 use Drupal\Component\EventDispatcher\Event;
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\node\NodeInterface;
 
 /**
  * Base class on which all Scheduler events are extended.
@@ -19,20 +19,20 @@ class_alias('Symfony\Component\EventDispatcher\Event', 'Drupal\Component\EventDi
 class EventBase extends Event {
 
   /**
-   * The entity which is being processed.
+   * The node which is being processed.
    *
-   * @var \Drupal\Core\Entity\EntityInterface
+   * @var \Drupal\node\NodeInterface
    */
-  public $entity;
+  public $node;
 
   /**
    * Constructs the object.
    *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity which is being processed.
+   * @param \Drupal\node\NodeInterface $node
+   *   The node which is being processed.
    */
-  public function __construct(EntityInterface $entity) {
-    $this->entity = $entity;
+  public function __construct(NodeInterface $node) {
+    $this->node = $node;
   }
 
 }
diff --git a/web/modules/scheduler/src/Exception/SchedulerEntityTypeNotEnabledException.php b/web/modules/scheduler/src/Exception/SchedulerEntityTypeNotEnabledException.php
deleted file mode 100644
index 7f7931409ece4dedc0e4ae2d077e2f76be416f09..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Exception/SchedulerEntityTypeNotEnabledException.php
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Exception;
-
-/**
- * Defines an exception when the entity type is not enabled for Scheduler.
- *
- * This exception is thrown when Scheduler attempts to publish or unpublish an
- * entity during cron but the entity type/bundle is not enabled for Scheduler.
- *
- * @see \Drupal\scheduler\SchedulerManager::publish()
- * @see \Drupal\scheduler\SchedulerManager::unpublish()
- */
-class SchedulerEntityTypeNotEnabledException extends \Exception {}
diff --git a/web/modules/scheduler/src/Exception/SchedulerMissingDateException.php b/web/modules/scheduler/src/Exception/SchedulerMissingDateException.php
new file mode 100644
index 0000000000000000000000000000000000000000..eda12bc3a4e034cd9e3a7075f62763164992fec7
--- /dev/null
+++ b/web/modules/scheduler/src/Exception/SchedulerMissingDateException.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\scheduler\Exception;
+
+/**
+ * Defines an exception when the scheduled date is missing.
+ *
+ * This exception is thrown when Scheduler attempts to publish or unpublish a
+ * node during cron but the date is missing.
+ *
+ * @see \Drupal\scheduler\SchedulerManager::publish()
+ * @see \Drupal\scheduler\SchedulerManager::unpublish()
+ */
+class SchedulerMissingDateException extends \Exception {}
diff --git a/web/modules/scheduler/src/Exception/SchedulerNodeTypeNotEnabledException.php b/web/modules/scheduler/src/Exception/SchedulerNodeTypeNotEnabledException.php
new file mode 100644
index 0000000000000000000000000000000000000000..f26b4a2809126caf2dfcbb3753afd676d6c70953
--- /dev/null
+++ b/web/modules/scheduler/src/Exception/SchedulerNodeTypeNotEnabledException.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\scheduler\Exception;
+
+/**
+ * Defines an exception when the node type is not enabled for Scheduler.
+ *
+ * This exception is thrown when Scheduler attempts to publish or unpublish a
+ * node during cron but the node type is not enabled for Scheduler.
+ *
+ * @see \Drupal\scheduler\SchedulerManager::publish()
+ * @see \Drupal\scheduler\SchedulerManager::unpublish()
+ */
+class SchedulerNodeTypeNotEnabledException extends \Exception {}
diff --git a/web/modules/scheduler/src/Form/SchedulerAdminForm.php b/web/modules/scheduler/src/Form/SchedulerAdminForm.php
index ad195646fe3175f34d5211b0d32b80d68867826e..1aacd572686381a96c8d7d93885cb76a9a0ab476 100644
--- a/web/modules/scheduler/src/Form/SchedulerAdminForm.php
+++ b/web/modules/scheduler/src/Form/SchedulerAdminForm.php
@@ -5,7 +5,6 @@
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -20,28 +19,12 @@ class SchedulerAdminForm extends ConfigFormBase {
    */
   protected $dateFormatter;
 
-  /**
-   * The scheduler manager service.
-   *
-   * @var \Drupal\scheduler\SchedulerManager
-   */
-  protected $schedulerManager;
-
-  /**
-   * Entity Type Manager service object.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
   /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
     $instance = parent::create($container);
     $instance->setDateFormatter($container->get('date.formatter'));
-    $instance->schedulerManager = $container->get('scheduler.manager');
-    $instance->entityTypeManager = $container->get('entity_type.manager');
     return $instance;
   }
 
@@ -73,72 +56,6 @@ protected function getEditableConfigNames() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $form['description'] = [
-      '#markup' => '<p>' . $this->t('Most of the Scheduler options are set independently for each entity type and bundle. These can be accessed from the <a href="@link">admin structure</a> page or directly by using the drop-button', ['@link' => Url::fromRoute('system.admin_structure')->toString()]) . '</p>',
-    ];
-
-    // Build a drop-button with links to configure all supported entity types.
-    $plugins = $this->schedulerManager->getPlugins();
-    $links = [];
-    $links[] = [
-      'title' => $this->t('Entity Types'),
-      'url' => Url::fromRoute('system.admin_structure'),
-    ];
-    foreach ($plugins as $entityTypeId => $plugin) {
-      $publishing_enabled_types = $this->schedulerManager->getEnabledTypes($entityTypeId, 'publish');
-      $unpublishing_enabled_types = $this->schedulerManager->getEnabledTypes($entityTypeId, 'unpublish');
-
-      // When a module is enabled via drush there is no automatic clear cache.
-      // Thus moduleHandler()->moduleExists({module}) can return false when
-      // the module is actually enabled. This means we get nothing for
-      // plugin->getTypes() and processing should stop with a useful exception
-      // message, instead of letting Core give a confusing exception later.
-      $bundle_id = $this->entityTypeManager->getDefinition($entityTypeId)->getBundleEntityType();
-      $entity_type_definition = $this->entityTypeManager->getDefinition($bundle_id, FALSE);
-      if (!$entity_type_definition) {
-        throw new \Exception(sprintf('Invalid or empty %s entity type definition for %s module. Do a full cache clear via admin/config/development/performance or drush cr.', $bundle_id, $plugin->dependency()));
-      }
-      $collection_label = (string) ($entity_type_definition->get('label_collection') ?: $entity_type_definition->get('label'));
-
-      // $plugin->getTypes() will usually give a non-empty array of values, but
-      // it can be empty if no default bundle type is defined, or all types have
-      // been deleted.
-      if (!$types = $plugin->getTypes()) {
-        // Some modules may not create a default entity type during installation
-        // or the entity type definitions may have been deleted. This is not an
-        // exception, but will cause an error if we do not stop this loop.
-        $message_parms = [
-          '%module' => $plugin->dependency(),
-          '%plugin_label' => $plugin->label(),
-          '%bundle_id' => $bundle_id,
-        ];
-        $this->logger('scheduler')->notice('No %bundle_id entity types returned by %module module for use in %plugin_label', $message_parms);
-        $links[] = ['title' => "-- $collection_label --  (" . $this->t('no entity types defined') . ')'];
-        continue;
-      }
-
-      $links[] = ['title' => "-- $collection_label --"];
-      foreach ($types as $id => $type) {
-        $text = [];
-        in_array($id, $publishing_enabled_types) ? $text[] = $this->t('publishing') : NULL;
-        in_array($id, $unpublishing_enabled_types) ? $text[] = $this->t('unpublishing') : NULL;
-        $links[] = [
-          'title' => $type->label() . (!empty($text) ? ' (' . implode(', ', $text) . ')' : ''),
-          // Example: the route 'entity.media_type.edit_form' with parameter
-          // media_type={typeid} has url /admin/structure/media/manage/{typeid}.
-          'url' => Url::fromRoute("entity.$bundle_id.edit_form", [$bundle_id => $type->id()]),
-        ];
-      }
-    }
-    $form['entity_type_links'] = [
-      '#type' => 'dropbutton',
-      '#links' => $links,
-    ];
-
-    $form['description2'] = [
-      '#markup' => '<p>' . $this->t('The settings below are common to all entity types.') . '</p>',
-    ];
-
     // Options for setting date-only with default time.
     $form['date_only_fieldset'] = [
       '#type' => 'fieldset',
@@ -178,9 +95,6 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#description' => $this->t('When entering a time, only show hours and minutes in the input field.'),
     ];
 
-    // Attach library for admin css file.
-    $form['#attached']['library'][] = 'scheduler/admin-css';
-
     return parent::buildForm($form, $form_state);
   }
 
diff --git a/web/modules/scheduler/src/Plugin/Derivative/DynamicLocalTasks.php b/web/modules/scheduler/src/Plugin/Derivative/DynamicLocalTasks.php
deleted file mode 100644
index 0b58e377d91bdf75b6019874fb536df803f5456c..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Plugin/Derivative/DynamicLocalTasks.php
+++ /dev/null
@@ -1,161 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Plugin\Derivative;
-
-use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Defines dynamic local tasks.
- *
- * The local tasks that define tabs for the 'Scheduled' entity views cannot be
- * hard-coded in the links.task.yml file because if a view is disabled its route
- * will not exist and this produces an exception "Route X does not exist." The
- * routes are defined here instead to enable checking that the views are loaded.
- */
-class DynamicLocalTasks extends DeriverBase implements ContainerDeriverInterface {
-
-  use StringTranslationTrait;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * Creates a DynamicLocalTasks object.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
-    $this->entityTypeManager = $entity_type_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, $base_plugin_id) {
-    return new static(
-      $container->get('entity_type.manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-
-    $view_storage = $this->entityTypeManager->getStorage('view');
-
-    // Define a local task for scheduled content (nodes) view, only when the
-    // view can be loaded, is enabled and that the overview display exists.
-    /** @var \Drupal\views\ViewEntityInterface $view */
-    $view = $view_storage->load('scheduler_scheduled_content');
-    if ($view && $view->status() && $view->getDisplay('overview')) {
-      // The content overview has weight 0 and moderated content has weight 1
-      // so use weight 5 for the scheduled content tab.
-      $this->derivatives['scheduler.scheduled_content'] = [
-        'title' => $this->t('Scheduled content'),
-        'route_name' => 'view.scheduler_scheduled_content.overview',
-        'parent_id' => 'system.admin_content',
-        'weight' => 5,
-      ] + $base_plugin_definition;
-
-      // Core content_moderation module defines an 'overview' local task which
-      // is required when adding additional local tasks. If that module is not
-      // installed then define the tab here. This can be removed if
-      // https://www.drupal.org/project/drupal/issues/3199682 gets committed.
-      // See also scheduler_local_tasks_alter().
-      $this->derivatives['scheduler.content_overview'] = [
-        'title' => $this->t('Overview'),
-        'route_name' => 'system.admin_content',
-        'parent_id' => 'system.admin_content',
-      ] + $base_plugin_definition;
-    }
-
-    $view = $view_storage->load('scheduler_scheduled_media');
-    if ($view && $view->status() && $view->getDisplay('overview')) {
-      // Define local task for scheduled media view.
-      $this->derivatives['scheduler.scheduled_media'] = [
-        'title' => $this->t('Scheduled media'),
-        'route_name' => 'view.scheduler_scheduled_media.overview',
-        'parent_id' => 'entity.media.collection',
-        'weight' => 5,
-      ] + $base_plugin_definition;
-
-      // This task is added so that we get an 'overview' sub-task link alongside
-      // the 'scheduled media' sub-task link.
-      $this->derivatives['scheduler.media_overview'] = [
-        'title' => $this->t('Overview'),
-        'route_name' => 'entity.media.collection',
-        'parent_id' => 'entity.media.collection',
-      ] + $base_plugin_definition;
-    }
-
-    $view = $view_storage->load('scheduler_scheduled_commerce_product');
-    if ($view && $view->status() && $view->getDisplay('overview')) {
-      // The page created by route entity.commerce_product.collection does not
-      // have any tabs or sub-links, because the Commerce Product module does
-      // not specify any local tasks for this route. Therefore we need a
-      // top-level task which just defines the route name as a base route. This
-      // will be used as the parent for the two tabs defined below.
-      $this->derivatives['scheduler.commerce_products'] = [
-        'route_name' => 'entity.commerce_product.collection',
-        'base_route' => 'entity.commerce_product.collection',
-      ] + $base_plugin_definition;
-
-      // Define local task for the scheduled products view.
-      $this->derivatives['scheduler.scheduled_products'] = [
-        'title' => $this->t('Scheduled products'),
-        'route_name' => 'view.scheduler_scheduled_commerce_product.overview',
-        'parent_id' => 'scheduler.local_tasks:scheduler.commerce_products',
-        'weight' => 5,
-      ] + $base_plugin_definition;
-
-      // This task is added so that we get an 'overview' sub-task link alongside
-      // the 'scheduled products' sub-task link.
-      $this->derivatives['scheduler.commerce_product.collection'] = [
-        'title' => $this->t('Overview'),
-        'route_name' => 'entity.commerce_product.collection',
-        'parent_id' => 'scheduler.local_tasks:scheduler.commerce_products',
-      ] + $base_plugin_definition;
-    }
-
-    $view = $view_storage->load('scheduler_scheduled_taxonomy_term');
-    if ($view && $view->status() && $view->getDisplay('overview')) {
-      // In the same manner as for Commerce Products the page created by route
-      // entity.taxonomy_vocabulary.collection does not have tabs or sub-links,
-      // so we need to definine one with a route name and base route here, to be
-      // used as the parent for the two tabs defined below.
-      $this->derivatives['scheduler.taxonomy_collection'] = [
-        'route_name' => 'entity.taxonomy_vocabulary.collection',
-        'base_route' => 'entity.taxonomy_vocabulary.collection',
-      ] + $base_plugin_definition;
-
-      // Define local task for the scheduled taxonomy terms view.
-      $this->derivatives['scheduler.scheduled_taxonomy_terms'] = [
-        'title' => $this->t('Scheduled terms'),
-        'route_name' => 'view.scheduler_scheduled_taxonomy_term.overview',
-        'parent_id' => 'scheduler.local_tasks:scheduler.taxonomy_collection',
-        'weight' => 5,
-      ] + $base_plugin_definition;
-
-      // This task is added so that we get an 'overview' sub-task link alongside
-      // the 'scheduled taxonomy terms' sub-task link.
-      $this->derivatives['scheduler.taxonomy_vocabulary.collection'] = [
-        'title' => $this->t('Overview'),
-        'route_name' => 'entity.taxonomy_vocabulary.collection',
-        'parent_id' => 'scheduler.local_tasks:scheduler.taxonomy_collection',
-      ] + $base_plugin_definition;
-    }
-
-    return parent::getDerivativeDefinitions($base_plugin_definition);
-  }
-
-}
diff --git a/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php b/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php
index 818f096a4079472ca5c585b373f59415bf076f58..2bf4e73d8d77e7dadbd7896ab53b47ee689e1416 100644
--- a/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php
+++ b/web/modules/scheduler/src/Plugin/Field/FieldWidget/TimestampDatetimeNoDefaultWidget.php
@@ -29,7 +29,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     $element = parent::formElement($items, $delta, $element, $form, $form_state);
     // The default description "Format: html-date html-time. Leave blank to use
     // the time of form submission" is inherited from TimestampDatetimeWidget,
-    // but this is entirely replaced in _scheduler_entity_form_alter().
+    // but this is entirely replaced in scheduler_form_node_form_alter().
     // However this widget is generic and may be used elsewhere, so provide
     // an accurate #description here.
     $element['value']['#description'] = $this->t('Leave blank for no date.');
diff --git a/web/modules/scheduler/src/Plugin/Scheduler/CommerceProductScheduler.php b/web/modules/scheduler/src/Plugin/Scheduler/CommerceProductScheduler.php
deleted file mode 100644
index 9b313b26a9116130b33af6f87ba1a8ae14e5d8a2..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Plugin/Scheduler/CommerceProductScheduler.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Plugin\Scheduler;
-
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\scheduler\SchedulerPluginBase;
-
-/**
- * Plugin for Commerce Product entity type.
- *
- * @package Drupal\Scheduler\Plugin\Scheduler
- *
- * @SchedulerPlugin(
- *  id = "commerce_product_scheduler",
- *  label = @Translation("Commerce Product Scheduler Plugin"),
- *  description = @Translation("Provides support for scheduling Commerce Product entities"),
- *  entityType = "commerce_product",
- *  dependency = "commerce_product",
- *  schedulerEventClass = "\Drupal\scheduler\Event\SchedulerCommerceProductEvents",
- *  publishAction = "commerce_publish_product",
- *  unpublishAction = "commerce_unpublish_product"
- * )
- */
-class CommerceProductScheduler extends SchedulerPluginBase implements ContainerFactoryPluginInterface {}
diff --git a/web/modules/scheduler/src/Plugin/Scheduler/MediaScheduler.php b/web/modules/scheduler/src/Plugin/Scheduler/MediaScheduler.php
deleted file mode 100644
index 0a8eca03eb44b7c864f548889ccd4e3628d99bf5..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Plugin/Scheduler/MediaScheduler.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Plugin\Scheduler;
-
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\scheduler\SchedulerPluginBase;
-
-/**
- * Plugin for Media entity type.
- *
- * @package Drupal\Scheduler\Plugin\Scheduler
- *
- * @SchedulerPlugin(
- *  id = "media_scheduler",
- *  label = @Translation("Media Scheduler Plugin"),
- *  description = @Translation("Provides support for scheduling media entities"),
- *  entityType = "media",
- *  dependency = "media",
- *  develGenerateForm = "devel_generate_form_media",
- *  userViewRoute = "view.scheduler_scheduled_media.user_page",
- * )
- */
-class MediaScheduler extends SchedulerPluginBase implements ContainerFactoryPluginInterface {}
diff --git a/web/modules/scheduler/src/Plugin/Scheduler/NodeScheduler.php b/web/modules/scheduler/src/Plugin/Scheduler/NodeScheduler.php
deleted file mode 100644
index 03db5dd7b660445851d66cc99dafd42b181aab47..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Plugin/Scheduler/NodeScheduler.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Plugin\Scheduler;
-
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\scheduler\SchedulerPluginBase;
-
-/**
- * Plugin for Node entity type.
- *
- * @package Drupal\Scheduler\Plugin\Scheduler
- *
- * @SchedulerPlugin(
- *  id = "node_scheduler",
- *  label = @Translation("Node Scheduler Plugin"),
- *  description = @Translation("Provides support for scheduling node entities"),
- *  entityType = "node",
- *  dependency = "node",
- *  develGenerateForm = "devel_generate_form_content",
- *  collectionRoute = "system.admin_content",
- *  userViewRoute = "view.scheduler_scheduled_content.user_page",
- * )
- */
-class NodeScheduler extends SchedulerPluginBase implements ContainerFactoryPluginInterface {}
diff --git a/web/modules/scheduler/src/Plugin/Scheduler/TaxonomyTermScheduler.php b/web/modules/scheduler/src/Plugin/Scheduler/TaxonomyTermScheduler.php
deleted file mode 100644
index 6b8ad51f8c8e2452ed4d741cdab21bd80b203454..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Plugin/Scheduler/TaxonomyTermScheduler.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Plugin\Scheduler;
-
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\scheduler\SchedulerPluginBase;
-
-/**
- * Plugin for Taxonomy Term entity type.
- *
- * @package Drupal\Scheduler\Plugin\Scheduler
- *
- * @SchedulerPlugin(
- *  id = "taxonomy_term_scheduler",
- *  label = @Translation("Taxonomy Term Scheduler Plugin"),
- *  description = @Translation("Provides support for scheduling Taxonomy Term entities"),
- *  entityType = "taxonomy_term",
- *  dependency = "taxonomy",
- *  develGenerateForm = "devel_generate_form_term",
- *  collectionRoute = "entity.taxonomy_vocabulary.collection",
- *  schedulerEventClass = "\Drupal\scheduler\Event\SchedulerTaxonomyTermEvents",
- * )
- */
-class TaxonomyTermScheduler extends SchedulerPluginBase implements ContainerFactoryPluginInterface {}
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraint.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraint.php
index 36aaab8c22ec0ab4c2c14b18dd58da0c5e0df3ff..a7eafe792de34d13f2522cf8dd928c2e21af686d 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraint.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraint.php
@@ -10,13 +10,13 @@
  * @Constraint(
  *   id = "SchedulerPublishOn",
  *   label = @Translation("Scheduler publish on", context = "Validation"),
- *   type = "entity"
+ *   type = "entity:node"
  * )
  */
 class SchedulerPublishOnConstraint extends CompositeConstraintBase {
 
   /**
-   * Message shown when publish_on is not in the future.
+   * Message shown when publish_on is not the future.
    *
    * @var string
    */
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
index 9df8df940cbd82f60131a29390dcf5d84735929e..b4963e1dd940ca127fa873c059e842633ebbd078 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
@@ -16,7 +16,7 @@ class SchedulerPublishOnConstraintValidator extends ConstraintValidator {
   public function validate($entity, Constraint $constraint) {
     $publish_on = $entity->value;
     $default_publish_past_date = \Drupal::config('scheduler.settings')->get('default_publish_past_date');
-    $scheduler_publish_past_date = \Drupal::service('scheduler.manager')->getThirdPartySetting($entity->getEntity(), 'publish_past_date', $default_publish_past_date);
+    $scheduler_publish_past_date = $entity->getEntity()->type->entity->getThirdPartySetting('scheduler', 'publish_past_date', $default_publish_past_date);
 
     if ($publish_on && $scheduler_publish_past_date == 'error' && $publish_on < \Drupal::time()->getRequestTime()) {
       $this->context->buildViolation($constraint->messagePublishOnDateNotInFuture)
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraint.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraint.php
index 2f577c7acd3bd2965c1569c856dc55cd57eb8ccc..85230349b54d1fd275b718c3b7d917754b401811 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraint.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraint.php
@@ -10,7 +10,7 @@
  * @Constraint(
  *   id = "SchedulerUnpublishOn",
  *   label = @Translation("Scheduler unpublish on", context = "Validation"),
- *   type = "entity"
+ *   type = "entity:node"
  * )
  */
 class SchedulerUnpublishOnConstraint extends CompositeConstraintBase {
@@ -23,11 +23,11 @@ class SchedulerUnpublishOnConstraint extends CompositeConstraintBase {
   public $messageUnpublishOnRequiredIfPublishOnEntered = "If you set a 'publish on' date then you must also set an 'unpublish on' date.";
 
   /**
-   * Message shown when unpublish_on is missing but trying to save as published.
+   * Message shown when unpublish_on is missing but node is published directly.
    *
    * @var string
    */
-  public $messageUnpublishOnRequiredIfPublishing = "Either you must set an 'unpublish on' date or save as unpublished.";
+  public $messageUnpublishOnRequiredIfPublishing = "Either you must set an 'unpublish on' date or save this node as unpublished.";
 
   /**
    * Message shown when unpublish_on is not in the future.
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
index c53545fb4be95ffd6372845d4975047bc5e48707..e20784807c3f51c681002b3bf31a41c2f4684471 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
@@ -16,12 +16,12 @@ class SchedulerUnpublishOnConstraintValidator extends ConstraintValidator {
   public function validate($entity, Constraint $constraint) {
 
     // If the content type is not enabled for unpublishing then exit early.
-    if (!\Drupal::service('scheduler.manager')->getThirdPartySetting($entity->getEntity(), 'unpublish_enable', FALSE)) {
+    if (!$entity->getEntity()->type->entity->getThirdPartySetting('scheduler', 'unpublish_enable', FALSE)) {
       return;
     }
 
     $default_unpublish_required = \Drupal::config('scheduler.settings')->get('default_unpublish_required');
-    $scheduler_unpublish_required = \Drupal::service('scheduler.manager')->getThirdPartySetting($entity->getEntity(), 'unpublish_required', $default_unpublish_required);
+    $scheduler_unpublish_required = $entity->getEntity()->type->entity->getThirdPartySetting('scheduler', 'unpublish_required', $default_unpublish_required);
     $publish_on = $entity->getEntity()->publish_on->value;
     $unpublish_on = $entity->value;
     $status = $entity->getEntity()->status->value;
diff --git a/web/modules/scheduler/src/Plugin/migrate/process/SchedulerHideSeconds.php b/web/modules/scheduler/src/Plugin/migrate/process/SchedulerHideSeconds.php
deleted file mode 100644
index 5b3bf3d3a7ff91dfb9707fc6acf68538ebbd3550..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Plugin/migrate/process/SchedulerHideSeconds.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Plugin\migrate\process;
-
-use Drupal\migrate\MigrateExecutableInterface;
-use Drupal\migrate\ProcessPluginBase;
-use Drupal\migrate\Row;
-
-/**
- * Provides a process plugin for the hide_seconds global setting.
- *
- * The hide_seconds setting does not exist in Drupal 7 because the entire date
- * and time input format could be specified. However we can use the date format
- * as source input here and set hide_seconds to true if the seconds were not
- * included in the full date format.
- *
- * @MigrateProcessPlugin(
- *   id = "scheduler_hide_seconds"
- * )
- */
-class SchedulerHideSeconds extends ProcessPluginBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
-    // The value of hide_seconds is set to true if the source date format does
-    // not contain seconds (lower case 's').
-    $hide_seconds = !strstr($value, 's');
-    return $hide_seconds;
-  }
-
-}
diff --git a/web/modules/scheduler/src/Plugin/views/access/Scheduler.php b/web/modules/scheduler/src/Plugin/views/access/Scheduler.php
index 9f371c08b86d935fbb989acfc5dcd27ff2f169e3..4255a7fc830a74c5927c73ce2db98ff0ef0e6c1f 100644
--- a/web/modules/scheduler/src/Plugin/views/access/Scheduler.php
+++ b/web/modules/scheduler/src/Plugin/views/access/Scheduler.php
@@ -2,37 +2,58 @@
 
 namespace Drupal\scheduler\Plugin\views\access;
 
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\views\Plugin\views\access\AccessPluginBase;
 use Symfony\Component\Routing\Route;
 
 /**
- * Access plugin that provided access control for Scheduler views.
+ * Access plugin that provides access control for Scheduler.
  *
- * This access plugin has been replaced by SchedulerRouteAccess, and is no
- * longer needed. However it has to remain (temporarily) as it is used in the
- * existing view. Deleting this class causes errors before the view can be
- * updated via update.php. The content below has been reduced to the minimum
- * necessary to avoid errors before update.php is run.
+ * @ingroup views_access_plugins
  *
  * @ViewsAccess(
  *   id = "scheduler",
- *   title = @Translation("Scheduled content access. REDUNDANT, DO NOT USE THIS."),
- *   help = @Translation("NOT USED"),
+ *   title = @Translation("Scheduled content access"),
+ *   help = @Translation("All Scheduler users can see their own scheduled content via their user page. In addition, if they have 'view scheduled content' permission they will be able to see all scheduled content by all authors."),
  * )
  */
-class Scheduler extends AccessPluginBase {
+class Scheduler extends AccessPluginBase implements CacheableDependencyInterface {
 
   /**
    * {@inheritdoc}
    */
   public function access(AccountInterface $account) {
+    return \Drupal::service('access_checker.scheduler_content')->access($account);
   }
 
   /**
    * {@inheritdoc}
    */
   public function alterRouteDefinition(Route $route) {
+    $route->setRequirement('_access_scheduler_content', 'TRUE');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return ['user'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    return Cache::PERMANENT;
   }
 
 }
diff --git a/web/modules/scheduler/src/Routing/SchedulerRouteSubscriber.php b/web/modules/scheduler/src/Routing/SchedulerRouteSubscriber.php
deleted file mode 100644
index 94f084ea730fb0158826d78ad705c34aeda73408..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/Routing/SchedulerRouteSubscriber.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-namespace Drupal\scheduler\Routing;
-
-use Drupal\Core\Routing\RouteSubscriberBase;
-use Symfony\Component\Routing\RouteCollection;
-
-/**
- * Scheduler route subscriber to add custom access for user views.
- */
-class SchedulerRouteSubscriber extends RouteSubscriberBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function alterRoutes(RouteCollection $collection) {
-    $user_page_routes = \Drupal::service('scheduler.manager')->getUserPageViewRoutes();
-    foreach ($user_page_routes as $user_route) {
-      if ($route = $collection->get($user_route)) {
-        $requirements = $route->getRequirements();
-        $requirements['_custom_access'] = '\Drupal\scheduler\Access\SchedulerRouteAccess::access';
-        $route->setRequirements($requirements);
-      }
-    }
-  }
-
-}
diff --git a/web/modules/scheduler/src/SchedulerEvent.php b/web/modules/scheduler/src/SchedulerEvent.php
index 7faa2c4f5be8c385725a12ba81a4f22e57fe2c95..cda8c6f87e8e4f5912f8ade548bf0bb3e4d20eb9 100644
--- a/web/modules/scheduler/src/SchedulerEvent.php
+++ b/web/modules/scheduler/src/SchedulerEvent.php
@@ -1,24 +1,32 @@
 <?php
 
-/**
- * @file
- * Class alias for Drupal\scheduler\SchedulerEvent.
- */
+namespace Drupal\scheduler;
+
+use Drupal\node\NodeInterface;
 
 /**
- * Create event class alias to maintain backwards-compatibility.
- *
- * The original event classes, named Drupal\scheduler\SchedulerEvent and
- * Drupal\scheduler\SchedulerEvents must remain for backwards-compatibility
- * with existing implementations of event subscribers for Node events. The
- * namespace should have been Drupal\scheduler\Event and all the event-related
- * files stored in a src/Event folder, but instead they were just in /src.
- *
- * Now that Scheduler supports non-node entities and each type has to have its
- * own specific event class named 'Scheduler{Type}Events', they can be moved
- * into a Drupal\scheduler\Event namespace, with all event files being stored in
- * a src/Event folder. These two aliases, for the original node events, ensure
- * that any existing event subscribers will continue work unchnaged.
+ * Wraps a scheduler event for event listeners.
  */
+class SchedulerEvent extends EventBase {
+
+  /**
+   * Gets node object.
+   *
+   * @return \Drupal\node\NodeInterface
+   *   The node object that caused the event to fire.
+   */
+  public function getNode() {
+    return $this->node;
+  }
+
+  /**
+   * Sets the node object.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node object that caused the event to fire.
+   */
+  public function setNode(NodeInterface $node) {
+    $this->node = $node;
+  }
 
-class_alias('Drupal\scheduler\Event\SchedulerEvent', 'Drupal\scheduler\SchedulerEvent');
+}
diff --git a/web/modules/scheduler/src/SchedulerEvents.php b/web/modules/scheduler/src/SchedulerEvents.php
index 4a3002aae72e6c598fa375d896c57a7dce7be7f4..dc1d895af5336b4fadb5a07e7a8b4c943a70d0bf 100644
--- a/web/modules/scheduler/src/SchedulerEvents.php
+++ b/web/modules/scheduler/src/SchedulerEvents.php
@@ -1,24 +1,103 @@
 <?php
 
-/**
- * @file
- * Class alias for Drupal\scheduler\SchedulerEvents.
- */
+namespace Drupal\scheduler;
 
 /**
- * Create event class alias to maintain backwards-compatibility.
- *
- * The original event classes, named Drupal\scheduler\SchedulerEvent and
- * Drupal\scheduler\SchedulerEvents must remain for backwards-compatibility
- * with existing implementations of event subscribers for Node events. The
- * namespace should have been Drupal\scheduler\Event and all the event-related
- * files stored in a src/Event folder, but instead they were just in /src.
+ * Contains all events dispatched by Scheduler.
  *
- * Now that Scheduler supports non-node entities and each type has to have its
- * own specific event class named 'Scheduler{Type}Events', they can be moved
- * into a Drupal\scheduler\Event namespace, with all event files being stored in
- * a src/Event folder. These two aliases, for the original node events, ensure
- * that any existing event subscribers will continue work unchnaged.
+ * Ideally the namespace should have been Drupal\scheduler\Event and all the
+ * event-related files stored in a src/Event folder. This cannot be chnaged now
+ * as it would break the API which is being used by 3rd-party modules
+ * subscribing to scheduler's events.
  */
+final class SchedulerEvents {
+
+  /**
+   * The event triggered after a node is published immediately.
+   *
+   * This event allows modules to react after a node is published immediately.
+   * The event listener method receives a \Drupal\Core\Entity\EntityInterface
+   * instance.
+   *
+   * @Event
+   *
+   * @see \Drupal\scheduler\SchedulerEvent
+   *
+   * @var string
+   */
+  const PUBLISH_IMMEDIATELY = 'scheduler.publish_immediately';
+
+  /**
+   * The event triggered after a node is published via cron.
+   *
+   * This event allows modules to react after a node is published. The event
+   * listener method receives a \Drupal\Core\Entity\EntityInterface instance.
+   *
+   * @Event
+   *
+   * @see \Drupal\scheduler\SchedulerEvent
+   *
+   * @var string
+   */
+  const PUBLISH = 'scheduler.publish';
+
+  /**
+   * The event triggered before a node is published immediately.
+   *
+   * This event allows modules to react before a node is published immediately.
+   * The event listener method receives a \Drupal\Core\Entity\EntityInterface
+   * instance.
+   *
+   * @Event
+   *
+   * @see \Drupal\scheduler\SchedulerEvent
+   *
+   * @var string
+   */
+  const PRE_PUBLISH_IMMEDIATELY = 'scheduler.pre_publish_immediately';
+
+  /**
+   * The event triggered before a node is published via cron.
+   *
+   * This event allows modules to react before a node is published. The event
+   * listener method receives a \Drupal\Core\Entity\EntityInterface
+   * instance.
+   *
+   * @Event
+   *
+   * @see \Drupal\scheduler\SchedulerEvent
+   *
+   * @var string
+   */
+  const PRE_PUBLISH = 'scheduler.pre_publish';
+
+  /**
+   * The event triggered before a node is unpublished via cron.
+   *
+   * This event allows modules to react before a node is unpublished. The
+   * event listener method receives a \Drupal\Core\Entity\EntityInterface
+   * instance.
+   *
+   * @Event
+   *
+   * @see \Drupal\scheduler\SchedulerEvent
+   *
+   * @var string
+   */
+  const PRE_UNPUBLISH = 'scheduler.pre_unpublish';
+
+  /**
+   * The event triggered after a node is unpublished via cron.
+   *
+   * This event allows modules to react after a node is unpublished. The event
+   * listener method receives a \Drupal\Core\Entity\EntityInterface instance.
+   *
+   * @Event
+   *
+   * @see \Drupal\scheduler\SchedulerEvent
+   *
+   * @var string
+   */
+  const UNPUBLISH = 'scheduler.unpublish';
 
-class_alias('Drupal\scheduler\Event\SchedulerNodeEvents', 'Drupal\scheduler\SchedulerEvents');
+}
diff --git a/web/modules/scheduler/src/SchedulerManager.php b/web/modules/scheduler/src/SchedulerManager.php
index cfda7e9bfea2b490082b75a1f0cf9c2a7fd3e17b..fbb77355aa9317bd11884f708cec5f31ba2af92c 100644
--- a/web/modules/scheduler/src/SchedulerManager.php
+++ b/web/modules/scheduler/src/SchedulerManager.php
@@ -5,19 +5,16 @@
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
 use Drupal\Component\EventDispatcher\Event;
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Config\FileStorage;
 use Drupal\Core\Datetime\DateFormatterInterface;
-use Drupal\Core\Entity\EntityChangedInterface;
-use Drupal\Core\Entity\EntityFieldManagerInterface;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Link;
-use Drupal\Core\Messenger\MessengerInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
+use Drupal\node\NodeInterface;
+use Drupal\scheduler\Exception\SchedulerMissingDateException;
+use Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -76,33 +73,10 @@ class SchedulerManager {
    */
   protected $time;
 
-  /**
-   * Entity Field Manager service object.
-   *
-   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
-   */
-  private $entityFieldManager;
-
-  /**
-   * Scheduler Plugin Manager service object.
-   *
-   * @var SchedulerPluginManager
-   */
-  private $pluginManager;
-
   /**
    * Constructs a SchedulerManager object.
    */
-  public function __construct(DateFormatterInterface $dateFormatter,
-                              LoggerInterface $logger,
-                              ModuleHandlerInterface $moduleHandler,
-                              EntityTypeManagerInterface $entityTypeManager,
-                              ConfigFactoryInterface $configFactory,
-                              ContainerAwareEventDispatcher $eventDispatcher,
-                              TimeInterface $time,
-                              EntityFieldManagerInterface $entityFieldManager,
-                              SchedulerPluginManager $pluginManager
-  ) {
+  public function __construct(DateFormatterInterface $dateFormatter, LoggerInterface $logger, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, ConfigFactoryInterface $configFactory, ContainerAwareEventDispatcher $eventDispatcher, TimeInterface $time) {
     $this->dateFormatter = $dateFormatter;
     $this->logger = $logger;
     $this->moduleHandler = $moduleHandler;
@@ -110,8 +84,6 @@ public function __construct(DateFormatterInterface $dateFormatter,
     $this->configFactory = $configFactory;
     $this->eventDispatcher = $eventDispatcher;
     $this->time = $time;
-    $this->entityFieldManager = $entityFieldManager;
-    $this->pluginManager = $pluginManager;
   }
 
   /**
@@ -148,295 +120,181 @@ public function dispatch(Event $event, string $event_name) {
   }
 
   /**
-   * Dispatches a Scheduler event for an entity.
-   *
-   * This function dispatches a Scheduler event, identified by $event_id, for
-   * the entity type of the provided $entity. Each entity type has its own
-   * events class Scheduler{EntityType}Events, for example SchedulerNodeEvents,
-   * SchedulerMediaEvents, etc. This class contains constants (with names
-   * matching the $event_id parameter) which uniquely define the final event
-   * name string to be dispatched. The actual event object dispatched is always
-   * of class SchedulerEvent.
-   *
-   * The $entity is passed by reference so that any changes made in the event
-   * subscriber implementations are automatically stored and passed forward.
-   *
-   * @param Drupal\Core\Entity\EntityInterface $entity
-   *   The entity object.
-   * @param string $event_id
-   *   The short text id the event, for example 'PUBLISH' or 'PRE_UNPUBLISH'.
-   */
-  public function dispatchSchedulerEvent(EntityInterface &$entity, string $event_id) {
-    // Get the fully named-spaced event class name for the entity type, for use
-    // in the constant() function.
-    $event_class = $this->getPlugin($entity->getEntityTypeId())->schedulerEventClass();
-    $event_name = constant("$event_class::$event_id");
-
-    // Create the event object and dispatch the required event_name.
-    $event = new SchedulerEvent($entity);
-    $this->dispatch($event, $event_name);
-    // Get the entity, as it may have been modified by an event subscriber.
-    $entity = $event->getEntity();
-  }
-
-  /**
-   * Handles throwing exceptions.
-   *
-   * @param Drupal\Core\Entity\EntityInterface $entity
-   *   The entity causing the exepction.
-   * @param string $exception_name
-   *   Which exception to throw.
-   * @param string $process
-   *   The process being performed (publish|unpublish).
-   *
-   * @throws \Drupal\scheduler\Exception\SchedulerEntityTypeNotEnabledException
-   */
-  private function throwSchedulerException(EntityInterface $entity, $exception_name, $process) {
-    $plugin = $this->getPlugin($entity->getEntityTypeId());
-
-    // Exception messages are developer-facing and do not need to be translated
-    // from English. So it is accpetable to create words such as "{$process}ed"
-    // and "{$process}ing".
-    switch ($exception_name) {
-      case 'SchedulerEntityTypeNotEnabledException':
-        $message = "'%s' (id %d) was not %s because %s %s '%s' is not enabled for scheduled %s. One of the following hook functions added the id incorrectly: %s. Processing halted";
-        $p1 = $entity->label();
-        $p2 = $entity->id();
-        $p3 = "{$process}ed";
-        $p4 = $entity->getEntityTypeId();
-        $p5 = $plugin->typeFieldName();
-        $p6 = $entity->{$plugin->typeFieldName()}->entity->label();
-        $p7 = "{$process}ing";
-        // Get a list of the hook function implementations, as one of these will
-        // have caused this exception.
-        $hooks = array_merge(
-          $this->getHookImplementations('list', $entity),
-          $this->getHookImplementations('list_alter', $entity)
-        );
-        asort($hooks);
-        $p8 = implode(', ', $hooks);
-        break;
-    }
-
-    $class = "\\Drupal\\scheduler\\Exception\\$exception_name";
-    throw new $class(sprintf($message, $p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8));
-  }
-
-  /**
-   * Publish scheduled entities.
+   * Publish scheduled nodes.
    *
    * @return bool
-   *   TRUE if any entity has been published, FALSE otherwise.
+   *   TRUE if any node has been published, FALSE otherwise.
    *
-   * @throws \Drupal\scheduler\Exception\SchedulerEntityTypeNotEnabledException
+   * @throws \Drupal\scheduler\Exception\SchedulerMissingDateException
+   * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException
    */
   public function publish() {
     $result = FALSE;
-    $process = 'publish';
-    $plugins = $this->getPlugins();
-
-    foreach ($plugins as $entityTypeId => $plugin) {
-      // Select all entities of the types for this plugin that are enabled for
-      // scheduled publishing and where publish_on is less than or equal to the
-      // current time.
-      $ids = [];
-      $scheduler_enabled_types = $this->getEnabledTypes($entityTypeId, $process);
-
-      if (!empty($scheduler_enabled_types)) {
-        $query = $this->entityTypeManager->getStorage($entityTypeId)->getQuery()
-          ->exists('publish_on')
-          ->condition('publish_on', $this->time->getRequestTime(), '<=')
-          ->condition($plugin->typeFieldName(), $scheduler_enabled_types, 'IN')
-          ->sort('publish_on');
-        // Disable access checks for this query.
-        // @see https://www.drupal.org/node/2700209
-        $query->accessCheck(FALSE);
-        // If the entity type is revisionable then make sure we look for the
-        // latest revision. This is important for moderated entities.
-        if ($plugin->entityTypeObject()->isRevisionable()) {
-          $query->latestRevision();
-        }
-        $ids = $query->execute();
+    $action = 'publish';
+
+    // Select all nodes of the types that are enabled for scheduled publishing
+    // and where publish_on is less than or equal to the current time.
+    $nids = [];
+    $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action));
+    if (!empty($scheduler_enabled_types)) {
+      $query = $this->entityTypeManager->getStorage('node')->getQuery()
+        ->exists('publish_on')
+        ->condition('publish_on', $this->time->getRequestTime(), '<=')
+        ->condition('type', $scheduler_enabled_types, 'IN')
+        ->latestRevision()
+        ->sort('publish_on')
+        ->sort('nid');
+      // Disable access checks for this query.
+      // @see https://www.drupal.org/node/2700209
+      $query->accessCheck(FALSE);
+      $nids = $query->execute();
+    }
+
+    // Allow other modules to add to the list of nodes to be published.
+    $nids = array_unique(array_merge($nids, $this->nidList($action)));
+
+    // Allow other modules to alter the list of nodes to be published.
+    $this->moduleHandler->alter('scheduler_nid_list', $nids, $action);
+
+    // In 8.x the entity translations are all associated with one node id
+    // unlike 7.x where each translation was a separate node. This means that
+    // the list of node ids returned above may have some translations that need
+    // processing now and others that do not.
+    /** @var \Drupal\node\NodeInterface[] $nodes */
+    $nodes = $this->loadNodes($nids);
+    foreach ($nodes as $node_multilingual) {
+
+      // The API calls could return nodes of types which are not enabled for
+      // scheduled publishing, so do not process these. This check can be done
+      // once, here, as the setting will be the same for all translations.
+      if (!$node_multilingual->type->entity->getThirdPartySetting('scheduler', 'publish_enable', $this->setting('default_publish_enable'))) {
+        throw new SchedulerNodeTypeNotEnabledException(sprintf("Node %d '%s' will not be published because node type '%s' is not enabled for scheduled publishing", $node_multilingual->id(), $node_multilingual->getTitle(), node_get_type_label($node_multilingual)));
       }
 
-      // Allow other modules to add to the list of entities to be published.
-      $hook_implementations = $this->getHookImplementations('list', $entityTypeId);
-      foreach ($hook_implementations as $function) {
-        // Cast each hook result as array, to protect from bad implementations.
-        $ids = array_merge($ids, (array) $function($process, $entityTypeId));
-      }
+      $languages = $node_multilingual->getTranslationLanguages();
+      foreach ($languages as $language) {
+        // The object returned by getTranslation() behaves the same as a $node.
+        $node = $node_multilingual->getTranslation($language->getId());
 
-      // Allow other modules to alter the list of entities to be published.
-      $hook_implementations = $this->getHookImplementations('list_alter', $entityTypeId);
-      foreach ($hook_implementations as $function) {
-        $function($ids, $process, $entityTypeId);
-      }
+        // If the current translation does not have a publish on value, or it is
+        // later than the date we are processing then move on to the next.
+        $publish_on = $node->publish_on->value;
+        if (empty($publish_on) || $publish_on > $this->time->getRequestTime()) {
+          continue;
+        }
 
-      // Finally ensure that there are no duplicates in the list of ids.
-      $ids = array_unique($ids);
-
-      // In 8.x the entity translations are all associated with one entity id
-      // unlike 7.x where each translation was a separate id. This means that
-      // the list of ids returned above may have some translations that need
-      // processing now and others that do not.
-      /** @var \Drupal\Core\Entity\EntityInterface[] $entities */
-      $entities = $this->loadEntities($ids, $entityTypeId);
-      foreach ($entities as $entity_multilingual) {
-
-        // The API calls could return entities of types which are not enabled
-        // for scheduled publishing, so do not process these. This check can be
-        // done once as the setting will be the same for all translations.
-        if (!$this->getThirdPartySetting($entity_multilingual, 'publish_enable', $this->setting('default_publish_enable'))) {
-          $this->throwSchedulerException($entity_multilingual, 'SchedulerEntityTypeNotEnabledException', $process);
+        // Check that other modules allow the action on this node.
+        if (!$this->isAllowed($node, $action)) {
+          continue;
+        }
+
+        // $node->setChangedTime($publish_on) will fail badly if an API call has
+        // removed the date. Trap this as an exception here and give a
+        // meaningful message.
+        // @todo This will now never be thrown due to the empty(publish_on)
+        // check above to cater for translations. Remove this exception?
+        if (empty($node->publish_on->value)) {
+          $field_definitions = $this->entityTypeManager->getFieldDefinitions('node', $node->getType());
+          $field = (string) $field_definitions['publish_on']->getLabel();
+          throw new SchedulerMissingDateException(sprintf("Node %d '%s' will not be published because field '%s' has no value", $node->id(), $node->getTitle(), $field));
         }
 
-        $languages = $entity_multilingual->getTranslationLanguages();
-        foreach ($languages as $language) {
-          // The object returned by getTranslation() is a normal $entity.
-          $entity = $entity_multilingual->getTranslation($language->getId());
-
-          // If the current translation does not have a publish on value, or it
-          // is later than the date we are processing then move on to the next.
-          $publish_on = $entity->publish_on->value;
-          if (empty($publish_on) || $publish_on > $this->time->getRequestTime()) {
-            continue;
-          }
-
-          // Check that other modules allow the process on this entity.
-          if (!$this->isAllowed($entity, $process)) {
-            continue;
-          }
-
-          // Trigger the PRE_PUBLISH Scheduler event so that modules can react
-          // before the entity is published.
-          $this->dispatchSchedulerEvent($entity, 'PRE_PUBLISH');
-
-          // Update 'changed' timestamp.
-          if ($entity instanceof EntityChangedInterface) {
-            $entity->setChangedTime($publish_on);
-          }
-
-          $msg_extra = '';
-
-          // If required, set the created date to match published date.
-          if ($this->getThirdPartySetting($entity, 'publish_touch', $this->setting('default_publish_touch')) ||
-            ($this->getThirdPartySetting($entity, 'publish_past_date_created', $this->setting('default_publish_past_date_created')) && $entity->getCreatedTime() > $publish_on)
-          ) {
-            $old_creation_date = $entity->getCreatedTime();
-            $entity->setCreatedTime($publish_on);
-            $msg_extra = $this->t('The previous creation date was @old_creation_date, now updated to match the publishing date.', [
-              '@old_creation_date' => $this->dateFormatter->format($old_creation_date, 'short'),
-            ]);
-          }
-
-          $create_publishing_revision = $this->getThirdPartySetting($entity, 'publish_revision', $this->setting('default_publish_revision'));
-          if ($create_publishing_revision && $entity->getEntityType()->isRevisionable()) {
-            $entity->setNewRevision();
-            // Use a core date format to guarantee a time is included.
-            $revision_log_message = rtrim($this->t('Published by Scheduler. The scheduled publishing date was @publish_on.', [
-              '@publish_on' => $this->dateFormatter->format($publish_on, 'short'),
-            ]) . ' ' . $msg_extra);
-            $entity->setRevisionLogMessage($revision_log_message)
-              ->setRevisionCreationTime($this->time->getRequestTime());
-          }
-          // Unset publish_on so the entity will not get rescheduled by any
-          // interim calls to $entity->save().
-          $entity->publish_on->value = NULL;
-
-          // Invoke all implementations of hook_scheduler_publish_process() and
-          // hook_scheduler_{type}_publish_process() to allow other modules to
-          // do the "publishing" process instead of Scheduler.
-          $hook_implementations = $this->getHookImplementations('publish_process', $entity);
-          $sucessful_hooks = [];
-          $failed_hooks = [];
-          foreach ($hook_implementations as $function) {
-            $return = $function($entity);
-            if ($return === 1) {
-              $sucessful_hooks[] = $function;
-              if (stristr($function, '_action')) {
-                // If this is a legacy action hook, for safety call ->save() as
-                // this used to be done here in Scheduler 8.x-1.x.
-                $entity->save();
-              }
-            }
-            $return === -1 ? $failed_hooks[] = $function : NULL;
-          }
-          $processed = count($sucessful_hooks) > 0;
-          $failed = count($failed_hooks) > 0;
-
-          // Create a set of variables for use in the log message.
-          $bundle_type = $entity->getEntityType()->getBundleEntityType();
-          $entity_type = $this->entityTypeManager->getStorage($bundle_type)->load($entity->bundle());
-          $links = [];
-          if ($entity->hasLinkTemplate('canonical')) {
-            $links[] = $entity->toLink($this->t('View @type', [
-              '@type' => strtolower($entity_type->label()),
-            ]))->toString();
-          }
-          if ($entity_type->hasLinkTemplate('edit-form')) {
-            $links[] = $entity_type->toLink($this->t('@label settings', [
-              '@label' => $entity_type->label(),
-            ]), 'edit-form')->toString();
-          }
-          $logger_variables = [
-            '@type' => $entity_type->label(),
-            '%title' => $entity->label(),
-            '@sucessful_hooks' => implode(', ', $sucessful_hooks),
-            '@failed_hooks' => implode(', ', $failed_hooks),
-            'link' => implode(' ', $links),
-          ];
-
-          if ($failed) {
-            // At least one hook function returned a failure or exception, so
-            // stop processing this entity and move on to the next one.
-            $this->logger->warning('Publishing failed for %title. @failed_hooks returned a failure code.', $logger_variables);
-            // Restore the publish_on date to allow another attempt next time.
-            $entity->publish_on->value = $publish_on;
-            $entity->save();
-            continue;
-          }
-          elseif ($processed) {
-            // The entity was 'published' by a module implementing the hook, so
-            // we only need to log this result.
-            $this->logger->notice('@type: scheduled "publish" processing of %title completed by @sucessful_hooks.', $logger_variables);
-          }
-          else {
-            // None of the above hook calls processed the entity and there were
-            // no errors detected so set the entity to published.
-            $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables);
-
-            // Use the actions system to publish and save the entity.
-            $action_id = $plugin->publishAction();
-            if ($this->moduleHandler->moduleExists('workbench_moderation_actions')) {
-              // workbench_moderation_actions module replaces the standard
-              // action with a custom one which should be used only when the
-              // entity type is part of a moderation workflow.
-              /** @var \Drupal\workbench_moderation\ModerationInformationInterface $moderation_info */
-              $moderation_info = \Drupal::service('workbench_moderation.moderation_information');
-              if ($moderation_info->isModeratableEntity($entity)) {
-                $action_id = 'state_change__' . $entityTypeId . '__published';
-              }
-            }
-            if ($loaded_action = $this->entityTypeManager->getStorage('action')->load($action_id)) {
-              $loaded_action->getPlugin()->execute($entity);
-            }
-            else {
-              // Fallback to the direct method if the action does not exist.
-              $entity->setPublished()->save();
-            }
-          }
-
-          // Invoke event to tell Rules that Scheduler has published the entity.
-          if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) {
-            _scheduler_rules_integration_dispatch_cron_event($entity, $process);
-          }
-
-          // Trigger the PUBLISH Scheduler event so that modules can react after
-          // the entity is published.
-          $this->dispatchSchedulerEvent($entity, 'PUBLISH');
-
-          $result = TRUE;
+        // Trigger the PRE_PUBLISH event so that modules can react before the
+        // node is published.
+        $event = new SchedulerEvent($node);
+        $this->dispatch($event, SchedulerEvents::PRE_PUBLISH);
+        $node = $event->getNode();
+
+        // Update 'changed' timestamp.
+        $node->setChangedTime($publish_on);
+        $old_creation_date = $node->getCreatedTime();
+        $msg_extra = '';
+        // If required, set the created date to match published date.
+        if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_touch', $this->setting('default_publish_touch')) ||
+          ($node->getCreatedTime() > $publish_on && $node->type->entity->getThirdPartySetting('scheduler', 'publish_past_date_created', $this->setting('default_publish_past_date_created')))
+        ) {
+          $node->setCreatedTime($publish_on);
+          $msg_extra = $this->t('The previous creation date was @old_creation_date, now updated to match the publishing date.', [
+            '@old_creation_date' => $this->dateFormatter->format($old_creation_date, 'short'),
+          ]);
         }
+
+        $create_publishing_revision = $node->type->entity->getThirdPartySetting('scheduler', 'publish_revision', $this->setting('default_publish_revision'));
+        if ($create_publishing_revision) {
+          $node->setNewRevision();
+          // Use a core date format to guarantee a time is included.
+          $revision_log_message = rtrim($this->t('Published by Scheduler. The scheduled publishing date was @publish_on.', [
+            '@publish_on' => $this->dateFormatter->format($publish_on, 'short'),
+          ]) . ' ' . $msg_extra);
+          $node->setRevisionLogMessage($revision_log_message)
+            ->setRevisionCreationTime($this->time->getRequestTime());
+        }
+        // Unset publish_on so the node will not get rescheduled by subsequent
+        // calls to $node->save().
+        $node->publish_on->value = NULL;
+
+        // Invoke all implementations of hook_scheduler_publish_action() to
+        // allow other modules to do the "publishing" process instead of
+        // Scheduler.
+        $hook = 'scheduler_publish_action';
+        $processed = FALSE;
+        $failed = FALSE;
+        foreach ($this->moduleHandler->getImplementations($hook) as $module) {
+          $function = $module . '_' . $hook;
+          $return = $function($node);
+          $processed = $processed || ($return === 1);
+          $failed = $failed || ($return === -1);
+        }
+
+        // Log the fact that a scheduled publication is about to take place.
+        $view_link = $node->toLink($this->t('View node'));
+        $node_type = $this->entityTypeManager->getStorage('node_type')->load($node->bundle());
+        $node_type_link = $node_type->toLink($this->t('@label settings', ['@label' => $node_type->label()]), 'edit-form');
+        $logger_variables = [
+          '@type' => $node_type->label(),
+          '%title' => $node->getTitle(),
+          'link' => $node_type_link->toString() . ' ' . $view_link->toString(),
+          '@hook' => 'hook_' . $hook,
+        ];
+
+        if ($failed) {
+          // At least one hook function returned a failure or exception, so stop
+          // processing this node and move on to the next one.
+          $this->logger->warning('Publishing failed for %title. Calls to @hook returned a failure code.', $logger_variables);
+          continue;
+        }
+        elseif ($processed) {
+          // The node had 'publishing' processed by a module implementing the
+          // hook, so no need to do anything more, apart from log this result.
+          $this->logger->notice('@type: scheduled processing of %title completed by calls to @hook.', $logger_variables);
+        }
+        else {
+          // None of the above hook calls processed the node and there were no
+          // errors detected so set the node to published.
+          $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables);
+          $node->setPublished();
+        }
+
+        // Invoke the event to tell Rules that Scheduler has published the node.
+        if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) {
+          _scheduler_rules_integration_dispatch_cron_event($node, 'publish');
+        }
+
+        // Trigger the PUBLISH event so that modules can react after the node is
+        // published.
+        $event = new SchedulerEvent($node);
+        $this->dispatch($event, SchedulerEvents::PUBLISH);
+
+        // Use the standard actions system to publish and save the node.
+        $node = $event->getNode();
+        $action_id = 'node_publish_action';
+        if ($this->moduleHandler->moduleExists('workbench_moderation_actions')) {
+          // workbench_moderation_actions module uses a custom action instead.
+          $action_id = 'state_change__node__published';
+        }
+        $this->entityTypeManager->getStorage('action')->load($action_id)->getPlugin()->execute($node);
+
+        $result = TRUE;
       }
     }
 
@@ -444,214 +302,174 @@ public function publish() {
   }
 
   /**
-   * Unpublish scheduled entities.
+   * Unpublish scheduled nodes.
    *
    * @return bool
-   *   TRUE if any entity has been unpublished, FALSE otherwise.
+   *   TRUE if any node has been unpublished, FALSE otherwise.
    *
-   * @throws \Drupal\scheduler\Exception\SchedulerEntityTypeNotEnabledException
+   * @throws \Drupal\scheduler\Exception\SchedulerMissingDateException
+   * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException
    */
   public function unpublish() {
     $result = FALSE;
-    $process = 'unpublish';
-    $plugins = $this->getPlugins();
-
-    foreach ($plugins as $entityTypeId => $plugin) {
-      // Select all entities of the types for this plugin that are enabled for
-      // scheduled unpublishing and where unpublish_on is less than or equal to
-      // the current time.
-      $ids = [];
-      $scheduler_enabled_types = $this->getEnabledTypes($entityTypeId, $process);
-
-      if (!empty($scheduler_enabled_types)) {
-        $query = $this->entityTypeManager->getStorage($entityTypeId)->getQuery()
-          ->exists('unpublish_on')
-          ->condition('unpublish_on', $this->time->getRequestTime(), '<=')
-          ->condition($plugin->typeFieldName(), $scheduler_enabled_types, 'IN')
-          ->sort('unpublish_on');
-        // Disable access checks for this query.
-        // @see https://www.drupal.org/node/2700209
-        $query->accessCheck(FALSE);
-        // If the entity type is revisionable then make sure we look for the
-        // latest revision. This is important for moderated entities.
-        if ($plugin->entityTypeObject()->isRevisionable()) {
-          $query->latestRevision();
-        }
-        $ids = $query->execute();
+    $action = 'unpublish';
+
+    // Select all nodes of the types that are enabled for scheduled unpublishing
+    // and where unpublish_on is less than or equal to the current time.
+    $nids = [];
+    $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action));
+    if (!empty($scheduler_enabled_types)) {
+      $query = $this->entityTypeManager->getStorage('node')->getQuery()
+        ->exists('unpublish_on')
+        ->condition('unpublish_on', $this->time->getRequestTime(), '<=')
+        ->condition('type', $scheduler_enabled_types, 'IN')
+        ->latestRevision()
+        ->sort('unpublish_on')
+        ->sort('nid');
+      // Disable access checks for this query.
+      // @see https://www.drupal.org/node/2700209
+      $query->accessCheck(FALSE);
+      $nids = $query->execute();
+    }
+
+    // Allow other modules to add to the list of nodes to be unpublished.
+    $nids = array_unique(array_merge($nids, $this->nidList($action)));
+
+    // Allow other modules to alter the list of nodes to be unpublished.
+    $this->moduleHandler->alter('scheduler_nid_list', $nids, $action);
+
+    /** @var \Drupal\node\NodeInterface[] $nodes */
+    $nodes = $this->loadNodes($nids);
+    foreach ($nodes as $node_multilingual) {
+      // The API calls could return nodes of types which are not enabled for
+      // scheduled unpublishing. Do not process these.
+      if (!$node_multilingual->type->entity->getThirdPartySetting('scheduler', 'unpublish_enable', $this->setting('default_unpublish_enable'))) {
+        throw new SchedulerNodeTypeNotEnabledException(sprintf("Node %d '%s' will not be unpublished because node type '%s' is not enabled for scheduled unpublishing", $node_multilingual->id(), $node_multilingual->getTitle(), node_get_type_label($node_multilingual)));
       }
 
-      // Allow other modules to add to the list of entities to be unpublished.
-      $hook_implementations = $this->getHookImplementations('list', $entityTypeId);
-      foreach ($hook_implementations as $function) {
-        // Cast each hook result as array, to protect from bad implementations.
-        $ids = array_merge($ids, (array) $function($process, $entityTypeId));
-      }
+      $languages = $node_multilingual->getTranslationLanguages();
+      foreach ($languages as $language) {
+        // The object returned by getTranslation() behaves the same as a $node.
+        $node = $node_multilingual->getTranslation($language->getId());
 
-      // Allow other modules to alter the list of entities to be unpublished.
-      $hook_implementations = $this->getHookImplementations('list_alter', $entityTypeId);
-      foreach ($hook_implementations as $function) {
-        $function($ids, $process, $entityTypeId);
-      }
+        // If the current translation does not have an unpublish on value, or it
+        // is later than the date we are processing then move on to the next.
+        $unpublish_on = $node->unpublish_on->value;
+        if (empty($unpublish_on) || $unpublish_on > $this->time->getRequestTime()) {
+          continue;
+        }
+
+        // Do not process the node if it still has a publish_on time which is in
+        // the past, as this implies that scheduled publishing has been blocked
+        // by one of the hook functions we provide, and is still being blocked
+        // now that the unpublishing time has been reached.
+        $publish_on = $node->publish_on->value;
+        if (!empty($publish_on) && $publish_on <= $this->time->getRequestTime()) {
+          continue;
+        }
 
-      // Finally ensure that there are no duplicates in the list of ids.
-      $ids = array_unique($ids);
+        // Check that other modules allow the action on this node.
+        if (!$this->isAllowed($node, $action)) {
+          continue;
+        }
 
-      /** @var \Drupal\Core\Entity\EntityInterface[] $entities */
-      $entities = $this->loadEntities($ids, $entityTypeId);
-      foreach ($entities as $entity_multilingual) {
+        // $node->setChangedTime($unpublish_on) will fail badly if an API call
+        // has removed the date. Trap this as an exception here and give a
+        // meaningful message.
+        // @todo This will now never be thrown due to the empty(unpublish_on)
+        // check above to cater for translations. Remove this exception?
+        if (empty($unpublish_on)) {
+          $field_definitions = $this->entityTypeManager->getFieldDefinitions('node', $node->getType());
+          $field = (string) $field_definitions['unpublish_on']->getLabel();
+          throw new SchedulerMissingDateException(sprintf("Node %d '%s' will not be unpublished because field '%s' has no value", $node->id(), $node->getTitle(), $field));
+        }
+
+        // Trigger the PRE_UNPUBLISH event so that modules can react before the
+        // node is unpublished.
+        $event = new SchedulerEvent($node);
+        $this->dispatch($event, SchedulerEvents::PRE_UNPUBLISH);
+        $node = $event->getNode();
+
+        // Update 'changed' timestamp.
+        $node->setChangedTime($unpublish_on);
+
+        $create_unpublishing_revision = $node->type->entity->getThirdPartySetting('scheduler', 'unpublish_revision', $this->setting('default_unpublish_revision'));
+        if ($create_unpublishing_revision) {
+          $node->setNewRevision();
+          // Use a core date format to guarantee a time is included.
+          $revision_log_message = $this->t('Unpublished by Scheduler. The scheduled unpublishing date was @unpublish_on.', [
+            '@unpublish_on' => $this->dateFormatter->format($unpublish_on, 'short'),
+          ]);
+          // Create the new revision, setting message and revision timestamp.
+          $node->setRevisionLogMessage($revision_log_message)
+            ->setRevisionCreationTime($this->time->getRequestTime());
+        }
+        // Unset unpublish_on so the node will not get rescheduled by subsequent
+        // calls to $node->save().
+        $node->unpublish_on->value = NULL;
+
+        // Invoke all implementations of hook_scheduler_unpublish_action() to
+        // allow other modules to do the "unpublishing" process instead of
+        // Scheduler.
+        $hook = 'scheduler_unpublish_action';
+        $processed = FALSE;
+        $failed = FALSE;
+        foreach ($this->moduleHandler->getImplementations($hook) as $module) {
+          $function = $module . '_' . $hook;
+          $return = $function($node);
+          $processed = $processed || ($return === 1);
+          $failed = $failed || ($return === -1);
+        }
+
+        // Set up the log variables.
+        $view_link = $node->toLink($this->t('View node'));
+        $node_type = $this->entityTypeManager->getStorage('node_type')->load($node->bundle());
+        $node_type_link = $node_type->toLink($this->t('@label settings', ['@label' => $node_type->label()]), 'edit-form');
+        $logger_variables = [
+          '@type' => $node_type->label(),
+          '%title' => $node->getTitle(),
+          'link' => $node_type_link->toString() . ' ' . $view_link->toString(),
+          '@hook' => 'hook_' . $hook,
+        ];
+
+        if ($failed) {
+          // At least one hook function returned a failure or exception, so stop
+          // processing this node and move on to the next one.
+          $this->logger->warning('Unpublishing failed for %title. Calls to @hook returned a failure code.', $logger_variables);
+          continue;
+        }
+        elseif ($processed) {
+          // The node has 'unpublishing' processed by a module implementing the
+          // hook, so no need to do anything more, apart from log this result.
+          $this->logger->notice('@type: scheduled processing of %title completed by calls to @hook.', $logger_variables);
+        }
+        else {
+          // None of the above hook calls processed the node and there were no
+          // errors detected so set the node to unpublished.
+          $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables);
+          $node->setUnpublished();
+        }
 
-        // The API calls could return entities of types which are not enabled
-        // for scheduled unpublishing, so do not process these. This check can
-        // be done once as the setting will be the same for all translations.
-        if (!$this->getThirdPartySetting($entity_multilingual, 'unpublish_enable', $this->setting('default_unpublish_enable'))) {
-          $this->throwSchedulerException($entity_multilingual, 'SchedulerEntityTypeNotEnabledException', $process);
+        // Invoke event to tell Rules that Scheduler has unpublished this node.
+        if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) {
+          _scheduler_rules_integration_dispatch_cron_event($node, 'unpublish');
         }
 
-        $languages = $entity_multilingual->getTranslationLanguages();
-        foreach ($languages as $language) {
-          // The object returned by getTranslation() is a normal $entity.
-          $entity = $entity_multilingual->getTranslation($language->getId());
-
-          // If the current translation does not have an unpublish-on value, or
-          // it is later than the date we are processing then move to the next.
-          $unpublish_on = $entity->unpublish_on->value;
-          if (empty($unpublish_on) || $unpublish_on > $this->time->getRequestTime()) {
-            continue;
-          }
-
-          // Do not process the entity if it still has a publish_on time which
-          // is in the past, as this implies that scheduled publishing has been
-          // blocked by one of the hook functions we provide, and is still being
-          // blocked now that the unpublishing time has been reached.
-          $publish_on = $entity->publish_on->value;
-          if (!empty($publish_on) && $publish_on <= $this->time->getRequestTime()) {
-            continue;
-          }
-
-          // Check that other modules allow the process on this entity.
-          if (!$this->isAllowed($entity, $process)) {
-            continue;
-          }
-
-          // Trigger the PRE_UNPUBLISH Scheduler event so that modules can react
-          // before the entity is unpublished.
-          $this->dispatchSchedulerEvent($entity, 'PRE_UNPUBLISH');
-
-          // Update 'changed' timestamp.
-          if ($entity instanceof EntityChangedInterface) {
-            $entity->setChangedTime($unpublish_on);
-          }
-
-          $create_unpublishing_revision = $this->getThirdPartySetting($entity, 'unpublish_revision', $this->setting('default_unpublish_revision'));
-          if ($create_unpublishing_revision && $entity->getEntityType()->isRevisionable()) {
-            $entity->setNewRevision();
-            // Use a core date format to guarantee a time is included.
-            $revision_log_message = $this->t('Unpublished by Scheduler. The scheduled unpublishing date was @unpublish_on.', [
-              '@unpublish_on' => $this->dateFormatter->format($unpublish_on, 'short'),
-            ]);
-            // Create the new revision, setting message and revision timestamp.
-            $entity->setRevisionLogMessage($revision_log_message)
-              ->setRevisionCreationTime($this->time->getRequestTime());
-          }
-          // Unset publish_on so the entity will not get rescheduled by any
-          // interim calls to $entity->save().
-          $entity->unpublish_on->value = NULL;
-
-          // Invoke all implementations of hook_scheduler_unpublish_process()
-          // and hook_scheduler_{type}_unpublish_process() to allow other
-          // modules to do the "unpublishing" process instead of Scheduler.
-          $hook_implementations = $this->getHookImplementations('unpublish_process', $entity);
-          $sucessful_hooks = [];
-          $failed_hooks = [];
-          foreach ($hook_implementations as $function) {
-            $return = $function($entity);
-            if ($return === 1) {
-              $sucessful_hooks[] = $function;
-              if (stristr($function, '_action')) {
-                // If this is a legacy action hook, for safety call ->save() as
-                // this used to be done here in Scheduler 8.x-1.x.
-                $entity->save();
-              }
-            }
-            $return === -1 ? $failed_hooks[] = $function : NULL;
-          }
-          $processed = count($sucessful_hooks) > 0;
-          $failed = count($failed_hooks) > 0;
-
-          // Create a set of variables for use in the log message.
-          $bundle_type = $entity->getEntityType()->getBundleEntityType();
-          $entity_type = $this->entityTypeManager->getStorage($bundle_type)->load($entity->bundle());
-          $links = [];
-          if ($entity->hasLinkTemplate('canonical')) {
-            $links[] = $entity->toLink($this->t('View @type', [
-              '@type' => strtolower($entity_type->label()),
-            ]))->toString();
-          }
-          if ($entity_type->hasLinkTemplate('edit-form')) {
-            $links[] = $entity_type->toLink($this->t('@label settings', [
-              '@label' => $entity_type->label(),
-            ]), 'edit-form')->toString();
-          }
-          $logger_variables = [
-            '@type' => $entity_type->label(),
-            '%title' => $entity->label(),
-            '@sucessful_hooks' => implode(', ', $sucessful_hooks),
-            '@failed_hooks' => implode(', ', $failed_hooks),
-            'link' => implode(' ', $links),
-          ];
-
-          if ($failed) {
-            // At least one hook function returned a failure or exception, so
-            // stop processing this entity and move on to the next one.
-            $this->logger->warning('Unpublishing failed for %title. @failed_hooks returned a failure code.', $logger_variables);
-            // Restore the unpublish_on date to allow another attempt next time.
-            $entity->unpublish_on->value = $unpublish_on;
-            $entity->save();
-            continue;
-          }
-          elseif ($processed) {
-            // The entity was 'unpublished' by a module implementing the hook,
-            // so we only need to log this result.
-            $this->logger->notice('@type: scheduled "unpublish" processing of %title completed by @sucessful_hooks.', $logger_variables);
-          }
-          else {
-            // None of the above hook calls processed the entity and there were
-            // no errors detected so set the entity to unpublished.
-            $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables);
-
-            // Use the actions system to unpublish and save the entity.
-            $action_id = $plugin->unpublishAction();
-            if ($this->moduleHandler->moduleExists('workbench_moderation_actions')) {
-              // workbench_moderation_actions module replaces the standard
-              // action with a custom one which should be used only when the
-              // entity type is part of a moderation workflow.
-              /** @var \Drupal\workbench_moderation\ModerationInformationInterface $moderation_info */
-              $moderation_info = \Drupal::service('workbench_moderation.moderation_information');
-              if ($moderation_info->isModeratableEntity($entity)) {
-                $action_id = 'state_change__' . $entityTypeId . '__archived';
-              }
-            }
-            if ($loaded_action = $this->entityTypeManager->getStorage('action')->load($action_id)) {
-              $loaded_action->getPlugin()->execute($entity);
-            }
-            else {
-              // Fallback to the direct method if the action does not exist.
-              $entity->setUnpublished()->save();
-            }
-          }
-
-          // Invoke event to tell Rules that Scheduler has unpublished the
-          // entity.
-          if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) {
-            _scheduler_rules_integration_dispatch_cron_event($entity, $process);
-          }
-
-          // Trigger the UNPUBLISH Scheduler event so that modules can react
-          // after the entity is unpublished.
-          $this->dispatchSchedulerEvent($entity, 'UNPUBLISH');
-
-          $result = TRUE;
+        // Trigger the UNPUBLISH event so that modules can react after the node
+        // is unpublished.
+        $event = new SchedulerEvent($node);
+        $this->dispatch($event, SchedulerEvents::UNPUBLISH);
+
+        // Use the standard actions system to unpublish and save the node.
+        $node = $event->getNode();
+        $action_id = 'node_unpublish_action';
+        if ($this->moduleHandler->moduleExists('workbench_moderation_actions')) {
+          // workbench_moderation_actions module uses a custom action instead.
+          $action_id = 'state_change__node__archived';
         }
+        $this->entityTypeManager->getStorage('action')->load($action_id)->getPlugin()->execute($node);
+
+        $result = TRUE;
       }
     }
 
@@ -659,143 +477,57 @@ public function unpublish() {
   }
 
   /**
-   * Checks whether a scheduled process on an entity is allowed.
+   * Checks whether a scheduled action on a node is allowed.
    *
-   * Other modules can prevent scheduled publishing or unpublishing by
-   * implementing any or all of the following:
-   *   hook_scheduler_publishing_allowed()
-   *   hook_scheduler_unpublishing_allowed()
-   *   hook_scheduler_{type}_publishing_allowed()
-   *   hook_scheduler_{type}_unpublishing_allowed()
+   * This provides a way for other modules to prevent scheduled publishing or
+   * unpublishing, by implementing hook_scheduler_allow_publishing() or
+   * hook_scheduler_allow_unpublishing().
    *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity on which the process is to be performed.
-   * @param string $process
-   *   The process to be checked. Values are 'publish' or 'unpublish'.
+   * @param \Drupal\node\NodeInterface $node
+   *   The node on which the action is to be performed.
+   * @param string $action
+   *   The action that needs to be checked. Can be 'publish' or 'unpublish'.
    *
    * @return bool
-   *   TRUE if the process is allowed, FALSE if not.
+   *   TRUE if the action is allowed, FALSE if not.
+   *
+   * @see hook_scheduler_allow_publishing()
+   * @see hook_scheduler_allow_unpublishing()
    */
-  public function isAllowed(EntityInterface $entity, $process) {
+  public function isAllowed(NodeInterface $node, $action) {
     // Default to TRUE.
     $result = TRUE;
-
-    // Get all implementations of the required hook function.
-    $hook_implementations = $this->getHookImplementations($process . 'ing_allowed', $entity);
-
-    // Call the hook functions. If any specifically return FALSE the overall
-    // result is FALSE. If a hook returns nothing it will not affect the result.
-    foreach ($hook_implementations as $function) {
-      $returned = $function($entity);
-      $result &= !(isset($returned) && $returned == FALSE);
+    // Check that other modules allow the action.
+    $hook = 'scheduler_allow_' . $action . 'ing';
+    foreach ($this->moduleHandler->getImplementations($hook) as $module) {
+      $function = $module . '_' . $hook;
+      $result &= $function($node);
     }
+
     return $result;
   }
 
   /**
-   * Returns an array of hook function names implemented for a hook type.
+   * Gather node IDs for all nodes that need to be $action'ed.
    *
-   * The return array will include all implementations of the general hook
-   * function called for all entity types, plus all implemented hooks for the
-   * specific type of entity being processed. In addition, for node entities,
-   * the original hook functions (prior to entity plugins) are added to maintain
-   * backwards-compatibility.
+   * Modules can implement hook_scheduler_nid_list($action) and return an array
+   * of node ids which will be added to the existing list.
    *
-   * @param string $hookType
-   *   The identifier of the hook function, for example 'publish_process' or
-   *   'unpublishing_allowed' or 'hide_publish_date'.
-   * @param \Drupal\Core\Entity\EntityInterface|string $entity
-   *   The entity object which is being processed, or a string containing the
-   *   entity type id (for example 'node' or 'media').
+   * @param string $action
+   *   The action being performed, either "publish" or "unpublish".
    *
    * @return array
-   *   An array of callable function names for the implementations of this hook
-   *   function for the type of entity being processed.
+   *   An array of node ids.
    */
-  public function getHookImplementations(string $hookType, $entity) {
-    $entityTypeId = (is_object($entity)) ? $entity->getEntityTypeid() : $entity;
-    $hooks = [$hookType, "{$entityTypeId}_{$hookType}"];
-
-    // For backwards compatibility the original node hook is also added.
-    if ($entityTypeId == 'node') {
-      $legacy_node_hooks = [
-        'hide_publish_date' => 'hide_publish_on_field',
-        'hide_unpublish_date' => 'hide_unpublish_on_field',
-        'list' => 'nid_list',
-        'list_alter' => 'nid_list_alter',
-        'publish_process' => 'publish_action',
-        'unpublish_process' => 'unpublish_action',
-        'publishing_allowed' => 'allow_publishing',
-        'unpublishing_allowed' => 'allow_unpublishing',
-      ];
-      $hooks[] = $legacy_node_hooks[$hookType];
-    }
+  public function nidList($action) {
+    $nids = [];
 
-    // Find all modules that implement these hooks, then append the $hookName to
-    // the end of the module, thus giving the full function name.
-    $all_hook_implementations = [];
-    foreach ($hooks as $hook) {
-      $hookName = "scheduler_$hook";
-      if (version_compare(\Drupal::VERSION, '9.4', '>=')) {
-        // getImplementations() is deprecated in D9.4, use invokeAllWith().
-        $this->moduleHandler->invokeAllWith($hookName, function (callable $hook, string $module) use ($hookName, &$all_hook_implementations) {
-          $all_hook_implementations[] = $module . "_" . $hookName;
-        });
-      }
-      else {
-        // Use getImplementations() to maintain compatibility with Drupal 8.9.
-        $implementations = $this->moduleHandler->getImplementations($hookName);
-        array_walk($implementations, function (&$module) use ($hookName, &$all_hook_implementations) {
-          $all_hook_implementations[] = $module . "_" . $hookName;
-        });
-      }
-    }
-    return $all_hook_implementations;
-  }
-
-  /**
-   * Gives details and throws exception when a required action is missing.
-   *
-   * This displays a screen error message which is useful if the cron run was
-   * initiated via the site UI. This will also be shown on the terminal if cron
-   * was run via drush. If the Config Update module is installed then a link is
-   * given to the actions report in Config UI, which lists the missing items and
-   * provides a button to import from source. If Config Update is not installed
-   * then a link is provided to its Drupal project page.
-   *
-   * @param string $action_id
-   *   The id of the missing action.
-   * @param string $process
-   *   The Scheduler process being run, 'publish' or 'unpublish'.
-   */
-  protected function missingAction(string $action_id, string $process) {
-    $logger_variables = ['%action_id' => $action_id];
-    // If the Config Update module is available then link to the UI report. If
-    // not then link to the project page on drupal.org.
-    if (\Drupal::moduleHandler()->moduleExists('config_update')) {
-      // If the report UI sub-module is enabled then link directly to the
-      // actions report. Otherwise link to 'Extend' so it can be enabled.
-      if (\Drupal::moduleHandler()->moduleExists('config_update_ui')) {
-        $link = Link::fromTextAndUrl($this->t('Config Update for actions'), Url::fromRoute('config_update_ui.report', [
-          'report_type' => 'type',
-          'name' => 'action',
-        ]));
-      }
-      else {
-        $link = Link::fromTextAndUrl($this->t('Enable Config Update Reports'), Url::fromRoute('system.modules_list', ['filter' => 'config_update']));
-      }
-      $logger_variables['link'] = $link->toString();
-      $logger_variables[':url'] = $link->getUrl()->toString();
-    }
-    else {
-      $project_page = 'https://www.drupal.org/project/config_update';
-      $logger_variables[':url'] = $project_page;
-      $logger_variables['link'] = Link::fromTextAndUrl('Config Update project page', Url::fromUri($project_page))->toString();
+    foreach ($this->moduleHandler->getImplementations('scheduler_nid_list') as $module) {
+      $function = $module . '_scheduler_nid_list';
+      $nids = array_merge($nids, $function($action));
     }
 
-    \Drupal::messenger()->addError($this->t("Action '%action_id' is missing. Use <a href=':url'>Config Update</a> to import the missing action.", $logger_variables));
-    $this->logger->warning("Action '%action_id' is missing. Use Config Update to import the missing action.", $logger_variables);
-    throw new \Exception("Action '{$action_id}' is missing. Scheduled $process halted.");
+    return $nids;
   }
 
   /**
@@ -826,7 +558,6 @@ public function runLightweightCron(array $options = []) {
       else {
         $trigger = 'url';
       }
-      // This has to be 'notice' not 'info' so that drush can show the message.
       $this->logger->notice('Lightweight cron run activated by @trigger.', ['@trigger' => $trigger]);
     }
     scheduler_cron();
@@ -856,608 +587,30 @@ protected function setting($key) {
   }
 
   /**
-   * Get third-party setting for an entity type, via the entity object.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity.
-   * @param string $setting
-   *   The setting to retrieve.
-   * @param mixed $default
-   *   The default value for setting if none is found.
-   *
-   * @return mixed
-   *   The value of the setting.
-   */
-  public function getThirdPartySetting(EntityInterface $entity, $setting, $default) {
-    $typeFieldName = $this->getPlugin($entity->getEntityTypeId())->typeFieldName();
-    if (empty($entity->$typeFieldName)) {
-      // Avoid exception and give details if the typeFieldName does not exist.
-      $params = [
-        '%field' => $typeFieldName,
-        '%id' => $this->getPlugin($entity->getEntityTypeId())->getPluginId(),
-        '%entity' => $entity->getEntityTypeId(),
-      ];
-      \Drupal::messenger()->addError($this->t("Field '%field' specified by typeFieldName in the Scheduler plugin %id is not found in entity type %entity", $params));
-      $this->logger->error("Field '%field' specified by typeFieldName in the Scheduler plugin %id is not found in entity type %entity", $params);
-      return $default;
-    }
-    else {
-      return $entity->$typeFieldName->entity->getThirdPartySetting('scheduler', $setting, $default);
-    }
-  }
-
-  /**
-   * Helper method to load latest revision of each entity.
-   *
-   * @param array $ids
-   *   Array of entity ids.
-   * @param string $type
-   *   The type of entity.
-   *
-   * @return array
-   *   Array of loaded entity objects, keyed by id.
-   */
-  protected function loadEntities(array $ids, string $type) {
-    $storage = $this->entityTypeManager->getStorage($type);
-    $entities = [];
-    foreach ($ids as $id) {
-      // Avoid errors when an implementation of hook_scheduler_{type}_list has
-      // added an id of the wrong type.
-      if (!$entity = $storage->load($id)) {
-        $this->logger->warning('Entity id @id is not a @type entity. Processing skipped.', [
-          '@id' => $id,
-          '@type' => $type,
-        ]);
-        continue;
-      }
-      // If the entity type is revisionable then load the latest revision. For
-      // moderated entities this may be an unpublished draft update of a
-      // currently published entity.
-      if ($entity->getEntityType()->isRevisionable()) {
-        $vid = $storage->getLatestRevisionId($id);
-        $entities[$id] = $storage->loadRevision($vid);
-      }
-      else {
-        $entities[$id] = $entity;
-      }
-    }
-    return $entities;
-  }
-
-  /**
-   * Get a list of all scheduler plugin definitions.
-   *
-   * @return array|mixed[]|null
-   *   A list of definitions for the registered scheduler plugins.
-   */
-  public function getPluginDefinitions() {
-    $plugin_definitions = $this->pluginManager->getDefinitions();
-    // Sort in reverse order so that we have 'node_scheduler' followed by
-    // 'media_scheduler'. When a third entity type plugin gets implemented it
-    // would be possible to add a 'weight' property and sort by that.
-    arsort($plugin_definitions);
-    return $plugin_definitions;
-  }
-
-  /**
-   * Gets instances of applicable Scheduler plugins for the enabled modules.
-   *
-   * @param string $provider
-   *   Optional. Filter the plugins to return only those that are provided by
-   *   the named $provider module.
-   *
-   * @return array
-   *   Array of plugin objects, keyed by the entity type the plugin supports.
-   */
-  public function getPlugins(string $provider = NULL) {
-    $cache = \Drupal::cache()->get('scheduler.plugins');
-    if (!empty($cache) && !empty($cache->data) && empty($provider)) {
-      return $cache->data;
-    }
-
-    $definitions = $this->getPluginDefinitions();
-    $plugins = [];
-    foreach ($definitions as $definition) {
-      $plugin = $this->pluginManager->createInstance($definition['id']);
-      $dependency = $plugin->dependency();
-      // Ignore plugins if there is a dependency module and it is not enabled.
-      if ($dependency && !\Drupal::moduleHandler()->moduleExists($dependency)) {
-        continue;
-      }
-      // Ignore plugins that do not match the specified provider module name.
-      if ($provider && $definition['provider'] != $provider) {
-        continue;
-      }
-      $plugins[$plugin->entityType()] = $plugin;
-    }
-
-    // Save to the cache only when not filtered for a particular a provider.
-    if (empty($provider)) {
-      \Drupal::cache()->set('scheduler.plugins', $plugins);
-    }
-    return $plugins;
-  }
-
-  /**
-   * Reset the scheduler plugins cache.
-   */
-  public function invalidatePluginCache() {
-    \Drupal::cache()->invalidate('scheduler.plugins');
-  }
-
-  /**
-   * Get the supported entity types applicable to the currently enabled modules.
-   *
-   * @param string $provider
-   *   Optional. Filter the returned entity types for only those from the
-   *   plugins that are provided by the named $provider module.
-   *
-   * @return array
-   *   A list of the entity type ids.
-   */
-  public function getPluginEntityTypes(string $provider = NULL) {
-    return array_keys($this->getPlugins($provider));
-  }
-
-  /**
-   * Get a plugin for a specific entity type.
-   *
-   * @param string $entityTypeId
-   *   The entity type id, for example 'node' or 'media'.
-   *
-   * @return mixed
-   *   The plugin object associated with a specific entity, or NULL if none.
-   */
-  public function getPlugin($entityTypeId) {
-    $plugins = $this->getPlugins();
-    return $plugins[$entityTypeId] ?? NULL;
-  }
-
-  /**
-   * Gets the names of the types/bundles enabled for a specific process.
-   *
-   * If the entity type is not supported by Scheduler, or there are no enabled
-   * bundles for this process within the entity type, then an empty array is
-   * returned.
-   *
-   * @param string $entityTypeId
-   *   The entity type id, for example 'node' or 'media'.
-   * @param string $process
-   *   The process to check - 'publish' or 'unpublish'.
-   *
-   * @return array
-   *   The entity's type/bundle names that are enabled for the required process.
-   */
-  public function getEnabledTypes($entityTypeId, $process) {
-    if (!$plugin = $this->getPlugin($entityTypeId)) {
-      return [];
-    };
-    $types = $plugin->getTypes();
-    $types = array_filter($types, function ($bundle) use ($process) {
-      return $bundle->getThirdPartySetting('scheduler', $process . '_enable', $this->setting('default_' . $process . '_enable'));
-    });
-    return array_keys($types);
-  }
-
-  /**
-   * Gets list of entity add/edit form IDs.
-   *
-   * @return array
-   *   List of entity add/edit form IDs for all registered scheduler plugins.
-   */
-  public function getEntityFormIds() {
-    $plugins = $this->getPlugins();
-    $form_ids = [];
-    foreach ($plugins as $plugin) {
-      $form_ids = array_merge($form_ids, $plugin->entityFormIDs());
-    }
-    return $form_ids;
-  }
-
-  /**
-   * Gets list of entity type add/edit form IDs.
-   *
-   * @return array
-   *   List of entity type add/edit form IDs for registered scheduler plugins.
-   */
-  public function getEntityTypeFormIds() {
-    $plugins = $this->getPlugins();
-    $form_ids = [];
-    foreach ($plugins as $plugin) {
-      $form_ids = array_merge($form_ids, $plugin->entityTypeFormIDs());
-    }
-    return $form_ids;
-  }
-
-  /**
-   * Gets the supported Devel Generate form IDs.
-   *
-   * @return array
-   *   List of form IDs used by Devel Generate, keyed by entity type.
-   */
-  public function getDevelGenerateFormIds() {
-    $plugins = $this->getPlugins();
-    $form_ids = [];
-    foreach ($plugins as $entityTypeId => $plugin) {
-      // The devel_generate form id is optional so only save if a value exists.
-      // Use entity type as key so we can get back from form_id to entity.
-      if ($form_id = $plugin->develGenerateForm()) {
-        $form_ids[$entityTypeId] = $form_id;
-      }
-    }
-    return $form_ids;
-  }
-
-  /**
-   * Gets the routes for the entity collection pages.
-   *
-   * @return array
-   *   List of routes for collection pages, keyed by entity type.
-   */
-  public function getCollectionRoutes() {
-    $plugins = $this->getPlugins();
-    $routes = [];
-    foreach ($plugins as $entityTypeId => $plugin) {
-      $routes[$entityTypeId] = $plugin->collectionRoute();
-    }
-    return $routes;
-  }
-
-  /**
-   * Gets the routes for user profile page scheduled views.
-   *
-   * @return array
-   *   List of routes for the user page views, keyed by entity type.
-   */
-  public function getUserPageViewRoutes() {
-    $plugins = $this->getPlugins();
-    $routes = [];
-    foreach ($plugins as $entityTypeId => $plugin) {
-      // The user view is optional so only save if there is a value.
-      if ($route = $plugin->userViewRoute()) {
-        $routes[$entityTypeId] = $route;
-      }
-    }
-    return $routes;
-  }
-
-  /**
-   * Derives the permission name for an entity type and permission type.
-   *
-   * This function is added because for backwards-compatibility the node
-   * permission names have to end with 'nodes' and 'content'. For all other
-   * newly-supported entity types it is $entityTypeId.
+   * Helper method to load latest revision of each node.
    *
-   * @param string $entityTypeId
-   *   The entity type id, for example 'node', 'media' etc.
-   * @param string $permissionType
-   *   The type of permission - 'schedule' or 'view'.
-   *
-   * @return string
-   *   The internal name of the scheduler permission.
-   */
-  public function permissionName($entityTypeId, $permissionType) {
-    switch ($permissionType) {
-      case 'schedule':
-        return 'schedule publishing of ' . ($entityTypeId == 'node' ? 'nodes' : $entityTypeId);
-
-      case 'view':
-        return 'view scheduled ' . ($entityTypeId == 'node' ? 'content' : $entityTypeId);
-    }
-  }
-
-  /**
-   * Updates db tables for entities that should have the Scheduler fields.
-   *
-   * This is called from scheduler_modules_installed and scheduler_update_8201.
-   * It can also be called manually via drush command scheduler-entity-update.
+   * @param array $nids
+   *   Array of node ids.
    *
    * @return array
-   *   Labels of the entity types updated.
-   */
-  public function entityUpdate() {
-    $entityUpdateManager = \Drupal::entityDefinitionUpdateManager();
-    $updated = [];
-    $list = $entityUpdateManager->getChangeList();
-    foreach ($list as $entity_type_id => $definitions) {
-      if ($definitions['field_storage_definitions']['publish_on'] ?? 0) {
-        $entity_type = $entityUpdateManager->getEntityType($entity_type_id);
-        $fields = scheduler_entity_base_field_info($entity_type);
-        foreach ($fields as $field_name => $field_definition) {
-          $entityUpdateManager->installFieldStorageDefinition($field_name, $entity_type_id, $entity_type_id, $field_definition);
-        }
-        $this->logger->notice('%entity updated with Scheduler publish_on and unpublish_on fields.', [
-          '%entity' => $entity_type->getLabel(),
-        ]);
-        $updated[] = (string) $entity_type->getLabel();
-      }
-    }
-    return $updated;
-  }
-
-  /**
-   * Refreshes scheduler views from source.
-   *
-   * If the view exists in the site's active storage it will be updated from the
-   * source yml file. If the view is now required but does not exist in active
-   * storage it will be loaded.
+   *   Array of loaded nodes.
    *
-   * Called from scheduler_modules_installed() and scheduler_update_8202().
-   *
-   * @param array $only_these_types
-   *   List of entity types to restrict the update of views to these types only.
-   *   Optional. If none then revert/load all applicable scheduler views.
-   *
-   * @return array
-   *   Labels of the views that were updated.
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
    */
-  public function viewsUpdate(array $only_these_types = []) {
-    $updated = [];
-    $definition = $this->entityTypeManager->getDefinition('view');
-    $view_storage = $this->entityTypeManager->getStorage('view');
-    // Get the supported entity type ids for enabled modules where the provider
-    // is Scheduler. Third-party plugins do not need to be processed here.
-    $entity_types = $this->getPluginEntityTypes('scheduler');
-    if ($only_these_types) {
-      $entity_types = array_intersect($entity_types, $only_these_types);
-    }
-
-    foreach ($entity_types as $entity_type) {
-      $name = 'scheduler_scheduled_' . ($entity_type == 'node' ? 'content' : $entity_type);
-      $full_name = $definition->getConfigPrefix() . '.' . $name;
-
-      // Read the view definition from the .yml file. First try the /optional
-      // folder, then the main /config folder.
-      $optional_folder = \Drupal::service('extension.list.module')->getPath('scheduler') . '/config/optional';
-      $source_storage = new FileStorage($optional_folder);
-      if (!$source = $source_storage->read($full_name)) {
-        $install_folder = \Drupal::service('extension.list.module')->getPath('scheduler') . '/config/install';
-        $source_storage = new FileStorage($install_folder);
-        if (!$source = $source_storage->read($full_name)) {
-          $this->logger->notice('No source file for %full_name in either %install_folder or %optional_folder folders',
-            ['%full_name' => $full_name, '%install_folder' => $install_folder, '%optional_folder' => $optional_folder]);
-          continue;
-        }
-      }
+  protected function loadNodes(array $nids) {
+    $node_storage = $this->entityTypeManager->getStorage('node');
+    $nodes = [];
 
-      // Try to read the view definition from active config storage.
-      /** @var \Drupal\Core\Config\StorageInterface $config_storage */
-      $config_storage = \Drupal::service('config.storage');
-      if ($config_storage->read($full_name)) {
-        // The view does exist in active storage, so load it, then replace the
-        // value with the source, but retain the _core and uuid values.
-        $view = $view_storage->load($name);
-        $core = $view->get('_core');
-        $uuid = $view->get('uuid');
-        $view = $view_storage->updateFromStorageRecord($view, $source);
-        $view->set('_core', $core);
-        $view->set('uuid', $uuid);
-        $view->save();
-        $this->logger->info('%view view updated.', ['%view' => $source['label']]);
-      }
-      else {
-        // The view does not exist in active storage so import it from source.
-        $view = $view_storage->createFromStorageRecord($source);
-        $view->save();
-        $this->logger->info('%view view loaded from source.', ['%view' => $source['label']]);
-      }
-      $updated[] = $source['label'];
+    // Load the latest revision for each node.
+    foreach ($nids as $nid) {
+      $node = $node_storage->load($nid);
+      $revision_ids = $node_storage->revisionIds($node);
+      $vid = end($revision_ids);
+      $nodes[] = $node_storage->loadRevision($vid);
     }
-    // The views are loaded OK but the publish-on and unpublish-on views field
-    // handlers are not found. Clearing the views data cache solves the problem.
-    Cache::invalidateTags(['views_data']);
-    return $updated;
-  }
 
-  /**
-   * Reverts entity types that are no longer supported by Scheduler plugins.
-   *
-   * In normal situations this function is not required. However in the case
-   * when a plugin (either provided by Scheduler or another modules) is removed
-   * after being used, the db fields and third-party-settings remain and have to
-   * be deleted. This function was added to clean up the Paragraphs entity type
-   * but has been made generic for future use. It is called from a hook_update()
-   * and can also be run via drush command scheduler:entity-revert.
-   * See https://www.drupal.org/project/scheduler/issues/3259200
-   *
-   * @param array $only_these_types
-   *   Optional list of entity type ids to restrict the updates. If none given
-   *   then reverts all applicable entity types that have schema changes showing
-   *   that the db fields need to be removed.
-   *
-   * @return array
-   *   Messages about the entity types reverted.
-   */
-  public function entityRevert(array $only_these_types = []) {
-    // Find all changed entity definitions.
-    $entityUpdateManager = \Drupal::entityDefinitionUpdateManager();
-    $changeList = $entityUpdateManager->getChangeList();
-
-    $output = [];
-    if ($only_these_types) {
-      // First remove any non-existent entity types requested.
-      $all_entity_types = array_keys($this->entityTypeManager->getDefinitions());
-      if ($unknown = array_diff($only_these_types, $all_entity_types)) {
-        $output['unknown'] = $this->t('Unknown entity types (@unknown)', ['@unknown' => implode(' ', $unknown)]);
-      }
-      $entity_type_ids = array_intersect($only_these_types, $all_entity_types);
-    }
-    else {
-      // Nothing given. Get the list of changed entity types.
-      $entity_type_ids = array_keys($changeList);
-    }
-    // Remove any requested entity types that do have enabled plugins, as these
-    // must not be reverted.
-    $supported_types = $this->getPluginEntityTypes();
-    $entity_type_ids = array_diff($entity_type_ids, $supported_types);
-
-    foreach ($entity_type_ids as $entity_type_id) {
-      $entityType = $this->entityTypeManager->getDefinition($entity_type_id);
-      $bundleType = $entityType->getBundleEntityType();
-
-      // Remove the Scheduler fields from the entity type if they are shown in
-      // the changeList as 'deleted'.
-      if (isset($changeList[$entity_type_id]['field_storage_definitions'])) {
-        foreach (['publish_on', 'unpublish_on'] as $field_name) {
-          $change = ($changeList[$entity_type_id]['field_storage_definitions'][$field_name] ?? NULL);
-          // If the field is marked as deleted then remove it.
-          if ($change == $entityUpdateManager::DEFINITION_DELETED && $field = $entityUpdateManager->getFieldStorageDefinition($field_name, $entity_type_id)) {
-            $entityUpdateManager->uninstallFieldStorageDefinition($field);
-            $output["{$entity_type_id} fields"] = $this->t('Scheduler fields removed from @entityType', [
-              '@entityType' => $entityType->getLabel(),
-            ]);
-            $this->logger->info('%field field removed from %entityType entity type', [
-              '%field' => $field->getLabel(),
-              '%entityType' => $entityType->getLabel(),
-            ]);
-          }
-        }
-      }
-
-      // Remove Scheduler third-party-settings from each bundle.
-      foreach ($this->entityTypeManager->getStorage($bundleType)->loadMultiple() as $bundle) {
-        // Remove each third_party_setting. The last one to be removed will also
-        // cause the 'scheduler' top-level array to be deleted.
-        $third_party_settings = $bundle->getThirdPartySettings('scheduler');
-        if ($third_party_settings) {
-          foreach (array_keys($third_party_settings) as $setting) {
-            $bundle->unsetThirdPartySetting('scheduler', $setting)->save();
-          }
-          $this->logger->info('Scheduler settings removed from %entity %bundle', [
-            '%entity' => $bundle->getEntityType()->getLabel(),
-            '%bundle' => $bundle->label(),
-          ]);
-          $output["{$bundle->id()} settings"] = $this->t('Settings removed from @bundle', [
-            '@bundle' => $bundle->label(),
-          ]);
-        }
-      }
-    }
-
-    return $output;
-  }
-
-  /**
-   * Reset the form display fields to match the Scheduler enabled settings.
-   *
-   * The Scheduler fields are disabled by default and only enabled in a form
-   * display when that entity bundle is enabled for scheduled publishing or
-   * unpublishing. See _scheduler_form_entity_type_submit() for details.
-   *
-   * This was a design change during the development of Scheduler 2.0 and any
-   * site that had installed Scheduler prior to 2.0-rc8 will have all fields
-   * enabled. Whilst this should not be a problem, it is preferrable to update
-   * the displays to match the scenario when the modules is freshly installed.
-   * Hence this function was added and called from scheduler_update_8208().
-   */
-  public function resetFormDisplayFields() {
-    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
-    $display_repository = \Drupal::service('entity_display.repository');
-    $fields_displayed = [];
-    $fields_hidden = [];
-
-    foreach ($this->getPlugins() as $entityTypeId => $plugin) {
-      // Get all active display modes. getFormModes() returns the additional
-      // modes then add the default.
-      $all_display_modes = array_keys($display_repository->getFormModes($entityTypeId));
-      $all_display_modes[] = $display_repository::DEFAULT_DISPLAY_MODE;
-
-      $supported_display_modes = $plugin->entityFormDisplayModes();
-
-      $bundles = $plugin->getTypes();
-      foreach ($bundles as $bundle_id => $bundle) {
-        foreach ($all_display_modes as $display_mode) {
-          $form_display = $display_repository->getFormDisplay($entityTypeId, $bundle_id, $display_mode);
-
-          foreach (['publish', 'unpublish'] as $value) {
-            $field = $value . '_on';
-            $setting = $value . '_enable';
-            // If this bundle is not enabled for scheduled (un)publishing or the
-            // form display mode is not supported then remove the field.
-            if (!$bundle->getThirdPartySetting('scheduler', $setting, FALSE) || !in_array($display_mode, $supported_display_modes)) {
-              $form_display->removeComponent($field)->save();
-              if ($display_mode == $display_repository::DEFAULT_DISPLAY_MODE) {
-                $fields_hidden[$field]["{$bundle->getEntityType()->getCollectionLabel()}"][] = $bundle->label();
-              }
-            }
-            else {
-              // Scheduling is enabled. Get the existing component to preserve
-              // any changed settings, but if the type is empty or set the to
-              // the core default 'datetime_timestamp' then change it to
-              // Scheduler's 'datetime_timestamp_no_default'.
-              $component = $form_display->getComponent($field);
-              if (empty($component['type']) || $component['type'] == 'datetime_timestamp') {
-                $component['type'] = 'datetime_timestamp_no_default';
-              }
-              $component['weight'] = ($field == 'publish_on' ? 52 : 54);
-              // Make sure the field and the settings group are displayed.
-              $form_display->setComponent('scheduler_settings', ['weight' => 50])
-                ->setComponent($field, $component)->save();
-              if ($display_mode == $display_repository::DEFAULT_DISPLAY_MODE) {
-                $fields_displayed[$field]["{$bundle->getEntityType()->getCollectionLabel()}"][] = $bundle->label();
-              }
-            }
-          }
-          // If the display mode is not supported remove the group fieldset.
-          if (!in_array($display_mode, $supported_display_modes)) {
-            $form_display->removeComponent('scheduler_settings')->save();
-          }
-        }
-      }
-    }
-
-    // It is not possible to determine whether a field on an enabled entity type
-    // had been manually hidden before this update. It is a rare scenario but
-    // inform the admin that there is potentially some manual work to do.
-    $uri = 'https://www.drupal.org/project/scheduler/issues/3320341';
-    $link = Link::fromTextAndUrl($this->t('Scheduler issue 3320341'), Url::fromUri($uri));
-    \Drupal::messenger()->addMessage($this->t(
-      'The Scheduler fields are now hidden by default and automatically changed to be displayed when an entity
-      bundle is enabled for scheduling. If you have previously manually hidden scheduler fields for enabled
-      entity types then these fields will now be displayed. You will need to manually hide them again or
-      implement hook_scheduler_hide_publish_date() or hook_scheduler_{TYPE}_hide_publish_date() and the
-      equivalent for unpublish_date. See @issue for details.',
-      ['@issue' => $link->toString()]), MessengerInterface::TYPE_STATUS, FALSE);
-    $this->logger->warning(
-      'The Scheduler fields are now hidden by default and automatically changed to be displayed when an entity
-      bundle is enabled for scheduling. If you have previously manually hidden scheduler fields for enabled
-      entity types then these fields will now be displayed. You will need to manually hide them again or
-      implement hook_scheduler_hide_publish_date() or hook_scheduler_{TYPE}_hide_publish_date() and the
-      equivalent for unpublish_date. See @issue for details.',
-      ['@issue' => $link->toString(), 'link' => $link->toString()]
-    );
-
-    /**
-     * Helper function to format the list of fields on bundles.
-     */
-    function formatOutputText($fields) {
-      return implode(', ', array_map(function ($name, $bundles) {
-        return "$name (" . implode(',', $bundles) . ")";
-      }, array_keys($fields), $fields));
-    }
-
-    $output = [];
-    if (isset($fields_displayed['publish_on'])) {
-      $output[] = $this->t('Publish On field displayed for: @list', [
-        '@list' => formatOutputText($fields_displayed['publish_on']),
-      ]);
-    }
-    if (isset($fields_displayed['unpublish_on'])) {
-      $output[] = $this->t('Unpublish On field displayed for: @list', [
-        '@list' => formatOutputText($fields_displayed['unpublish_on']),
-      ]);
-    }
-    if (isset($fields_hidden['publish_on'])) {
-      $output[] = $this->t('Publish On field hidden for: @list', [
-        '@list' => formatOutputText($fields_hidden['publish_on']),
-      ]);
-    }
-    if (isset($fields_hidden['unpublish_on'])) {
-      $output[] = $this->t('Unpublish On field hidden for: @list', [
-        '@list' => formatOutputText($fields_hidden['unpublish_on']),
-      ]);
-    }
-    return $output;
+    return $nodes;
   }
 
 }
diff --git a/web/modules/scheduler/src/SchedulerPermissions.php b/web/modules/scheduler/src/SchedulerPermissions.php
deleted file mode 100644
index 0af7384dd4467eae1617a793f05c0f9251ece747..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/SchedulerPermissions.php
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-
-namespace Drupal\scheduler;
-
-use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides dynamic permissions for scheduler plugins.
- */
-class SchedulerPermissions implements ContainerInjectionInterface {
-
-  use StringTranslationTrait;
-
-  /**
-   * The scheduler manager service.
-   *
-   * @var SchedulerManager
-   */
-  private $schedulerManager;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * Constructs a \Drupal\scheduler\SchedulerPermissions instance.
-   *
-   * @param \Drupal\scheduler\SchedulerManager $scheduler_manager
-   *   The entity type manager.
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager.
-   */
-  public function __construct(SchedulerManager $scheduler_manager, EntityTypeManagerInterface $entity_type_manager) {
-    $this->schedulerManager = $scheduler_manager;
-    $this->entityTypeManager = $entity_type_manager;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static($container->get('scheduler.manager'), $container->get('entity_type.manager'));
-  }
-
-  /**
-   * Build permissions for each entity type.
-   *
-   * SchedulerManager function permissionName() can be used to return the
-   * permission name for a given entity type and permission type.
-   *
-   * @return array|array[]
-   *   The full list of permissions to schedule and to view each entity type.
-   */
-  public function permissions() {
-    $permissions = [];
-    $types = $this->schedulerManager->getPluginEntityTypes();
-    foreach ($types as $entity_type_id) {
-      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-      // For backwards-compatibility with existing permissions, the node
-      // permission names have to end with 'nodes' and 'content'. For all other
-      // entity types we use $entity_type_id for both permissions.
-      if ($entity_type_id == 'node') {
-        $edit_key = 'nodes';
-        $view_key = 'content';
-      }
-      else {
-        $edit_key = $view_key = $entity_type_id;
-      }
-      $t_args = [
-        '%label' => $entity_type->getLabel(),
-        '%singular_label' => $entity_type->getSingularLabel(),
-        '%plural_label' => $entity_type->getPluralLabel(),
-      ];
-
-      $permissions += [
-        "schedule publishing of $edit_key"  => [
-          'title' => $this->t('Schedule publishing and unpublishing of %label', $t_args),
-          'description' => $this->t('Allows users to set a start and end time for %singular_label publication.', $t_args),
-        ],
-        "view scheduled $view_key" => [
-          'title' => $this->t('View scheduled %label', $t_args),
-          'description' => $this->t('Allows users to see a list of all %plural_label that are scheduled.', $t_args),
-        ],
-      ];
-    }
-    return $permissions;
-  }
-
-}
diff --git a/web/modules/scheduler/src/SchedulerPluginBase.php b/web/modules/scheduler/src/SchedulerPluginBase.php
deleted file mode 100644
index fb5ca8facda06b49450f85374fc5f0ef9e9ba0a8..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/SchedulerPluginBase.php
+++ /dev/null
@@ -1,282 +0,0 @@
-<?php
-
-namespace Drupal\scheduler;
-
-use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
-use Drupal\Core\Plugin\PluginBase;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Base class for scheduler plugins.
- */
-abstract class SchedulerPluginBase extends PluginBase implements SchedulerPluginInterface {
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The entity type object for this plugin.
-   *
-   * @var Drupal\Core\Config\Entity\ConfigEntityType
-   */
-  protected $entityTypeObject;
-
-  /**
-   * A static cache of create/edit entity form IDs.
-   *
-   * @var string[]
-   */
-  protected $entityFormIds;
-
-  /**
-   * A static cache of create/edit entity type form IDs.
-   *
-   * @var string[]
-   */
-  protected $entityTypeFormIds;
-
-  /**
-   * Create method.
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    $instance = new static($configuration, $plugin_id, $plugin_definition);
-    $instance->entityTypeManager = $container->get('entity_type.manager');
-    $instance->entityTypeObject = $instance->entityTypeManager
-      ->getDefinition($plugin_definition['entityType']);
-
-    return $instance;
-  }
-
-  /**
-   * Get plugin label.
-   *
-   * @return string
-   *   The label.
-   */
-  public function label() {
-    return $this->pluginDefinition['label'];
-  }
-
-  /**
-   * Get the plugin description.
-   *
-   * @inheritDoc
-   */
-  public function description() {
-    return $this->pluginDefinition['description'];
-  }
-
-  /**
-   * Get the type of entity supported by this plugin.
-   *
-   * @return string
-   *   The name of the entity type.
-   */
-  public function entityType() {
-    return $this->pluginDefinition['entityType'];
-  }
-
-  /**
-   * Get the entity type object supported by this plugin.
-   *
-   * @return Drupal\Core\Config\Entity\ConfigEntityType
-   *   The entity type object.
-   */
-  public function entityTypeObject() {
-    return $this->entityTypeObject;
-  }
-
-  /**
-   * Get module dependency.
-   *
-   * @return string
-   *   The name of the required module.
-   */
-  public function dependency() {
-    return $this->pluginDefinition['dependency'];
-  }
-
-  /**
-   * Get the id of the Devel Generate form for this entity type.
-   *
-   * @return string
-   *   The form id, or an empty string if none.
-   */
-  public function develGenerateForm() {
-    return $this->pluginDefinition['develGenerateForm'];
-  }
-
-  /**
-   * Get the route of the entity collection page.
-   *
-   * @return string
-   *   The route. Defaults to entity.{entityType}.collection.
-   */
-  public function collectionRoute() {
-    return $this->pluginDefinition['collectionRoute'] ?? "entity.{$this->entityType()}.collection";
-  }
-
-  /**
-   * Get the route of the scheduled view on the user profile page.
-   *
-   * @return string
-   *   The route, or blank if none.
-   */
-  public function userViewRoute() {
-    return $this->pluginDefinition['userViewRoute'];
-  }
-
-  /**
-   * Get the Scheduler event class.
-   *
-   * @return string
-   *   The event class.
-   */
-  public function schedulerEventClass() {
-    // If no event class is defined in the plugin then it will default to
-    // '\Drupal\scheduler\Event\Scheduler{entityType}Events'. Specifying an
-    // event class is only required when the entityType value contains an
-    // underscore because that produces an invalid class name.
-    $class = $this->pluginDefinition['schedulerEventClass'] ??
-      '\Drupal\scheduler\Event\Scheduler' . ucfirst($this->entityType()) . 'Events';
-    return $class;
-  }
-
-  /**
-   * Get the publish action name of the entity type.
-   *
-   * If no value is given in the plugin annotation then default to the commonly
-   * used {entity type id}_publish_action.
-   *
-   * @return string
-   *   The action name.
-   */
-  public function publishAction() {
-    return $this->pluginDefinition['publishAction'] ?? $this->entityType() . '_publish_action';
-  }
-
-  /**
-   * Get the unpublish action name of the entity type.
-   *
-   * If no value is given in the plugin annotation then default to the commonly
-   * used {entity type id}_unpublish_action.
-   *
-   * @return string
-   *   The action name.
-   */
-  public function unpublishAction() {
-    return $this->pluginDefinition['unpublishAction'] ?? $this->entityType() . '_unpublish_action';
-  }
-
-  /**
-   * Get the field name for the 'type' or 'bundle'.
-   *
-   * @return string
-   *   The name of the type/bundle field for this entity type.
-   */
-  public function typeFieldName() {
-    return $this->entityTypeObject->getKey('bundle');
-  }
-
-  /**
-   * Get all the type/bundle objects for this entity.
-   *
-   * @return array
-   *   The type/bundle objects, keyed by type/bundle name.
-   */
-  public function getTypes() {
-    $bundleEntityType = $this->entityTypeObject->getBundleEntityType();
-
-    return $this->entityTypeManager
-      ->getStorage($bundleEntityType)
-      ->loadMultiple();
-  }
-
-  /**
-   * Get the form IDs for entity add/edit forms.
-   */
-  public function entityFormIds() {
-    if (isset($this->entityFormIds)) {
-      return $this->entityFormIds;
-    }
-
-    return $this->entityFormIds = $this->entityFormIdsByType($this->entityType(), FALSE);
-  }
-
-  /**
-   * Get the form IDs for entity type add/edit forms.
-   */
-  public function entityTypeFormIds() {
-    if (isset($this->entityTypeFormIds)) {
-      return $this->entityTypeFormIds;
-    }
-
-    $bundleEntityType = $this->entityTypeObject->getBundleEntityType();
-
-    return $this->entityTypeFormIds = $this->entityFormIdsByType($bundleEntityType, TRUE);
-  }
-
-  /**
-   * Get the form IDs for the add/edit forms of a certain entity type.
-   *
-   * The logic for this function is based on EntityForm::getFormId.
-   *
-   * @param string $entityType
-   *   The entity type for which to return the form ids.
-   * @param bool $isBundle
-   *   TRUE if this is the entity type/bundle form.
-   *
-   * @see \Drupal\Core\Entity\EntityForm::getFormId()
-   */
-  protected function entityFormIdsByType(string $entityType, bool $isBundle): array {
-    $ids = [];
-    $definition = $this->entityTypeManager->getDefinition($entityType);
-    $operations = [];
-
-    // Some entity types, such as node, do not have 'add' in the add form id.
-    if ($definition->getFormClass('add')) {
-      $operations[] = 'add';
-    }
-    else {
-      $operations[] = 'default';
-    }
-    // Some entity types, for example taxonomy_vocabulary and taxonomy_term, do
-    // not have a separate edit form.
-    if ($definition->getFormClass('edit')) {
-      $operations[] = 'edit';
-    }
-
-    // When creating the first type/bundle there will be nothing returned for
-    // $this->getTypes(). This is only a problem when getting the 'type' forms,
-    // which do not actually need the list of types anyway. Hence for this case
-    // we need an element in $types, one is enough and it can be anything.
-    $types = $isBundle ? [''] : array_keys($this->getTypes());
-    foreach ($types as $typeId) {
-      foreach ($operations as $operation) {
-        $form_id = $entityType;
-        // Do not add typeId for the entity type forms.
-        if ($definition->hasKey('bundle')) {
-          $form_id .= '_' . $typeId;
-        }
-        if ($operation != 'default') {
-          $form_id .= '_' . $operation;
-        }
-        $ids[] = $form_id . '_form';
-      }
-    }
-
-    return array_unique($ids);
-  }
-
-  /**
-   * Return all supported entity form display modes.
-   */
-  public function entityFormDisplayModes() {
-    return [EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE];
-  }
-
-}
diff --git a/web/modules/scheduler/src/SchedulerPluginInterface.php b/web/modules/scheduler/src/SchedulerPluginInterface.php
deleted file mode 100644
index 050ab7fb779ba55fe1efe763ff2eeb74290b8f6a..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/SchedulerPluginInterface.php
+++ /dev/null
@@ -1,148 +0,0 @@
-<?php
-
-namespace Drupal\scheduler;
-
-/**
- * Interface for Scheduler entity plugin definition.
- */
-interface SchedulerPluginInterface {
-
-  /**
-   * Get the label.
-   *
-   * @return mixed
-   *   The label.
-   */
-  public function label();
-
-  /**
-   * Get the description.
-   *
-   * @return mixed
-   *   The description.
-   */
-  public function description();
-
-  /**
-   * Get the type of entity supported by this plugin.
-   *
-   * @return string
-   *   The name of the entity type.
-   */
-  public function entityType();
-
-  /**
-   * Get the name of the "type" field for the entity.
-   *
-   * @return string
-   *   The name of the type/bundle field for this entity type.
-   */
-  public function typeFieldName();
-
-  /**
-   * Get module dependency.
-   *
-   * @return string
-   *   The name of the required module.
-   */
-  public function dependency();
-
-  /**
-   * Get the id of the Devel Generate form for this entity type. Optional.
-   *
-   * @return string
-   *   The form id.
-   */
-  public function develGenerateForm();
-
-  /**
-   * Get the route of the entity collection page.
-   *
-   * Optional. Defaults to entity.{entity type id}.collection.
-   *
-   * @return string
-   *   The route id.
-   */
-  public function collectionRoute();
-
-  /**
-   * Get the route of the user page scheduled view. Optional.
-   *
-   * @return string
-   *   The route id.
-   */
-  public function userViewRoute();
-
-  /**
-   * Get the scheduler event class.
-   *
-   * Optional. Defaults to '\Drupal\scheduler\Event\Scheduler{Type}Events' the
-   * event class within the Scheduler module namespace.
-   *
-   * @return string
-   *   The event class.
-   */
-  public function schedulerEventClass();
-
-  /**
-   * Get the publish action name of the entity type.
-   *
-   * Optional. Defaults to the commonly used {entity type id}_publish_action.
-   *
-   * @return string
-   *   The action name.
-   */
-  public function publishAction();
-
-  /**
-   * Get the unpublish action name of the entity type.
-   *
-   * Optional. Defaults to the commonly used {entity type id}_unpublish_action.
-   *
-   * @return string
-   *   The action name.
-   */
-  public function unpublishAction();
-
-  /**
-   * Get all the type/bundle objects for this entity.
-   *
-   * @return array
-   *   The type/bundle objects.
-   */
-  public function getTypes();
-
-  /**
-   * Get the form IDs for entity add/edit forms.
-   *
-   * @return array
-   *   A list of add/edit form ids for all bundles in this entity type.
-   */
-  public function entityFormIds();
-
-  /**
-   * Get the form IDs for entity type add/edit forms.
-   *
-   * @return array
-   *   A list of add/edit form ids for this entity type.
-   */
-  public function entityTypeFormIds();
-
-  /**
-   * Return all supported entity edit form display modes.
-   *
-   * \Drupal\Core\Entity\EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE
-   * is the 'default' display mode and this is always supported. If there are no
-   * other supported modes then this function does not need to be implemented in
-   * the plugin. However if additional form display modes are provided by other
-   * modules and Scheduler has been updated to support these modes for editting
-   * the entity, then the plugin implementaion of this function should return
-   * all supported modes including 'default'. The implementation does not need
-   * to check if the third-party module is actually available or enabled.
-   *
-   * @return array
-   *   A list of entity form display mode ids.
-   */
-  public function entityFormDisplayModes();
-
-}
diff --git a/web/modules/scheduler/src/SchedulerPluginManager.php b/web/modules/scheduler/src/SchedulerPluginManager.php
deleted file mode 100755
index 5e215ae37b6b122a52cd99aa2c830a496325d1b9..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/src/SchedulerPluginManager.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-namespace Drupal\scheduler;
-
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\scheduler\Annotation\SchedulerPlugin;
-
-/**
- * Provides a Scheduler Plugin Manager.
- *
- * @package Drupal\scheduler
- */
-class SchedulerPluginManager extends DefaultPluginManager {
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * Constructs a new SchedulerPluginManager object.
-   */
-  public function __construct(
-    \Traversable $namespaces,
-    CacheBackendInterface $cacheBackend,
-    ModuleHandlerInterface $module_handler,
-    EntityTypeManagerInterface $entity_type_manager
-  ) {
-    $subdir = 'Plugin/Scheduler';
-    $plugin_interface = SchedulerPluginInterface::class;
-    $plugin_definition_annotation_name = SchedulerPlugin::class;
-
-    parent::__construct(
-      $subdir,
-      $namespaces,
-      $module_handler,
-      $plugin_interface,
-      $plugin_definition_annotation_name
-    );
-
-    $this->alterInfo('scheduler_info');
-    $this->setCacheBackend($cacheBackend, 'scheduler_info');
-    $this->entityTypeManager = $entity_type_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function findDefinitions() {
-    // This is an overridden method from the parent class. This version filters
-    // out plugins with missing module and entity type depencencies before they
-    // are initialized, removing the need for separate checks per plugin.
-    $definitions = parent::findDefinitions();
-
-    foreach ($definitions as $plugin_id => $plugin_definition) {
-      if (!empty($plugin_definition['dependency']) && !$this->moduleHandler->moduleExists($plugin_definition['dependency'])) {
-        unset($definitions[$plugin_id]);
-        continue;
-      }
-
-      $entityType = $this->entityTypeManager->getDefinition($plugin_definition['entityType'], FALSE);
-      if (!$entityType || !$entityType->getBundleEntityType()) {
-        unset($definitions[$plugin_id]);
-      }
-    }
-
-    return $definitions;
-  }
-
-}
diff --git a/web/modules/scheduler/src/Theme/SchedulerThemeNegotiator.php b/web/modules/scheduler/src/Theme/SchedulerThemeNegotiator.php
index 33eec06a75dfd1108fddbda3d4395ee45f39d6b5..11f5a454babfcbe92e1cb5591b87390b4a890b41 100644
--- a/web/modules/scheduler/src/Theme/SchedulerThemeNegotiator.php
+++ b/web/modules/scheduler/src/Theme/SchedulerThemeNegotiator.php
@@ -14,9 +14,8 @@ class SchedulerThemeNegotiator implements ThemeNegotiatorInterface {
    * {@inheritdoc}
    */
   public function applies(RouteMatchInterface $route_match) {
-    // Use the Scheduler theme negotiator for scheduler views on the user page.
-    $user_page_routes = \Drupal::service('scheduler.manager')->getUserPageViewRoutes();
-    $applies = (in_array($route_match->getRouteName(), $user_page_routes));
+    // Use the Scheduler theme negotiator for the user 'scheduled' tab.
+    $applies = ($route_match->getRouteName() == 'view.scheduler_scheduled_content.user_page');
     return $applies;
   }
 
@@ -24,12 +23,10 @@ public function applies(RouteMatchInterface $route_match) {
    * {@inheritdoc}
    */
   public function determineActiveTheme(RouteMatchInterface $route_match) {
+    // Return the admin theme.
     $config = \Drupal::service('config.factory')->getEditable('system.theme');
     $admin_theme = $config->get('admin');
-    // Return the admin theme only if the user has permission to use it.
-    if (\Drupal::currentUser()->hasPermission('view the administration theme')) {
-      return $admin_theme;
-    }
+    return $admin_theme;
   }
 
 }
diff --git a/web/modules/scheduler/tests/fixtures/node_type_config.php b/web/modules/scheduler/tests/fixtures/node_type_config.php
deleted file mode 100644
index f488fb0688bc768c74a73fb6358d735beaf74bc6..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/node_type_config.php
+++ /dev/null
@@ -1,122 +0,0 @@
-<?php
-
-/**
- * @file
- * DB fixture for scheduler node type migration tests on top of core's fixture.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->insert('system')
-  ->fields([
-    'filename',
-    'name',
-    'type',
-    'owner',
-    'status',
-    'bootstrap',
-    'schema_version',
-    'weight',
-    'info',
-  ])
-  ->values([
-    'filename' => 'sites/all/modules/contrib/scheduler/scheduler.module',
-    'name' => 'scheduler',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7103',
-    'weight' => '0',
-    'info' => 'a:14:{s:4:\"name\";s:9:\"Scheduler\";s:11:\"description\";s:85:\"This module allows nodes to be published and unpublished on specified dates and time.\";s:4:\"core\";s:3:\"7.x\";s:9:\"configure\";s:30:\"admin/config/content/scheduler\";s:5:\"files\";a:3:{i:0;s:47:\"scheduler_handler_field_scheduler_countdown.inc\";i:1;s:20:\"tests/scheduler.test\";i:2;s:24:\"tests/scheduler_api.test\";}s:17:\"test_dependencies\";a:2:{i:0;s:4:\"date\";i:1;s:5:\"rules\";}s:7:\"version\";s:7:\"7.x-1.6\";s:7:\"project\";s:9:\"scheduler\";s:9:\"datestamp\";s:10:\"1600171819\";s:5:\"mtime\";i:1600171819;s:12:\"dependencies\";a:0:{}s:7:\"package\";s:5:\"Other\";s:3:\"php\";s:5:\"5.2.4\";s:9:\"bootstrap\";i:0;}',
-  ])
-  ->execute();
-
-$connection->insert('variable')
-  ->fields([
-    'name',
-    'value',
-  ])
-  ->values([
-    'name' => 'scheduler_expand_fieldset_article',
-    'value' => 's:1:"0";',
-  ])
-  ->values([
-    'name' => 'scheduler_expand_fieldset_page',
-    'value' => 's:1:"1";',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_enable_article',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_enable_page',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_past_date_article',
-    'value' => 's:5:"error";',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_past_date_page',
-    'value' => 's:7:"publish";',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_required_article',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_required_page',
-    'value' => 'i:0;',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_revision_article',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_revision_page',
-    'value' => 'i:0;',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_touch_article',
-    'value' => 'i:0;',
-  ])
-  ->values([
-    'name' => 'scheduler_publish_touch_page',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_unpublish_enable_article',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_unpublish_enable_page',
-    'value' => 'i:0;',
-  ])
-  ->values([
-    'name' => 'scheduler_unpublish_required_article',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_unpublish_required_page',
-    'value' => 'i:0;',
-  ])
-  ->values([
-    'name' => 'scheduler_unpublish_revision_article',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_unpublish_revision_page',
-    'value' => 'i:0;',
-  ])
-  ->values([
-    'name' => 'scheduler_use_vertical_tabs_article',
-    'value' => 's:1:"1";',
-  ])
-  ->values([
-    'name' => 'scheduler_use_vertical_tabs_page',
-    'value' => 's:1:"0";',
-  ])
-  ->execute();
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_config.php b/web/modules/scheduler/tests/fixtures/scheduler_config.php
deleted file mode 100644
index fc08e2599bf67fe77654e318fc2181c7f0ce9b70..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_config.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-/**
- * @file
- * DB fixture for scheduler migration tests on top of core's fixture.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->insert('system')
-  ->fields([
-    'filename',
-    'name',
-    'type',
-    'owner',
-    'status',
-    'bootstrap',
-    'schema_version',
-    'weight',
-    'info',
-  ])
-  ->values([
-    'filename' => 'sites/all/modules/contrib/scheduler/scheduler.module',
-    'name' => 'scheduler',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7103',
-    'weight' => '0',
-    'info' => 'a:14:{s:4:\"name\";s:9:\"Scheduler\";s:11:\"description\";s:85:\"This module allows nodes to be published and unpublished on specified dates and time.\";s:4:\"core\";s:3:\"7.x\";s:9:\"configure\";s:30:\"admin/config/content/scheduler\";s:5:\"files\";a:3:{i:0;s:47:\"scheduler_handler_field_scheduler_countdown.inc\";i:1;s:20:\"tests/scheduler.test\";i:2;s:24:\"tests/scheduler_api.test\";}s:17:\"test_dependencies\";a:2:{i:0;s:4:\"date\";i:1;s:5:\"rules\";}s:7:\"version\";s:7:\"7.x-1.6\";s:7:\"project\";s:9:\"scheduler\";s:9:\"datestamp\";s:10:\"1600171819\";s:5:\"mtime\";i:1600171819;s:12:\"dependencies\";a:0:{}s:7:\"package\";s:5:\"Other\";s:3:\"php\";s:5:\"5.2.4\";s:9:\"bootstrap\";i:0;}',
-  ])
-  ->execute();
-
-$connection->insert('variable')
-  ->fields([
-    'name',
-    'value',
-  ])
-  ->values([
-    'name' => 'scheduler_allow_date_only',
-    'value' => 'i:1;',
-  ])
-  ->values([
-    'name' => 'scheduler_default_time',
-    'value' => 's:8:"00:00:38";',
-  ])
-  ->values([
-    'name' => 'scheduler_date_format',
-    'value' => 's:9:"Y-m-d H:i";',
-  ])
-  ->execute();
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data.php b/web/modules/scheduler/tests/fixtures/scheduler_data.php
deleted file mode 100644
index 7eab2abacbd66cd57fa134051a8d679b7672c1ac..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-/**
- * @file
- * Include all database dump files.
- */
-
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'field_config.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'field_config_instance.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'node.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'node_revision.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'node_type.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'role.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'role_permission.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'scheduler.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'system.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'users.php';
-include 'scheduler_data' . DIRECTORY_SEPARATOR . 'variable.php';
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/field_config.php b/web/modules/scheduler/tests/fixtures/scheduler_data/field_config.php
deleted file mode 100644
index d2f4f05e9993a67ec2259b6531f81171451a4b83..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/field_config.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('field_config', [
-  'fields' => [
-    'id' => [
-      'type' => 'serial',
-      'not null' => TRUE,
-      'size' => 'normal',
-    ],
-    'field_name' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '32',
-    ],
-    'type' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-    ],
-    'module' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-      'default' => '',
-    ],
-    'active' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'storage_type' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-    ],
-    'storage_module' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-      'default' => '',
-    ],
-    'storage_active' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'locked' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'data' => [
-      'type' => 'blob',
-      'not null' => TRUE,
-      'size' => 'big',
-    ],
-    'cardinality' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'translatable' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'deleted' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-  ],
-  'primary key' => [
-    'id',
-  ],
-  'indexes' => [
-    'field_name' => [
-      'field_name',
-    ],
-    'active' => [
-      'active',
-    ],
-    'storage_active' => [
-      'storage_active',
-    ],
-    'deleted' => [
-      'deleted',
-    ],
-    'module' => [
-      'module',
-    ],
-    'storage_module' => [
-      'storage_module',
-    ],
-    'type' => [
-      'type',
-    ],
-    'storage_type' => [
-      'storage_type',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/field_config_instance.php b/web/modules/scheduler/tests/fixtures/scheduler_data/field_config_instance.php
deleted file mode 100644
index e0713c82d4c0426bdccf03dc09bab001cb44a42a..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/field_config_instance.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('field_config_instance', [
-  'fields' => [
-    'id' => [
-      'type' => 'serial',
-      'not null' => TRUE,
-      'size' => 'normal',
-    ],
-    'field_id' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-    ],
-    'field_name' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '32',
-      'default' => '',
-    ],
-    'entity_type' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '32',
-      'default' => '',
-    ],
-    'bundle' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-      'default' => '',
-    ],
-    'data' => [
-      'type' => 'blob',
-      'not null' => TRUE,
-      'size' => 'big',
-    ],
-    'deleted' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-  ],
-  'primary key' => [
-    'id',
-  ],
-  'indexes' => [
-    'field_name_bundle' => [
-      'field_name',
-      'entity_type',
-      'bundle',
-    ],
-    'deleted' => [
-      'deleted',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/node.php b/web/modules/scheduler/tests/fixtures/scheduler_data/node.php
deleted file mode 100644
index 62c05b746b07e615f469f815cea2757774b815b2..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/node.php
+++ /dev/null
@@ -1,222 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('node', [
-  'fields' => [
-    'nid' => [
-      'type' => 'serial',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'unsigned' => TRUE,
-    ],
-    'vid' => [
-      'type' => 'int',
-      'not null' => FALSE,
-      'size' => 'normal',
-      'unsigned' => TRUE,
-    ],
-    'type' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '32',
-      'default' => '',
-    ],
-    'language' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '12',
-      'default' => '',
-    ],
-    'title' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'uid' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'status' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '1',
-    ],
-    'created' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'changed' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'comment' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'promote' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'sticky' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'tnid' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-      'unsigned' => TRUE,
-    ],
-    'translate' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-  ],
-  'primary key' => [
-    'nid',
-  ],
-  'unique keys' => [
-    'vid' => [
-      'vid',
-    ],
-  ],
-  'indexes' => [
-    'node_changed' => [
-      'changed',
-    ],
-    'node_created' => [
-      'created',
-    ],
-    'node_frontpage' => [
-      'promote',
-      'status',
-      'sticky',
-      'created',
-    ],
-    'node_status_type' => [
-      'status',
-      'type',
-      'nid',
-    ],
-    'node_title_type' => [
-      'title',
-      [
-        'type',
-        '4',
-      ],
-    ],
-    'node_type' => [
-      [
-        'type',
-        '4',
-      ],
-    ],
-    'uid' => [
-      'uid',
-    ],
-    'tnid' => [
-      'tnid',
-    ],
-    'translate' => [
-      'translate',
-    ],
-    'language' => [
-      'language',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
-
-$connection->insert('node')
-  ->fields([
-    'nid',
-    'vid',
-    'type',
-    'language',
-    'title',
-    'uid',
-    'status',
-    'created',
-    'changed',
-    'comment',
-    'promote',
-    'sticky',
-    'tnid',
-    'translate',
-  ])
-  ->values([
-    'nid' => '1',
-    'vid' => '1',
-    'type' => 'article',
-    'language' => 'und',
-    'title' => 'Article one',
-    'uid' => '1',
-    'status' => '0',
-    'created' => '1647492702',
-    'changed' => '1647492702',
-    'comment' => '2',
-    'promote' => '1',
-    'sticky' => '0',
-    'tnid' => '0',
-    'translate' => '0',
-  ])
-  ->values([
-    'nid' => '2',
-    'vid' => '2',
-    'type' => 'article',
-    'language' => 'und',
-    'title' => 'Article two',
-    'uid' => '1',
-    'status' => '0',
-    'created' => '1647492753',
-    'changed' => '1647492753',
-    'comment' => '2',
-    'promote' => '1',
-    'sticky' => '0',
-    'tnid' => '0',
-    'translate' => '0',
-  ])
-  ->values([
-    'nid' => '3',
-    'vid' => '3',
-    'type' => 'article',
-    'language' => 'und',
-    'title' => 'Article three',
-    'uid' => '1',
-    'status' => '1',
-    'created' => '1647492779',
-    'changed' => '1647492779',
-    'comment' => '2',
-    'promote' => '1',
-    'sticky' => '0',
-    'tnid' => '0',
-    'translate' => '0',
-  ])
-  ->execute();
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/node_revision.php b/web/modules/scheduler/tests/fixtures/scheduler_data/node_revision.php
deleted file mode 100644
index 34a9d36733dc121d66fccabab44eb639404df578..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/node_revision.php
+++ /dev/null
@@ -1,140 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('node_revision', [
-  'fields' => [
-    'nid' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-      'unsigned' => TRUE,
-    ],
-    'vid' => [
-      'type' => 'serial',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'unsigned' => TRUE,
-    ],
-    'uid' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'title' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'log' => [
-      'type' => 'text',
-      'not null' => TRUE,
-      'size' => 'big',
-    ],
-    'timestamp' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'status' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '1',
-    ],
-    'comment' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'promote' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'sticky' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-  ],
-  'primary key' => [
-    'vid',
-  ],
-  'indexes' => [
-    'nid' => [
-      'nid',
-    ],
-    'uid' => [
-      'uid',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
-
-$connection->insert('node_revision')
-  ->fields([
-    'nid',
-    'vid',
-    'uid',
-    'title',
-    'log',
-    'timestamp',
-    'status',
-    'comment',
-    'promote',
-    'sticky',
-  ])
-  ->values([
-    'nid' => '1',
-    'vid' => '1',
-    'uid' => '1',
-    'title' => 'Article one',
-    'log' => '',
-    'timestamp' => '1647492702',
-    'status' => '0',
-    'comment' => '2',
-    'promote' => '1',
-    'sticky' => '0',
-  ])
-  ->values([
-    'nid' => '2',
-    'vid' => '2',
-    'uid' => '1',
-    'title' => 'Article two',
-    'log' => '',
-    'timestamp' => '1647492753',
-    'status' => '0',
-    'comment' => '2',
-    'promote' => '1',
-    'sticky' => '0',
-  ])
-  ->values([
-    'nid' => '3',
-    'vid' => '3',
-    'uid' => '1',
-    'title' => 'Article three',
-    'log' => '',
-    'timestamp' => '1647492779',
-    'status' => '1',
-    'comment' => '2',
-    'promote' => '1',
-    'sticky' => '0',
-  ])
-  ->execute();
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/node_type.php b/web/modules/scheduler/tests/fixtures/scheduler_data/node_type.php
deleted file mode 100644
index 1cd74f17c629c750a22862bea3c1ff21e9c635ba..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/node_type.php
+++ /dev/null
@@ -1,127 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('node_type', [
-  'fields' => [
-    'type' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '32',
-    ],
-    'name' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'base' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-    ],
-    'module' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-    ],
-    'description' => [
-      'type' => 'text',
-      'not null' => TRUE,
-      'size' => 'medium',
-    ],
-    'help' => [
-      'type' => 'text',
-      'not null' => TRUE,
-      'size' => 'medium',
-    ],
-    'has_title' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'unsigned' => TRUE,
-    ],
-    'title_label' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'custom' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'modified' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'locked' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'disabled' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'orig_type' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-  ],
-  'primary key' => [
-    'type',
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
-
-$connection->insert('node_type')
-  ->fields([
-    'type',
-    'name',
-    'base',
-    'module',
-    'description',
-    'help',
-    'has_title',
-    'title_label',
-    'custom',
-    'modified',
-    'locked',
-    'disabled',
-    'orig_type',
-  ])
-  ->values([
-    'type' => 'article',
-    'name' => 'Article',
-    'base' => 'node_content',
-    'module' => 'node',
-    'description' => 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.',
-    'help' => '',
-    'has_title' => '1',
-    'title_label' => 'Title',
-    'custom' => '1',
-    'modified' => '1',
-    'locked' => '0',
-    'disabled' => '0',
-    'orig_type' => 'article',
-  ])
-  ->execute();
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/role.php b/web/modules/scheduler/tests/fixtures/scheduler_data/role.php
deleted file mode 100644
index e4d52cea697bf2a8900081bedbdfeb2e8a79917e..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/role.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('role', [
-  'fields' => [
-    'rid' => [
-      'type' => 'serial',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'unsigned' => TRUE,
-    ],
-    'name' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '64',
-      'default' => '',
-    ],
-    'weight' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-  ],
-  'primary key' => [
-    'rid',
-  ],
-  'unique keys' => [
-    'name' => [
-      'name',
-    ],
-  ],
-  'indexes' => [
-    'name_weight' => [
-      'name',
-      'weight',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/role_permission.php b/web/modules/scheduler/tests/fixtures/scheduler_data/role_permission.php
deleted file mode 100644
index 9605848fb615cd22586799f6b32ce06eed089ce6..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/role_permission.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('role_permission', [
-  'fields' => [
-    'rid' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'unsigned' => TRUE,
-    ],
-    'permission' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-      'default' => '',
-    ],
-    'module' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-  ],
-  'primary key' => [
-    'rid',
-    'permission',
-  ],
-  'indexes' => [
-    'permission' => [
-      'permission',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/scheduler.php b/web/modules/scheduler/tests/fixtures/scheduler_data/scheduler.php
deleted file mode 100644
index 8e802398e4f6834a9e43a4eb919ccc9094a90785..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/scheduler.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('scheduler', [
-  'fields' => [
-    'nid' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'unsigned' => TRUE,
-    ],
-    'publish_on' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-      'unsigned' => TRUE,
-    ],
-    'unpublish_on' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-      'unsigned' => TRUE,
-    ],
-  ],
-  'primary key' => [
-    'nid',
-  ],
-  'indexes' => [
-    'scheduler_publish_on' => [
-      'publish_on',
-    ],
-    'scheduler_unpublish_on' => [
-      'unpublish_on',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
-
-$connection->insert('scheduler')
-  ->fields([
-    'nid',
-    'publish_on',
-    'unpublish_on',
-  ])
-  ->values([
-    'nid' => '1',
-    'publish_on' => '1647751855',
-    'unpublish_on' => '1647838255',
-  ])
-  ->values([
-    'nid' => '2',
-    'publish_on' => '1647579055',
-    'unpublish_on' => '0',
-  ])
-  ->execute();
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/system.php b/web/modules/scheduler/tests/fixtures/scheduler_data/system.php
deleted file mode 100644
index 84599ac86bef3af450d37861268c8739df853506..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/system.php
+++ /dev/null
@@ -1,178 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('system', [
-  'fields' => [
-    'filename' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'name' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'type' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '12',
-      'default' => '',
-    ],
-    'owner' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'status' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'bootstrap' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'schema_version' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'small',
-      'default' => '-1',
-    ],
-    'weight' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'info' => [
-      'type' => 'blob',
-      'not null' => FALSE,
-      'size' => 'normal',
-    ],
-  ],
-  'primary key' => [
-    'filename',
-  ],
-  'indexes' => [
-    'system_list' => [
-      'status',
-      'bootstrap',
-      'type',
-      'weight',
-      'name',
-    ],
-    'type_name' => [
-      'type',
-      'name',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
-
-$connection->insert('system')
-  ->fields([
-    'filename',
-    'name',
-    'type',
-    'owner',
-    'status',
-    'bootstrap',
-    'schema_version',
-    'weight',
-    'info',
-  ])
-  ->values([
-    'filename' => 'modules/field/field.module',
-    'name' => 'field',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7004',
-    'weight' => '0',
-    'info' => 'a:14:{s:4:"name";s:5:"Field";s:11:"description";s:57:"Field API to add fields to entities like nodes and users.";s:7:"package";s:4:"Core";s:7:"version";s:4:"7.82";s:4:"core";s:3:"7.x";s:5:"files";a:4:{i:0;s:12:"field.module";i:1;s:16:"field.attach.inc";i:2;s:20:"field.info.class.inc";i:3;s:16:"tests/field.test";}s:12:"dependencies";a:1:{i:0;s:17:"field_sql_storage";}s:8:"required";b:1;s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:15:"theme/field.css";s:29:"modules/field/theme/field.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1626883669";s:5:"mtime";i:1626883669;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
-  ])
-  ->values([
-    'filename' => 'modules/field/modules/text/text.module',
-    'name' => 'text',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7000',
-    'weight' => '0',
-    'info' => 'a:14:{s:4:"name";s:4:"Text";s:11:"description";s:32:"Defines simple text field types.";s:7:"package";s:4:"Core";s:7:"version";s:4:"7.82";s:4:"core";s:3:"7.x";s:12:"dependencies";a:1:{i:0;s:5:"field";}s:5:"files";a:1:{i:0;s:9:"text.test";}s:8:"required";b:1;s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1626883669";s:5:"mtime";i:1626883669;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;s:11:"explanation";s:73:"Field type(s) in use - see <a href="/admin/reports/fields">Field list</a>";}',
-  ])
-  ->values([
-    'filename' => 'modules/filter/filter.module',
-    'name' => 'filter',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7010',
-    'weight' => '0',
-    'info' => 'a:14:{s:4:"name";s:6:"Filter";s:11:"description";s:43:"Filters content in preparation for display.";s:7:"package";s:4:"Core";s:7:"version";s:4:"7.82";s:4:"core";s:3:"7.x";s:5:"files";a:1:{i:0;s:11:"filter.test";}s:8:"required";b:1;s:9:"configure";s:28:"admin/config/content/formats";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1626883669";s:5:"mtime";i:1626883669;s:12:"dependencies";a:0:{}s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
-  ])
-  ->values([
-    'filename' => 'modules/node/node.module',
-    'name' => 'node',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7016',
-    'weight' => '0',
-    'info' => 'a:15:{s:4:"name";s:4:"Node";s:11:"description";s:66:"Allows content to be submitted to the site and displayed on pages.";s:7:"package";s:4:"Core";s:7:"version";s:4:"7.82";s:4:"core";s:3:"7.x";s:5:"files";a:2:{i:0;s:11:"node.module";i:1;s:9:"node.test";}s:8:"required";b:1;s:9:"configure";s:21:"admin/structure/types";s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:8:"node.css";s:21:"modules/node/node.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1626883669";s:5:"mtime";i:1626883669;s:12:"dependencies";a:0:{}s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
-  ])
-  ->values([
-    'filename' => 'modules/system/system.module',
-    'name' => 'system',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7084',
-    'weight' => '0',
-    'info' => 'a:14:{s:4:"name";s:6:"System";s:11:"description";s:54:"Handles general site configuration for administrators.";s:7:"package";s:4:"Core";s:7:"version";s:4:"7.82";s:4:"core";s:3:"7.x";s:5:"files";a:6:{i:0;s:19:"system.archiver.inc";i:1;s:15:"system.mail.inc";i:2;s:16:"system.queue.inc";i:3;s:14:"system.tar.inc";i:4;s:18:"system.updater.inc";i:5;s:11:"system.test";}s:8:"required";b:1;s:9:"configure";s:19:"admin/config/system";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1626883669";s:5:"mtime";i:1626883669;s:12:"dependencies";a:0:{}s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
-  ])
-  ->values([
-    'filename' => 'modules/user/user.module',
-    'name' => 'user',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7019',
-    'weight' => '0',
-    'info' => 'a:15:{s:4:"name";s:4:"User";s:11:"description";s:47:"Manages the user registration and login system.";s:7:"package";s:4:"Core";s:7:"version";s:4:"7.82";s:4:"core";s:3:"7.x";s:5:"files";a:2:{i:0;s:11:"user.module";i:1;s:9:"user.test";}s:8:"required";b:1;s:9:"configure";s:19:"admin/config/people";s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:8:"user.css";s:21:"modules/user/user.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1626883669";s:5:"mtime";i:1626883669;s:12:"dependencies";a:0:{}s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
-  ])
-  ->values([
-    'filename' => 'sites/all/modules/contrib/scheduler/scheduler.module',
-    'name' => 'scheduler',
-    'type' => 'module',
-    'owner' => '',
-    'status' => '1',
-    'bootstrap' => '0',
-    'schema_version' => '7103',
-    'weight' => '0',
-    'info' => 'a:12:{s:4:"name";s:9:"Scheduler";s:11:"description";s:85:"This module allows nodes to be published and unpublished on specified dates and time.";s:4:"core";s:3:"7.x";s:9:"configure";s:30:"admin/config/content/scheduler";s:5:"files";a:3:{i:0;s:47:"scheduler_handler_field_scheduler_countdown.inc";i:1;s:20:"tests/scheduler.test";i:2;s:24:"tests/scheduler_api.test";}s:17:"test_dependencies";a:2:{i:0;s:4:"date";i:1;s:5:"rules";}s:5:"mtime";i:1636363545;s:12:"dependencies";a:0:{}s:7:"package";s:5:"Other";s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
-  ])
-  ->execute();
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/users.php b/web/modules/scheduler/tests/fixtures/scheduler_data/users.php
deleted file mode 100644
index bd3049a07402089df2e4bef00076ef8eb6d0ac18..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/users.php
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('users', [
-  'fields' => [
-    'uid' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-      'unsigned' => TRUE,
-    ],
-    'name' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '60',
-      'default' => '',
-    ],
-    'pass' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-      'default' => '',
-    ],
-    'mail' => [
-      'type' => 'varchar',
-      'not null' => FALSE,
-      'length' => '254',
-      'default' => '',
-    ],
-    'theme' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'signature' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ],
-    'signature_format' => [
-      'type' => 'varchar',
-      'not null' => FALSE,
-      'length' => '255',
-    ],
-    'created' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'access' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'login' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'status' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'tiny',
-      'default' => '0',
-    ],
-    'timezone' => [
-      'type' => 'varchar',
-      'not null' => FALSE,
-      'length' => '32',
-    ],
-    'language' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '12',
-      'default' => '',
-    ],
-    'picture' => [
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ],
-    'init' => [
-      'type' => 'varchar',
-      'not null' => FALSE,
-      'length' => '254',
-      'default' => '',
-    ],
-    'data' => [
-      'type' => 'blob',
-      'not null' => FALSE,
-      'size' => 'big',
-    ],
-  ],
-  'primary key' => [
-    'uid',
-  ],
-  'unique keys' => [
-    'name' => [
-      'name',
-    ],
-  ],
-  'indexes' => [
-    'access' => [
-      'access',
-    ],
-    'created' => [
-      'created',
-    ],
-    'mail' => [
-      'mail',
-    ],
-    'picture' => [
-      'picture',
-    ],
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
diff --git a/web/modules/scheduler/tests/fixtures/scheduler_data/variable.php b/web/modules/scheduler/tests/fixtures/scheduler_data/variable.php
deleted file mode 100644
index 59ca281846ded1a5418f4e2e4c2426bdc48c6536..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/fixtures/scheduler_data/variable.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-/**
- * @file
- * A database agnostic dump for testing purposes.
- *
- * This file was generated by the Drupal 9.2.6 db-tools.php script.
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-$connection->schema()->createTable('variable', [
-  'fields' => [
-    'name' => [
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '128',
-      'default' => '',
-    ],
-    'value' => [
-      'type' => 'blob',
-      'not null' => TRUE,
-      'size' => 'big',
-    ],
-  ],
-  'primary key' => [
-    'name',
-  ],
-  'mysql_character_set' => 'utf8mb3',
-]);
diff --git a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
index e9cc2547a4ad52fdb30ffa827f6c409e66319725..2b115fd4eb19defe1158292dcb94d4ed6e4a8b05 100644
--- a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
@@ -1,11 +1,13 @@
-name: 'Scheduler Entity Access Test'
+name: 'Scheduler Node Access Test'
 type: module
-description: 'Support module for Scheduler testing with restricted entity access.'
+description: 'Support module for Scheduler restricted node access testing.'
 package: Testing
+core: 8.x
+core_version_requirement: ^8 || ^9
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2022-11-20
-version: '2.0.0-rc8'
+# Information added by Drupal.org packaging script on 2021-07-19
+version: '8.x-1.4'
 project: 'scheduler'
-datestamp: 1668951020
+datestamp: 1626701769
diff --git a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.module b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.module
index df95ee9bc7521262bc349e230677f964d270c17d..2caafbb261ef4a7cfcdf5a46619dd980114fb801 100644
--- a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.module
+++ b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.module
@@ -2,12 +2,7 @@
 
 /**
  * @file
- * Scheduler Access Test module.
- *
- * This module is used in SchedulerEntityAccessTest and removes access to all
- * published nodes. The Media module does not provide any corresponding hooks to
- * restrict Media access. This module, and the tests, can be expanded when a
- * suitable access restriction method becomes available for Media entities.
+ * Installation file for Scheduler Access Test module.
  */
 
 use Drupal\Core\Session\AccountInterface;
@@ -18,8 +13,7 @@
  */
 function scheduler_access_test_node_access_records(NodeInterface $node) {
   // For the purpose of this test we deny access to every node regardless of
-  // its published status. However, users with 'View own unpublished {type}'
-  // permission will still be able to view their unpublished nodes.
+  // its published status.
   $grants = [[
     'realm' => 'scheduler',
     'gid' => 1,
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/commerce_product.commerce_product_type.scheduler_api_product_test.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/commerce_product.commerce_product_type.scheduler_api_product_test.yml
deleted file mode 100644
index ac258170d4142232d2d346c71dc641b89e3cfbcb..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/commerce_product.commerce_product_type.scheduler_api_product_test.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - scheduler
-  enforced:
-    module:
-      - scheduler_api_test
-third_party_settings:
-  scheduler:
-    expand_fieldset: always
-    fields_display_mode: fieldset
-    publish_enable: true
-    publish_past_date: schedule
-    publish_past_date_created: false
-    publish_required: false
-    publish_revision: false
-    publish_touch: false
-    show_message_after_update: true
-    unpublish_enable: true
-    unpublish_required: false
-    unpublish_revision: false
-id: scheduler_api_product_test
-label: 'Scheduler API Product Test'
-description: 'Commerce Product type used in Scheduler API testing'
-variationType: default
-multipleVariations: true
-injectVariationFields: true
-traits: {  }
-locked: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.commerce_product.scheduler_api_product_test.default.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.commerce_product.scheduler_api_product_test.default.yml
deleted file mode 100644
index ee919a896ca71f583b1c2c23270e586e7936a01c..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.commerce_product.scheduler_api_product_test.default.yml
+++ /dev/null
@@ -1,97 +0,0 @@
-uuid: fdf40551-08a6-4b2c-83eb-44bc868be4ac
-langcode: en
-status: true
-dependencies:
-  config:
-    - commerce_product.commerce_product_type.scheduler_api_product_test
-    - field.field.commerce_product.scheduler_api_product_test.field_approved_publishing
-    - field.field.commerce_product.scheduler_api_product_test.field_approved_unpublishing
-  module:
-    - commerce
-    - path
-    - scheduler
-id: commerce_product.scheduler_api_product_test.default
-targetEntityType: commerce_product
-bundle: scheduler_api_product_test
-mode: default
-content:
-  created:
-    type: datetime_timestamp
-    weight: 8
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  field_approved_publishing:
-    type: boolean_checkbox
-    weight: 1
-    region: content
-    settings:
-      display_label: true
-    third_party_settings: {  }
-  field_approved_unpublishing:
-    type: boolean_checkbox
-    weight: 2
-    region: content
-    settings:
-      display_label: true
-    third_party_settings: {  }
-  path:
-    type: path
-    weight: 9
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  publish_on:
-    type: datetime_timestamp_no_default
-    weight: 4
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  scheduler_settings:
-    weight: 3
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  status:
-    type: boolean_checkbox
-    settings:
-      display_label: true
-    weight: 10
-    region: content
-    third_party_settings: {  }
-  stores:
-    type: commerce_entity_select
-    weight: 6
-    region: content
-    settings:
-      hide_single_entity: true
-      autocomplete_threshold: 7
-      autocomplete_size: 60
-      autocomplete_placeholder: ''
-    third_party_settings: {  }
-  title:
-    type: string_textfield
-    weight: 0
-    region: content
-    settings:
-      size: 60
-      placeholder: ''
-    third_party_settings: {  }
-  uid:
-    type: entity_reference_autocomplete
-    weight: 7
-    region: content
-    settings:
-      match_operator: CONTAINS
-      match_limit: 10
-      size: 60
-      placeholder: ''
-    third_party_settings: {  }
-  unpublish_on:
-    type: datetime_timestamp_no_default
-    weight: 5
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-hidden:
-  variations: true
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.media.scheduler_api_media_test.default.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.media.scheduler_api_media_test.default.yml
deleted file mode 100644
index a226a64a3799b746cd8c684c4b78c4f50fd892d2..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.media.scheduler_api_media_test.default.yml
+++ /dev/null
@@ -1,95 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - field.field.media.scheduler_api_media_test.field_approved_publishing
-    - field.field.media.scheduler_api_media_test.field_approved_unpublishing
-    - field.field.media.scheduler_api_media_test.field_media_image_api
-    - image.style.thumbnail
-    - media.type.scheduler_api_media_test
-  module:
-    - image
-    - path
-    - scheduler
-id: media.scheduler_api_media_test.default
-targetEntityType: media
-bundle: scheduler_api_media_test
-mode: default
-content:
-  created:
-    type: datetime_timestamp
-    weight: 8
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  field_approved_publishing:
-    weight: 2
-    settings:
-      display_label: true
-    third_party_settings: {  }
-    type: boolean_checkbox
-    region: content
-  field_approved_unpublishing:
-    weight: 3
-    settings:
-      display_label: true
-    third_party_settings: {  }
-    type: boolean_checkbox
-    region: content
-  field_media_image_api:
-    weight: 1
-    settings:
-      progress_indicator: throbber
-      preview_image_style: thumbnail
-    third_party_settings: {  }
-    type: image_image
-    region: content
-  name:
-    type: string_textfield
-    weight: 0
-    region: content
-    settings:
-      size: 60
-      placeholder: ''
-    third_party_settings: {  }
-  path:
-    type: path
-    weight: 9
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  publish_on:
-    type: datetime_timestamp_no_default
-    weight: 5
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  scheduler_settings:
-    weight: 4
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  status:
-    type: boolean_checkbox
-    settings:
-      display_label: true
-    weight: 10
-    region: content
-    third_party_settings: {  }
-  uid:
-    type: entity_reference_autocomplete
-    weight: 7
-    settings:
-      match_operator: CONTAINS
-      size: 60
-      placeholder: ''
-      match_limit: 10
-    region: content
-    third_party_settings: {  }
-  unpublish_on:
-    type: datetime_timestamp_no_default
-    weight: 6
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-hidden: {  }
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.node.scheduler_api_node_test.default.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.node.scheduler_api_test.default.yml
similarity index 66%
rename from web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.node.scheduler_api_node_test.default.yml
rename to web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.node.scheduler_api_test.default.yml
index 9f2db1471c1e951359db9a7f54a460b8efefee21..e4bb485a91da4e1100fd21119f7ff30e7a8e6216 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.node.scheduler_api_node_test.default.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_form_display.node.scheduler_api_test.default.yml
@@ -2,93 +2,76 @@ langcode: en
 status: true
 dependencies:
   config:
-    - field.field.node.scheduler_api_node_test.field_approved_publishing
-    - field.field.node.scheduler_api_node_test.field_approved_unpublishing
-    - node.type.scheduler_api_node_test
+    - field.field.node.scheduler_api_test.field_approved_publishing
+    - field.field.node.scheduler_api_test.field_approved_unpublishing
+    - node.type.scheduler_api_test
   module:
     - path
     - scheduler
-id: node.scheduler_api_node_test.default
+id: node.scheduler_api_test.default
 targetEntityType: node
-bundle: scheduler_api_node_test
+bundle: scheduler_api_test
 mode: default
 content:
+  title:
+    type: string_textfield
+    weight: 0
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  langcode:
+    type: language_select
+    weight: 1
+    settings: {  }
+    third_party_settings: {  }
+  uid:
+    type: number
+    weight: 2
+    settings: { }
+    third_party_settings: {  }
   created:
     type: datetime_timestamp
-    weight: 7
+    weight: 3
     settings: {  }
     third_party_settings: {  }
-    region: content
-  field_approved_publishing:
+  promote:
     type: boolean_checkbox
-    weight: 1
     settings:
       display_label: true
+    weight: 4
     third_party_settings: {  }
-    region: content
-  field_approved_unpublishing:
+  sticky:
     type: boolean_checkbox
-    weight: 2
     settings:
       display_label: true
+    weight: 5
     third_party_settings: {  }
-    region: content
   path:
     type: path
-    weight: 10
+    weight: 6
     settings: {  }
     third_party_settings: {  }
-    region: content
-  promote:
+  field_approved_publishing:
     type: boolean_checkbox
+    weight: 7
     settings:
       display_label: true
-    weight: 8
     third_party_settings: {  }
-    region: content
-  publish_on:
-    type: datetime_timestamp_no_default
-    weight: 4
-    settings: {  }
-    third_party_settings: {  }
-    region: content
-  scheduler_settings:
-    weight: 3
-    region: content
-    settings: {  }
-    third_party_settings: {  }
-  status:
+  field_approved_unpublishing:
     type: boolean_checkbox
+    weight: 8
     settings:
       display_label: true
-    weight: 11
-    region: content
     third_party_settings: {  }
-  sticky:
-    type: boolean_checkbox
-    settings:
-      display_label: true
+  publish_on:
+    type: datetime_timestamp_no_default
     weight: 9
-    third_party_settings: {  }
-    region: content
-  title:
-    type: string_textfield
-    weight: 0
-    settings:
-      size: 60
-      placeholder: ''
-    third_party_settings: {  }
-    region: content
-  uid:
-    type: options_select
-    weight: 6
     settings: {  }
     third_party_settings: {  }
-    region: content
   unpublish_on:
     type: datetime_timestamp_no_default
-    weight: 5
+    weight: 10
     settings: {  }
     third_party_settings: {  }
-    region: content
 hidden: {  }
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.commerce_product.scheduler_api_product_test.default.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.commerce_product.scheduler_api_product_test.default.yml
deleted file mode 100644
index 2fe808d7e44d0c992364a4b3c7a4393e4bebf793..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.commerce_product.scheduler_api_product_test.default.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-uuid: a46cedee-917e-4576-9b67-ecbd2fc01501
-langcode: en
-status: true
-dependencies:
-  config:
-    - commerce_product.commerce_product_type.scheduler_api_product_test
-    - field.field.commerce_product.scheduler_api_product_test.field_approved_publishing
-    - field.field.commerce_product.scheduler_api_product_test.field_approved_unpublishing
-id: commerce_product.scheduler_api_product_test.default
-targetEntityType: commerce_product
-bundle: scheduler_api_product_test
-mode: default
-content:
-  field_approved_publishing:
-    type: boolean
-    weight: 2
-    region: content
-    label: inline
-    settings:
-      format: default
-      format_custom_false: ''
-      format_custom_true: ''
-    third_party_settings: {  }
-  field_approved_unpublishing:
-    type: boolean
-    weight: 3
-    region: content
-    label: inline
-    settings:
-      format: default
-      format_custom_false: ''
-      format_custom_true: ''
-    third_party_settings: {  }
-  title:
-    label: hidden
-    type: string
-    weight: 0
-    region: content
-    settings:
-      link_to_entity: false
-    third_party_settings: {  }
-  variations:
-    type: entity_reference_entity_view
-    weight: 1
-    region: content
-    label: above
-    settings:
-      view_mode: default
-      link: false
-    third_party_settings: {  }
-hidden:
-  created: true
-  stores: true
-  uid: true
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.media.scheduler_api_media_test.default.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.media.scheduler_api_media_test.default.yml
deleted file mode 100644
index 00e691f8cc63211ed4a897d0c4e1783e578092b5..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.media.scheduler_api_media_test.default.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - field.field.media.scheduler_api_media_test.field_approved_publishing
-    - field.field.media.scheduler_api_media_test.field_approved_unpublishing
-    - field.field.media.scheduler_api_media_test.field_media_image_api
-    - image.style.large
-    - media.type.scheduler_api_media_test
-  module:
-    - image
-id: media.scheduler_api_media_test.default
-targetEntityType: media
-bundle: scheduler_api_media_test
-mode: default
-content:
-  field_approved_publishing:
-    weight: 2
-    label: inline
-    settings:
-      format: default
-      format_custom_false: ''
-      format_custom_true: ''
-    third_party_settings: {  }
-    type: boolean
-    region: content
-  field_approved_unpublishing:
-    weight: 3
-    label: inline
-    settings:
-      format: default
-      format_custom_false: ''
-      format_custom_true: ''
-    third_party_settings: {  }
-    type: boolean
-    region: content
-  field_media_image_api:
-    label: visually_hidden
-    weight: 1
-    settings:
-      image_style: large
-      image_link: ''
-    third_party_settings: {  }
-    type: image
-    region: content
-  name:
-    type: string
-    weight: 0
-    region: content
-    label: above
-    settings:
-      link_to_entity: false
-    third_party_settings: {  }
-hidden:
-  created: true
-  thumbnail: true
-  uid: true
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.node.scheduler_api_node_test.default.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.node.scheduler_api_test.default.yml
similarity index 62%
rename from web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.node.scheduler_api_node_test.default.yml
rename to web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.node.scheduler_api_test.default.yml
index f502c32bb5310aa4098a57ff809972fbd5c261ff..34011b144f4d1d0ad006d0c09f25f80fe87b52f3 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.node.scheduler_api_node_test.default.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/core.entity_view_display.node.scheduler_api_test.default.yml
@@ -2,14 +2,14 @@ langcode: en
 status: true
 dependencies:
   config:
-    - field.field.node.scheduler_api_node_test.field_approved_publishing
-    - field.field.node.scheduler_api_node_test.field_approved_unpublishing
-    - node.type.scheduler_api_node_test
+    - field.field.node.scheduler_api_test.field_approved_publishing
+    - field.field.node.scheduler_api_test.field_approved_unpublishing
+    - node.type.scheduler_api_test
   module:
     - user
-id: node.scheduler_api_node_test.default
+id: node.scheduler_api_test.default
 targetEntityType: node
-bundle: scheduler_api_node_test
+bundle: scheduler_api_test
 mode: default
 content:
   field_approved_publishing:
@@ -21,20 +21,18 @@ content:
       format_custom_false: ''
       format_custom_true: ''
     third_party_settings: {  }
-    region: content
   field_approved_unpublishing:
     type: boolean
-    weight: 2
+    weight: 1
     label: inline
     settings:
       format: default
       format_custom_false: ''
       format_custom_true: ''
     third_party_settings: {  }
-    region: content
   links:
     weight: 0
-    region: content
     settings: {  }
     third_party_settings: {  }
-hidden: {  }
+hidden:
+  langcode: true
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.commerce_product.scheduler_api_product_test.field_approved_publishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.commerce_product.scheduler_api_product_test.field_approved_publishing.yml
deleted file mode 100644
index 02d5f1a02e759d40c38bfcbf37efdfe98c297904..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.commerce_product.scheduler_api_product_test.field_approved_publishing.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - commerce_product.commerce_product_type.scheduler_api_product_test
-    - field.storage.commerce_product.field_approved_publishing
-id: commerce_product.scheduler_api_product_test.field_approved_publishing
-field_name: field_approved_publishing
-entity_type: commerce_product
-bundle: scheduler_api_product_test
-label: 'Product Approved for Publishing'
-description: ''
-required: false
-translatable: false
-default_value:
-  -
-    value: 0
-default_value_callback: ''
-settings:
-  on_label: 'Yes'
-  off_label: 'No'
-field_type: boolean
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.commerce_product.scheduler_api_product_test.field_approved_unpublishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.commerce_product.scheduler_api_product_test.field_approved_unpublishing.yml
deleted file mode 100644
index ef0dca2c54cccd936892ab6abf0f356ee415c18e..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.commerce_product.scheduler_api_product_test.field_approved_unpublishing.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - commerce_product.commerce_product_type.scheduler_api_product_test
-    - field.storage.commerce_product.field_approved_unpublishing
-id: commerce_product.scheduler_api_product_test.field_approved_unpublishing
-field_name: field_approved_unpublishing
-entity_type: commerce_product
-bundle: scheduler_api_product_test
-label: 'Product Approved for Unpublishing'
-description: ''
-required: false
-translatable: false
-default_value:
-  -
-    value: 0
-default_value_callback: ''
-settings:
-  on_label: 'Yes'
-  off_label: 'No'
-field_type: boolean
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_approved_publishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_approved_publishing.yml
deleted file mode 100644
index 6acad3e7c985fda694eb2d5c15043148ad100ec4..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_approved_publishing.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - field.storage.media.field_approved_publishing
-    - media.type.scheduler_api_media_test
-id: media.scheduler_api_media_test.field_approved_publishing
-field_name: field_approved_publishing
-entity_type: media
-bundle: scheduler_api_media_test
-label: 'Media Approved for Publishing'
-description: ''
-required: false
-translatable: false
-default_value:
-  -
-    value: 0
-default_value_callback: ''
-settings:
-  on_label: 'Yes'
-  off_label: 'No'
-field_type: boolean
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_approved_unpublishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_approved_unpublishing.yml
deleted file mode 100644
index ea52fe4912b35f50e9d4e788e1563e7f01e727cd..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_approved_unpublishing.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - field.storage.media.field_approved_unpublishing
-    - media.type.scheduler_api_media_test
-id: media.scheduler_api_media_test.field_approved_unpublishing
-field_name: field_approved_unpublishing
-entity_type: media
-bundle: scheduler_api_media_test
-label: 'Media Approved for Unpublishing'
-description: ''
-required: false
-translatable: false
-default_value:
-  -
-    value: 0
-default_value_callback: ''
-settings:
-  on_label: 'Yes'
-  off_label: 'No'
-field_type: boolean
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_media_image_api.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_media_image_api.yml
deleted file mode 100644
index 8d59c3cab210ebd2f2d42a85b43b7f71623b9d8e..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.media.scheduler_api_media_test.field_media_image_api.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - field.storage.media.field_media_image_api
-    - media.type.scheduler_api_media_test
-  module:
-    - image
-id: media.scheduler_api_media_test.field_media_image_api
-field_name: field_media_image_api
-entity_type: media
-bundle: scheduler_api_media_test
-label: 'Image for API test'
-description: ''
-required: false
-translatable: true
-default_value: {  }
-default_value_callback: ''
-settings:
-  file_directory: '[date:custom:Y]-[date:custom:m]'
-  file_extensions: 'png gif jpg jpeg'
-  max_filesize: ''
-  max_resolution: ''
-  min_resolution: ''
-  alt_field: true
-  alt_field_required: false
-  title_field: false
-  title_field_required: false
-  default_image:
-    uuid: ''
-    alt: ''
-    title: ''
-    width: null
-    height: null
-  handler: 'default:file'
-  handler_settings: {  }
-field_type: image
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_node_test.field_approved_publishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_test.field_approved_publishing.yml
similarity index 76%
rename from web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_node_test.field_approved_publishing.yml
rename to web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_test.field_approved_publishing.yml
index e2b2a852bf8eb44b2d946566c348f9a572dd6261..9d1a167c06261c67de97a69d2a8e77f10c599bed 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_node_test.field_approved_publishing.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_test.field_approved_publishing.yml
@@ -3,14 +3,14 @@ status: true
 dependencies:
   config:
     - field.storage.node.field_approved_publishing
-    - node.type.scheduler_api_node_test
+    - node.type.scheduler_api_test
   enforced:
     module:
       - scheduler_api_test
-id: node.scheduler_api_node_test.field_approved_publishing
+id: node.scheduler_api_test.field_approved_publishing
 field_name: field_approved_publishing
 entity_type: node
-bundle: scheduler_api_node_test
+bundle: scheduler_api_test
 label: 'Approved for Publishing'
 description: ''
 required: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_node_test.field_approved_unpublishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_test.field_approved_unpublishing.yml
similarity index 76%
rename from web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_node_test.field_approved_unpublishing.yml
rename to web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_test.field_approved_unpublishing.yml
index c8d837af7c3cf95f6eb2876d6a656f6ff6eda5ef..1902663667955d163cda769be3b66b57fd61ea06 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_node_test.field_approved_unpublishing.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.field.node.scheduler_api_test.field_approved_unpublishing.yml
@@ -3,14 +3,14 @@ status: true
 dependencies:
   config:
     - field.storage.node.field_approved_unpublishing
-    - node.type.scheduler_api_node_test
+    - node.type.scheduler_api_test
   enforced:
     module:
       - scheduler_api_test
-id: node.scheduler_api_node_test.field_approved_unpublishing
+id: node.scheduler_api_test.field_approved_unpublishing
 field_name: field_approved_unpublishing
 entity_type: node
-bundle: scheduler_api_node_test
+bundle: scheduler_api_test
 label: 'Approved for Unpublishing'
 description: ''
 required: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.commerce_product.field_approved_publishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.commerce_product.field_approved_publishing.yml
deleted file mode 100644
index 8221204b3a2c37520ef87eb515e3c5befac5d636..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.commerce_product.field_approved_publishing.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - commerce_product
-  enforced:
-    module:
-      - scheduler_api_test
-id: commerce_product.field_approved_publishing
-field_name: field_approved_publishing
-entity_type: commerce_product
-type: boolean
-settings: {  }
-module: core
-locked: false
-cardinality: 1
-translatable: true
-indexes: {  }
-persist_with_no_fields: false
-custom_storage: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.commerce_product.field_approved_unpublishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.commerce_product.field_approved_unpublishing.yml
deleted file mode 100644
index 477fc606c9339221120e5bd44b96904807ea9392..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.commerce_product.field_approved_unpublishing.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - commerce_product
-  enforced:
-    module:
-      - scheduler_api_test
-id: commerce_product.field_approved_unpublishing
-field_name: field_approved_unpublishing
-entity_type: commerce_product
-type: boolean
-settings: {  }
-module: core
-locked: false
-cardinality: 1
-translatable: true
-indexes: {  }
-persist_with_no_fields: false
-custom_storage: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_approved_publishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_approved_publishing.yml
deleted file mode 100644
index 478e5595b753eebdb5fea7deecdf4c63afeebee3..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_approved_publishing.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - media
-  enforced:
-    module:
-      - scheduler_api_test
-id: media.field_approved_publishing
-field_name: field_approved_publishing
-entity_type: media
-type: boolean
-settings: {  }
-module: core
-locked: false
-cardinality: 1
-translatable: true
-indexes: {  }
-persist_with_no_fields: false
-custom_storage: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_approved_unpublishing.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_approved_unpublishing.yml
deleted file mode 100644
index 5b054d31da10eeff1aed77bfecbfe939dd4f404e..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_approved_unpublishing.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - media
-  enforced:
-    module:
-      - scheduler_api_test
-id: media.field_approved_unpublishing
-field_name: field_approved_unpublishing
-entity_type: media
-type: boolean
-settings: {  }
-module: core
-locked: false
-cardinality: 1
-translatable: true
-indexes: {  }
-persist_with_no_fields: false
-custom_storage: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_media_image_api.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_media_image_api.yml
deleted file mode 100644
index 28daadc5866e8d7b048b7383b739aa5b06b9648f..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/field.storage.media.field_media_image_api.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - file
-    - image
-    - media
-  enforced:
-    module:
-      - scheduler_api_test
-id: media.field_media_image_api
-field_name: field_media_image_api
-entity_type: media
-type: image
-settings:
-  default_image:
-    uuid: null
-    alt: ''
-    title: ''
-    width: null
-    height: null
-  target_type: file
-  display_field: false
-  display_default: false
-  uri_scheme: public
-module: image
-locked: false
-cardinality: 1
-translatable: true
-indexes: {  }
-persist_with_no_fields: false
-custom_storage: false
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/media.type.scheduler_api_media_test.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/media.type.scheduler_api_media_test.yml
deleted file mode 100644
index a451b7a300a0b7d1a77b625b41397e8e274e2084..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/media.type.scheduler_api_media_test.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - scheduler
-  enforced:
-    module:
-      - scheduler_api_test
-third_party_settings:
-  scheduler:
-    expand_fieldset: always
-    fields_display_mode: fieldset
-    publish_enable: true
-    publish_past_date: schedule
-    publish_past_date_created: false
-    publish_required: false
-    publish_revision: false
-    publish_touch: false
-    show_message_after_update: true
-    unpublish_enable: true
-    unpublish_required: false
-    unpublish_revision: false
-id: scheduler_api_media_test
-label: 'Scheduler API Media Test'
-description: 'Image media type used in Scheduler API testing'
-source: image
-queue_thumbnail_downloads: false
-new_revision: false
-source_configuration:
-  source_field: field_media_image_api
-field_map:
-  name: name
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/node.type.scheduler_api_node_test.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/node.type.scheduler_api_test.yml
similarity index 71%
rename from web/modules/scheduler/tests/modules/scheduler_api_test/config/install/node.type.scheduler_api_node_test.yml
rename to web/modules/scheduler/tests/modules/scheduler_api_test/config/install/node.type.scheduler_api_test.yml
index 28d667c7c1ab5c4da9d2212d5e6c381a0d27f766..4e75ce1d9ce939f7184002ebf6e6d70ef41820ac 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/node.type.scheduler_api_node_test.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/config/install/node.type.scheduler_api_test.yml
@@ -13,21 +13,19 @@ third_party_settings:
       - main
     parent: 'main:'
   scheduler:
-    expand_fieldset: always
+    expand_fieldset: when_required
     fields_display_mode: fieldset
     publish_enable: true
     publish_past_date: schedule
-    publish_past_date_created: false
     publish_required: false
     publish_revision: false
     publish_touch: false
-    show_message_after_update: true
     unpublish_enable: true
     unpublish_required: false
     unpublish_revision: false
-type: scheduler_api_node_test
-name: 'Scheduler API Node Test'
-description: 'Node content type used in Scheduler API testing'
+name: 'Scheduler API test type'
+type: scheduler_api_test
+description: 'Used for Scheduler API testing'
 help: ''
 new_revision: false
 preview_mode: 1
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.info.yml
deleted file mode 100644
index 603c3864137f57b34712c53ddec13facce8afbf4..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.info.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: 'Scheduler API Legacy Test'
-type: module
-description: 'Sub-module containing the legacy hook implementations.'
-package: Testing
-dependencies:
-  - scheduler:scheduler
-  - scheduler_api_test:scheduler_api_test
-
-# Information added by Drupal.org packaging script on 2022-11-20
-version: '2.0.0-rc8'
-project: 'scheduler'
-datestamp: 1668951020
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.module b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.module
deleted file mode 100644
index acf7a0c66ec30b8b4aeb5064157e835ef677259f..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_legacy_test/scheduler_api_legacy_test.module
+++ /dev/null
@@ -1,197 +0,0 @@
-<?php
-
-/**
- * @file
- * Legacy hook implementations for the Scheduler API Test module.
- *
- * Scheduler provides eight hook functions. As part of the enhancements to cater
- * for non-node entities the hook functions had to be renamed to allow generic
- * and specific variants and to maintain predictable expansion for any future
- * entity type. For backwards-compatibility the original hook function names are
- * maintined for node entities only, and this file provides test coverage.
- */
-
-use Drupal\node\NodeInterface;
-
-/**
- * Implements hook_scheduler_nid_list().
- */
-function scheduler_api_legacy_test_scheduler_nid_list($action) {
-  $nids = [];
-  $request_time = \Drupal::time()->getRequestTime();
-  // Check to see what test nodes exist.
-  $results = _scheduler_api_test_get_entities('node');
-  foreach ($results as $nid => $node) {
-    // If publishing and this is the publish test node, set a date and add
-    // the node id to the list.
-    if ($action == 'publish' && $node->title->value == 'API TEST nid_list publish me') {
-      $node->set('publish_on', $request_time)->save();
-      $nids[] = $nid;
-    }
-    // If unpublishing and this is the unpublish test node, set a date and add
-    // the node id to the list.
-    if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list unpublish me') {
-      $node->set('unpublish_on', $request_time)->save();
-      $nids[] = $nid;
-    }
-  }
-  return $nids;
-}
-
-/**
- * Implements hook_scheduler_nid_list_alter().
- */
-function scheduler_api_legacy_test_scheduler_nid_list_alter(&$nids, $action) {
-  $request_time = \Drupal::time()->getRequestTime();
-  $results = _scheduler_api_test_get_entities('node');
-  foreach ($results as $nid => $node) {
-    if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter do not publish me') {
-      // Remove the node id.
-      $nids = array_diff($nids, [$nid]);
-    }
-    if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter publish me') {
-      // Set a publish_on date and add the node id.
-      $node->set('publish_on', $request_time)->save();
-      $nids[] = $nid;
-    }
-    if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter do not unpublish me') {
-      // Remove the node id.
-      $nids = array_diff($nids, [$nid]);
-    }
-    if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter unpublish me') {
-      // Set an unpublish_on date and add the node id.
-      $node->set('unpublish_on', $request_time)->save();
-      $nids[] = $nid;
-    }
-  }
-}
-
-/**
- * Implements hook_scheduler_allow_publishing().
- */
-function scheduler_api_legacy_test_scheduler_allow_publishing(NodeInterface $node) {
-  // If there is no 'Approved for Publishing' field then allow publishing.
-  if (!isset($node->field_approved_publishing)) {
-    $allowed = TRUE;
-  }
-  else {
-    // Only publish nodes that have 'Approved for Publishing' set.
-    $allowed = $node->field_approved_publishing->value;
-    // If publication is denied then inform the user why.
-    if (!$allowed) {
-      \Drupal::messenger()->addMessage(t('%title is scheduled for publishing, but will not be published until approved.', ['%title' => $node->title->value]), 'status', FALSE);
-      // If the time is in the past it means that the action has been prevented.
-      // Write a dblog message to show this. Give a link to view the node but
-      // cater for no nid as the node may be new and not yet saved.
-      if ($node->publish_on->value <= \Drupal::time()->getRequestTime()) {
-        \Drupal::logger('scheduler_api_test')->warning('Publishing of "%title" is prevented until approved.', [
-          '%title' => $node->title->value,
-          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
-        ]);
-      }
-    }
-  }
-  return $allowed;
-}
-
-/**
- * Implements hook_scheduler_allow_unpublishing().
- */
-function scheduler_api_legacy_test_scheduler_allow_unpublishing(NodeInterface $node) {
-  // If there is no 'Approved for Unpublishing' field then allow unpublishing.
-  if (!isset($node->field_approved_unpublishing)) {
-    $allowed = TRUE;
-  }
-  else {
-    // Only unpublish nodes that have 'Approved for Unpublishing' set.
-    $allowed = $node->field_approved_unpublishing->value;
-    // If unpublication is denied then inform the user why.
-    if (!$allowed) {
-      \Drupal::messenger()->addMessage(t('%title is scheduled for unpublishing, but will not be unpublished until approved.', ['%title' => $node->title->value]), 'status', FALSE);
-      // If the time is in the past it means that the action has been prevented.
-      // Write a dblog message to show this. Give a link to view the node but
-      // cater for no nid as the node may be new and not yet saved.
-      if ($node->unpublish_on->value <= \Drupal::time()->getRequestTime()) {
-        \Drupal::logger('scheduler_api_test')->warning('Unpublishing of "%title" is prevented until approved.', [
-          '%title' => $node->title->value,
-          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
-        ]);
-      }
-    }
-  }
-  return $allowed;
-}
-
-/**
- * Implements hook_scheduler_hide_publish_on_field().
- */
-function scheduler_api_legacy_test_scheduler_hide_publish_on_field($form, $form_state, $node) {
-  // Hide the publish_on field if the node title contains orange or green.
-  $title = $node->title->value ?? '';
-  if (stristr($title, 'orange legacy') || stristr($title, 'green legacy')) {
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The publish_on field is hidden for orange or green node titles.'), 'status', FALSE);
-    return TRUE;
-  }
-  else {
-    return FALSE;
-  }
-}
-
-/**
- * Implements hook_scheduler_hide_unpublish_on_field().
- */
-function scheduler_api_legacy_test_scheduler_hide_unpublish_on_field($form, $form_state, $node) {
-  // Hide the unpublish_on field if the node title contains yellow or green.
-  $title = $node->title->value ?? '';
-  if (stristr($title, 'yellow legacy') || stristr($title, 'green legacy')) {
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The unpublish_on field is hidden for yellow or green node titles.'), 'status', FALSE);
-    return TRUE;
-  }
-  else {
-    return FALSE;
-  }
-}
-
-/**
- * Implements hook_scheduler_publish_action().
- */
-function scheduler_api_legacy_test_scheduler_publish_action($node) {
-  if (stristr($node->title->value, 'red legacy')) {
-    // Nodes with red in the title are simulated to cause a failure and should
-    // then be skipped by Scheduler.
-    $node->set('title', $node->title->value . ' - publishing failed in API test module');
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Red nodes should cause Scheduler to abandon publishing.'), 'status', FALSE);
-    return -1;
-  }
-  elseif (stristr($node->title->value, 'yellow legacy')) {
-    // Nodes with yellow in the title are simulated to be processed by this
-    // hook, and will not be published by Scheduler.
-    $node->set('title', $node->title->value . ' - publishing processed by API test module');
-    $node->setPublished();
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Yellow nodes should not have publishing processed by Scheduler.'), 'status', FALSE);
-    return 1;
-  }
-  return 0;
-}
-
-/**
- * Implements hook_scheduler_unpublish_action().
- */
-function scheduler_api_legacy_test_scheduler_unpublish_action($node) {
-  if (stristr($node->title->value, 'blue legacy')) {
-    // Nodes with blue in the title are simulated to cause a failure and should
-    // then be skipped by Scheduler.
-    $node->set('title', $node->title->value . ' - unpublishing failed in API test module');
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Blue nodes should cause Scheduler to abandon unpublishing.'), 'status', FALSE);
-    return -1;
-  }
-  if (stristr($node->title->value, 'orange legacy')) {
-    // Nodes with orange in the title are simulated to be processed by this
-    // hook, and will not be published by Scheduler.
-    $node->set('title', $node->title->value . ' - unpublishing processed by API test module');
-    $node->setUnpublished();
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Orange nodes should not have unpublishing processed by Scheduler.'), 'status', FALSE);
-    return 1;
-  }
-  return 0;
-}
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
index 5972cfe9f0e31a744394c5a8e6697f97b21845e9..7971a6e01c83cf39f3edcd7c70f80ca7bd9b44d1 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
@@ -2,11 +2,12 @@ name: 'Scheduler API Test'
 type: module
 description: 'Support module for Scheduler API-related testing.'
 package: Testing
+core: 8.x
+core_version_requirement: ^8 || ^9
 dependencies:
   - scheduler:scheduler
-  - drupal:media
 
-# Information added by Drupal.org packaging script on 2022-11-20
-version: '2.0.0-rc8'
+# Information added by Drupal.org packaging script on 2021-07-19
+version: '8.x-1.4'
 project: 'scheduler'
-datestamp: 1668951020
+datestamp: 1626701769
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install
index 3dd61222b7ae797ee2beec085af91aa93ffe4d4d..0c2c7b45c6155ec36671331a2b4bb58e05b138c7 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install
@@ -12,33 +12,25 @@
  * module config and content on uninstalling. Plus, when developing this module
  * and enabling it manually as a real module, this code is needed to clean up,
  * otherwise a re-install fails.
- *
- * The entity types, custom fields and storage are deleted automatically by
- * having 'enforced: module: - scheduler_api_test' in the config/install/*.yml
- * files. However, we have to delete the actual entity content here.
  */
 function scheduler_api_test_uninstall() {
 
-  // Delete all content for the custom api types.
-  $api_data = [
-    'node' => ['db_id' => 'nid', 'type' => 'scheduler_api_node_test', 'bundle_field' => 'type'],
-    'media' => ['db_id' => 'mid', 'type' => 'scheduler_api_media_test', 'bundle_field' => 'bundle'],
-    'commerce_product' => ['db_id' => 'product_id', 'type' => 'scheduler_api_product_test', 'bundle_field' => 'type'],
-  ];
-  // @todo Re-write this using proper entity queries not database select().
-  foreach ($api_data as $entityTypeId => $values) {
-    $ids_query = \Drupal::database()->select($entityTypeId, 'a')
-      ->fields('a', ["{$values['db_id']}"])
-      ->condition($values['bundle_field'], ["{$values['type']}"], 'IN')
-      ->execute();
-    if ($ids = $ids_query->fetchCol()) {
-      $storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
-      $entities = $storage->loadMultiple($ids);
-      $entity = reset($entities);
-      $storage->delete($entities);
-      \Drupal::messenger()->addMessage(\Drupal::translation()->formatPlural(count($ids), '1 %bundle has been deleted.', '@count %bundle have been deleted.', [
-        '%bundle' => $entity->{$values['bundle_field']}->entity->label(),
-      ]));
-    }
+  // Delete any content that may have been created for the custom node type.
+  $nids_query = \Drupal::database()->select('node', 'n')
+    ->fields('n', ['nid'])
+    ->condition('n.type', ['scheduler_api_test'], 'IN')
+    ->execute();
+  if ($nids = $nids_query->fetchCol()) {
+    $storage = \Drupal::entityTypeManager()->getStorage('node');
+    $entities = $storage->loadMultiple($nids);
+    $storage->delete($entities);
+    \Drupal::messenger()->addMessage(t('@number %type node(s) have been deleted.', [
+      '@number' => count($nids),
+      '%type' => 'scheduler_api_test',
+    ]));
   }
+
+  // The content type, custom fields and storage are deleted automatically by
+  // having 'enforced: module: - scheduler_api_test' in the
+  // config/install/*.yml files.
 }
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module
index 874598d3279bb060b60628d842a4134dfa30da8b..30e035ab10dc39b41deff701cc28e6db9a70133a 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module
@@ -3,240 +3,89 @@
 /**
  * @file
  * Hook implementations of the Scheduler API Test module.
- *
- * Scheduler provides eight hook functions. Each has a non-specific version with
- * no _{type}_ in the name, which is invoked for all entity types, and a version
- * with _{type}_ in the name, which is invoked only when that entity types is
- * being processed. Hence for complete test coverage this module has eight plain
- * implementations, and eight implementations for Nodes, Media, Products and
- * Taxonomy Terms.
  */
 
-use Drupal\commerce_product\Entity\Product;
-use Drupal\commerce_product\Entity\ProductInterface;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\media\Entity\Media;
-use Drupal\media\MediaInterface;
 use Drupal\node\Entity\Node;
 use Drupal\node\NodeInterface;
-use Drupal\taxonomy\Entity\Term;
-use Drupal\taxonomy\TermInterface;
 
 /**
- * Helper function to return all entities of a given type.
+ * Implements hook_scheduler_nid_list().
  */
-function _scheduler_api_test_get_entities($entityTypeId) {
-  switch ($entityTypeId) {
-    case 'node':
-      $results = Node::loadMultiple(\Drupal::entityQuery('node')->accessCheck(FALSE)->execute());
-      break;
+function scheduler_api_test_scheduler_nid_list($action) {
+  $nids = [];
 
-    case 'media':
-      $results = Media::loadMultiple(\Drupal::entityQuery('media')->accessCheck(FALSE)->execute());
-      break;
-
-    case 'commerce_product':
-      $results = Product::loadMultiple(\Drupal::entityQuery('commerce_product')->accessCheck(FALSE)->execute());
-      break;
-
-    case 'taxonomy_term':
-      $results = Term::loadMultiple(\Drupal::entityQuery('taxonomy_term')->accessCheck(FALSE)->execute());
-      break;
-
-    default:
-      throw new \Exception("Entity type id '{$entityTypeId}' is unrecognised in _scheduler_api_test_get_entities().");
-  }
-  return $results;
-}
-
-/**
- * Implements hook_scheduler_list().
- */
-function scheduler_api_test_scheduler_list($process, $entityTypeId) {
-  $ids = [];
-  $request_time = \Drupal::time()->getRequestTime();
-  $results = _scheduler_api_test_get_entities($entityTypeId);
-  foreach ($results as $id => $entity) {
-    // If publishing and this is the 'publish me' test entity, set the date and
-    // add the id to the list.
-    if ($process == 'publish' && !$entity->isPublished() && $entity->label() == "Pink $entityTypeId list publish me") {
-      $entity->set('publish_on', $request_time)->save();
-      $ids[] = $id;
-    }
-    // If unpublishing and this is the 'unpublish me' test entity, set the date
-    // and add the id to the list.
-    if ($process == 'unpublish' && $entity->isPublished() && $entity->label() == "Pink $entityTypeId list unpublish me") {
-      $entity->set('unpublish_on', $request_time)->save();
-      $ids[] = $id;
-    }
-  }
-  return $ids;
-}
-
-/**
- * Implements hook_scheduler_node_list().
- */
-function scheduler_api_test_scheduler_node_list($process, $entityTypeId) {
-  $ids = [];
-  $request_time = \Drupal::time()->getRequestTime();
-  $results = _scheduler_api_test_get_entities($entityTypeId);
-  foreach ($results as $id => $entity) {
-    // If publishing and this is the 'publish me' test entity, set the date and
-    // add the id to the list.
-    if ($process == 'publish' && !$entity->isPublished() && $entity->label() == "Purple $entityTypeId list publish me") {
-      $entity->set('publish_on', $request_time)->save();
-      $ids[] = $id;
-    }
-    // If unpublishing and this is the 'unpublish me' test entity, set the date
-    // and add the id to the list.
-    if ($process == 'unpublish' && $entity->isPublished() && $entity->label() == "Purple $entityTypeId list unpublish me") {
-      $entity->set('unpublish_on', $request_time)->save();
-      $ids[] = $id;
-    }
-  }
-  return $ids;
-}
-
-/**
- * Implements hook_scheduler_media_list().
- */
-function scheduler_api_test_scheduler_media_list($process, $entityTypeId) {
-  // This hook does exactly the same as the node version, so re-use that.
-  return scheduler_api_test_scheduler_node_list($process, $entityTypeId);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_list().
- */
-function scheduler_api_test_scheduler_commerce_product_list($process, $entityTypeId) {
-  // This hook does exactly the same as the node version, so re-use that.
-  return scheduler_api_test_scheduler_node_list($process, $entityTypeId);
-}
-
-/**
- * Implements hook_scheduler_taxonomy_term_list().
- */
-function scheduler_api_test_scheduler_taxonomy_term_list($process, $entityTypeId) {
-  // This hook does exactly the same as the node version, so re-use that.
-  return scheduler_api_test_scheduler_node_list($process, $entityTypeId);
-}
-
-/**
- * Implements hook_scheduler_list_alter().
- */
-function scheduler_api_test_scheduler_list_alter(&$ids, $process, $entityTypeId) {
+  // Check to see what test nodes exist.
+  $query = \Drupal::entityQuery('node');
+  $nodes = Node::loadMultiple($query->execute());
   $request_time = \Drupal::time()->getRequestTime();
-  $results = _scheduler_api_test_get_entities($entityTypeId);
-  foreach ($results as $id => $entity) {
-    if ($process == 'publish' && $entity->label() == "Pink $entityTypeId list_alter do not publish me") {
-      // Remove the id.
-      $ids = array_diff($ids, [$id]);
+  foreach ($nodes as $nid => $node) {
+    // If publishing and this is the publish test node, set a date and add
+    // the node id to the list.
+    if ($action == 'publish' && $node->title->value == 'API TEST nid_list publish me') {
+      $node->set('publish_on', $request_time)->save();
+      $nids[] = $nid;
     }
-    if ($process == 'publish' && $entity->label() == "Pink $entityTypeId list_alter publish me") {
-      // Set a publish_on date and add the id.
-      $entity->set('publish_on', $request_time)->save();
-      $ids[] = $id;
-    }
-    if ($process == 'unpublish' && $entity->label() == "Pink $entityTypeId list_alter do not unpublish me") {
-      // Remove the id.
-      $ids = array_diff($ids, [$id]);
-    }
-    if ($process == 'unpublish' && $entity->label() == "Pink $entityTypeId list_alter unpublish me") {
-      // Set an unpublish_on date and add the id.
-      $entity->set('unpublish_on', $request_time)->save();
-      $ids[] = $id;
+    // If unpublishing and this is the unpublish test node, set a date and add
+    // the node id to the list.
+    if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list unpublish me') {
+      $node->set('unpublish_on', $request_time)->save();
+      $nids[] = $nid;
     }
   }
+  return $nids;
 }
 
 /**
- * Implements hook_scheduler_node_list_alter().
+ * Implements hook_scheduler_nid_list_alter().
  */
-function scheduler_api_test_scheduler_node_list_alter(&$ids, $process, $entityTypeId) {
+function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
+  $query = \Drupal::entityQuery('node');
+  $nodes = Node::loadMultiple($query->execute());
   $request_time = \Drupal::time()->getRequestTime();
-  $results = _scheduler_api_test_get_entities($entityTypeId);
-  foreach ($results as $id => $entity) {
-    if ($process == 'publish' && $entity->label() == "Purple $entityTypeId list_alter do not publish me") {
-      // Remove the id.
-      $ids = array_diff($ids, [$id]);
+  foreach ($nodes as $nid => $node) {
+    if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter do not publish me') {
+      // Remove the node id.
+      $nids = array_diff($nids, [$nid]);
     }
-    if ($process == 'publish' && $entity->label() == "Purple $entityTypeId list_alter publish me") {
-      // Set a publish_on date and add the id.
-      $entity->set('publish_on', $request_time)->save();
-      $ids[] = $id;
+    if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter publish me') {
+      // Set a publish_on date and add the node id.
+      $node->set('publish_on', $request_time)->save();
+      $nids[] = $nid;
     }
-    if ($process == 'unpublish' && $entity->label() == "Purple $entityTypeId list_alter do not unpublish me") {
-      // Remove the id.
-      $ids = array_diff($ids, [$id]);
+    if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter do not unpublish me') {
+      // Remove the node id.
+      $nids = array_diff($nids, [$nid]);
     }
-    if ($process == 'unpublish' && $entity->label() == "Purple $entityTypeId list_alter unpublish me") {
-      // Set an unpublish_on date and add the id.
-      $entity->set('unpublish_on', $request_time)->save();
-      $ids[] = $id;
+    if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter unpublish me') {
+      // Set an unpublish_on date and add the node id.
+      $node->set('unpublish_on', $request_time)->save();
+      $nids[] = $nid;
     }
   }
+  return $nids;
 }
 
 /**
- * Implements hook_scheduler_media_list_alter().
+ * Implements hook_scheduler_allow_publishing().
  */
-function scheduler_api_test_scheduler_media_list_alter(&$ids, $process, $entityTypeId) {
-  // This hook does exactly the same as the node version, so re-use that.
-  scheduler_api_test_scheduler_node_list_alter($ids, $process, $entityTypeId);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_list_alter().
- */
-function scheduler_api_test_scheduler_commerce_product_list_alter(&$ids, $process, $entityTypeId) {
-  // This hook does exactly the same as the node version, so re-use that.
-  scheduler_api_test_scheduler_node_list_alter($ids, $process, $entityTypeId);
-}
-
-/**
- * Implements hook_scheduler_taxonomy_term_list_alter().
- */
-function scheduler_api_test_scheduler_taxonomy_term_list_alter(&$ids, $process, $entityTypeId) {
-  // This hook does exactly the same as the node version, so re-use that.
-  scheduler_api_test_scheduler_node_list_alter($ids, $process, $entityTypeId);
-}
-
-/**
- * Implements hook_scheduler_publishing_allowed().
- */
-function scheduler_api_test_scheduler_publishing_allowed(EntityInterface $entity) {
-  // @todo Fill in this function and add test coverage.
-}
-
-/**
- * Generic function to check if the entity is allowed to be published.
- */
-function _scheduler_api_test_publishing_allowed(EntityInterface $entity) {
-  // If there is no 'Approved for Publishing' field or we are not dealing with
-  // an entity designed for this test then allow publishing.
-  if (!isset($entity->field_approved_publishing) || !stristr($entity->label(), "blue {$entity->getEntityTypeId()}")) {
+function scheduler_api_test_scheduler_allow_publishing(NodeInterface $node) {
+  // If there is no 'Approved for Publishing' field then allow publishing.
+  if (!isset($node->field_approved_publishing)) {
     $allowed = TRUE;
   }
   else {
-    // Only publish entities that have 'Approved for Publishing' set.
-    $allowed = $entity->field_approved_publishing->value;
-    // If publishing is denied then inform the user why.
+    // Only publish nodes that have 'Approved for Publishing' set.
+    $allowed = $node->field_approved_publishing->value;
+    // If publication is denied then inform the user why.
     if (!$allowed) {
-      // Show a message when the entity is saved.
-      \Drupal::messenger()->addMessage(t('%title is scheduled for publishing @publish_time, but will not be published until approved.', [
-        '%title' => $entity->label(),
-        '@publish_time' => \Drupal::service('date.formatter')->format($entity->publish_on->value, 'long'),
-      ]), 'status', FALSE);
+      \Drupal::messenger()->addMessage(t('%title is scheduled for publishing, but will not be published until approved.', ['%title' => $node->title->value]), 'status', FALSE);
       // If the time is in the past it means that the action has been prevented.
-      // Write a dblog message to show this. Give a link to view the entity but
-      // cater for no id as the entity may be new and not yet saved.
-      if ($entity->publish_on->value <= \Drupal::time()->getRequestTime()) {
-        if ($entity->id() && $entity->hasLinkTemplate('canonical')) {
-          $link = $entity->toLink(t('View'))->toString();
-        }
+      // Write a dblog message to show this. Give a link to view the node but
+      // cater for no nid as the node may be new and not yet saved.
+      if ($node->publish_on->value <= \Drupal::time()->getRequestTime()) {
         \Drupal::logger('scheduler_api_test')->warning('Publishing of "%title" is prevented until approved.', [
-          '%title' => $entity->label(),
-          'link' => $link ?? NULL,
+          '%title' => $node->title->value,
+          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
         ]);
       }
     }
@@ -245,65 +94,26 @@ function _scheduler_api_test_publishing_allowed(EntityInterface $entity) {
 }
 
 /**
- * Implements hook_scheduler_node_publishing_allowed().
- */
-function scheduler_api_test_scheduler_node_publishing_allowed(NodeInterface $node) {
-  // Use the generic publishing_allowed helper function.
-  return _scheduler_api_test_publishing_allowed($node);
-}
-
-/**
- * Implements hook_scheduler_media_publishing_allowed().
- */
-function scheduler_api_test_scheduler_media_publishing_allowed(MediaInterface $media) {
-  // Use the generic publishing_allowed helper function.
-  return _scheduler_api_test_publishing_allowed($media);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_publishing_allowed().
- */
-function scheduler_api_test_scheduler_commerce_product_publishing_allowed(ProductInterface $product) {
-  // Use the generic publishing_allowed helper function.
-  return _scheduler_api_test_publishing_allowed($product);
-}
-
-/**
- * Implements hook_scheduler_unpublishing_allowed().
+ * Implements hook_scheduler_allow_unpublishing().
  */
-function scheduler_api_test_scheduler_unpublishing_allowed(EntityInterface $entity) {
-  // @todo Fill in this function and add test coverage.
-}
-
-/**
- * Generic function to check if the entity is allowed to be unpublished.
- */
-function _scheduler_api_test_unpublishing_allowed(EntityInterface $entity) {
-  // If there is no 'Approved for Unpublishing' field or we are not dealing with
-  // an entity designed for this test then allow unpublishing.
-  if (!isset($entity->field_approved_unpublishing) || !stristr($entity->label(), "red {$entity->getEntityTypeId()}")) {
+function scheduler_api_test_scheduler_allow_unpublishing(NodeInterface $node) {
+  // If there is no 'Approved for Unpublishing' field then allow unpublishing.
+  if (!isset($node->field_approved_unpublishing)) {
     $allowed = TRUE;
   }
   else {
-    // Only unpublish entities that have 'Approved for Unpublishing' set.
-    $allowed = $entity->field_approved_unpublishing->value;
-    // If unpublishing is denied then inform the user why.
+    // Only unpublish nodes that have 'Approved for Unpublishing' set.
+    $allowed = $node->field_approved_unpublishing->value;
+    // If unpublication is denied then inform the user why.
     if (!$allowed) {
-      // Show a message when the entity is saved.
-      \Drupal::messenger()->addMessage(t('%title is scheduled for unpublishing @unpublish_time, but will not be unpublished until approved.', [
-        '%title' => $entity->label(),
-        '@unpublish_time' => \Drupal::service('date.formatter')->format($entity->unpublish_on->value, 'long'),
-      ]), 'status', FALSE);
+      \Drupal::messenger()->addMessage(t('%title is scheduled for unpublishing, but will not be unpublished until approved.', ['%title' => $node->title->value]), 'status', FALSE);
       // If the time is in the past it means that the action has been prevented.
-      // Write a dblog message to show this. Give a link to view the entity but
-      // cater for no id as the entity may be new and not yet saved.
-      if ($entity->unpublish_on->value <= \Drupal::time()->getRequestTime()) {
-        if ($entity->id() && $entity->hasLinkTemplate('canonical')) {
-          $link = $entity->toLink(t('View'))->toString();
-        }
+      // Write a dblog message to show this. Give a link to view the node but
+      // cater for no nid as the node may be new and not yet saved.
+      if ($node->unpublish_on->value <= \Drupal::time()->getRequestTime()) {
         \Drupal::logger('scheduler_api_test')->warning('Unpublishing of "%title" is prevented until approved.', [
-          '%title' => $entity->label(),
-          'link' => $link ?? NULL,
+          '%title' => $node->title->value,
+          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
         ]);
       }
     }
@@ -312,96 +122,12 @@ function _scheduler_api_test_unpublishing_allowed(EntityInterface $entity) {
 }
 
 /**
- * Implements hook_scheduler_node_unpublishing_allowed().
- */
-function scheduler_api_test_scheduler_node_unpublishing_allowed(NodeInterface $node) {
-  // Use the generic unpublishing_allowed helper function.
-  return _scheduler_api_test_unpublishing_allowed($node);
-}
-
-/**
- * Implements hook_scheduler_media_unpublishing_allowed().
- */
-function scheduler_api_test_scheduler_media_unpublishing_allowed(MediaInterface $media) {
-  // Use the generic unpublishing_allowed helper function.
-  return _scheduler_api_test_unpublishing_allowed($media);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_unpublishing_allowed().
- */
-function scheduler_api_test_scheduler_commerce_product_unpublishing_allowed(ProductInterface $product) {
-  // Use the generic unpublishing_allowed helper function.
-  return _scheduler_api_test_unpublishing_allowed($product);
-}
-
-/**
- * Implements hook_scheduler_hide_publish_date().
- */
-function scheduler_api_test_scheduler_hide_publish_date($form, $form_state, $entity) {
-  // Hide the publish_on field if the title contains 'orange {type}'.
-  if (stristr($entity->label() ?? '', "orange {$entity->getEntityTypeId()}")) {
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The publish_on field is hidden for orange.'), 'status', FALSE);
-    return TRUE;
-  }
-  else {
-    return FALSE;
-  }
-}
-
-/**
- * Generic function to hide the publish_on date field.
- */
-function _scheduler_api_test_hide_publish_date($form, $form_state, $entity) {
-  // Hide the publish_on field if the title contains 'green {type}'.
-  if (stristr($entity->label() ?? '', "green {$entity->getEntityTypeId()}")) {
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The publish_on field is hidden for green.'), 'status', FALSE);
-    return TRUE;
-  }
-  else {
-    return FALSE;
-  }
-}
-
-/**
- * Implements hook_scheduler_node_hide_publish_date().
- */
-function scheduler_api_test_scheduler_node_hide_publish_date($form, $form_state, $entity) {
-  // Use the generic hide_publish_date helper function.
-  return _scheduler_api_test_hide_publish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_media_hide_publish_date().
- */
-function scheduler_api_test_scheduler_media_hide_publish_date($form, $form_state, $entity) {
-  // Use the generic hide_publish_date helper function.
-  return _scheduler_api_test_hide_publish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_hide_publish_date().
- */
-function scheduler_api_test_scheduler_commerce_product_hide_publish_date($form, $form_state, $entity) {
-  // Use the generic hide_publish_date helper function.
-  return _scheduler_api_test_hide_publish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_taxonomy_term_hide_publish_date().
+ * Implements hook_scheduler_hide_publish_on_field().
  */
-function scheduler_api_test_scheduler_taxonomy_term_hide_publish_date($form, $form_state, $entity) {
-  // Use the generic hide_publish_date helper function.
-  return _scheduler_api_test_hide_publish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_hide_unpublish_date().
- */
-function scheduler_api_test_scheduler_hide_unpublish_date($form, $form_state, $entity) {
-  // Hide the unpublish_on field if the title contains 'yellow {type}'.
-  if (stristr($entity->label() ?? '', "yellow {$entity->getEntityTypeId()}")) {
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The unpublish_on field is hidden for yellow.'), 'status', FALSE);
+function scheduler_api_test_scheduler_hide_publish_on_field($form, $form_state, $node) {
+  // Hide the publish_on field if the node title contains orange or green.
+  if (stristr($node->title->value, 'orange') || stristr($node->title->value, 'green')) {
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The publish_on field is hidden for orange or green node titles.'), 'status', FALSE);
     return TRUE;
   }
   else {
@@ -410,12 +136,12 @@ function scheduler_api_test_scheduler_hide_unpublish_date($form, $form_state, $e
 }
 
 /**
- * Generic function to hide the unpublish_on date field.
+ * Implements hook_scheduler_hide_unpublish_on_field().
  */
-function _scheduler_api_test_hide_unpublish_date($form, $form_state, $entity) {
-  // Hide the unpublish_on field if the title contains 'green {type}'.
-  if (stristr($entity->label() ?? '', "green {$entity->getEntityTypeId()}")) {
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The unpublish_on field is hidden for green.'), 'status', FALSE);
+function scheduler_api_test_scheduler_hide_unpublish_on_field($form, $form_state, $node) {
+  // Hide the publish_on field if the node title contains yellow or green.
+  if (stristr($node->title->value, 'yellow') || stristr($node->title->value, 'green')) {
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The unpublish_on field is hidden for yellow or green node titles.'), 'status', FALSE);
     return TRUE;
   }
   else {
@@ -424,159 +150,45 @@ function _scheduler_api_test_hide_unpublish_date($form, $form_state, $entity) {
 }
 
 /**
- * Implements hook_scheduler_node_hide_unpublish_date().
- */
-function scheduler_api_test_scheduler_node_hide_unpublish_date($form, $form_state, $entity) {
-  // Use the generic hide_unpublish_date helper function.
-  return _scheduler_api_test_hide_unpublish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_media_hide_unpublish_date().
+ * Implements hook_scheduler_publish_action().
  */
-function scheduler_api_test_scheduler_media_hide_unpublish_date($form, $form_state, $entity) {
-  // Use the generic hide_unpublish_date helper function.
-  return _scheduler_api_test_hide_unpublish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_hide_unpublish_date().
- */
-function scheduler_api_test_scheduler_commerce_product_hide_unpublish_date($form, $form_state, $entity) {
-  // Use the generic hide_unpublish_date helper function.
-  return _scheduler_api_test_hide_unpublish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_taxonomy_term_hide_unpublish_date().
- */
-function scheduler_api_test_scheduler_taxonomy_term_hide_unpublish_date($form, $form_state, $entity) {
-  // Use the generic hide_unpublish_date helper function.
-  return _scheduler_api_test_hide_unpublish_date($form, $form_state, $entity);
-}
-
-/**
- * Implements hook_scheduler_publish_process().
- */
-function scheduler_api_test_scheduler_publish_process(EntityInterface $entity) {
-  // Any entity with 'red {type}' in the title is simulated to cause a failure
-  // and should then be skipped by Scheduler.
-  if (stristr($entity->label(), "red {$entity->getEntityTypeId()}")) {
-    $label_field = $entity->getEntityType()->getKey('label');
-    $entity->set($label_field, $entity->label() . ' - publishing failed in API test module')->save();
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Red should cause Scheduler to abandon publishing.'), 'status', FALSE);
+function scheduler_api_test_scheduler_publish_action($node) {
+  if (stristr($node->title->value, 'red')) {
+    // Nodes with red in the title are simulated to cause a failure and should
+    // then be skipped by Scheduler.
+    $node->set('title', $node->title->value . ' - publishing failed in API test module');
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Red nodes should cause Scheduler to abandon publishing.'), 'status', FALSE);
     return -1;
   }
-  return 0;
-}
-
-/**
- * Generic function to process third-party publishing.
- */
-function _scheduler_api_test_publish_process(EntityInterface $entity) {
-  // Entities with 'yellow {type}' in the title are simulated to be processed
-  // by this hook, and will not be published by Scheduler.
-  if (stristr($entity->label(), "yellow {$entity->getEntityTypeId()}")) {
-    $label_field = $entity->getEntityType()->getKey('label');
-    $entity->set($label_field, $entity->label() . ' - publishing processed by API test module');
-    $entity->setPublished()->save();
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Yellow should not have publishing processed by Scheduler.'), 'status', FALSE);
+  elseif (stristr($node->title->value, 'yellow')) {
+    // Nodes with yellow in the title are simulated to be processed by this
+    // hook, and will not be published by Scheduler.
+    $node->set('title', $node->title->value . ' - publishing processed by API test module');
+    $node->setPublished();
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Yellow nodes should not have publishing processed by Scheduler.'), 'status', FALSE);
     return 1;
   }
   return 0;
 }
 
 /**
- * Implements hook_scheduler_node_publish_process().
- */
-function scheduler_api_test_scheduler_node_publish_process(NodeInterface $node) {
-  // Use the generic publish_process helper function.
-  return _scheduler_api_test_publish_process($node);
-}
-
-/**
- * Implements hook_scheduler_media_publish_process().
- */
-function scheduler_api_test_scheduler_media_publish_process(MediaInterface $media) {
-  // Use the generic publish_process helper function.
-  return _scheduler_api_test_publish_process($media);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_publish_process().
- */
-function scheduler_api_test_scheduler_commerce_product_publish_process(ProductInterface $product) {
-  // Use the generic publish_process helper function.
-  return _scheduler_api_test_publish_process($product);
-}
-
-/**
- * Implements hook_scheduler_taxonomy_term_publish_process().
- */
-function scheduler_api_test_scheduler_taxonomy_term_publish_process(TermInterface $term) {
-  // Use the generic publish_process helper function.
-  return _scheduler_api_test_publish_process($term);
-}
-
-/**
- * Implements hook_scheduler_unpublish_process().
+ * Implements hook_scheduler_unpublish_action().
  */
-function scheduler_api_test_scheduler_unpublish_process(EntityInterface $entity) {
-  // Any entity with 'blue {type}' in the title is simulated to cause a failure
-  // and should then be skipped by Scheduler.
-  if (stristr($entity->label(), "blue {$entity->getEntityTypeId()}")) {
-    $label_field = $entity->getEntityType()->getKey('label');
-    $entity->set($label_field, $entity->label() . ' - unpublishing failed in API test module')->save();
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Blue should cause Scheduler to abandon unpublishing.'), 'status', FALSE);
+function scheduler_api_test_scheduler_unpublish_action($node) {
+  if (stristr($node->title->value, 'blue')) {
+    // Nodes with blue in the title are simulated to cause a failure and should
+    // then be skipped by Scheduler.
+    $node->set('title', $node->title->value . ' - unpublishing failed in API test module');
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Blue nodes should cause Scheduler to abandon unpublishing.'), 'status', FALSE);
     return -1;
   }
-  return 0;
-}
-
-/**
- * Generic function to process third-party unpublishing.
- */
-function _scheduler_api_test_unpublish_process(EntityInterface $entity) {
-  // Entities with 'orange {type}' in the title are simulated to be processed by
-  // this hook, and will not be unpublished by Scheduler.
-  if (stristr($entity->label(), "orange {$entity->getEntityTypeId()}")) {
-    $label_field = $entity->getEntityType()->getKey('label');
-    $entity->set($label_field, $entity->label() . ' - unpublishing processed by API test module')->save();
-    $entity->setUnpublished()->save();
-    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Orange should not have unpublishing processed by Scheduler.'), 'status', FALSE);
+  if (stristr($node->title->value, 'orange')) {
+    // Nodes with orange in the title are simulated to be processed by this
+    // hook, and will not be published by Scheduler.
+    $node->set('title', $node->title->value . ' - unpublishing processed by API test module');
+    $node->setUnpublished();
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Orange nodes should not have unpublishing processed by Scheduler.'), 'status', FALSE);
     return 1;
   }
   return 0;
 }
-
-/**
- * Implements hook_scheduler_node_unpublish_process().
- */
-function scheduler_api_test_scheduler_node_unpublish_process(NodeInterface $node) {
-  // Use the generic unpublish_process helper function.
-  return _scheduler_api_test_unpublish_process($node);
-}
-
-/**
- * Implements hook_scheduler_media_unpublish_process().
- */
-function scheduler_api_test_scheduler_media_unpublish_process(MediaInterface $media) {
-  // Use the generic unpublish_process helper function.
-  return _scheduler_api_test_unpublish_process($media);
-}
-
-/**
- * Implements hook_scheduler_commerce_product_unpublish_process().
- */
-function scheduler_api_test_scheduler_commerce_product_unpublish_process(ProductInterface $product) {
-  // Use the generic unpublish_process helper function.
-  return _scheduler_api_test_unpublish_process($product);
-}
-
-/**
- * Implements hook_scheduler_taxonomy_term_unpublish_process().
- */
-function scheduler_api_test_scheduler_taxonomy_term_unpublish_process(TermInterface $term) {
-  // Use the generic unpublish_process helper function.
-  return _scheduler_api_test_unpublish_process($term);
-}
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/src/EventSubscriber.php b/web/modules/scheduler/tests/modules/scheduler_api_test/src/EventSubscriber.php
index 1f9101e4241c30e5e3f3a54cb0f471b7ae1fd4bc..4efdc6c63c2f47c16f60a6e26be689d678cfe703 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/src/EventSubscriber.php
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/src/EventSubscriber.php
@@ -2,11 +2,8 @@
 
 namespace Drupal\scheduler_api_test;
 
-use Drupal\scheduler\Event\SchedulerCommerceProductEvents;
-use Drupal\scheduler\Event\SchedulerMediaEvents;
-use Drupal\scheduler\Event\SchedulerNodeEvents;
-use Drupal\scheduler\Event\SchedulerTaxonomyTermEvents;
 use Drupal\scheduler\SchedulerEvent;
+use Drupal\scheduler\SchedulerEvents;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
@@ -15,19 +12,15 @@
  * These events allow modules to react to the Scheduler process being performed.
  * They are all triggered during Scheduler cron processing with the exception of
  * 'pre_publish_immediately' and 'publish_immediately' which are triggered from
- * scheduler_entity_presave().
+ * scheduler_node_presave().
  *
- * The node event tests use the 'sticky' and 'promote' fields as a simple way to
- * check the processing. There are extra conditional checks on isPublished() to
+ * The tests use the standard 'sticky' and 'promote' fields as a simple way to
+ * check the processing. Use extra conditional checks on $node->isPublished() to
  * make the tests stronger so they fail if the calls are in the wrong place.
  *
- * The media tests cannot use 'sticky' and 'promote' as these fields do not
- * exist, so the media name is altered instead. This is also the case with
- * products and taxonomy terms.
- *
  * To allow this API test module to be enabled interactively (for development
  * and testing) we must avoid unwanted side-effects on other non-test nodes.
- * This is done simply by checking that the titles start with 'API TEST'.
+ * This is done simply by checking that the node title starts with 'API TEST'.
  *
  * @group scheduler_api_test
  */
@@ -37,53 +30,23 @@ class EventSubscriber implements EventSubscriberInterface {
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-
-    // Initialise the array to avoid 'variable is undefined' phpcs error.
-    $events = [];
-
     // The values in the arrays give the function names below.
-    // These six events are the originals, dispatched for Nodes.
-    $events[SchedulerNodeEvents::PRE_PUBLISH][] = ['apiTestNodePrePublish'];
-    $events[SchedulerNodeEvents::PUBLISH][] = ['apiTestNodePublish'];
-    $events[SchedulerNodeEvents::PRE_UNPUBLISH][] = ['apiTestNodePreUnpublish'];
-    $events[SchedulerNodeEvents::UNPUBLISH][] = ['apiTestNodeUnpublish'];
-    $events[SchedulerNodeEvents::PRE_PUBLISH_IMMEDIATELY][] = ['apiTestNodePrePublishImmediately'];
-    $events[SchedulerNodeEvents::PUBLISH_IMMEDIATELY][] = ['apiTestNodePublishImmediately'];
-
-    // These six events are dispatched for Media entity types only.
-    $events[SchedulerMediaEvents::PRE_PUBLISH][] = ['apiTestMediaPrePublish'];
-    $events[SchedulerMediaEvents::PUBLISH][] = ['apiTestMediaPublish'];
-    $events[SchedulerMediaEvents::PRE_UNPUBLISH][] = ['apiTestMediaPreUnpublish'];
-    $events[SchedulerMediaEvents::UNPUBLISH][] = ['apiTestMediaUnpublish'];
-    $events[SchedulerMediaEvents::PRE_PUBLISH_IMMEDIATELY][] = ['apiTestMediaPrePublishImmediately'];
-    $events[SchedulerMediaEvents::PUBLISH_IMMEDIATELY][] = ['apiTestMediaPublishImmediately'];
-
-    // These six events are dispatched for Product entity types only.
-    $events[SchedulerCommerceProductEvents::PRE_PUBLISH][] = ['apiTestProductPrePublish'];
-    $events[SchedulerCommerceProductEvents::PUBLISH][] = ['apiTestProductPublish'];
-    $events[SchedulerCommerceProductEvents::PRE_UNPUBLISH][] = ['apiTestProductPreUnpublish'];
-    $events[SchedulerCommerceProductEvents::UNPUBLISH][] = ['apiTestProductUnpublish'];
-    $events[SchedulerCommerceProductEvents::PRE_PUBLISH_IMMEDIATELY][] = ['apiTestProductPrePublishImmediately'];
-    $events[SchedulerCommerceProductEvents::PUBLISH_IMMEDIATELY][] = ['apiTestProductPublishImmediately'];
-
-    // These six events are dispatched for Taxomony Term entity types only.
-    $events[SchedulerTaxonomyTermEvents::PRE_PUBLISH][] = ['apiTestTaxonomyTermPrePublish'];
-    $events[SchedulerTaxonomyTermEvents::PUBLISH][] = ['apiTestTaxonomyTermPublish'];
-    $events[SchedulerTaxonomyTermEvents::PRE_UNPUBLISH][] = ['apiTestTaxonomyTermPreUnpublish'];
-    $events[SchedulerTaxonomyTermEvents::UNPUBLISH][] = ['apiTestTaxonomyTermUnpublish'];
-    $events[SchedulerTaxonomyTermEvents::PRE_PUBLISH_IMMEDIATELY][] = ['apiTestTaxonomyTermPrePublishImmediately'];
-    $events[SchedulerTaxonomyTermEvents::PUBLISH_IMMEDIATELY][] = ['apiTestTaxonomyTermPublishImmediately'];
-
+    $events[SchedulerEvents::PRE_PUBLISH][] = ['apiTestPrePublish'];
+    $events[SchedulerEvents::PUBLISH][] = ['apiTestPublish'];
+    $events[SchedulerEvents::PRE_UNPUBLISH][] = ['apiTestPreUnpublish'];
+    $events[SchedulerEvents::UNPUBLISH][] = ['apiTestUnpublish'];
+    $events[SchedulerEvents::PRE_PUBLISH_IMMEDIATELY][] = ['apiTestPrePublishImmediately'];
+    $events[SchedulerEvents::PUBLISH_IMMEDIATELY][] = ['apiTestPublishImmediately'];
     return $events;
   }
 
   /**
    * Operations to perform before Scheduler publishes a node.
    *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
+   * @param \Drupal\scheduler\SchedulerEvent $event
    *   The scheduler event.
    */
-  public function apiTestNodePrePublish(SchedulerEvent $event) {
+  public function apiTestPrePublish(SchedulerEvent $event) {
     /** @var \Drupal\node\Entity\Node $node */
     $node = $event->getNode();
     // Before publishing a node make it sticky.
@@ -94,65 +57,65 @@ public function apiTestNodePrePublish(SchedulerEvent $event) {
   }
 
   /**
-   * Operations to perform after Scheduler publishes a node.
+   * Operations to perform before Scheduler unpublishes a node.
    *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
+   * @param \Drupal\scheduler\SchedulerEvent $event
    *   The scheduler event.
    */
-  public function apiTestNodePublish(SchedulerEvent $event) {
+  public function apiTestPreUnpublish(SchedulerEvent $event) {
     /** @var \Drupal\node\Entity\Node $node */
     $node = $event->getNode();
-    // After publishing a node promote it to the front page.
     if ($node->isPublished() && strpos($node->title->value, 'API TEST') === 0) {
-      $node->setPromoted(TRUE)->save();
+      // Before unpublishing a node make it unsticky.
+      $node->setSticky(FALSE);
       $event->setNode($node);
     }
   }
 
   /**
-   * Operations to perform before Scheduler unpublishes a node.
+   * Operations before Scheduler publishes a node immediately not via cron.
    *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
+   * @param \Drupal\scheduler\SchedulerEvent $event
    *   The scheduler event.
    */
-  public function apiTestNodePreUnpublish(SchedulerEvent $event) {
+  public function apiTestPrePublishImmediately(SchedulerEvent $event) {
     /** @var \Drupal\node\Entity\Node $node */
     $node = $event->getNode();
-    // Before unpublishing a node make it unsticky.
-    if ($node->isPublished() && strpos($node->title->value, 'API TEST') === 0) {
-      $node->setSticky(FALSE);
+    // Before publishing immediately set the node to sticky.
+    if (!$node->isPromoted() && strpos($node->title->value, 'API TEST') === 0) {
+      $node->setSticky(TRUE);
       $event->setNode($node);
     }
   }
 
   /**
-   * Operations to perform after Scheduler unpublishes a node.
+   * Operations to perform after Scheduler publishes a node.
    *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
+   * @param \Drupal\scheduler\SchedulerEvent $event
    *   The scheduler event.
    */
-  public function apiTestNodeUnpublish(SchedulerEvent $event) {
+  public function apiTestPublish(SchedulerEvent $event) {
     /** @var \Drupal\node\Entity\Node $node */
     $node = $event->getNode();
-    // After unpublishing a node remove it from the front page.
-    if (!$node->isPublished() && strpos($node->title->value, 'API TEST') === 0) {
-      $node->setPromoted(FALSE)->save();
+    // After publishing a node promote it to the front page.
+    if ($node->isPublished() && strpos($node->title->value, 'API TEST') === 0) {
+      $node->setPromoted(TRUE);
       $event->setNode($node);
     }
   }
 
   /**
-   * Operations before Scheduler publishes a node immediately not via cron.
+   * Operations to perform after Scheduler unpublishes a node.
    *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
+   * @param \Drupal\scheduler\SchedulerEvent $event
    *   The scheduler event.
    */
-  public function apiTestNodePrePublishImmediately(SchedulerEvent $event) {
+  public function apiTestUnpublish(SchedulerEvent $event) {
     /** @var \Drupal\node\Entity\Node $node */
     $node = $event->getNode();
-    // Before publishing immediately set the node to sticky.
-    if (!$node->isPromoted() && strpos($node->title->value, 'API TEST') === 0) {
-      $node->setSticky(TRUE);
+    // After unpublishing a node remove it from the front page.
+    if (!$node->isPublished() && strpos($node->title->value, 'API TEST') === 0) {
+      $node->setPromoted(FALSE);
       $event->setNode($node);
     }
   }
@@ -160,10 +123,10 @@ public function apiTestNodePrePublishImmediately(SchedulerEvent $event) {
   /**
    * Operations after Scheduler publishes a node immediately not via cron.
    *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
+   * @param \Drupal\scheduler\SchedulerEvent $event
    *   The scheduler event.
    */
-  public function apiTestNodePublishImmediately(SchedulerEvent $event) {
+  public function apiTestPublishImmediately(SchedulerEvent $event) {
     /** @var \Drupal\node\Entity\Node $node */
     $node = $event->getNode();
     // After publishing immediately set the node to promoted and change the
@@ -175,280 +138,4 @@ public function apiTestNodePublishImmediately(SchedulerEvent $event) {
     }
   }
 
-  /**
-   * Generic helper function to do the PrePublish work.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  private function apiTestPrePublish(SchedulerEvent $event) {
-    $entity = $event->getEntity();
-    if (!$entity->isPublished() && strpos($entity->label(), "API TEST {$entity->getEntityTypeId()}") === 0) {
-      $label_field = $entity->getEntityType()->getKey('label');
-      $entity->set($label_field, "API TEST {$entity->getEntityTypeId()} - changed by PRE_PUBLISH event");
-      $event->setEntity($entity);
-    }
-  }
-
-  /**
-   * Generic helper function to do the Publish work.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  private function apiTestPublish(SchedulerEvent $event) {
-    $entity = $event->getEntity();
-    // The label will be changed here only if it has already been changed by the
-    // PRE_PUBLISH event. This will demonstrate that both events worked.
-    if ($entity->isPublished() && $entity->label() == "API TEST {$entity->getEntityTypeId()} - changed by PRE_PUBLISH event") {
-      $label_field = $entity->getEntityType()->getKey('label');
-      $entity->set($label_field, "API TEST {$entity->getEntityTypeId()} - altered a second time by PUBLISH event")->save();
-      $event->setEntity($entity);
-    }
-  }
-
-  /**
-   * Generic helper function to do the PreUnpublish work.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  private function apiTestPreUnpublish(SchedulerEvent $event) {
-    $entity = $event->getEntity();
-    if ($entity->isPublished() && strpos($entity->label(), "API TEST {$entity->getEntityTypeId()}") === 0) {
-      $label_field = $entity->getEntityType()->getKey('label');
-      $entity->set($label_field, "API TEST {$entity->getEntityTypeId()} - changed by PRE_UNPUBLISH event");
-      $event->setEntity($entity);
-    }
-  }
-
-  /**
-   * Generic helper function to do the Unpublish work.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  private function apiTestUnpublish(SchedulerEvent $event) {
-    $entity = $event->getEntity();
-    // The name will be changed here only if it has already been changed by the
-    // PRE_UNPUBLISH event. This will demonstrate that both events worked.
-    if (!$entity->isPublished() && $entity->label() == "API TEST {$entity->getEntityTypeId()} - changed by PRE_UNPUBLISH event") {
-      $label_field = $entity->getEntityType()->getKey('label');
-      $entity->set($label_field, "API TEST {$entity->getEntityTypeId()} - altered a second time by UNPUBLISH event")->save();
-      $event->setEntity($entity);
-    }
-  }
-
-  /**
-   * Generic helper function to do the PrePublishImmediately work.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestPrePublishImmediately(SchedulerEvent $event) {
-    $entity = $event->getEntity();
-    if (!$entity->isPublished() && strpos($entity->label(), "API TEST {$entity->getEntityTypeId()}") === 0) {
-      $label_field = $entity->getEntityType()->getKey('label');
-      $entity->set($label_field, "API TEST {$entity->getEntityTypeId()} - changed by PRE_PUBLISH_IMMEDIATELY event");
-      $event->setEntity($entity);
-    }
-  }
-
-  /**
-   * Generic helper function to do the PublishImmediately work.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestPublishImmediately(SchedulerEvent $event) {
-    $entity = $event->getEntity();
-    // The name will be changed here only if it has already been changed in the
-    // PRE_PUBLISH_IMMEDIATELY event function, to show that both events worked.
-    if ($entity->label() == "API TEST {$entity->getEntityTypeId()} - changed by PRE_PUBLISH_IMMEDIATELY event") {
-      $label_field = $entity->getEntityType()->getKey('label');
-      $entity->set($label_field, "API TEST {$entity->getEntityTypeId()} - altered a second time by PUBLISH_IMMEDIATELY event");
-      $event->setEntity($entity);
-    }
-  }
-
-  /**
-   * Operations to perform before Scheduler publishes a media item.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestMediaPrePublish(SchedulerEvent $event) {
-    $this->apiTestPrePublish($event);
-  }
-
-  /**
-   * Operations to perform after Scheduler publishes a media item.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestMediaPublish(SchedulerEvent $event) {
-    $this->apiTestPublish($event);
-  }
-
-  /**
-   * Operations to perform before Scheduler unpublishes a media item.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestMediaPreUnpublish(SchedulerEvent $event) {
-    $this->apiTestPreUnpublish($event);
-  }
-
-  /**
-   * Operations to perform after Scheduler unpublishes a media item.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestMediaUnpublish(SchedulerEvent $event) {
-    $this->apiTestUnpublish($event);
-  }
-
-  /**
-   * Operations before Scheduler publishes a media immediately not via cron.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestMediaPrePublishImmediately(SchedulerEvent $event) {
-    $this->apiTestPrePublishImmediately($event);
-  }
-
-  /**
-   * Operations after Scheduler publishes a media immediately not via cron.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestMediaPublishImmediately(SchedulerEvent $event) {
-    $this->apiTestPublishImmediately($event);
-  }
-
-  /**
-   * Operations to perform before Scheduler publishes a commerce product.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestProductPrePublish(SchedulerEvent $event) {
-    $this->apiTestPrePublish($event);
-  }
-
-  /**
-   * Operations to perform after Scheduler publishes a commerce product.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestProductPublish(SchedulerEvent $event) {
-    $this->apiTestPublish($event);
-  }
-
-  /**
-   * Operations to perform before Scheduler unpublishes a commerce product.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestProductPreUnpublish(SchedulerEvent $event) {
-    $this->apiTestPreUnpublish($event);
-  }
-
-  /**
-   * Operations to perform after Scheduler unpublishes a commerce product.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestProductUnpublish(SchedulerEvent $event) {
-    $this->apiTestUnpublish($event);
-  }
-
-  /**
-   * Operations before Scheduler publishes a product immediately not via cron.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestProductPrePublishImmediately(SchedulerEvent $event) {
-    $this->apiTestPrePublishImmediately($event);
-  }
-
-  /**
-   * Operations after Scheduler publishes a product immediately not via cron.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestProductPublishImmediately(SchedulerEvent $event) {
-    $this->apiTestPublishImmediately($event);
-  }
-
-  /**
-   * Operations to perform before Scheduler publishes a taxonomy term.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestTaxonomyTermPrePublish(SchedulerEvent $event) {
-    $this->apiTestPrePublish($event);
-  }
-
-  /**
-   * Operations to perform after Scheduler publishes a taxonomy term.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestTaxonomyTermPublish(SchedulerEvent $event) {
-    $this->apiTestPublish($event);
-  }
-
-  /**
-   * Operations to perform before Scheduler unpublishes a taxonomy term.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestTaxonomyTermPreUnpublish(SchedulerEvent $event) {
-    $this->apiTestPreUnpublish($event);
-  }
-
-  /**
-   * Operations to perform after Scheduler unpublishes a taxonomy term.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestTaxonomyTermUnpublish(SchedulerEvent $event) {
-    $this->apiTestUnpublish($event);
-  }
-
-  /**
-   * Operations before Scheduler publishes a term immediately not via cron.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestTaxonomyTermPrePublishImmediately(SchedulerEvent $event) {
-    $this->apiTestPrePublishImmediately($event);
-  }
-
-  /**
-   * Operations after Scheduler publishes a term immediately not via cron.
-   *
-   * @param \Drupal\scheduler\Event\SchedulerEvent $event
-   *   The scheduler event.
-   */
-  public function apiTestTaxonomyTermPublishImmediately(SchedulerEvent $event) {
-    $this->apiTestPublishImmediately($event);
-  }
-
 }
diff --git a/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml b/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml
index 17adef8c456e1295f762599149d841faa1d0e307..d48a69091f67be70319e19ffdad580253c101303 100644
--- a/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.info.yml
@@ -2,10 +2,12 @@ name: 'Scheduler Extras'
 type: module
 description: 'Support module for general Scheduler testing.'
 package: Testing
+core: 8.x
+core_version_requirement: ^8 || ^9
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2022-11-20
-version: '2.0.0-rc8'
+# Information added by Drupal.org packaging script on 2021-07-19
+version: '8.x-1.4'
 project: 'scheduler'
-datestamp: 1668951020
+datestamp: 1626701769
diff --git a/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.module b/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.module
index 761f7833f478955527baa9d5eba26208dc010695..9264b69710a73ff6c76cf7102cabf81165a7d31a 100644
--- a/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.module
+++ b/web/modules/scheduler/tests/modules/scheduler_extras/scheduler_extras.module
@@ -3,31 +3,23 @@
 /**
  * @file
  * Hook implementations for the Scheduler Extras test module.
- *
- * This module is used in SchedulerDefaultTimeTest to check that the default
- * time is set correctly when the time element of the datetime input is hidden.
  */
 
 use Drupal\Core\Form\FormStateInterface;
 
 /**
- * Implements hook_form_alter().
+ * Implements hook_form_FORM_ID_alter() for node_form().
  */
-function scheduler_extras_form_alter(&$form, FormStateInterface $form_state, $form_id) {
-  // Only continue if the form is for adding the standard test entity types.
-  if (!in_array($form_id, [
-    'node_testpage_form',
-    'media_test_video_add_form',
-    'commerce_product_test_product_add_form',
-    'taxonomy_term_test_vocab_form',
-  ])) {
-    return;
-  }
-  // Hide the time element when the scheduler field exists.
-  if (isset($form['publish_on'])) {
-    $form['publish_on']['widget'][0]['value']['#date_time_element'] = 'none';
-  }
-  if (isset($form['unpublish_on'])) {
-    $form['unpublish_on']['widget'][0]['value']['#date_time_element'] = 'none';
+function scheduler_extras_form_node_form_alter(&$form, FormStateInterface $form_state) {
+  // This is used in SchedulerDefaultTimeTest to check that the default time is
+  // set correctly even when the time elememt of the datetime input is hidden.
+  $type = $form_state->getFormObject()->getEntity()->type->entity->get('type');
+  if ($type == 'hidden_time') {
+    if (isset($form['publish_on'])) {
+      $form['publish_on']['widget'][0]['value']['#date_time_element'] = 'none';
+    }
+    if (isset($form['unpublish_on'])) {
+      $form['unpublish_on']['widget'][0]['value']['#date_time_element'] = 'none';
+    }
   }
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
index 82d769ee1d51afc15d24be01a4bcb2e097f35c99..688ca0c095fda8d19773b11855b4bedcc79a7b54 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
@@ -19,31 +19,9 @@ class SchedulerAdminSettingsTest extends SchedulerBrowserTestBase {
   public function testAdminSettings() {
     $this->drupalLogin($this->adminUser);
 
-    // Check that menu links exists for the node entity types, and that we are
-    // informed that no media types or taxonomy vocabularies exist.
-    $this->drupalGet('admin/config/content/scheduler');
-    $this->assertSession()->linkExists("{$this->typeName} (publishing, unpublishing)");
-    $this->assertSession()->linkExists("{$this->nonSchedulerTypeName}");
-    $this->assertSession()->pageTextContains('-- Media types -- (no entity types defined)');
-    $this->assertSession()->pageTextContains('-- Taxonomy -- (no entity types defined)');
-
-    // Call the setUp functions for all entity types.
-    $this->schedulerMediaSetUp();
-    $this->SchedulerCommerceProductSetUp();
-    $this->SchedulerTaxonomyTermSetUp();
-
-    // Check that the drop-down information has been updated.
-    $this->drupalGet('admin/config/content/scheduler');
-    $this->assertSession()->pageTextNotContains('-- Media types -- (no entity types defined)');
-    $this->assertSession()->linkExists("{$this->mediaTypeLabel} (publishing, unpublishing)");
-    $this->assertSession()->linkExists("{$this->nonSchedulerMediaTypeLabel}");
-    $this->assertSession()->pageTextNotContains('-- Taxonomy -- (no entity types defined)');
-    $this->assertSession()->pageTextContains("{$this->vocabularyName} (publishing, unpublishing)");
-    $this->assertSession()->linkExists("{$this->nonSchedulerVocabularyName}");
-
     // Verify that the default values are as expected.
     $this->assertFalse($this->config('scheduler.settings')->get('allow_date_only'), 'The default setting for allow_date_only is False.');
-    $this->assertEquals('00:00:00', $this->config('scheduler.settings')->get('default_time'), 'The default config setting for default_time is 00:00:00');
+    $this->assertEquals($this->config('scheduler.settings')->get('default_time'), '00:00:00', 'The default config setting for default_time is 00:00:00');
     $this->assertFalse($this->config('scheduler.settings')->get('hide_seconds'), 'The default setting for hide_seconds is False.');
 
     // Check that a default time can be stored, and that the option is saved.
@@ -52,9 +30,7 @@ public function testAdminSettings() {
       'allow_date_only' => TRUE,
       'default_time' => '6:30',
     ];
-    $this->drupalGet('admin/config/content/scheduler');
-    $this->submitForm($settings, 'Save configuration');
-
+    $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration');
     // Verify that the values have been saved correctly.
     $this->assertTrue($this->config('scheduler.settings')->get('allow_date_only'), 'The config setting for allow_date_only is stored correctly.');
     $this->assertEquals('06:30:00', $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time is stored correctly.');
@@ -64,8 +40,7 @@ public function testAdminSettings() {
       'allow_date_only' => TRUE,
       'default_time' => '123',
     ];
-    $this->drupalGet('admin/config/content/scheduler');
-    $this->submitForm($settings, 'Save configuration');
+    $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration');
     // Verify that the value has not been saved and an error is displayed.
     $this->assertEquals('06:30:00', $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time has not changed.');
     $this->assertSession()->pageTextContains('The default time should be in the format HH:MM:SS');
@@ -74,8 +49,7 @@ public function testAdminSettings() {
     $settings = [
       'hide_seconds' => TRUE,
     ];
-    $this->drupalGet('admin/config/content/scheduler');
-    $this->submitForm($settings, 'Save configuration');
+    $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration');
     // Verify that the hide seconds option is saved and the default time is
     // stored in HH:MM format with no seconds.
     $this->assertTrue($this->config('scheduler.settings')->get('hide_seconds'), 'The config setting for hide_seconds is stored correctly.');
@@ -85,8 +59,7 @@ public function testAdminSettings() {
     $settings = [
       'default_time' => '456',
     ];
-    $this->drupalGet('admin/config/content/scheduler');
-    $this->submitForm($settings, 'Save configuration');
+    $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration');
     // Verify that the value has not been saved, and that an error message is
     // displayed showing the correct format HH:MM not HH:MM:SS.
     $this->assertEquals('06:30', $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time has not changed.');
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerHooksLegacyTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
similarity index 71%
rename from web/modules/scheduler/tests/src/Functional/SchedulerHooksLegacyTest.php
rename to web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
index 3178195ad123333409b9a5d350c95eef794604ce..3632884dd343766f9fb053128aa2cc3d02c2dade 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerHooksLegacyTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
@@ -5,26 +5,22 @@
 use Drupal\node\Entity\NodeType;
 
 /**
- * Tests the legacy API hook functions of the Scheduler module.
+ * Tests the API of the Scheduler module.
  *
- * This class covers the eight original hook functions for node entity types
- * only. These are maintained for backwards-compatibility.
- *
- * @group scheduler_api
+ * @group scheduler
  */
-class SchedulerHooksLegacyTest extends SchedulerBrowserTestBase {
+class SchedulerApiTest extends SchedulerBrowserTestBase {
 
   /**
    * Additional modules required.
    *
    * @var array
+   *
+   * @todo 'menu_ui' is in the exported node.type definition, and 'path' is in
+   * the entity_form_display. Could these be removed from the config files and
+   * then not needed here?
    */
-  protected static $modules = [
-    'scheduler_api_test',
-    'scheduler_api_legacy_test',
-    'menu_ui',
-    'path',
-  ];
+  protected static $modules = ['scheduler_api_test', 'menu_ui', 'path'];
 
   /**
    * {@inheritdoc}
@@ -34,132 +30,19 @@ protected function setUp(): void {
 
     // Load the custom node type. It will be enabled for Scheduler automatically
     // as that is pre-configured in node.type.scheduler_api_test.yml.
-    $this->customName = 'scheduler_api_node_test';
+    $this->customName = 'scheduler_api_test';
     $this->customNodetype = NodeType::load($this->customName);
 
     // Check that the custom node type has loaded OK.
-    $this->assertNotNull($this->customNodetype, 'Custom node type "' . $this->customName . '" was created during install');
+    $this->assertNotNull($this->customNodetype, 'Custom node type "' . $this->customName . '"  was created during install');
 
-    // Create a web user that has permission to create and edit and schedule
-    // the custom entity type.
+    // Create a web user for this content type.
     $this->webUser = $this->drupalCreateUser([
       'create ' . $this->customName . ' content',
       'edit any ' . $this->customName . ' content',
       'schedule publishing of nodes',
     ]);
-    $this->webUser->set('name', 'Wenlock the Web user')->save();
-
-  }
-
-  /**
-   * Covers hook_scheduler_nid_list($action)
-   *
-   * Hook_scheduler_nid_list() allows other modules to add more node ids into
-   * the list to be processed. In real scenarios, the third-party module would
-   * likely have more complex data structures and/or tables from which to
-   * identify nodes to add. In this test, to keep it simple, we identify nodes
-   * by the text of the title.
-   */
-  public function testNidList() {
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create test nodes. Use the ordinary page type for this test, as having
-    // the 'approved' fields here would unnecessarily complicate the processing.
-    // Node 1 is not published and has no publishing date set. The test API
-    // module will add node 1 into the list to be published.
-    $node1 = $this->drupalCreateNode([
-      'type' => $this->type,
-      'status' => FALSE,
-      'title' => 'API TEST nid_list publish me',
-    ]);
-    // Node 2 is published and has no unpublishing date set. The test API module
-    // will add node 2 into the list to be unpublished.
-    $node2 = $this->drupalCreateNode([
-      'type' => $this->type,
-      'status' => TRUE,
-      'title' => 'API TEST nid_list unpublish me',
-    ]);
-
-    // Before cron, check node 1 is unpublished and node 2 is published.
-    $this->assertFalse($node1->isPublished(), 'Before cron, node 1 "' . $node1->title->value . '" is unpublished.');
-    $this->assertTrue($node2->isPublished(), 'Before cron, node 2 "' . $node2->title->value . '" is published.');
-
-    // Run cron and refresh the nodes.
-    scheduler_cron();
-    $this->nodeStorage->resetCache();
-    $node1 = $this->nodeStorage->load($node1->id());
-    $node2 = $this->nodeStorage->load($node2->id());
-
-    // Check node 1 is published and node 2 is unpublished.
-    $this->assertTrue($node1->isPublished(), 'After cron, node 1 "' . $node1->title->value . '" is published.');
-    $this->assertFalse($node2->isPublished(), 'After cron, node 2 "' . $node2->title->value . '" is unpublished.');
-  }
-
-  /**
-   * Covers hook_scheduler_nid_list_alter($action)
-   *
-   * This hook allows other modules to add or remove node ids from the list to
-   * be processed. As in testNidList() we make it simple by using the title text
-   * to identify which nodes to act on.
-   */
-  public function testNidListAlter() {
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create test nodes. Use the ordinary page type for this test, as having
-    // the 'approved' fields here would unnecessarily complicate the processing.
-    // Node 1 is set for scheduled publishing, but will be removed by the test
-    // API hook_nid_list_alter().
-    $node1 = $this->drupalCreateNode([
-      'type' => $this->type,
-      'status' => FALSE,
-      'title' => 'API TEST nid_list_alter do not publish me',
-      'publish_on' => strtotime('-1 day'),
-    ]);
-
-    // Node 2 is not published and has no publishing date set. The test API
-    // module will add node 2 into the list to be published.
-    $node2 = $this->drupalCreateNode([
-      'type' => $this->type,
-      'status' => FALSE,
-      'title' => 'API TEST nid_list_alter publish me',
-    ]);
-
-    // Node 3 is set for scheduled unpublishing, but will be removed by the test
-    // API hook_nid_list_alter().
-    $node3 = $this->drupalCreateNode([
-      'type' => $this->type,
-      'status' => TRUE,
-      'title' => 'API TEST nid_list_alter do not unpublish me',
-      'unpublish_on' => strtotime('-1 day'),
-    ]);
-
-    // Node 4 is published and has no unpublishing date set. The test API module
-    // will add node 4 into the list to be unpublished.
-    $node4 = $this->drupalCreateNode([
-      'type' => $this->type,
-      'status' => TRUE,
-      'title' => 'API TEST nid_list_alter unpublish me',
-    ]);
-
-    // Check node 1 and 2 are unpublished and node 3 and 4 are published.
-    $this->assertFalse($node1->isPublished(), 'Before cron, node 1 "' . $node1->title->value . '" is unpublished.');
-    $this->assertFalse($node2->isPublished(), 'Before cron, node 2 "' . $node2->title->value . '" is unpublished.');
-    $this->assertTrue($node3->isPublished(), 'Before cron, node 3 "' . $node3->title->value . '" is published.');
-    $this->assertTrue($node4->isPublished(), 'Before cron, node 4 "' . $node4->title->value . '" is published.');
-
-    // Run cron and refresh the nodes.
-    scheduler_cron();
-    $this->nodeStorage->resetCache();
-    $node1 = $this->nodeStorage->load($node1->id());
-    $node2 = $this->nodeStorage->load($node2->id());
-    $node3 = $this->nodeStorage->load($node3->id());
-    $node4 = $this->nodeStorage->load($node4->id());
 
-    // Check node 2 and 3 are published and node 1 and 4 are unpublished.
-    $this->assertFalse($node1->isPublished(), 'After cron, node 1 "' . $node1->title->value . '" is still unpublished.');
-    $this->assertTrue($node2->isPublished(), 'After cron, node 2 "' . $node2->title->value . '" is published.');
-    $this->assertTrue($node3->isPublished(), 'After cron, node 3 "' . $node3->title->value . '" is still published.');
-    $this->assertFalse($node4->isPublished(), 'After cron, node 4 "' . $node4->title->value . '" is unpublished.');
   }
 
   /**
@@ -168,10 +51,12 @@ public function testNidListAlter() {
    * This hook can allow or deny the publishing of individual nodes. This test
    * uses the customised content type which has checkboxes 'Approved for
    * publication' and 'Approved for unpublication'.
+   *
+   * @todo Create and update the nodes through the interface so we can check if
+   *   the correct messages are displayed.
    */
   public function testAllowedPublishing() {
     $this->drupalLogin($this->webUser);
-
     // Check the 'approved for publishing' field is shown on the node form.
     $this->drupalGet('node/add/' . $this->customName);
     $this->assertSession()->fieldExists('edit-field-approved-publishing-value');
@@ -183,9 +68,8 @@ public function testAllowedPublishing() {
       'publish_on[0][value][date]' => date('Y-m-d', time() + 3),
       'publish_on[0][value][time]' => date('H:i:s', time() + 3),
     ];
-    $this->drupalGet("node/add/{$this->customName}");
-    $this->submitForm($edit, 'Save');
-    $this->assertSession()->pageTextMatches('/is scheduled for publishing.* but will not be published until approved/');
+    $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save');
+    $this->assertSession()->pageTextContains('is scheduled for publishing, but will not be published until approved.');
 
     // Create a node that is scheduled but not approved for publication. Then
     // simulate a cron run, and check that the node is still not published.
@@ -193,7 +77,7 @@ public function testAllowedPublishing() {
     scheduler_cron();
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
-    $this->assertFalse($node->isPublished(), "Unapproved '{$node->label()}' should not be published during cron processing.");
+    $this->assertFalse($node->isPublished(), 'An unapproved node is not published during cron processing.');
 
     // Create a node and approve it for publication, simulate a cron run and
     // check that the node is published. This is a stronger test than simply
@@ -201,31 +85,30 @@ public function testAllowedPublishing() {
     // state that may be in after the cron run above.
     $node = $this->createUnapprovedNode('publish_on');
     $this->approveNode($node->id(), 'field_approved_publishing');
-    $this->assertFalse($node->isPublished(), "New approved '{$node->label()}' should not be initially published.");
+    $this->assertFalse($node->isPublished(), 'A new approved node is initially not published.');
     scheduler_cron();
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
-    $this->assertTrue($node->isPublished(), "Approved '{$node->label()}' should be published during cron processing.");
+    $this->assertTrue($node->isPublished(), 'An approved node is published during cron processing.');
 
     // Turn on immediate publication of nodes with publication dates in the past
     // and repeat the tests. It is not needed to simulate cron runs here.
     $this->customNodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
     $node = $this->createUnapprovedNode('publish_on');
-    $this->assertFalse($node->isPublished(), "New unapproved '{$node->label()}' with a date in the past should not be published immediately after saving.");
+    $this->assertFalse($node->isPublished(), 'An unapproved node with a date in the past is not published immediately after saving.');
 
     // Check that the node can be approved and published programatically.
     $this->approveNode($node->id(), 'field_approved_publishing');
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
-    $this->assertTrue($node->isPublished(), "New approved '{$node->label()}' with a date in the past should be published immediately when created programatically.");
+    $this->assertTrue($node->isPublished(), 'An approved node with a date in the past is published immediately via $node->set()->save().');
 
     // Check that a node can be approved and published via edit form.
     $node = $this->createUnapprovedNode('publish_on');
-    $this->drupalGet("node/{$node->id()}/edit");
-    $this->submitForm(['field_approved_publishing[value]' => '1'], 'Save');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved_publishing[value]' => '1'], 'Save');
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
-    $this->assertTrue($node->isPublished(), "Approved '{$node->label()}' with a date in the past should be published immediately after saving via edit form.");
+    $this->assertTrue($node->isPublished(), 'An approved node with a date in the past is published immediately after saving via edit form.');
 
     // Show the dblog messages.
     $this->drupalLogin($this->adminUser);
@@ -241,7 +124,6 @@ public function testAllowedPublishing() {
    */
   public function testAllowedUnpublishing() {
     $this->drupalLogin($this->webUser);
-
     // Check the 'approved for unpublishing' field is shown on the node form.
     $this->drupalGet('node/add/' . $this->customName);
     $this->assertSession()->fieldExists('edit-field-approved-unpublishing-value');
@@ -253,9 +135,8 @@ public function testAllowedUnpublishing() {
       'unpublish_on[0][value][date]' => date('Y-m-d', time() + 3),
       'unpublish_on[0][value][time]' => date('H:i:s', time() + 3),
     ];
-    $this->drupalGet("node/add/{$this->customName}");
-    $this->submitForm($edit, 'Save');
-    $this->assertSession()->pageTextMatches('/is scheduled for unpublishing.* but will not be unpublished until approved/');
+    $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save');
+    $this->assertSession()->pageTextContains('is scheduled for unpublishing, but will not be unpublished until approved.');
 
     // Create a node that is scheduled but not approved for unpublication. Then
     // simulate a cron run, and check that the node is still published.
@@ -263,17 +144,17 @@ public function testAllowedUnpublishing() {
     scheduler_cron();
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
-    $this->assertTrue($node->isPublished(), "Unapproved '{$node->label()}' should not be unpublished during cron processing.");
+    $this->assertTrue($node->isPublished(), 'An unapproved node is not unpublished during cron processing.');
 
     // Create a node, then approve it for unpublishing, simulate a cron run and
     // check that the node is now unpublished.
     $node = $this->createUnapprovedNode('unpublish_on');
     $this->approveNode($node->id(), 'field_approved_unpublishing');
-    $this->assertTrue($node->isPublished(), "New approved '{$node->label()}' should initially remain published.");
+    $this->assertTrue($node->isPublished(), 'A new approved node is initially published.');
     scheduler_cron();
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
-    $this->assertFalse($node->isPublished(), "Approved '{$node->label()}' should be unpublished during cron processing.");
+    $this->assertFalse($node->isPublished(), 'An approved node is unpublished during cron processing.');
 
     // Show the dblog messages.
     $this->drupalLogin($this->adminUser);
@@ -294,7 +175,6 @@ public function testAllowedUnpublishing() {
    */
   protected function createUnapprovedNode($date_field) {
     $settings = [
-      'title' => (($date_field == 'publish_on') ? 'Blue' : 'Red') . " legacy node {$this->randomMachineName(10)}",
       'status' => ($date_field == 'unpublish_on'),
       $date_field => strtotime('-1 day'),
       'field_approved_publishing' => 0,
@@ -316,8 +196,185 @@ protected function createUnapprovedNode($date_field) {
   protected function approveNode($nid, $field_name) {
     $this->nodeStorage->resetCache([$nid]);
     $node = $this->nodeStorage->load($nid);
-    $node->set($field_name, TRUE);
-    $node->set('title', $node->label() . " - approved for publishing: {$node->field_approved_publishing->value}, for unpublishing: {$node->field_approved_unpublishing->value}")->save();
+    $node->set($field_name, TRUE)->save();
+  }
+
+  /**
+   * Covers six events.
+   *
+   * The events allow other modules to react to the Scheduler process being run.
+   * The API test implementations of the event listeners alter the nodes
+   * 'promote' and 'sticky' settings and changes the title.
+   */
+  public function testApiNodeAction() {
+    $this->drupalLogin($this->schedulerUser);
+
+    // Create a test node. Having the 'approved' fields here would complicate
+    // the tests, so use the ordinary page type.
+    $settings = [
+      'publish_on' => strtotime('-1 day'),
+      'type' => $this->type,
+      'promote' => FALSE,
+      'sticky' => FALSE,
+      'title' => 'API TEST node action',
+    ];
+    $node = $this->drupalCreateNode($settings);
+
+    // Check that the 'sticky' and 'promote' fields are off for the new node.
+    $this->assertFalse($node->isSticky(), 'The unpublished node is not sticky.');
+    $this->assertFalse($node->isPromoted(), 'The unpublished node is not promoted.');
+
+    // Run cron and check that hook_scheduler_api() has executed correctly, by
+    // verifying that the node has become promoted and is sticky.
+    scheduler_cron();
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    $this->assertTrue($node->isSticky(), 'API action "PRE_PUBLISH" has changed the node to sticky.');
+    $this->assertTrue($node->isPromoted(), 'API action "PUBLISH" has changed the node to promoted.');
+
+    // Now set a date for unpublishing the node. Ensure 'sticky' and 'promote'
+    // are set, so that the assertions are not affected by any failures above.
+    $node->set('unpublish_on', strtotime('-1 day'))
+      ->set('sticky', TRUE)->set('promote', TRUE)->save();
+
+    // Run cron and check that hook_scheduler_api() has executed correctly, by
+    // verifying that the node is not promoted and no longer sticky.
+    scheduler_cron();
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    $this->assertFalse($node->isSticky(), 'API action "PRE_UNPUBLISH" has changed the node to not sticky.');
+    $this->assertFalse($node->isPromoted(), 'API action "UNPUBLISH" has changed the node to not promoted.');
+
+    // Turn on immediate publication when a publish date is in the past.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
+
+    // Ensure 'sticky' and 'promote' are not set, so that the assertions are not
+    // affected by any failures above.
+    $node->set('sticky', FALSE)->set('promote', FALSE)->save();
+
+    // Edit the node and set a publish-on date in the past.
+    $edit = [
+      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', $this->requestTime)),
+      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', $this->requestTime)),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    // Verify that the values have been altered as expected.
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    $this->assertTrue($node->isSticky(), 'API action "PRE_PUBLISH_IMMEDIATELY" has changed the node to sticky.');
+    $this->assertTrue($node->isPromoted(), 'API action "PUBLISH_IMMEDIATELY" has changed the node to promoted.');
+    $this->assertEquals('Published immediately', $node->title->value, 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.');
+  }
+
+  /**
+   * Covers hook_scheduler_nid_list($action)
+   *
+   * Hook_scheduler_nid_list() allows other modules to add more node ids into
+   * the list to be processed. In real scenarios, the third-party module would
+   * likely have more complex data structures and/or tables from which to
+   * identify nodes to add. In this test, to keep it simple, we identify nodes
+   * by the text of the title.
+   */
+  public function testNidList() {
+    $this->drupalLogin($this->schedulerUser);
+
+    // Create test nodes. Use the ordinary page type for this test, as having
+    // the 'approved' fields here would unnecessarily complicate the processing.
+    // Node 1 is not published and has no publishing date set. The test API
+    // module will add node 1 into the list to be published.
+    $node1 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'title' => 'API TEST nid_list publish me',
+    ]);
+    // Node 2 is published and has no unpublishing date set. The test API module
+    // will add node 2 into the list to be unpublished.
+    $node2 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'title' => 'API TEST nid_list unpublish me',
+    ]);
+
+    // Before cron, check node 1 is unpublished and node 2 is published.
+    $this->assertFalse($node1->isPublished(), 'Before cron, node 1 "' . $node1->title->value . '" is unpublished.');
+    $this->assertTrue($node2->isPublished(), 'Before cron, node 2 "' . $node2->title->value . '" is published.');
+
+    // Run cron and refresh the nodes.
+    scheduler_cron();
+    $this->nodeStorage->resetCache();
+    $node1 = $this->nodeStorage->load($node1->id());
+    $node2 = $this->nodeStorage->load($node2->id());
+
+    // Check node 1 is published and node 2 is unpublished.
+    $this->assertTrue($node1->isPublished(), 'After cron, node 1 "' . $node1->title->value . '" is published.');
+    $this->assertFalse($node2->isPublished(), 'After cron, node 2 "' . $node2->title->value . '" is unpublished.');
+  }
+
+  /**
+   * Covers hook_scheduler_nid_list_alter($action)
+   *
+   * This hook allows other modules to add or remove node ids from the list to
+   * be processed. As in testNidList() we make it simple by using the title text
+   * to identify which nodes to act on.
+   */
+  public function testNidListAlter() {
+    $this->drupalLogin($this->schedulerUser);
+
+    // Create test nodes. Use the ordinary page type for this test, as having
+    // the 'approved' fields here would unnecessarily complicate the processing.
+    // Node 1 is set for scheduled publishing, but will be removed by the test
+    // API hook_nid_list_alter().
+    $node1 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'title' => 'API TEST nid_list_alter do not publish me',
+      'publish_on' => strtotime('-1 day'),
+    ]);
+
+    // Node 2 is not published and has no publishing date set. The test API
+    // module will add node 2 into the list to be published.
+    $node2 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'title' => 'API TEST nid_list_alter publish me',
+    ]);
+
+    // Node 3 is set for scheduled unpublishing, but will be removed by the test
+    // API hook_nid_list_alter().
+    $node3 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'title' => 'API TEST nid_list_alter do not unpublish me',
+      'unpublish_on' => strtotime('-1 day'),
+    ]);
+
+    // Node 4 is published and has no unpublishing date set. The test API module
+    // will add node 4 into the list to be unpublished.
+    $node4 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'title' => 'API TEST nid_list_alter unpublish me',
+    ]);
+
+    // Check node 1 and 2 are unpublished and node 3 and 4 are published.
+    $this->assertFalse($node1->isPublished(), 'Before cron, node 1 "' . $node1->title->value . '" is unpublished.');
+    $this->assertFalse($node2->isPublished(), 'Before cron, node 2 "' . $node2->title->value . '" is unpublished.');
+    $this->assertTrue($node3->isPublished(), 'Before cron, node 3 "' . $node3->title->value . '" is published.');
+    $this->assertTrue($node4->isPublished(), 'Before cron, node 4 "' . $node4->title->value . '" is published.');
+
+    // Run cron and refresh the nodes.
+    scheduler_cron();
+    $this->nodeStorage->resetCache();
+    $node1 = $this->nodeStorage->load($node1->id());
+    $node2 = $this->nodeStorage->load($node2->id());
+    $node3 = $this->nodeStorage->load($node3->id());
+    $node4 = $this->nodeStorage->load($node4->id());
+
+    // Check node 2 and 3 are published and node 1 and 4 are unpublished.
+    $this->assertFalse($node1->isPublished(), 'After cron, node 1 "' . $node1->title->value . '" is still unpublished.');
+    $this->assertTrue($node2->isPublished(), 'After cron, node 2 "' . $node2->title->value . '" is published.');
+    $this->assertTrue($node3->isPublished(), 'After cron, node 3 "' . $node3->title->value . '" is still published.');
+    $this->assertFalse($node4->isPublished(), 'After cron, node 4 "' . $node4->title->value . '" is unpublished.');
   }
 
   /**
@@ -332,24 +389,21 @@ public function testHideField() {
     // Create test nodes.
     $node1 = $this->drupalCreateNode([
       'type' => $this->type,
-      'title' => 'Red Legacy will not have either field hidden',
+      'title' => 'Red will not have either field hidden',
     ]);
     $node2 = $this->drupalCreateNode([
       'type' => $this->type,
-      'title' => 'Orange Legacy will have the publish-on field hidden',
+      'title' => 'Orange will have the publish-on field hidden',
     ]);
     $node3 = $this->drupalCreateNode([
       'type' => $this->type,
-      'title' => 'Yellow Legacy will have the unpublish-on field hidden',
+      'title' => 'Yellow will have the unpublish-on field hidden',
     ]);
     $node4 = $this->drupalCreateNode([
       'type' => $this->type,
-      'title' => 'Green Legacy will have both Scheduler fields hidden',
+      'title' => 'Green will have both Scheduler fields hidden',
     ]);
 
-    // Set the scheduler fieldset to always expand, for ease during development.
-    $this->nodetype->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save();
-
     /** @var \Drupal\Tests\WebAssert $assert */
     $assert = $this->assertSession();
 
@@ -380,26 +434,26 @@ public function testHideField() {
    * This covers hook_scheduler_publish_action and
    * hook_scheduler_unpublish_action.
    */
-  public function testPublishUnpublishAction() {
+  public function testHookPublishUnpublishAction() {
     $this->drupalLogin($this->schedulerUser);
 
     // Create test nodes.
     $node1 = $this->drupalCreateNode([
       'type' => $this->type,
       'status' => FALSE,
-      'title' => 'Red Legacy will cause a failure on publishing',
+      'title' => 'Red will cause a failure on publishing',
       'publish_on' => strtotime('-1 day'),
     ]);
     $node2 = $this->drupalCreateNode([
       'type' => $this->type,
       'status' => TRUE,
-      'title' => 'Orange Legacy will be unpublished by the API test module not Scheduler',
+      'title' => 'Orange will be unpublished by the API test module not Scheduler',
       'unpublish_on' => strtotime('-1 day'),
     ]);
     $node3 = $this->drupalCreateNode([
       'type' => $this->type,
       'status' => FALSE,
-      'title' => 'Yellow Legacy will be published by the API test module not Scheduler',
+      'title' => 'Yellow will be published by the API test module not Scheduler',
       'publish_on' => strtotime('-1 day'),
     ]);
     // 'green' nodes will have both fields hidden so is harder to test manually.
@@ -407,7 +461,7 @@ public function testPublishUnpublishAction() {
     $node4 = $this->drupalCreateNode([
       'type' => $this->type,
       'status' => TRUE,
-      'title' => 'Blue Legacy will cause a failure on unpublishing',
+      'title' => 'Blue will cause a failure on unpublishing',
       'unpublish_on' => strtotime('-1 day'),
     ]);
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBasicMediaTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerBasicMediaTest.php
deleted file mode 100644
index 5ceb50b7e3d3722af8230b3a8141df42f29edd05..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBasicMediaTest.php
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests the modules primary functions with a Media entity type.
- *
- * @group scheduler
- */
-class SchedulerBasicMediaTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Tests scheduled publishing of a media entity.
-   *
-   * Covers scheduler_entity_presave(), scheduler_cron(),
-   * schedulerManager::publish.
-   */
-  public function testMediaPublishing() {
-    // Specify values for the entity.
-    $values = [
-      'name' => 'Publish This Media',
-      'publish_on' => $this->requestTime + 3600,
-    ];
-    // Create a media entity with the scheduler fields populated as required.
-    $entity = $this->createMediaItem($values);
-    $this->assertNotEmpty($entity, 'The entity was created sucessfully.');
-
-    // Assert that the entity has a publish_on date.
-    $this->assertNotEmpty($entity->publish_on, 'The entity has a publish_on date');
-
-    // Assert that the entity is not published before cron.
-    $this->assertFalse($entity->isPublished(), 'The entity is unpublished before cron run');
-
-    // Modify the scheduler field to a time in the past, then run cron.
-    $entity->publish_on = $this->requestTime - 1;
-    $entity->save();
-    $this->cronRun();
-
-    // Refresh the cache, reload the entity and check the entity is published.
-    $this->mediaStorage->resetCache([$entity->id()]);
-    $entity = $this->mediaStorage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), 'The entity is published after cron run');
-  }
-
-  /**
-   * Tests scheduled publishing of a media entity when action is missing.
-   */
-  public function testMissingActionMediaPublishing() {
-    $this->deleteAction('media_scheduler', 'publish');
-    $this->testMediaPublishing();
-  }
-
-  /**
-   * Tests scheduled unpublishing of a media entity.
-   *
-   * Covers scheduler_entity_presave(), scheduler_cron(),
-   * schedulerManager::unpublish.
-   */
-  public function testMediaUnpublishing() {
-    // Specify values for the entity.
-    $values = [
-      'name' => 'Unpublish This Media',
-      'unpublish_on' => $this->requestTime + 3600,
-    ];
-    // Create a media entity with the scheduler fields populated as required.
-    $entity = $this->createMediaItem($values);
-    $this->assertNotEmpty($entity, 'The entity was created sucessfully.');
-
-    // Assert that the entity has an unpublish_on date.
-    $this->assertNotEmpty($entity->unpublish_on, 'The entity has an unpublish_on date');
-
-    // Assert that the entity is published before cron.
-    $this->assertTrue($entity->isPublished(), 'The entity is published before cron run');
-
-    // Modify the scheduler field to a time in the past, then run cron.
-    $entity->unpublish_on = $this->requestTime - 1;
-    $entity->save();
-    $this->cronRun();
-
-    // Refresh the cache, reload the entity and check the entity is unpublished.
-    $this->mediaStorage->resetCache([$entity->id()]);
-    $entity = $this->mediaStorage->load($entity->id());
-    $this->assertFalse($entity->isPublished(), 'The entity is unpublished after cron run');
-
-  }
-
-  /**
-   * Tests scheduled unpublishing of a media entity when action is missing.
-   */
-  public function testMissingActionMediaUnpublishing() {
-    $this->deleteAction('media_scheduler', 'unpublish');
-    $this->testMediaUnpublishing();
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBasicProductTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerBasicProductTest.php
deleted file mode 100644
index a147dc82b31303f3886118d7dd011fa168655af4..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBasicProductTest.php
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests the modules primary functions with a Commerce Product entity type.
- *
- * @group scheduler
- */
-class SchedulerBasicProductTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Tests scheduled publishing of a commerce product entity.
-   *
-   * Covers scheduler_entity_presave(), scheduler_cron(),
-   * schedulerManager::publish.
-   */
-  public function testProductPublishing() {
-    // Specify values for the entity.
-    $values = [
-      'title' => 'Publish This Product',
-      'publish_on' => $this->requestTime + 3600,
-    ];
-    // Create a product entity with the scheduler fields populated as required.
-    $entity = $this->createProduct($values);
-    $this->assertNotEmpty($entity, 'The entity was created sucessfully.');
-
-    // Assert that the entity has a publish_on date.
-    $this->assertNotEmpty($entity->publish_on, 'The entity has a publish_on date');
-
-    // Assert that the entity is not published before cron.
-    $this->assertFalse($entity->isPublished(), 'The entity is unpublished before cron run');
-
-    // Modify the scheduler field to a time in the past, then run cron.
-    $entity->publish_on = $this->requestTime - 1;
-    $entity->save();
-    $this->cronRun();
-
-    // Refresh the cache, reload the entity and check the entity is published.
-    $this->productStorage->resetCache([$entity->id()]);
-    $entity = $this->productStorage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), 'The entity is published after cron run');
-  }
-
-  /**
-   * Tests scheduled publishing of a product when action is missing.
-   */
-  public function testMissingActionProductPublishing() {
-    $this->deleteAction('commerce_product_scheduler', 'publish');
-    $this->testProductPublishing();
-  }
-
-  /**
-   * Tests scheduled unpublishing of a commerce product entity.
-   *
-   * Covers scheduler_entity_presave(), scheduler_cron(),
-   * schedulerManager::unpublish.
-   */
-  public function testProductUnpublishing() {
-    // Specify values for the entity.
-    $values = [
-      'title' => 'Unpublish This Product',
-      'unpublish_on' => $this->requestTime + 3600,
-    ];
-    // Create a product with the scheduler fields populated as required.
-    $entity = $this->createProduct($values);
-    $this->assertNotEmpty($entity, 'The entity was created sucessfully.');
-
-    // Assert that the entity has an unpublish_on date.
-    $this->assertNotEmpty($entity->unpublish_on, 'The entity has an unpublish_on date');
-
-    // Assert that the entity is published before cron.
-    $this->assertTrue($entity->isPublished(), 'The entity is published before cron run');
-
-    // Modify the scheduler field to a time in the past, then run cron.
-    $entity->unpublish_on = $this->requestTime - 1;
-    $entity->save();
-    $this->cronRun();
-
-    // Refresh the cache, reload the entity and check the entity is unpublished.
-    $this->productStorage->resetCache([$entity->id()]);
-    $entity = $this->productStorage->load($entity->id());
-    $this->assertFalse($entity->isPublished(), 'The entity is unpublished after cron run');
-
-  }
-
-  /**
-   * Tests scheduled unpublishing of a product when action is missing.
-   */
-  public function testMissingActionProductUnpublishing() {
-    $this->deleteAction('commerce_product_scheduler', 'unpublish');
-    $this->testProductUnpublishing();
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTaxonomyTermTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTaxonomyTermTest.php
deleted file mode 100644
index 0d660e99b81b534fd068da38c7d850b186b3e898..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTaxonomyTermTest.php
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests the modules primary functions with a Taxonomy Term entity type.
- *
- * @group scheduler
- */
-class SchedulerBasicTaxonomyTermTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Tests scheduled publishing of a taxonomy term entity.
-   *
-   * Covers scheduler_entity_presave(), scheduler_cron(),
-   * schedulerManager::publish.
-   */
-  public function testTaxonomyTermPublishing() {
-    // Specify values for the entity.
-    $values = [
-      'name' => 'Publish This Taxonomy Term',
-      'publish_on' => $this->requestTime + 3600,
-    ];
-    // Create a taxonomy term with the scheduler fields populated as required.
-    $entity = $this->createTaxonomyTerm($values);
-    $this->assertNotEmpty($entity, 'The entity was created sucessfully.');
-
-    // Assert that the entity has a publish_on date.
-    $this->assertNotEmpty($entity->publish_on, 'The entity has a publish_on date');
-
-    // Assert that the entity is not published before cron.
-    $this->assertFalse($entity->isPublished(), 'The entity is unpublished before cron run');
-
-    // Modify the scheduler field to a time in the past, then run cron.
-    $entity->publish_on = $this->requestTime - 1;
-    $entity->save();
-    $this->cronRun();
-
-    // Refresh the cache, reload the entity and check the entity is published.
-    $this->taxonomyTermStorage->resetCache([$entity->id()]);
-    $entity = $this->taxonomyTermStorage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), 'The entity is published after cron run');
-  }
-
-  /**
-   * Tests scheduled publishing of a taxonomy term when action is missing.
-   */
-  public function testMissingActionTaxonomyTermPublishing() {
-    $this->deleteAction('taxonomy_term_scheduler', 'publish');
-    $this->testTaxonomyTermPublishing();
-  }
-
-  /**
-   * Tests scheduled unpublishing of a taxonomy term.
-   *
-   * Covers scheduler_entity_presave(), scheduler_cron(),
-   * schedulerManager::unpublish.
-   */
-  public function testTaxonomyTermUnpublishing() {
-    // Specify values for the entity.
-    $values = [
-      'name' => 'Unpublish This Taxonomy Term',
-      'unpublish_on' => $this->requestTime + 3600,
-    ];
-    // Create a taxonomy term with the scheduler fields populated as required.
-    $entity = $this->createTaxonomyTerm($values);
-    $this->assertNotEmpty($entity, 'The entity was created sucessfully.');
-
-    // Assert that the entity has an unpublish_on date.
-    $this->assertNotEmpty($entity->unpublish_on, 'The entity has an unpublish_on date');
-
-    // Assert that the entity is published before cron.
-    $this->assertTrue($entity->isPublished(), 'The entity is published before cron run');
-
-    // Modify the scheduler field to a time in the past, then run cron.
-    $entity->unpublish_on = $this->requestTime - 1;
-    $entity->save();
-    $this->cronRun();
-
-    // Refresh the cache, reload the entity and check the entity is unpublished.
-    $this->taxonomyTermStorage->resetCache([$entity->id()]);
-    $entity = $this->taxonomyTermStorage->load($entity->id());
-    $this->assertFalse($entity->isPublished(), 'The entity is unpublished after cron run');
-
-  }
-
-  /**
-   * Tests scheduled unpublishing of a taxonomy term when action is missing.
-   */
-  public function testMissingActionTaxonomyTermUnpublishing() {
-    $this->deleteAction('taxonomy_term_scheduler', 'unpublish');
-    $this->testTaxonomyTermUnpublishing();
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBasicNodeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
similarity index 84%
rename from web/modules/scheduler/tests/src/Functional/SchedulerBasicNodeTest.php
rename to web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
index 3c4bd0f78b7f38e8dfb8ba3ffab794ce9dead706..ab286338fd2d50490b8d0188a2fae3fa1caca1b1 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBasicNodeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
@@ -7,12 +7,12 @@
  *
  * @group scheduler
  */
-class SchedulerBasicNodeTest extends SchedulerBrowserTestBase {
+class SchedulerBasicTest extends SchedulerBrowserTestBase {
 
   /**
    * Tests basic scheduling of content.
    */
-  public function testNodePublishingAndUnpublishing() {
+  public function testPublishingAndUnpublishing() {
     // Login is required here before creating the publish_on date and time
     // values so that $this->dateFormatter can utilise the current users
     // timezone. The constraints receive values which have been converted using
@@ -37,23 +37,13 @@ public function testNodePublishingAndUnpublishing() {
     $this->helpTestScheduler($edit);
   }
 
-  /**
-   * Tests scheduled publishing/unpublishing of a node when actions are missing.
-   */
-  public function testMissingActionNodePublishingAndUnpublishing() {
-    $this->deleteAction('node_scheduler', 'publish');
-    $this->deleteAction('node_scheduler', 'unpublish');
-    $this->testNodePublishingAndUnpublishing();
-  }
-
   /**
    * Helper function for testPublishingAndUnpublishing().
    *
    * Schedules content, runs cron and asserts status.
    */
   protected function helpTestScheduler($edit) {
-    $this->drupalGet("node/add/{$this->type}");
-    $this->submitForm($edit, 'Save');
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     // Verify that the node was created.
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertNotEmpty($node, sprintf('"%s" was created sucessfully.', $edit['title[0][value]']));
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
index b0a4104b5fbe5aa8053e810ab66f1ebcb56a7726..0a84666f81b499baa94cefd5f83e80206c5796ab 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
@@ -3,20 +3,14 @@
 namespace Drupal\Tests\scheduler\Functional;
 
 use Drupal\Tests\BrowserTestBase;
-use Drupal\Tests\scheduler\Traits\SchedulerCommerceProductSetupTrait;
-use Drupal\Tests\scheduler\Traits\SchedulerMediaSetupTrait;
 use Drupal\Tests\scheduler\Traits\SchedulerSetupTrait;
-use Drupal\Tests\scheduler\Traits\SchedulerTaxonomyTermSetupTrait;
 
 /**
  * Base class to provide common browser test setup.
  */
 abstract class SchedulerBrowserTestBase extends BrowserTestBase {
 
-  use SchedulerCommerceProductSetupTrait;
-  use SchedulerMediaSetupTrait;
   use SchedulerSetupTrait;
-  use SchedulerTaxonomyTermSetupTrait;
 
   /**
    * The standard modules to load for all browser tests.
@@ -25,13 +19,7 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase {
    *
    * @var array
    */
-  protected static $modules = [
-    'scheduler',
-    'dblog',
-    'media',
-    'commerce_product',
-    'taxonomy',
-  ];
+  protected static $modules = ['scheduler', 'dblog'];
 
   /**
    * The profile to install as a basis for testing.
@@ -50,22 +38,9 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    // Call the common set-up functions defined in the traits.
+
     $this->schedulerSetUp();
-    // $this->getName() includes the test class and the dataProvider key. We can
-    // use this to save time and resources by avoiding calls to the media and
-    // product setup functions when they are not needed. The exception is the
-    // permissions tests, which use all entities for all tests.
-    $testName = $this->getName();
-    if (stristr($testName, 'media') || stristr($testName, 'permission')) {
-      $this->schedulerMediaSetUp();
-    }
-    if (stristr($this->getName(), 'product') || stristr($testName, 'permission')) {
-      $this->SchedulerCommerceProductSetUp();
-    }
-    if (stristr($this->getName(), 'taxonomy') || stristr($testName, 'permission')) {
-      $this->SchedulerTaxonomyTermSetup();
-    }
+
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
index 91506f6794a902f7ef049652cc65ad23413e8586..ad2c5127745ab6621ca3ea53788ec8401749eccc 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
@@ -5,15 +5,17 @@
 /**
  * Tests the default time functionality.
  *
- * The test helper module scheduler_extras is used in testDefaultWithHiddenTime.
- * To reduce complexity, and avoid having to create custom entity types, it acts
- * on the standard test entity types. Hence the module is only enabled in that
- * test, not via a protected static $modules declaration.
- *
  * @group scheduler
  */
 class SchedulerDefaultTimeTest extends SchedulerBrowserTestBase {
 
+  /**
+   * Additional modules required.
+   *
+   * @var array
+   */
+  protected static $modules = ['scheduler_extras'];
+
   /**
    * The default time.
    *
@@ -62,13 +64,10 @@ protected function setUp(): void {
    *
    * This test covers the default scenario where the dates are optional and not
    * required. A javascript test covers the cases where the dates are required.
-   *
-   * @dataProvider dataStandardEntityTypes()
    */
-  public function testDefaultTime($entityTypeId, $bundle) {
+  public function testDefaultTime() {
     $this->drupalLogin($this->schedulerUser);
     $config = $this->config('scheduler.settings');
-    $titleField = $this->titleField($entityTypeId);
 
     // We cannot easily test the full validation message as they contain the
     // current time which can be one or two seconds in the past. The best we can
@@ -81,28 +80,24 @@ public function testDefaultTime($entityTypeId, $bundle) {
     $config->set('allow_date_only', FALSE)->save();
 
     // Test that entering a time is required.
-    $title = 'No time ' . $this->randomMachineName(8);
     $edit = [
-      "{$titleField}[0][value]" => $title,
+      'title[0][value]' => 'No time ' . $this->randomMachineName(8),
       'publish_on[0][value][date]' => $this->publishTime->format('Y-m-d'),
       'unpublish_on[0][value][date]' => $this->unpublishTime->format('Y-m-d'),
     ];
-    // Create an entity and check that the expected error messages are shown.
-    $add_url = $this->entityAddUrl($entityTypeId, $bundle);
-    $this->drupalGet($add_url);
-    $this->submitForm($edit, 'Save');
+    // Create a node and check that the expected error messages are shown.
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     // By default it is required to enter a time when scheduling content for
     // publishing and for unpublishing.
-    $this->assertSession()->pageTextNotMatches('/' . $title . ' is scheduled to be published .* and unpublished .*/');
+    $this->assertSession()->pageTextNotMatches('/' . $edit['title[0][value]'] . ' is scheduled to be published .* and unpublished .*/');
     $this->assertSession()->pageTextContains($publish_validation_message);
     $this->assertSession()->pageTextContains($unpublish_validation_message);
 
     // Allow the user to enter only a date with no time.
     $config->set('allow_date_only', TRUE)->save();
 
-    // Create an entity and check that the validation messages are not shown.
-    $this->drupalGet($add_url);
-    $this->submitForm($edit, 'Save');
+    // Create a node and check that the validation messages are not shown.
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $this->assertSession()->pageTextNotContains($publish_validation_message);
     $this->assertSession()->pageTextNotContains($unpublish_validation_message);
 
@@ -110,57 +105,66 @@ public function testDefaultTime($entityTypeId, $bundle) {
     $date_format_storage = $this->container->get('entity_type.manager')->getStorage('date_format');
     $long_pattern = $date_format_storage->load('long')->getPattern();
 
-    // Check that the scheduled information is shown after saving and that the
-    // time is correct.
+    // Check that the scheduled information is shown after saving.
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s and unpublished %s',
-      $title, $this->publishTime->format($long_pattern), $this->unpublishTime->format($long_pattern)));
+      $edit['title[0][value]'], $this->publishTime->format($long_pattern), $this->unpublishTime->format($long_pattern)));
 
-    if ($entity = $this->getEntityByTitle($entityTypeId, $title)) {
-      // Check that the correct scheduled dates are stored in the entity.
-      $this->assertEquals($this->publishTime->getTimestamp(), (int) $entity->publish_on->value, 'The publish_on value is stored correctly.');
-      $this->assertEquals($this->unpublishTime->getTimestamp(), (int) $entity->unpublish_on->value, 'The unpublish_on value is stored correctly.');
+    // Protect this section in case the node was not created.
+    if ($node = $this->drupalGetNodeByTitle($edit['title[0][value]'])) {
+      // Check that the correct scheduled dates are stored in the node.
+      $this->assertEquals($this->publishTime->getTimestamp(), (int) $node->publish_on->value, 'The node publish_on value is stored correctly.');
+      $this->assertEquals($this->unpublishTime->getTimestamp(), (int) $node->unpublish_on->value, 'The node unpublish_on value is stored correctly.');
 
       // Check that the default time has been added to the form on edit.
-      $this->drupalGet($entity->toUrl('edit-form'));
+      $this->drupalGet('node/' . $node->id() . '/edit');
       $this->assertSession()->FieldValueEquals('publish_on[0][value][time]', $this->defaultTime);
       $this->assertSession()->FieldValueEquals('unpublish_on[0][value][time]', $this->defaultTime);
     }
     else {
-      $this->fail('The expected entity was not found.');
+      $this->fail('The expected node was not found.');
     }
   }
 
   /**
    * Test that the default times are set if the form time elements are hidden.
-   *
-   * This test uses the 'scheduler_extras' helper module, which hides the time
-   * elements of both of the scheduler date input fields.
-   *
-   * @dataProvider dataStandardEntityTypes()
    */
-  public function testDefaultWithHiddenTime($entityTypeId, $bundle) {
-    \Drupal::service('module_installer')->install(['scheduler_extras']);
-    $titleField = $this->titleField($entityTypeId);
-    $this->drupalLogin($this->schedulerUser);
+  public function testDefaultTimeWithHiddenTime() {
+    // Create a content type that will have both of the time form elements
+    // hidden. See hook_form_node_form_alter() in scheduler_extras test module.
+    $type = 'hidden_time';
+    $typeName = 'Content with hidden times';
+    /** @var NodeTypeInterface $nodetype */
+    $nodetype = $this->drupalCreateContentType([
+      'type' => $type,
+      'name' => $typeName,
+    ]);
+
+    // Add scheduler functionality to the content type.
+    $nodetype->setThirdPartySetting('scheduler', 'publish_enable', TRUE)
+      ->setThirdPartySetting('scheduler', 'unpublish_enable', TRUE)
+      ->save();
+
+    // Log in as a user with permission to create and schedule this type.
+    $this->drupalLogin($this->drupalCreateUser([
+      'create ' . $type . ' content',
+      'edit own ' . $type . ' content',
+      'delete own ' . $type . ' content',
+      'view own unpublished content',
+      'schedule publishing of nodes',
+      'view scheduled content',
+    ]));
 
     // Allow the user to enter only a date with no time.
     $this->config('scheduler.settings')->set('allow_date_only', TRUE)->save();
 
-    // Define date values but no time values.
-    $title = 'Hidden Time Elements ' . $this->randomMachineName(8);
+    // Test that entering a time is required.
     $edit = [
-      "{$titleField}[0][value]" => $title,
+      'title[0][value]' => 'Hidden Time Elements ' . $this->randomMachineName(8),
       'publish_on[0][value][date]' => $this->publishTime->format('Y-m-d'),
       'unpublish_on[0][value][date]' => $this->unpublishTime->format('Y-m-d'),
     ];
-
-    // Create an entity and check that the time fields are hidden.
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->assertSession()->FieldExists('publish_on[0][value][date]');
-    $this->assertSession()->FieldExists('unpublish_on[0][value][date]');
-    $this->assertSession()->FieldNotExists('publish_on[0][value][time]');
-    $this->assertSession()->FieldNotExists('unpublish_on[0][value][time]');
-    $this->submitForm($edit, 'Save');
+    // Create a node and check that the expected default time has been saved.
+    $this->drupalPostForm('node/add/' . $type, $edit, 'Save');
 
     // Get the pattern of the 'long' default date format.
     $date_format_storage = $this->container->get('entity_type.manager')->getStorage('date_format');
@@ -168,15 +172,16 @@ public function testDefaultWithHiddenTime($entityTypeId, $bundle) {
 
     // Check that the message has the correct default time after saving.
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s and unpublished %s',
-      $title, $this->publishTime->format($long_pattern), $this->unpublishTime->format($long_pattern)));
+      $edit['title[0][value]'], $this->publishTime->format($long_pattern), $this->unpublishTime->format($long_pattern)));
 
-    if ($entity = $this->getEntityByTitle($entityTypeId, $title)) {
+    // Protect this section in case the node was not created.
+    if ($node = $this->drupalGetNodeByTitle($edit['title[0][value]'])) {
       // Check that the correct scheduled dates are stored in the node.
-      $this->assertEquals($this->publishTime->getTimestamp(), (int) $entity->publish_on->value, 'The publish_on value is stored correctly.');
-      $this->assertEquals($this->unpublishTime->getTimestamp(), (int) $entity->unpublish_on->value, 'The unpublish_on value is stored correctly.');
+      $this->assertEquals($this->publishTime->getTimestamp(), (int) $node->publish_on->value, 'The node publish_on value is stored correctly.');
+      $this->assertEquals($this->unpublishTime->getTimestamp(), (int) $node->unpublish_on->value, 'The node unpublish_on value is stored correctly.');
     }
     else {
-      $this->fail('The expected entity was not found.');
+      $this->fail('The expected node was not found.');
     }
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteEntityTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteEntityTest.php
deleted file mode 100644
index f2ad0e208375f53b448fb5c1184a3ecf4fe043f9..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteEntityTest.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests deletion of entities enabled for Scheduler.
- *
- * This checks how the deletion of an entity interacts with the Scheduler
- * 'required' options and scheduled dates in the past.
- *
- * @group scheduler
- */
-class SchedulerDeleteEntityTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Tests the deletion of an entity when the scheduler dates are required.
-   *
-   * Check that it is possible to delete an entity that does not have a
-   * publishing date set, when scheduled publishing is required.
-   * Likewise for unpublishing.
-   *
-   * @see https://www.drupal.org/project/scheduler/issues/1614880
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testDeleteEntityWhenSchedulingIsRequired($entityTypeId, $bundle) {
-    // Log in.
-    $this->drupalLogin($this->adminUser);
-
-    // Create a published and an unpublished entity, with no scheduled dates.
-    $published_entity = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-    ]);
-    $unpublished_entity = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-    ]);
-
-    // Make scheduled publishing and unpublishing required.
-    $bundle_field_name = $published_entity->getEntityType()->get('entity_keys')['bundle'];
-    $published_entity->$bundle_field_name->entity->setThirdPartySetting('scheduler', 'publish_required', TRUE)
-      ->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)
-      ->save();
-    $entity_type_label = $published_entity->getEntityType()->getSingularLabel();
-
-    // Check that deleting the entity does not throw form validation errors.
-    $this->drupalGet($published_entity->toUrl('edit-form'));
-    $this->clickLink('Delete');
-    // The text 'error message' is used in a header h2 html tag which is
-    // normally made hidden from browsers but will be in the page source.
-    // It is also good when testing for the absense of something to also test
-    // for the presence of text, hence the second assertion for each check.
-    $this->assertSession()->pageTextNotContains('Error message');
-    $this->assertSession()->pageTextContains("Are you sure you want to delete the $entity_type_label {$published_entity->label()}");
-
-    // Do the same test for the unpublished entity.
-    $this->drupalGet($unpublished_entity->toUrl('edit-form'));
-    $this->clickLink('Delete');
-    $this->assertSession()->pageTextNotContains('Error message');
-    $this->assertSession()->pageTextContains("Are you sure you want to delete the $entity_type_label {$unpublished_entity->label()}");
-  }
-
-  /**
-   * Tests the deletion of scheduled entities.
-   *
-   * Check that entities can be deleted with no validation errors even if the
-   * dates are in the past.
-   *
-   * @see https://www.drupal.org/project/scheduler/issues/2627370
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testDeleteEntityWithPastDates($entityTypeId, $bundle) {
-    // Log in.
-    $this->drupalLogin($this->adminUser);
-
-    // Create entities with publish_on and unpublish_on dates in the past.
-    $published_entity = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'unpublish_on' => strtotime('- 2 day'),
-    ]);
-    $unpublished_entity = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'publish_on' => strtotime('- 2 day'),
-    ]);
-    $entity_type_label = $published_entity->getEntityType()->getSingularLabel();
-
-    // Attempt to delete the published entity and check for no validation error.
-    $this->drupalGet($published_entity->toUrl('edit-form'));
-    $this->clickLink('Delete');
-    $this->assertSession()->pageTextNotContains('Error message');
-    $this->assertSession()->pageTextContains("Are you sure you want to delete the $entity_type_label {$published_entity->label()}");
-
-    // Attempt to delete the unpublished entity and check no validation error.
-    $this->drupalGet($unpublished_entity->toUrl('edit-form'));
-    $this->clickLink('Delete');
-    $this->assertSession()->pageTextNotContains('Error message');
-    $this->assertSession()->pageTextContains("Are you sure you want to delete the $entity_type_label {$unpublished_entity->label()}");
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd7a16f1744a7f8fc5f8f997e5ff868a06bac5be
--- /dev/null
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\Tests\scheduler\Functional;
+
+/**
+ * Tests deletion of nodes enabled for Scheduler.
+ *
+ * This checks how the deletion of a node interacts with the Scheduler
+ * 'required' options and scheduled dates in the past.
+ *
+ * @group scheduler
+ */
+class SchedulerDeleteNodeTest extends SchedulerBrowserTestBase {
+
+  /**
+   * Tests the deletion of a scheduled node.
+   *
+   * Check that it is possible to delete a node that does not have a publishing
+   * date set, when scheduled publishing is required. Likewise for unpublishing.
+   *
+   * @see https://drupal.org/node/1614880
+   */
+  public function testDeleteNodeWhenSchedulingIsRequired() {
+    // Log in.
+    $this->drupalLogin($this->adminUser);
+
+    // Create a published and an unpublished node, both without scheduled dates.
+    $published_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => 1,
+    ]);
+    $unpublished_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => 0,
+    ]);
+
+    // Make scheduled publishing and unpublishing required.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', TRUE)
+      ->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)
+      ->save();
+
+    // Check that deleting the nodes does not throw form validation errors.
+    $this->drupalGet('node/' . $published_node->id() . '/edit');
+    $this->clickLink('Delete');
+    // The text 'error message' is used in a header h2 html tag which is
+    // normally made hidden from browsers but will be in the page source.
+    // It is also good when testing for the absense of something to also test
+    // for the presence of text, hence the second assertion for each check.
+    $this->assertSession()->pageTextNotContains('Error message');
+    $this->assertSession()->pageTextContains('Are you sure you want to delete the content');
+
+    // Do the same test for the unpublished node.
+    $this->drupalGet('node/' . $unpublished_node->id() . '/edit');
+    $this->clickLink('Delete');
+    $this->assertSession()->pageTextNotContains('Error message');
+    $this->assertSession()->pageTextContains('Are you sure you want to delete the content');
+  }
+
+  /**
+   * Tests the deletion of a scheduled node.
+   *
+   * Check that nodes can be deleted with no validation errors if the dates are
+   * in the past.
+   *
+   * @see http://drupal.org/node/2627370
+   */
+  public function testDeleteNodeWithPastDates() {
+    // Log in.
+    $this->drupalLogin($this->adminUser);
+
+    // Create nodes with publish_on and unpublish_on dates in the past.
+    $published_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'unpublish_on' => strtotime('- 2 day'),
+    ]);
+    $unpublished_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'publish_on' => strtotime('- 2 day'),
+    ]);
+
+    // Attempt to delete the published node and check for no validation error.
+    $this->drupalGet('node/' . $published_node->id() . '/edit');
+    $this->clickLink('Delete');
+    $this->assertSession()->pageTextNotContains('Error message');
+    $this->assertSession()->pageTextContains('Are you sure you want to delete the content');
+
+    // Attempt to delete the unpublished node and check for no validation error.
+    $this->drupalGet('node/' . $unpublished_node->id() . '/edit');
+    $this->clickLink('Delete');
+    $this->assertSession()->pageTextNotContains('Error message');
+    $this->assertSession()->pageTextContains('Are you sure you want to delete the content');
+  }
+
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php
index 63d1cfa19dcb335911210aa4c9c8b14c938708f8..b4834414563af4e469a47166ae4a5abfa9dddc86 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php
@@ -6,6 +6,10 @@
  * Tests the Scheduler interaction with Devel Generate module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Devel no longer uses the deprecated
+ * $published parameter for setPublished(), and does not use functions
+ * drupal_set_message(), format_date() and db_query_range().
  */
 class SchedulerDevelGenerateTest extends SchedulerBrowserTestBase {
 
@@ -22,53 +26,51 @@ class SchedulerDevelGenerateTest extends SchedulerBrowserTestBase {
   protected function setUp(): void {
     parent::setUp();
 
-    // Add Devel Generate permission to the admin user.
-    $this->addPermissionsToUser($this->adminUser, [
+    // Create a user with devel permission. Only 'administer devel_generate' is
+    // actually required for these tests, but the others are useful.
+    // 'access content overview' is needed for /admin/content  (but it is empty)
+    // 'access content' is required to actually see the content list data.
+    // 'view scheduled content' is required for /admin/content/scheduled.
+    $this->develUser = $this->drupalCreateUser([
       'administer devel_generate',
+      'view scheduled content',
+      'access content',
+      'access content overview',
     ]);
-
   }
 
   /**
-   * Helper function to count scheduled entities and assert the expected number.
+   * Helper function to count scheduled nodes and assert the expected number.
    *
    * @param string $type
-   *   The machine-name for the entity type to be checked.
-   * @param string $bundle_field
-   *   The name of the field which contains the bundle.
-   * @param string $bundle
-   *   The machine-name for the bundle/content type to be checked.
-   * @param string $scheduler_field
+   *   The machine-name for the content type to be checked.
+   * @param string $field
    *   The field name to count, either 'publish_on' or 'unpublish_on'.
-   * @param int $num_total
-   *   The total number of entities that should exist.
+   * @param int $num_nodes
+   *   The total number of nodes that should exist.
    * @param int $num_scheduled
-   *   The number of entities which should have a value in $scheduler_field.
+   *   The number of those nodes which should be scheduled with a $field.
    * @param int $time_range
    *   Optional time range from the devel form. The generated scheduler dates
    *   should be in a range of +/- this value from the current time.
    */
-  protected function countScheduledEntities($type, $bundle_field, $bundle, $scheduler_field, $num_total, $num_scheduled, $time_range = NULL) {
-    $storage = $this->entityStorageObject($type);
-
-    // Check that the expected number of entities have been created.
-    $count = $storage->getQuery()
-      ->accessCheck(FALSE)
-      ->condition($bundle_field, $bundle)
+  protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled, $time_range = NULL) {
+    // Check that the expected number of nodes have been created.
+    $count = $this->nodeStorage->getQuery()
+      ->condition('type', $type)
       ->count()
       ->execute();
-    $this->assertEquals($num_total, $count, sprintf('The expected number of %s %s is %s, found %s', $bundle, $type, $num_total, $count));
+    $this->assertEquals($num_nodes, $count, sprintf('The expected number of %s is %s, found %s', $type, $num_nodes, $count));
 
-    // Check that the expected number of entities have been scheduled.
-    $count = $storage->getQuery()
-      ->accessCheck(FALSE)
-      ->condition($bundle_field, $bundle)
-      ->exists($scheduler_field)
+    // Check that the expected number of nodes have been scheduled.
+    $count = $this->nodeStorage->getQuery()
+      ->condition('type', $type)
+      ->exists($field)
       ->count()
       ->execute();
-    $this->assertEquals($num_scheduled, $count, sprintf('The expected number of %s %s with scheduled %s is %s, found %s', $bundle, $type, $scheduler_field, $num_total, $count));
+    $this->assertEquals($num_scheduled, $count, sprintf('The expected number of scheduled %s is %s, found %s', $field, $num_scheduled, $count));
 
-    if (isset($time_range) && $num_scheduled > 0) {
+    if (isset($time_range)) {
       // Define the minimum and maximum times that we expect the scheduled dates
       // to be within. REQUEST_TIME remains static for the duration of this test
       // but even though devel_generate also uses uses REQUEST_TIME this will
@@ -78,118 +80,98 @@ protected function countScheduledEntities($type, $bundle_field, $bundle, $schedu
       $min = $this->requestTime - $time_range;
       $max = time() + $time_range;
 
-      $query = $storage->getAggregateQuery();
+      $query = $this->nodeStorage->getAggregateQuery();
       $result = $query
-        ->accessCheck(FALSE)
-        ->condition($bundle_field, $bundle)
-        ->aggregate($scheduler_field, 'min')
-        ->aggregate($scheduler_field, 'max')
+        ->condition('type', $type)
+        ->aggregate($field, 'min')
+        ->aggregate($field, 'max')
         ->execute();
-      $min_found = $result[0]["{$scheduler_field}_min"];
-      $max_found = $result[0]["{$scheduler_field}_max"];
+      $min_found = $result[0]["{$field}_min"];
+      $max_found = $result[0]["{$field}_max"];
 
-      // Assert that the found values are within the expected range.
-      $this->assertGreaterThanOrEqual($min, $min_found, sprintf('The minimum value found for %s is %s, earlier than the expected %s', $scheduler_field, $this->dateFormatter->format($min_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($min, 'custom', 'j M, H:i:s')));
-      $this->assertLessThanOrEqual($max, $max_found, sprintf('The maximum value found for %s is %s, later than the expected %s', $scheduler_field, $this->dateFormatter->format($max_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($max, 'custom', 'j M, H:i:s')));
+      // Assert that the found values are within the expcted range.
+      $this->assertGreaterThanOrEqual($min, $min_found, sprintf('The minimum value for %s is %s, smaller than the expected %s', $field, $this->dateFormatter->format($min_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($min, 'custom', 'j M, H:i:s')));
+      $this->assertLessThanOrEqual($max, $max_found, sprintf('The maximum value for %s is %s which is larger than expected %s', $field, $this->dateFormatter->format($max_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($max, 'custom', 'j M, H:i:s')));
     }
   }
 
   /**
-   * Test the functionality that Scheduler adds during entity generation.
-   *
-   * @dataProvider dataDevelGenerate()
+   * Test the functionality that Scheduler adds during content generation.
    */
-  public function testDevelGenerate($entityTypeId, $enabled) {
-    $this->drupalLogin($this->adminUser);
-    $entityType = $this->entityTypeObject($entityTypeId, $enabled ? NULL : 'non-enabled');
-    $bundle = $entityType->id();
-    $bundle_field = $this->container->get('entity_type.manager')
-      ->getDefinition($entityTypeId)->get('entity_keys')['bundle'];
-
-    // Use just the minimum settings that are required, to see what happens when
-    // everything else is left as default. The devel_generate form has a
-    // selection list of vocabularies when generating terms but has a table of
-    // checkboxes to chose which node and media types to generate.
-    if ($entityTypeId == 'taxonomy_term') {
-      $entity_selection = ['vids[]' => ["$bundle" => "$bundle"]];
-    }
-    else {
-      $entity_selection = ["{$entityTypeId}_types[$bundle]" => TRUE];
-    }
-    $this->drupalGet($this->adminUrl('generate', $entityTypeId, $bundle));
-    $this->submitForm($entity_selection, 'Generate');
+  public function testDevelGenerate() {
+    $this->drupalLogin($this->develUser);
 
-    // Display the full content list and the scheduled list for the entity type
-    // being generated. Calls to these pages are for information and debug only.
-    // The default number of entities to create varies across the different
-    // devel_generate plugins, therefore we do not count any on this first run.
-    $this->drupalGet($this->adminUrl('collection', $entityTypeId, $bundle));
-    $this->drupalGet($this->adminUrl('scheduled', $entityTypeId, $bundle));
+    // Use the minimum required settings to see what happens when everything
+    // else is left as default.
+    $generate_settings = [
+      "edit-node-types-$this->type" => TRUE,
+    ];
+    $this->drupalPostForm('admin/config/development/generate/content', $generate_settings, 'Generate');
+    // Display the full content list and the scheduled list. Calls to these
+    // pages are for information and debug only. They could be removed.
+    $this->drupalGet('admin/content');
+    $this->drupalGet('admin/content/scheduled');
 
     // Delete all content for this type and generate new content with only
     // publish-on dates. Use 100% as this is how we can count the expected
-    // number of scheduled entities. The time range of 3600 is one hour.
-    // The number of entities has to be lower than 50 until the Devel issue with
+    // number of scheduled nodes. The time range of 3600 is one hour.
+    // The number of nodes has to be lower than 50 until Devel issue with
     // undefined index 'users' is available and we switch to using 8.x-3.0
     // See https://www.drupal.org/project/devel/issues/3076613
-    $generate_settings = $entity_selection + [
+    $generate_settings = [
+      "edit-node-types-$this->type" => TRUE,
       'num' => 40,
       'kill' => TRUE,
       'time_range' => 3600,
       'scheduler_publishing' => 100,
       'scheduler_unpublishing' => 0,
     ];
-    $this->drupalGet($this->adminUrl('generate', $entityTypeId, $bundle));
-    $this->submitForm($generate_settings, 'Generate');
-    // Display the full content list and the scheduled content list.
-    $this->drupalGet($this->adminUrl('collection', $entityTypeId, $bundle));
-    $this->drupalGet($this->adminUrl('scheduled', $entityTypeId, $bundle));
+    $this->drupalPostForm('admin/config/development/generate/content', $generate_settings, 'Generate');
+    $this->drupalGet('admin/content');
+    $this->drupalGet('admin/content/scheduled');
 
-    // Check we have the expected number of entities scheduled for publishing
-    // only, and verify that that the dates are within the time range specified.
-    $this->countScheduledEntities($entityTypeId, $bundle_field, $bundle, 'publish_on', 40, $enabled ? 40 : 0, $generate_settings['time_range']);
-    $this->countScheduledEntities($entityTypeId, $bundle_field, $bundle, 'unpublish_on', 40, 0);
+    // Check we have the expected number of nodes scheduled for publishing only
+    // and verify that that the dates are within the time range specified.
+    $this->countScheduledNodes($this->type, 'publish_on', 40, 40, $generate_settings['time_range']);
+    $this->countScheduledNodes($this->type, 'unpublish_on', 40, 0);
 
     // Do similar for unpublish_on date. Delete all then generate new content
     // with only unpublish-on dates. Time range 86400 is one day.
-    $generate_settings = $entity_selection + [
+    $generate_settings = [
+      "edit-node-types-$this->type" => TRUE,
       'num' => 30,
       'kill' => TRUE,
       'time_range' => 86400,
       'scheduler_publishing' => 0,
       'scheduler_unpublishing' => 100,
     ];
-    $this->drupalGet($this->adminUrl('generate', $entityTypeId, $bundle));
-    $this->submitForm($generate_settings, 'Generate');
-    // Display the full content list and the scheduled content list.
-    $this->drupalGet($this->adminUrl('collection', $entityTypeId, $bundle));
-    $this->drupalGet($this->adminUrl('scheduled', $entityTypeId, $bundle));
+    $this->drupalPostForm('admin/config/development/generate/content', $generate_settings, 'Generate');
+    $this->drupalGet('admin/content');
+    $this->drupalGet('admin/content/scheduled');
 
-    // Check we have the expected number of entities scheduled for unpublishing
+    // Check we have the expected number of nodes scheduled for unpublishing
     // only, and verify that that the dates are within the time range specified.
-    $this->countScheduledEntities($entityTypeId, $bundle_field, $bundle, 'publish_on', 30, 0);
-    $this->countScheduledEntities($entityTypeId, $bundle_field, $bundle, 'unpublish_on', 30, $enabled ? 30 : 0, $generate_settings['time_range']);
-
-  }
+    $this->countScheduledNodes($this->type, 'publish_on', 30, 0);
+    $this->countScheduledNodes($this->type, 'unpublish_on', 30, 30, $generate_settings['time_range']);
+
+    // Generate new content using the type which is not enabled for Scheduler.
+    // The nodes should be created but no dates should be added even though the
+    // scheduler values are set to 100.
+    $non_scheduler_id = $this->nonSchedulerNodeType->id();
+    $generate_settings = [
+      "edit-node-types-$non_scheduler_id" => TRUE,
+      'num' => 20,
+      'kill' => TRUE,
+      'scheduler_publishing' => 100,
+      'scheduler_unpublishing' => 100,
+    ];
+    $this->drupalPostForm('admin/config/development/generate/content', $generate_settings, 'Generate');
+    $this->drupalGet('admin/content');
+    $this->drupalGet('admin/content/scheduled');
 
-  /**
-   * Provides data for testDevelGenerate().
-   *
-   * @return array
-   *   Each array item has the values:
-   *     [entity type id, enable for Scheduler TRUE/FALSE].
-   */
-  public function dataDevelGenerate() {
-    $types = $this->dataStandardEntityTypes();
-    // Remove commerce_product, becuase Devel Generate does not cover products.
-    unset($types['#commerce_product']);
-    $data = [];
-    // For each entity type, add a row for enabled TRUE and enabled FALSE.
-    foreach ($types as $key => $values) {
-      $data["$key-1"] = [$values[0], TRUE];
-      $data["$key-2"] = [$values[0], FALSE];
-    }
-    return $data;
+    // Check we have the expected number of nodes but that none are scheduled.
+    $this->countScheduledNodes($non_scheduler_id, 'publish_on', 20, 0);
+    $this->countScheduledNodes($non_scheduler_id, 'unpublish_on', 20, 0);
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDrushTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDrushTest.php
index 04576d3b5e2a3ea8a4502418c34c1ebd33057ee5..664fcad3b0ddd5334b2089e79225fa972e0bcb6b 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDrushTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDrushTest.php
@@ -14,7 +14,7 @@ class SchedulerDrushTest extends SchedulerBrowserTestBase {
   use DrushTestTrait;
 
   /**
-   * Tests the messages from Scheduler Drush cron.
+   * Tests the Scheduler Drush messages.
    */
   public function testDrushCronMessages() {
     // Run the plain command using the full scheduler:cron command name, and
@@ -40,22 +40,22 @@ public function testDrushCronMessages() {
   }
 
   /**
-   * Tests scheduled publishing and unpublishing of entities via Drush.
-   *
-   * @dataProvider dataStandardEntityTypes()
+   * Tests scheduled publishing via Drush command.
    */
-  public function testDrushCronPublishing($entityTypeId, $bundle) {
-    // Create an entity which is scheduled for publishing.
-    $title1 = $this->randomMachineName(20) . ' for publishing';
-    $entity = $this->createEntity($entityTypeId, $bundle, [
+  public function testDrushCronPublishing() {
+    // Create a node which is scheduled for publishing.
+    $title1 = $this->randomMachineName(20);
+    $this->drupalCreateNode([
       'title' => $title1,
+      'type' => $this->type,
       'publish_on' => strtotime('-3 hours'),
     ]);
 
-    // Create an entity which is scheduled for unpublishing.
-    $title2 = $this->randomMachineName(20) . ' for unpublishing';
-    $entity = $this->createEntity($entityTypeId, $bundle, [
+    // Create a node which is scheduled for unpublishing.
+    $title2 = $this->randomMachineName(20);
+    $this->drupalCreateNode([
       'title' => $title2,
+      'type' => $this->type,
       'unpublish_on' => strtotime('-2 hours'),
     ]);
 
@@ -63,22 +63,8 @@ public function testDrushCronPublishing($entityTypeId, $bundle) {
     // and unpublishing messages are found.
     $this->drush('scheduler:cron');
     $messages = $this->getErrorOutput();
-    $bundle_field = $entity->getEntityType()->get('entity_keys')['bundle'];
-    $type_label = $entity->$bundle_field->entity->label();
-    $this->assertStringContainsString(sprintf('%s: scheduled publishing of %s', $type_label, $title1), $messages, 'Scheduled publishing message not found', TRUE);
-    $this->assertStringContainsString(sprintf('%s: scheduled unpublishing of %s', $type_label, $title2), $messages, 'Scheduled unpublishing message not found', TRUE);
-  }
-
-  /**
-   * Tests the Entity Update command.
-   */
-  public function testDrushEntityUpdate() {
-    // This test could be expanded to check the full functionality of the
-    // entityUpdate() function. But initially, just call the function to check
-    // that it runs, and produces the 'nothing to update' message.
-    $this->drush('scheduler:entity-update');
-    $messages = $this->getErrorOutput();
-    $this->assertStringContainsString('Scheduler entity update - nothing to update', $messages, 'Error! Entity update message not found', TRUE);
+    $this->assertStringContainsString(sprintf('%s: scheduled publishing of %s', $this->typeName, $title1), $messages, 'Scheduled publishing message not found', TRUE);
+    $this->assertStringContainsString(sprintf('%s: scheduled unpublishing of %s', $this->typeName, $title2), $messages, 'Scheduled unpublishing message not found', TRUE);
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerEntityAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerEntityAccessTest.php
deleted file mode 100644
index 44ee683897b23e2b633aa08ef0a4c85c4ead6dd1..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerEntityAccessTest.php
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests that Scheduler cron has full access to the scheduled entities.
- *
- * This test uses a additional test module 'scheduler_access_test' which has a
- * custom entity access definition to deny viewing of all entities by any user
- * except user 1.
- *
- * The purpose of checking for '403' is only to demonstrate that the helper
- * module is doing its thing, it is not testing any part of the Scheduler
- * functionality. If we tested with an anonymous visitor then both the published
- * and unpublished entities would give 403 but the unpublished entity would
- * return this regardless of what the helper module was doing. Likewise if we
- * run the test with a logged in user who does not have 'view own unpublished..'
- * then the unpublished entity would give 403 regardless. However, if the user
- * does have 'view own unpublished ..' then due to the design of checkAccess()
- * within NodeAccessControlHandler this entirely takes precedence and overrides
- * any prevention of access attempted via contrib hook_node_access_records() and
- * hook_node_grants(). It is clearer in the dblog output if this test is run
- * using a logged-in user rather than the anonymous user, and hence create a new
- * user who does not have the permission 'view own unpublished {type}'.
- *
- * @group scheduler
- */
-class SchedulerEntityAccessTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Additional modules required.
-   *
-   * @var array
-   */
-  protected static $modules = ['scheduler_access_test'];
-
-  /**
-   * Tests Scheduler cron functionality when access to the entity is denied.
-   *
-   * @dataProvider dataEntityAccess()
-   */
-  public function testEntityAccess($entityTypeId, $bundle, $field, $status) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    // scheduler_access_test_install() sets node_access_needs_rebuild(TRUE) and
-    // this works when testing the module interactively, but in a phpunit run
-    // the node access table is not rebuilt. Hence do that explicitly here.
-    node_access_rebuild();
-
-    // Login as a user who is only able to view the published entities.
-    $this->drupalLogin($this->drupalCreateUser());
-
-    // Create an entity with the necessary scheduler date.
-    $process = $status ? 'unpublishing' : 'publishing';
-    $settings = [
-      'status' => $status,
-      'title' => "$entityTypeId $bundle for $process",
-      $field => $this->requestTime + 1,
-    ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $settings);
-    $this->drupalGet($entity->toUrl());
-    // Before running cron, viewing the entity should give "403 Not Authorized"
-    // regardless of whether it is published or unpublished.
-    $this->assertSession()->statusCodeEquals(403);
-
-    // Delay so that the date entered is now in the past, then run cron.
-    sleep(2);
-    $this->cronRun();
-
-    // Reload the entity.
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check that the entity has been published or unpublished as required.
-    $this->assertTrue($entity->isPublished() === !$status, "Scheduled $process of $entityTypeId via cron.");
-    // Check that the entity is still not viewable.
-    $this->drupalGet($entity->toUrl());
-    // After cron, viewing the entity should still give "403 Not Authorized".
-    $this->assertSession()->statusCodeEquals(403);
-
-    // Log in as admin and check that the dblog cron message is shown.
-    $this->drupalLogin($this->adminUser);
-    $this->drupalGet('admin/reports/dblog');
-    $this->assertSession()->pageTextContains($this->entityTypeObject($entityTypeId)->label() . ": scheduled $process");
-  }
-
-  /**
-   * Provides data for testEntityAccess.
-   *
-   * @return array
-   *   Each row has values: [entity type id, bundle id, field name, status].
-   */
-  public function dataEntityAccess() {
-    // This test is only applicable to node entity types because the other
-    // entity types do not have a hook access grant system.
-    // @todo Investigate how scheduler_access_test module can be expanded to
-    // deny access to other entity types using a different method.
-    $data = [
-      '#node-1' => ['node', $this->type, 'publish_on', FALSE],
-      '#node-2' => ['node', $this->type, 'unpublish_on', TRUE],
-    ];
-    return $data;
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerEventsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerEventsTest.php
deleted file mode 100644
index 4bf38bad00940e382c601a4e661e3235babc308b..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerEventsTest.php
+++ /dev/null
@@ -1,161 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests the six generic events that Scheduler dispatches.
- *
- * @group scheduler_api
- */
-class SchedulerEventsTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Additional modules required.
-   *
-   * @var array
-   *
-   * @todo 'menu_ui' is in the exported node.type definition, and 'path' is in
-   * the entity_form_display. Could these be removed from the config files and
-   * then not needed here?
-   */
-  protected static $modules = ['scheduler_api_test', 'menu_ui', 'path'];
-
-  /**
-   * Covers six events for nodes.
-   *
-   * The events allow other modules to react to the Scheduler process being run.
-   * The API test implementations of the event listeners alter the nodes
-   * 'promote' and 'sticky' settings and changes the title.
-   */
-  public function testNodeEvents() {
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create a test node.
-    $settings = [
-      'publish_on' => strtotime('-1 day'),
-      'type' => $this->type,
-      'promote' => FALSE,
-      'sticky' => FALSE,
-      'title' => 'API TEST node action',
-    ];
-    $node = $this->drupalCreateNode($settings);
-
-    // Check that the 'sticky' and 'promote' fields are off for the new node.
-    $this->assertFalse($node->isSticky(), 'The unpublished node is not sticky.');
-    $this->assertFalse($node->isPromoted(), 'The unpublished node is not promoted.');
-
-    // Run cron and check that the events have been dispatched correctly, by
-    // verifying that the node is now sticky and has been promoted.
-    scheduler_cron();
-    $this->nodeStorage->resetCache([$node->id()]);
-    $node = $this->nodeStorage->load($node->id());
-    $this->assertTrue($node->isSticky(), 'API event "PRE_PUBLISH" has changed the node to sticky.');
-    $this->assertTrue($node->isPromoted(), 'API event "PUBLISH" has changed the node to promoted.');
-
-    // Now set a date for unpublishing the node. Ensure 'sticky' and 'promote'
-    // are set, so that the assertions are not affected by any failures above.
-    $node->set('unpublish_on', strtotime('-1 day'))
-      ->set('sticky', TRUE)->set('promote', TRUE)->save();
-
-    // Run cron and check that the events have been dispatched correctly, by
-    // verifying that the node is no longer sticky and not promoted.
-    scheduler_cron();
-    $this->nodeStorage->resetCache([$node->id()]);
-    $node = $this->nodeStorage->load($node->id());
-    $this->assertFalse($node->isSticky(), 'API event "PRE_UNPUBLISH" has changed the node to not sticky.');
-    $this->assertFalse($node->isPromoted(), 'API event "UNPUBLISH" has changed the node to not promoted.');
-
-    // Turn on immediate publication when a publish date is in the past.
-    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
-
-    // Ensure 'sticky' and 'promote' are not set, so that the assertions are not
-    // affected by any failures above.
-    $node->set('sticky', FALSE)->set('promote', FALSE)->save();
-
-    // Edit the node and set a publish-on date in the past.
-    $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', $this->requestTime)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', $this->requestTime)),
-    ];
-    $this->drupalGet('node/' . $node->id() . '/edit');
-    $this->submitForm($edit, 'Save');
-    // Verify that the values have been altered as expected.
-    $this->nodeStorage->resetCache([$node->id()]);
-    $node = $this->nodeStorage->load($node->id());
-    $this->assertTrue($node->isSticky(), 'API event "PRE_PUBLISH_IMMEDIATELY" has changed the node to sticky.');
-    $this->assertTrue($node->isPromoted(), 'API event "PUBLISH_IMMEDIATELY" has changed the node to promoted.');
-    $this->assertEquals('Published immediately', $node->title->value, 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.');
-  }
-
-  /**
-   * Tests six scheduler events for entity types other than node.
-   *
-   * @dataProvider dataSchedulerEvents()
-   */
-  public function testSchedulerEvents($entityTypeId, $bundle) {
-    $this->drupalLogin($this->schedulerUser);
-    $storage = $this->entityStorageObject($entityTypeId);
-    $title_prefix = "API TEST $entityTypeId";
-
-    // Create an entity of the required type, scheduled for publishing.
-    $entity = $this->createEntity($entityTypeId, $bundle, [
-      'title' => $title_prefix,
-      'publish_on' => strtotime('-1 day'),
-    ]);
-    // Run cron and check that the events have been dispatched correctly. The
-    // name is first changed by a PRE_PUBLISH event subscriber, then a second
-    // time by a PUBLISH event watcher. Checking the final value tests both.
-    scheduler_cron();
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertEquals($title_prefix . ' - altered a second time by PUBLISH event', $entity->label());
-
-    // Create an entity of the required type, scheduled for unpublishing.
-    $entity = $this->createEntity($entityTypeId, $bundle, [
-      'title' => $title_prefix,
-      'unpublish_on' => strtotime('-1 day'),
-    ]);
-    // Run cron and check that the events have been dispatched correctly.
-    scheduler_cron();
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertEquals($title_prefix . ' - altered a second time by UNPUBLISH event', $entity->label());
-
-    // Turn on immediate publishing when a publish date is in the past.
-    $this->entityTypeObject($entityTypeId, $bundle)
-      ->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
-
-    // Create an unpublished and unscheduled entity.
-    $entity = $this->createEntity($entityTypeId, $bundle, [
-      'title' => $title_prefix,
-      'status' => FALSE,
-    ]);
-    // Edit the media item, setting a publish-on date in the past.
-    $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', $this->requestTime)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', $this->requestTime)),
-    ];
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
-    // Verify that the values have been altered as expected, without cron.
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertEquals($title_prefix . ' - altered a second time by PUBLISH_IMMEDIATELY event', $entity->label());
-  }
-
-  /**
-   * Provides test data for scheduler events test.
-   *
-   * The original node events test is different (and no benefit in re-writing)
-   * so this test excludes the node entity type.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id].
-   */
-  public function dataSchedulerEvents() {
-    $data = $this->dataStandardEntityTypes();
-    unset($data['#node']);
-    return $data;
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
index b2835dc8cac705ef7212c860bf1c82c47bce0cfd..806ca70fab829a1d780a7814cbb4ac3dbed0fb8f 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
@@ -2,168 +2,88 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Core\Url;
-
 /**
  * Tests the display of date entry fields and form elements.
  *
- * @todo Extend these tests to cover form display processing when entity
- * type is enabled/disabled.
- * @see https://www.drupal.org/project/scheduler/issues/3320341
- *
  * @group scheduler
  */
 class SchedulerFieldsDisplayTest extends SchedulerBrowserTestBase {
 
   /**
-   * Additional core module field_ui is required for testManageFormDisplay.
+   * Additional module field_ui is required for the 'manage form display' test.
    *
    * @var array
    */
   protected static $modules = ['field_ui'];
 
   /**
-   * Tests the Scheduler options display on entity type add and edit forms.
-   *
-   * This test covers hook_form_alter() and _scheduler_entity_type_form_alter().
-   *
-   * @dataProvider dataEntityTypeForm()
+   * {@inheritdoc}
    */
-  public function testEntityTypeForm($entityTypeId, $bundle, $operation) {
-    $this->drupalLogin($this->adminUser);
-
-    if ($operation == 'add first') {
-      // Delete all the entity types for this bundle, to check that 'add'
-      // works when it would be the first type being added.
-      $this->entityTypeObject($entityTypeId)->delete();
-      $this->entityTypeObject($entityTypeId, 'non-enabled')->delete();
-    }
-
-    $url = $this->adminUrl($operation == 'edit' ? 'bundle_edit' : 'bundle_add', $entityTypeId, $bundle);
-    $this->drupalGet($url);
-    $this->assertSession()->fieldExists('edit-scheduler-publish-enable');
-    $this->assertSession()->fieldExists('edit-scheduler-unpublish-enable');
-  }
-
-  /**
-   * Provides data for testEntityTypeForm.
-   *
-   * @return array
-   *   Each row has values: [entity type id, bundle id, operation].
-   */
-  public function dataEntityTypeForm() {
-    $types = $this->dataStandardEntityTypes();
-    $data = [];
-    foreach ($types as $key => $values) {
-      $data["$key-1"] = array_merge($values, ['add first']);
-      $data["$key-2"] = array_merge($values, ['add']);
-      $data["$key-3"] = array_merge($values, ['edit']);
-    }
-    return $data;
-  }
-
-  /**
-   * Tests the scheduler fields on the admin entity type form display tab.
-   *
-   * This test covers scheduler_entity_extra_field_info().
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testManageFormDisplay($entityTypeId, $bundle) {
-    // Give adminUser the permissions to use the field_ui 'manage form display'
-    // tab for the entity type being tested.
-    $this->addPermissionsToUser($this->adminUser, ["administer {$entityTypeId} form display"]);
-    $this->drupalLogin($this->adminUser);
-    $entityType = $this->entityTypeObject($entityTypeId, $bundle);
-
-    // Check that the weight input field is displayed when the entity bundle is
-    // enabled for scheduling. This field still exists even with tabledrag on.
-    $form_display_url = Url::fromRoute("entity.entity_form_display.{$entityTypeId}.default", [$entityType->getEntityTypeId() => $bundle]);
-    $this->drupalGet($form_display_url);
-    $this->assertSession()->fieldExists('edit-fields-scheduler-settings-weight');
-
-    // Check that the weight input field is not displayed when the entity bundle
-    // is not enabled for scheduling.
-    $this->entityTypeObject($entityTypeId, $bundle)
-      ->setThirdPartySetting('scheduler', 'publish_enable', FALSE)
-      ->setThirdPartySetting('scheduler', 'unpublish_enable', FALSE)->save();
-    $this->drupalGet($form_display_url);
-    $this->assertSession()->pageTextContains('Manage form display');
-    $this->assertSession()->FieldNotExists('edit-fields-scheduler-settings-weight');
+  protected function setUp(): void {
+    parent::setUp();
+
+    // Create a custom user with admin permissions but also permission to use
+    // the field_ui module 'node form display' tab.
+    $this->adminUser2 = $this->drupalCreateUser([
+      'access content',
+      'administer content types',
+      'administer node form display',
+      'create ' . $this->type . ' content',
+      'schedule publishing of nodes',
+    ]);
   }
 
   /**
    * Tests date input is displayed as vertical tab or an expandable fieldset.
    *
-   * This test covers hook_form_alter() and _scheduler_entity_form_alter().
-   *
-   * @dataProvider dataStandardEntityTypes()
+   * This test covers scheduler_form_node_form_alter().
    */
-  public function testVerticalTabOrFieldset($entityTypeId, $bundle) {
+  public function testVerticalTabOrFieldset() {
     $this->drupalLogin($this->adminUser);
-    $entityType = $this->entityTypeObject($entityTypeId, $bundle);
 
     /** @var \Drupal\Tests\WebAssert $assert */
     $assert = $this->assertSession();
 
-    // For rendering of vertical tabs, node and media entity forms have a div
-    // with class 'js-form-type-vertical-tabs'. However, the Commerce Product
-    // module does things differently and does not have this class, but instead
-    // has a class 'layout-region-product-secondary' (for vertical tabs) and
-    // 'layout-region-product-main' if in the main form not in vertical tabs. So
-    // to cover all entity types we can check for either of these classes as an
-    // ancestor of the 'edit-scheduler-settings' section.
-    $vertical_tab_xpath = '//div[contains(@class, "form-type-vertical-tabs") or contains(@class, "-secondary")]//details[@id = "edit-scheduler-settings"]';
-
-    // The 'open' and 'closed' xpath searches do apply to vertical tabs, even if
-    // the theme does not actually make use of it (such as in Bartik and Stark).
-    $details_open_xpath = '//details[@id = "edit-scheduler-settings" and @open = "open"]';
-    $details_closed_xpath = '//details[@id = "edit-scheduler-settings" and not(@open = "open")]';
-
-    // Check that the dates are shown in a vertical tab by default. The taxonomy
-    // term form does not have a vertical tab section, so cannot check for this.
-    $add_url = $this->entityAddUrl($entityTypeId, $bundle);
-    $this->drupalGet($add_url);
-    if ($check_vertical_tab = ($entityTypeId != 'taxonomy_term')) {
-      $assert->elementExists('xpath', $vertical_tab_xpath);
-    }
-    $assert->elementExists('xpath', $details_closed_xpath);
+    // Check that the dates are shown in a vertical tab by default.
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementExists('xpath', '//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]');
 
     // Check that the dates are shown as a fieldset when configured to do so,
     // and that fieldset is collapsed by default.
-    $entityType->setThirdPartySetting('scheduler', 'fields_display_mode', 'fieldset')->save();
-    $this->drupalGet($add_url);
-    $assert->elementNotExists('xpath', $vertical_tab_xpath);
-    $assert->elementExists('xpath', $details_closed_xpath);
+    $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'fieldset')->save();
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementNotExists('xpath', '//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]');
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and not(@open = "open")]');
 
     // Check that the fieldset is expanded if either of the scheduling dates
     // are required.
-    $entityType->setThirdPartySetting('scheduler', 'publish_required', TRUE)->save();
-    $this->drupalGet($add_url);
-    $assert->elementExists('xpath', $details_open_xpath);
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', TRUE)->save();
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and @open = "open"]');
 
-    $entityType->setThirdPartySetting('scheduler', 'publish_required', FALSE)
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)->save();
-    $this->drupalGet($add_url);
-    $assert->elementExists('xpath', $details_open_xpath);
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and @open = "open"]');
 
     // Check that the fieldset is expanded if the 'always' option is set.
-    $entityType->setThirdPartySetting('scheduler', 'publish_required', FALSE)
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save();
-    $this->drupalGet($add_url);
-    $assert->elementExists('xpath', $details_open_xpath);
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and @open = "open"]');
 
-    // Check that the fieldset is expanded if the entity already has a
-    // publish-on date. This requires editing an existing scheduled entity.
-    $entityType->setThirdPartySetting('scheduler', 'expand_fieldset', 'when_required')->save();
+    // Check that the fieldset is expanded if the node already has a publish-on
+    // date. This requires editing an existing scheduled node.
+    $this->nodetype->setThirdPartySetting('scheduler', 'expand_fieldset', 'when_required')->save();
     $options = [
       'title' => 'Contains Publish-on date ' . $this->randomMachineName(10),
+      'type' => $this->type,
       'publish_on' => strtotime('+1 day'),
     ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $options);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $assert->elementExists('xpath', $details_open_xpath);
+    $node = $this->drupalCreateNode($options);
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and @open = "open"]');
 
     // Repeat the check with a timestamp value of zero. This is a valid date
     // so the fieldset should be opened. It will not be used much on real sites
@@ -171,101 +91,123 @@ public function testVerticalTabOrFieldset($entityTypeId, $bundle) {
     // we get zero. Debugging Rules is easier if the fieldset opens as expected.
     $options = [
       'title' => 'Contains Publish-on date with timestamp value zero - ' . $this->randomMachineName(10),
+      'type' => $this->type,
       'publish_on' => 0,
     ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $options);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $assert->elementExists('xpath', $details_open_xpath);
+    $node = $this->drupalCreateNode($options);
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and @open = "open"]');
 
-    // Check that the fieldset is expanded if there is an unpublish-on date.
+    // Check that the fieldset is expanded if the node has an unpublish-on date.
     $options = [
       'title' => 'Contains Unpublish-on date ' . $this->randomMachineName(10),
+      'type' => $this->type,
       'unpublish_on' => strtotime('+1 day'),
     ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $options);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $assert->elementExists('xpath', $details_open_xpath);
+    $node = $this->drupalCreateNode($options);
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and @open = "open"]');
 
     // Repeat with a timestamp value of zero.
     $options = [
       'title' => 'Contains Unpublish-on date with timestamp value zero - ' . $this->randomMachineName(10),
+      'type' => $this->type,
       'unpublish_on' => 0,
     ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $options);
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $assert->elementExists('xpath', $details_open_xpath);
+    $node = $this->drupalCreateNode($options);
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings" and @open = "open"]');
 
     // Check that the display reverts to a vertical tab again when specifically
     // configured to do so.
-    $entityType->setThirdPartySetting('scheduler', 'fields_display_mode', 'vertical_tab')->save();
-    $this->drupalGet($entity->toUrl('edit-form'));
-    if ($check_vertical_tab) {
-      $assert->elementExists('xpath', $vertical_tab_xpath);
-    }
-    $assert->elementExists('xpath', $details_open_xpath);
+    $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'vertical_tab')->save();
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementExists('xpath', '//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]');
   }
 
   /**
-   * Tests the edit form when scheduler fields have been disabled.
+   * Tests the settings entry in the content type form display.
    *
-   * This test covers _scheduler_entity_form_alter().
+   * This test covers scheduler_entity_extra_field_info().
+   */
+  public function testManageFormDisplay() {
+    $this->drupalLogin($this->adminUser2);
+
+    // Check that the weight input field is displayed when the content type is
+    // enabled for scheduling. This field still exists even with tabledrag on.
+    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/form-display');
+    $this->assertSession()->fieldExists('edit-fields-scheduler-settings-weight');
+
+    // Check that the weight input field is not displayed when the content type
+    // is not enabled for scheduling.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_enable', FALSE)
+      ->setThirdPartySetting('scheduler', 'unpublish_enable', FALSE)->save();
+    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/form-display');
+    $this->assertSession()->FieldNotExists('edit-fields-scheduler-settings-weight');
+  }
+
+  /**
+   * Tests the edit form when scheduler fields have been disabled.
    *
-   * @dataProvider dataStandardEntityTypes()
+   * This test covers scheduler_form_node_form_alter().
    */
-  public function testDisabledFields($entityTypeId, $bundle) {
-    $this->drupalLogin($this->schedulerUser);
+  public function testDisabledFields() {
+    $this->drupalLogin($this->adminUser2);
 
     /** @var \Drupal\Tests\WebAssert $assert */
     $assert = $this->assertSession();
 
-    // 1. Set the publish_on field to 'hidden' in the entity edit form.
-    $formDisplay = $this->container->get('entity_display.repository')->getFormDisplay($entityTypeId, $bundle);
-    $publish_on_component = $formDisplay->getComponent('publish_on');
-    $formDisplay->removeComponent('publish_on')->save();
+    // 1. Set the publish_on field to 'hidden' in the node edit form.
+    $edit = [
+      'fields[publish_on][region]' => 'hidden',
+    ];
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save');
 
-    // Check that the scheduler details element is shown and that the
-    // unpublish_on field is shown, but the publish_on field is not shown.
-    $add_url = $this->entityAddUrl($entityTypeId, $bundle);
-    $this->drupalGet($add_url);
-    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings"]');
+    // Check that a scheduler vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementExists('xpath', '//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]');
+    // Check the publish_on field is not shown, but the unpublish_on field is.
     $this->assertSession()->FieldNotExists('publish_on[0][value][date]');
     $this->assertSession()->FieldExists('unpublish_on[0][value][date]');
 
     // 2. Set publish_on to be displayed but hide the unpublish_on field.
-    $formDisplay->setComponent('publish_on', $publish_on_component)
-      ->removeComponent('unpublish_on')->save();
+    $edit = [
+      'fields[publish_on][region]' => 'content',
+      'fields[unpublish_on][region]' => 'hidden',
+    ];
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save');
 
-    // Check that the scheduler details element is shown and that the
-    // publish_on field is shown, but the unpublish_on field is not shown.
-    $this->drupalGet($add_url);
-    $assert->elementExists('xpath', '//details[@id = "edit-scheduler-settings"]');
+    // Check that a scheduler vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementExists('xpath', '//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]');
+    // Check the publish_on field is not shown, but the unpublish_on field is.
     $this->assertSession()->FieldExists('publish_on[0][value][date]');
     $this->assertSession()->FieldNotExists('unpublish_on[0][value][date]');
 
     // 3. Set both fields to be hidden.
-    $formDisplay->removeComponent('publish_on')->save();
+    $edit = [
+      'fields[publish_on][region]' => 'hidden',
+      'fields[unpublish_on][region]' => 'hidden',
+    ];
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save');
 
-    // Check that the scheduler details element is not shown when both of the
-    // date fields are set to be hidden.
-    $this->drupalGet($add_url);
-    $assert->elementNotExists('xpath', '//details[@id = "edit-scheduler-settings"]');
+    // Check that no vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $assert->elementNotExists('xpath', '//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]');
+    // Check the neither field is displayed.
     $this->assertSession()->FieldNotExists('publish_on[0][value][date]');
     $this->assertSession()->FieldNotExists('unpublish_on[0][value][date]');
   }
 
   /**
    * Test the option to hide the seconds on the time input fields.
-   *
-   * @dataProvider dataStandardEntityTypes()
    */
-  public function testHideSeconds($entityTypeId, $bundle) {
+  public function testHideSeconds() {
     $this->drupalLogin($this->schedulerUser);
     $config = $this->config('scheduler.settings');
-    $titleField = $this->titleField($entityTypeId);
 
     // Check that the default is to show the seconds on the input fields.
-    $add_url = $this->entityAddUrl($entityTypeId, $bundle);
-    $this->drupalGet($add_url);
+    $this->drupalGet('node/add/' . $this->type);
     $publish_time_field = $this->xpath('//input[@id="edit-publish-on-0-value-time"]');
     $unpublish_time_field = $this->xpath('//input[@id="edit-unpublish-on-0-value-time"]');
     $this->assertEquals(1, $publish_time_field[0]->getAttribute('step'), 'The input time step for publish-on is 1, so the seconds will be visible and usable.');
@@ -275,8 +217,8 @@ public function testHideSeconds($entityTypeId, $bundle) {
     // to the granularity of one minute.
     $config->set('hide_seconds', TRUE)->save();
 
-    // Go to the 'add' url and check the input fields.
-    $this->drupalGet($add_url);
+    // Get the node-add page and check the input fields.
+    $this->drupalGet('node/add/' . $this->type);
     $publish_time_field = $this->xpath('//input[@id="edit-publish-on-0-value-time"]');
     $unpublish_time_field = $this->xpath('//input[@id="edit-unpublish-on-0-value-time"]');
     $this->assertEquals(60, $publish_time_field[0]->getAttribute('step'), 'The input time step for publish-on is 60, so the seconds will be hidden and not usable.');
@@ -285,17 +227,18 @@ public function testHideSeconds($entityTypeId, $bundle) {
 
     // Save with both dates entered, including seconds in the times.
     $edit = [
-      "{$titleField}[0][value]" => 'Hide the seconds',
+      'title[0][value]' => 'Hide the seconds',
+      'body[0][value]' => $this->randomString(30),
       'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)),
       'publish_on[0][value][time]' => '01:02:03',
       'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)),
       'unpublish_on[0][value][time]' => '04:05:06',
     ];
     $this->submitForm($edit, 'Save');
-    $entity = $this->getEntityByTitle($entityTypeId, 'Hide the seconds');
+    $node = $this->drupalGetNodeByTitle('Hide the seconds');
 
     // Edit and check that the seconds have been set to zero.
-    $this->drupalGet($entity->toUrl('edit-form'));
+    $this->drupalGet("node/{$node->id()}/edit");
     $this->assertSession()->FieldValueEquals('publish_on[0][value][time]', '01:02:00');
     $this->assertSession()->FieldValueEquals('unpublish_on[0][value][time]', '04:05:00');
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerHooksTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerHooksTest.php
deleted file mode 100644
index 858c8f8f0e4cc784b3ce5c61b5318b0875a034aa..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerHooksTest.php
+++ /dev/null
@@ -1,534 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-use Drupal\commerce_product\Entity\ProductType;
-use Drupal\node\Entity\NodeType;
-use Drupal\media\Entity\MediaType;
-
-/**
- * Tests the API hook functions of the Scheduler module.
- *
- * This class covers the eight hook functions that Scheduler provides, allowing
- * other modules to interact with editting, scheduling and processing via cron.
- *
- * @group scheduler_api
- */
-class SchedulerHooksTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Additional modules required.
-   *
-   * @var array
-   *
-   * @todo 'menu_ui' is in the exported node.type definition, and 'path' is in
-   * the entity_form_display. Could these be removed from the config files and
-   * then not needed here?
-   */
-  protected static $modules = ['scheduler_api_test', 'menu_ui', 'path'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    // Load the custom node type and check that it loaded OK. These entity types
-    // are enabled for Scheduler automatically because that is pre-configured
-    // in the scheduler_api_test {type}.yml files.
-    $customNodeName = 'scheduler_api_node_test';
-    $customNodetype = NodeType::load($customNodeName);
-    $this->assertNotNull($customNodetype, "Custom node type $customNodeName failed to load during setUp");
-
-    // Load the custom media type and check that it loaded OK.
-    $customMediaName = 'scheduler_api_media_test';
-    $customMediatype = MediaType::load($customMediaName);
-    $this->assertNotNull($customMediatype, "Custom media type $customMediaName failed to load during setUp");
-
-    // Load the custom product type and check that it loaded OK.
-    $customProductName = 'scheduler_api_product_test';
-    $customProductType = ProductType::load($customProductName);
-    $this->assertNotNull($customProductType, "Custom product type $customProductName failed to load during setUp");
-
-    // Create a web user that has permission to create and edit and schedule
-    // the custom entity types.
-    $this->webUser = $this->drupalCreateUser([
-      "create $customNodeName content",
-      "edit any $customNodeName content",
-      'schedule publishing of nodes',
-      "create $customMediaName media",
-      "edit any $customMediaName media",
-      'schedule publishing of media',
-      "create $customProductName commerce_product",
-      "update any $customProductName commerce_product",
-      'schedule publishing of commerce_product',
-      // 'administer commerce_store' is needed to see and use any store, i.e
-      // cannot add a product without this. Is it a bug?
-      'administer commerce_store',
-    ]);
-    $this->webUser->set('name', 'Wenlock the Web user')->save();
-  }
-
-  /**
-   * Provides test data containing the custom entity types.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id].
-   */
-  public function dataCustomEntityTypes() {
-    $data = [
-      '#node' => ['node', 'scheduler_api_node_test'],
-      '#media' => ['media', 'scheduler_api_media_test'],
-      '#commerce_product' => ['commerce_product', 'scheduler_api_product_test'],
-    ];
-    return $data;
-  }
-
-  /**
-   * Covers hook_scheduler_list() and hook_scheduler_{type}_list()
-   *
-   * These hooks allow other modules to add more entity ids into the list being
-   * processed. In real scenarios, the third-party module would likely have more
-   * complex data structures and/or tables from which to identify the ids to
-   * add. In this test, to keep it simple, we identify entities simply by title.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testList($entityTypeId, $bundle) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create test entities using the standard scheduler test entity types.
-    // Entity 1 is not published and has no publishing date set. The test API
-    // module will add this entity into the list to be published using an
-    // implementation of general hook_scheduler_list() function. Entity 2 is
-    // similar but will be added via the hook_scheduler_{type}_list() function.
-    $entity1 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Pink $entityTypeId list publish me",
-    ]);
-    $entity2 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Purple $entityTypeId list publish me",
-    ]);
-
-    // Entity 3 is published and has no unpublishing date set. The test API
-    // module will add this entity into the list to be unpublished.
-    $entity3 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Pink $entityTypeId list unpublish me",
-    ]);
-    $entity4 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Purple $entityTypeId list unpublish me",
-    ]);
-
-    // Before cron, check that entity 1 and 2 are unpublished and entity 3 and 4
-    // are published.
-    $this->assertFalse($entity1->isPublished(), "Before cron, $entityTypeId 1 '{$entity1->label()}' should be unpublished.");
-    $this->assertFalse($entity2->isPublished(), "Before cron, $entityTypeId 2 '{$entity2->label()}' should be unpublished.");
-    $this->assertTrue($entity3->isPublished(), "Before cron, $entityTypeId 3 '{$entity3->label()}' should be published.");
-    $this->assertTrue($entity4->isPublished(), "Before cron, $entityTypeId 4 '{$entity4->label()}' should be published.");
-
-    // Run cron and refresh the entities.
-    scheduler_cron();
-    $storage->resetCache();
-    for ($i = 1; $i <= 4; $i++) {
-      ${"entity$i"} = $storage->load(${"entity$i"}->id());
-    }
-
-    // Check tha entity 1 and 2 have been published.
-    $this->assertTrue($entity1->isPublished(), "After cron, $entityTypeId 1 '{$entity1->label()}' should be published.");
-    $this->assertTrue($entity2->isPublished(), "After cron, $entityTypeId 2 '{$entity2->label()}' should be published.");
-
-    // Check that entity 3 and 4 have been unpublished.
-    $this->assertFalse($entity3->isPublished(), "After cron, $entityTypeId 3 '{$entity3->label()}' should be unpublished.");
-    $this->assertFalse($entity4->isPublished(), "After cron, $entityTypeId 4 '{$entity4->label()}' should be unpublished.");
-  }
-
-  /**
-   * Covers hook_scheduler_list_alter() and hook_scheduler_{type}_list_alter()
-   *
-   * These hook allows other modules to add or remove entity ids from the list
-   * to be processed.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testListAlter($entityTypeId, $bundle) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create test entities using the standard scheduler test entity types.
-    // Entity 1 is set for scheduled publishing, but will be removed by the test
-    // API generic hook_scheduler_list_alter() function. Entity 2 is similar but
-    // is removed via the specifc hook_scheduler_{type}_list_alter() function.
-    $entity1 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Pink $entityTypeId list_alter do not publish me",
-      'publish_on' => strtotime('-1 day'),
-    ]);
-    $entity2 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Purple $entityTypeId list_alter do not publish me",
-      'publish_on' => strtotime('-1 day'),
-    ]);
-
-    // Entity 3 is not published and has no publishing date set. The test module
-    // generic hook_scheduler_list_alter() function will add a date and add the
-    // id into the list to be published. Entity 4 is similar but the date and id
-    // is added by the specifc hook_scheduler_{type}_list_alter() function.
-    $entity3 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Pink $entityTypeId list_alter publish me",
-    ]);
-    $entity4 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Purple $entityTypeId list_alter publish me",
-    ]);
-
-    // Entity 5 is set for scheduled unpublishing, but will be removed by the
-    // generic hook_scheduler_list_alter() function. Entity 6 is similar but is
-    // removed by the specifc hook_scheduler_{type}_list_alter() function.
-    $entity5 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Pink $entityTypeId list_alter do not unpublish me",
-      'unpublish_on' => strtotime('-1 day'),
-    ]);
-    $entity6 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Purple $entityTypeId list_alter do not unpublish me",
-      'unpublish_on' => strtotime('-1 day'),
-    ]);
-
-    // Entity 7 is published and has no unpublishing date set. The generic
-    // hook_scheduler_list_alter() will add a date and add the id into the list
-    // to be unpublished. Entity 8 is similar but the date and id will be added
-    // by the specifc hook_scheduler_{type}_list_alter() function.
-    $entity7 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Pink $entityTypeId list_alter unpublish me",
-    ]);
-    $entity8 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Purple $entityTypeId list_alter unpublish me",
-    ]);
-
-    // Before cron, check entities 1-4 are unpublished and 5-8 are published.
-    $this->assertFalse($entity1->isPublished(), "Before cron, $entityTypeId 1 '{$entity1->label()}' should be unpublished.");
-    $this->assertFalse($entity2->isPublished(), "Before cron, $entityTypeId 2 '{$entity2->label()}' should be unpublished.");
-    $this->assertFalse($entity3->isPublished(), "Before cron, $entityTypeId 3 '{$entity3->label()}' should be unpublished.");
-    $this->assertFalse($entity4->isPublished(), "Before cron, $entityTypeId 4 '{$entity4->label()}' should be unpublished.");
-    $this->assertTrue($entity5->isPublished(), "Before cron, $entityTypeId 5 '{$entity5->label()}' should be published.");
-    $this->assertTrue($entity6->isPublished(), "Before cron, $entityTypeId 6 '{$entity6->label()}' should be published.");
-    $this->assertTrue($entity7->isPublished(), "Before cron, $entityTypeId 7 '{$entity7->label()}' should be published.");
-    $this->assertTrue($entity8->isPublished(), "Before cron, $entityTypeId 8 '{$entity8->label()}' should be published.");
-
-    // Run cron and refresh the entities from storage.
-    scheduler_cron();
-    $storage->resetCache();
-    for ($i = 1; $i <= 8; $i++) {
-      ${"entity$i"} = $storage->load(${"entity$i"}->id());
-    }
-
-    // After cron, check that entities 1-2 remain unpublished, 3-4 have now
-    // been published, 5-6 remain published and 7-8 have been unpublished.
-    $this->assertFalse($entity1->isPublished(), "After cron, $entityTypeId 1 '{$entity1->label()}' should be unpublished.");
-    $this->assertFalse($entity2->isPublished(), "After cron, $entityTypeId 2 '{$entity2->label()}' should be unpublished.");
-    $this->assertTrue($entity3->isPublished(), "After cron, $entityTypeId 3 '{$entity3->label()}' should be published.");
-    $this->assertTrue($entity4->isPublished(), "After cron, $entityTypeId 4 '{$entity4->label()}' should be published.");
-    $this->assertTrue($entity5->isPublished(), "After cron, $entityTypeId 5 '{$entity5->label()}' should be published.");
-    $this->assertTrue($entity6->isPublished(), "After cron, $entityTypeId 6 '{$entity6->label()}' should be published.");
-    $this->assertFalse($entity7->isPublished(), "After cron, $entityTypeId 7 '{$entity7->label()}' should be unpublished.");
-    $this->assertFalse($entity8->isPublished(), "After cron, $entityTypeId 8 '{$entity8->label()}' should be unpublished.");
-  }
-
-  /**
-   * Covers hook_scheduler_{type}_publishing_allowed()
-   *
-   * This hook is used to deny the publishing of individual entities. The test
-   * uses the customised content type which has checkboxes 'Approved for
-   * publishing' and 'Approved for unpublishing'.
-   *
-   * @dataProvider dataCustomEntityTypes()
-   */
-  public function testPublishingAllowed($entityTypeId, $bundle) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    $titleField = $this->titleField($entityTypeId);
-    $this->drupalLogin($this->webUser);
-
-    // Check the 'approved for publishing' field is shown on the entity form.
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->assertSession()->fieldExists('edit-field-approved-publishing-value');
-
-    // Check that the message is shown when scheduling an entity for publishing
-    // which is not yet allowed to be published.
-    $edit = [
-      "{$titleField}[0][value]" => "Blue $entityTypeId - Set publish-on date without approval",
-      'publish_on[0][value][date]' => date('Y-m-d', time() + 3),
-      'publish_on[0][value][time]' => date('H:i:s', time() + 3),
-    ];
-    $this->submitForm($edit, 'Save');
-    $this->assertSession()->pageTextMatches('/is scheduled for publishing.* but will not be published until approved/');
-
-    // Create an entity that is scheduled but not approved for publishing. Then
-    // run cron for scheduler, and check that the entity is still not published.
-    $entity = $this->createUnapprovedEntity($entityTypeId, $bundle, 'publish_on');
-    scheduler_cron();
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertFalse($entity->isPublished(), "Unapproved '{$entity->label()}' should not be published during cron processing.");
-
-    // Create an entity and approve it for publishing, run cron for scheduler
-    // and check that the entity is published. This is a stronger test than
-    // simply approving the previously used entity above, as we do not know what
-    // publish state that may be in after the cron run above.
-    $entity = $this->createUnapprovedEntity($entityTypeId, $bundle, 'publish_on');
-    $this->approveEntity($entityTypeId, $entity->id(), 'field_approved_publishing');
-    $this->assertFalse($entity->isPublished(), "New approved '{$entity->label()}' should not be initially published.");
-    scheduler_cron();
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), "Approved '{$entity->label()}' should be published during cron processing.");
-
-    // Turn on immediate publishing when the date is in the past and repeat
-    // the tests. It is not needed to run cron jobs here.
-    $bundle_field_name = $entity->getEntityType()->get('entity_keys')['bundle'];
-    $entity->$bundle_field_name->entity->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
-
-    // Check that an entity can be approved and published programatically.
-    $entity = $this->createUnapprovedEntity($entityTypeId, $bundle, 'publish_on');
-    $this->assertFalse($entity->isPublished(), "New unapproved '{$entity->label()}' with a date in the past should not be published immediately after saving.");
-    $this->approveEntity($entityTypeId, $entity->id(), 'field_approved_publishing');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), "New approved '{$entity->label()}' with a date in the past should be published immediately when created programatically.");
-
-    // Check that an entity can be approved and published via edit form.
-    $entity = $this->createUnapprovedEntity($entityTypeId, $bundle, 'publish_on');
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm(['field_approved_publishing[value]' => '1'], 'Save');
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), "Approved '{$entity->label()}' with a date in the past is published immediately after saving via edit form.");
-  }
-
-  /**
-   * Covers hook_scheduler_{type}_unpublishing_allowed()
-   *
-   * This hook is used to deny the unpublishing of individual entities. This
-   * test is simpler than the test sequence for allowed publishing, because the
-   * past date 'publish' option is not applicable.
-   *
-   * @dataProvider dataCustomEntityTypes()
-   */
-  public function testUnpublishingAllowed($entityTypeId, $bundle) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    $titleField = $this->titleField($entityTypeId);
-    $this->drupalLogin($this->webUser);
-
-    // Check the 'approved for unpublishing' field is shown on the entity form.
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->assertSession()->fieldExists('edit-field-approved-unpublishing-value');
-
-    // Check that the message is shown when scheduling an entity for
-    // unpublishing which is not yet allowed to be unpublished.
-    $edit = [
-      "{$titleField}[0][value]" => "Red $entityTypeId - Set unpublish-on date without approval",
-      'unpublish_on[0][value][date]' => date('Y-m-d', time() + 3),
-      'unpublish_on[0][value][time]' => date('H:i:s', time() + 3),
-    ];
-    $this->submitForm($edit, 'Save');
-    $this->assertSession()->pageTextMatches('/is scheduled for unpublishing.* but will not be unpublished until approved/');
-
-    // Create an entity that is scheduled but not approved for unpublishing, run
-    // cron for scheduler, and check that the entity is still published.
-    $entity = $this->createUnapprovedEntity($entityTypeId, $bundle, 'unpublish_on');
-    scheduler_cron();
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), "Unapproved '{$entity->label()}' should not be unpublished during cron processing.");
-
-    // Create an entity and approve it for unpublishing, run cron for scheduler
-    // and check that the entity is unpublished.
-    $entity = $this->createUnapprovedEntity($entityTypeId, $bundle, 'unpublish_on');
-    $this->approveEntity($entityTypeId, $entity->id(), 'field_approved_unpublishing');
-    $this->assertTrue($entity->isPublished(), "New approved '{$entity->label()}' should initially remain published.");
-    scheduler_cron();
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertFalse($entity->isPublished(), "Approved '{$entity->label()}' should be unpublished during cron processing.");
-  }
-
-  /**
-   * Creates a new entity that is not approved.
-   *
-   * The entity will have a publish/unpublish date in the past to make sure it
-   * will be included in the next cron run.
-   *
-   * @param string $entityTypeId
-   *   The entity type to create, 'node' or 'media'.
-   * @param string $bundle
-   *   The bundle to create, 'scheduler_api_test' or 'scheduler_api_media_test'.
-   * @param string $date_field
-   *   The Scheduler date field to set, either 'publish_on' or 'unpublish_on'.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface
-   *   The created entity object.
-   */
-  protected function createUnapprovedEntity($entityTypeId, $bundle, $date_field) {
-    $settings = [
-      'title' => (($date_field == 'publish_on') ? 'Blue' : 'Red') . " $entityTypeId {$this->randomMachineName(10)}",
-      'status' => ($date_field == 'unpublish_on'),
-      $date_field => strtotime('-1 day'),
-      'field_approved_publishing' => 0,
-      'field_approved_unpublishing' => 0,
-    ];
-    return $this->createEntity($entityTypeId, $bundle, $settings);
-  }
-
-  /**
-   * Approves an entity for publication or unpublication.
-   *
-   * @param string $entityTypeId
-   *   The entity type to approve, 'node' or 'media'.
-   * @param int $id
-   *   The id of the entity to approve.
-   * @param string $field_name
-   *   The name of the field to set, either 'field_approved_publishing' or
-   *   'field_approved_unpublishing'.
-   */
-  protected function approveEntity($entityTypeId, $id, $field_name) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    $storage->resetCache([$id]);
-    $entity = $storage->load($id);
-    $entity->set($field_name, TRUE);
-    $label_field = $entity->getEntityType()->get('entity_keys')['label'];
-    $entity->set($label_field, $entity->label() . " - approved for publishing: {$entity->field_approved_publishing->value}, for unpublishing: {$entity->field_approved_unpublishing->value}")->save();
-  }
-
-  /**
-   * Tests the hooks which allow hiding of scheduler input fields.
-   *
-   * This test covers:
-   *   hook_scheduler_hide_publish_date()
-   *   hook_scheduler_hide_unpublish_date()
-   *   hook_scheduler_{type}_hide_publish_date()
-   *   hook_scheduler_{type}_hide_unpublish_date()
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testHideDateField($entityTypeId, $bundle) {
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create test entities.
-    $entity1 = $this->createEntity($entityTypeId, $bundle, [
-      'title' => "Red $entityTypeId will have neither field hidden",
-    ]);
-    $entity2 = $this->createEntity($entityTypeId, $bundle, [
-      'title' => "Orange $entityTypeId will have the publish-on field hidden",
-    ]);
-    $entity3 = $this->createEntity($entityTypeId, $bundle, [
-      'title' => "Yellow $entityTypeId will have the unpublish-on field hidden",
-    ]);
-    $entity4 = $this->createEntity($entityTypeId, $bundle, [
-      'title' => "Green $entityTypeId will have both Scheduler fields hidden",
-    ]);
-
-    // Set the scheduler fieldset to always expand, for ease during development.
-    $bundle_field_name = $entity1->getEntityType()->get('entity_keys')['bundle'];
-    $entity1->$bundle_field_name->entity->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save();
-
-    /** @var \Drupal\Tests\WebAssert $assert */
-    $assert = $this->assertSession();
-
-    // Entity 1 'Red' should have both fields displayed.
-    $this->drupalGet($entity1->toUrl('edit-form'));
-    $assert->ElementExists('xpath', '//input[@id = "edit-publish-on-0-value-date"]');
-    $assert->ElementExists('xpath', '//input[@id = "edit-unpublish-on-0-value-date"]');
-
-    // Entity 2 'Orange' should have only the publish-on field hidden.
-    $this->drupalGet($entity2->toUrl('edit-form'));
-    $assert->ElementNotExists('xpath', '//input[@id = "edit-publish-on-0-value-date"]');
-    $assert->ElementExists('xpath', '//input[@id = "edit-unpublish-on-0-value-date"]');
-
-    // Entity 3 'Yellow' should have only the unpublish-on field hidden.
-    $this->drupalGet($entity3->toUrl('edit-form'));
-    $assert->ElementExists('xpath', '//input[@id = "edit-publish-on-0-value-date"]');
-    $assert->ElementNotExists('xpath', '//input[@id = "edit-unpublish-on-0-value-date"]');
-
-    // Entity 4 'Green' should have both publish-on and unpublish-on hidden.
-    $this->drupalGet($entity4->toUrl('edit-form'));
-    $assert->ElementNotExists('xpath', '//input[@id = "edit-publish-on-0-value-date"]');
-    $assert->ElementNotExists('xpath', '//input[@id = "edit-unpublish-on-0-value-date"]');
-  }
-
-  /**
-   * Tests when other modules execute the 'publish' and 'unpublish' processes.
-   *
-   * This test covers:
-   *   hook_scheduler_publish_process()
-   *   hook_scheduler_unpublish_process()
-   *   hook_scheduler_{type}_publish_process()
-   *   hook_scheduler_{type}_unpublish_process()
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testPublishUnpublishProcess($entityTypeId, $bundle) {
-    // $this->drupalLogin($this->schedulerUser);
-    $storage = $this->entityStorageObject($entityTypeId);
-
-    // Create test entities.
-    $entity1 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Red $entityTypeId will cause a failure on publishing",
-      'publish_on' => strtotime('-1 day'),
-    ]);
-    $entity2 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Orange $entityTypeId will be unpublished by the API test module not Scheduler",
-      'unpublish_on' => strtotime('-1 day'),
-    ]);
-    $entity3 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => FALSE,
-      'title' => "Yellow $entityTypeId will be published by the API test module not Scheduler",
-      'publish_on' => strtotime('-1 day'),
-    ]);
-    // 'Green' will have both fields hidden so is harder to test manually.
-    // Therefore introduce a different colour - Blue.
-    $entity4 = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'title' => "Blue $entityTypeId will cause a failure on unpublishing",
-      'unpublish_on' => strtotime('-1 day'),
-    ]);
-
-    // Simulate a cron run.
-    scheduler_cron();
-
-    // Check red.
-    $storage->resetCache([$entity1->id()]);
-    $entity1 = $storage->load($entity1->id());
-    $this->assertFalse($entity1->isPublished(), 'Red should remain unpublished.');
-    $this->assertNotEmpty($entity1->publish_on->value, 'Red should still have a publish-on date.');
-
-    // Check orange.
-    $storage->resetCache([$entity2->id()]);
-    $entity2 = $storage->load($entity2->id());
-    $this->assertFalse($entity2->isPublished(), 'Orange should be unpublished.');
-    $this->assertStringContainsString('unpublishing processed by API test module', $entity2->label(), 'Orange should be processed by the API test module.');
-    $this->assertEmpty($entity2->unpublish_on->value, 'Orange should not have an unpublish-on date.');
-
-    // Check yellow.
-    $storage->resetCache([$entity3->id()]);
-    $entity3 = $storage->load($entity3->id());
-    $this->assertTrue($entity3->isPublished(), 'Yellow should be published.');
-    $this->assertStringContainsString('publishing processed by API test module', $entity3->label(), 'Yellow should be processed by the API test module.');
-    $this->assertEmpty($entity3->publish_on->value, 'Yellow should not have a publish-on date.');
-
-    // Check blue.
-    $storage->resetCache([$entity4->id()]);
-    $entity4 = $storage->load($entity4->id());
-    $this->assertTrue($entity4->isPublished(), 'Blue should remain published.');
-    $this->assertNotEmpty($entity4->unpublish_on->value, 'Blue should still have an unpublish-on date.');
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
index c79c6bcc11b0bb7eb531b296e4ab94d4ab851f3e..a182fd4454006b2c2549bddda39653336e3a24c3 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
@@ -59,8 +59,7 @@ public function testLightweightCronSettingsForm() {
     $this->assertEquals(20, strlen($key), 'The default lightweight cron key string length should be 20');
 
     // Check that a new random key can be generated.
-    $this->drupalGet($this->routeCronForm);
-    $this->submitForm([], 'Generate new random key');
+    $this->drupalPostForm($this->routeCronForm, [], 'Generate new random key');
     $new_key_xpath = $this->xpath('//input[@id="edit-lightweight-access-key"]/@value');
     $new_key = $new_key_xpath[0]->getText();
     $this->assertNotEmpty($new_key, 'The lightweight cron key field should not be empty after generating a new key');
@@ -68,13 +67,11 @@ public function testLightweightCronSettingsForm() {
     $this->assertNotEquals($new_key, $key, 'The new lightweight cron key should be different from the previous key.');
 
     // Check that the 'run lightweight cron' button works.
-    $this->drupalGet($this->routeCronForm);
-    $this->submitForm([], "Run Scheduler's lightweight cron now");
+    $this->drupalPostForm($this->routeCronForm, [], "Run Scheduler's lightweight cron now");
     $this->assertSession()->pageTextContains('Lightweight cron run completed.');
 
     // Check that the form cannot be saved if the cron key is blank.
-    $this->drupalGet($this->routeCronForm);
-    $this->submitForm(['lightweight_access_key' => ''], 'Save configuration');
+    $this->drupalPostForm($this->routeCronForm, ['lightweight_access_key' => ''], 'Save configuration');
     $this->assertSession()->pageTextContains('Lightweight cron access key field is required.');
     $this->assertSession()->pageTextNotContains('The configuration options have been saved.');
   }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMessageTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMessageTest.php
index 7e6bc915e4c4dfb4970eae124421b79cf663fb74..8ca84158424e5ba07f332e3d3b58e78fe6964dd2 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerMessageTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerMessageTest.php
@@ -3,21 +3,18 @@
 namespace Drupal\Tests\scheduler\Functional;
 
 /**
- * Tests the 'show confirmation message' entity type setting.
+ * Tests the option to display or not display the confirmations message.
  *
  * @group scheduler
  */
 class SchedulerMessageTest extends SchedulerBrowserTestBase {
 
   /**
-   * Tests the option to display or not display the confirmation message.
-   *
-   * @dataProvider dataStandardEntityTypes()
+   * Test the                .
    */
-  public function testConfirmationMessage($entityTypeId, $bundle) {
+  public function testConfirmationMessage() {
     // Log in.
     $this->drupalLogin($this->schedulerUser);
-    $titleField = $this->titleField($entityTypeId);
 
     $publish_on = strtotime('+ 1 day 5 hours');
     $unpublish_on = strtotime('+ 2 day 7 hours');
@@ -30,62 +27,52 @@ public function testConfirmationMessage($entityTypeId, $bundle) {
     // Create the content and check that the messages are shown by default.
     // First just a publish_on date.
     $edit = [
-      "{$titleField}[0][value]" => $title1,
+      'title[0][value]' => $title1,
       'publish_on[0][value][date]' => date('Y-m-d', $publish_on),
       'publish_on[0][value][time]' => date('H:i:s', $publish_on),
     ];
-    $add_url = $this->entityAddUrl($entityTypeId, $bundle);
-    $this->drupalGet($add_url);
-    $this->submitForm($edit, 'Save');
-    $entity1 = $this->getEntityByTitle($entityTypeId, $title1);
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node1 = $this->drupalGetNodeByTitle($title1);
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s', $title1, $publish_on_formatted));
 
     // Second, just an unpublish_on date.
     $edit = [
-      "{$titleField}[0][value]" => $title2,
+      'title[0][value]' => $title2,
       'unpublish_on[0][value][date]' => date('Y-m-d', $unpublish_on),
       'unpublish_on[0][value][time]' => date('H:i:s', $unpublish_on),
     ];
-    $this->drupalGet($add_url);
-    $this->submitForm($edit, 'Save');
-    $entity2 = $this->getEntityByTitle($entityTypeId, $title2);
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node2 = $this->drupalGetNodeByTitle($title2);
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be unpublished %s', $title2, $unpublish_on_formatted));
 
-    // Third, with both dates.
+    // Third, a node with both dates.
     $edit = [
-      "{$titleField}[0][value]" => $title3,
+      'title[0][value]' => $title3,
       'publish_on[0][value][date]' => date('Y-m-d', $publish_on),
       'publish_on[0][value][time]' => date('H:i:s', $publish_on),
       'unpublish_on[0][value][date]' => date('Y-m-d', $unpublish_on),
       'unpublish_on[0][value][time]' => date('H:i:s', $unpublish_on),
     ];
-    $this->drupalGet($add_url);
-    $this->submitForm($edit, 'Save');
-    $entity3 = $this->getEntityByTitle($entityTypeId, $title3);
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node3 = $this->drupalGetNodeByTitle($title3);
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s and unpublished %s', $title3, $publish_on_formatted, $unpublish_on_formatted));
 
     // Change the option to not display the messages.
-    $this->entityTypeObject($entityTypeId, $bundle)->setThirdPartySetting('scheduler', 'show_message_after_update', FALSE)->save();
-    $this->drupalGet($entity1->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
+    $this->nodetype->setThirdPartySetting('scheduler', 'show_message_after_update', FALSE)->save();
+    $this->drupalPostForm('node/' . $node1->id() . '/edit', [], 'Save');
     $this->assertSession()->pageTextNotContains('is scheduled to be published');
-    $this->drupalGet($entity2->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
+    $this->drupalPostForm('node/' . $node2->id() . '/edit', [], 'Save');
     $this->assertSession()->pageTextNotContains('is scheduled to be unpublished');
-    $this->drupalGet($entity3->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
+    $this->drupalPostForm('node/' . $node3->id() . '/edit', [], 'Save');
     $this->assertSession()->pageTextNotContains('is scheduled to be published');
 
     // Set back to display the messages, and check after edit.
-    $this->entityTypeObject($entityTypeId, $bundle)->setThirdPartySetting('scheduler', 'show_message_after_update', TRUE)->save();
-    $this->drupalGet($entity1->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
+    $this->nodetype->setThirdPartySetting('scheduler', 'show_message_after_update', TRUE)->save();
+    $this->drupalPostForm('node/' . $node1->id() . '/edit', [], 'Save');
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s', $title1, $publish_on_formatted));
-    $this->drupalGet($entity2->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
+    $this->drupalPostForm('node/' . $node2->id() . '/edit', [], 'Save');
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be unpublished %s', $title2, $unpublish_on_formatted));
-    $this->drupalGet($entity3->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
+    $this->drupalPostForm('node/' . $node3->id() . '/edit', [], 'Save');
     $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s and unpublished %s', $title3, $publish_on_formatted, $unpublish_on_formatted));
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
index 86b6a0c6feddcac2264f1be722b1ea1b9e77747f..b4b8116ca08cf1eb228e115b647cd3266ed5a0fd 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
@@ -10,47 +10,39 @@
 class SchedulerMetaInformationTest extends SchedulerBrowserTestBase {
 
   /**
-   * Tests meta-information on scheduled entities.
+   * Tests meta-information on scheduled nodes.
    *
-   * When an entity is scheduled for unpublication, an X-Robots-Tag HTTP header
-   * is included, telling crawlers about when an item will expire and should be
-   * removed from search results.
-   *
-   * @dataProvider dataStandardEntityTypes()
+   * When nodes are scheduled for unpublication, an X-Robots-Tag HTTP header is
+   * sent, alerting crawlers about when an item expires and should be removed
+   * from search results.
    */
-  public function testMetaInformation($entityTypeId, $bundle) {
+  public function testMetaInformation() {
     // Log in.
     $this->drupalLogin($this->schedulerUser);
 
-    // Create a published entity without scheduling dates.
-    $entity = $this->createEntity($entityTypeId, $bundle, ['status' => TRUE]);
+    // Create a published node without scheduling.
+    $published_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+    ]);
+    $this->drupalGet('node/' . $published_node->id());
 
     // Since we did not set an unpublish date, there should be no X-Robots-Tag
     // header on the response.
-    $this->drupalGet($entity->toUrl());
-    $this->assertNull($this->getSession()->getResponseHeader('X-Robots-Tag'), 'X-Robots-Tag should not be present when no unpublish date is set.');
-    // Also check that there is no meta tag.
-    $this->assertSession()->responseNotContains('unavailable_after:');
+    $this->assertNull($this->drupalGetHeader('X-Robots-Tag'), 'X-Robots-Tag is not present when no unpublish date is set.');
 
-    // Set an unpublish date on the entity.
+    // Set a scheduler unpublish date on the node.
     $unpublish_date = strtotime('+1 day');
-    $entity->set('unpublish_on', $unpublish_date)->save();
+    $edit = [
+      'unpublish_on[0][value][date]' => $this->dateFormatter->format($unpublish_date, 'custom', 'Y-m-d'),
+      'unpublish_on[0][value][time]' => $this->dateFormatter->format($unpublish_date, 'custom', 'H:i:s'),
+    ];
+    $this->drupalPostForm('node/' . $published_node->id() . '/edit', $edit, 'Save');
 
-    // The entity full page view should now have an X-Robots-Tag header with an
+    // The node page should now have an X-Robots-Tag header with an
     // unavailable_after-directive and RFC850 date- and time-value.
-    $this->drupalGet($entity->toUrl());
+    $this->drupalGet('node/' . $published_node->id());
     $this->assertSession()->responseHeaderEquals('X-Robots-Tag', 'unavailable_after: ' . date(DATE_RFC850, $unpublish_date));
-
-    // Check that the required meta tag is added to the html head section.
-    $this->assertSession()->responseMatches('~meta name=[\'"]robots[\'"] content=[\'"]unavailable_after: ' . date(DATE_RFC850, $unpublish_date) . '[\'"]~');
-
-    // If the entity type has a summary listing page, check that the entity is
-    // shown but the two tags are not present.
-    if ($this->drupalGet("$entityTypeId") && $this->getSession()->getStatusCode() == '200') {
-      $this->assertSession()->pageTextContains($entity->label());
-      $this->assertNull($this->getSession()->getResponseHeader('X-Robots-Tag'), 'X-Robots-Tag should not be added when entity is not in "full" view mode.');
-      $this->assertSession()->responseNotContains('unavailable_after:');
-    }
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
index 9a0b876aa21cdb38387494be80773ee9d04de6d3..79bd226b8db9354981bbb029dfd6d105f50a5c77 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
@@ -38,7 +38,7 @@ class SchedulerMultilingualTest extends SchedulerBrowserTestBase {
   protected function setUp(): void {
     parent::setUp();
 
-    // Add four extra permissions for the adminUser -
+    // Create a user with the required translation permissions.
     // 'administer languages' for url admin/config/regional/content-language.
     // 'administer content translation' to show the list of content fields at
     // url admin/config/regional/content-language.
@@ -46,13 +46,21 @@ protected function setUp(): void {
     // url node/*/translations.
     // 'translate any entity' for the 'add translation' link on the translations
     // page, url node/*/translations/add/.
-    $this->addPermissionsToUser($this->adminUser, [
+    $this->translatorUser = $this->drupalCreateUser([
       'administer languages',
       'administer content translation',
       'create content translations',
       'translate any entity',
     ]);
-    $this->drupalLogin($this->adminUser);
+
+    // Get the additional role already assigned to the scheduler admin user
+    // created in SchedulerBrowserTestBase and add this role to the translator
+    // user, to avoid switching between users throughout this test.
+    $admin_roles = $this->adminUser->getRoles();
+    // Key 0 is 'authenticated' role. Key 1 is the first real role.
+    $this->translatorUser->addRole($admin_roles[1]);
+    $this->translatorUser->save();
+    $this->drupalLogin($this->translatorUser);
 
     // Allow scheduler dates in the past to be published on next cron run.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
@@ -130,23 +138,23 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     // The submit shows the updated values, so no need for second get.
     $this->submitForm($settings, 'Save configuration');
 
+    $early_return = FALSE;
     if ($publish_on_translatable <> $status_translatable) {
       // Check for validation form error on status and publish_on.
       $this->assertSession()->elementExists('xpath', '//input[@id = "edit-settings-node-' . $this->type . '-fields-publish-on" and contains(@class, "error")]');
       $this->assertSession()->elementExists('xpath', '//input[@id = "edit-settings-node-' . $this->type . '-fields-status" and contains(@class, "error")]');
+      $early_return = TRUE;
     }
     if ($unpublish_on_translatable <> $status_translatable) {
       // Check for validation form error on status and unpublish_on.
       $this->assertSession()->elementExists('xpath', '//input[@id = "edit-settings-node-' . $this->type . '-fields-unpublish-on" and contains(@class, "error")]');
       $this->assertSession()->elementExists('xpath', '//input[@id = "edit-settings-node-' . $this->type . '-fields-status" and contains(@class, "error")]');
+      $early_return = TRUE;
     }
-
-    if (empty($expected_status_values_before)) {
-      // The test data on this run was to verify the validation messages above.
+    if ($early_return) {
       // The rest of the test is meaningless so skip it and move to the next.
       return;
     }
-    $this->assertSession()->pageTextContains('Settings successfully updated.');
 
     // Create a node in the 'original' language, before any translations. It is
     // unpublished with no scheduled date.
@@ -169,7 +177,7 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     ];
     $this->submitForm($edit, 'Save');
 
-    // Create second translation, for publishing and unpublishing in the future.
+    // Create second translation, for publishing and unpublising in the future.
     $this->drupalGet('node/' . $nid . '/translations/add/' . $this->languages[0]['code'] . '/' . $this->languages[2]['code']);
     $edit = [
       'title[0][value]' => $this->languages[2]['name'] . '(2) - Publish in the future',
@@ -180,25 +188,21 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     ];
     $this->submitForm($edit, 'Save');
 
-    // Reset the cache, reload the node, and check if the dates of translation 2
-    // have been synchronized onto the other translations, or not, as required.
+    // Reset the cache, reload the node, and check if the dates of translation
+    // 3 have been synchronized to the other translations, or not, as required.
     $this->nodeStorage->resetCache([$nid]);
     $node = $this->nodeStorage->load($nid);
     $translation1 = $node->getTranslation($this->languages[1]['code']);
     $translation2 = $node->getTranslation($this->languages[2]['code']);
     if ($publish_on_translatable) {
-      $this->assertNotEquals($translation2->publish_on->value, $node->publish_on->value, 'The original translation publish_on should not be synchronized');
-      $this->assertNotEquals($translation2->unpublish_on->value, $node->unpublish_on->value, 'The original translation unpublish_on should not be synchronized');
-      $this->assertNotEquals($translation2->publish_on->value, $translation1->publish_on->value, 'Translation1 publish_on should not be synchronized');
-      $this->assertNotEquals($translation2->unpublish_on->value, $translation1->unpublish_on->value, 'Translation1 unpublish_on should not be synchronized');
+      $this->assertNotEquals($translation2->publish_on->value, $node->publish_on->value, 'Node publish_on');
+      $this->assertNotEquals($translation2->unpublish_on->value, $node->unpublish_on->value, 'Node unpublish_on');
     }
     else {
-      $this->assertEquals($translation2->publish_on->value, $node->publish_on->value, 'The original translation publish_on should be synchronized');
-      $this->assertEquals($translation2->unpublish_on->value, $node->unpublish_on->value, 'The original translation unpublish_on should be synchronized');
-      $this->assertEquals($translation2->isPublished(), $node->isPublished(), 'The original translation status should be synchronized');
-      $this->assertEquals($translation2->publish_on->value, $translation1->publish_on->value, 'Translation1 publish_on should be synchronized');
-      $this->assertEquals($translation2->unpublish_on->value, $translation1->unpublish_on->value, 'Translation1 unpublish_on should be synchronized');
-      $this->assertEquals($translation2->isPublished(), $translation1->isPublished(), 'Translation1 status should be synchronized');
+      $this->assertEquals($translation2->publish_on->value, $node->publish_on->value, 'Node publish_on');
+      $this->assertEquals($translation2->unpublish_on->value, $node->unpublish_on->value, 'Node unpublish_on');
+      $this->assertEquals($translation2->publish_on->value, $translation1->publish_on->value, 'Translation 1 publish_on');
+      $this->assertEquals($translation2->unpublish_on->value, $translation1->unpublish_on->value, 'Translation 1 unpublish_on');
     }
 
     // Create the third translation, to be published in the past.
@@ -210,33 +214,30 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     ];
     $this->submitForm($edit, 'Save');
 
-    // Reset the cache, reload the node, and check if the dates of translation 3
-    // have been synchronized onto the other translations, or not, as required.
+    // Reset the cache, reload the node, and check if the dates of translation
+    // 3 have been synchronized to the other translations, or not, as required.
     $this->nodeStorage->resetCache([$nid]);
     $node = $this->nodeStorage->load($nid);
     $translation1 = $node->getTranslation($this->languages[1]['code']);
     $translation2 = $node->getTranslation($this->languages[2]['code']);
     $translation3 = $node->getTranslation($this->languages[3]['code']);
     if ($publish_on_translatable) {
-      $this->assertNotEquals($translation3->publish_on->value, $translation2->publish_on->value, 'The original translation publish_on should not be synchronized');
-      $this->assertNotEquals($translation3->unpublish_on->value, $translation2->unpublish_on->value, 'The original translation unpublish_on should not be synchronized');
+      $this->assertNotEquals($translation3->publish_on->value, $translation2->publish_on->value, 'Node publish_on');
+      $this->assertNotEquals($translation3->unpublish_on->value, $translation2->unpublish_on->value, 'Node unpublish_on');
     }
     else {
       // The scheduer dates should be synchronized across all translations.
-      $this->assertEquals($translation3->publish_on->value, $node->publish_on->value, 'The original translation publish_on should be synchronized');
-      $this->assertEquals($translation3->unpublish_on->value, $node->unpublish_on->value, 'The original translation unpublish_on should be synchronized');
-      $this->assertEquals($translation3->isPublished(), $node->isPublished(), 'The original translation status should be synchronized');
-      $this->assertEquals($translation3->publish_on->value, $translation1->publish_on->value, 'Translation1 publish_on should be synchronized');
-      $this->assertEquals($translation3->unpublish_on->value, $translation1->unpublish_on->value, 'Translation1 unpublish_on should be synchronized');
-      $this->assertEquals($translation3->isPublished(), $translation1->isPublished(), 'Translation1 status should be synchronized');
-      $this->assertEquals($translation3->publish_on->value, $translation2->publish_on->value, 'Translation2 publish_on should be synchronized');
-      $this->assertEquals($translation3->unpublish_on->value, $translation2->unpublish_on->value, 'Translation2 unpublish_on should be synchronized');
-      $this->assertEquals($translation3->isPublished(), $translation2->isPublished(), 'Translation2 status should be synchronized');
+      $this->assertEquals($translation3->publish_on->value, $node->publish_on->value, 'Node publish_on');
+      $this->assertEquals($translation3->unpublish_on->value, $node->unpublish_on->value, 'Node unpublish_on');
+      $this->assertEquals($translation3->publish_on->value, $translation1->publish_on->value, 'Translation 1 publish_on');
+      $this->assertEquals($translation3->unpublish_on->value, $translation1->unpublish_on->value, 'Translation 1 unpublish_on');
+      $this->assertEquals($translation3->publish_on->value, $translation2->publish_on->value, 'Translation 2 publish_on');
+      $this->assertEquals($translation3->unpublish_on->value, $translation2->unpublish_on->value, 'Translation 2 unpublish_on');
     }
 
     // For info only.
     $this->drupalGet($this->languages[0]['code'] . '/node/' . $nid . '/translations');
-    $this->drupalGet($this->adminUrl('scheduled', 'node'));
+    $this->drupalGet('admin/content/scheduled');
 
     // Check that the status of all four pieces of content before running cron
     // match the expected values.
@@ -247,8 +248,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     $this->checkStatus($nid, 'After cron', $expected_status_values_after);
 
     // For info only.
-    $this->drupalGet($this->adminUrl('scheduled', 'node'));
-    $this->drupalGet($this->adminUrl('collection', 'node'));
+    $this->drupalGet('admin/content/scheduled');
+    $this->drupalGet('admin/content');
     $this->drupalGet('admin/reports/dblog');
     $this->drupalGet($this->languages[0]['code'] . '/node/' . $nid . '/translations');
   }
@@ -256,11 +257,9 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
   /**
    * Provides data for testPublishingTranslations().
    *
-   * Case 1 when the dates are translatable and can differ between translations.
-   * Case 2 when the dates are not translatable and the behavior should be
+   * Case 1 when the date is translatable and can differ between translations.
+   * Case 2 when the date is not translatable and the behavior should be
    *   consistent over all translations.
-   * Case 3 - 8 when there are differences in the settings and the validation
-   *   should prevent the form being saved.
    *
    * @return array
    *   The test data. Each array element has the format:
@@ -271,8 +270,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
    *   Expected status of four translations after cron
    */
   public function dataPublishingTranslations() {
-    // The key text is just for info, and shows which fields are translatable.
-    $data = [
+    // The key text relates to which fields are translatable.
+    return [
       'all fields' => [TRUE, TRUE, TRUE,
         [FALSE, TRUE, FALSE, FALSE],
         [FALSE, TRUE, FALSE, TRUE],
@@ -289,7 +288,6 @@ public function dataPublishingTranslations() {
       'publish_on and status' => [TRUE, FALSE, TRUE, [], []],
       'unpublish_on and status' => [FALSE, TRUE, TRUE, [], []],
     ];
-    return $data;
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ca88be88584ccb554e949f867496973d5a4954e
--- /dev/null
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\Tests\scheduler\Functional;
+
+/**
+ * Tests that Scheduler cron has full access to the scheduled nodes.
+ *
+ * This test uses an additional test module 'scheduler_access_test' which uses
+ * a custom node access definition to deny viewing of all nodes.
+ *
+ * @group scheduler
+ */
+class SchedulerNodeAccessTest extends SchedulerBrowserTestBase {
+
+  /**
+   * Additional modules required.
+   *
+   * @var array
+   */
+  protected static $modules = ['scheduler_access_test'];
+
+  /**
+   * Tests Scheduler cron functionality when access to the nodes is denied.
+   */
+  public function testNodeAccess() {
+
+    // scheduler_access_test_install() sets node_access_needs_rebuild(TRUE) and
+    // this works when testing the module interactively, but during simpletest
+    // the node access table is not rebuilt. Hence do that here explicitly here.
+    node_access_rebuild();
+
+    // Create data to test publishing then unpublishing via loop.
+    // @todo Convert this test to use a @dataProvider function instead of this
+    // array and the loop.
+    $test_data = [
+      'publish_on' => [
+        'status' => FALSE,
+        'before' => 'unpublished',
+        'after' => 'published',
+      ],
+      'unpublish_on' => [
+        'status' => TRUE,
+        'before' => 'published',
+        'after' => 'unpublished',
+      ],
+    ];
+
+    foreach ($test_data as $field => $data) {
+      // Create a node with the necessary scheduler date.
+      $settings = [
+        'type' => $this->type,
+        'status' => $data['status'],
+        'title' => 'Test node to be ' . $data['after'],
+        $field => $this->requestTime + 1,
+      ];
+      $node = $this->drupalCreateNode($settings);
+      $this->drupalGet('node/' . $node->id());
+      // Before running cron, viewing the node should give "403 Not Authorized".
+      $this->assertSession()->statusCodeEquals(403);
+
+      // Delay so that the date entered is now in the past, then run cron.
+      sleep(2);
+      $this->cronRun();
+
+      // Reload the node.
+      $this->nodeStorage->resetCache([$node->id()]);
+      $node = $this->nodeStorage->load($node->id());
+      // Check that the node has been published or unpublished as required.
+      $this->assertTrue($node->isPublished() === !$data['status'], 'Scheduler has ' . $data['after'] . ' the node via cron.');
+
+      // Check the node is still not viewable.
+      $this->drupalGet('node/' . $node->id());
+      // After cron, viewing the node should still give "403 Not Authorized".
+      $this->assertSession()->statusCodeEquals(403);
+    }
+
+    // Log in and assert that the two dblog messages are shown.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('admin/reports/dblog');
+    $this->assertSession()->pageTextContains('scheduled publishing');
+    $this->assertSession()->pageTextContains('scheduled unpublishing');
+  }
+
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
index 18ad8140e0b02575a2044f2915f2c194a21fc576..69232e49a9849c7c630f274a5255db29ea5c33d3 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
@@ -3,69 +3,36 @@
 namespace Drupal\Tests\scheduler\Functional;
 
 /**
- * Tests entity types which are not enabled for scheduling.
+ * Tests a content type which is not enabled for scheduling.
  *
  * @group scheduler
  */
 class SchedulerNonEnabledTypeTest extends SchedulerBrowserTestBase {
 
   /**
-   * Additional core module field_ui is required for entity form display page.
+   * Tests the publish_enable and unpublish_enable node type settings.
    *
-   * @var array
+   * @dataProvider dataNonEnabledType()
    */
-  protected static $modules = ['field_ui'];
-
-  /**
-   * Tests the publish_enable and unpublish_enable entity type settings.
-   *
-   * @dataProvider dataNonEnabledScenarios()
-   */
-  public function testNonEnabledType($id, $entityTypeId, $bundle, $description, $publishing_enabled, $unpublishing_enabled) {
-    // Give adminUser the permissions to use the field_ui 'manage form display'
-    // tab for the entity type being tested.
-    $this->addPermissionsToUser($this->adminUser, ["administer {$entityTypeId} form display"]);
+  public function testNonEnabledType($id, $description, $publishing_enabled, $unpublishing_enabled) {
     $this->drupalLogin($this->adminUser);
-    $entityType = $this->entityTypeObject($entityTypeId, $bundle);
-    $storage = $this->entityStorageObject($entityTypeId);
-    $titleField = $this->titleField($entityTypeId);
-
-    // The 'default' case specifically checks the behavior of the unchanged
-    // settings, so only change these when not running the default test.
-    if ($description != 'Default') {
-      // Set the enabled checkboxes via entity type admin form. This will also
-      // partially test the form display adjustments.
-      $this->drupalGet($this->adminUrl('bundle_edit', $entityTypeId, $bundle));
-      $edit = [
-        'scheduler_publish_enable' => $publishing_enabled,
-        'scheduler_unpublish_enable' => $unpublishing_enabled,
-      ];
-      $this->submitForm($edit, 'Save');
-
-      // Show the form display page for info.
-      $this->drupalGet($this->adminUrl('bundle_form_display', $entityTypeId, $bundle));
-
-      // ThirdPartySettings are set correctly by saving the entity type form,
-      // however this does not get replicated back to $entityType here (is this
-      // a bug is core test traits somewhere?). Thwerefore resort to setting the
-      // values here too.
-      $entityType->setThirdPartySetting('scheduler', 'publish_enable', $publishing_enabled)
+
+    // The first test case specifically checks the behavior of the default
+    // unchanged settings, so only change these settings for later runs.
+    if ($id > 0) {
+      $this->nonSchedulerNodeType->setThirdPartySetting('scheduler', 'publish_enable', $publishing_enabled)
         ->setThirdPartySetting('scheduler', 'unpublish_enable', $unpublishing_enabled)
         ->save();
     }
 
-    // When publishing and/or unpublishing are not enabled but the 'required'
-    // setting remains on, the entity must be able to be saved without a date.
-    $entityType->setThirdPartySetting('scheduler', 'publish_required', !$publishing_enabled)->save();
-    $entityType->setThirdPartySetting('scheduler', 'unpublish_required', !$unpublishing_enabled)->save();
-
-    // Allow dates in the past to be valid on saving the entity, to simplify the
-    // testing process.
-    $entityType->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
+    // Create info string to show what combinations are being tested.
+    $info = 'Publishing ' . ($publishing_enabled ? 'enabled' : 'not enabled')
+      . ', Unpublishing ' . ($unpublishing_enabled ? 'enabled' : 'not enabled')
+      . ', ' . $description;
 
-    // Create a new entity via the add/bundle url, and check that the correct
-    // fields are displayed on the form depending on the enabled settings.
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
+    // Check that the field(s) are displayed only for the correct settings.
+    $title = $id . 'a - ' . $info;
+    $this->drupalGet('node/add/' . $this->nonSchedulerNodeType->id());
     if ($publishing_enabled) {
       $this->assertSession()->fieldExists('publish_on[0][value][date]');
     }
@@ -80,83 +47,71 @@ public function testNonEnabledType($id, $entityTypeId, $bundle, $description, $p
       $this->assertSession()->fieldNotExists('unpublish_on[0][value][date]');
     }
 
-    // Fill in the title field and check that the entity can be saved OK.
-    $title = $id . 'a - ' . $description;
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
-    $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
-
-    // Create an unpublished entity with a publishing date, which mimics what
-    // could be done by a third-party module, or a by-product of the entity type
+    // When publishing and/or unpublishing are not enabled but the 'required'
+    // setting remains on, the node must be able to be saved without a date.
+    $this->nonSchedulerNodeType->setThirdPartySetting('scheduler', 'publish_required', !$publishing_enabled)->save();
+    $this->nonSchedulerNodeType->setThirdPartySetting('scheduler', 'unpublish_required', !$unpublishing_enabled)->save();
+    $this->drupalPostForm('node/add/' . $this->nonSchedulerNodeType->id(), ['title[0][value]' => $title], 'Save');
+    // Check that the node has saved OK.
+    $string = sprintf('%s %s has been created.', $this->nonSchedulerNodeType->get('name'), $title);
+    $this->assertSession()->pageTextContains($string);
+
+    // Create an unpublished node with a publishing date, which mimics what
+    // could be done by a third-party module, or a by-product of the node type
     // being enabled for publishing then being disabled before it got published.
-    $title = $id . 'b - ' . $description;
-    $values = [
-      "$titleField" => $title,
-      'status' => FALSE,
-      'publish_on' => $this->requestTime - 120,
+    $title = $id . 'b - ' . $info;
+    $edit = [
+      'title' => $title,
+      'status' => 0,
+      'type' => $this->nonSchedulerNodeType->id(),
+      'publish_on' => $this->requestTime - 2,
     ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $values);
-
-    // Check that the entity can be edited and saved OK.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
-    $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
+    $node = $this->drupalCreateNode($edit);
 
     // Run cron and display the dblog.
     $this->cronRun();
     $this->drupalGet('admin/reports/dblog');
 
-    // Reload the entity.
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check if the entity has been published or remains unpublished.
+    // Reload the node.
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check if the node has been published or remains unpublished.
     if ($publishing_enabled) {
-      $this->assertTrue($entity->isPublished(), "The unpublished entity '$title' should now be published");
+      $this->assertTrue($node->isPublished(), 'The unpublished node has been published - ' . $title);
     }
     else {
-      $this->assertFalse($entity->isPublished(), "The unpublished entity '$title' should remain unpublished");
+      $this->assertFalse($node->isPublished(), 'The unpublished node remains unpublished - ' . $title);
     }
 
-    // Do the same for unpublishing - create a published entity with an
-    // unpublishing date in the future, to be valid for editing and saving.
-    $title = $id . 'c - ' . $description;
-    $values = [
-      "$titleField" => $title,
-      'status' => TRUE,
-      'unpublish_on' => $this->requestTime + 180,
-    ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $values);
-
-    // Check that the entity can be edited and saved.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm([], 'Save');
-    $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
-
-    // Create a published entity with a date in the past, then run cron.
-    $title = $id . 'd - ' . $description;
-    $values = [
-      "$titleField" => $title,
-      'status' => TRUE,
-      'unpublish_on' => $this->requestTime - 120,
+    // Do the same for unpublishing.
+    $title = $id . 'c - ' . $info;
+    $edit = [
+      'title' => $title,
+      'status' => 1,
+      'type' => $this->nonSchedulerNodeType->id(),
+      'unpublish_on' => $this->requestTime - 1,
     ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $values);
+    $node = $this->drupalCreateNode($edit);
+
+    // Run cron and display the dblog.
     $this->cronRun();
     $this->drupalGet('admin/reports/dblog');
 
-    // Reload the entity.
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    // Check if the entity has been unpublished or remains published.
+    // Reload the node.
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check if the node has been unpublished or remains published.
     if ($unpublishing_enabled) {
-      $this->assertFalse($entity->isPublished(), "The published entity '$title' should now be unpublished");
+      $this->assertFalse($node->isPublished(), 'The published node has been unpublished - ' . $title);
     }
     else {
-      $this->assertTrue($entity->isPublished(), "The published entity '$title' should remain published");
+      $this->assertTrue($node->isPublished(), 'The published node remains published - ' . $title);
     }
 
     // Display the full content list and the scheduled list. Calls to these
-    // pages are for information and debug only.
-    $this->drupalGet($this->adminUrl('collection', $entityTypeId, $bundle));
-    $this->drupalGet($this->adminUrl('scheduled', $entityTypeId, $bundle));
+    // pages are for information and debug only. They could be removed.
+    $this->drupalGet('admin/content');
+    $this->drupalGet('admin/content/scheduled');
   }
 
   /**
@@ -164,34 +119,34 @@ public function testNonEnabledType($id, $entityTypeId, $bundle, $description, $p
    *
    * @return array
    *   Each item in the test data array has the follow elements:
-   *     id                     - (int) a sequential id for use in titles
-   *     entityTypeId           - (string) 'node', 'media' or 'commerce_product'
-   *     bundle                 - (string) the bundle which is not enabled
-   *     description            - (string) describing the scenario being checked
-   *     publishing_enabled     - (bool) whether publishing is enabled
-   *     unpublishing_enabled   - (bool) whether unpublishing is enabled
+   *     id                   - (in) a sequential id for use in node titles
+   *     description          - (string) describing the scenario being checked
+   *     publishing_enabled   - (bool) whether publishing is enabled
+   *     unpublishing_enabled - (bool) whether unpublishing is enabled
    */
-  public function dataNonEnabledScenarios() {
-    $data = [];
-    foreach ($this->dataNonEnabledTypes() as $key => $values) {
-      $entityTypeId = $values[0];
-      $bundle = $values[1];
+  public function dataNonEnabledType() {
+    $data = [
       // By default check that the scheduler date fields are not displayed.
-      $data["$key-1"] = [1, $entityTypeId, $bundle, 'Default', FALSE, FALSE];
+      0 => [0, 'Default', FALSE, FALSE],
 
       // Explicitly disable this content type for both settings.
-      $data["$key-2"] = [2, $entityTypeId, $bundle, 'Disabling both settings', FALSE, FALSE];
+      1 => [1, 'Disabling both settings', FALSE, FALSE],
 
       // Turn on scheduled publishing only.
-      $data["$key-3"] = [3, $entityTypeId, $bundle, 'Enabling publishing only', TRUE, FALSE];
+      2 => [2, 'Enabling publishing only', TRUE, FALSE],
 
       // Turn on scheduled unpublishing only.
-      $data["$key-4"] = [4, $entityTypeId, $bundle, 'Enabling unpublishing only', FALSE, TRUE];
+      3 => [3, 'Enabling unpublishing only', FALSE, TRUE],
 
-      // For completeness turn on both scheduled publishing and unpublishing.
-      $data["$key-5"] = [5, $entityTypeId, $bundle, 'Enabling both publishing and unpublishing', TRUE, TRUE];
-    }
+      // For completeness turn on bothbscheduled publishing and unpublishing.
+      4 => [4, 'Enabling both publishing and unpublishing', TRUE, TRUE],
+    ];
+
+    // Use unset($data[n]) to remove a temporarily unwanted item, use
+    // return [$data[n]] to selectively test just one item, or have the
+    // default return $data to test everything.
     return $data;
+
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
index ef2a419d9104b069974baebeaffa1f127a533975..6440ac77d7a5523cfc866bd35ae5eeea2c75e6ae 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
@@ -11,132 +11,112 @@ class SchedulerPastDatesTest extends SchedulerBrowserTestBase {
 
   /**
    * Test the different options for past publication dates.
-   *
-   * @dataProvider dataStandardEntityTypes()
    */
-  public function testSchedulerPastDates($entityTypeId, $bundle) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    $titleField = $this->titleField($entityTypeId);
-    $entityType = $this->entityTypeObject($entityTypeId, $bundle);
-
+  public function testSchedulerPastDates() {
     // Log in.
     $this->drupalLogin($this->schedulerUser);
 
-    // Create data for use in edits.
-    $title = 'Publish in the past ' . $this->randomString(10);
+    // Create an unpublished page node.
+    /** @var NodeInterface $node */
+    $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]);
+    $created_time = $node->getCreatedTime();
+
+    // Test the default behavior: an error message should be shown when the user
+    // enters a publication date that is in the past.
     $edit = [
-      "{$titleField}[0][value]" => $title,
+      'title[0][value]' => 'Past ' . $this->randomString(10),
       'publish_on[0][value][date]' => $this->dateFormatter->format(strtotime('-1 day', $this->requestTime), 'custom', 'Y-m-d'),
       'publish_on[0][value][time]' => $this->dateFormatter->format(strtotime('-1 day', $this->requestTime), 'custom', 'H:i:s'),
     ];
-
-    // Create an unpublished entity.
-    $entity = $this->createEntity($entityTypeId, $bundle, ['status' => FALSE]);
-    // Some entities do not have a 'created' date and if that is the case we
-    // skip the specific parts of this test that relate to this.
-    if ($check_created_time = method_exists($entity, 'getCreatedTime')) {
-      $created_time = $entity->getCreatedTime();
-    }
-
-    // Test the default behavior: an error message should be shown when the user
-    // enters a publication date that is in the past.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertSession()->pageTextContains("The 'publish on' date must be in the future");
 
     // Test the 'error' behavior explicitly.
-    $entityType->setThirdPartySetting('scheduler', 'publish_past_date', 'error')->save();
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'error')->save();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertSession()->pageTextContains("The 'publish on' date must be in the future");
 
-    // Test the 'publish' behavior: the entity should be published immediately.
-    $entityType->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
-
+    // Test the 'publish' behavior: the node should be published immediately.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     // Check that no error message is shown when the publication date is in the
     // past and the "publish" behavior is chosen.
     $this->assertSession()->pageTextNotContains("The 'publish on' date must be in the future");
-    $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
-
-    // Reload the entity.
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-
-    // Check that the entity is published and has the expected timestamps.
-    $this->assertTrue($entity->isPublished(), 'The entity has been published immediately when the publication date is in the past and the "publish" behavior is chosen.');
-    $this->assertNull($entity->publish_on->value, 'The entity publish_on date has been removed after publishing when the "publish" behavior is chosen.');
-    $this->assertEquals($entity->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the entity has been updated to the publish_on time when published immediately.');
-    $check_created_time ? $this->assertEquals($entity->getCreatedTime(), $created_time, 'The created time of the entity has not been changed when the "publish" behavior is chosen.') : NULL;
-
-    // Test the 'schedule' behavior: the entity should be unpublished and become
-    // published on the next cron run. Use a new unpublished entity.
-    $entityType->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
-    $entity = $this->createEntity($entityTypeId, $bundle, ['status' => FALSE]);
-    $check_created_time ? $created_time = $entity->getCreatedTime() : NULL;
-
-    // Edit, save and check that no error is shown when the publish_on date is
-    // in the past.
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
+    $this->assertSession()->pageTextContains(sprintf('%s %s has been updated.', $this->typeName, $edit['title[0][value]']));
+
+    // Reload the node.
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+
+    // Check that the node is published and has the expected timestamps.
+    $this->assertTrue($node->isPublished(), 'The node has been published immediately when the publication date is in the past and the "publish" behavior is chosen.');
+    $this->assertNull($node->publish_on->value, 'The node publish_on date has been removed after publishing when the "publish" behavior is chosen.');
+    $this->assertEquals($node->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the node has been updated to the publish_on time when published immediately.');
+    $this->assertEquals($node->getCreatedTime(), $created_time, 'The created time of the node has not been changed when the "publish" behavior is chosen.');
+
+    // Test the 'schedule' behavior: the node should be unpublished and become
+    // published on the next cron run. Use a new unpublished node.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
+    $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]);
+    $created_time = $node->getCreatedTime();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    // Check that no error is shown when the publish_on date is in the past.
     $this->assertSession()->pageTextNotContains("The 'publish on' date must be in the future");
-    $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published', $title));
-    $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
+    $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published', $edit['title[0][value]']));
+    $this->assertSession()->pageTextContains(sprintf('%s %s has been updated.', $this->typeName, $edit['title[0][value]']));
 
-    // Reload the entity.
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
+    // Reload the node.
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
 
-    // Check that the entity is unpublished but scheduled correctly.
-    $this->assertFalse($entity->isPublished(), 'The entity has been unpublished when the publication date is in the past and the "schedule" behavior is chosen.');
-    $this->assertEquals(strtotime('-1 day', $this->requestTime), (int) $entity->publish_on->value, 'The entity has the correct publish_on date stored.');
+    // Check that the node is unpublished but scheduled correctly.
+    $this->assertFalse($node->isPublished(), 'The node has been unpublished when the publication date is in the past and the "schedule" behavior is chosen.');
+    $this->assertEquals(strtotime('-1 day', $this->requestTime), (int) $node->publish_on->value, 'The node has the correct publish_on date stored.');
 
-    // Simulate a cron run and check that the entity is published.
+    // Simulate a cron run and check that the node is published.
     scheduler_cron();
-    $storage->resetCache([$entity->id()]);
-    $entity = $storage->load($entity->id());
-    $this->assertTrue($entity->isPublished(), 'The entity with publication date in the past and the "schedule" behavior has now been published by cron.');
-    $this->assertEquals($entity->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the entity has been updated to the publish_on time when published via cron.');
-    $check_created_time ? $this->assertEquals($entity->getCreatedTime(), $created_time, 'The created time of the entity has not been changed when the "schedule" behavior is chosen.') : NULL;
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    $this->assertTrue($node->isPublished(), 'The node with publication date in the past and the "schedule" behavior has now been published by cron.');
+    $this->assertEquals($node->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the node has been updated to the publish_on time when published via cron.');
+    $this->assertEquals($node->getCreatedTime(), $created_time, 'The created time of the node has not been changed when the "schedule" behavior is chosen.');
 
     // Test the option to alter the creation time if the publishing time is
-    // earlier than the entity created time.
-    if ($check_created_time) {
-      $entityType->setThirdPartySetting('scheduler', 'publish_past_date_created', TRUE)->save();
-      $past_date_options = [
-        'publish' => 'publish',
-        'schedule' => 'schedule',
-      ];
-      foreach ($past_date_options as $key => $option) {
-        $entityType->setThirdPartySetting('scheduler', 'publish_past_date', $key)->save();
-
-        // Create a new unpublished entity, edit and save.
-        $entity = $this->createEntity($entityTypeId, $bundle, ['status' => FALSE]);
-        $this->drupalGet($entity->toUrl('edit-form'));
-        $this->submitForm($edit, 'Save');
-
-        if ($option == 'schedule') {
-          scheduler_cron();
-        }
-
-        // Reload the entity.
-        $storage->resetCache([$entity->id()]);
-        $entity = $storage->load($entity->id());
-
-        // Check that the created time is altered to match publishing time.
-        $this->assertEquals($entity->getCreatedTime(), strtotime('-1 day', $this->requestTime), sprintf('The created time of the entity has not been changed when the %s option is chosen.', $option));
+    // earlier than the node created time.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date_created', TRUE)->save();
+
+    $past_date_options = [
+      'publish' => 'publish',
+      'schedule' => 'schedule',
+    ];
+
+    foreach ($past_date_options as $key => $option) {
+      $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', $key)->save();
+
+      // Create a new node, edit and save.
+      $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]);
+      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+
+      if ($option == 'schedule') {
+        scheduler_cron();
       }
+
+      // Reload the node.
+      $this->nodeStorage->resetCache([$node->id()]);
+      $node = $this->nodeStorage->load($node->id());
+
+      // Check that the created time has been altered to match publishing time.
+      $this->assertEquals($node->getCreatedTime(), strtotime('-1 day', $this->requestTime), sprintf('The created time of the node has not been changed when the %s option is chosen.', $option));
+
     }
 
     // Check that an Unpublish date in the past fails validation.
     $edit = [
-      "{$titleField}[0][value]" => 'Unpublish in the past ' . $this->randomString(10),
+      'title[0][value]' => 'Unpublish in the past ' . $this->randomString(10),
       'unpublish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime - 3600, 'custom', 'Y-m-d'),
       'unpublish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime - 3600, 'custom', 'H:i:s'),
     ];
-    $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
-    $this->submitForm($edit, 'Save');
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $this->assertSession()->pageTextContains("The 'unpublish on' date must be in the future");
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
index b3d4bf75491e11a1f2430670c4b2408507dd1a08..d2de79f8dcfff5a943052aac0aad3533468735c1 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
@@ -3,114 +3,57 @@
 namespace Drupal\Tests\scheduler\Functional;
 
 /**
- * Tests some permissions of the Scheduler module.
- *
- * These tests check the permissions when adding and editing a scheduler-enabled
- * node or media entity type. The permission to access the scheduled content
- * overview and user tab views is covered in SchedulerViewsAccessTest.
+ * Tests the permissions of the Scheduler module.
  *
  * @group scheduler
  */
 class SchedulerPermissionsTest extends SchedulerBrowserTestBase {
 
   /**
-   * {@inheritdoc}
+   * Tests that users without permission do not see the scheduler date fields.
    */
-  protected function setUp(): void {
-    parent::setUp();
-
-    // Define a set of permissions which all users get. Then in addition, each
-    // user gets the specific permission to schedule their own entity type.
-    // The permission 'administer nodes' is needed when setting the node status
-    // field on edit. There is no corresponding separate permission for media or
-    // product entity types.
-    $permissions = [
+  public function testUserPermissionsAdd() {
+    // Create a user who can add the content type but who does not have the
+    // permission to use the scheduler functionality.
+    $this->webUser = $this->drupalCreateUser([
+      'access content',
+      'administer nodes',
       'create ' . $this->type . ' content',
       'edit own ' . $this->type . ' content',
-      'administer nodes',
-      'create ' . $this->mediaTypeName . ' media',
-      'edit own ' . $this->mediaTypeName . ' media',
-      'view own unpublished media',
-      'create ' . $this->productTypeName . ' commerce_product',
-      'update own ' . $this->productTypeName . ' commerce_product',
-      'view own unpublished commerce_product',
-      // 'administer commerce_store' is needed to see and use any store, i.e
-      // cannot add a product without this. Is it a bug?
-      'administer commerce_store',
-      'create terms in ' . $this->vocabularyId,
-      'edit terms in ' . $this->vocabularyId,
-    ];
-
-    // Create a user who can add and edit the standard scheduler-enabled
-    // entities, but only schedule nodes.
-    $this->nodeUser = $this->drupalCreateUser(array_merge($permissions, ['schedule publishing of nodes']));
-    $this->nodeUser->set('name', 'Noddy the Node Editor')->save();
-
-    // Create a user who can add and edit the standard scheduler-enabled
-    // entities, but only schedule media items.
-    $this->mediaUser = $this->drupalCreateUser(array_merge($permissions, ['schedule publishing of media']));
-    $this->mediaUser->set('name', 'Medina the Media Editor')->save();
-
-    // Create a user who can add and edit the standard scheduler-enabled
-    // entities, but only schedule products.
-    $this->commerce_productUser = $this->drupalCreateUser(array_merge($permissions, ['schedule publishing of commerce_product']));
-    $this->commerce_productUser->set('name', 'Proctor the Product Editor')->save();
-
-    // Create a user who can add and edit the standard scheduler-enabled
-    // entities, but only schedule taxonomy terms.
-    $this->taxonomy_termUser = $this->drupalCreateUser(array_merge($permissions, ['schedule publishing of taxonomy_term']));
-    $this->taxonomy_termUser->set('name', 'Taximayne the Taxonomy Editor')->save();
-  }
-
-  /**
-   * Tests that users without permission do not see the scheduler date fields.
-   *
-   * @dataProvider dataPermissionsTest()
-   */
-  public function testUserPermissionsAdd($entityTypeId, $bundle, $user) {
-    $titleField = $this->titleField($entityTypeId);
+      'delete own ' . $this->type . ' content',
+      'view own unpublished content',
+    ]);
+    $this->drupalLogin($this->webUser);
 
-    // Log in with the required user, as specified by the parameter.
-    $this->drupalLogin($this->$user);
+    // Check that neither of the fields are displayed when creating a node.
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertSession()->fieldNotExists('publish_on[0][value][date]');
+    $this->assertSession()->fieldNotExists('unpublish_on[0][value][date]');
 
     // Initially run tests when publishing and unpublishing are not required.
-    $this->entityTypeObject($entityTypeId)->setThirdPartySetting('scheduler', 'publish_required', FALSE)
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE)
       ->save();
 
-    // Check that the fields are displayed as expected when creating an entity.
-    // If the user variable matches the entity type id then that user has
-    // scheduling permission on this type, so the fields should be shown.
-    // Otherwise the fields should not be shown.
-    $add_url = $this->entityAddUrl($entityTypeId, $bundle);
-    $this->drupalGet($add_url);
-    if (strpos($user, $entityTypeId) !== FALSE) {
-      $this->assertSession()->fieldExists('publish_on[0][value][date]');
-      $this->assertSession()->fieldExists('unpublish_on[0][value][date]');
-    }
-    else {
-      $this->assertSession()->fieldNotExists('publish_on[0][value][date]');
-      $this->assertSession()->fieldNotExists('unpublish_on[0][value][date]');
-    }
-
-    // Check that the new entity can be saved and published.
-    $title = 'Published - ' . $this->randomString(15);
-    $edit = ["{$titleField}[0][value]" => $title, 'status[value]' => TRUE];
-    $this->submitForm($edit, 'Save');
-    $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
-    $this->assertNotEmpty($entity = $this->getEntityByTitle($entityTypeId, $title), sprintf('The new %s with title "%s" was created sucessfully.', $entityTypeId, $title));
-    $this->assertTrue($entity->isPublished(), 'The new entity is published');
-
-    // Check that a new entity can be saved as unpublished.
-    $title = 'Unpublished - ' . $this->randomString(15);
-    $edit = ["{$titleField}[0][value]" => $title, 'status[value]' => FALSE];
-    $this->drupalGet($add_url);
-    $this->submitForm($edit, 'Save');
-    $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
-    $this->assertNotEmpty($entity = $this->getEntityByTitle($entityTypeId, $title), sprintf('The new %s with title "%s" was created sucessfully.', $entityTypeId, $title));
-    $this->assertFalse($entity->isPublished(), 'The new entity is unpublished');
+    // Check that a new node can be saved and published.
+    $title = $this->randomString(15);
+    $edit = ['title[0][value]' => $title, 'status[value]' => TRUE];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $this->assertSession()->pageTextContains(sprintf('%s %s has been created.', $this->typeName, $title));
+    $this->assertTrue($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is published');
+
+    // Check that a new node can be saved as unpublished.
+    $title = $this->randomString(15);
+    $edit = ['title[0][value]' => $title, 'status[value]' => FALSE];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $this->assertSession()->pageTextContains(sprintf('%s %s has been created.', $this->typeName, $title));
+    $this->assertFalse($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is unpublished');
 
     // Set publishing and unpublishing to required, to make it a stronger test.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', TRUE)
+      ->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)
+      ->save();
+
     // @todo Add tests when scheduled publishing and unpublishing are required.
     // Cannot be done until we make a decision on what 'required'  means.
     // @see https://www.drupal.org/node/2707411
@@ -120,90 +63,64 @@ public function testUserPermissionsAdd($entityTypeId, $bundle, $user) {
 
   /**
    * Tests that users without permission can edit existing scheduled content.
-   *
-   * @dataProvider dataPermissionsTest()
    */
-  public function testUserPermissionsEdit($entityTypeId, $bundle, $user) {
-    $storage = $this->entityStorageObject($entityTypeId);
-    $titleField = $this->titleField($entityTypeId);
-
-    // Log in with the required user, as specified by the parameter.
-    $this->drupalLogin($this->$user);
+  public function testUserPermissionsEdit() {
+    // Create a user who can add the content type but who does not have the
+    // permission to use the scheduler functionality.
+    $this->webUser = $this->drupalCreateUser([
+      'access content',
+      'administer nodes',
+      'create ' . $this->type . ' content',
+      'edit own ' . $this->type . ' content',
+      'delete own ' . $this->type . ' content',
+      'view own unpublished content',
+    ]);
+    $this->drupalLogin($this->webUser);
 
     $publish_time = strtotime('+ 6 hours', $this->requestTime);
     $unpublish_time = strtotime('+ 10 hours', $this->requestTime);
 
-    // Create an unpublished entity with a publish_on date.
-    $unpublished_entity = $this->createEntity($entityTypeId, $bundle, [
+    // Create nodes with publish_on and unpublish_on dates.
+    $unpublished_node = $this->drupalCreateNode([
+      'type' => $this->type,
       'status' => FALSE,
       'publish_on' => $publish_time,
     ]);
+    $published_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'unpublish_on' => $unpublish_time,
+    ]);
 
     // Verify that the publish_on date is stored as expected before editing.
-    $this->assertEquals($publish_time, $unpublished_entity->publish_on->value, 'The publish_on value is stored correctly before edit.');
-
-    // Edit the unpublished entity and check that the fields are displayed as
-    // expected, depending on the user.
-    $this->drupalGet($unpublished_entity->toUrl('edit-form'));
-    if (strpos($user, $entityTypeId) !== FALSE) {
-      $this->assertSession()->fieldExists('publish_on[0][value][date]');
-      $this->assertSession()->fieldExists('unpublish_on[0][value][date]');
-    }
-    else {
-      $this->assertSession()->fieldNotExists('publish_on[0][value][date]');
-      $this->assertSession()->fieldNotExists('unpublish_on[0][value][date]');
-    }
-
-    // Save the entity and check the title is updated as expected.
+    $this->assertEquals($publish_time, $unpublished_node->publish_on->value, 'The publish_on value is stored correctly before edit.');
+
+    // Edit the unpublished node and save.
     $title = 'For Publishing ' . $this->randomString(10);
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
-    $unpublished_entity = $storage->load($unpublished_entity->id());
-    $this->assertEquals($title, $unpublished_entity->label(), 'The unpublished entity title has been updated correctly after edit.');
+    $this->drupalPostForm('node/' . $unpublished_node->id() . '/edit', ['title[0][value]' => $title], 'Save');
 
-    // Test that the publish_on date is still stored and is unchanged.
-    $this->assertEquals($publish_time, $unpublished_entity->publish_on->value, 'The publish_on value is still stored correctly after edit.');
+    // Check the updated title, to verify that edit and save was sucessful.
+    $unpublished_node = $this->nodeStorage->load($unpublished_node->id());
+    $this->assertEquals($title, $unpublished_node->title->value, 'The unpublished node title has been updated correctly after edit.');
 
-    // Repeat for unpublishing. Create an entity scheduled for unpublishing.
-    $published_entity = $this->createEntity($entityTypeId, $bundle, [
-      'status' => TRUE,
-      'unpublish_on' => $unpublish_time,
-    ]);
+    // Test that the publish_on date is still stored and is unchanged.
+    $this->assertEquals($publish_time, $unpublished_node->publish_on->value, 'The node publish_on value is still stored correctly after edit.');
 
+    // Do the same for unpublishing.
     // Verify that the unpublish_on date is stored as expected before editing.
-    $this->assertEquals($unpublish_time, $published_entity->unpublish_on->value, 'The unpublish_on value is stored correctly before edit.');
+    $this->assertEquals($unpublish_time, $published_node->unpublish_on->value, 'The unpublish_on value is stored correctly before edit.');
 
-    // Edit the published entity and save.
+    // Edit the published node and save.
     $title = 'For Unpublishing ' . $this->randomString(10);
-    $this->drupalGet($published_entity->toUrl('edit-form'));
-    $this->submitForm(["{$titleField}[0][value]" => $title], 'Save');
+    $this->drupalPostForm('node/' . $published_node->id() . '/edit', ['title[0][value]' => $title], 'Save');
 
     // Check the updated title, to verify that edit and save was sucessful.
-    $published_entity = $storage->load($published_entity->id());
-    $this->assertEquals($title, $published_entity->label(), 'The published entity title has been updated correctly after edit.');
+    $published_node = $this->nodeStorage->load($published_node->id());
+    $this->assertEquals($title, $published_node->title->value, 'The published node title has been updated correctly after edit.');
 
     // Test that the unpublish_on date is still stored and is unchanged.
-    $this->assertEquals($unpublish_time, $published_entity->unpublish_on->value, 'The unpublish_on value is still stored correctly after edit.');
-  }
+    $this->assertEquals($unpublish_time, $published_node->unpublish_on->value, 'The node unpublish_on value is still stored correctly after edit.');
 
-  /**
-   * Provides data for testUserPermissionsAdd() and testUserPermissionsEdit()
-   *
-   * The data in dataStandardEntityTypes() is expanded to test each entity type
-   * with users who only have scheduler permission on one entity type and no
-   * permission for the other entity types.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id, user name].
-   */
-  public function dataPermissionsTest() {
-    $data = [];
-    foreach ($this->dataStandardEntityTypes() as $key => $values) {
-      $data["$key-1"] = array_merge($values, ['nodeUser']);
-      $data["$key-2"] = array_merge($values, ['mediaUser']);
-      $data["$key-3"] = array_merge($values, ['commerce_productUser']);
-      $data["$key-4"] = array_merge($values, ['taxonomy_termUser']);
-    }
-    return $data;
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
index ad42b469dca58c564881c99c94d45414c183c13a..f7801360bf08134460d4f7c43b398ee4bc4685e0 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
@@ -73,8 +73,7 @@ public function testRequiredScheduling($id, $publish_required, $unpublish_requir
       'unpublish_on[0][value][time]' => '',
     ];
     // Add or edit the node.
-    $this->drupalGet($path);
-    $this->submitForm($values, 'Save');
+    $this->drupalPostForm($path, $values, 'Save');
 
     // Check for the expected result.
     if ($publish_expected) {
@@ -114,7 +113,7 @@ public function dataRequiredScheduling() {
       // http://drupal.org/node/1198788#comment-7816119
 
       // Check the default case when neither date should be required.
-      '#node-0' => [
+      [
         'id' => 0,
         'publish_required' => FALSE,
         'unpublish_required' => FALSE,
@@ -129,7 +128,7 @@ public function dataRequiredScheduling() {
       // A. Test scenarios that require scheduled publishing.
       // When creating a new unpublished node it is required to enter a
       // publication date.
-      '#node-1' => [
+      [
         'id' => 1,
         'publish_required' => TRUE,
         'unpublish_required' => FALSE,
@@ -143,7 +142,7 @@ public function dataRequiredScheduling() {
 
       // When creating a new published node it is required to enter a
       // publication date. The node will be unpublished on form submit.
-      '#node-2' => [
+      [
         'id' => 2,
         'publish_required' => TRUE,
         'unpublish_required' => FALSE,
@@ -157,7 +156,7 @@ public function dataRequiredScheduling() {
 
       // When editing a published node it is not needed to enter a publication
       // date since the node is already published.
-      '#node-3' => [
+      [
         'id' => 3,
         'publish_required' => TRUE,
         'unpublish_required' => FALSE,
@@ -171,7 +170,7 @@ public function dataRequiredScheduling() {
 
       // When editing an unpublished node that is scheduled for publication it
       // is required to enter a publication date.
-      '#node-4' => [
+      [
         'id' => 4,
         'publish_required' => TRUE,
         'unpublish_required' => FALSE,
@@ -186,7 +185,7 @@ public function dataRequiredScheduling() {
       // When editing an unpublished node that is not scheduled for publication
       // it is not required to enter a publication date since this means that
       // the node has already gone through a publication > unpublication cycle.
-      '#node-5' => [
+      [
         'id' => 5,
         'publish_required' => TRUE,
         'unpublish_required' => FALSE,
@@ -203,7 +202,7 @@ public function dataRequiredScheduling() {
       // When creating a new unpublished node it is required to enter an
       // unpublication date since it is to be expected that the node will be
       // published at some point and should subsequently be unpublished.
-      '#node-6' => [
+      [
         'id' => 6,
         'publish_required' => FALSE,
         'unpublish_required' => TRUE,
@@ -217,7 +216,7 @@ public function dataRequiredScheduling() {
 
       // When creating a new published node it is required to enter an
       // unpublication date.
-      '#node-7' => [
+      [
         'id' => 7,
         'publish_required' => FALSE,
         'unpublish_required' => TRUE,
@@ -231,7 +230,7 @@ public function dataRequiredScheduling() {
 
       // When editing a published node it is required to enter an unpublication
       // date.
-      '#node-8' => [
+      [
         'id' => 8,
         'publish_required' => FALSE,
         'unpublish_required' => TRUE,
@@ -245,7 +244,7 @@ public function dataRequiredScheduling() {
 
       // When editing an unpublished node that is scheduled for publication it
       // it is required to enter an unpublication date.
-      '#node-9' => [
+      [
         'id' => 9,
         'publish_required' => FALSE,
         'unpublish_required' => TRUE,
@@ -260,7 +259,7 @@ public function dataRequiredScheduling() {
       // When editing an unpublished node that is not scheduled for publication
       // it is not required to enter an unpublication date since this means that
       // the node has already gone through a publication - unpublication cycle.
-      '#node-10' => [
+      [
         'id' => 10,
         'publish_required' => FALSE,
         'unpublish_required' => TRUE,
@@ -276,7 +275,7 @@ public function dataRequiredScheduling() {
 
       // This section is an amalgamation of the values in the sections A and B
       // to check that the settings do not interfere with each other.
-      '#node-11' => [
+      [
         'id' => 11,
         'publish_required' => TRUE,
         'unpublish_required' => TRUE,
@@ -288,7 +287,7 @@ public function dataRequiredScheduling() {
         'message' => 'When both scheduled publishing and unpublishing are required and a new unpublished node is created, entering a date in both the publish and unpublish on fields is required.',
       ],
 
-      '#node-12' => [
+      [
         'id' => 12,
         'publish_required' => TRUE,
         'unpublish_required' => TRUE,
@@ -300,7 +299,7 @@ public function dataRequiredScheduling() {
         'message' => 'When both scheduled publishing and unpublishing are required and a new published node is created, entering a date in both the publish and unpublish on fields is required.',
       ],
 
-      '#node-13' => [
+      [
         'id' => 13,
         'publish_required' => TRUE,
         'unpublish_required' => TRUE,
@@ -312,7 +311,7 @@ public function dataRequiredScheduling() {
         'message' => 'When both scheduled publishing and unpublishing are required and an existing published, unscheduled node is edited, entering a date in the unpublish on field is required, but a publish date is not required.',
       ],
 
-      '#node-14' => [
+      [
         'id' => 14,
         'publish_required' => TRUE,
         'unpublish_required' => TRUE,
@@ -324,7 +323,7 @@ public function dataRequiredScheduling() {
         'message' => 'When both scheduled publishing and unpublishing are required and an existing unpublished, scheduled node is edited, entering a date in both the publish and unpublish on fields is required.',
       ],
 
-      '#node-15' => [
+      [
         'id' => 15,
         'publish_required' => TRUE,
         'unpublish_required' => TRUE,
@@ -338,6 +337,9 @@ public function dataRequiredScheduling() {
 
     ];
 
+    // Use unset($data[n]) to remove a temporarily unwanted item, use
+    // return [$data[n]] to selectively test just one item, or have the default
+    // return $data to test everything.
     return $data;
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
index 29f16b54eadb7d4fed90e399e533ca6e1435b10e..56e0a82cc85e63ea92924d14b21cf8526d255e57 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\node\NodeInterface;
 
 /**
  * Tests revision options when Scheduler publishes or unpublishes content.
@@ -12,155 +12,148 @@
 class SchedulerRevisioningTest extends SchedulerBrowserTestBase {
 
   /**
-   * Simulates the scheduled (un)publication of an entity.
+   * Simulates the scheduled (un)publication of a node.
    *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to schedule.
+   * @param \Drupal\node\NodeInterface $node
+   *   The node to schedule.
    * @param string $action
-   *   The action to perform: either 'publish' or 'unpublish'.
+   *   The action to perform: either 'publish' or 'unpublish'. Defaults to
+   *   'publish'.
    *
-   * @return \Drupal\Core\Entity\EntityInterface
-   *   The updated entity, after scheduled (un)publication via a cron run.
+   * @return \Drupal\node\NodeInterface
+   *   The updated node, after scheduled (un)publication via a cron run.
    */
-  protected function scheduleAndRunCron(EntityInterface $entity, string $action) {
+  protected function schedule(NodeInterface $node, $action = 'publish') {
     // Simulate scheduling by setting the (un)publication date in the past and
     // running cron.
-    $entity->{$action . '_on'} = strtotime('-5 hour', $this->requestTime);
-    $entity->save();
+    $node->{$action . '_on'} = strtotime('-5 hour', $this->requestTime);
+    $node->save();
     scheduler_cron();
-    $storage = $this->entityStorageObject($entity->getEntityTypeId());
-    $storage->resetCache([$entity->id()]);
-    return $storage->load($entity->id());
+    $this->nodeStorage->resetCache([$node->id()]);
+    return $this->nodeStorage->load($node->id());
   }
 
   /**
-   * Check if the number of revisions for an entity matches a given value.
+   * Check if the number of revisions for a node matches a given value.
    *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to check.
-   * @param int $expected
-   *   The expected number of revisions.
+   * @param int $nid
+   *   The node id of the node to check.
+   * @param string $value
+   *   The value with which the number of revisions will be compared.
    * @param string $message
    *   The message to display along with the assertion.
    */
-  protected function assertRevisionCount(EntityInterface $entity, int $expected, string $message = '') {
-    if (!$entity->getEntityType()->isRevisionable()) {
-      return;
-    }
+  protected function assertRevisionCount($nid, $value, $message = '') {
     // Because we are not deleting any revisions we can take a short cut and use
     // getLatestRevisionId() which will effectively be the number of revisions.
-    $storage = $this->entityStorageObject($entity->getEntityTypeId());
-    $count = $storage->getLatestRevisionId($entity->id());
-    $this->assertEquals($expected, (int) $count, $message);
+    $count = $this->nodeStorage->getLatestRevisionId($nid);
+    $this->assertEquals($value, (int) $count, $message);
   }
 
   /**
-   * Tests the creation of new revisions on scheduling.
+   * Check if the latest revision log message of a node matches a given string.
    *
-   * This test is still useful for Commerce Products which are not revisionable
-   * because it shows that this entity type can be processed correctly even if
-   * the scheduler revision option is incorrectly set on.
+   * @param int $nid
+   *   The node id of the node to check.
+   * @param string $value
+   *   The value with which the log message will be compared.
+   * @param string $message
+   *   The message to display along with the assertion.
    *
-   * @dataProvider dataStandardEntityTypes()
+   * @return bool
+   *   TRUE if the assertion succeeded, FALSE otherwise.
    */
-  public function testNewRevision($entityTypeId, $bundle) {
-    $entityType = $this->entityTypeObject($entityTypeId, $bundle);
+  protected function assertRevisionLogMessage($nid, $value, $message = '') {
+    // Retrieve the latest revision log message for this node.
+    $log_message = $this->database->select('node_revision', 'r')
+      ->fields('r', ['revision_log'])
+      ->condition('nid', $nid)
+      ->orderBy('vid', 'DESC')
+      ->range(0, 1)
+      ->execute()
+      ->fetchField();
+
+    return $this->assertEquals($value, $log_message, $message);
+  }
 
-    // Create a scheduled entity that is not automatically revisioned.
-    $entity = $this->createEntity($entityTypeId, $bundle, ['revision' => 0]);
-    $this->assertRevisionCount($entity, 1, 'The initial revision count is 1 when the entity is created.');
+  /**
+   * Tests the creation of new revisions on scheduling.
+   */
+  public function testRevisioning() {
+    // Create a scheduled node that is not automatically revisioned.
+    $created = strtotime('-2 day', $this->requestTime);
+    $settings = [
+      'type' => $this->type,
+      'revision' => 0,
+      'created' => $created,
+    ];
+    $node = $this->drupalCreateNode($settings);
 
-    // Ensure entities with past dates are scheduled not published immediately.
-    $entityType->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
+    // Ensure nodes with past dates will be scheduled not published immediately.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
 
     // First test scheduled publication with revisioning disabled by default.
-    $entity = $this->scheduleAndRunCron($entity, 'publish');
-    $this->assertRevisionCount($entity, 1, 'No new revision is created by default when entity is published. Revision count remains at 1.');
+    $node = $this->schedule($node);
+    $this->assertRevisionCount($node->id(), 1, 'No new revision is created by default when a node is published.');
 
     // Test scheduled unpublication.
-    $entity = $this->scheduleAndRunCron($entity, 'unpublish');
-    $this->assertRevisionCount($entity, 1, 'No new revision is created by default when entity is unpublished. Revision count remains at 1.');
+    $node = $this->schedule($node, 'unpublish');
+    $this->assertRevisionCount($node->id(), 1, 'No new revision is created by default when a node is unpublished.');
 
     // Enable revisioning.
-    $entityType->setThirdPartySetting('scheduler', 'publish_revision', TRUE)
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_revision', TRUE)
       ->setThirdPartySetting('scheduler', 'unpublish_revision', TRUE)
       ->save();
 
     // Test scheduled publication with revisioning enabled.
-    $entity = $this->scheduleAndRunCron($entity, 'publish');
-    $this->assertTrue($entity->isPublished(), 'Entity is published after cron.');
-
-    if ($entity->getEntityType()->isRevisionable()) {
-      $this->assertRevisionCount($entity, 2, 'A new revision was created when the entity was published with revisioning enabled.');
-      $expected_message = sprintf('Published by Scheduler. The scheduled publishing date was %s.',
-        $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short'));
-      $this->assertEquals($entity->getRevisionLogMessage(), $expected_message, 'The correct message was found in the entity revision log after scheduled publishing.');
-    }
+    $node = $this->schedule($node);
+    $this->assertRevisionCount($node->id(), 2, 'A new revision was created when revisioning is enabled.');
+    $expected_message = sprintf('Published by Scheduler. The scheduled publishing date was %s.',
+    $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short'));
+    $this->assertRevisionLogMessage($node->id(), $expected_message, 'The correct message was found in the node revision log after scheduled publishing.');
 
     // Test scheduled unpublication with revisioning enabled.
-    $entity = $this->scheduleAndRunCron($entity, 'unpublish');
-    $this->assertFalse($entity->isPublished(), 'Entity is unpublished after cron.');
-
-    if ($entity->getEntityType()->isRevisionable()) {
-      $this->assertRevisionCount($entity, 3, 'A new revision was created when the entity was unpublished with revisioning enabled.');
-      $expected_message = sprintf('Unpublished by Scheduler. The scheduled unpublishing date was %s.',
-        $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short'));
-      $this->assertEquals($entity->getRevisionLogMessage(), $expected_message, 'The correct message was found in the entity revision log after scheduled unpublishing.');
-    }
+    $node = $this->schedule($node, 'unpublish');
+    $this->assertRevisionCount($node->id(), 3, 'A new revision was created when a node was unpublished with revisioning enabled.');
+    $expected_message = sprintf('Unpublished by Scheduler. The scheduled unpublishing date was %s.',
+    $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short'));
+    $this->assertRevisionLogMessage($node->id(), $expected_message, 'The correct message was found in the node revision log after scheduled unpublishing.');
   }
 
   /**
-   * Tests the 'touch' option to alter the created date during publishing.
-   *
-   * @dataProvider dataAlterCreationDate()
+   * Tests the 'touch' option to alter the node created date during publishing.
    */
-  public function testAlterCreationDate($entityTypeId, $bundle) {
-    // Ensure entities with past dates are scheduled not published immediately.
-    $entityType = $this->entityTypeObject($entityTypeId, $bundle);
-    $entityType->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
+  public function testAlterCreationDate() {
+    // Ensure nodes with past dates will be scheduled not published immediately.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
 
-    // Create an entity with a 'created' date two days in the past.
+    // Create a node with a 'created' date two days in the past.
     $created = strtotime('-2 day', $this->requestTime);
     $settings = [
+      'type' => $this->type,
       'created' => $created,
       'status' => FALSE,
     ];
-    $entity = $this->createEntity($entityTypeId, $bundle, $settings);
+    $node = $this->drupalCreateNode($settings);
+    // Show that the node is not published.
+    $this->assertFalse($node->isPublished(), 'The node is not published.');
 
-    // Show that the entity is not published.
-    $this->assertFalse($entity->isPublished(), 'The entity is not published.');
-
-    // Schedule the entity for publishing and run cron.
-    $entity = $this->scheduleAndRunCron($entity, 'publish');
-    // Get the created date from the entity and check that it has not changed.
-    $created_after_cron = $entity->created->value;
-    $this->assertTrue($entity->isPublished(), 'The entity has been published.');
-    $this->assertEquals($created, $created_after_cron, 'The entity creation date is not changed by default.');
+    // Schedule the node for publishing and run cron.
+    $node = $this->schedule($node, 'publish');
+    // Get the created date from the node and check that it has not changed.
+    $created_after_cron = $node->created->value;
+    $this->assertTrue($node->isPublished(), 'The node has been published.');
+    $this->assertEquals($created, $created_after_cron, 'The node creation date is not changed by default.');
 
     // Set option to change the created date to match the publish_on date.
-    $entityType->setThirdPartySetting('scheduler', 'publish_touch', TRUE)->save();
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_touch', TRUE)->save();
 
-    // Schedule the entity again and run cron.
-    $entity = $this->scheduleAndRunCron($entity, 'publish');
+    // Schedule the node again and run cron.
+    $node = $this->schedule($node, 'publish');
     // Check that the created date has changed to match the publish_on date.
-    $created_after_cron = $entity->created->value;
-    $this->assertEquals(strtotime('-5 hour', $this->requestTime), $created_after_cron, "With 'touch' option set, the entity creation date is changed to match the publishing date.");
-
-  }
+    $created_after_cron = $node->created->value;
+    $this->assertEquals(strtotime('-5 hour', $this->requestTime), $created_after_cron, "With 'touch' option set, the node creation date is changed to match the publishing date.");
 
-  /**
-   * Provides test data for testAlterCreationDate.
-   *
-   * Taxonomy terms do not have a 'created' date and the therefore the 'touch'
-   * option is not available, and the test should be skipped.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id].
-   */
-  public function dataAlterCreationDate() {
-    $data = $this->dataStandardEntityTypes();
-    unset($data['#taxonomy_term']);
-    return $data;
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..94b3593253bddb01e5d274fa10dd1e5cef75cbc7
--- /dev/null
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
@@ -0,0 +1,443 @@
+<?php
+
+namespace Drupal\Tests\scheduler\Functional;
+
+use Drupal\Core\Logger\RfcLogLevel;
+use Drupal\rules\Context\ContextConfig;
+
+/**
+ * Tests the six actions that Scheduler provides for use in Rules module.
+ *
+ * @group scheduler
+ */
+class SchedulerRulesActionsTest extends SchedulerBrowserTestBase {
+
+  /**
+   * Additional modules required.
+   *
+   * @var array
+   */
+  protected static $modules = ['scheduler_rules_integration'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->rulesStorage = $this->container->get('entity_type.manager')->getStorage('rules_reaction_rule');
+    $this->expressionManager = $this->container->get('plugin.manager.rules_expression');
+    $this->drupalLogin($this->adminUser);
+
+    // Create node A which is published and enabled for Scheduling.
+    $this->node_a = $this->drupalCreateNode([
+      'title' => 'Initial Test Node',
+      'type' => $this->type,
+      'uid' => $this->adminUser->id(),
+      'status' => TRUE,
+    ]);
+
+    // Create node B which is published but not enabled for Scheduling.
+    $this->node_b = $this->drupalCreateNode([
+      'title' => 'Something Else',
+      'type' => $this->nonSchedulerNodeType->id(),
+      'uid' => $this->adminUser->id(),
+      'status' => TRUE,
+    ]);
+  }
+
+  /**
+   * Tests the actions which set and remove the 'Publish On' date.
+   */
+  public function testPublishOnActions() {
+
+    $publish_on = $this->requestTime + 1800;
+    $publish_on_formatted = $this->dateFormatter->format($publish_on, 'long');
+
+    // Create rule 1 to set the publishing date.
+    $rule1 = $this->expressionManager->createRule();
+    $rule1->addCondition('rules_data_comparison',
+        ContextConfig::create()
+          ->map('data', 'node.title.value')
+          ->setValue('operation', 'contains')
+          ->setValue('value', 'Trigger Action Rule 1')
+    );
+    $message1 = 'RULES message 1. Action to set Publish-on date.';
+    $rule1->addAction('scheduler_set_publishing_date_action',
+      ContextConfig::create()
+        ->map('node', 'node')
+        ->setValue('date', $publish_on)
+      )
+      ->addAction('rules_system_message',
+        ContextConfig::create()
+          ->setValue('message', $message1)
+          ->setValue('type', 'status')
+    );
+    // The event needs to be rules_entity_presave:node 'before saving' because
+    // rules_entity_update:node 'after save' is too late to set the date.
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule1',
+      'events' => [['event_name' => 'rules_entity_presave:node']],
+      'expression' => $rule1->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    // Create rule 2 to remove the publishing date and publish the node.
+    $rule2 = $this->expressionManager->createRule();
+    $rule2->addCondition('rules_data_comparison',
+        ContextConfig::create()
+          ->map('data', 'node.title.value')
+          ->setValue('operation', 'contains')
+          ->setValue('value', 'Trigger Action Rule 2')
+    );
+    $message2 = 'RULES message 2. Action to remove Publish-on date and publish the node immediately.';
+    $rule2->addAction('scheduler_remove_publishing_date_action',
+      ContextConfig::create()
+        ->map('node', 'node')
+      )
+      ->addAction('scheduler_publish_now_action',
+        ContextConfig::create()
+          ->map('node', 'node')
+      )
+      ->addAction('rules_system_message',
+        ContextConfig::create()
+          ->setValue('message', $message2)
+          ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule2',
+      'events' => [['event_name' => 'rules_entity_presave:node']],
+      'expression' => $rule2->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    $assert = $this->assertSession();
+
+    // First, create a new scheduler-enabled node, triggering rule 1.
+    $edit = [
+      'title[0][value]' => 'New node - Trigger Action Rule 1',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle('New node - Trigger Action Rule 1');
+    $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be published %s', 'New node - Trigger Action Rule 1', $publish_on_formatted));
+
+    // Check that rule 1 is triggered and rule 2 is not. Check that a publishing
+    // date has been set and the status is now unpublished.
+    $assert->pageTextContains($message1);
+    $assert->pageTextNotContains($message2);
+    $this->assertEquals($node->publish_on->value, $publish_on, 'Node is scheduled for publishing at the correct time.');
+    $this->assertEmpty($node->unpublish_on->value, 'Node is not scheduled for unpublishing.');
+    $this->assertFalse($node->isPublished(), 'Node is now unpublished for title: "' . $node->title->value . '".');
+
+    // Second, edit a pre-existing Scheduler-enabled node, without triggering
+    // either of the rules.
+    $node = $this->node_a;
+    $edit = [
+      'title[0][value]' => 'Edit node - but no rules will be triggered',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check that neither of the rules are triggered, no publish and unpublish
+    // dates are set and the status is still published.
+    $assert->pageTextNotContains($message1);
+    $assert->pageTextNotContains($message2);
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    $this->assertEmpty($node->unpublish_on->value, 'Node is not scheduled for unpublishing.');
+    $this->assertTrue($node->isPublished(), 'Node remains published for title: "' . $node->title->value . '".');
+
+    // Edit the node, triggering rule 1.
+    $edit = [
+      'title[0][value]' => 'Edit node - Trigger Action Rule 1',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check that rule 1 is triggered and rule 2 is not. Check that a publishing
+    // date has been set and the status is now unpublished.
+    $assert->pageTextContains($message1);
+    $assert->pageTextNotContains($message2);
+    $this->assertNotEmpty($node->publish_on->value, 'Node is scheduled for publishing.');
+    $this->assertEmpty($node->unpublish_on->value, 'Node is not scheduled for unpublishing.');
+    $this->assertFalse($node->isPublished(), 'Node is now unpublished for title: "' . $node->title->value . '".');
+
+    // Edit the node, triggering rule 2.
+    $edit = [
+      'title[0][value]' => 'Edit node - Trigger Action Rule 2',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check that rule 2 is triggered and rule 1 is not. Check that the
+    // publishing date has been removed and the status is now published.
+    $assert->pageTextNotContains($message1);
+    $assert->pageTextContains($message2);
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    $this->assertEmpty($node->unpublish_on->value, 'Node is not scheduled for unpublishing.');
+    $this->assertTrue($node->isPublished(), 'Node is now published for title: "' . $node->title->value . '".');
+
+    // Third, create a new node which is not scheduler-enabled.
+    $edit = [
+      'title[0][value]' => 'New non-enabled node - Trigger Action Rule 1',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->nonSchedulerNodeType->id(), $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle('New non-enabled node - Trigger Action Rule 1');
+    // Check that rule 1 issued a warning message.
+    $assert->pageTextContains('warning message');
+    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
+    // Check that no publishing date is set.
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    // Check that a log message has been recorded.
+    $log = \Drupal::database()->select('watchdog', 'w')
+      ->condition('type', 'scheduler')
+      ->condition('severity', RfcLogLevel::WARNING)
+      ->countQuery()
+      ->execute()
+      ->fetchColumn();
+    $this->assertEquals(1, $log, 'There is 1 watchdog warning message from Scheduler');
+
+    // Fourthly, edit a pre-existing node which is not enabled for Scheduler.
+    $node = $this->node_b;
+
+    // Edit the node, triggering rule 1.
+    $edit = [
+      'title[0][value]' => 'Edit non-enabled node - Trigger Action Rule 1',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    // Check that rule 1 issued a warning message.
+    $assert->pageTextContains('warning message');
+    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
+    // Check that no publishing date is set.
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    // Check that a log message has been recorded.
+    $log = \Drupal::database()->select('watchdog', 'w')
+      ->condition('type', 'scheduler')
+      ->condition('severity', RfcLogLevel::WARNING)
+      ->countQuery()
+      ->execute()
+      ->fetchColumn();
+    $this->assertEquals(2, $log, 'There are now 2 watchdog warning messages from Scheduler');
+
+    // Edit the node, triggering rule 2.
+    $edit = [
+      'title[0][value]' => 'Edit non-enabled node - Trigger Action Rule 2',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    // Check that rule 2 issued a warning message.
+    $assert->pageTextContains('warning message');
+    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
+    // Check that a second log message has been recorded.
+    $log = \Drupal::database()->select('watchdog', 'w')
+      ->condition('type', 'scheduler')
+      ->condition('severity', RfcLogLevel::WARNING)
+      ->countQuery()
+      ->execute()
+      ->fetchColumn();
+    $this->assertEquals(3, $log, 'There are now 3 watchdog warning messages from Scheduler');
+  }
+
+  /**
+   * Tests the actions which set and remove the 'Unpublish On' date.
+   */
+  public function testUnpublishOnActions() {
+
+    $unpublish_on = $this->requestTime + 2400;
+    $unpublish_on_formatted = $this->dateFormatter->format($unpublish_on, 'long');
+
+    // Create rule 3 to set the unpublishing date.
+    $rule3 = $this->expressionManager->createRule();
+    $rule3->addCondition('rules_data_comparison',
+        ContextConfig::create()
+          ->map('data', 'node.title.value')
+          ->setValue('operation', 'contains')
+          ->setValue('value', 'Trigger Action Rule 3')
+    );
+    $message3 = 'RULES message 3. Action to set Unpublish-on date.';
+    $rule3->addAction('scheduler_set_unpublishing_date_action',
+      ContextConfig::create()
+        ->map('node', 'node')
+        ->setValue('date', $unpublish_on)
+      )
+      ->addAction('rules_system_message',
+        ContextConfig::create()
+          ->setValue('message', $message3)
+          ->setValue('type', 'status')
+    );
+    // The event needs to be rules_entity_presave:node 'before saving' because
+    // rules_entity_update:node 'after save' is too late to set the date.
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule3',
+      'events' => [['event_name' => 'rules_entity_presave:node']],
+      'expression' => $rule3->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    // Create rule 4 to remove the unpublishing date and unpublish the node.
+    $rule4 = $this->expressionManager->createRule();
+    $rule4->addCondition('rules_data_comparison',
+        ContextConfig::create()
+          ->map('data', 'node.title.value')
+          ->setValue('operation', 'contains')
+          ->setValue('value', 'Trigger Action Rule 4')
+    );
+    $message4 = 'RULES message 4. Action to remove Unpublish-on date and unpublish the node immediately.';
+    $rule4->addAction('scheduler_remove_unpublishing_date_action',
+      ContextConfig::create()
+        ->map('node', 'node')
+      )
+      ->addAction('scheduler_unpublish_now_action',
+        ContextConfig::create()
+          ->map('node', 'node')
+      )
+      ->addAction('rules_system_message',
+        ContextConfig::create()
+          ->setValue('message', $message4)
+          ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule4',
+      'events' => [['event_name' => 'rules_entity_presave:node']],
+      'expression' => $rule4->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    $assert = $this->assertSession();
+
+    // First, create a new scheduler-enabled node, triggering rule 3.
+    $edit = [
+      'title[0][value]' => 'New node - Trigger Action Rule 3',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle('New node - Trigger Action Rule 3');
+    $this->assertSession()->pageTextContains(sprintf('%s is scheduled to be unpublished %s', 'New node - Trigger Action Rule 3', $unpublish_on_formatted));
+
+    // Check that rule 3 is triggered and rule 4 is not. Check that a publishing
+    // date has been set and the status is now unpublished.
+    $assert->pageTextContains($message3);
+    $assert->pageTextNotContains($message4);
+    $this->assertEquals($node->unpublish_on->value, $unpublish_on, 'Node is scheduled for unpublishing at the correct time.');
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    $this->assertTrue($node->isPublished(), 'Node is published for title: "' . $node->title->value . '".');
+
+    // Second, edit a pre-existing Scheduler-enabled node, without triggering
+    // either of the rules.
+    $node = $this->node_a;
+    $edit = [
+      'title[0][value]' => 'Edit node - but no rules will be triggered',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check that neither of the rules are triggered, no publish and unpublish
+    // dates are set and the status is still published.
+    $assert->pageTextNotContains($message3);
+    $assert->pageTextNotContains($message4);
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    $this->assertEmpty($node->unpublish_on->value, 'Node is not scheduled for unpublishing.');
+    $this->assertTrue($node->isPublished(), 'Node remains published for title: "' . $node->title->value . '".');
+
+    // Edit the node, triggering rule 3.
+    $edit = [
+      'title[0][value]' => 'Edit node - Trigger Action Rule 3',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check that rule 3 is triggered and rule 4 is not. Check that an
+    // unpublishing date has been set and the status is still published.
+    $assert->pageTextContains($message3);
+    $assert->pageTextNotContains($message4);
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    $this->assertNotEmpty($node->unpublish_on->value, 'Node is scheduled for unpublishing.');
+    $this->assertTrue($node->isPublished(), 'Node is still published for title: "' . $node->title->value . '".');
+
+    // Edit the node, triggering rule 4.
+    $edit = [
+      'title[0][value]' => 'Edit node - Trigger Action Rule 4',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    // Check that rule 4 is triggered and rule 3 is not. Check that the
+    // unpublishing date has been removed and the status is now unpublished.
+    $assert->pageTextNotContains($message3);
+    $assert->pageTextContains($message4);
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    $this->assertEmpty($node->unpublish_on->value, 'Node is not scheduled for unpublishing.');
+    $this->assertFalse($node->isPublished(), 'Node is now unpublished for title: "' . $node->title->value . '".');
+
+    // Third, create a new node which is not scheduler-enabled.
+    $edit = [
+      'title[0][value]' => 'New non-enabled node - Trigger Action Rule 3',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->nonSchedulerNodeType->id(), $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle('New non-enabled node - Trigger Action Rule 3');
+    // Check that rule 3 issued a warning message.
+    $assert->pageTextContains('warning message');
+    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
+    // Check that no publishing date is set.
+    $this->assertEmpty($node->publish_on->value, 'Node is not scheduled for publishing.');
+    // Check that a log message has been recorded.
+    $log = \Drupal::database()->select('watchdog', 'w')
+      ->condition('type', 'scheduler')
+      ->condition('severity', RfcLogLevel::WARNING)
+      ->countQuery()
+      ->execute()
+      ->fetchColumn();
+    $this->assertEquals(1, $log, 'There is 1 watchdog warning message from Scheduler');
+
+    // Fourthly, edit a pre-existing node which is not enabled for Scheduler.
+    $node = $this->node_b;
+
+    // Edit the node, triggering rule 3.
+    $edit = [
+      'title[0][value]' => 'Edit non-enabled node - Trigger Action Rule 3',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    // Check that rule 3 issued a warning message.
+    $assert->pageTextContains('warning message');
+    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
+    // Check that no unpublishing date is set.
+    $this->assertEmpty($node->unpublish_on->value, 'Node is not scheduled for unpublishing.');
+    // Check that a log message has been recorded.
+    $log = \Drupal::database()->select('watchdog', 'w')
+      ->condition('type', 'scheduler')
+      ->condition('severity', RfcLogLevel::WARNING)
+      ->countQuery()
+      ->execute()
+      ->fetchColumn();
+    $this->assertEquals(2, $log, 'There are now 2 watchdog warning messages from Scheduler');
+
+    // Edit the node, triggering rule 4.
+    $edit = [
+      'title[0][value]' => 'Edit non-enabled node - Trigger Action Rule 4',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    // Check that rule 4 issued a warning message.
+    $assert->pageTextContains('warning message');
+    $assert->elementExists('xpath', '//div[@aria-label="Warning message" and contains(string(), "Action")]');
+    // Check that a second log message has been recorded.
+    $log = \Drupal::database()->select('watchdog', 'w')
+      ->condition('type', 'scheduler')
+      ->condition('severity', RfcLogLevel::WARNING)
+      ->countQuery()
+      ->execute()
+      ->fetchColumn();
+    $this->assertEquals(3, $log, 'There are now 3 watchdog warning messages from Scheduler');
+  }
+
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..972d958d387b520a183566922b2f533e5523792d
--- /dev/null
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
@@ -0,0 +1,265 @@
+<?php
+
+namespace Drupal\Tests\scheduler\Functional;
+
+use Drupal\rules\Context\ContextConfig;
+
+/**
+ * Tests the four conditions that Scheduler provides for use in Rules module.
+ *
+ * @group scheduler
+ */
+class SchedulerRulesConditionsTest extends SchedulerBrowserTestBase {
+
+  /**
+   * Additional modules required.
+   *
+   * @var array
+   */
+  protected static $modules = ['scheduler_rules_integration'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->rulesStorage = $this->container->get('entity_type.manager')->getStorage('rules_reaction_rule');
+    $this->expressionManager = $this->container->get('plugin.manager.rules_expression');
+
+    // Create a published node.
+    $this->node = $this->drupalCreateNode([
+      'title' => 'Rules Test Node',
+      'type' => $this->type,
+      'uid' => $this->schedulerUser->id(),
+      'status' => TRUE,
+    ]);
+  }
+
+  /**
+   * Tests the conditions for whether a nodetype is enabled for Scheduler.
+   */
+  public function testNodeTypeEnabledConditions() {
+    // Create a reaction rule to display a message when viewing a node of a type
+    // that is enabled for scheduled publishing.
+    // "viewing content" actually means "viewing PUBLISHED content".
+    $rule1 = $this->expressionManager->createRule();
+    $rule1->addCondition('scheduler_condition_publishing_is_enabled',
+      ContextConfig::create()->map('node', 'node')
+    );
+    $message1 = 'RULES message 1. This node type is enabled for scheduled publishing.';
+    $rule1->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message1)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule1',
+      'events' => [['event_name' => 'rules_entity_view:node']],
+      'expression' => $rule1->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    // Create a reaction rule to display a message when viewing a node of a type
+    // that is enabled for scheduled unpublishing.
+    $rule2 = $this->expressionManager->createRule();
+    $rule2->addCondition('scheduler_condition_unpublishing_is_enabled',
+      ContextConfig::create()->map('node', 'node')
+    );
+    $message2 = 'RULES message 2. This node type is enabled for scheduled unpublishing.';
+    $rule2->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message2)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule2',
+      'events' => [['event_name' => 'rules_entity_view:node']],
+      'expression' => $rule2->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    // Create a reaction rule to display a message when viewing a node of a type
+    // that is NOT enabled for scheduled publishing.
+    $rule3 = $this->expressionManager->createRule();
+    $rule3->addCondition('scheduler_condition_publishing_is_enabled',
+      ContextConfig::create()->map('node', 'node')->negateResult()
+    );
+    $message3 = 'RULES message 3. This node type is not enabled for scheduled publishing.';
+    $rule3->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message3)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule3',
+      'events' => [['event_name' => 'rules_entity_view:node']],
+      'expression' => $rule3->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    // Create a reaction rule to display a message when viewing a node of a type
+    // that is NOT enabled for scheduled unpublishing.
+    $rule4 = $this->expressionManager->createRule();
+    $rule4->addCondition('scheduler_condition_unpublishing_is_enabled',
+      ContextConfig::create()->map('node', 'node')->negateResult()
+    );
+    $message4 = 'RULES message 4. This node type is not enabled for scheduled unpublishing.';
+    $rule4->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message4)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule4',
+      'events' => [['event_name' => 'rules_entity_view:node']],
+      'expression' => $rule4->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    $assert = $this->assertSession();
+
+    // View the node and check the default position - that the node type is
+    // enabled for both publishing and unpublishing.
+    $this->drupalGet('node/' . $this->node->id());
+    $assert->pageTextContains($message1);
+    $assert->pageTextContains($message2);
+    $assert->pageTextNotContains($message3);
+    $assert->pageTextNotContains($message4);
+
+    // Turn off scheduled publishing for the node type and check the rules.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_enable', FALSE)->save();
+    // Flushing the caches was not required when using WebTestBase but is needed
+    // after converting to BrowserTestBase.
+    drupal_flush_all_caches();
+    $this->drupalGet('node/' . $this->node->id());
+    $assert->pageTextNotContains($message1);
+    $assert->pageTextContains($message2);
+    $assert->pageTextContains($message3);
+    $assert->pageTextNotContains($message4);
+
+    // Turn off scheduled unpublishing for the node type and the check again.
+    $this->nodetype->setThirdPartySetting('scheduler', 'unpublish_enable', FALSE)->save();
+    drupal_flush_all_caches();
+    $this->drupalGet('node/' . $this->node->id());
+    $assert->pageTextNotContains($message1);
+    $assert->pageTextNotContains($message2);
+    $assert->pageTextContains($message3);
+    $assert->pageTextContains($message4);
+
+  }
+
+  /**
+   * Tests the conditions for whether a node is scheduled.
+   */
+  public function testNodeIsScheduledConditions() {
+    // Create a reaction rule to display a message when a node is updated and
+    // is not scheduled for publishing.
+    $rule5 = $this->expressionManager->createRule();
+    $rule5->addCondition('scheduler_condition_node_scheduled_for_publishing',
+      ContextConfig::create()->map('node', 'node')->negateResult()
+    );
+    $message5 = 'RULES message 5. This content is not scheduled for publishing.';
+    $rule5->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message5)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule5',
+      'events' => [['event_name' => 'rules_entity_update:node']],
+      'expression' => $rule5->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    // Create a reaction rule to display a message when a node is updated and
+    // is not scheduled for unpublishing.
+    $rule6 = $this->expressionManager->createRule();
+    $rule6->addCondition('scheduler_condition_node_scheduled_for_unpublishing',
+      ContextConfig::create()->map('node', 'node')->negateResult()
+    );
+    $message6 = 'RULES message 6. This content is not scheduled for unpublishing.';
+    $rule6->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message6)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule6',
+      'events' => [['event_name' => 'rules_entity_update:node']],
+      'expression' => $rule6->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    // Create a reaction rule to display a message when a node is updated and
+    // is scheduled for publishing.
+    $rule7 = $this->expressionManager->createRule();
+    $rule7->addCondition('scheduler_condition_node_scheduled_for_publishing',
+      ContextConfig::create()->map('node', 'node')
+    );
+    $message7 = 'RULES message 7. This content is scheduled for publishing.';
+    $rule7->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message7)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule7',
+      'events' => [['event_name' => 'rules_entity_update:node']],
+      'expression' => $rule7->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    $assert = $this->assertSession();
+
+    // Create a reaction rule to display a message when a node is updated and
+    // is scheduled for unpublishing.
+    $rule8 = $this->expressionManager->createRule();
+    $rule8->addCondition('scheduler_condition_node_scheduled_for_unpublishing',
+      ContextConfig::create()->map('node', 'node')
+    );
+    $message8 = 'RULES message 8. This content is scheduled for unpublishing.';
+    $rule8->addAction('rules_system_message', ContextConfig::create()
+      ->setValue('message', $message8)
+      ->setValue('type', 'status')
+      );
+    $config_entity = $this->rulesStorage->create([
+      'id' => 'rule8',
+      'events' => [['event_name' => 'rules_entity_update:node']],
+      'expression' => $rule8->getConfiguration(),
+    ]);
+    $config_entity->save();
+
+    $this->drupalLogin($this->schedulerUser);
+
+    // Edit the node but do not enter any scheduling dates.
+    $edit = [
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
+
+    $assert->pageTextContains($message5);
+    $assert->pageTextContains($message6);
+    $assert->pageTextNotContains($message7);
+    $assert->pageTextNotContains($message8);
+
+    // Edit the node and set a publish_on date.
+    $edit = [
+      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)),
+      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', $this->requestTime)),
+    ];
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
+
+    $assert->pageTextNotContains($message5);
+    $assert->pageTextContains($message6);
+    $assert->pageTextContains($message7);
+    $assert->pageTextNotContains($message8);
+
+    // Edit the node and set an unpublish_on date.
+    $edit = [
+      'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', $this->requestTime)),
+      'unpublish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', $this->requestTime)),
+    ];
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
+
+    $assert->pageTextNotContains($message5);
+    $assert->pageTextNotContains($message6);
+    $assert->pageTextContains($message7);
+    $assert->pageTextContains($message8);
+
+  }
+
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..803a16c005f14aff4dd6eb81b8b3fc06e18826db
--- /dev/null
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace Drupal\Tests\scheduler\Functional;
+
+use Drupal\rules\Context\ContextConfig;
+
+/**
+ * Tests the six events that Scheduler provides for use in Rules module.
+ *
+ * phpcs:set Drupal.Arrays.Array lineLimit 140
+ *
+ * @group scheduler
+ */
+class SchedulerRulesEventsTest extends SchedulerBrowserTestBase {
+
+  /**
+   * Additional modules required.
+   *
+   * @var array
+   */
+  protected static $modules = ['scheduler_rules_integration'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->rulesStorage = $this->container->get('entity_type.manager')->getStorage('rules_reaction_rule');
+    $this->expressionManager = $this->container->get('plugin.manager.rules_expression');
+
+    // Create six reaction rules, one for each event that Scheduler triggers.
+    // These rules are all active throughout all of the tests, which makes the
+    // tests stronger, because it will show not only that the correct events are
+    // triggered in the right places, but also that they are not triggered in
+    // the wrong places.
+    $rule_data = [
+      1 => ['scheduler_new_node_is_scheduled_for_publishing_event', 'A new node is created and is scheduled for publishing.'],
+      2 => ['scheduler_existing_node_is_scheduled_for_publishing_event', 'An existing node is saved and is scheduled for publishing.'],
+      3 => ['scheduler_has_published_this_node_event', 'Scheduler has published this node during cron.'],
+      4 => ['scheduler_new_node_is_scheduled_for_unpublishing_event', 'A new node is created and is scheduled for unpublishing.'],
+      5 => ['scheduler_existing_node_is_scheduled_for_unpublishing_event', 'An existing node is saved and is scheduled for unpublishing.'],
+      6 => ['scheduler_has_unpublished_this_node_event', 'Scheduler has unpublished this node during cron.'],
+    ];
+    // PHPCS throws a false-positive 'variable $var is undefined' message when
+    // the variable is defined by list( ) syntax. To avoid the unwanted warnings
+    // we can wrap the section with codingStandardsIgnoreStart and IgnoreEnd.
+    // @see https://www.drupal.org/project/coder/issues/2876245
+    // @codingStandardsIgnoreStart
+    foreach ($rule_data as $i => list($event_name, $description)) {
+      $rule[$i] = $this->expressionManager->createRule();
+      $this->message[$i] = 'RULES message ' . $i . '. ' . $description;
+      $rule[$i]->addAction('rules_system_message', ContextConfig::create()
+        ->setValue('message', $this->message[$i])
+        ->setValue('type', 'status')
+        );
+      $config_entity = $this->rulesStorage->create([
+        'id' => 'rule' . $i,
+        'events' => [['event_name' => $event_name]],
+        'expression' => $rule[$i]->getConfiguration(),
+      ]);
+      $config_entity->save();
+    }
+    // @codingStandardsIgnoreEnd
+
+    $this->drupalLogin($this->schedulerUser);
+  }
+
+  /**
+   * Tests that no events are triggered when there are no scheduling dates.
+   */
+  public function testRulesEventsNone() {
+    $assert = $this->assertSession();
+
+    // Create a node without any scheduled dates, using node/add/ not
+    // drupalCreateNode(), and check that no events are triggered.
+    $edit = [
+      'title[0][value]' => 'Test for no events',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+
+    // Edit the node and check that no events are triggered.
+    $edit = [
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+  }
+
+  /**
+   * Tests the three events related to publishing a node.
+   */
+  public function testRulesEventsPublish() {
+    $assert = $this->assertSession();
+
+    // Create a node with a publish-on date, and check that only event 1 is
+    // triggered.
+    $edit = [
+      'title[0][value]' => 'Create node with publish-on date',
+      'publish_on[0][value][date]' => date('Y-m-d', time() + 3),
+      'publish_on[0][value][time]' => date('H:i:s', time() + 3),
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $assert->pageTextContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+
+    // Edit this node and check that only event 2 is triggered.
+    $edit = [
+      'title[0][value]' => 'Edit node with publish-on date',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+
+    // Delay to ensure that the date entered is now in the past so that the node
+    // will be processed during cron, and assert that only event 3 is triggered.
+    sleep(5);
+    $this->cronRun();
+    $this->drupalGet('admin/reports/dblog');
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+  }
+
+  /**
+   * Tests the three events related to unpublishing a node.
+   */
+  public function testRulesEventsUnpublish() {
+    $assert = $this->assertSession();
+
+    // Create a node with an unpublish-on date, and check that only event 4 is
+    // triggered.
+    $edit = [
+      'title[0][value]' => 'Create node with unpublish-on date',
+      'unpublish_on[0][value][date]' => date('Y-m-d', time() + 3),
+      'unpublish_on[0][value][time]' => date('H:i:s', time() + 3),
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+
+    // Edit this node and check that only event 5 is triggered.
+    $edit = [
+      'title[0][value]' => 'Edit node with unpublish-on date',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+
+    // Delay to ensure that the date entered is now in the past so that the node
+    // will be processed during cron, and assert that only event 6 is triggered.
+    sleep(5);
+    $this->cronRun();
+    $this->drupalGet('admin/reports/dblog');
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextContains($this->message[6]);
+  }
+
+  /**
+   * Tests all six events related to publishing and unpublishing a node.
+   */
+  public function testRulesEventsBoth() {
+    $assert = $this->assertSession();
+
+    // Create a node with both publish-on and unpublish-on dates, and check that
+    // both event 1 and event 4 are triggered.
+    $edit = [
+      'title[0][value]' => 'Create node with both dates',
+      'publish_on[0][value][date]' => date('Y-m-d', time() + 3),
+      'publish_on[0][value][time]' => date('H:i:s', time() + 3),
+      'unpublish_on[0][value][date]' => date('Y-m-d', time() + 4),
+      'unpublish_on[0][value][time]' => date('H:i:s', time() + 4),
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $assert->pageTextContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextContains($this->message[4]);
+    $assert->pageTextNotContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+
+    // Edit this node and check that events 2 and 5 are triggered.
+    $edit = [
+      'title[0][value]' => 'Edit node with both dates',
+      'body[0][value]' => $this->randomString(30),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextContains($this->message[2]);
+    $assert->pageTextNotContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextContains($this->message[5]);
+    $assert->pageTextNotContains($this->message[6]);
+
+    // Delay to ensure that the dates are now in the past so that the node will
+    // be processed during cron, and assert that events 3, 5 & 6 are triggered.
+    sleep(6);
+    $this->cronRun();
+    $this->drupalGet('admin/reports/dblog');
+    $assert->pageTextNotContains($this->message[1]);
+    $assert->pageTextNotContains($this->message[2]);
+    $assert->pageTextContains($this->message[3]);
+    $assert->pageTextNotContains($this->message[4]);
+    $assert->pageTextContains($this->message[5]);
+    $assert->pageTextContains($this->message[6]);
+
+  }
+
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerScheduledContentListAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerScheduledContentListAccessTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..33fd8874dfe596c4550b1ccba543132be0d9387a
--- /dev/null
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerScheduledContentListAccessTest.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Drupal\Tests\scheduler\Functional;
+
+/**
+ * Tests access to the scheduled content overview page and user tab.
+ *
+ * @group scheduler
+ */
+class SchedulerScheduledContentListAccessTest extends SchedulerBrowserTestBase {
+
+  /**
+   * Additional modules required.
+   *
+   * @var array
+   */
+  protected static $modules = ['views'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $base_permissions = [
+      'access content',
+      'create ' . $this->type . ' content',
+      'view own unpublished content',
+    ];
+
+    $this->editorUser = $this->drupalCreateUser(array_merge($base_permissions, ['access content overview']));
+    $this->schedulerUser = $this->drupalCreateUser(array_merge($base_permissions, ['schedule publishing of nodes']));
+    $this->schedulerManager = $this->drupalCreateUser(array_merge($base_permissions, ['view scheduled content']));
+
+    // Create nodes scheduled for publishing and for unpublishing.
+    $this->node1 = $this->drupalCreateNode([
+      'title' => 'Node created by Scheduler User for publishing',
+      'uid' => $this->schedulerUser->id(),
+      'status' => FALSE,
+      'type' => $this->type,
+      'publish_on' => strtotime('+1 week'),
+    ]);
+    $this->node2 = $this->drupalCreateNode([
+      'title' => 'Node created by Scheduler User for unpublishing',
+      'uid' => $this->schedulerUser->id(),
+      'status' => TRUE,
+      'type' => $this->type,
+      'unpublish_on' => strtotime('+1 week'),
+    ]);
+    $this->node3 = $this->drupalCreateNode([
+      'title' => 'Node created by Scheduler Manager for publishing',
+      'uid' => $this->schedulerManager->id(),
+      'status' => FALSE,
+      'type' => $this->type,
+      'publish_on' => strtotime('+1 week'),
+    ]);
+    $this->node4 = $this->drupalCreateNode([
+      'title' => 'Node created by Scheduler Manager for unpublishing',
+      'uid' => $this->schedulerManager->id(),
+      'status' => TRUE,
+      'type' => $this->type,
+      'unpublish_on' => strtotime('+1 week'),
+    ]);
+  }
+
+  /**
+   * Tests the scheduled content tab on the user page.
+   */
+  public function testViewScheduledContentUser() {
+    $assert = $this->assertSession();
+
+    // Access a scheduled content user tab as an anonymous visitor.
+    $this->drupalGet("user/{$this->schedulerUser->id()}/scheduled");
+    // An anonymous visitor cannot access a user's scheduled content tab.
+    $assert->statusCodeEquals(403);
+
+    // Try to access a users own scheduled content tab when they do not have
+    // any scheduler permissions. This should give "403 Access Denied".
+    $this->drupalLogin($this->editorUser);
+    $this->drupalGet("user/{$this->editorUser->id()}/scheduled");
+    $assert->statusCodeEquals(403);
+
+    // Access a users own scheduled content tab when they have only
+    // 'schedule publishing of nodes' permission. This will give "200 OK".
+    $this->drupalLogin($this->schedulerUser);
+    $this->drupalGet("user/{$this->schedulerUser->id()}/scheduled");
+    $assert->statusCodeEquals(200);
+    $assert->pageTextContains('Node created by Scheduler User for publishing');
+    $assert->pageTextContains('Node created by Scheduler User for unpublishing');
+    $assert->pageTextNotContains('Node created by Scheduler Manager for unpublishing');
+
+    // Access another users scheduled content tab as "Scheduler User". This
+    // should not be possible and will give "403 Access Denied".
+    $this->drupalGet("user/{$this->schedulerManager->id()}/scheduled");
+    $assert->statusCodeEquals(403);
+
+    // Access the users own scheduled content tab as "Scheduler Manager" with
+    // only 'view scheduled content' permission.
+    $this->drupalLogin($this->schedulerManager);
+    $this->drupalGet("user/{$this->schedulerManager->id()}/scheduled");
+    $assert->statusCodeEquals(200);
+    $assert->pageTextContains('Node created by Scheduler Manager for publishing');
+    $assert->pageTextContains('Node created by Scheduler Manager for unpublishing');
+    $assert->pageTextNotContains('Node created by Scheduler User for unpublishing');
+
+    // Access another users scheduled content tab as "Scheduler Manager".
+    // The published and unpublished content should be listed.
+    $this->drupalGet("user/{$this->schedulerUser->id()}/scheduled");
+    $assert->statusCodeEquals(200);
+    $assert->pageTextContains('Node created by Scheduler User for publishing');
+    $assert->pageTextContains('Node created by Scheduler User for unpublishing');
+  }
+
+  /**
+   * Tests the scheduled content overview.
+   */
+  public function testViewScheduledContentOverview() {
+    $assert = $this->assertSession();
+
+    // Access the scheduled content overview as anonymous visitor.
+    $this->drupalGet('admin/content/scheduled');
+    $assert->statusCodeEquals(403);
+
+    // Access the scheduled content overview as "Editor" without any
+    // scheduler permissions.
+    $this->drupalLogin($this->editorUser);
+    $this->drupalGet('admin/content/scheduled');
+    $assert->statusCodeEquals(403);
+
+    // Access the scheduled content overview as "Scheduler User" with only
+    // 'schedule publishing of nodes' permission.
+    $this->drupalLogin($this->schedulerUser);
+    $this->drupalGet('admin/content/scheduled');
+    $assert->statusCodeEquals(403);
+
+    // Access the scheduled content overview as "Scheduler Manager" with only
+    // 'view scheduled content' permission. They should be able to see the
+    // scheduled published and unpublished content by all users.
+    $this->drupalLogin($this->schedulerManager);
+    $this->drupalGet('admin/content/scheduled');
+    $assert->statusCodeEquals(200);
+    $assert->pageTextContains('Node created by Scheduler User for publishing');
+    $assert->pageTextContains('Node created by Scheduler User for unpublishing');
+    $assert->pageTextContains('Node created by Scheduler Manager for publishing');
+    $assert->pageTextContains('Node created by Scheduler Manager for unpublishing');
+  }
+
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
index 042ab30966e8e2911eb0ea51a24f409cb8320951..5aeebb0ec17602a4e999775c64406d510ce4e4f1 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
@@ -11,27 +11,25 @@ class SchedulerTokenReplaceTest extends SchedulerBrowserTestBase {
 
   /**
    * Creates a node, then tests the tokens generated from it.
-   *
-   * @dataProvider dataSchedulerTokenReplacement()
    */
-  public function testSchedulerTokenReplacement($entityTypeId, $bundle) {
+  public function testSchedulerTokenReplacement() {
     $this->drupalLogin($this->schedulerUser);
     // Define timestamps for consistent use when repeated throughout this test.
     $publish_on_timestamp = $this->requestTime + 3600;
     $unpublish_on_timestamp = $this->requestTime + 7200;
 
-    // Create an unpublished entity with scheduled dates.
-    $entity = $this->createEntity($entityTypeId, $bundle, [
+    // Create an unpublished page with scheduled dates.
+    $node = $this->drupalCreateNode([
+      'type' => $this->type,
       'status' => FALSE,
       'publish_on' => $publish_on_timestamp,
       'unpublish_on' => $unpublish_on_timestamp,
     ]);
-    // Check that the entity is scheduled.
-    $this->assertFalse($entity->isPublished(), 'The entity is not published');
-    $this->assertNotEmpty($entity->publish_on->value, 'The entity has a publish_on date');
-    $this->assertNotEmpty($entity->unpublish_on->value, 'The entity has an unpublish_on date');
+    // Show that the node is scheduled.
+    $this->drupalGet('admin/content/scheduled');
 
     // Create array of test case data.
+    // @todo Convert this test to use @dataProvider instead of array and loop?
     $test_cases = [
       ['token_format' => '', 'date_format' => 'medium', 'custom' => ''],
       ['token_format' => ':long', 'date_format' => 'long', 'custom' => ''],
@@ -43,21 +41,18 @@ public function testSchedulerTokenReplacement($entityTypeId, $bundle) {
       ],
     ];
 
-    $storage = $this->entityStorageObject($entityTypeId);
     foreach ($test_cases as $test_data) {
-      // Edit the entity and set the body tokens to use the format being tested.
+      // Edit the node and set the body tokens to use the format being tested.
       $edit = [
-        "{$this->bodyField($entityTypeId)}[0][value]" => "Publish on: [{$entityTypeId}:scheduler-publish{$test_data['token_format']}]. Unpublish on: [{$entityTypeId}:scheduler-unpublish{$test_data['token_format']}].",
+        'body[0][value]' => 'Publish on: [node:scheduler-publish' . $test_data['token_format'] . ']. Unpublish on: [node:scheduler-unpublish' . $test_data['token_format'] . '].',
       ];
-      $this->drupalGet($entity->toUrl('edit-form'));
-      $this->submitForm($edit, 'Save');
-      // View the entity.
-      $this->drupalGet($entity->toUrl());
+      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+      $this->drupalGet('node/' . $node->id());
 
-      // Refresh the entity and get the body output value using token replace.
-      $storage->resetCache([$entity->id()]);
-      $entity = $storage->load($entity->id());
-      $body_output = \Drupal::token()->replace($entity->{$this->bodyField($entityTypeId)}->value, ["$entityTypeId" => $entity]);
+      // Refresh the node and get the body output value.
+      $this->nodeStorage->resetCache([$node->id()]);
+      $node = $this->nodeStorage->load($node->id());
+      $body_output = \Drupal::token()->replace($node->body->value, ['node' => $node]);
 
       // Create the expected text for the body.
       $publish_on_date = $this->dateFormatter->format($publish_on_timestamp, $test_data['date_format'], $test_data['custom']);
@@ -68,18 +63,4 @@ public function testSchedulerTokenReplacement($entityTypeId, $bundle) {
     }
   }
 
-  /**
-   * Provides test data for TokenReplacement test.
-   *
-   * This test is not run for Media entities because there is no body field.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id].
-   */
-  public function dataSchedulerTokenReplacement() {
-    $data = $this->dataStandardEntityTypes();
-    unset($data['#media']);
-    return $data;
-  }
-
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
index eb27cbc27a9076dda1e693d6ae26db707b7d654c..370f325ace4051f309a3b7599ed708e06cf64f02 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
@@ -13,62 +13,62 @@ class SchedulerValidationTest extends SchedulerBrowserTestBase {
    * Tests the validation when editing a node.
    *
    * The 'required' checks and 'dates in the past' checks are handled in other
-   * tests. This test checks validation when the two fields interact, and covers
-   * the error message text stored in the following constraint variables:
-   *   $messageUnpublishOnRequiredIfPublishOnEntered
-   *   $messageUnpublishOnRequiredIfPublishing
-   *   $messageUnpublishOnTooEarly.
-   *
-   * @dataProvider dataStandardEntityTypes()
+   * tests. This test checks validation when fields interact.
    */
-  public function testValidationDuringEdit($entityTypeId, $bundle) {
+  public function testValidationDuringEdit() {
     $this->drupalLogin($this->adminUser);
 
-    // Set unpublishing to be required for this entity type.
-    $this->entityTypeObject($entityTypeId)->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)->save();
-
-    // Create an unpublished entity.
-    $entity = $this->createEntity($entityTypeId, $bundle, ['status' => FALSE]);
+    // Set unpublishing to be required.
+    $this->nodetype->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)->save();
 
-    // Edit the unpublished entity and try to save a publish-on date.
+    // Create an unpublished page node, then edit the node and check that if a
+    // publish-on date is entered then an unpublish-on date is also needed.
+    $node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+    ]);
     $edit = [
       'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)),
       'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', $this->requestTime)),
     ];
-    $this->drupalGet($entity->toUrl('edit-form'));
+    $this->drupalGet('node/' . $node->id() . '/edit');
+
     $this->submitForm($edit, 'Save');
     // Check that validation prevents entering a publish-on date with no
     // unpublish-on date if unpublishing is required.
     $this->assertSession()->pageTextContains("If you set a 'publish on' date then you must also set an 'unpublish on' date.");
-    $this->assertSession()->pageTextNotMatches('/has been (updated|successfully saved)/');
+    $this->assertSession()->pageTextNotContains(sprintf('%s %s has been updated.', $this->typeName, $node->title->value));
 
-    // Create an unpublished entity.
-    $entity = $this->createEntity($entityTypeId, $bundle, ['status' => FALSE]);
-
-    // Edit the unpublished entity and try to change the status to 'published'.
+    // Create an unpublished page node, then edit the node and check that if the
+    // status is changed to published, then an unpublish-on date is also needed.
+    $node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+    ]);
     $edit = ['status[value]' => TRUE];
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
-    // Check that validation prevents publishing the entity directly without an
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    // Check that validation prevents publishing the node directly without an
     // unpublish-on date if unpublishing is required.
-    $this->assertSession()->pageTextContains("Either you must set an 'unpublish on' date or save as unpublished.");
-    $this->assertSession()->pageTextNotMatches('/has been (updated|successfully saved)/');
+    $this->assertSession()->pageTextContains("Either you must set an 'unpublish on' date or save this node as unpublished.");
+    $this->assertSession()->pageTextNotContains(sprintf('%s %s has been updated.', $this->typeName, $node->title->value));
 
-    // Create an unpublished entity, and try to edit and save with a publish-on
-    // date later than the unpublish-on date.
-    $entity = $this->createEntity($entityTypeId, $bundle, ['status' => FALSE]);
+    // Create an unpublished node, edit the node and check that if both dates
+    // are entered then the unpublish date is later than the publish date.
+    $node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+    ]);
     $edit = [
-      'publish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime + 7200, 'custom', 'Y-m-d'),
-      'publish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime + 7200, 'custom', 'H:i:s'),
+      'publish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime + 8100, 'custom', 'Y-m-d'),
+      'publish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime + 8100, 'custom', 'H:i:s'),
       'unpublish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime + 1800, 'custom', 'Y-m-d'),
       'unpublish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime + 1800, 'custom', 'H:i:s'),
     ];
-    $this->drupalGet($entity->toUrl('edit-form'));
-    $this->submitForm($edit, 'Save');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     // Check that validation prevents entering an unpublish-on date which is
     // earlier than the publish-on date.
     $this->assertSession()->pageTextContains("The 'unpublish on' date must be later than the 'publish on' date.");
-    $this->assertSession()->pageTextNotMatches('/has been (updated|successfully saved)/');
+    $this->assertSession()->pageTextNotContains(sprintf('%s %s has been updated.', $this->typeName, $node->title->value));
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerViewsAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerViewsAccessTest.php
deleted file mode 100644
index ded873e6f1e372b857142d6644cd3fee3d3f9ad1..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerViewsAccessTest.php
+++ /dev/null
@@ -1,236 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests access to the scheduled content overview page and user tab.
- *
- * @group scheduler
- */
-class SchedulerViewsAccessTest extends SchedulerBrowserTestBase {
-
-  /**
-   * Additional modules required.
-   *
-   * @var array
-   */
-  protected static $modules = ['views'];
-
-  /**
-   * Create users and scheduled content for the entity type being tested.
-   */
-  protected function createScheduledItems($entityTypeId, $bundle) {
-    // For backwards-compatibility the node permission names have to end with
-    // 'nodes' and 'content'. For all other entity types we use $entityTypeId.
-    if ($entityTypeId == 'node') {
-      $edit_key = 'nodes';
-      $view_key = 'content';
-    }
-    else {
-      $edit_key = $view_key = $entityTypeId;
-    }
-    // "view own unpublished $view_key" is needed for Products. It is not
-    // required for Node or Media, and does not exist for Taxonomy terms.
-    $base_permissions = ($entityTypeId == 'commerce_product') ? ["view own unpublished $view_key"] : [];
-
-    $this->webUser = $this->drupalCreateUser();
-    $this->webUser->set('name', 'Webisa the Web User')->save();
-
-    $this->schedulerEditor = $this->drupalCreateUser(array_merge($base_permissions, ["schedule publishing of $edit_key"]));
-    $this->schedulerEditor->set('name', 'Eddie the Scheduler Editor')->save();
-
-    $this->schedulerViewer = $this->drupalCreateUser(array_merge($base_permissions, ["view scheduled $view_key"]));
-    $this->schedulerViewer->set('name', 'Vicenza the Scheduler Viewer')->save();
-
-    $this->addPermissionsToUser($this->adminUser, ['access user profiles']);
-
-    // Create content scheduled for publishing and for unpublishing. The first
-    // two are authored by schedulerEditor, the second two by schedulerViewer.
-    $this->createEntity($entityTypeId, $bundle, [
-      'title' => "$entityTypeId created by Scheduler Editor for publishing",
-      'uid' => $this->schedulerEditor->id(),
-      'status' => FALSE,
-      'publish_on' => strtotime('+1 week'),
-    ]);
-    $this->createEntity($entityTypeId, $bundle, [
-      'title' => "$entityTypeId created by Scheduler Editor for unpublishing",
-      'uid' => $this->schedulerEditor->id(),
-      'status' => TRUE,
-      'unpublish_on' => strtotime('+1 week'),
-    ]);
-    $this->createEntity($entityTypeId, $bundle, [
-      'title' => "$entityTypeId created by Scheduler Viewer for publishing",
-      'uid' => $this->schedulerViewer->id(),
-      'status' => FALSE,
-      'publish_on' => strtotime('+1 week'),
-    ]);
-    $this->createEntity($entityTypeId, $bundle, [
-      'title' => "$entityTypeId created by Scheduler Viewer for unpublishing",
-      'uid' => $this->schedulerViewer->id(),
-      'status' => TRUE,
-      'unpublish_on' => strtotime('+1 week'),
-    ]);
-  }
-
-  /**
-   * Tests the scheduled content tab on the user page.
-   *
-   * @dataProvider dataViewScheduledContentUser()
-   */
-  public function testViewScheduledContentUser($entityTypeId, $bundle) {
-    $this->createScheduledItems($entityTypeId, $bundle);
-    $url_end = ($entityTypeId == 'node') ? 'scheduled' : "scheduled_{$entityTypeId}";
-    $assert = $this->assertSession();
-
-    // Try to access a scheduled content user tab as an anonymous visitor. This
-    // should not be allowed, and will give "403 Access Denied".
-    $this->drupalGet("user/{$this->schedulerEditor->id()}/$url_end");
-    $assert->statusCodeEquals(403);
-
-    // Try to access a user's own scheduled content tab when they do not have
-    // any scheduler permissions. This should give "403 Access Denied".
-    $this->drupalLogin($this->webUser);
-    $this->drupalGet("user/{$this->webUser->id()}/$url_end");
-    $assert->statusCodeEquals(403);
-
-    // Access a user's own scheduled content tab when they have only
-    // 'schedule publishing of {type}' permission. This should give "200 OK".
-    $this->drupalLogin($this->schedulerEditor);
-    $this->drupalGet("user/{$this->schedulerEditor->id()}/$url_end");
-    $assert->statusCodeEquals(200);
-    $assert->pageTextContains("$entityTypeId created by Scheduler Editor for publishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Editor for unpublishing");
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Viewer for publishing");
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Viewer for unpublishing");
-
-    // Access another user's scheduled content tab. This should not be possible
-    // and will give "403 Access Denied".
-    $this->drupalGet("user/{$this->schedulerViewer->id()}/$url_end");
-    $assert->statusCodeEquals(403);
-
-    // Try to access a user's own scheduled content tab when that user only has
-    // 'view scheduled {type}' and not 'schedule publishing of {type}'. This is
-    // allowed and should give "200 OK" and show the users scheduled items.
-    $this->drupalLogin($this->schedulerViewer);
-    $this->drupalGet("user/{$this->schedulerViewer->id()}/$url_end");
-    $assert->statusCodeEquals(200);
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Editor for publishing");
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Editor for unpublishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for publishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for unpublishing");
-
-    // Access another user's scheduled content tab. This should not be possible
-    // and will give "403 Access Denied".
-    $this->drupalGet("user/{$this->schedulerEditor->id()}/$url_end");
-    $assert->statusCodeEquals(403);
-
-    // Log in as Admin who has 'access user profiles' permission and access the
-    // user who can schedule content. This is allowed and the content just for
-    // that user should be listed.
-    $this->drupalLogin($this->adminUser);
-    $this->drupalGet("user/{$this->schedulerEditor->id()}/$url_end");
-    $assert->statusCodeEquals(200);
-    $assert->pageTextContains("$entityTypeId created by Scheduler Editor for publishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Editor for unpublishing");
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Viewer for publishing");
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Viewer for unpublishing");
-
-    // Try to access the scheduled tab for a user who cannot schedule content
-    // themselves but can view their scheduled content if scheduled by someone
-    // else. This should give "200 OK" and the scheduled items will be shown.
-    $this->drupalGet("user/{$this->schedulerViewer->id()}/$url_end");
-    $assert->statusCodeEquals(200);
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Editor for publishing");
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Editor for unpublishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for publishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for unpublishing");
-  }
-
-  /**
-   * Provides test data for user view test.
-   *
-   * There is no user view for scheduled Commerce Products or Taxonomy Terms so
-   * these entity types are removed from the user view test.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id].
-   */
-  public function dataViewScheduledContentUser() {
-    $data = $this->dataStandardEntityTypes();
-    unset($data['#commerce_product']);
-    unset($data['#taxonomy_term']);
-    return $data;
-  }
-
-  /**
-   * Tests the scheduled content overview.
-   *
-   * @dataProvider dataStandardEntityTypes()
-   */
-  public function testViewScheduledContentOverview($entityTypeId, $bundle) {
-    $this->createScheduledItems($entityTypeId, $bundle);
-    $scheduled_url = $this->adminUrl('scheduled', $entityTypeId, $bundle);
-    $assert = $this->assertSession();
-
-    // Try to access the scheduled content overview as an anonymous visitor.
-    $this->drupalGet($scheduled_url);
-    $assert->statusCodeEquals(403);
-
-    // Try to access the scheduled content overview as a user who has no
-    // scheduler permissions. This should not be possible.
-    $this->drupalLogin($this->webUser);
-    $this->drupalGet($scheduled_url);
-    $assert->statusCodeEquals(403);
-
-    // Try to access the scheduled content overview as a user with only
-    // 'schedule publishing of {type}' permission. This should not be possible.
-    $this->drupalLogin($this->schedulerEditor);
-    $this->drupalGet($scheduled_url);
-    $assert->statusCodeEquals(403);
-
-    // Access the scheduled content overview as a user who only has
-    // 'view scheduled {type}' permission. This is allowed and they should see
-    // the scheduled content for all users.
-    $this->drupalLogin($this->schedulerViewer);
-    $this->drupalGet($scheduled_url);
-    $assert->statusCodeEquals(200);
-    // Unpublished nodes, media items and taxonomy terms by other users are
-    // listed but products are not. Therefore do not check for the unpublished
-    // product by Scheduler Editor here.
-    if ($entityTypeId != 'commerce_product') {
-      $assert->pageTextContains("$entityTypeId created by Scheduler Editor for publishing");
-    }
-    $assert->pageTextContains("$entityTypeId created by Scheduler Editor for unpublishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for publishing");
-    $assert->pageTextContains("$entityTypeId created by Scheduler Viewer for unpublishing");
-
-    // Disable the scheduled view.
-    $view_ids = [
-      'node' => 'scheduler_scheduled_content',
-      'media' => 'scheduler_scheduled_media',
-      'commerce_product' => 'scheduler_scheduled_commerce_product',
-      'taxonomy_term' => 'scheduler_scheduled_taxonomy_term',
-    ];
-    $view = $this->container->get('entity_type.manager')->getStorage('view')->load($view_ids[$entityTypeId]);
-    $view->disable()->save();
-
-    // Attempt to view the scheduled entity page. Interactively this gives a
-    // '404 page not found' error, but in phpunit it is served with a 200 code.
-    // However the page is empty so we can check that the content is not shown.
-    $this->drupalGet($scheduled_url);
-    $assert->pageTextNotContains("$entityTypeId created by Scheduler Editor for unpublishing");
-
-    // Log in as admin and check that access to the overview page is unaffected.
-    $this->drupalLogin($this->adminUser);
-    $this->drupalGet($this->adminUrl('collection', $entityTypeId, $bundle));
-    $assert->statusCodeEquals(200);
-    $assert->pageTextContains("$entityTypeId created by Scheduler Editor for unpublishing");
-
-    // Delete the view and check again that the overview remains accessible.
-    $view->delete();
-    $this->drupalGet($this->adminUrl('collection', $entityTypeId, $bundle));
-    $assert->statusCodeEquals(200);
-    $assert->pageTextContains("$entityTypeId created by Scheduler Editor for unpublishing");
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerWorkbenchModerationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerWorkbenchModerationTest.php
deleted file mode 100644
index dcb03f96571243ea1504dc6c3b3cdacfcdf5ed96..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Functional/SchedulerWorkbenchModerationTest.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Functional;
-
-/**
- * Tests Scheduler with Workbench Moderation installed.
- *
- * @group scheduler
- */
-class SchedulerWorkbenchModerationTest extends SchedulerBrowserTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp(): void {
-    parent::setUp();
-    // This test class is "optional" and will be run if the workbench_moderation
-    // modules are available. This allows testing with Drupal 9 but also will
-    // not fail with Drupal 10, where the workbench moderation modules are not
-    // compatible. See https://www.drupal.org/project/scheduler/issues/3314267
-    $modulesList = \Drupal::service('extension.list.module')->getList();
-    if (!isset($modulesList['workbench_moderation']) || !isset($modulesList['workbench_moderation_actions'])) {
-      $this->markTestSkipped('Skipping test because the workbench moderation module(s) are not available.');
-    }
-    else {
-      // The workbench_moderation module is available so install it.
-      // workbench_moderation_actions is installed later.
-      \Drupal::service('module_installer')->install(['workbench_moderation']);
-    }
-  }
-
-  /**
-   * Helper function to test publishing and unpublishing via cron.
-   */
-  public function schedulingWithWorkbenchModeration($type) {
-    $this->drupalLogin($this->schedulerUser);
-
-    // Create a node that is scheduled for publishing.
-    $settings = [
-      'publish_on' => strtotime('-1 day'),
-      'status' => FALSE,
-      'type' => $type,
-      'title' => "{$type} for publishing",
-    ];
-    $node = $this->drupalCreateNode($settings);
-
-    // Run cron and check that the node has been published successfully.
-    scheduler_cron();
-    $this->nodeStorage->resetCache([$node->id()]);
-    $node = $this->nodeStorage->load($node->id());
-    $this->assertTrue($node->isPublished(), "The node should be published after cron");
-
-    // Set a date for unpublishing the node.
-    $node->set('unpublish_on', strtotime('-1 day'))->save();
-
-    // Run cron and check that the node has been unpublished successfully.
-    scheduler_cron();
-    $this->nodeStorage->resetCache([$node->id()]);
-    $node = $this->nodeStorage->load($node->id());
-    $this->assertFalse($node->isPublished(), "The node should be unpublished after cron");
-  }
-
-  /**
-   * Test when only workbench_moderation is installed.
-   */
-  public function testWorkbenchModerationOnly() {
-    // Test with a node type that is not included in a moderation workflow.
-    $this->schedulingWithWorkbenchModeration($this->type);
-  }
-
-  /**
-   * Test when workbench_moderation_actions is also installed.
-   */
-  public function testWorkbenchModerationWithWorkbenchModerationActions() {
-    // Install workbench_moderation_actions and run the same test as above.
-    \Drupal::service('module_installer')->install(['workbench_moderation_actions']);
-    $this->schedulingWithWorkbenchModeration($this->type);
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php
index 2c0ae45cd4dcd75e8279ba7afa996c0a352fa847..7bf365fca28ea83ad9cc3cce44dce1f40e9ef008 100644
--- a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php
+++ b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptDefaultTimeTest.php
@@ -5,7 +5,7 @@
 /**
  * Tests the JavaScript functionality for default dates.
  *
- * @group scheduler_js
+ * @group scheduler
  */
 class SchedulerJavascriptDefaultTimeTest extends SchedulerJavascriptTestBase {
 
@@ -30,7 +30,7 @@ protected function setUp(): void {
     $this->drupalLogin($this->schedulerUser);
     $this->drupalGet('node/add/' . $this->type);
     $page = $this->getSession()->getPage();
-    $title = "Add a {$this->typeName} to determine the date-picker format";
+    $title = 'Date format test ' . $this->randomString(12);
     $page->fillField('edit-title-0-value', $title);
     $page->clickLink('Scheduling options');
     // Set the date using a day and month which could be correctly interpreted
@@ -41,7 +41,6 @@ protected function setUp(): void {
     $page->fillField('edit-publish-on-0-value-time', '06:00:00pm');
     $page->pressButton('Save');
     $node = $this->drupalGetNodeByTitle($title);
-    $this->drupalGet('node/' . $node->id());
     // If the saved month is 2 then the format is d/m/Y, otherwise it is m/d/Y.
     $this->datepickerFormat = (date('n', $node->publish_on->value) == 2 ? 'd/m/Y' : 'm/d/Y');
   }
@@ -51,10 +50,8 @@ protected function setUp(): void {
    *
    * @dataProvider dataTimeWhenSchedulingIsRequired()
    */
-  public function testTimeWhenSchedulingIsRequired($entityTypeId, $bundle, $field) {
+  public function testTimeWhenSchedulingIsRequired($field) {
     $config = $this->config('scheduler.settings');
-    $titleField = $this->titleField($entityTypeId);
-    $entityType = $this->entityTypeObject($entityTypeId);
 
     // This test is only relevant when the configuration allows a date only with
     // a default time specified. Testing with 'allow_date_only' = false is
@@ -70,39 +67,36 @@ public function testTimeWhenSchedulingIsRequired($entityTypeId, $bundle, $field)
     $scheduling_time = new \DateTime();
     $scheduling_time->add(new \DateInterval('P1D'))->setTime(19, 30, 20);
 
-    // Node and Media entities are revisionable and the 'Revision Information'
-    // tab is the default active one, so needs a click on 'Scheduling Options'.
-    // Products do not have this link, so the click would fail. A simple way to
-    // resolve this is display the scheduler options as a separate fieldset.
-    $entityType->setThirdPartySetting('scheduler', 'fields_display_mode', 'fieldset')->save();
-
     foreach ([TRUE, FALSE] as $required) {
-      // Set the publish_on/unpublish_on required setting.
-      $entityType->setThirdPartySetting('scheduler', $field . '_required', $required)->save();
+      // Set the publish-on/unpublish-on date to the $required setting.
+      $this->nodetype->setThirdPartySetting('scheduler', $field . '_required', $required)->save();
 
-      // Create an entity.
-      $this->drupalGet($this->entityAddUrl($entityTypeId, $bundle));
+      // Create a node.
+      $this->drupalGet('node/add/' . $this->type);
       $page = $this->getSession()->getPage();
-      $title = ucfirst($field) . ($required ? ' required' : ' not required') . ', datepickerFormat = ' . $this->datepickerFormat;
-      $page->fillField("edit-{$titleField}-0-value", $title);
+
+      $title = ucfirst($field) . ($required ? ' required ' : ' not required ') . $this->randomString(12);
+      $page->fillField('edit-title-0-value', $title);
+      $page->fillField('edit-body-0-value', 'datepickerFormat = ' . $this->datepickerFormat);
+      $page->clickLink('Scheduling options');
       if ($required) {
         // Fill in the date value but do nothing with the time field.
         $page->fillField('edit-' . $field . '-on-0-value-date', $scheduling_time->format($this->datepickerFormat));
       }
       $page->pressButton('Save');
 
-      // Test that the entity has saved properly.
-      $this->assertSession()->pageTextMatches($this->entitySavedMessage($entityTypeId, $title));
+      // Test that the content has saved properly.
+      $this->assertSession()->pageTextContains(sprintf('%s %s has been created', $this->typeName, $title));
 
-      $entity = $this->getEntityByTitle($entityTypeId, $title);
-      $this->assertNotEmpty($entity, 'The entity object can be found by title');
+      $node = $this->drupalGetNodeByTitle($title);
+      $this->assertNotEmpty($node, 'The node could not be found');
       if ($required) {
         // Check that the scheduled date and time are correct.
-        $this->assertEquals($scheduling_time->getTimestamp(), (int) $entity->{$field . '_on'}->value);
+        $this->assertEquals($scheduling_time->getTimestamp(), (int) $node->{$field . '_on'}->value);
       }
       else {
         // Check that no scheduled date was stored.
-        $this->assertEmpty($entity->{$field . '_on'}->value);
+        $this->assertEmpty($node->{$field . '_on'}->value);
       }
     }
   }
@@ -110,19 +104,14 @@ public function testTimeWhenSchedulingIsRequired($entityTypeId, $bundle, $field)
   /**
    * Provides data for testTimeWhenSchedulingIsRequired().
    *
-   * The data in dataStandardEntityTypes() is expanded to test each entity type
-   * with each of the scheduler date fields.
-   *
    * @return array
-   *   Each array item has the values: [entity type id, bundle id, field name].
+   *   The test data.
    */
   public function dataTimeWhenSchedulingIsRequired() {
-    $data = [];
-    foreach ($this->dataStandardEntityTypes() as $key => $values) {
-      $data["$key-1"] = array_merge($values, ['publish']);
-      $data["$key-2"] = array_merge($values, ['unpublish']);
-    }
-    return $data;
+    return [
+      ['publish'],
+      ['unpublish'],
+    ];
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php
index 9747b39c8555900e29e451c62c9aeae247260652..0e92de9d76074e13de7dde6cd718fdbd335c5c77 100644
--- a/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php
+++ b/web/modules/scheduler/tests/src/FunctionalJavascript/SchedulerJavascriptTestBase.php
@@ -3,20 +3,16 @@
 namespace Drupal\Tests\scheduler\FunctionalJavascript;
 
 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
-use Drupal\Tests\scheduler\Traits\SchedulerCommerceProductSetupTrait;
-use Drupal\Tests\scheduler\Traits\SchedulerMediaSetupTrait;
 use Drupal\Tests\scheduler\Traits\SchedulerSetupTrait;
-use Drupal\Tests\scheduler\Traits\SchedulerTaxonomyTermSetupTrait;
 
 /**
  * Base class for Scheduler javascript tests.
+ *
+ * @group scheduler
  */
 abstract class SchedulerJavascriptTestBase extends WebDriverTestBase {
 
-  use SchedulerCommerceProductSetupTrait;
-  use SchedulerMediaSetupTrait;
   use SchedulerSetupTrait;
-  use SchedulerTaxonomyTermSetupTrait;
 
   /**
    * The standard modules to load for all javascript tests.
@@ -25,12 +21,7 @@ abstract class SchedulerJavascriptTestBase extends WebDriverTestBase {
    *
    * @var array
    */
-  protected static $modules = [
-    'scheduler',
-    'media',
-    'commerce_product',
-    'taxonomy',
-  ];
+  protected static $modules = ['scheduler'];
 
   /**
    * The profile to install as a basis for testing.
@@ -49,20 +40,8 @@ abstract class SchedulerJavascriptTestBase extends WebDriverTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    // Call the common set-up functions defined in the traits.
+    // Call the common set-up function defined in the trait.
     $this->schedulerSetUp();
-    // $this->getName() includes the test class and the dataProvider key. We can
-    // use this to save time and resources by avoiding calls to the media and
-    // product setup functions when they are not needed.
-    if (stristr($this->getName(), 'media')) {
-      $this->schedulerMediaSetUp();
-    }
-    if (stristr($this->getName(), 'product')) {
-      $this->SchedulerCommerceProductSetUp();
-    }
-    if (stristr($this->getName(), 'taxonomy')) {
-      $this->SchedulerTaxonomyTermSetup();
-    }
   }
 
   /**
diff --git a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerConfigTest.php b/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerConfigTest.php
deleted file mode 100644
index a3c118828fdc6f13e398ad3658ee29636f5abdac..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerConfigTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Kernel;
-
-/**
- * Tests the migration of Drupal 7 scheduler configuration.
- *
- * @group scheduler_kernel
- */
-class MigrateSchedulerConfigTest extends MigrateSchedulerTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    $this->loadFixture(implode(DIRECTORY_SEPARATOR, [
-      DRUPAL_ROOT,
-      \Drupal::service('extension.list.module')->getPath('scheduler'),
-      'tests',
-      'fixtures',
-      'scheduler_config.php',
-    ]));
-    $this->installConfig(['scheduler']);
-  }
-
-  /**
-   * Tests the migration of Scheduler global settings.
-   */
-  public function testGlobalSettingsMigration() {
-    $config_before = $this->config('scheduler.settings');
-    $this->assertFalse($config_before->get('allow_date_only'));
-    $this->assertSame('00:00:00', $config_before->get('default_time'));
-    $this->assertFalse($config_before->get('hide_seconds'));
-
-    // See /migrations/d7_scheduler_settings.yml.
-    $this->executeMigration('d7_scheduler_settings');
-
-    $config_after = $this->config('scheduler.settings');
-    $this->assertTrue($config_after->get('allow_date_only'));
-    $this->assertSame('00:00:38', $config_after->get('default_time'));
-    $this->assertTrue($config_after->get('hide_seconds'));
-
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerDataTest.php b/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerDataTest.php
deleted file mode 100644
index 5313cf48a9dac3f12a7e757cf24b7fb8b1fbe33d..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerDataTest.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Kernel;
-
-use Drupal\node\Entity\Node;
-
-/**
- * Tests the migration of Drupal 7 scheduler data.
- *
- * @group scheduler_kernel
- */
-class MigrateSchedulerDataTest extends MigrateSchedulerTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getFixtureFilePath() {
-    return implode(DIRECTORY_SEPARATOR, [
-      \Drupal::service('extension.list.module')->getPath('scheduler'),
-      'tests',
-      'fixtures',
-      'scheduler_data.php',
-    ]);
-  }
-
-  /**
-   * Tests the migration of Scheduler data into node.
-   */
-  public function testSchedulerDataMigration() {
-    $this->installConfig(['node']);
-    $this->installEntitySchema('node');
-    $this->executeMigrations([
-      'd7_node_type',
-      'd7_node_complete',
-    ]);
-    $nodes = Node::loadMultiple([1, 2, 3]);
-    $this->assertSame(['1647751855', '1647838255'], [
-      $nodes[1]->publish_on->value,
-      $nodes[1]->unpublish_on->value,
-    ]);
-    $this->assertSame(['1647579055', NULL], [
-      $nodes[2]->publish_on->value,
-      $nodes[2]->unpublish_on->value,
-    ]);
-    $this->assertSame([NULL, NULL], [
-      $nodes[3]->publish_on->value,
-      $nodes[3]->unpublish_on->value,
-    ]);
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerNodeTypeConfigTest.php b/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerNodeTypeConfigTest.php
deleted file mode 100644
index 349e43b3e18a6dd8b8b525db3974bb71545711f3..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerNodeTypeConfigTest.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Kernel;
-
-use Drupal\node\Entity\NodeType;
-
-/**
- * Tests the migration of Drupal 7 Scheduler node type settings.
- *
- * @group scheduler_kernel
- */
-class MigrateSchedulerNodeTypeConfigTest extends MigrateSchedulerTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = ['menu_ui'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    $this->loadFixture(implode(DIRECTORY_SEPARATOR, [
-      DRUPAL_ROOT,
-      \Drupal::service('extension.list.module')->getPath('scheduler'),
-      'tests',
-      'fixtures',
-      'node_type_config.php',
-    ]));
-    $this->installConfig(['scheduler']);
-  }
-
-  /**
-   * Tests the migration of Scheduler settings per node type.
-   */
-  public function testNodeTypeSettingsMigration() {
-    $this->migrateContentTypes();
-    $article_config = NodeType::load('article');
-    $this->assertEquals([
-      'expand_fieldset' => 'when_required',
-      'publish_enable' => TRUE,
-      'publish_past_date' => 'error',
-      'publish_required' => TRUE,
-      'publish_revision' => TRUE,
-      'publish_touch' => FALSE,
-      'unpublish_enable' => TRUE,
-      'unpublish_required' => TRUE,
-      'unpublish_revision' => TRUE,
-      'fields_display_mode' => 'vertical_tab',
-    ], $article_config->get('third_party_settings')['scheduler']);
-
-    $page_config = NodeType::load('page');
-    $this->assertEquals([
-      'expand_fieldset' => 'always',
-      'publish_enable' => TRUE,
-      'publish_past_date' => 'publish',
-      'publish_required' => FALSE,
-      'publish_revision' => FALSE,
-      'publish_touch' => TRUE,
-      'unpublish_enable' => FALSE,
-      'unpublish_required' => FALSE,
-      'unpublish_revision' => FALSE,
-      'fields_display_mode' => 'fieldset',
-    ], $page_config->get('third_party_settings')['scheduler']);
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerTestBase.php b/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerTestBase.php
deleted file mode 100644
index 2b8da5fdfb13a3e0d995d521d104644e4af1cdb7..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Kernel/MigrateSchedulerTestBase.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Kernel;
-
-use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
-
-/**
- * Base class for testing the migration of Drupal 7 configuration and data.
- *
- * @group scheduler_kernel
- */
-class MigrateSchedulerTestBase extends MigrateDrupal7TestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'node',
-    'scheduler',
-    'text',
-    'views',
-  ];
-
-}
diff --git a/web/modules/scheduler/tests/src/Traits/SchedulerCommerceProductSetupTrait.php b/web/modules/scheduler/tests/src/Traits/SchedulerCommerceProductSetupTrait.php
deleted file mode 100644
index 924b37128ca79f50493be001d0a844a03697b314..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Traits/SchedulerCommerceProductSetupTrait.php
+++ /dev/null
@@ -1,204 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Traits;
-
-/**
- * Additional setup trait for Scheduler tests that use Commerce Product.
- */
-trait SchedulerCommerceProductSetupTrait {
-
-  /**
-   * The internal name of the standard product type for testing.
-   *
-   * Use the pre-existing 'default' product type. This is a short-cut.
-   *
-   * @var string
-   */
-  protected $productTypeName = 'test_product';
-
-  /**
-   * The readable label of the standard product type for testing.
-   *
-   * @var string
-   */
-  protected $productTypeLabel = 'Test Product';
-
-  /**
-   * The product type object which is enabled for scheduling.
-   *
-   * @var Drupal\commerce_product\Entity\ProductType
-   */
-  protected $productType;
-
-  /**
-   * The default commerce store to which all products are added.
-   *
-   * @var Drupal\commerce_store\Entity\Store
-   */
-  protected $store;
-
-  /**
-   * The internal name of the product type not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerProductTypeName = 'non_enabled_product';
-
-  /**
-   * The readable label of the product type not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerProductTypeLabel = 'Non-scheduler Product';
-
-  /**
-   * The product type object which is not enabled for scheduling.
-   *
-   * @var Drupal\commerce_product\Entity\ProductType
-   */
-  protected $nonSchedulerProductType;
-
-  /**
-   * The product entity storage.
-   *
-   * Is this really needed now that we can use $this->entityStorageObject() ?
-   *
-   * @var Drupal\commerce\CommerceContentEntityStorage
-   */
-  protected $productStorage;
-
-  /**
-   * Set common properties, define content types and create users.
-   */
-  public function schedulerCommerceProductSetUp() {
-
-    /** @var Store $store */
-    $this->store = $this->entityStorageObject('commerce_store')->create([
-      'type' => 'online',
-      'name' => 'My Test Store',
-    ]);
-    $this->store->save();
-
-    $product_type_storage = $this->container->get('entity_type.manager')->getStorage('commerce_product_type');
-
-    // Create a test product type that is enabled for scheduling.
-    /** @var Drupal\commerce_product\Entity\ProductType $productType */
-    $this->productType = $product_type_storage->create([
-      'id' => $this->productTypeName,
-      'label' => $this->productTypeLabel,
-      'variationType' => 'default',
-    ]);
-
-    // Add scheduler functionality to the product type, then save.
-    $this->productType->setThirdPartySetting('scheduler', 'publish_enable', TRUE)
-      ->setThirdPartySetting('scheduler', 'unpublish_enable', TRUE)
-      ->save();
-
-    // Enable the scheduler fields in the default form display, mimicking what
-    // would be done if the entity bundle had been enabled via admin UI.
-    $this->container->get('entity_display.repository')
-      ->getFormDisplay('commerce_product', $this->productTypeName)
-      ->setComponent('publish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->setComponent('unpublish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->save();
-
-    // Add the body field using the existing commerce_product function.
-    commerce_product_add_body_field($this->productType);
-
-    // Create a test product type which is not enabled for scheduling.
-    /** @var Drupal\commerce_product\Entity\ProductType $nonSchedulerProductType */
-    $this->nonSchedulerProductType = $product_type_storage->create([
-      'id' => $this->nonSchedulerProductTypeName,
-      'label' => $this->nonSchedulerProductTypeLabel,
-      'variationType' => 'default',
-    ]);
-    // Requires a separate save, not part of the create() above, if not doing
-    // any other save() on the product type.
-    $this->nonSchedulerProductType->save();
-
-    /** @var Drupal\commerce\CommerceContentEntityStorage $productStorage */
-    $this->productStorage = $this->container->get('entity_type.manager')->getStorage('commerce_product');
-
-    // Add extra permisssions to the role assigned to the adminUser.
-    $this->addPermissionsToUser($this->adminUser, [
-      'create ' . $this->productTypeName . ' commerce_product',
-      'update any ' . $this->productTypeName . ' commerce_product',
-      'delete any ' . $this->productTypeName . ' commerce_product',
-      'create ' . $this->nonSchedulerProductTypeName . ' commerce_product',
-      'update any ' . $this->nonSchedulerProductTypeName . ' commerce_product',
-      'delete any ' . $this->nonSchedulerProductTypeName . ' commerce_product',
-      'administer commerce_product_type',
-      // 'administer commerce_store' is needed to see and use any store, i.e
-      // cannot add a product without this. Is it a bug?
-      'administer commerce_store',
-      'access commerce_product overview',
-      'view own unpublished commerce_product',
-      'schedule publishing of commerce_product',
-      'view scheduled commerce_product',
-    ]);
-
-    // Add extra permisssions to the role assigned to the schedulerUser.
-    $this->addPermissionsToUser($this->schedulerUser, [
-      'create ' . $this->productTypeName . ' commerce_product',
-      'update any ' . $this->productTypeName . ' commerce_product',
-      'delete any ' . $this->productTypeName . ' commerce_product',
-      // 'administer commerce_store' is needed to see and use any store, i.e
-      // cannot add a product without this. Is it a bug?
-      'administer commerce_store',
-      'view own unpublished commerce_product',
-      'schedule publishing of commerce_product',
-    ]);
-  }
-
-  /**
-   * Creates a product entity.
-   *
-   * @param array $values
-   *   The values to use for the entity.
-   *
-   * @return Drupal\commerce_product\Entity\ProductInterface
-   *   The created product object.
-   */
-  public function createProduct(array $values = []) {
-    // Provide defaults for the critical values.
-    $values += [
-      'type' => $this->productTypeName,
-      'title' => $this->randomstring(12),
-    ];
-    /** @var \Drupal\commerce_product\ProductInterface $product */
-    $product = $this->productStorage->create($values);
-    $product->save();
-    return $product;
-  }
-
-  /**
-   * Gets a product from storage.
-   *
-   * For nodes, there is drupalGetNodeByTitle() but nothing similar exists to
-   * help Product testing. See getMediaItem for more details.
-   *
-   * @param string $name
-   *   Optional name text to match on. If given and no match, returns NULL.
-   *   If no $name is given then returns the product with the highest id value.
-   *
-   * @return \Drupal\commerce_product\Entity\ProductInterface
-   *   The commerce product object.
-   */
-  public function getProduct(string $name = NULL) {
-    $query = $this->productStorage->getQuery()
-      ->accessCheck(FALSE)
-      ->sort('product_id', 'DESC');
-    if (!empty($name)) {
-      $query->condition('title', $name);
-    }
-    $result = $query->execute();
-    if (count($result)) {
-      $id = reset($result);
-      return $this->productStorage->load($id);
-    }
-    else {
-      return NULL;
-    }
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Traits/SchedulerMediaSetupTrait.php b/web/modules/scheduler/tests/src/Traits/SchedulerMediaSetupTrait.php
deleted file mode 100644
index 926bc12dcba76c39ca798765bc8efc52031e02e7..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Traits/SchedulerMediaSetupTrait.php
+++ /dev/null
@@ -1,200 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Traits;
-
-use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
-
-/**
- * Additional setup trait for Scheduler tests that use Media.
- *
- * This builds on the standard SchedulerSetupTrait.
- */
-trait SchedulerMediaSetupTrait {
-
-  use MediaTypeCreationTrait;
-
-  /**
-   * The internal name of the standard media type created for testing.
-   *
-   * @var string
-   */
-  protected $mediaTypeName = 'test_video';
-
-  /**
-   * The readable label of the standard media type created for testing.
-   *
-   * @var string
-   */
-  protected $mediaTypeLabel = 'Test Video';
-
-  /**
-   * The media type object which is enabled for scheduling.
-   *
-   * @var \Drupal\media\MediaTypeInterface
-   */
-  protected $mediaType;
-
-  /**
-   * The internal name of the media type not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerMediaTypeName = 'test_audio_not_enabled';
-
-  /**
-   * The readable label of the media type not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerMediaTypeLabel = 'Test Audio - not for scheduling';
-
-  /**
-   * The media type object which is not enabled for scheduling.
-   *
-   * @var \Drupal\media\MediaTypeInterface
-   */
-  protected $nonSchedulerMediaType;
-
-  /**
-   * The media entity storage.
-   *
-   * @var \Drupal\Core\Entity\ContentEntityStorageInterface
-   */
-  protected $mediaStorage;
-
-  /**
-   * Set common properties, define content types and create users.
-   */
-  public function schedulerMediaSetUp() {
-
-    // Create a test media type for video that is enabled for scheduling.
-    /** @var \Drupal\media\Entity\MediaTypeInterface $mediaType */
-    $this->mediaType = $this->createMediaType('video_file', [
-      'id' => $this->mediaTypeName,
-      'label' => $this->mediaTypeLabel,
-    ]);
-
-    // Add scheduler functionality to the video media type.
-    $this->mediaType->setThirdPartySetting('scheduler', 'publish_enable', TRUE)
-      ->setThirdPartySetting('scheduler', 'unpublish_enable', TRUE)
-      ->save();
-
-    // Enable the scheduler fields in the default form display, mimicking what
-    // would be done if the entity bundle had been enabled via admin UI.
-    $this->container->get('entity_display.repository')
-      ->getFormDisplay('media', $this->mediaTypeName)
-      ->setComponent('publish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->setComponent('unpublish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->save();
-
-    // Create a test media type for audio which is not enabled for scheduling.
-    /** @var \Drupal\media\Entity\MediaTypeInterface $nonSchedulerMediaType */
-    $this->nonSchedulerMediaType = $this->createMediaType('audio_file', [
-      'id' => $this->nonSchedulerMediaTypeName,
-      'label' => $this->nonSchedulerMediaTypeLabel,
-    ]);
-
-    // Define mediaStorage for use in many tests.
-    /** @var MediaStorageInterface $mediaStorage */
-    $this->mediaStorage = $this->container->get('entity_type.manager')->getStorage('media');
-
-    // Add extra permisssions to the role assigned to the adminUser.
-    $this->addPermissionsToUser($this->adminUser, [
-      'create ' . $this->mediaTypeName . ' media',
-      'edit any ' . $this->mediaTypeName . ' media',
-      'delete any ' . $this->mediaTypeName . ' media',
-      'create ' . $this->nonSchedulerMediaTypeName . ' media',
-      'edit any ' . $this->nonSchedulerMediaTypeName . ' media',
-      'delete any ' . $this->nonSchedulerMediaTypeName . ' media',
-      'administer media types',
-      'access media overview',
-      'view own unpublished media',
-      'schedule publishing of media',
-      'view scheduled media',
-    ]);
-
-    // Add extra permisssions to the role assigned to the schedulerUser.
-    $this->addPermissionsToUser($this->schedulerUser, [
-      'create ' . $this->mediaTypeName . ' media',
-      'edit own ' . $this->mediaTypeName . ' media',
-      'delete own ' . $this->mediaTypeName . ' media',
-      'view own unpublished media',
-      'schedule publishing of media',
-    ]);
-
-    // By default, media items cannot be viewed directly, and the url media/mid
-    // gives a 404 not found. Changing this setting makes debugging the tests
-    // easier. It is also required for the meta information test.
-    $configFactory = $this->container->get('config.factory');
-    $configFactory->getEditable('media.settings')
-      ->set('standalone_url', TRUE)
-      ->save(TRUE);
-    $this->container->get('router.builder')->rebuild();
-
-    // Set the media file attachments to be optional not required, to simplify
-    // editing and saving media entities.
-    $configFactory->getEditable('field.field.media.test_video.field_media_video_file')
-      ->set('required', FALSE)
-      ->save(TRUE);
-    $configFactory->getEditable('field.field.media.test_audio_not_enabled.field_media_audio_file')
-      ->set('required', FALSE)
-      ->save(TRUE);
-  }
-
-  /**
-   * Creates a media entity.
-   *
-   * @param array $values
-   *   The values to use for the entity.
-   *
-   * @return \Drupal\media\MediaInterface
-   *   The created media object.
-   */
-  public function createMediaItem(array $values) {
-    // Provide defaults for the critical values. The title is stored in the
-    // 'name' field, so use 'title' when the 'name' is not defined, to allow
-    // the same calling $value parameter names as for Node.
-    $values += [
-      'bundle' => $this->mediaTypeName,
-      'name' => $values['title'] ?? $this->randomstring(12),
-    ];
-    /** @var \Drupal\media\MediaInterface $media */
-    $media = $this->mediaStorage->create($values);
-    $media->save();
-    return $media;
-  }
-
-  /**
-   * Gets a media item from storage.
-   *
-   * For nodes, there is drupalGetNodeByTitle() but nothing similar exists to
-   * help Media testing. But this function goes one better - if a name is given,
-   * then a match will be attempted on the name, and fail if none found. But if
-   * no name is supplied then the media entity with the highest id value (the
-   * newest item created) is returned, as this is often what is required.
-   *
-   * @param string $name
-   *   Optional name text to match on. If given and no match, returns NULL.
-   *   If no $name is given then returns the media with the highest id value.
-   *
-   * @return \Drupal\media\MediaInterface
-   *   The media object.
-   */
-  public function getMediaItem(string $name = NULL) {
-    $query = $this->mediaStorage->getQuery()
-      ->accessCheck(FALSE)
-      ->sort('mid', 'DESC');
-    if (!empty($name)) {
-      $query->condition('name', $name);
-    }
-    $result = $query->execute();
-    if (count($result)) {
-      $media_id = reset($result);
-      return $this->mediaStorage->load($media_id);
-    }
-    else {
-      return NULL;
-    }
-  }
-
-}
diff --git a/web/modules/scheduler/tests/src/Traits/SchedulerSetupTrait.php b/web/modules/scheduler/tests/src/Traits/SchedulerSetupTrait.php
index 0ab451227d6cf8764c74946136b76b39411aa3d7..476bbd4737d481b1dc72988a6e913f68a8ac51ee 100644
--- a/web/modules/scheduler/tests/src/Traits/SchedulerSetupTrait.php
+++ b/web/modules/scheduler/tests/src/Traits/SchedulerSetupTrait.php
@@ -2,9 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Traits;
 
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\Url;
-use Drupal\Tests\node\Traits\NodeCreationTrait;
 use Drupal\Tests\Traits\Core\CronRunTrait;
 
 /**
@@ -16,13 +13,6 @@ trait SchedulerSetupTrait {
 
   use CronRunTrait;
 
-  use NodeCreationTrait {
-    // Allow this trait to be used in Kernel tests (which do not use
-    // BrowserTestBase) and hence will not have these two functions.
-    getNodeByTitle as drupalGetNodeByTitle;
-    createNode as drupalCreateNode;
-  }
-
   // @todo Remove this when core 8.8 is the lowest supported version.
   // @see https://www.drupal.org/project/scheduler/issues/3136744
   use PhpunitCompatibilityCore87Trait;
@@ -46,14 +36,14 @@ trait SchedulerSetupTrait {
    *
    * @var string
    */
-  protected $type = 'testpage';
+  protected $type;
 
   /**
    * The readable name of the standard content type created for testing.
    *
    * @var string
    */
-  protected $typeName = 'Test Page';
+  protected $typeName;
 
   /**
    * The node type object.
@@ -62,26 +52,12 @@ trait SchedulerSetupTrait {
    */
   protected $nodetype;
 
-  /**
-   * The machine name of the content type which is not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerType = 'not_for_scheduler';
-
-  /**
-   * The readable name of content type which is not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerTypeName = 'Not For Scheduler';
-
   /**
    * The node type object which is not enabled for scheduling.
    *
    * @var \Drupal\node\Entity\NodeType
    */
-  protected $nonSchedulerNodeType;
+  protected $nonSchedulerNodetype;
 
   /**
    * The node storage object.
@@ -116,10 +92,12 @@ trait SchedulerSetupTrait {
    */
   public function schedulerSetUp() {
 
-    // Create a test content type using the type and name constants defined
-    // above. The tests should use $this->type and $this->typeName and not use
+    // Create a test content type with id 'testpage' and name 'Test Page'.
+    // The tests should use $this->type and $this->typeName and not use
     // $this->nodetype->get('type') or $this->nodetype->get('name'), nor have
     // the hard-coded strings 'testpage' or 'Test Page'.
+    $this->type = 'testpage';
+    $this->typeName = 'Test Page';
     /** @var NodeTypeInterface $nodetype */
     $this->nodetype = $this->drupalCreateContentType([
       'type' => $this->type,
@@ -131,19 +109,11 @@ public function schedulerSetUp() {
       ->setThirdPartySetting('scheduler', 'unpublish_enable', TRUE)
       ->save();
 
-    // Enable the scheduler fields in the default form display, mimicking what
-    // would be done if the entity bundle had been enabled via admin UI.
-    $this->container->get('entity_display.repository')
-      ->getFormDisplay('node', $this->type)
-      ->setComponent('publish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->setComponent('unpublish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->save();
-
     // The majority of tests use the standard Scheduler-enabled content type but
     // we also need a content type which is not enabled for Scheduler.
     $this->nonSchedulerNodeType = $this->drupalCreateContentType([
-      'type' => $this->nonSchedulerType,
-      'name' => $this->nonSchedulerTypeName,
+      'type' => 'not-for-scheduler',
+      'name' => 'Not For Scheduler',
     ]);
 
     // Define nodeStorage for use in many tests.
@@ -154,35 +124,34 @@ public function schedulerSetUp() {
     // rights on the test content type and all of the Scheduler permissions.
     // 'access site reports' is required for admin/reports/dblog.
     // 'administer site configuration' is required for admin/reports/status.
-    // 'administer content types' is required for admin/structure/types/manage.
     $this->adminUser = $this->drupalCreateUser([
       'access content',
       'access content overview',
       'access site reports',
       'administer nodes',
-      'administer content types',
       'administer site configuration',
       'create ' . $this->type . ' content',
       'edit any ' . $this->type . ' content',
       'delete any ' . $this->type . ' content',
-      'create ' . $this->nonSchedulerType . ' content',
-      'edit any ' . $this->nonSchedulerType . ' content',
+      'create ' . $this->nonSchedulerNodeType->id() . ' content',
+      'edit any ' . $this->nonSchedulerNodeType->id() . ' content',
+      'delete any ' . $this->nonSchedulerNodeType->id() . ' content',
       'view own unpublished content',
       'administer scheduler',
       'schedule publishing of nodes',
       'view scheduled content',
     ]);
-    $this->adminUser->set('name', 'Admolly the Admin user')->save();
 
     // Create an ordinary Scheduler user, with permission to create and schedule
     // content but not with administrator permissions.
     $this->schedulerUser = $this->drupalCreateUser([
       'create ' . $this->type . ' content',
       'edit own ' . $this->type . ' content',
+      'delete own ' . $this->type . ' content',
       'view own unpublished content',
       'schedule publishing of nodes',
+      'view scheduled content',
     ]);
-    $this->schedulerUser->set('name', 'Shelly the Scheduler user')->save();
 
     // Store the database connection for re-use in the actual tests.
     $this->database = $this->container->get('database');
@@ -195,413 +164,4 @@ public function schedulerSetUp() {
 
   }
 
-  /**
-   * Adds a set of permissions to an existing user.
-   *
-   * This avoids having to create new users when a test requires additional
-   * permissions, as that leads to having a list of existing permissions which
-   * has to be kept in sync with the standard user permissions.
-   *
-   * Each test user has two roles, 'authenticated' and one other randomly-named
-   * role assigned when the user is created, and unique to that user. This is
-   * the role to which these permissions are added.
-   *
-   * @param \Drupal\Core\Session\AccountInterface $user
-   *   The user object.
-   * @param array $permissions
-   *   The machine names of new permissions to add to the user's unique role.
-   */
-  public function addPermissionsToUser(AccountInterface $user, array $permissions) {
-    /** @var \Drupal\user\Entity\RoleStorageInterface $roleStorage */
-    $roleStorage = $this->container->get('entity_type.manager')->getStorage('user_role');
-    foreach ($user->getRoles() as $rid) {
-      // The user will have two roles, 'authenticated' and one other.
-      if ($rid != 'authenticated') {
-        $role = $roleStorage->load($rid);
-        foreach ($permissions as $permission) {
-          $role->grantPermission($permission);
-        }
-        $role->save();
-      }
-    }
-  }
-
-  /**
-   * Creates a test entity.
-   *
-   * This is called to generate a node, media or product entity, for tests that
-   * process all types of entities, either in loops or via a data provider.
-   *
-   * @param string $entityTypeId
-   *   The entity type - 'node', 'media', 'commerce_product' or 'taxonomy_term'.
-   * @param string $bundle
-   *   The name of the bundle. Optional, will default to $this->type for nodes
-   *   $this->mediaTypeName for media, or $this->productTypeName for products.
-   * @param array $values
-   *   Values for the new entity, passed through to the specific create method.
-   *   'title' can be used for all entity types, and will be converted to the
-   *   necessary property name.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface
-   *   The created entity object.
-   */
-  public function createEntity(string $entityTypeId, string $bundle = NULL, array $values = []) {
-
-    switch ($entityTypeId) {
-      case 'node':
-        // For nodes the field for bundle is called 'type'.
-        $values += ['type' => $bundle ?? $this->type];
-        $entity = $this->drupalCreateNode($values);
-        break;
-
-      case 'media':
-        $values += ['bundle' => $bundle ?? $this->mediaTypeName];
-        $entity = $this->createMediaItem($values);
-        break;
-
-      case 'commerce_product':
-        // For products the bundle field is 'type'.
-        $values += ['type' => $bundle ?? $this->productTypeName];
-        $entity = $this->createProduct($values);
-        break;
-
-      case 'taxonomy_term':
-        // For taxonomy terms, the bundle field is 'vid'.
-        $values += ['vid' => $bundle ?? $this->vocabularyId];
-        $entity = $this->createTaxonomyTerm($values);
-        break;
-
-      default:
-        // Incorrect parameter values.
-        throw new \Exception(sprintf('Unrecognised combination of entityTypeId "%s" and bundle "%s" passed to createEntity()', $entityTypeId, $bundle));
-
-    }
-    return $entity;
-  }
-
-  /**
-   * Gets an entity by title, a direct replacement of drupalGetNodeByTitle().
-   *
-   * This allows the same test code to be run for Nodes, Media and Products.
-   *
-   * @param string $entityTypeId
-   *   The machine id of the entity type - 'node', 'media', 'commerce_product'.
-   * @param string $title
-   *   The title to match with.
-   *
-   * @return mixed
-   *   Either a node object, media object, commerce_product object, or none.
-   */
-  public function getEntityByTitle(string $entityTypeId, string $title) {
-    switch ($entityTypeId) {
-      case 'node':
-        return $this->drupalGetNodeByTitle($title);
-
-      case 'media':
-        return $this->getMediaItem($title);
-
-      case 'commerce_product':
-        return $this->getProduct($title);
-
-      case 'taxonomy_term':
-        return $this->getTaxonomyTerm($title);
-
-      default:
-        // Incorrect parameter value.
-        throw new \Exception(sprintf('Unrecognised entityTypeId value "%s" passed to getEntityByTitle()', $entityTypeId));
-    }
-  }
-
-  /**
-   * Returns the stored entity type object from a type id and bundle id.
-   *
-   * This allows previous usages of $this->nodetype to be replaced by
-   * entityTypeObject($entityTypeId) or entityTypeObject($entityTypeId, $bundle)
-   * when expanding tests to cover Media and Product entities.
-   *
-   * @param string $entityTypeId
-   *   The machine id of the entity type - 'node', 'media', 'commerce_product'.
-   * @param string $bundle
-   *   The machine name of the bundle, for example 'testpage', 'test_video',
-   *   'not_for_scheduler', etc. Optional. Defaults to the enabled bundle. Also
-   *   accepts the fixed string 'non-enabled' to indicate the non-enabled bundle
-   *   for the entity type.
-   *
-   * @return \Drupal\Core\Entity\EntityTypeInterface
-   *   The stored entity type object.
-   */
-  public function entityTypeObject(string $entityTypeId, string $bundle = NULL) {
-    if (empty($bundle) || $bundle == 'non-enabled') {
-      $default_types = [
-        'node' => $this->type,
-        'media' => $this->mediaTypeName,
-        'commerce_product' => $this->productTypeName,
-        'taxonomy_term' => $this->vocabularyId,
-      ];
-      $non_enabled_types = [
-        'node' => $this->nonSchedulerType,
-        'media' => $this->nonSchedulerMediaTypeName,
-        'commerce_product' => $this->nonSchedulerProductTypeName,
-        'taxonomy_term' => $this->nonSchedulerVocabularyId,
-      ];
-      $bundle = (empty($bundle)) ? $default_types[$entityTypeId] : $non_enabled_types[$entityTypeId];
-    }
-    $entityTypeManager = $this->container->get('entity_type.manager');
-    $bundleEntityType = $entityTypeManager->getDefinition($entityTypeId)->getBundleEntityType();
-    if (!$entity_type = $entityTypeManager->getStorage($bundleEntityType)->load($bundle)) {
-      // Incorrect parameter values.
-      throw new \Exception(sprintf('Unrecognised combination of entityTypeId "%s" and bundle "%s" passed to entityTypeObject()', $entityTypeId, $bundle));
-    };
-    return $entity_type;
-  }
-
-  /**
-   * Returns the field name used for the "title" of an entity.
-   *
-   * @param string $entityTypeId
-   *   The machine id of the entity type.
-   *
-   * @return string
-   *   The name of the title field.
-   */
-  public function titleField(string $entityTypeId) {
-    switch ($entityTypeId) {
-      case 'node':
-      case 'commerce_product':
-        return 'title';
-
-      case 'media':
-      case 'taxonomy_term':
-        return 'name';
-
-      default:
-        // Incorrect parameter value.
-        throw new \Exception(sprintf('Unrecognised entityTypeId "%s" passed to titleField()', $entityTypeId));
-    }
-  }
-
-  /**
-   * Returns the field name used for the "body" of an entity.
-   *
-   * @param string $entityTypeId
-   *   The machine id of the entity type.
-   *
-   * @return string
-   *   The name of the body field.
-   */
-  public function bodyField(string $entityTypeId) {
-    switch ($entityTypeId) {
-      case 'node':
-      case 'commerce_product':
-        return 'body';
-
-      case 'taxonomy_term':
-        return 'description';
-
-      default:
-        // Incorrect parameter value.
-        throw new \Exception(sprintf('Unrecognised entityTypeId "%s" passed to bodyField()', $entityTypeId));
-    }
-  }
-
-  /**
-   * Returns the message that is shown when an entity is saved.
-   *
-   * @param string $entityTypeId
-   *   The machine id of the entity type.
-   * @param string $title
-   *   The title of the entity being checked.
-   *
-   * @return string
-   *   The text of the message to check, used in pageTextMatches() assertions.
-   */
-  public function entitySavedMessage(string $entityTypeId, string $title) {
-    switch ($entityTypeId) {
-      case 'node':
-        return '/' . preg_quote($title, '/') . ' has been (created|updated)/';
-
-      case 'media':
-        return '/' . preg_quote($title, '/') . ' has been (created|updated)/';
-
-      case 'commerce_product':
-        return '/The product ' . preg_quote($title, '/') . ' has been successfully saved/';
-
-      case 'taxonomy_term':
-        return '/(Created new|Updated) term ' . preg_quote($title, '/') . '/';
-
-      default:
-        // Incorrect parameter value.
-        throw new \Exception(sprintf('Unrecognised entityTypeId "%s" passed to entitySavedMessage()', $entityTypeId));
-    }
-  }
-
-  /**
-   * Returns the url for adding an entity, for use in drupalGet().
-   *
-   * @param string $entityTypeId
-   *   The machine id of the entity type - 'node', 'media', 'commerce_product'.
-   * @param string $bundle
-   *   The machine name of the bundle, for example 'testpage', 'test_video',
-   *   'not_for_scheduler', etc. Optional. Defaults to the enabled bundle. Also
-   *   accepts the fixed string 'non-enabled' to indicate the non-enabled bundle
-   *   for the entity type.
-   *
-   * @return \Drupal\Core\Url
-   *   The url object for adding the required entity.
-   */
-  public function entityAddUrl(string $entityTypeId, string $bundle = NULL) {
-    switch ($entityTypeId) {
-      case 'node':
-        $bundle = ($bundle == 'non-enabled') ? $this->nonSchedulerType : ($bundle ?? $this->type);
-        $route = 'node.add';
-        $type_parameter = 'node_type';
-        break;
-
-      case 'media':
-        $bundle = ($bundle == 'non-enabled') ? $this->nonSchedulerMediaTypeName : ($bundle ?? $this->mediaTypeName);
-        $route = 'entity.media.add_form';
-        $type_parameter = 'media_type';
-        break;
-
-      case 'commerce_product':
-        $bundle = ($bundle == 'non-enabled') ? $this->nonSchedulerProductTypeName : ($bundle ?? $this->productTypeName);
-        $route = 'entity.commerce_product.add_form';
-        $type_parameter = 'commerce_product_type';
-        break;
-
-      case 'taxonomy_term':
-        $bundle = ($bundle == 'non-enabled') ? $this->nonSchedulerVocabularyId : ($bundle ?? $this->vocabularyId);
-        $route = 'entity.taxonomy_term.add_form';
-        $type_parameter = 'taxonomy_vocabulary';
-        break;
-
-      default:
-        // Incorrect parameter values.
-        throw new \Exception(sprintf('Unrecognised combination of entityTypeId "%s" and bundle "%s" passed to entityAddUrl()', $entityTypeId, $bundle));
-    }
-    if (!$url = Url::fromRoute($route, [$type_parameter => $bundle])) {
-      // Incorrect parameter values.
-      throw new \Exception(sprintf('No url found for entityTypeId "%s" and bundle "%s" with route "%s" in entityAddUrl()', $entityTypeId, $bundle, $route));
-    }
-    return $url;
-  }
-
-  /**
-   * Returns the url for a specified page, entity type and optionally bundle.
-   *
-   * @param string $page
-   *   The page required - 'collection', 'scheduled', 'generate', etc.
-   * @param string $entityTypeId
-   *   The machine id of the entity type - 'node', 'media', 'commerce_product'.
-   * @param string $bundle
-   *   (optional) The machine name of the bundle.
-   *
-   * @return string
-   *   The url for the required page.
-   */
-  public function adminUrl($page, $entityTypeId, $bundle = NULL) {
-    // $bundle_id will be 'node_type', 'media_type', 'commerce_product_type',
-    // 'taxonomy_vocabulary' etc.
-    $bundle_id = $this->container->get('entity_type.manager')->getDefinition($entityTypeId)->getBundleEntityType();
-
-    $urls = [
-      'collection' => [
-        'node' => Url::fromRoute('system.admin_content'),
-        'taxonomy_term' => Url::fromRoute('entity.taxonomy_vocabulary.overview_form', [$bundle_id => $bundle]),
-        'default' => Url::fromRoute("entity.{$entityTypeId}.collection"),
-      ],
-      'scheduled' => [
-        'node' => Url::fromRoute('view.scheduler_scheduled_content.overview'),
-        'default' => Url::fromRoute("view.scheduler_scheduled_{$entityTypeId}.overview"),
-      ],
-      'generate' => [
-        'node' => Url::fromRoute('devel_generate.content'),
-        'media' => Url::fromRoute('devel_generate.media'),
-        'taxonomy_term' => Url::fromRoute('devel_generate.term'),
-      ],
-      'bundle_add' => [
-        'node' => Url::fromRoute('node.type_add'),
-        'default' => Url::fromRoute("entity.{$bundle_id}.add_form"),
-      ],
-      'bundle_edit' => [
-        'default' => Url::fromRoute("entity.{$bundle_id}.edit_form", [$bundle_id => $bundle]),
-      ],
-      'bundle_form_display' => [
-        'default' => Url::fromRoute("entity.entity_form_display.{$entityTypeId}.default", [$bundle_id => $bundle]),
-      ],
-    ];
-
-    $url = $urls[$page][$entityTypeId] ?? ($urls[$page]['default'] ?? NULL);
-    if (empty($url)) {
-      // Incorrect parameter values.
-      throw new \Exception(sprintf('Unrecognised combination of page "%s", entityTypeId "%s" and bundle "%s" passed to adminUrl()', $page, $entityTypeId, $bundle));
-    }
-    return $url;
-  }
-
-  /**
-   * Returns the storage object of the entity type passed by string.
-   *
-   * This allows previous usage of the hard-coded $this->nodeStorage to be
-   * replaced with $this->entityStorageObject($entityTypeId) when expanding the
-   * tests to cover media and product entity types.
-   *
-   * @param string $entityTypeId
-   *   The machine id of the entity type.
-   *
-   * @return \Drupal\Core\Entity\ContentEntityStorageInterface
-   *   The entity storage object.
-   */
-  public function entityStorageObject(string $entityTypeId) {
-    return $this->container->get('entity_type.manager')->getStorage($entityTypeId);
-  }
-
-  /**
-   * Deletes an action that is associated with a scheduler entity type.
-   */
-  public function deleteAction($plugin_id, $process) {
-    $plugin = $this->container->get('plugin.manager.scheduler')->createInstance($plugin_id);
-    $action_id = ($process == 'publish' ? $plugin->publishAction() : $plugin->unpublishAction());
-    if ($loaded_action = $this->container->get('entity_type.manager')->getStorage('action')->load($action_id)) {
-      // To avoid error, only call delete if the action exists and was loaded.
-      $loaded_action->delete();
-    }
-  }
-
-  /**
-   * Provides test data containing the standard entity types.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id]. The array
-   *   key is #entity_type_id, to allow easy removal of unwanted rows later.
-   */
-  public function dataStandardEntityTypes() {
-    // The data provider has access to $this where the values are set in the
-    // property definition.
-    $data = [
-      '#node' => ['node', $this->type],
-      '#media' => ['media', $this->mediaTypeName],
-      '#commerce_product' => ['commerce_product', $this->productTypeName],
-      '#taxonomy_term' => ['taxonomy_term', $this->vocabularyId],
-    ];
-    return $data;
-  }
-
-  /**
-   * Provides test data containing the non-enabled entity types.
-   *
-   * @return array
-   *   Each array item has the values: [entity type id, bundle id]. The array
-   *   key is #entity_type_id, to allow easy removal of unwanted rows later.
-   */
-  public function dataNonEnabledTypes() {
-    $data = [
-      '#node' => ['node', $this->nonSchedulerType],
-      '#media' => ['media', $this->nonSchedulerMediaTypeName],
-      '#commerce_product' => ['commerce_product', $this->nonSchedulerProductTypeName],
-      '#taxonomy_term' => ['taxonomy_term', $this->nonSchedulerVocabularyId],
-    ];
-    return $data;
-  }
-
 }
diff --git a/web/modules/scheduler/tests/src/Traits/SchedulerTaxonomyTermSetupTrait.php b/web/modules/scheduler/tests/src/Traits/SchedulerTaxonomyTermSetupTrait.php
deleted file mode 100644
index 59109016c876bdfa09c168a7a3d0422ec88aa331..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/tests/src/Traits/SchedulerTaxonomyTermSetupTrait.php
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-
-namespace Drupal\Tests\scheduler\Traits;
-
-use Drupal\taxonomy\Entity\Term;
-use Drupal\taxonomy\Entity\Vocabulary;
-
-/**
- * Additional setup trait for Scheduler tests that use Taxonomy.
- *
- * This builds on the standard SchedulerSetupTrait.
- */
-trait SchedulerTaxonomyTermSetupTrait {
-
-  /**
-   * The internal name of the standard taxonomy vocabulary created for testing.
-   *
-   * @var string
-   */
-  protected $vocabularyId = 'test_vocab';
-
-  /**
-   * The readable name of the standard media type created for testing.
-   *
-   * @var string
-   */
-  protected $vocabularyName = 'Test Vocabulary';
-
-  /**
-   * The media type object which is enabled for scheduling.
-   *
-   * @var \Drupal\taxonomy\VocabularyInterface
-   */
-  protected $vocabulary;
-
-  /**
-   * The internal name of the media type not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerVocabularyId = 'vocab_not_enabled';
-
-  /**
-   * The readable label of the media type not enabled for scheduling.
-   *
-   * @var string
-   */
-  protected $nonSchedulerVocabularyName = 'Test Vocabulary - not for scheduling';
-
-  /**
-   * The media type object which is not enabled for scheduling.
-   *
-   * @var \Drupal\taxonomy\VocabularyInterface
-   */
-  protected $nonSchedulerVocabulary;
-
-  /**
-   * The taxonomy term storage.
-   *
-   * @var \Drupal\Core\Entity\ContentEntityStorageInterface
-   */
-  protected $taxonomyTermStorage;
-
-  /**
-   * Set common properties, define content types and create users.
-   */
-  public function schedulerTaxonomyTermSetUp() {
-
-    // Create a test vocabulary that is enabled for scheduling.
-    /** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */
-    $this->vocabulary = Vocabulary::create([
-      'vid' => $this->vocabularyId,
-      'name' => $this->vocabularyName,
-    ]);
-    $this->vocabulary->save();
-
-    // Add scheduler functionality to the vocabulary.
-    $this->vocabulary->setThirdPartySetting('scheduler', 'publish_enable', TRUE)
-      ->setThirdPartySetting('scheduler', 'unpublish_enable', TRUE)
-      ->save();
-
-    // Enable the scheduler fields in the default form display, mimicking what
-    // would be done if the entity bundle had been enabled via admin UI.
-    $this->container->get('entity_display.repository')
-      ->getFormDisplay('taxonomy_term', $this->vocabularyId)
-      ->setComponent('publish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->setComponent('unpublish_on', ['type' => 'datetime_timestamp_no_default'])
-      ->save();
-
-    // Create a vocabulary which is not enabled for scheduling.
-    /** @var \Drupal\taxonomy\VocabularyInterface $nonSchedulerVocabulary */
-    $this->nonSchedulerVocabulary = Vocabulary::create([
-      'vid' => $this->nonSchedulerVocabularyId,
-      'name' => $this->nonSchedulerVocabularyName,
-    ]);
-    $this->nonSchedulerVocabulary->save();
-
-    /** @var  taxonomyTermStorage $taxonomyTermStorage */
-    $this->taxonomyTermStorage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term');
-
-    // Add extra permisssions to the role assigned to the adminUser.
-    $this->addPermissionsToUser($this->adminUser, [
-      'create terms in ' . $this->vocabularyId,
-      'edit terms in ' . $this->vocabularyId,
-      'delete terms in ' . $this->vocabularyId,
-      'create terms in ' . $this->nonSchedulerVocabularyId,
-      'edit terms in ' . $this->nonSchedulerVocabularyId,
-      'administer taxonomy',
-      'access taxonomy overview',
-      'schedule publishing of taxonomy_term',
-      'view scheduled taxonomy_term',
-    ]);
-
-    // Add extra permisssions to the role assigned to the schedulerUser.
-    $this->addPermissionsToUser($this->schedulerUser, [
-      'create terms in ' . $this->vocabularyId,
-      'edit terms in ' . $this->vocabularyId,
-      'schedule publishing of taxonomy_term',
-    ]);
-
-  }
-
-  /**
-   * Creates a taxonomy term.
-   *
-   * @param array $values
-   *   The values to use for the entity.
-   *
-   * @return \Drupal\taxonomy\TermInterface
-   *   The created taxonomy term object.
-   */
-  public function createTaxonomyTerm(array $values) {
-    // Provide defaults for the critical values.
-    $values += [
-      'vid' => $this->vocabularyId,
-      // If no name is specified then use title, or default to a random name.
-      'name' => $values['title'] ?? $this->randomMachineName(),
-    ];
-    /** @var \Drupal\taxonomy\TermInterface $term */
-    $term = Term::create($values);
-    $term->save();
-    return $term;
-  }
-
-  /**
-   * Gets a taxonomy term from storage.
-   *
-   * @param string $name
-   *   Optional name text to match on. If given and no match, returns NULL.
-   *   If no $name is given then returns the term with the highest id value.
-   *
-   * @return \Drupal\taxonomy\Entity\Term
-   *   The taxonomy term object.
-   */
-  public function getTaxonomyTerm(string $name = NULL) {
-    $query = $this->taxonomyTermStorage->getQuery()
-      ->accessCheck(FALSE)
-      ->sort('tid', 'DESC');
-    if (!empty($name)) {
-      $query->condition('name', $name);
-    }
-    $result = $query->execute();
-    if (count($result)) {
-      $term_id = reset($result);
-      return $this->taxonomyTermStorage->load($term_id);
-    }
-    else {
-      return NULL;
-    }
-  }
-
-}