diff --git a/composer.json b/composer.json
index 378475bbedc24917eec462618e568fa9e563bf9b..ed6c28cfd233b6a9d246fbdd961671d27b541a64 100644
--- a/composer.json
+++ b/composer.json
@@ -3,8 +3,7 @@
     "description": "OSU ASC Pantheon custom upstream for Drupal 8",
     "type": "project",
     "license": "None",
-    "repositories": [
-        {
+    "repositories": [{
             "type": "composer",
             "url": "https://packages.drupal.org/8"
         },
@@ -37,14 +36,14 @@
         {
             "type": "package",
             "package": {
-              "name": "gdsmith/jquery.easing",
-              "version": "1.4.1",
-              "type": "drupal-library",
-              "source": {
-                "url": "https://github.com/gdsmith/jquery.easing",
-                "type": "git",
-                "reference": "origin/master"
-              }
+                "name": "gdsmith/jquery.easing",
+                "version": "1.4.1",
+                "type": "drupal-library",
+                "source": {
+                    "url": "https://github.com/gdsmith/jquery.easing",
+                    "type": "git",
+                    "reference": "origin/master"
+                }
             }
         },
         {
@@ -62,19 +61,19 @@
         {
             "type": "package",
             "package": {
-              "name": "ckeditor/indentblock",
-              "version": "4.8.0",
-              "type": "drupal-library",
-              "extra": {
-                "installer-name": "indentblock"
-              },
-              "dist": {
-                "url": "https://download.ckeditor.com/indentblock/releases/indentblock_4.8.0.zip",
-                "type": "zip"
-              },
-              "require": {
-                "composer/installers": "~1.0"
-              }
+                "name": "ckeditor/indentblock",
+                "version": "4.8.0",
+                "type": "drupal-library",
+                "extra": {
+                    "installer-name": "indentblock"
+                },
+                "dist": {
+                    "url": "https://download.ckeditor.com/indentblock/releases/indentblock_4.8.0.zip",
+                    "type": "zip"
+                },
+                "require": {
+                    "composer/installers": "~1.0"
+                }
             }
         }
     ],
@@ -109,8 +108,7 @@
         "drupal/content_access": "1.0-alpha1",
         "drupal/core": "8.7.*",
         "drupal/crop": "2.0-rc1",
-        "drupal/ctools": "3.0",
-        "drupal/ctools_views": "3.0",
+        "drupal/ctools": "^3.0",
         "drupal/devel": "2.0",
         "drupal/draggableviews": "1.0",
         "drupal/dropzonejs": "2.0-alpha3",
@@ -134,7 +132,7 @@
         "drupal/image_popup": "1.1",
         "drupal/inline_entity_form": "1.0-rc1",
         "drupal/libraries": "^3.0@alpha",
-        "drupal/link_attributes": "1.0",
+        "drupal/link_attributes": "^1.0",
         "drupal/linkit": "5.0-beta10",
         "drupal/magnific_popup": "1.3",
         "drupal/mathjax": "^2.7",
@@ -159,7 +157,7 @@
         "drupal/redirect": "^1.3",
         "drupal/redis": "1.0",
         "drupal/roleassign": "^1.0@alpha",
-        "drupal/scheduler": "1.0",
+        "drupal/scheduler": "^1.0",
         "drupal/search_api": "1.1",
         "drupal/search_api_db": "1.1",
         "drupal/simple_gmap": "^2.0",
@@ -175,7 +173,7 @@
         "drupal/twig_tweak": "^2.4",
         "drupal/twitter_block": "3.0-alpha0",
         "drupal/userprotect": "1.0",
-        "drupal/video_embed_field": "2.0",
+        "drupal/video_embed_field": "^2.0",
         "drupal/view_unpublished": "^1.0@alpha",
         "drupal/views_accordion": "1.1",
         "drupal/views_ajax_history": "^1.1",
@@ -183,7 +181,7 @@
         "drupal/views_bootstrap": "3.1",
         "drupal/views_bulk_operations": "^3.0",
         "drupal/views_fieldsets": "3.3",
-        "drupal/views_infinite_scroll": "1.5",
+        "drupal/views_infinite_scroll": "^1.5",
         "drupal/views_slideshow": "4.4",
         "drupal/webform": "5.2",
         "drupal/webform_views": "5.0-alpha2",
@@ -196,10 +194,9 @@
         "rvtraveller/qs-composer-installer": "^1.1",
         "zaporylie/composer-drupal-optimizations": "^1.0"
     },
-    "require-dev": {
-    },
+    "require-dev": {},
     "conflict": {
-            "drupal/drupal": "*"
+        "drupal/drupal": "*"
     },
     "minimum-stability": "dev",
     "prefer-stable": true,
@@ -224,14 +221,14 @@
             "DrupalProject\\composer\\ScriptHandler::createRequiredFiles"
         ],
         "post-package-install": [
-          "rm -rf web/modules/smtp/.git"
+            "rm -rf web/modules/smtp/.git"
         ],
         "post-update-cmd": [
-          "rm -rf vendor/simplesamlphp/simplesamlphp/config",
-          "cp -r config/simplesamlphp/config vendor/simplesamlphp/simplesamlphp/config",
-          "rm -f web/simplesaml",
-          "ln -s ../vendor/simplesamlphp/simplesamlphp/www web/simplesaml",
-          "DrupalProject\\composer\\ScriptHandler::createRequiredFiles"
+            "rm -rf vendor/simplesamlphp/simplesamlphp/config",
+            "cp -r config/simplesamlphp/config vendor/simplesamlphp/simplesamlphp/config",
+            "rm -f web/simplesaml",
+            "ln -s ../vendor/simplesamlphp/simplesamlphp/www web/simplesaml",
+            "DrupalProject\\composer\\ScriptHandler::createRequiredFiles"
         ],
         "post-create-project-cmd": [
             "@drupal-scaffold",
@@ -273,37 +270,37 @@
         },
         "patches": {
             "drupal/core": {
-              "2799049": "patches/role_based_email_access-2799049-d87.patch",
-              "2949017": "https://www.drupal.org/files/issues/2018-09-19/allow-uid-1-to-delete-2949017-36-3.patch"
+                "2799049": "patches/role_based_email_access-2799049-d87.patch",
+                "2949017": "https://www.drupal.org/files/issues/2018-09-19/allow-uid-1-to-delete-2949017-36-3.patch"
             },
             "drupal/better_exposed_filters": {
-              "2961022": "https://www.drupal.org/files/issues/2018-09-27/better_exposed_filters-autosubmit-fix-2961022-4.patch"
+                "2961022": "https://www.drupal.org/files/issues/2018-09-27/better_exposed_filters-autosubmit-fix-2961022-4.patch"
             },
             "mehrpadin/superfish": {
-              "Fontawesome Tags": "patches/superfish-fontawesome-tags.patch"
+                "Fontawesome Tags": "patches/superfish-fontawesome-tags.patch"
             },
             "drupal/addtocalendar": {
-              "UTC Time Adjustment": "patches/utc-time-adjustment.patch"
+                "UTC Time Adjustment": "patches/utc-time-adjustment.patch"
             },
             "drupal/block_permissions": {
-              "2962965": "https://www.drupal.org/files/issues/2018-09-01/block-permissions-2962965-4.patch"
+                "2962965": "https://www.drupal.org/files/issues/2018-09-01/block-permissions-2962965-4.patch"
             },
             "drupal/menu_block": {
-              "2809699": "https://www.drupal.org/files/issues/2018-10-26/menu_block-label_configuration-2809699-82.patch",
-              "2811337": "https://www.drupal.org/files/issues/menu_block-2_level_menu_block_not_limited_to_active_parent-2811337-58.patch"
+                "2809699": "https://www.drupal.org/files/issues/2018-10-26/menu_block-label_configuration-2809699-82.patch",
+                "2811337": "https://www.drupal.org/files/issues/menu_block-2_level_menu_block_not_limited_to_active_parent-2811337-58.patch"
             },
             "drupal/entity_embed": {
-              "2881745": "patches/entity_embed_2881745-22.patch",
-              "2511404": "patches/entity-embed-img-link-2511404-68.patch"
+                "2881745": "patches/entity_embed_2881745-22.patch",
+                "2511404": "patches/entity-embed-img-link-2511404-68.patch"
             },
             "drupal/smtp": {
-              "2781157": "https://www.drupal.org/files/issues/2018-11-07/2781157-n10.patch"
+                "2781157": "https://www.drupal.org/files/issues/2018-11-07/2781157-n10.patch"
             },
             "drupal/linkit": {
-              "2712951": "https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch"
+                "2712951": "https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch"
             },
             "drupal/entity_clone": {
-              "3060223": "https://www.drupal.org/files/issues/2019-10-17/%20entity_clone-corrupted-paragraph-cloning-3060223-5.patch"
+                "3060223": "https://www.drupal.org/files/issues/2019-10-17/%20entity_clone-corrupted-paragraph-cloning-3060223-5.patch"
             }
         }
     },
@@ -315,4 +312,4 @@
             "php": "7.0.8"
         }
     }
-}
+}
\ No newline at end of file
diff --git a/composer.lock b/composer.lock
index 56f349310f58599b23c3a66dc7167cbb4e3f7018..50364c7af942429b638927b5ea4b5529c8c5269e 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": "5d437286ae2e43f67225efd0efc295c9",
+    "content-hash": "4676d2d5e1c207157aecfe496dbffe52",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -3726,20 +3726,20 @@
         },
         {
             "name": "drupal/ctools",
-            "version": "3.0.0",
+            "version": "3.2.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/ctools.git",
-                "reference": "8.x-3.0"
+                "reference": "8.x-3.2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/ctools-8.x-3.0.zip",
-                "reference": "8.x-3.0",
-                "shasum": "302e869ecd1e59fe55663673999fee2ccac5daa8"
+                "url": "https://ftp.drupal.org/files/projects/ctools-8.x-3.2.zip",
+                "reference": "8.x-3.2",
+                "shasum": "d6da87239b64ba708a5977e7b33b1e009e36b091"
             },
             "require": {
-                "drupal/core": "~8.0"
+                "drupal/core": "^8.5"
             },
             "type": "drupal-module",
             "extra": {
@@ -3747,8 +3747,8 @@
                     "dev-3.x": "3.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-3.0",
-                    "datestamp": "1493401742",
+                    "version": "8.x-3.2",
+                    "datestamp": "1550728386",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -3817,83 +3817,6 @@
                 "issues": "https://www.drupal.org/project/issues/ctools"
             }
         },
-        {
-            "name": "drupal/ctools_views",
-            "version": "3.0.0",
-            "require": {
-                "drupal/core": "*",
-                "drupal/ctools": "self.version"
-            },
-            "type": "metapackage",
-            "extra": {
-                "branch-alias": {
-                    "dev-3.x": "3.x-dev"
-                },
-                "drupal": {
-                    "version": "8.x-3.0",
-                    "datestamp": "1493401742",
-                    "security-coverage": {
-                        "status": "covered",
-                        "message": "Covered by Drupal's security advisory policy"
-                    }
-                }
-            },
-            "notification-url": "https://packages.drupal.org/8/downloads",
-            "license": [
-                "GPL-2.0-or-later"
-            ],
-            "authors": [
-                {
-                    "name": "EclipseGc",
-                    "homepage": "https://www.drupal.org/user/61203"
-                },
-                {
-                    "name": "damiankloip",
-                    "homepage": "https://www.drupal.org/user/1037976"
-                },
-                {
-                    "name": "dawehner",
-                    "homepage": "https://www.drupal.org/user/99340"
-                },
-                {
-                    "name": "esmerel",
-                    "homepage": "https://www.drupal.org/user/164022"
-                },
-                {
-                    "name": "japerry",
-                    "homepage": "https://www.drupal.org/user/45640"
-                },
-                {
-                    "name": "joelpittet",
-                    "homepage": "https://www.drupal.org/user/160302"
-                },
-                {
-                    "name": "merlinofchaos",
-                    "homepage": "https://www.drupal.org/user/26979"
-                },
-                {
-                    "name": "neclimdul",
-                    "homepage": "https://www.drupal.org/user/48673"
-                },
-                {
-                    "name": "sdboyer",
-                    "homepage": "https://www.drupal.org/user/146719"
-                },
-                {
-                    "name": "sun",
-                    "homepage": "https://www.drupal.org/user/54136"
-                },
-                {
-                    "name": "tim.plunkett",
-                    "homepage": "https://www.drupal.org/user/241634"
-                }
-            ],
-            "description": "A set of improvements to the core Views code that allows for greater control over Blocks.",
-            "homepage": "https://www.drupal.org/project/ctools",
-            "support": {
-                "source": "http://cgit.drupalcode.org/ctools"
-            }
-        },
         {
             "name": "drupal/devel",
             "version": "2.0.0",
@@ -5397,20 +5320,20 @@
         },
         {
             "name": "drupal/link_attributes",
-            "version": "1.0.0",
+            "version": "1.10.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/link_attributes.git",
-                "reference": "8.x-1.0"
+                "reference": "8.x-1.10"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/link_attributes-8.x-1.0.zip",
-                "reference": "8.x-1.0",
-                "shasum": "8a5851467f17c01b4dcb6e446f00cd7abe116475"
+                "url": "https://ftp.drupal.org/files/projects/link_attributes-8.x-1.10.zip",
+                "reference": "8.x-1.10",
+                "shasum": "3df450b83a05911575aaf9b234a3b43833d3c1c2"
             },
             "require": {
-                "drupal/core": "*"
+                "drupal/core": "^8 || ^9"
             },
             "type": "drupal-module",
             "extra": {
@@ -5418,8 +5341,8 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.0",
-                    "datestamp": "1500846243",
+                    "version": "8.x-1.10",
+                    "datestamp": "1580259496",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -5439,7 +5362,7 @@
             "description": "Provides a widget to allow settings of link attributes for menu links.",
             "homepage": "https://www.drupal.org/project/link_attributes",
             "support": {
-                "source": "http://cgit.drupalcode.org/link_attributes"
+                "source": "https://git.drupalcode.org/project/link_attributes"
             }
         },
         {
@@ -5860,20 +5783,20 @@
         },
         {
             "name": "drupal/menu_breadcrumb",
-            "version": "1.7.0",
+            "version": "1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/menu_breadcrumb.git",
-                "reference": "8.x-1.7"
+                "reference": "8.x-1.12"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/menu_breadcrumb-8.x-1.7.zip",
-                "reference": "8.x-1.7",
-                "shasum": "998c7908bd411afe745f91c725b39483e6dd693f"
+                "url": "https://ftp.drupal.org/files/projects/menu_breadcrumb-8.x-1.12.zip",
+                "reference": "8.x-1.12",
+                "shasum": "661512d420697e83242c7134cc945258fd531ee0"
             },
             "require": {
-                "drupal/core": "~8.0"
+                "drupal/core": "^8 || ^9"
             },
             "type": "drupal-module",
             "extra": {
@@ -5881,8 +5804,8 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.7",
-                    "datestamp": "1539197280",
+                    "version": "8.x-1.12",
+                    "datestamp": "1581496466",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -5922,7 +5845,7 @@
             "description": "Create breadcrumbs from nested menu titles and/or taxonomy membership.",
             "homepage": "https://www.drupal.org/project/menu_breadcrumb",
             "support": {
-                "source": "http://cgit.drupalcode.org/menu_breadcrumb"
+                "source": "https://git.drupalcode.org/project/menu_breadcrumb"
             }
         },
         {
@@ -6893,23 +6816,24 @@
         },
         {
             "name": "drupal/scheduler",
-            "version": "1.0.0",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/scheduler.git",
-                "reference": "8.x-1.0"
+                "reference": "8.x-1.1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.0.zip",
-                "reference": "8.x-1.0",
-                "shasum": "213025b64b417f459b73d260d7287ca1f916d587"
+                "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.1.zip",
+                "reference": "8.x-1.1",
+                "shasum": "52ff9c778f183d970ec2d57dfc1ba45282054e97"
             },
             "require": {
                 "drupal/core": "*"
             },
             "require-dev": {
-                "drupal/devel_generate": "*",
+                "drupal/coder": "^8.3.3",
+                "drupal/devel_generate": "^2.0",
                 "drupal/rules": "*"
             },
             "type": "drupal-module",
@@ -6918,12 +6842,17 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.0",
-                    "datestamp": "1510690385",
+                    "version": "8.x-1.1",
+                    "datestamp": "1566578555",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
                     }
+                },
+                "drush": {
+                    "services": {
+                        "drush.services.yml": "^9"
+                    }
                 }
             },
             "notification-url": "https://packages.drupal.org/8/downloads",
@@ -7362,20 +7291,21 @@
         },
         {
             "name": "drupal/social_media",
-            "version": "1.3.0",
+            "version": "1.8.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/social_media.git",
-                "reference": "8.x-1.3"
+                "reference": "8.x-1.8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/social_media-8.x-1.3.zip",
-                "reference": "8.x-1.3",
-                "shasum": "7b0ab2b3e368109343905c99beac4bd38a8b86a7"
+                "url": "https://ftp.drupal.org/files/projects/social_media-8.x-1.8.zip",
+                "reference": "8.x-1.8",
+                "shasum": "aaf92e087e5d3c4c6907b00b5dbb66b05eb5cda2"
             },
             "require": {
-                "drupal/core": "*"
+                "drupal/core": "*",
+                "drupal/token": "*"
             },
             "type": "drupal-module",
             "extra": {
@@ -7383,8 +7313,8 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.3",
-                    "datestamp": "1521748685",
+                    "version": "8.x-1.8",
+                    "datestamp": "1581591836",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -7400,6 +7330,11 @@
                     "name": "Takim Islam",
                     "homepage": "http://drupalsharing.com/",
                     "role": "Maintainer"
+                },
+                {
+                    "name": "Jack Over",
+                    "homepage": "https://www.drupal.org/user/252386",
+                    "role": "Maintainer"
                 }
             ],
             "description": "Share current page to social media",
@@ -7797,25 +7732,26 @@
         },
         {
             "name": "drupal/video_embed_field",
-            "version": "2.0.0",
+            "version": "2.2.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/video_embed_field.git",
-                "reference": "8.x-2.0"
+                "reference": "8.x-2.2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/video_embed_field-8.x-2.0.zip",
-                "reference": "8.x-2.0",
-                "shasum": "e864f090b3cb9405376ca324d81ace83613e2019"
+                "url": "https://ftp.drupal.org/files/projects/video_embed_field-8.x-2.2.zip",
+                "reference": "8.x-2.2",
+                "shasum": "1e76eeefb27f4166d3d119e8c27469bbf12c0f06"
             },
             "require": {
-                "drupal/core": "*"
+                "drupal/core": "^8.7.0"
             },
             "require-dev": {
                 "drupal/colorbox": "*",
                 "drupal/media_entity": "*",
-                "drupal/media_entity_embeddable_video": "*"
+                "drupal/media_entity_embeddable_video": "*",
+                "drupal/video_embed_media": "*"
             },
             "type": "drupal-module",
             "extra": {
@@ -7823,13 +7759,12 @@
                     "dev-2.x": "2.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.x",
-                    "datestamp": "1523338084",
+                    "version": "8.x-2.2",
+                    "datestamp": "1564103585",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
-                    },
-                    "package": "Field types"
+                    }
                 }
             },
             "notification-url": "https://packages.drupal.org/8/downloads",
@@ -7853,7 +7788,7 @@
             "description": "A pluggable field type for storing videos from external video hosts such as Vimeo and YouTube.",
             "homepage": "https://www.drupal.org/project/video_embed_field",
             "support": {
-                "source": "http://cgit.drupalcode.org/video_embed_field"
+                "source": "https://git.drupalcode.org/project/video_embed_field"
             }
         },
         {
@@ -7965,17 +7900,17 @@
         },
         {
             "name": "drupal/views_ajax_history",
-            "version": "1.1.0",
+            "version": "1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/views_ajax_history.git",
-                "reference": "8.x-1.1"
+                "reference": "8.x-1.2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/views_ajax_history-8.x-1.1.zip",
-                "reference": "8.x-1.1",
-                "shasum": "dd5f5256e139dff1371a109959dde70d593d4d1a"
+                "url": "https://ftp.drupal.org/files/projects/views_ajax_history-8.x-1.2.zip",
+                "reference": "8.x-1.2",
+                "shasum": "97c19dd21327025a58deec6200e008b3c794b022"
             },
             "require": {
                 "drupal/core": "*"
@@ -7986,8 +7921,8 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.1",
-                    "datestamp": "1559296088",
+                    "version": "8.x-1.2",
+                    "datestamp": "1562339886",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -8262,17 +8197,17 @@
         },
         {
             "name": "drupal/views_infinite_scroll",
-            "version": "1.5.0",
+            "version": "1.6.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/views_infinite_scroll.git",
-                "reference": "8.x-1.5"
+                "reference": "8.x-1.6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/views_infinite_scroll-8.x-1.5.zip",
-                "reference": "8.x-1.5",
-                "shasum": "fa19d65a9f6421fe64463b8b64169c6f13d423c3"
+                "url": "https://ftp.drupal.org/files/projects/views_infinite_scroll-8.x-1.6.zip",
+                "reference": "8.x-1.6",
+                "shasum": "614a2b01d57e2a75255883294bfd6b9246c85efd"
             },
             "require": {
                 "drupal/core": "*"
@@ -8283,8 +8218,8 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.5",
-                    "datestamp": "1503109444",
+                    "version": "8.x-1.6",
+                    "datestamp": "1561996386",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -8296,6 +8231,10 @@
                 "GPL-2.0-or-later"
             ],
             "authors": [
+                {
+                    "name": "Bobík",
+                    "homepage": "https://www.drupal.org/user/123612"
+                },
                 {
                     "name": "Remon",
                     "homepage": "https://www.drupal.org/user/143827"
@@ -8308,7 +8247,7 @@
             "description": "A pager which allows an infinite scroll effect for views.",
             "homepage": "https://www.drupal.org/project/views_infinite_scroll",
             "support": {
-                "source": "http://cgit.drupalcode.org/views_infinite_scroll"
+                "source": "https://git.drupalcode.org/project/views_infinite_scroll"
             }
         },
         {
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 6d90aa965d82613bac25451e5065af616f2ce679..4de8c16e4518285d21c9bb8858e2bf022bf05509 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -3841,21 +3841,21 @@
     },
     {
         "name": "drupal/ctools",
-        "version": "3.0.0",
-        "version_normalized": "3.0.0.0",
+        "version": "3.2.0",
+        "version_normalized": "3.2.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/ctools.git",
-            "reference": "8.x-3.0"
+            "reference": "8.x-3.2"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/ctools-8.x-3.0.zip",
-            "reference": "8.x-3.0",
-            "shasum": "302e869ecd1e59fe55663673999fee2ccac5daa8"
+            "url": "https://ftp.drupal.org/files/projects/ctools-8.x-3.2.zip",
+            "reference": "8.x-3.2",
+            "shasum": "d6da87239b64ba708a5977e7b33b1e009e36b091"
         },
         "require": {
-            "drupal/core": "~8.0"
+            "drupal/core": "^8.5"
         },
         "type": "drupal-module",
         "extra": {
@@ -3863,8 +3863,8 @@
                 "dev-3.x": "3.x-dev"
             },
             "drupal": {
-                "version": "8.x-3.0",
-                "datestamp": "1493401742",
+                "version": "8.x-3.2",
+                "datestamp": "1550728386",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -3934,84 +3934,6 @@
             "issues": "https://www.drupal.org/project/issues/ctools"
         }
     },
-    {
-        "name": "drupal/ctools_views",
-        "version": "3.0.0",
-        "version_normalized": "3.0.0.0",
-        "require": {
-            "drupal/core": "*",
-            "drupal/ctools": "self.version"
-        },
-        "type": "metapackage",
-        "extra": {
-            "branch-alias": {
-                "dev-3.x": "3.x-dev"
-            },
-            "drupal": {
-                "version": "8.x-3.0",
-                "datestamp": "1493401742",
-                "security-coverage": {
-                    "status": "covered",
-                    "message": "Covered by Drupal's security advisory policy"
-                }
-            }
-        },
-        "notification-url": "https://packages.drupal.org/8/downloads",
-        "license": [
-            "GPL-2.0-or-later"
-        ],
-        "authors": [
-            {
-                "name": "EclipseGc",
-                "homepage": "https://www.drupal.org/user/61203"
-            },
-            {
-                "name": "damiankloip",
-                "homepage": "https://www.drupal.org/user/1037976"
-            },
-            {
-                "name": "dawehner",
-                "homepage": "https://www.drupal.org/user/99340"
-            },
-            {
-                "name": "esmerel",
-                "homepage": "https://www.drupal.org/user/164022"
-            },
-            {
-                "name": "japerry",
-                "homepage": "https://www.drupal.org/user/45640"
-            },
-            {
-                "name": "joelpittet",
-                "homepage": "https://www.drupal.org/user/160302"
-            },
-            {
-                "name": "merlinofchaos",
-                "homepage": "https://www.drupal.org/user/26979"
-            },
-            {
-                "name": "neclimdul",
-                "homepage": "https://www.drupal.org/user/48673"
-            },
-            {
-                "name": "sdboyer",
-                "homepage": "https://www.drupal.org/user/146719"
-            },
-            {
-                "name": "sun",
-                "homepage": "https://www.drupal.org/user/54136"
-            },
-            {
-                "name": "tim.plunkett",
-                "homepage": "https://www.drupal.org/user/241634"
-            }
-        ],
-        "description": "A set of improvements to the core Views code that allows for greater control over Blocks.",
-        "homepage": "https://www.drupal.org/project/ctools",
-        "support": {
-            "source": "http://cgit.drupalcode.org/ctools"
-        }
-    },
     {
         "name": "drupal/devel",
         "version": "2.0.0",
@@ -5562,21 +5484,21 @@
     },
     {
         "name": "drupal/link_attributes",
-        "version": "1.0.0",
-        "version_normalized": "1.0.0.0",
+        "version": "1.10.0",
+        "version_normalized": "1.10.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/link_attributes.git",
-            "reference": "8.x-1.0"
+            "reference": "8.x-1.10"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/link_attributes-8.x-1.0.zip",
-            "reference": "8.x-1.0",
-            "shasum": "8a5851467f17c01b4dcb6e446f00cd7abe116475"
+            "url": "https://ftp.drupal.org/files/projects/link_attributes-8.x-1.10.zip",
+            "reference": "8.x-1.10",
+            "shasum": "3df450b83a05911575aaf9b234a3b43833d3c1c2"
         },
         "require": {
-            "drupal/core": "*"
+            "drupal/core": "^8 || ^9"
         },
         "type": "drupal-module",
         "extra": {
@@ -5584,8 +5506,8 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.0",
-                "datestamp": "1500846243",
+                "version": "8.x-1.10",
+                "datestamp": "1580259496",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -5606,7 +5528,7 @@
         "description": "Provides a widget to allow settings of link attributes for menu links.",
         "homepage": "https://www.drupal.org/project/link_attributes",
         "support": {
-            "source": "http://cgit.drupalcode.org/link_attributes"
+            "source": "https://git.drupalcode.org/project/link_attributes"
         }
     },
     {
@@ -6041,21 +5963,21 @@
     },
     {
         "name": "drupal/menu_breadcrumb",
-        "version": "1.7.0",
-        "version_normalized": "1.7.0.0",
+        "version": "1.12.0",
+        "version_normalized": "1.12.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/menu_breadcrumb.git",
-            "reference": "8.x-1.7"
+            "reference": "8.x-1.12"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/menu_breadcrumb-8.x-1.7.zip",
-            "reference": "8.x-1.7",
-            "shasum": "998c7908bd411afe745f91c725b39483e6dd693f"
+            "url": "https://ftp.drupal.org/files/projects/menu_breadcrumb-8.x-1.12.zip",
+            "reference": "8.x-1.12",
+            "shasum": "661512d420697e83242c7134cc945258fd531ee0"
         },
         "require": {
-            "drupal/core": "~8.0"
+            "drupal/core": "^8 || ^9"
         },
         "type": "drupal-module",
         "extra": {
@@ -6063,8 +5985,8 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.7",
-                "datestamp": "1539197280",
+                "version": "8.x-1.12",
+                "datestamp": "1581496466",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -6105,7 +6027,7 @@
         "description": "Create breadcrumbs from nested menu titles and/or taxonomy membership.",
         "homepage": "https://www.drupal.org/project/menu_breadcrumb",
         "support": {
-            "source": "http://cgit.drupalcode.org/menu_breadcrumb"
+            "source": "https://git.drupalcode.org/project/menu_breadcrumb"
         }
     },
     {
@@ -7108,24 +7030,25 @@
     },
     {
         "name": "drupal/scheduler",
-        "version": "1.0.0",
-        "version_normalized": "1.0.0.0",
+        "version": "1.1.0",
+        "version_normalized": "1.1.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/scheduler.git",
-            "reference": "8.x-1.0"
+            "reference": "8.x-1.1"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.0.zip",
-            "reference": "8.x-1.0",
-            "shasum": "213025b64b417f459b73d260d7287ca1f916d587"
+            "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.1.zip",
+            "reference": "8.x-1.1",
+            "shasum": "52ff9c778f183d970ec2d57dfc1ba45282054e97"
         },
         "require": {
             "drupal/core": "*"
         },
         "require-dev": {
-            "drupal/devel_generate": "*",
+            "drupal/coder": "^8.3.3",
+            "drupal/devel_generate": "^2.0",
             "drupal/rules": "*"
         },
         "type": "drupal-module",
@@ -7134,12 +7057,17 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.0",
-                "datestamp": "1510690385",
+                "version": "8.x-1.1",
+                "datestamp": "1566578555",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
                 }
+            },
+            "drush": {
+                "services": {
+                    "drush.services.yml": "^9"
+                }
             }
         },
         "installation-source": "dist",
@@ -7592,21 +7520,22 @@
     },
     {
         "name": "drupal/social_media",
-        "version": "1.3.0",
-        "version_normalized": "1.3.0.0",
+        "version": "1.8.0",
+        "version_normalized": "1.8.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/social_media.git",
-            "reference": "8.x-1.3"
+            "reference": "8.x-1.8"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/social_media-8.x-1.3.zip",
-            "reference": "8.x-1.3",
-            "shasum": "7b0ab2b3e368109343905c99beac4bd38a8b86a7"
+            "url": "https://ftp.drupal.org/files/projects/social_media-8.x-1.8.zip",
+            "reference": "8.x-1.8",
+            "shasum": "aaf92e087e5d3c4c6907b00b5dbb66b05eb5cda2"
         },
         "require": {
-            "drupal/core": "*"
+            "drupal/core": "*",
+            "drupal/token": "*"
         },
         "type": "drupal-module",
         "extra": {
@@ -7614,8 +7543,8 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.3",
-                "datestamp": "1521748685",
+                "version": "8.x-1.8",
+                "datestamp": "1581591836",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -7632,6 +7561,11 @@
                 "name": "Takim Islam",
                 "homepage": "http://drupalsharing.com/",
                 "role": "Maintainer"
+            },
+            {
+                "name": "Jack Over",
+                "homepage": "https://www.drupal.org/user/252386",
+                "role": "Maintainer"
             }
         ],
         "description": "Share current page to social media",
@@ -8043,26 +7977,27 @@
     },
     {
         "name": "drupal/video_embed_field",
-        "version": "2.0.0",
-        "version_normalized": "2.0.0.0",
+        "version": "2.2.0",
+        "version_normalized": "2.2.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/video_embed_field.git",
-            "reference": "8.x-2.0"
+            "reference": "8.x-2.2"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/video_embed_field-8.x-2.0.zip",
-            "reference": "8.x-2.0",
-            "shasum": "e864f090b3cb9405376ca324d81ace83613e2019"
+            "url": "https://ftp.drupal.org/files/projects/video_embed_field-8.x-2.2.zip",
+            "reference": "8.x-2.2",
+            "shasum": "1e76eeefb27f4166d3d119e8c27469bbf12c0f06"
         },
         "require": {
-            "drupal/core": "*"
+            "drupal/core": "^8.7.0"
         },
         "require-dev": {
             "drupal/colorbox": "*",
             "drupal/media_entity": "*",
-            "drupal/media_entity_embeddable_video": "*"
+            "drupal/media_entity_embeddable_video": "*",
+            "drupal/video_embed_media": "*"
         },
         "type": "drupal-module",
         "extra": {
@@ -8070,13 +8005,12 @@
                 "dev-2.x": "2.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.x",
-                "datestamp": "1523338084",
+                "version": "8.x-2.2",
+                "datestamp": "1564103585",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
-                },
-                "package": "Field types"
+                }
             }
         },
         "installation-source": "dist",
@@ -8101,7 +8035,7 @@
         "description": "A pluggable field type for storing videos from external video hosts such as Vimeo and YouTube.",
         "homepage": "https://www.drupal.org/project/video_embed_field",
         "support": {
-            "source": "http://cgit.drupalcode.org/video_embed_field"
+            "source": "https://git.drupalcode.org/project/video_embed_field"
         }
     },
     {
@@ -8217,18 +8151,18 @@
     },
     {
         "name": "drupal/views_ajax_history",
-        "version": "1.1.0",
-        "version_normalized": "1.1.0.0",
+        "version": "1.2.0",
+        "version_normalized": "1.2.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/views_ajax_history.git",
-            "reference": "8.x-1.1"
+            "reference": "8.x-1.2"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/views_ajax_history-8.x-1.1.zip",
-            "reference": "8.x-1.1",
-            "shasum": "dd5f5256e139dff1371a109959dde70d593d4d1a"
+            "url": "https://ftp.drupal.org/files/projects/views_ajax_history-8.x-1.2.zip",
+            "reference": "8.x-1.2",
+            "shasum": "97c19dd21327025a58deec6200e008b3c794b022"
         },
         "require": {
             "drupal/core": "*"
@@ -8239,8 +8173,8 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.1",
-                "datestamp": "1559296088",
+                "version": "8.x-1.2",
+                "datestamp": "1562339886",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -8524,18 +8458,18 @@
     },
     {
         "name": "drupal/views_infinite_scroll",
-        "version": "1.5.0",
-        "version_normalized": "1.5.0.0",
+        "version": "1.6.0",
+        "version_normalized": "1.6.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/views_infinite_scroll.git",
-            "reference": "8.x-1.5"
+            "reference": "8.x-1.6"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/views_infinite_scroll-8.x-1.5.zip",
-            "reference": "8.x-1.5",
-            "shasum": "fa19d65a9f6421fe64463b8b64169c6f13d423c3"
+            "url": "https://ftp.drupal.org/files/projects/views_infinite_scroll-8.x-1.6.zip",
+            "reference": "8.x-1.6",
+            "shasum": "614a2b01d57e2a75255883294bfd6b9246c85efd"
         },
         "require": {
             "drupal/core": "*"
@@ -8546,8 +8480,8 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.5",
-                "datestamp": "1503109444",
+                "version": "8.x-1.6",
+                "datestamp": "1561996386",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -8560,6 +8494,10 @@
             "GPL-2.0-or-later"
         ],
         "authors": [
+            {
+                "name": "Bobík",
+                "homepage": "https://www.drupal.org/user/123612"
+            },
             {
                 "name": "Remon",
                 "homepage": "https://www.drupal.org/user/143827"
@@ -8572,7 +8510,7 @@
         "description": "A pager which allows an infinite scroll effect for views.",
         "homepage": "https://www.drupal.org/project/views_infinite_scroll",
         "support": {
-            "source": "http://cgit.drupalcode.org/views_infinite_scroll"
+            "source": "https://git.drupalcode.org/project/views_infinite_scroll"
         }
     },
     {
diff --git a/web/modules/ctools/ctools.info.yml b/web/modules/ctools/ctools.info.yml
index 3b54b6ab98b6dc4818d28d2974396793f8ec79c9..01f59c94d88e20026719402e647ad843b9c20d70 100644
--- a/web/modules/ctools/ctools.info.yml
+++ b/web/modules/ctools/ctools.info.yml
@@ -1,11 +1,13 @@
-name: Chaos tools
+name: Chaos Tools
 type: module
 description: 'Provides a number of utility and helper APIs for Drupal developers and site builders.'
 package: Chaos tool suite
 # core: 8.x
+dependencies:
+  - drupal:system (>=8.5)
 
-# Information added by Drupal.org packaging script on 2017-04-28
-version: '8.x-3.0'
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
 core: '8.x'
 project: 'ctools'
-datestamp: 1493401747
+datestamp: 1550728390
diff --git a/web/modules/ctools/ctools.services.yml b/web/modules/ctools/ctools.services.yml
index eaa38b81e35290778d6b499afb4df599ffe97daa..59f91f92af3ff74f5e370f07bb4a5c82dd50de26 100644
--- a/web/modules/ctools/ctools.services.yml
+++ b/web/modules/ctools/ctools.services.yml
@@ -4,17 +4,17 @@ services:
     arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory']
   ctools.wizard.entity.form:
     class: Drupal\ctools\Controller\WizardEntityFormController
-    arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory', '@entity.manager']
+    arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory', '@entity_type.manager']
   ctools.wizard_enhancer:
     class: Drupal\ctools\Routing\Enhancer\WizardEnhancer
     tags:
       - { name: route_enhancer }
   ctools.wizard.factory:
     class: Drupal\ctools\Wizard\WizardFactory
-    arguments: ['@form_builder', '@event_dispatcher']
+    arguments: ['@form_builder', '@event_dispatcher', '@renderer']
   ctools.paramconverter.tempstore:
     class: Drupal\ctools\ParamConverter\TempstoreConverter
-    arguments: ['@user.shared_tempstore', '@entity_type.manager']
+    arguments: ['@tempstore.shared', '@entity_type.manager']
     tags:
       - { name: paramconverter }
   ctools.typed_data.resolver:
@@ -22,7 +22,7 @@ services:
     arguments: ['@typed_data_manager', '@string_translation']
   ctools.access:
     class: Drupal\ctools\Access\TempstoreAccess
-    arguments: ['@user.shared_tempstore']
+    arguments: ['@tempstore.shared']
     tags:
       - { name: access_check, applies_to: _ctools_access }
   plugin.manager.ctools.relationship:
@@ -33,6 +33,6 @@ services:
     arguments: ['@entity.repository']
   ctools.serializable.tempstore.factory:
     class: Drupal\ctools\SerializableTempstoreFactory
-    arguments: ['@keyvalue.expirable', '@lock', '@request_stack', '%user.tempstore.expire%']
+    arguments: ['@keyvalue.expirable', '@lock', '@request_stack', '%tempstore.expire%', '@current_user']
     tags:
       - { name: backend_overridable }
diff --git a/web/modules/ctools/modules/ctools_block/ctools_block.info.yml b/web/modules/ctools/modules/ctools_block/ctools_block.info.yml
index e9fdf7c746109f64bfa7372fda66b8cf4d784bd5..e5cb62e95f3a91217da0a08710175206d5df5f62 100644
--- a/web/modules/ctools/modules/ctools_block/ctools_block.info.yml
+++ b/web/modules/ctools/modules/ctools_block/ctools_block.info.yml
@@ -1,14 +1,14 @@
-name: Chaos tools blocks
+name: Chaos Tools Blocks
 type: module
 description: 'Provides improvements to blocks that will one day be added to Drupal core.'
 package: Chaos tool suite (Experimental)
 # version: 3.x
 # core: 8.x
 dependencies:
-  - ctools
+  - ctools:ctools
 
-# Information added by Drupal.org packaging script on 2017-04-28
-version: '8.x-3.0'
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
 core: '8.x'
 project: 'ctools'
-datestamp: 1493401747
+datestamp: 1550728390
diff --git a/web/modules/ctools/modules/ctools_block/ctools_block.module b/web/modules/ctools/modules/ctools_block/ctools_block.module
new file mode 100644
index 0000000000000000000000000000000000000000..12c80348e973c61a884a842a416e8c2def5854c2
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_block/ctools_block.module
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * Remove ctools block from appearing on viewable block types.
+ * In general, users should be using the core block types instead.
+ */
+function ctools_block_plugin_filter_block_alter(array &$definitions, array $extra, $consumer) {
+  $moduleHandler = \Drupal::service('module_handler');
+  if ($moduleHandler->moduleExists('layout_builder')){
+    foreach ($definitions AS $label => $definition) {
+      if($definition['provider'] == 'ctools_block') {
+        unset($definitions[$label]);
+      }
+    }
+  }
+}
diff --git a/web/modules/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php b/web/modules/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php
index 14cf6ac21738a6e5ba30bd958c72626a048a7660..e86ce0101df61c0a66e5be3528294059ee53779f 100644
--- a/web/modules/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php
+++ b/web/modules/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php
@@ -5,7 +5,6 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Block\BlockBase;
-use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
@@ -189,7 +188,7 @@ public function defaultConfiguration() {
     return [
       'formatter' => [
         'label' => 'above',
-        'type' => $field_type_definition['default_formatter'] ?: '',
+        'type' => isset($field_type_definition['default_formatter']) ? $field_type_definition['default_formatter'] : '',
         'settings' => [],
         'third_party_settings' => [],
         'weight' => 0,
diff --git a/web/modules/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php b/web/modules/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php
index ac5b5d176b96f8654a32dfcc3fdc6c9cd7b69edf..7413605fb4137dc2c669d2813a65ba300f9da4a3 100644
--- a/web/modules/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php
+++ b/web/modules/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php
@@ -14,9 +14,9 @@ class EntityFieldDeriver extends EntityDeriverBase {
    * {@inheritdoc}
    */
   public function getDerivativeDefinitions($base_plugin_definition) {
-    $entity_type_labels = $this->entityManager->getEntityTypeLabels();
-    foreach ($this->entityManager->getFieldMap() as $entity_type_id => $entity_field_map) {
-      foreach ($this->entityManager->getFieldStorageDefinitions($entity_type_id) as $field_storage_definition) {
+    $entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels();
+    foreach ($this->entityFieldManager->getFieldMap() as $entity_type_id => $entity_field_map) {
+      foreach ($this->entityFieldManager->getFieldStorageDefinitions($entity_type_id) as $field_storage_definition) {
         $field_name = $field_storage_definition->getName();
 
         // The blocks are based on fields. However, we are looping through field
@@ -36,7 +36,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
         else {
           // We take the field label used on the first bundle.
           $first_bundle = reset($field_info['bundles']);
-          $bundle_field_definitions = $this->entityManager->getFieldDefinitions($entity_type_id, $first_bundle);
+          $bundle_field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $first_bundle);
 
           // The field storage config may exist, but it's possible that no
           // fields are actually using it. If that's the case, skip to the next
diff --git a/web/modules/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml b/web/modules/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml
index 17bf6d5139985be4367c75691be96f3b52d3f16f..82daed53d099ab5485c548f8b1198cd7424ae7fb 100644
--- a/web/modules/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml
+++ b/web/modules/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml
@@ -1,4 +1,4 @@
-name: 'Chaos tools blocks test'
+name: 'Chaos Tools blocks test'
 type: module
 description: 'Support module for Chaos tools blocks tests.'
 # core: 8.x
@@ -13,8 +13,8 @@ dependencies:
   - user
 features: true
 
-# Information added by Drupal.org packaging script on 2017-04-28
-version: '8.x-3.0'
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
 core: '8.x'
 project: 'ctools'
-datestamp: 1493401747
+datestamp: 1550728390
diff --git a/web/modules/ctools/modules/ctools_entity_mask/ctools_entity_mask.info.yml b/web/modules/ctools/modules/ctools_entity_mask/ctools_entity_mask.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e7d0d53dc1177193b5f92f6e4e3e4171ade7cc2
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/ctools_entity_mask.info.yml
@@ -0,0 +1,11 @@
+name: 'Entity Mask'
+# core: 8.x
+type: module
+description: 'Allows an entity type to borrow the fields and display configuration of another entity type.'
+# version: VERSION
+
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
+core: '8.x'
+project: 'ctools'
+datestamp: 1550728390
diff --git a/web/modules/ctools/modules/ctools_entity_mask/ctools_entity_mask.module b/web/modules/ctools/modules/ctools_entity_mask/ctools_entity_mask.module
new file mode 100644
index 0000000000000000000000000000000000000000..063a8d3cc40f3005e7ec173f614bc6f441766942
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/ctools_entity_mask.module
@@ -0,0 +1,189 @@
+<?php
+
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
+use Drupal\ctools_entity_mask\MaskContentEntityStorage;
+
+/**
+ * Ensures that mask entity types have the same display modes as masked ones.
+ *
+ * @param array $display_modes
+ *   The display modes.
+ *
+ * @see hook_entity_view_mode_info_alter()
+ * @see \Drupal\Core\Entity\EntityDisplayRepository::getAllDisplayModesByEntityType()
+ */
+function ctools_entity_mask_copy_display_modes(array &$display_modes) {
+  foreach (\Drupal::entityTypeManager()->getDefinitions() as $id => $entity_type) {
+    $mask = $entity_type->get('mask');
+
+    if ($mask && isset($display_modes[$mask])) {
+      $display_modes[$id] = $display_modes[$mask];
+    }
+  }
+}
+
+/**
+ * Implements hook_entity_view_mode_info_alter().
+ */
+function ctools_entity_mask_entity_view_mode_info_alter(&$view_modes) {
+  ctools_entity_mask_copy_display_modes($view_modes);
+}
+
+/**
+ * Implements hook_entity_form_mode_info_alter().
+ */
+function ctools_entity_mask_entity_form_mode_info_alter(&$form_modes) {
+  ctools_entity_mask_copy_display_modes($form_modes);
+}
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function ctools_entity_mask_entity_type_alter(array &$entity_types) {
+  /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+  foreach ($entity_types as $entity_type) {
+    // Mask entities should use our specialized storage handler, which simulates
+    // a save but does not write anything to the database.
+    if ($entity_type->get('mask') && $entity_type->getStorageClass() == SqlContentEntityStorage::class) {
+      $entity_type->setStorageClass(MaskContentEntityStorage::class);
+      // Mask entities should not maintain any tables.
+      $entity_type->set('base_table', NULL);
+      $entity_type->set('revision_table', NULL);
+      $entity_type->set('data_table', NULL);
+      $entity_type->set('revision_data_table', NULL);
+
+      // Nor should they be exposed to Field UI.
+      $entity_type->set('field_ui_base_route', NULL);
+    }
+  }
+}
+
+/**
+ * Copies all components from a display for a masked entity type.
+ *
+ * If the given display is for a mask entity type, the corresponding display for
+ * the masked entity type is loaded and all of its components are copied into
+ * the given display. If no corresponding display exists for the masked entity
+ * type, the default display will be loaded and used.
+ *
+ * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
+ *   The display for the mask entity type.
+ */
+function ctools_entity_mask_copy_display(EntityDisplayInterface $display) {
+  $mask = \Drupal::entityTypeManager()
+    ->getDefinition($display->getTargetEntityTypeId())
+    ->get('mask');
+
+  // If the target entity type is not masking another entity type, there is
+  // nothing to do here.
+  if (empty($mask)) {
+    return;
+  }
+
+  // Try to load the corresponding entity display for the masked entity type,
+  // in descending order of preference.
+  $bundle = $display->getTargetBundle();
+  $displays = $display::loadMultiple([
+    $mask . '.' . $bundle . '.' . $display->getMode(),
+    $mask . '.' . $bundle . '.default',
+  ]);
+
+  // Nothing to do if there is no display we can borrow components from.
+  if (empty($displays)) {
+    return;
+  }
+  foreach (reset($displays)->getComponents() as $key => $component) {
+    $display->setComponent($key, $component);
+  }
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_create().
+ */
+function ctools_entity_mask_entity_view_display_create(EntityViewDisplayInterface $display) {
+  ctools_entity_mask_copy_display($display);
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_create().
+ */
+function ctools_entity_mask_entity_form_display_create(EntityFormDisplayInterface $display) {
+  ctools_entity_mask_copy_display($display);
+}
+
+/**
+ * Implements hook_entity_view_display_alter().
+ */
+function ctools_entity_mask_entity_view_display_alter(EntityViewDisplayInterface $display, array $context) {
+  ctools_entity_mask_copy_display($display);
+}
+
+/**
+ * Implements hook_entity_form_display_alter().
+ */
+function ctools_entity_mask_entity_form_display_alter(EntityFormDisplayInterface $form_display, array $context) {
+  ctools_entity_mask_copy_display($form_display);
+}
+
+/**
+ * Implements hook_entity_bundle_field_info().
+ */
+function ctools_entity_mask_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle) {
+  $info = [];
+
+  $mask = $entity_type->get('mask');
+  // Nothing to do if the entity type is not masking another entity type.
+  if (empty($mask)) {
+    return $info;
+  }
+
+  $storage_info = ctools_entity_mask_entity_field_storage_info($entity_type);
+
+  /** @var \Drupal\field\FieldConfigInterface[] $fields */
+  $fields = \Drupal::entityTypeManager()
+    ->getStorage('field_config')
+    ->loadByProperties([
+      'entity_type' => $mask,
+      'bundle' => $bundle,
+    ]);
+
+  foreach ($fields as $field) {
+    $field_name = $field->getName();
+
+    $info[$field_name] = $field
+      ->createDuplicate()
+      ->set('entity_type', $mask)
+      ->set('fieldStorage', $storage_info[$field_name]);
+  }
+  return $info;
+}
+
+/**
+ * Implements hook_entity_field_storage_info().
+ */
+function ctools_entity_mask_entity_field_storage_info(EntityTypeInterface $entity_type) {
+  $info = [];
+
+  $mask = $entity_type->get('mask');
+  // Nothing to do if the entity type is not masking another entity type.
+  if (empty($mask)) {
+    return $info;
+  }
+
+  /** @var \Drupal\field\FieldStorageConfigInterface[] $fields */
+  $fields = \Drupal::entityTypeManager()
+    ->getStorage('field_storage_config')
+    ->loadByProperties([
+      'entity_type' => $mask,
+    ]);
+
+  foreach ($fields as $field) {
+    $field_name = $field->getName();
+    $info[$field_name] = $field->createDuplicate()->set('entity_type', $mask);
+  }
+  return $info;
+}
diff --git a/web/modules/ctools/modules/ctools_entity_mask/src/MaskContentEntityStorage.php b/web/modules/ctools/modules/ctools_entity_mask/src/MaskContentEntityStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c9151107202269c30b08e5b2c6a8ae9012256e7
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/src/MaskContentEntityStorage.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Drupal\ctools_entity_mask;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityStorageBase;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+
+/**
+ * Storage handler that simulates a full save, without writing to the database.
+ */
+class MaskContentEntityStorage extends ContentEntityStorageBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doLoadRevisionFieldItems($revision_id) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doDeleteFieldItems($entities) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doLoadMultiple(array $ids = NULL) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function has($id, EntityInterface $entity) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getQueryServiceName() {
+    return 'entity.query.null';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function countFieldData($storage_definition, $as_bool = FALSE) {
+    return $as_bool ? FALSE : 0;
+  }
+
+}
diff --git a/web/modules/ctools/modules/ctools_entity_mask/src/MaskEntityTrait.php b/web/modules/ctools/modules/ctools_entity_mask/src/MaskEntityTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..4876e106e7544f2cc1b1c32bcfb592302d6531ff
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/src/MaskEntityTrait.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\ctools_entity_mask;
+
+/**
+ * Provides common functionality for mask entities.
+ */
+trait MaskEntityTrait {
+
+  /**
+   * Implements \Drupal\Core\Entity\EntityInterface::id().
+   *
+   * Mask entities are generally not saved to the database like standard content
+   * entities, so it cannot be assumed that they will have a serial ID at any
+   * point in their lives. However, Drupal still expects all entities to have an
+   * identifier of some kind, so this dual-purposes the UUID as the canonical
+   * entity ID. (It would be nice if core did this as a rule for all entities
+   * and stopped using serial IDs, but, y'know, baby steps.)
+   *
+   * @return string
+   */
+  public function id() {
+    return $this->uuid();
+  }
+
+}
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/block_content.type.basic.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/block_content.type.basic.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f01ced96bc23d433db37b97ae9fc8ecb08574aec
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/block_content.type.basic.yml
@@ -0,0 +1,7 @@
+langcode: en
+status: true
+dependencies: {  }
+id: basic
+label: 'Basic block'
+revision: 0
+description: 'A basic block contains a title and a body.'
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_form_display.block_content.basic.default.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_form_display.block_content.basic.default.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4baffd4f823f9807c1aa5706ec1e674b04134cb3
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_form_display.block_content.basic.default.yml
@@ -0,0 +1,52 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - block_content.type.basic
+    - field.field.block_content.basic.body
+    - field.field.block_content.basic.field_image
+    - field.field.block_content.basic.field_link
+    - image.style.thumbnail
+  module:
+    - image
+    - link
+    - text
+id: block_content.basic.default
+targetEntityType: block_content
+bundle: basic
+mode: default
+content:
+  body:
+    type: text_textarea_with_summary
+    weight: 1
+    settings:
+      rows: 9
+      summary_rows: 3
+      placeholder: ''
+    third_party_settings: {  }
+    region: content
+  field_image:
+    type: image_image
+    weight: 3
+    settings:
+      preview_image_style: thumbnail
+      progress_indicator: throbber
+    third_party_settings: {  }
+    region: content
+  field_link:
+    weight: 2
+    settings:
+      placeholder_url: ''
+      placeholder_title: ''
+    third_party_settings: {  }
+    type: link_default
+    region: content
+  info:
+    type: string_textfield
+    weight: 0
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+    region: content
+hidden: {  }
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_view_display.block_content.basic.default.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_view_display.block_content.basic.default.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6b554554ebd6a774255a15e3b909ca6f3eb48b3f
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_view_display.block_content.basic.default.yml
@@ -0,0 +1,46 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - block_content.type.basic
+    - field.field.block_content.basic.body
+    - field.field.block_content.basic.field_image
+    - field.field.block_content.basic.field_link
+  module:
+    - image
+    - link
+    - text
+id: block_content.basic.default
+targetEntityType: block_content
+bundle: basic
+mode: default
+content:
+  body:
+    type: text_default
+    weight: 0
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+    region: content
+  field_image:
+    weight: 2
+    label: above
+    settings:
+      image_style: ''
+      image_link: ''
+    third_party_settings: {  }
+    type: image
+    region: content
+  field_link:
+    weight: 1
+    label: above
+    settings:
+      trim_length: 80
+      url_only: false
+      url_plain: false
+      rel: ''
+      target: ''
+    third_party_settings: {  }
+    type: link
+    region: content
+hidden: {  }
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.body.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.body.yml
new file mode 100644
index 0000000000000000000000000000000000000000..89118eff0821d2c675f31171b77a1dc7aab5c8a2
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.body.yml
@@ -0,0 +1,21 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - block_content.type.basic
+    - field.storage.block_content.body
+  module:
+    - text
+id: block_content.basic.body
+field_name: body
+entity_type: block_content
+bundle: basic
+label: Body
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  display_summary: false
+field_type: text_with_summary
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_image.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3e11e963944c50c026a3c5d700f52600f65ab3fd
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_image.yml
@@ -0,0 +1,37 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - block_content.type.basic
+    - field.storage.block_content.field_image
+  module:
+    - image
+id: block_content.basic.field_image
+field_name: field_image
+entity_type: block_content
+bundle: basic
+label: Image
+description: ''
+required: false
+translatable: false
+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: true
+  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/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_link.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_link.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d79e9ff02fb3654c3d2743aaae89663a3943c62d
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_link.yml
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - block_content.type.basic
+    - field.storage.block_content.field_link
+  module:
+    - link
+id: block_content.basic.field_link
+field_name: field_link
+entity_type: block_content
+bundle: basic
+label: Link
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  link_type: 17
+  title: 1
+field_type: link
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_image.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6e0ba246413bf73387d41df07b5b4791dcf11ce9
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_image.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - block_content
+    - file
+    - image
+id: block_content.field_image
+field_name: field_image
+entity_type: block_content
+type: image
+settings:
+  uri_scheme: public
+  default_image:
+    uuid: ''
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  target_type: file
+  display_field: false
+  display_default: false
+module: image
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_link.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_link.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bddf13e1c055dcb746104910882d9f3144232d93
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_link.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - block_content
+    - link
+id: block_content.field_link
+field_name: field_link
+entity_type: block_content
+type: link
+settings: {  }
+module: link
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.info.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..46be42c73a67163775e70eacd374eff7edc40cdf
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.info.yml
@@ -0,0 +1,14 @@
+name: 'Entity Mask test'
+# core: 8.x
+type: module
+dependencies:
+  - block_content
+  - ctools_entity_mask
+  - image
+  - text
+
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
+core: '8.x'
+project: 'ctools'
+datestamp: 1550728390
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.routing.yml b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..85d2c8d3b20640e47482405e7884453b0144e849
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.routing.yml
@@ -0,0 +1,9 @@
+fake_block_content.add_form:
+  path: '/fake-block/add/{block_content_type}'
+  defaults:
+    _controller: '\Drupal\block_content\Controller\BlockContentController::addForm'
+    _title_callback: 'Drupal\block_content\Controller\BlockContentController::getAddFormTitle'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'administer blocks'
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/src/Entity/BlockContent.php b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/src/Entity/BlockContent.php
new file mode 100644
index 0000000000000000000000000000000000000000..75446684831701f2e6aab4faafd89721207ce950
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/src/Entity/BlockContent.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\entity_mask_test\Entity;
+
+use Drupal\block_content\Entity\BlockContent as BaseBlockContent;
+use Drupal\ctools_entity_mask\MaskEntityTrait;
+
+/**
+ * Provides a masked version of BlockContent.
+ *
+ * @todo Investigate a better way to copy the upstream properties instead of
+ *   manually duplicating them.
+ *
+ * @ContentEntityType(
+ *   id = "fake_block_content",
+ *   label = @Translation("Custom block"),
+ *   bundle_label = @Translation("Custom block type"),
+ *   handlers = {
+ *     "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
+ *     "access" = "Drupal\block_content\BlockContentAccessControlHandler",
+ *     "list_builder" = "Drupal\block_content\BlockContentListBuilder",
+ *     "view_builder" = "Drupal\block_content\BlockContentViewBuilder",
+ *     "views_data" = "Drupal\block_content\BlockContentViewsData",
+ *     "form" = {
+ *       "add" = "Drupal\block_content\BlockContentForm",
+ *       "edit" = "Drupal\block_content\BlockContentForm",
+ *       "delete" = "Drupal\block_content\Form\BlockContentDeleteForm",
+ *       "default" = "Drupal\block_content\BlockContentForm"
+ *     },
+ *     "translation" = "Drupal\block_content\BlockContentTranslationHandler"
+ *   },
+ *   admin_permission = "administer blocks",
+ *   base_table = "block_content",
+ *   revision_table = "block_content_revision",
+ *   data_table = "block_content_field_data",
+ *   revision_data_table = "block_content_field_revision",
+ *   show_revision_ui = TRUE,
+ *   links = {
+ *     "canonical" = "/block/{block_content}",
+ *     "delete-form" = "/block/{block_content}/delete",
+ *     "edit-form" = "/block/{block_content}",
+ *     "collection" = "/admin/structure/block/block-content",
+ *     "create" = "/block",
+ *   },
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "revision" = "revision_id",
+ *     "bundle" = "type",
+ *     "label" = "info",
+ *     "langcode" = "langcode",
+ *     "uuid" = "uuid",
+ *     "published" = "status",
+ *   },
+ *   revision_metadata_keys = {
+ *     "revision_user" = "revision_user",
+ *     "revision_created" = "revision_created",
+ *     "revision_log_message" = "revision_log"
+ *   },
+ *   bundle_entity_type = "block_content_type",
+ *   field_ui_base_route = "entity.block_content_type.edit_form",
+ *   render_cache = FALSE,
+ *   mask = "block_content",
+ * )
+ */
+class BlockContent extends BaseBlockContent {
+
+  use MaskEntityTrait;
+
+}
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/src/Functional/DisplayTest.php b/web/modules/ctools/modules/ctools_entity_mask/tests/src/Functional/DisplayTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9e45a42459b1ea005480ea354fd5ea0673cf2f11
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/src/Functional/DisplayTest.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\Tests\ctools_entity_mask\Functional;
+
+use Drupal\entity_mask_test\Entity\BlockContent;
+use Drupal\file\Entity\File;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * @group ctools_entity_mask
+ */
+class DisplayTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'block',
+    'block_content',
+    'ctools_entity_mask',
+    'entity_mask_test',
+    'field',
+    'field_ui',
+    'file',
+    'image',
+    'link',
+    'system',
+    'text',
+    'user',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $account = $this->drupalCreateUser(['administer blocks']);
+    $this->drupalLogin($account);
+  }
+
+  /**
+   * Tests that the form display for a masked entity replicates its source.
+   */
+  public function testFormDisplay() {
+    $assert = $this->assertSession();
+
+    $this->drupalGet('/fake-block/add/basic');
+    $assert->statusCodeEquals(200);
+    $assert->fieldExists('Body');
+    $assert->fieldExists('Link');
+    $assert->fieldExists('Image');
+  }
+
+  /**
+   * Tests that the view display for a masked entity replicates its source.
+   */
+  public function testViewDisplay() {
+    // Generate a random image for the image field, since that can potentially
+    // be tricky.
+    $image_uri = uniqid('public://') . '.png';
+    $image_uri = $this->getRandomGenerator()->image($image_uri, '100x100', '200x200');
+    $image = File::create(['uri' => $image_uri]);
+    $image->save();
+
+    $body = 'Qui animated corpse, cricket bat max brucks terribilem incessu zomby.';
+    $link = 'https://www.drupal.org/project/ctools';
+
+    $block = BlockContent::create([
+      'type' => 'basic',
+      'body' => $body,
+      'field_link' => $link,
+      'field_image' => $image,
+    ]);
+    $block->save();
+
+    // Ensure that the entity is intact after serialization and deserialization,
+    // since that may prove to be a common storage mechanism for mask entities.
+    $block = serialize($block);
+    $block = unserialize($block);
+
+    $this->assertSame($body, $block->body->value);
+    $this->assertSame($link, $block->field_link->uri);
+    $this->assertSame($image_uri, $block->field_image->entity->getFileUri());
+
+    $build = \Drupal::entityTypeManager()
+      ->getViewBuilder('fake_block_content')
+      ->view($block);
+
+    // If the fields are not in the renderable array, something has gone awry.
+    $this->assertArrayHasKey('body', $build);
+    $this->assertArrayHasKey('field_link', $build);
+    $this->assertArrayHasKey('field_image', $build);
+
+    // Render the block and check the output too, just to be sure.
+    $rendered = \Drupal::service('renderer')->renderRoot($build);
+    $rendered = (string) $rendered;
+
+    $this->assertContains($block->body->value, $rendered);
+    $this->assertContains($block->field_link->uri, $rendered);
+
+    $image_url = $block->field_image->entity->getFileUri();
+    $image_url = file_create_url($image_url);
+    // file_create_url() will include the host and port, but the rendered output
+    // won't include those.
+    $image_url = file_url_transform_relative($image_url);
+    $this->assertContains($image_url, $rendered);
+  }
+
+}
diff --git a/web/modules/ctools/modules/ctools_entity_mask/tests/src/Kernel/EntityMaskTest.php b/web/modules/ctools/modules/ctools_entity_mask/tests/src/Kernel/EntityMaskTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..273a1a457670f09ebdde1c7cf42ae28b31e43c92
--- /dev/null
+++ b/web/modules/ctools/modules/ctools_entity_mask/tests/src/Kernel/EntityMaskTest.php
@@ -0,0 +1,216 @@
+<?php
+
+namespace Drupal\Tests\ctools_entity_mask\Kernel;
+
+use Drupal\Core\Entity\Entity\EntityFormMode;
+use Drupal\entity_mask_test\Entity\BlockContent;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Basic test of entity type masking.
+ *
+ * @group ctools_entity_mask
+ */
+class EntityMaskTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'block',
+    'block_content',
+    'ctools_entity_mask',
+    'entity_mask_test',
+    'field',
+    'field_ui',
+    'file',
+    'image',
+    'link',
+    'system',
+    'text',
+    'user',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installConfig(['block_content', 'entity_mask_test']);
+    $this->installEntitySchema('fake_block_content');
+  }
+
+  /**
+   * Tests that fields are correctly masked.
+   */
+  public function testFields() {
+    $block = BlockContent::create([
+      'type' => 'basic',
+    ]);
+
+    $this->assertTrue($block->hasField('body'));
+    $this->assertTrue($block->hasField('field_link'));
+    $this->assertTrue($block->hasField('field_image'));
+  }
+
+  /**
+   * Tests that entity view displays are correctly masked.
+   */
+  public function testViewDisplays() {
+    $view_modes = $this->container
+      ->get('entity_display.repository')
+      ->getAllViewModes();
+    $this->assertSame($view_modes['block_content'], $view_modes['fake_block_content']);
+
+    $display = entity_get_display('fake_block_content', 'basic', 'default');
+    $this->assertTrue($display->isNew());
+
+    $components = $display->getComponents();
+    $this->assertArrayHasKey('body', $components);
+    $this->assertArrayHasKey('field_link', $components);
+    $this->assertArrayHasKey('field_image', $components);
+  }
+
+  /**
+   * Tests that entity form displays are correctly masked.
+   */
+  public function testFormDisplays() {
+    EntityFormMode::create([
+      'id' => 'block_content.foobar',
+      'label' => $this->randomString(),
+      'targetEntityType' => 'block_content',
+    ])->save();
+
+    $form_modes = $this->container
+      ->get('entity_display.repository')
+      ->getAllFormModes();
+    $this->assertSame($form_modes['block_content'], $form_modes['fake_block_content']);
+
+    $display = entity_get_form_display('fake_block_content', 'basic', 'default');
+    $this->assertTrue($display->isNew());
+
+    $components = $display->getComponents();
+    $this->assertArrayHasKey('body', $components);
+    $this->assertArrayHasKey('field_link', $components);
+    $this->assertArrayHasKey('field_image', $components);
+  }
+
+  /**
+   * Tests that mask entity types define no tables.
+   */
+  public function testNoTables() {
+    /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+    $entity_type = $this->container
+      ->get('entity_type.manager')
+      ->getDefinition('fake_block_content');
+
+    $this->assertNull($entity_type->getBaseTable());
+    $this->assertNull($entity_type->getDataTable());
+    $this->assertNull($entity_type->getRevisionTable());
+    $this->assertNull($entity_type->getRevisionDataTable());
+  }
+
+  /**
+   * Tests that mask entity types are not exposed to Field UI.
+   */
+  public function testNotExposedToFieldUI() {
+    /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+    $entity_type = $this->container
+      ->get('entity_type.manager')
+      ->getDefinition('fake_block_content');
+
+    $this->assertNull($entity_type->get('field_ui_base_route'));
+  }
+
+  /**
+   * Asserts that a mask entity can be serialized and de-serialized coherently.
+   *
+   * @depends testFields
+   */
+  public function testSerialization() {
+    $body = $this->getRandomGenerator()->paragraphs(2);
+    $link = 'https://www.drupal.org/project/ctools';
+
+    /** @var \Drupal\Core\Entity\EntityInterface $block */
+    $block = BlockContent::create([
+      'type' => 'basic',
+      'body' => $body,
+      'field_link' => $link,
+    ]);
+
+    $block = serialize($block);
+    $block = unserialize($block);
+
+    $this->assertSame($body, $block->body->value);
+    $this->assertSame($link, $block->field_link->uri);
+  }
+
+  /**
+   * Tests that mask entities' isNew() method behaves consistently.
+   */
+  public function testIsNew() {
+    $block = BlockContent::create(['type' => 'basic']);
+    $this->assertTrue($block->isNew());
+    $block->save();
+    $this->assertFalse($block->isNew());
+  }
+
+  /**
+   * Tests that mask entities' id() method returns the UUID.
+   */
+  public function testId() {
+    $block = BlockContent::create(['type' => 'basic']);
+    $this->assertSame($block->id(), $block->uuid());
+    $block->save();
+    $this->assertSame($block->id(), $block->uuid());
+  }
+
+  /**
+   * Tests that mask entities cannot be loaded.
+   *
+   * @depends testId
+   */
+  public function testLoad() {
+    $block = BlockContent::create(['type' => 'basic']);
+    $block->save();
+
+    /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
+    $storage = $this->container->get('entity_type.manager')->getStorage('fake_block_content');
+
+    $id = $block->id();
+    $this->assertNull($storage->load($id));
+    $this->assertEmpty($storage->loadMultiple([$id]));
+  }
+
+  /**
+   * Tests that deleting a mask entity doesn't throw an exception or anything.
+   */
+  public function testDelete() {
+    $block = BlockContent::create(['type' => 'basic']);
+    $block->save();
+    $block->delete();
+  }
+
+  /**
+   * Tests that mask entities have field data after save.
+   *
+   * @depends testFields
+   * @depends testNoTables
+   */
+  public function testSave() {
+    $body = $this->getRandomGenerator()->paragraphs(2);
+    $link = 'https://www.drupal.org/project/ctools';
+
+    /** @var \Drupal\Core\Entity\EntityInterface $block */
+    $block = BlockContent::create([
+      'type' => 'basic',
+      'body' => $body,
+      'field_link' => $link,
+    ]);
+
+    // Ensure that the field values are preserved after save...
+    $this->assertSame($body, $block->body->value);
+    $this->assertSame($link, $block->field_link->uri);
+  }
+
+}
diff --git a/web/modules/ctools/modules/ctools_views/ctools_views.info.yml b/web/modules/ctools/modules/ctools_views/ctools_views.info.yml
index 2d66b5b53ae8e3f237bed767f5547236a12307fc..9f3898ee1d0b17d65f9f9753c56d737f6b123d05 100644
--- a/web/modules/ctools/modules/ctools_views/ctools_views.info.yml
+++ b/web/modules/ctools/modules/ctools_views/ctools_views.info.yml
@@ -1,15 +1,15 @@
-name: Chaos tools Views
+name: Chaos Tools Views
 type: module
 description: 'A set of improvements to the core Views code that allows for greater control over Blocks.'
 package: Chaos tool suite (Experimental)
 # version: 3.x
 # core: 8.x
 dependencies:
-    - block
-    - views
+  - drupal:block
+  - drupal:views
 
-# Information added by Drupal.org packaging script on 2017-04-28
-version: '8.x-3.0'
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
 core: '8.x'
 project: 'ctools'
-datestamp: 1493401747
+datestamp: 1550728390
diff --git a/web/modules/ctools/modules/ctools_views/src/Plugin/Display/Block.php b/web/modules/ctools/modules/ctools_views/src/Plugin/Display/Block.php
index e2d2a7c4d36aa25473e24d4b84f81933476c67f8..7ebc1b1fd0009cc34e699354be90e0625d77bdd4 100644
--- a/web/modules/ctools/modules/ctools_views/src/Plugin/Display/Block.php
+++ b/web/modules/ctools/modules/ctools_views/src/Plugin/Display/Block.php
@@ -2,11 +2,9 @@
 
 namespace Drupal\ctools_views\Plugin\Display;
 
-use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\Plugin\Block\ViewsBlock;
 use Drupal\views\Plugin\views\display\Block as CoreBlock;
-use Drupal\views\Plugin\views\filter\InOperator;
 
 /**
  * Provides a Block display plugin that allows for greater control over Views
@@ -52,10 +50,14 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     $options['configure_sorts'] = $this->t('Configure sorts');
     $form['allow']['#options'] = $options;
     // Update the items_per_page if set.
-    $defaults = array_filter($form['allow']['#default_value']);
-    if (isset($defaults['items_per_page'])) {
-      $defaults['items_per_page'] = 'items_per_page';
+    $defaults = [];
+    if (!empty($form['allow']['#default_value'])) {
+      $defaults = array_filter($form['allow']['#default_value']);
+      if (!empty($defaults['items_per_page'])) {
+        $defaults['items_per_page'] = 'items_per_page';
+      }
     }
+
     $form['allow']['#default_value'] = $defaults;
   }
 
@@ -157,7 +159,7 @@ public function blockForm(ViewsBlock $block, array &$form, FormStateInterface $f
        if (!empty($allow_settings['sort_fields'])) {
           $form['override']['order_fields'][$field_name]['#attributes']['class'][] = 'draggable';
         }
-        $form['override']['order_fields'][$field_name]['#weight'] = !empty($block_configuration['fields'][$field_name]['weight']) ? $block_configuration['fields'][$field_name]['weight'] : '';
+        $form['override']['order_fields'][$field_name]['#weight'] = !empty($block_configuration['fields'][$field_name]['weight']) ? $block_configuration['fields'][$field_name]['weight'] : 0;
         if (!empty($allow_settings['hide_fields'])) {
           $form['override']['order_fields'][$field_name]['hide'] = [
             '#type' => 'checkbox',
diff --git a/web/modules/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php b/web/modules/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php
index 85201046a09ca5ed3a45ad8cb78c1af4c9d38ef3..559f38ba9287f17a650c1d1dca4f136ac76a957f 100644
--- a/web/modules/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php
+++ b/web/modules/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php
@@ -41,11 +41,11 @@ class CToolsViewsBasicViewBlockTest extends UITestBase {
   /**
    * @inheritdoc
    */
-  protected function setUp() {
-    parent::setUp();
+  protected function setUp($import_test_views = TRUE) {
+    parent::setUp($import_test_views);
 
     ViewTestData::createTestViews(get_class($this), array('ctools_views_test_views'));
-    $this->storage = $this->container->get('entity.manager')->getStorage('block');
+    $this->storage = $this->container->get('entity_type.manager')->getStorage('block');
   }
 
   /**
diff --git a/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml b/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml
index 092f98c3781359e39a5e8a1b78e50f563575a3e2..d476c05185efbfff4bc18de4790338beda697acd 100644
--- a/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml
+++ b/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml
@@ -13,8 +13,8 @@ dependencies:
     - node
     - taxonomy
 
-# Information added by Drupal.org packaging script on 2017-04-28
-version: '8.x-3.0'
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
 core: '8.x'
 project: 'ctools'
-datestamp: 1493401747
+datestamp: 1550728390
diff --git a/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml b/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml
index c2ece3c05f353965f87ef87e5b587bd7b44b6bb9..fbdfb3e9c9e939310670836a5f7b21c9841725b6 100644
--- a/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml
+++ b/web/modules/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml
@@ -108,7 +108,7 @@ display:
           field_api_classes: false
       filters:
         status:
-          value: true
+          value: '1'
           table: node_field_data
           field: status
           plugin_id: boolean
@@ -173,7 +173,7 @@ display:
         filter_groups: false
       filters:
         status:
-          value: true
+          value: '1'
           table: node_field_data
           field: status
           plugin_id: boolean
@@ -266,7 +266,7 @@ display:
         filter_groups: false
       filters:
         status:
-          value: true
+          value: '1'
           table: node_field_data
           field: status
           plugin_id: boolean
@@ -361,7 +361,7 @@ display:
         filter_groups: false
       filters:
         status:
-          value: true
+          value: '1'
           table: node_field_data
           field: status
           plugin_id: boolean
@@ -463,7 +463,7 @@ display:
         filter_groups: false
       filters:
         status:
-          value: true
+          value: '1'
           table: node_field_data
           field: status
           plugin_id: boolean
diff --git a/web/modules/ctools/plugins/access/node.inc b/web/modules/ctools/plugins/access/node.inc
new file mode 100644
index 0000000000000000000000000000000000000000..07d500025007a62507690a6311d833b10d726956
--- /dev/null
+++ b/web/modules/ctools/plugins/access/node.inc
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide access control based on node.
+ */
diff --git a/web/modules/ctools/src/Access/TempstoreAccess.php b/web/modules/ctools/src/Access/TempstoreAccess.php
index 506ade6b482b3399c8e12e8ac3fbcdc6b37a7ce7..34561955f3395a94b8e875d0cdf749990b618dbb 100644
--- a/web/modules/ctools/src/Access/TempstoreAccess.php
+++ b/web/modules/ctools/src/Access/TempstoreAccess.php
@@ -2,13 +2,12 @@
 
 namespace Drupal\ctools\Access;
 
-
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Routing\Access\AccessInterface as CoreAccessInterface;
 use Drupal\Core\Routing\RouteMatch;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\ctools\Access\AccessInterface as CToolsAccessInterface;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\Routing\Route;
 
 class TempstoreAccess implements CoreAccessInterface {
@@ -16,7 +15,7 @@ class TempstoreAccess implements CoreAccessInterface {
   /**
    * The shared tempstore factory.
    *
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
diff --git a/web/modules/ctools/src/ContextNotFoundException.php b/web/modules/ctools/src/ContextNotFoundException.php
index b9cf15c4a2a7f4be9f8d05d3cd9e4c46e371ca68..a4c45f20f0b71c0e175f703ec9fc2efa88094999 100644
--- a/web/modules/ctools/src/ContextNotFoundException.php
+++ b/web/modules/ctools/src/ContextNotFoundException.php
@@ -2,5 +2,4 @@
 
 namespace Drupal\ctools;
 
-
 class ContextNotFoundException extends \Exception {}
diff --git a/web/modules/ctools/src/Controller/WizardEntityFormController.php b/web/modules/ctools/src/Controller/WizardEntityFormController.php
index 4525764e831041c63f09a50f99b086915fc7572b..3882e273bea7a23a0df0e4f6ac1a51c262c2d694 100644
--- a/web/modules/ctools/src/Controller/WizardEntityFormController.php
+++ b/web/modules/ctools/src/Controller/WizardEntityFormController.php
@@ -3,7 +3,7 @@
 namespace Drupal\ctools\Controller;
 
 use Drupal\Core\Controller\ControllerResolverInterface;
-use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\ctools\Wizard\WizardFactoryInterface;
@@ -14,11 +14,11 @@
 class WizardEntityFormController extends WizardFormController {
 
   /**
-   * The entity manager service.
+   * The entity type manager.
    *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
 
   /**
    * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
@@ -27,12 +27,12 @@ class WizardEntityFormController extends WizardFormController {
    *   The form builder.
    * @param \Drupal\ctools\Wizard\WizardFactoryInterface $wizard_factory
    *   The wizard factory.
-   * @param \Drupal\Core\Entity\EntityManagerInterface $manager
-   *   The entity manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    */
-  public function __construct(ControllerResolverInterface $controller_resolver, FormBuilderInterface $form_builder, WizardFactoryInterface $wizard_factory, EntityManagerInterface $manager) {
+  public function __construct(ControllerResolverInterface $controller_resolver, FormBuilderInterface $form_builder, WizardFactoryInterface $wizard_factory, EntityTypeManagerInterface $entity_type_manager) {
     parent::__construct($controller_resolver, $form_builder, $wizard_factory);
-    $this->entityManager = $manager;
+    $this->entityTypeManager = $entity_type_manager;
   }
 
   /**
@@ -41,7 +41,7 @@ public function __construct(ControllerResolverInterface $controller_resolver, Fo
   protected function getFormArgument(RouteMatchInterface $route_match) {
     $form_arg = $route_match->getRouteObject()->getDefault('_entity_wizard');
     list($entity_type_id, $operation) = explode('.', $form_arg);
-    $definition = $this->entityManager->getDefinition($entity_type_id);
+    $definition = $this->entityTypeManager->getDefinition($entity_type_id);
     $handlers = $definition->getHandlerClasses();
     if (empty($handlers['wizard'][$operation])) {
       throw new \Exception(sprintf('Unsupported wizard operation %s', $operation));
diff --git a/web/modules/ctools/src/Controller/WizardFormController.php b/web/modules/ctools/src/Controller/WizardFormController.php
index e4492a4c91f07f29b28f8c6fbfae42cd4123acf9..d7eb9b85c1cce856ff7c32d0981ccc9a6ce0937d 100644
--- a/web/modules/ctools/src/Controller/WizardFormController.php
+++ b/web/modules/ctools/src/Controller/WizardFormController.php
@@ -6,7 +6,6 @@
 use Drupal\Core\Controller\FormController;
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\ctools\Wizard\FormWizardInterface;
 use Drupal\ctools\Wizard\WizardFactoryInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -25,7 +24,7 @@ class WizardFormController extends FormController {
   /**
    * Tempstore Factory for keeping track of values in each step of the wizard.
    *
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
diff --git a/web/modules/ctools/src/Event/BlockVariantEvent.php b/web/modules/ctools/src/Event/BlockVariantEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..35df32dd36dc37554551088c78cd939531c1e49c
--- /dev/null
+++ b/web/modules/ctools/src/Event/BlockVariantEvent.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\ctools\Event;
+
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\ctools\Plugin\BlockVariantInterface;
+use Symfony\Component\EventDispatcher\Event;
+
+class BlockVariantEvent extends Event {
+
+  /**
+   * The block being acted upon.
+   *
+   * @var \Drupal\Core\Block\BlockPluginInterface
+   */
+  protected $block;
+
+  /**
+   * The variant acting on the block.
+   *
+   * @var \Drupal\ctools\Plugin\BlockVariantInterface
+   */
+  protected $variant;
+
+  /**
+   * BlockVariantEvent constructor.
+   *
+   * @param \Drupal\Core\Block\BlockPluginInterface $block
+   *   The block plugin.
+   * @param \Drupal\ctools\Plugin\BlockVariantInterface $variant
+   *   The variant plugin.
+   */
+  public function __construct(BlockPluginInterface $block, BlockVariantInterface $variant) {
+    $this->block = $block;
+    $this->variant = $variant;
+  }
+
+  /**
+   * Gets the block plugin.
+   *
+   * @return \Drupal\Core\Block\BlockPluginInterface
+   */
+  public function getBlock() {
+    return $this->block;
+  }
+
+  /**
+   * Gets the variant plugin.
+   *
+   * @return \Drupal\ctools\Plugin\BlockVariantInterface
+   */
+  public function getVariant() {
+    return $this->variant;
+  }
+
+}
diff --git a/web/modules/ctools/src/Event/BlockVariantEvents.php b/web/modules/ctools/src/Event/BlockVariantEvents.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea4ec364a8b3578e32956c8a4565e1fd425b66e9
--- /dev/null
+++ b/web/modules/ctools/src/Event/BlockVariantEvents.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\ctools\Event;
+
+/**
+ * Contains all events dispatched while manipulating blocks in a variant.
+ */
+final class BlockVariantEvents {
+
+  /**
+   * The name of the event triggered when a block is added to a variant.
+   *
+   * This event allows modules to react to a block being added to a variant. The
+   * event listener method receives a \Drupal\ctools\Event\BlockVariantEvent
+   * instance.
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const ADD_BLOCK = 'block.add';
+
+  /**
+   * The name of the event triggered when a block is modified in a variant.
+   *
+   * This event allows modules to react to a block being modified in a variant.
+   * The event listener method receives a \Drupal\ctools\Event\BlockVariantEvent
+   * instance.
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const UPDATE_BLOCK = 'block.update';
+
+  /**
+   * The name of the event triggered when a block is removed from a variant.
+   *
+   * This event allows modules to react to a block being removed from a variant.
+   * The event listener method receives a \Drupal\ctools\Event\BlockVariantEvent
+   * instance.
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const DELETE_BLOCK = 'block.delete';
+
+}
diff --git a/web/modules/ctools/src/Form/ConditionConfigure.php b/web/modules/ctools/src/Form/ConditionConfigure.php
index fd2608e13c25c160d7de1ed00ce3ea317aa95957..7a370df4be7805b7b5a856b58209b02e38b46824 100644
--- a/web/modules/ctools/src/Form/ConditionConfigure.php
+++ b/web/modules/ctools/src/Form/ConditionConfigure.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\ctools\Form;
 
-
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Ajax\AjaxResponse;
@@ -12,7 +11,8 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\ctools\ConstraintConditionInterface;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -21,7 +21,7 @@
 abstract class ConditionConfigure extends FormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -44,7 +44,7 @@ abstract class ConditionConfigure extends FormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'), $container->get('plugin.manager.condition'));
+    return new static($container->get('tempstore.shared'), $container->get('plugin.manager.condition'));
   }
 
   function __construct(SharedTempStoreFactory $tempstore, PluginManagerInterface $manager) {
@@ -132,7 +132,8 @@ public function ajaxSave(array &$form, FormStateInterface $form_state) {
     $response = new AjaxResponse();
     $cached_values = $this->tempstore->get($this->tempstore_id)->get($this->machine_name);
     list($route_name, $route_parameters) = $this->getParentRouteInfo($cached_values);
-    $response->addCommand(new RedirectCommand($this->url($route_name, $route_parameters)));
+    $url = Url::fromRoute($route_name, $route_parameters);
+    $response->addCommand(new RedirectCommand($url->toString()));
     $response->addCommand(new CloseModalDialogCommand());
     return $response;
   }
diff --git a/web/modules/ctools/src/Form/ConditionDelete.php b/web/modules/ctools/src/Form/ConditionDelete.php
index d2303bc8617709d2e179c4a7b989193d53073ce5..0655934d952c8625294df1f1fc9ffe67bb85fd09 100644
--- a/web/modules/ctools/src/Form/ConditionDelete.php
+++ b/web/modules/ctools/src/Form/ConditionDelete.php
@@ -8,13 +8,13 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Drupal\ctools\ConstraintConditionInterface;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 abstract class ConditionDelete extends ConfirmFormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -42,7 +42,7 @@ abstract class ConditionDelete extends ConfirmFormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'), $container->get('plugin.manager.condition'));
+    return new static($container->get('tempstore.shared'), $container->get('plugin.manager.condition'));
   }
 
   function __construct(SharedTempStoreFactory $tempstore, PluginManagerInterface $manager) {
diff --git a/web/modules/ctools/src/Form/ContextConfigure.php b/web/modules/ctools/src/Form/ContextConfigure.php
index 142f18abb224c656f39fa5767d490a24e5965781..f5f1c57d5644b2947e10b8dfd047d44dff9a927f 100644
--- a/web/modules/ctools/src/Form/ContextConfigure.php
+++ b/web/modules/ctools/src/Form/ContextConfigure.php
@@ -2,11 +2,9 @@
 
 namespace Drupal\ctools\Form;
 
-
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\CloseModalDialogCommand;
 use Drupal\Core\Ajax\RedirectCommand;
-use Drupal\Core\Entity\Entity;
 use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
@@ -14,23 +12,31 @@
 use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Plugin\Context\ContextInterface;
 use Drupal\Core\Url;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 
 abstract class ContextConfigure extends FormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
+  /**
+   * Object EntityTypeManager.
+   *
+   * @var Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
   /**
    * @var string
    */
   protected $tempstore_id;
 
   /**
-   * @var string;
+   * @var string
    */
   protected $machine_name;
 
@@ -38,11 +44,15 @@ abstract class ContextConfigure extends FormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'));
+    return new static(
+      $container->get('tempstore.shared'),
+      $container->get('entity_type.manager')
+    );
   }
 
-  function __construct(SharedTempStoreFactory $tempstore) {
+  function __construct(SharedTempStoreFactory $tempstore, EntityTypeManagerInterface $entity_type_manager) {
     $this->tempstore = $tempstore;
+    $this->entityTypeManager = $entity_type_manager;
   }
 
   /**
@@ -167,7 +177,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     if (strpos($context_definition->getDataType(), 'entity:') === 0) {
       list(, $entity_type) = explode(':', $context_definition->getDataType());
       if (is_numeric($form_state->getValue('context_value'))) {
-        $value = \Drupal::entityTypeManager()->getStorage($entity_type)->load($form_state->getValue('context_value'));
+        $value = $this->entityTypeManager->getStorage($entity_type)->load($form_state->getValue('context_value'));
       }
     }
     // No loading required for non-entity values.
diff --git a/web/modules/ctools/src/Form/ContextDelete.php b/web/modules/ctools/src/Form/ContextDelete.php
index 06e0794a86fb2122fe270591c687b74ae7d6cdef..ec1406b80bedb48d728232e5219c3dab0860b04b 100644
--- a/web/modules/ctools/src/Form/ContextDelete.php
+++ b/web/modules/ctools/src/Form/ContextDelete.php
@@ -2,10 +2,9 @@
 
 namespace Drupal\ctools\Form;
 
-
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Form\ConfirmFormBase;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -14,7 +13,7 @@
 abstract class ContextDelete extends ConfirmFormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -36,7 +35,7 @@ abstract class ContextDelete extends ConfirmFormBase {
   protected $context_id;
 
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'));
+    return new static($container->get('tempstore.shared'));
   }
 
   public function __construct(SharedTempStoreFactory $tempstore) {
diff --git a/web/modules/ctools/src/Form/ManageConditions.php b/web/modules/ctools/src/Form/ManageConditions.php
index ee506a89db31b68717214a6f5ed7fc2f3005bf35..7e4bcb6483e9ec43993ebb2d4101fbdca3cc33a4 100644
--- a/web/modules/ctools/src/Form/ManageConditions.php
+++ b/web/modules/ctools/src/Form/ManageConditions.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\ctools\Form;
 
-
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Component\Serialization\Json;
 use Drupal\Core\Ajax\AjaxResponse;
@@ -12,6 +11,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Form\FormBuilder;
 
 abstract class ManageConditions extends FormBase {
 
@@ -20,17 +20,28 @@ abstract class ManageConditions extends FormBase {
    */
   protected $manager;
 
+  /**
+   * The builder of form.
+   *
+   * @var \Drupal\Core\Form\FormBuilder
+   */
+  protected $formBuilder;
+
   /**
    * @var string
    */
   protected $machine_name;
 
   public static function create(ContainerInterface $container) {
-    return new static($container->get('plugin.manager.condition'));
+    return new static(
+      $container->get('plugin.manager.condition'),
+      $container->get('form_builder')
+    );
   }
 
-  function __construct(PluginManagerInterface $manager) {
+  function __construct(PluginManagerInterface $manager, FormBuilder $form_builder) {
     $this->manager = $manager;
+    $this->formBuilder = $form_builder;
   }
 
   /**
@@ -91,11 +102,18 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
   public function add(array &$form, FormStateInterface $form_state) {
     $condition = $form_state->getValue('conditions');
-    $content = \Drupal::formBuilder()->getForm($this->getConditionClass(), $condition, $this->getTempstoreId(), $this->machine_name);
+    $content = $this->formBuilder->getForm($this->getConditionClass(), $condition, $this->getTempstoreId(), $this->machine_name);
     $content['#attached']['library'][] = 'core/drupal.dialog.ajax';
     $cached_values = $form_state->getTemporaryValue('wizard');
     list(, $route_parameters) = $this->getOperationsRouteInfo($cached_values, $this->machine_name, $form_state->getValue('conditions'));
-    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]);
+    $route_name = $this->getAddRoute($cached_values);
+    $route_options = [
+      'query' => [
+        FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
+      ],
+    ];
+    $url = Url::fromRoute($route_name, $route_parameters, $route_options);
+    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString();
     $response = new AjaxResponse();
     $response->addCommand(new OpenModalDialogCommand($this->t('Configure Required Context'), $content, array('width' => '700')));
     return $response;
@@ -186,7 +204,7 @@ abstract protected function getTempstoreId();
    * Document the route name and parameters for edit/delete context operations.
    *
    * The route name returned from this method is used as a "base" to which
-   * ".edit" and ".delete" are appeneded in the getOperations() method.
+   * ".edit" and ".delete" are appended in the getOperations() method.
    * Subclassing '\Drupal\ctools\Form\ConditionConfigure' and
    * '\Drupal\ctools\Form\ConditionDelete' should set you up for using this
    * approach quite seamlessly.
diff --git a/web/modules/ctools/src/Form/ManageContext.php b/web/modules/ctools/src/Form/ManageContext.php
index 4ac3b8438f821cbc55e2a6634b0e4cfdb8ac2240..9b9d9cf8f4c157b113520af19e34b652884a4333 100644
--- a/web/modules/ctools/src/Form/ManageContext.php
+++ b/web/modules/ctools/src/Form/ManageContext.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\TypedData\TypedDataManagerInterface;
 use Drupal\Core\Url;
+use Drupal\ctools\TypedDataResolver;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 abstract class ManageContext extends FormBase {
@@ -35,6 +36,13 @@ abstract class ManageContext extends FormBase {
    */
   protected $formBuilder;
 
+  /**
+   * The typed data resolver.
+   *
+   * @var \Drupal\ctools\TypedDataResolver
+   */
+  protected $typedDataResolver;
+
   /**
    * An array of property types that are eligible as relationships.
    *
@@ -49,13 +57,6 @@ abstract class ManageContext extends FormBase {
    */
   protected $relationships = TRUE;
 
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static($container->get('typed_data_manager'), $container->get('form_builder'));
-  }
-
   /**
    * ManageContext constructor.
    *
@@ -63,12 +64,25 @@ public static function create(ContainerInterface $container) {
    *   The typed data manager.
    * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
    *   The form builder.
+   * @param \Drupal\ctools\TypedDataResolver $ctools_typed_data_resolver
+   *   The typed data resolver.
    */
-  public function __construct(TypedDataManagerInterface $typed_data_manager, FormBuilderInterface $form_builder) {
+  public function __construct(TypedDataManagerInterface $typed_data_manager, FormBuilderInterface $form_builder, TypedDataResolver $ctools_typed_data_resolver) {
     $this->typedDataManager = $typed_data_manager;
     $this->formBuilder = $form_builder;
+    $this->typedDataResolver = $ctools_typed_data_resolver;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('typed_data_manager'),
+      $container->get('form_builder'),
+      $container->get('ctools.typed_data.resolver')
+    );
+  }
 
   /**
    * {@inheritdoc}
@@ -160,7 +174,14 @@ public function addContext(array &$form, FormStateInterface $form_state) {
     $content['#attached']['library'][] = 'core/drupal.dialog.ajax';
     $cached_values = $form_state->getTemporaryValue('wizard');
     list(, $route_parameters) = $this->getContextOperationsRouteInfo($cached_values, $this->machine_name, $context);
-    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getContextAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]);
+    $route_name = $this->getContextAddRoute($cached_values);
+    $route_options = [
+      'query' => [
+        FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
+      ],
+    ];
+    $url = Url::fromRoute($route_name, $route_parameters, $route_options);
+    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString();
     $response = new AjaxResponse();
     $response->addCommand(new OpenModalDialogCommand($this->t('Add new context'), $content, array('width' => '700')));
     return $response;
@@ -172,7 +193,14 @@ public function addRelationship(array &$form, FormStateInterface $form_state) {
     $content['#attached']['library'][] = 'core/drupal.dialog.ajax';
     $cached_values = $form_state->getTemporaryValue('wizard');
     list(, $route_parameters) = $this->getRelationshipOperationsRouteInfo($cached_values, $this->machine_name, $relationship);
-    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getRelationshipAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]);
+    $route_name = $this->getRelationshipAddRoute($cached_values);
+    $route_options = [
+      'query' => [
+        FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
+      ],
+    ];
+    $url = Url::fromRoute($route_name, $route_parameters, $route_options);
+    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString();
     $response = new AjaxResponse();
     $response->addCommand(new OpenModalDialogCommand($this->t('Configure Relationship'), $content, array('width' => '700')));
     return $response;
@@ -180,7 +208,7 @@ public function addRelationship(array &$form, FormStateInterface $form_state) {
 
   protected function getAvailableRelationships($cached_values) {
     /** @var \Drupal\ctools\TypedDataResolver $resolver */
-    $resolver = \Drupal::service('ctools.typed_data.resolver');
+    $resolver = $this->typedDataResolver;
     return $resolver->getTokensForContexts($this->getContexts($cached_values));
   }
 
diff --git a/web/modules/ctools/src/Form/ManageResolverRelationships.php b/web/modules/ctools/src/Form/ManageResolverRelationships.php
index 7dfff59825b57cfc214e9a8fa9febe0173597874..bb6bcfb2ef215122e808d831a69dd4af7d4473ca 100644
--- a/web/modules/ctools/src/Form/ManageResolverRelationships.php
+++ b/web/modules/ctools/src/Form/ManageResolverRelationships.php
@@ -9,6 +9,8 @@
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
+use Drupal\ctools\TypedDataResolver;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 abstract class ManageResolverRelationships extends FormBase {
 
@@ -24,6 +26,43 @@ abstract class ManageResolverRelationships extends FormBase {
    */
   protected $property_types = [];
 
+  /**
+   * The typed data resolver.
+   *
+   * @var \Drupal\ctools\TypedDataResolver
+   */
+  protected $typedDataResolver;
+
+  /**
+   * The form builder.
+   *
+   * @var \Drupal\Core\Form\FormBuilder
+   */
+  protected $formBuilder;
+
+  /**
+   * Constructs a new ManageResolverRelationships object.
+   *
+   * @param \Drupal\ctools\TypedDataResolver $ctools_typed_data_resolver
+   *   The typed data resolver.
+   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+   *   The form builder.
+   */
+  public function __construct(TypedDataResolver $ctools_typed_data_resolver, FormBuilderInterface $form_builder) {
+    $this->typedDataResolver = $ctools_typed_data_resolver;
+    $this->formBuilder = $form_builder;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('ctools.typed_data.resolver'),
+      $container->get('form_builder')
+    );
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -80,11 +119,18 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
   public function addRelationship(array &$form, FormStateInterface $form_state) {
     $relationship = $form_state->getValue('relationships');
-    $content = \Drupal::formBuilder()->getForm($this->getContextClass(), $relationship, $this->getTempstoreId(), $this->machine_name);
+    $content = $this->formBuilder->getForm($this->getContextClass(), $relationship, $this->getTempstoreId(), $this->machine_name);
     $content['#attached']['library'][] = 'core/drupal.dialog.ajax';
     $cached_values = $form_state->getTemporaryValue('wizard');
     list(, $route_parameters) = $this->getRelationshipOperationsRouteInfo($cached_values, $this->machine_name, $relationship);
-    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]);
+    $route_name = $this->getAddRoute($cached_values);
+    $route_options = [
+      'query' => [
+        FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
+      ],
+    ];
+    $url = Url::fromRoute($route_name, $route_parameters, $route_options);
+    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString();
     $response = new AjaxResponse();
     $response->addCommand(new OpenModalDialogCommand($this->t('Configure Relationship'), $content, array('width' => '700')));
     return $response;
@@ -92,7 +138,7 @@ public function addRelationship(array &$form, FormStateInterface $form_state) {
 
   protected function getAvailableRelationships($cached_values) {
     /** @var \Drupal\ctools\TypedDataResolver $resolver */
-    $resolver = \Drupal::service('ctools.typed_data.resolver');
+    $resolver = $this->typedDataResolver;
     return $resolver->getTokensForContexts($this->getContexts($cached_values));
   }
 
diff --git a/web/modules/ctools/src/Form/RelationshipConfigure.php b/web/modules/ctools/src/Form/RelationshipConfigure.php
index 4f13f46e860a48fe2c9bbaf7cd52562ad5442195..167ee4cc4db93778864446fc4883f5206fcc7a31 100644
--- a/web/modules/ctools/src/Form/RelationshipConfigure.php
+++ b/web/modules/ctools/src/Form/RelationshipConfigure.php
@@ -8,13 +8,14 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\ctools\TypedDataResolver;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 abstract class RelationshipConfigure extends FormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -37,7 +38,7 @@ abstract class RelationshipConfigure extends FormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'), $container->get('ctools.typed_data.resolver'));
+    return new static($container->get('tempstore.shared'), $container->get('ctools.typed_data.resolver'));
   }
 
   public function __construct(SharedTempStoreFactory $tempstore, TypedDataResolver $resolver) {
@@ -111,7 +112,8 @@ public function ajaxSave(array &$form, FormStateInterface $form_state) {
     $cached_values = $this->tempstore->get($this->tempstore_id)->get($this->machine_name);
     list($route_name, $route_parameters) = $this->getParentRouteInfo($cached_values);
     $response = new AjaxResponse();
-    $response->addCommand(new RedirectCommand($this->url($route_name, $route_parameters)));
+    $url = Url::fromRoute($route_name, $route_parameters);
+    $response->addCommand(new RedirectCommand($url->toString()));
     $response->addCommand(new CloseModalDialogCommand());
     return $response;
   }
diff --git a/web/modules/ctools/src/Form/RequiredContext.php b/web/modules/ctools/src/Form/RequiredContext.php
index 76b5ec2da310bb78745410bea7b001ef3d009486..07ae6be6754f2d95dcbe66d467d940b621d458e9 100644
--- a/web/modules/ctools/src/Form/RequiredContext.php
+++ b/web/modules/ctools/src/Form/RequiredContext.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Form\FormBuilder;
 
 abstract class RequiredContext extends FormBase {
 
@@ -17,6 +18,13 @@ abstract class RequiredContext extends FormBase {
    */
   protected $typedDataManager;
 
+  /**
+   * The builder of form.
+   *
+   * @var \Drupal\Core\Form\FormBuilder
+   */
+  protected $formBuilder;
+
   /**
    * @var string
    */
@@ -26,11 +34,15 @@ abstract class RequiredContext extends FormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('typed_data_manager'));
+    return new static(
+      $container->get('typed_data_manager'),
+      $container->get('form_builder')
+    );
   }
 
-  public function __construct(PluginManagerInterface $typed_data_manager) {
+  public function __construct(PluginManagerInterface $typed_data_manager, FormBuilder $form_builder) {
     $this->typedDataManager = $typed_data_manager;
+    $this->formBuilder = $form_builder;
   }
 
   /**
@@ -98,7 +110,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    */
   public function add(array &$form, FormStateInterface $form_state) {
     $context = $form_state->getValue('contexts');
-    $content = \Drupal::formBuilder()->getForm($this->getContextClass(), $context, $this->getTempstoreId(), $this->machine_name);
+    $content = $this->formBuilder->getForm($this->getContextClass(), $context, $this->getTempstoreId(), $this->machine_name);
     $content['#attached']['library'][] = 'core/drupal.dialog.ajax';
     $response = new AjaxResponse();
     $response->addCommand(new OpenModalDialogCommand($this->t('Configure Required Context'), $content, array('width' => '700')));
@@ -183,7 +195,7 @@ abstract protected function getTempstoreId();
    * Document the route name and parameters for edit/delete context operations.
    *
    * The route name returned from this method is used as a "base" to which
-   * ".edit" and ".delete" are appeneded in the getOperations() method.
+   * ".edit" and ".delete" are appended in the getOperations() method.
    * Subclassing '\Drupal\ctools\Form\ContextConfigure' and
    * '\Drupal\ctools\Form\RequiredContextDelete' should set you up for using
    * this approach quite seamlessly.
diff --git a/web/modules/ctools/src/Form/RequiredContextDelete.php b/web/modules/ctools/src/Form/RequiredContextDelete.php
index e04429a98ff18a3cd7ef906698557d7ea9ddb496..e65ce80f205edd20b8ed05dbc0572345ea91bf2f 100644
--- a/web/modules/ctools/src/Form/RequiredContextDelete.php
+++ b/web/modules/ctools/src/Form/RequiredContextDelete.php
@@ -6,7 +6,7 @@
 use Drupal\Core\Form\ConfirmFormHelper;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -15,7 +15,7 @@
 abstract class RequiredContextDelete extends ConfirmFormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -38,11 +38,11 @@ abstract class RequiredContextDelete extends ConfirmFormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'));
+    return new static($container->get('tempstore.shared'));
   }
 
   /**
-   * @param \Drupal\user\SharedTempStoreFactory $tempstore
+   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
    */
   function __construct(SharedTempStoreFactory $tempstore) {
     $this->tempstore = $tempstore;
diff --git a/web/modules/ctools/src/Form/ResolverRelationshipConfigure.php b/web/modules/ctools/src/Form/ResolverRelationshipConfigure.php
index 00234cd9ab500766f9fe11cb7d1def448f0e5edf..5ec43d5f9148f7373719d35bf4d23720f9cfc40b 100644
--- a/web/modules/ctools/src/Form/ResolverRelationshipConfigure.php
+++ b/web/modules/ctools/src/Form/ResolverRelationshipConfigure.php
@@ -2,19 +2,19 @@
 
 namespace Drupal\ctools\Form;
 
-
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\CloseModalDialogCommand;
 use Drupal\Core\Ajax\RedirectCommand;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 abstract class ResolverRelationshipConfigure extends FormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -32,7 +32,7 @@ abstract class ResolverRelationshipConfigure extends FormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'));
+    return new static($container->get('tempstore.shared'));
   }
 
   function __construct(SharedTempStoreFactory $tempstore) {
@@ -141,7 +141,8 @@ public function ajaxSave(array &$form, FormStateInterface $form_state) {
     $response = new AjaxResponse();
     $cached_values = $this->tempstore->get($this->tempstore_id)->get($this->machine_name);
     list($route_name, $route_parameters) = $this->getParentRouteInfo($cached_values);
-    $response->addCommand(new RedirectCommand($this->url($route_name, $route_parameters)));
+    $url = Url::fromRoute($route_name, $route_parameters);
+    $response->addCommand(new RedirectCommand($url->toString()));
     $response->addCommand(new CloseModalDialogCommand());
     return $response;
   }
diff --git a/web/modules/ctools/src/Form/ResolverRelationshipDelete.php b/web/modules/ctools/src/Form/ResolverRelationshipDelete.php
index 4dfde28560fd9e4a98fad91a8092702ad1267c0b..7195e3cf8003d199933aadb6864109d392ccabbd 100644
--- a/web/modules/ctools/src/Form/ResolverRelationshipDelete.php
+++ b/web/modules/ctools/src/Form/ResolverRelationshipDelete.php
@@ -2,18 +2,17 @@
 
 namespace Drupal\ctools\Form;
 
-
 use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Core\Form\ConfirmFormHelper;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\ctools\TypedDataResolver;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 abstract class ResolverRelationshipDelete extends ConfirmFormBase {
 
   /**
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -41,14 +40,14 @@ abstract class ResolverRelationshipDelete extends ConfirmFormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'), $container->get('ctools.typed_data.resolver'));
+    return new static($container->get('tempstore.shared'), $container->get('ctools.typed_data.resolver'));
   }
 
   /**
-   * @param \Drupal\user\SharedTempStoreFactory $tempstore
+   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
    *   The shared tempstore.
    * @param \Drupal\ctools\TypedDataResolver $resolver
-   *   The the typed data resolver.
+   *   The typed data resolver.
    */
   public function __construct(SharedTempStoreFactory $tempstore, TypedDataResolver $resolver) {
     $this->tempstore = $tempstore;
diff --git a/web/modules/ctools/src/ParamConverter/TempstoreConverter.php b/web/modules/ctools/src/ParamConverter/TempstoreConverter.php
index 6458d90e2c13f2d517eabdd55f7c4912ae525ea8..939bd87d6177bbe8f52e938b43e5fb7f2ef229c9 100644
--- a/web/modules/ctools/src/ParamConverter/TempstoreConverter.php
+++ b/web/modules/ctools/src/ParamConverter/TempstoreConverter.php
@@ -3,10 +3,9 @@
 namespace Drupal\ctools\ParamConverter;
 
 use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\ParamConverter\ParamConverterInterface;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -83,7 +82,7 @@ class TempstoreConverter implements ParamConverterInterface {
   /**
    * The tempstore factory.
    *
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -97,7 +96,7 @@ class TempstoreConverter implements ParamConverterInterface {
   /**
    * Constructs a TempstoreConverter.
    *
-   * @param \Drupal\user\SharedTempStoreFactory $tempstore
+   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
    */
   public function __construct(SharedTempStoreFactory $tempstore, EntityTypeManagerInterface $entity_type_manager) {
     $this->tempstore = $tempstore;
diff --git a/web/modules/ctools/src/Plugin/Block/EntityView.php b/web/modules/ctools/src/Plugin/Block/EntityView.php
index 51342d010c02271d8fa8b3221879c8774190e6c8..3db471cf2204c1e8d45d81831af1345c699ba596 100644
--- a/web/modules/ctools/src/Plugin/Block/EntityView.php
+++ b/web/modules/ctools/src/Plugin/Block/EntityView.php
@@ -4,7 +4,8 @@
 
 use Drupal\Core\Block\BlockBase;
 use Drupal\Core\Cache\CacheableMetadata;
-use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
@@ -21,11 +22,18 @@
 class EntityView extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
 
   /**
-   * The entity manager.
+   * The entity type manager.
    *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
+
+  /**
+   * The entity display repository.
+   *
+   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
+   */
+  protected $entityDisplayRepository;
 
   /**
    * Constructs a new EntityView.
@@ -36,13 +44,16 @@ class EntityView extends BlockBase implements ContextAwarePluginInterface, Conta
    *   The plugin ID for the plugin instance.
    * @param mixed $plugin_definition
    *   The plugin implementation definition.
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
+   *   The entity display repository.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
-    $this->entityManager = $entity_manager;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityDisplayRepository = $entity_display_repository;
   }
 
   /**
@@ -53,7 +64,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('entity.manager')
+      $container->get('entity_type.manager'),
+      $container->get('entity_display.repository')
     );
   }
 
@@ -72,7 +84,7 @@ public function defaultConfiguration() {
   public function blockForm($form, FormStateInterface $form_state) {
     $form['view_mode'] = [
       '#type' => 'select',
-      '#options' => $this->entityManager->getViewModeOptions($this->getDerivativeId()),
+      '#options' => $this->entityDisplayRepository->getViewModeOptions($this->getDerivativeId()),
       '#title' => $this->t('View mode'),
       '#default_value' => $this->configuration['view_mode'],
     ];
@@ -93,7 +105,7 @@ public function build() {
     /** @var $entity \Drupal\Core\Entity\EntityInterface */
     $entity = $this->getContextValue('entity');
 
-    $view_builder = $this->entityManager->getViewBuilder($entity->getEntityTypeId());
+    $view_builder = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId());
     $build = $view_builder->view($entity, $this->configuration['view_mode']);
 
     CacheableMetadata::createFromObject($this->getContext('entity'))
diff --git a/web/modules/ctools/src/Plugin/BlockVariantTrait.php b/web/modules/ctools/src/Plugin/BlockVariantTrait.php
index fa0187073dea75f89973bab1e9049302e0f1bcd5..f368f2c751c20135da5f726855422aecfdfa7994 100644
--- a/web/modules/ctools/src/Plugin/BlockVariantTrait.php
+++ b/web/modules/ctools/src/Plugin/BlockVariantTrait.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\ctools\Plugin;
 
+use Drupal\ctools\Event\BlockVariantEvent;
+use Drupal\ctools\Event\BlockVariantEvents;
+
 /**
  * Provides methods for \Drupal\ctools\Plugin\BlockVariantInterface.
  */
@@ -21,6 +24,13 @@ trait BlockVariantTrait {
    */
   protected $blockPluginCollection;
 
+  /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $eventDispatcher;
+
   /**
    * @see \Drupal\ctools\Plugin\BlockVariantInterface::getRegionNames()
    */
@@ -39,6 +49,12 @@ public function getBlock($block_id) {
   public function addBlock(array $configuration) {
     $configuration['uuid'] = $this->uuidGenerator()->generate();
     $this->getBlockCollection()->addInstanceId($configuration['uuid'], $configuration);
+
+    $block = $this->getBlock($configuration['uuid']);
+    // Allow modules to react to the change.
+    $event = new BlockVariantEvent($block, $this);
+    $this->eventDispatcher()->dispatch(BlockVariantEvents::ADD_BLOCK, $event);
+
     return $configuration['uuid'];
   }
 
@@ -46,7 +62,13 @@ public function addBlock(array $configuration) {
    * @see \Drupal\ctools\Plugin\BlockVariantInterface::removeBlock()
    */
   public function removeBlock($block_id) {
+    $block = $this->getBlock($block_id);
     $this->getBlockCollection()->removeInstanceId($block_id);
+
+    // Allow modules to react to the change.
+    $event = new BlockVariantEvent($block, $this);
+    $this->eventDispatcher()->dispatch(BlockVariantEvents::DELETE_BLOCK, $event);
+
     return $this;
   }
 
@@ -54,8 +76,14 @@ public function removeBlock($block_id) {
    * @see \Drupal\ctools\Plugin\BlockVariantInterface::updateBlock()
    */
   public function updateBlock($block_id, array $configuration) {
-    $existing_configuration = $this->getBlock($block_id)->getConfiguration();
+    $block = $this->getBlock($block_id);
+    $existing_configuration = $block->getConfiguration();
     $this->getBlockCollection()->setInstanceConfiguration($block_id, $configuration + $existing_configuration);
+
+    // Allow modules to react to the change.
+    $event = new BlockVariantEvent($block, $this);
+    $this->eventDispatcher()->dispatch(BlockVariantEvents::UPDATE_BLOCK, $event);
+
     return $this;
   }
 
@@ -112,6 +140,18 @@ protected function getBlockCollection() {
     return $this->blockPluginCollection;
   }
 
+  /**
+   * Gets the event dispatcher.
+   *
+   * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected function eventDispatcher() {
+    if (!$this->eventDispatcher) {
+      $this->eventDispatcher = \Drupal::service('event_dispatcher');
+    }
+    return $this->eventDispatcher;
+  }
+
   /**
    * Returns the UUID generator.
    *
diff --git a/web/modules/ctools/src/Plugin/Deriver/EntityBundle.php b/web/modules/ctools/src/Plugin/Deriver/EntityBundle.php
index 073ab4a897f075a2aeff4ff96de4cf5b7140a7d0..1adf7058077f4e7dd27f8b50220ff49cbd895920 100644
--- a/web/modules/ctools/src/Plugin/Deriver/EntityBundle.php
+++ b/web/modules/ctools/src/Plugin/Deriver/EntityBundle.php
@@ -13,7 +13,7 @@ class EntityBundle extends EntityDeriverBase {
    * {@inheritdoc}
    */
   public function getDerivativeDefinitions($base_plugin_definition) {
-    foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
+    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
       if ($entity_type->hasKey('bundle')) {
         $this->derivatives[$entity_type_id] = $base_plugin_definition;
         $this->derivatives[$entity_type_id]['label'] = $this->getEntityBundleLabel($entity_type);
@@ -43,7 +43,7 @@ protected function getEntityBundleLabel($entity_type) {
     $fallback = $entity_type->getLabel();
     if ($bundle_entity_type = $entity_type->getBundleEntityType()) {
       // This is a better fallback.
-      $fallback =  $this->entityManager->getDefinition($bundle_entity_type)->getLabel();
+      $fallback =  $this->entityTypeManager->getDefinition($bundle_entity_type)->getLabel();
     }
 
     return $this->t('@label bundle', ['@label' => $fallback]);
diff --git a/web/modules/ctools/src/Plugin/Deriver/EntityDeriverBase.php b/web/modules/ctools/src/Plugin/Deriver/EntityDeriverBase.php
index 1adec996536209eb5f0895784ff09efa91927681..9fc99baa2e69b7068e9802f90924a9107e6d913d 100644
--- a/web/modules/ctools/src/Plugin/Deriver/EntityDeriverBase.php
+++ b/web/modules/ctools/src/Plugin/Deriver/EntityDeriverBase.php
@@ -4,7 +4,9 @@
 
 
 use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeRepositoryInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -18,23 +20,43 @@ abstract class EntityDeriverBase extends DeriverBase implements ContainerDeriver
   use StringTranslationTrait;
 
   /**
-   * The entity manager.
+   * The entity type manager.
    *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * The entity type repository.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
+   */
+  protected $entityTypeRepository;
 
   /**
    * Constructs new EntityViewDeriver.
    *
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
+   * @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\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
+   * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository
+   *   The entity type repository.
    */
-  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
-    $this->entityManager = $entity_manager;
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, EntityFieldManagerInterface $entity_field_manager, EntityTypeRepositoryInterface $entity_type_repository) {
+    $this->entityTypeManager = $entity_type_manager;
     $this->stringTranslation = $string_translation;
+    $this->entityFieldManager = $entity_field_manager;
+    $this->entityTypeRepository = $entity_type_repository;
   }
 
   /**
@@ -42,8 +64,10 @@ public function __construct(EntityManagerInterface $entity_manager, TranslationI
    */
   public static function create(ContainerInterface $container, $base_plugin_id) {
     return new static(
-      $container->get('entity.manager'),
-      $container->get('string_translation')
+      $container->get('entity_type.manager'),
+      $container->get('string_translation'),
+      $container->get('entity_field.manager'),
+      $container->get('entity_type.repository')
     );
   }
 
diff --git a/web/modules/ctools/src/Plugin/Deriver/EntityViewDeriver.php b/web/modules/ctools/src/Plugin/Deriver/EntityViewDeriver.php
index f8f189c5fa87ae922463e70fe848a70c0d677cd0..0e2c725e81d5414cfbfb5ed24bbca10c8e8f1292 100644
--- a/web/modules/ctools/src/Plugin/Deriver/EntityViewDeriver.php
+++ b/web/modules/ctools/src/Plugin/Deriver/EntityViewDeriver.php
@@ -13,7 +13,7 @@ class EntityViewDeriver extends EntityDeriverBase {
    * {@inheritdoc}
    */
   public function getDerivativeDefinitions($base_plugin_definition) {
-    foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
+    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
       if ($entity_type->hasViewBuilderClass()) {
         $this->derivatives[$entity_type_id] = $base_plugin_definition;
         $this->derivatives[$entity_type_id]['admin_label'] = $this->t('Entity view (@label)', ['@label' => $entity_type->getLabel()]);
diff --git a/web/modules/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php b/web/modules/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php
index 32c6e86fd0f548b332a5d1afdb3caecd8fdda3ee..eb8615da68ecfa6dcfa8cb19de6b0ac65cc9cd0c 100644
--- a/web/modules/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php
+++ b/web/modules/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php
@@ -4,13 +4,11 @@
 
 use Drupal\Component\Uuid\UuidInterface;
 use Drupal\Core\Block\BlockManager;
-use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Condition\ConditionManager;
 use Drupal\Core\Display\VariantBase;
 use Drupal\Core\Display\ContextAwareVariantInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\Context\ContextHandlerInterface;
-use Drupal\Core\Render\Element;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Utility\Token;
 use Drupal\ctools\Form\AjaxFormTrait;
@@ -21,7 +19,7 @@
 /**
  * Provides a base class for a display variant that simply contains blocks.
  */
-abstract class BlockDisplayVariant extends VariantBase implements ContextAwareVariantInterface, ContainerFactoryPluginInterface, BlockVariantInterface, RefinableCacheableDependencyInterface {
+abstract class BlockDisplayVariant extends VariantBase implements ContextAwareVariantInterface, ContainerFactoryPluginInterface, BlockVariantInterface {
 
   use AjaxFormTrait;
   use BlockVariantTrait;
diff --git a/web/modules/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php b/web/modules/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php
index ce96841d307a31341117f0d981ecddd0b41e8991..bee44c509d3a9ac98b9ee748e7c8c3ba8b3921b1 100644
--- a/web/modules/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php
+++ b/web/modules/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php
@@ -1,6 +1,7 @@
 <?php
 
 namespace Drupal\ctools\Plugin\Relationship;
+
 use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\Context\ContextDefinition;
 
diff --git a/web/modules/ctools/src/Plugin/Relationship/TypedDataLanguageRelationship.php b/web/modules/ctools/src/Plugin/Relationship/TypedDataLanguageRelationship.php
index c198231808cff944876ff81015d81a43f3975beb..17794eb4d1b55a629d5a766177a9441a626dc56a 100644
--- a/web/modules/ctools/src/Plugin/Relationship/TypedDataLanguageRelationship.php
+++ b/web/modules/ctools/src/Plugin/Relationship/TypedDataLanguageRelationship.php
@@ -1,6 +1,7 @@
 <?php
 
 namespace Drupal\ctools\Plugin\Relationship;
+
 use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\Context\ContextDefinition;
 
diff --git a/web/modules/ctools/src/Plugin/Relationship/TypedDataRelationship.php b/web/modules/ctools/src/Plugin/Relationship/TypedDataRelationship.php
index d795f28caef71c45314bec62192ff3d630849350..6c8523b55d8de0da47e245898dcf9d2b01a1631e 100644
--- a/web/modules/ctools/src/Plugin/Relationship/TypedDataRelationship.php
+++ b/web/modules/ctools/src/Plugin/Relationship/TypedDataRelationship.php
@@ -2,9 +2,7 @@
 
 namespace Drupal\ctools\Plugin\Relationship;
 
-
 use Drupal\Core\Field\FieldItemInterface;
-use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
 use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Plugin\Context\ContextInterface;
diff --git a/web/modules/ctools/src/Plugin/RelationshipManagerInterface.php b/web/modules/ctools/src/Plugin/RelationshipManagerInterface.php
index a97fa1ffaa099a007982425d80199144d287e1e9..6de2395b5aebfbfc3a8cf01e51f71dff98a1713d 100644
--- a/web/modules/ctools/src/Plugin/RelationshipManagerInterface.php
+++ b/web/modules/ctools/src/Plugin/RelationshipManagerInterface.php
@@ -1,6 +1,7 @@
 <?php
 
 namespace Drupal\ctools\Plugin;
+
 use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface;
 
diff --git a/web/modules/ctools/src/Routing/Enhancer/WizardEnhancer.php b/web/modules/ctools/src/Routing/Enhancer/WizardEnhancer.php
index 3e125eb9e75d4c4989ec9e6a977acd915161dfc6..e16f6d2648f825f290179fbe034e92759f1c6b70 100644
--- a/web/modules/ctools/src/Routing/Enhancer/WizardEnhancer.php
+++ b/web/modules/ctools/src/Routing/Enhancer/WizardEnhancer.php
@@ -2,33 +2,46 @@
 
 namespace Drupal\ctools\Routing\Enhancer;
 
-use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
+use Drupal\Core\Routing\EnhancerInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 
 /**
  * Sets the request format onto the request object.
  */
-class WizardEnhancer implements RouteEnhancerInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function applies(Route $route) {
-    return !$route->hasDefault('_controller') && ($route->hasDefault('_wizard') || $route->hasDefault('_entity_wizard'));
-  }
+class WizardEnhancer implements EnhancerInterface {
 
   /**
    * {@inheritdoc}
    */
   public function enhance(array $defaults, Request $request) {
+    $route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
+    if (!$this->isApplicable($route)) {
+      return $defaults;
+    }
+
     if (!empty($defaults['_wizard'])) {
       $defaults['_controller'] = 'ctools.wizard.form:getContentResult';
     }
     if (!empty($defaults['_entity_wizard'])) {
       $defaults['_controller'] = 'ctools.wizard.entity.form:getContentResult';
     }
+
     return $defaults;
   }
 
+  /**
+   * Returns if current route use ctools default parameters.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check.
+   *
+   * @return bool
+   *   TRUE if the route use one of ctools route default parameters or FALSE.
+   */
+  public function isApplicable(Route $route) {
+    return !$route->hasDefault('_controller') && ($route->hasDefault('_wizard') || $route->hasDefault('_entity_wizard'));
+  }
+
 }
diff --git a/web/modules/ctools/src/SerializableTempstore.php b/web/modules/ctools/src/SerializableTempstore.php
index 65567edc51be5e5e2aad2f1c79f51a2cfac46304..2fbac0b70ce1d834efaafdc6cd3b6812c1acda4d 100644
--- a/web/modules/ctools/src/SerializableTempstore.php
+++ b/web/modules/ctools/src/SerializableTempstore.php
@@ -3,7 +3,7 @@
 namespace Drupal\ctools;
 
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
-use Drupal\user\SharedTempStore;
+use Drupal\Core\TempStore\SharedTempStore;
 
 /**
  * An extension of the SharedTempStore system for serialized data.
diff --git a/web/modules/ctools/src/SerializableTempstoreFactory.php b/web/modules/ctools/src/SerializableTempstoreFactory.php
index 51cf02b460dd5f018f94c18ab44a9eaa02c6394a..c3e283b9dc813d5c602ef516268fdb966274015f 100644
--- a/web/modules/ctools/src/SerializableTempstoreFactory.php
+++ b/web/modules/ctools/src/SerializableTempstoreFactory.php
@@ -2,25 +2,55 @@
 
 namespace Drupal\ctools;
 
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
+use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * A factory for creating SerializableTempStore objects.
  */
 class SerializableTempstoreFactory extends SharedTempStoreFactory {
 
+  /**
+   * The current logged user.
+   *
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $currentUser;
+
+  /**
+   * Constructs a Drupal\Core\TempStore\SharedTempStoreFactory object.
+   *
+   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $storage_factory
+   *   The key/value store factory.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock_backend
+   *   The lock object used for this data.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   * @param int $expire
+   *   The time to live for items, in seconds.
+   * @param \Drupal\Core\Session\AccountProxyInterface|null $current_user
+   *   The current logged user.
+   */
+  public function __construct(KeyValueExpirableFactoryInterface $storage_factory, LockBackendInterface $lock_backend, RequestStack $request_stack, $expire = 604800, AccountProxyInterface $current_user = NULL) {
+    parent::__construct($storage_factory, $lock_backend, $request_stack, $expire);
+    $this->currentUser = $current_user ?: \Drupal::currentUser();
+  }
+
   /**
    * {@inheritdoc}
    */
-  function get($collection, $owner = NULL) {
+  public function get($collection, $owner = NULL) {
     // Use the currently authenticated user ID or the active user ID unless the
     // owner is overridden.
     if (!isset($owner)) {
-      $owner = \Drupal::currentUser()->id() ?: session_id();
+      $owner = $this->currentUser->id() ?: session_id();
     }
 
     // Store the data for this collection in the database.
-    $storage = $this->storageFactory->get("user.shared_tempstore.$collection");
+    $storage = $this->storageFactory->get("tempstore.shared.$collection");
     return new SerializableTempstore($storage, $this->lockBackend, $owner, $this->requestStack, $this->expire);
   }
 
diff --git a/web/modules/ctools/src/Testing/EntityCreationTrait.php b/web/modules/ctools/src/Testing/EntityCreationTrait.php
index 358f8a17a4f821ec8109662f339472a1a974a11f..6de483d60ae0a8d3978e21a8257ee6401cbb7eb6 100644
--- a/web/modules/ctools/src/Testing/EntityCreationTrait.php
+++ b/web/modules/ctools/src/Testing/EntityCreationTrait.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\ctools\Testing;
 
-
 use Drupal\Component\Render\FormattableMarkup;
 
 trait EntityCreationTrait {
diff --git a/web/modules/ctools/src/Tests/Wizard/CToolsWizardTest.php b/web/modules/ctools/src/Tests/Wizard/CToolsWizardTest.php
index c9b91eada8a020fbfc9335c1a691e0be17defd56..d459dfc38314fbb4c99f4e892b1bdba2c9a03fb7 100644
--- a/web/modules/ctools/src/Tests/Wizard/CToolsWizardTest.php
+++ b/web/modules/ctools/src/Tests/Wizard/CToolsWizardTest.php
@@ -1,43 +1,44 @@
 <?php
 
-namespace Drupal\ctools\Tests\Wizard;
+namespace Drupal\Tests\ctools\Functional;
 
-
-use Drupal\simpletest\WebTestBase;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
-
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests basic wizard functionality.
  *
  * @group ctools
  */
-class CToolsWizardTest extends WebTestBase {
+class CToolsWizardTest extends BrowserTestBase {
 
   use StringTranslationTrait;
-  public static $modules = array('ctools', 'ctools_wizard_test');
+  public static $modules = ['ctools', 'ctools_wizard_test'];
 
-  function testWizardSteps() {
+  /**
+   * Test wizard Multistep form.
+   */
+  public function testWizardSteps() {
     $this->drupalGet('ctools/wizard');
-    $this->assertText('Form One');
+    $this->assertSession()->pageTextContains('Form One');
     $this->dumpHeaders = TRUE;
     // Check that $operations['one']['values'] worked.
-    $this->assertText('Xylophone');
+    $this->assertSession()->pageTextContains('Xylophone');
     // Submit first step in the wizard.
     $edit = [
       'one' => 'test',
     ];
     $this->drupalPostForm('ctools/wizard', $edit, $this->t('Next'));
     // Redirected to the second step.
-    $this->assertText('Form Two');
-    $this->assertText('Dynamic value submitted: Xylophone');
+    $this->assertSession()->pageTextContains('Form Two');
+    $this->assertSession()->pageTextContains('Dynamic value submitted: Xylophone');
     // Check that $operations['two']['values'] worked.
-    $this->assertText('Zebra');
+    $this->assertSession()->pageTextContains('Zebra');
     // Hit previous to make sure our form value are preserved.
     $this->drupalPostForm(NULL, [], $this->t('Previous'));
     // Check the known form values.
-    $this->assertFieldByName('one', 'test');
-    $this->assertText('Xylophone');
+    $this->assertSession()->fieldValueEquals('one', 'test');
+    $this->assertSession()->pageTextContains('Xylophone');
     // Goto next step again and finish this wizard.
     $this->drupalPostForm(NULL, [], $this->t('Next'));
     $edit = [
@@ -45,44 +46,50 @@ function testWizardSteps() {
     ];
     $this->drupalPostForm(NULL, $edit, $this->t('Finish'));
     // Check that the wizard finished properly.
-    $this->assertText('Value One: test');
-    $this->assertText('Value Two: Second test');
+    $this->assertSession()->pageTextContains('Value One: test');
+    $this->assertSession()->pageTextContains('Value Two: Second test');
   }
 
-  function testStepValidateAndSubmit() {
+  /**
+   * Test wizard validate and submit.
+   */
+  public function testStepValidateAndSubmit() {
     $this->drupalGet('ctools/wizard');
-    $this->assertText('Form One');
+    $this->assertSession()->pageTextContains('Form One');
     // Submit first step in the wizard.
     $edit = [
       'one' => 'wrong',
     ];
     $this->drupalPostForm('ctools/wizard', $edit, $this->t('Next'));
     // We're still on the first form and the error is present.
-    $this->assertText('Form One');
-    $this->assertText('Cannot set the value to "wrong".');
+    $this->assertSession()->pageTextContains('Form One');
+    $this->assertSession()->pageTextContains('Cannot set the value to "wrong".');
     // Try again with the magic value.
     $edit = [
       'one' => 'magic',
     ];
     $this->drupalPostForm('ctools/wizard', $edit, $this->t('Next'));
     // Redirected to the second step.
-    $this->assertText('Form Two');
+    $this->assertSession()->pageTextContains('Form Two');
     $edit = [
       'two' => 'Second test',
     ];
     $this->drupalPostForm(NULL, $edit, $this->t('Finish'));
     // Check that the magic value triggered our submit callback.
-    $this->assertText('Value One: Abraham');
-    $this->assertText('Value Two: Second test');
+    $this->assertSession()->pageTextContains('Value One: Abraham');
+    $this->assertSession()->pageTextContains('Value Two: Second test');
   }
 
-  function testEntityWizard() {
+  /**
+   * Test wizard entity config update.
+   */
+  public function testEntityWizard() {
     $this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
 
     // Start adding a new config entity.
     $this->drupalGet('admin/structure/ctools_wizard_test_config_entity/add');
-    $this->assertText('Example entity');
-    $this->assertNoText('Existing entity');
+    $this->assertSession()->pageTextContains('Example entity');
+    $this->assertSession()->pageTextNotContains('Existing entity');
 
     // Submit the general step.
     $edit = [
@@ -104,33 +111,32 @@ function testEntityWizard() {
     $this->drupalPostForm(NULL, $edit, $this->t('Finish'));
 
     // Now we should be looking at the list of entities.
-    $this->assertUrl('admin/structure/ctools_wizard_test_config_entity');
-    $this->assertText('Test Config Entity 123');
+    $this->assertSession()->addressEquals('admin/structure/ctools_wizard_test_config_entity');
+    $this->assertSession()->pageTextContains('Test Config Entity 123');
 
     // Edit the entity again and make sure the values are what we expect.
     $this->clickLink(t('Edit'));
-    $this->assertText('Existing entity');
-    $this->assertFieldByName('label', 'Test Config Entity 123');
+    $this->assertSession()->pageTextContains('Existing entity');
+    $this->assertSession()->fieldValueEquals('label', 'Test Config Entity 123');
     $this->clickLink(t('Form One'));
-    $this->assertFieldByName('one', 'The first bit');
+    $this->assertSession()->fieldValueEquals('one', 'The first bit');
     $previous = $this->getUrl();
     $this->clickLink(t('Show on dialog'));
-    $this->assertRaw('Value from one: The first bit');
+    $this->assertSession()->responseContains('Value from one: The first bit');
     $this->drupalGet($previous);
     // Change the value for 'one'.
     $this->drupalPostForm(NULL, ['one' => 'New value'], $this->t('Next'));
-    $this->assertFieldByName('two', 'The second bit');
+    $this->assertSession()->fieldValueEquals('two', 'The second bit');
     $this->drupalPostForm(NULL, [], $this->t('Next'));
     // Make sure we get the additional step because the entity exists.
-    $this->assertText('This step only shows if the entity is already existing!');
+    $this->assertSession()->pageTextContains('This step only shows if the entity is already existing!');
     $this->drupalPostForm(NULL, [], $this->t('Finish'));
 
     // Edit the entity again and make sure the change stuck.
-    $this->assertUrl('admin/structure/ctools_wizard_test_config_entity');
+    $this->assertSession()->addressEquals('admin/structure/ctools_wizard_test_config_entity');
     $this->clickLink(t('Edit'));
-    $this->drupalPostForm(NULL, [], $this->t('Next'));
-    $this->assertFieldByName('one', 'New value');
+    $this->clickLink(t('Form One'));
+    $this->assertSession()->fieldValueEquals('one', 'New value');
   }
 
 }
-
diff --git a/web/modules/ctools/src/TypedDataResolver.php b/web/modules/ctools/src/TypedDataResolver.php
index 737ab3ce10e477da73d1f56319a98515cff32543..f06ec419cb37485090577564852f097eb8f5a534 100644
--- a/web/modules/ctools/src/TypedDataResolver.php
+++ b/web/modules/ctools/src/TypedDataResolver.php
@@ -2,11 +2,8 @@
 
 namespace Drupal\ctools;
 
-
-use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Plugin\Context\Context;
 use Drupal\Core\Plugin\Context\ContextDefinition;
-use Drupal\Core\Plugin\Context\ContextDefinitionInterface;
 use Drupal\Core\Plugin\Context\ContextInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
diff --git a/web/modules/ctools/src/Wizard/EntityFormWizardBase.php b/web/modules/ctools/src/Wizard/EntityFormWizardBase.php
index 1647f734aca55aea4690bb0c3f24c0da6de922ee..9cac6854b9ec4bf5392f7ceb5ee2a9870ef57f89 100644
--- a/web/modules/ctools/src/Wizard/EntityFormWizardBase.php
+++ b/web/modules/ctools/src/Wizard/EntityFormWizardBase.php
@@ -2,14 +2,13 @@
 
 namespace Drupal\ctools\Wizard;
 
-
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
-use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\ctools\Event\WizardEvent;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
@@ -18,14 +17,14 @@
 abstract class EntityFormWizardBase extends FormWizardBase implements EntityFormWizardInterface {
 
   /**
-   * The entity manager.
+   * The entity type manager.
    *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
 
   /**
-   * @param \Drupal\user\SharedTempStoreFactory $tempstore
+   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
    *   Tempstore Factory for keeping track of values in each step of the
    *   wizard.
    * @param \Drupal\Core\Form\FormBuilderInterface $builder
@@ -34,8 +33,8 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm
    *   The class resolver.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
    *   The event dispatcher.
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    * @param $tempstore_id
    *   The shared temp store factory collection name.
    * @param null $machine_name
@@ -43,8 +42,8 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm
    * @param null $step
    *   The current active step of the wizard.
    */
-  public function __construct(SharedTempStoreFactory $tempstore, FormBuilderInterface $builder, ClassResolverInterface $class_resolver, EventDispatcherInterface $event_dispatcher, EntityManagerInterface $entity_manager, RouteMatchInterface $route_match, $tempstore_id, $machine_name = NULL, $step = NULL) {
-    $this->entityManager = $entity_manager;
+  public function __construct(SharedTempStoreFactory $tempstore, FormBuilderInterface $builder, ClassResolverInterface $class_resolver, EventDispatcherInterface $event_dispatcher, EntityTypeManagerInterface $entity_type_manager, RouteMatchInterface $route_match, $tempstore_id, $machine_name = NULL, $step = NULL) {
+    $this->entityTypeManager = $entity_type_manager;
     parent::__construct($tempstore, $builder, $class_resolver, $event_dispatcher, $route_match, $tempstore_id, $machine_name, $step);
   }
 
@@ -53,11 +52,14 @@ public function __construct(SharedTempStoreFactory $tempstore, FormBuilderInterf
    */
   public static function getParameters() {
     return [
-      'tempstore' => \Drupal::service('user.shared_tempstore'),
+      'tempstore' => \Drupal::service('tempstore.shared'),
       'builder' => \Drupal::service('form_builder'),
       'class_resolver' => \Drupal::service('class_resolver'),
       'event_dispatcher' => \Drupal::service('event_dispatcher'),
+      // Keep the deprecated entity manager service as a parameter as well for
+      // BC, so that subclasses still work.
       'entity_manager' => \Drupal::service('entity.manager'),
+      'entity_type_manager' => \Drupal::service('entity_type.manager'),
     ];
   }
 
@@ -65,7 +67,7 @@ public static function getParameters() {
    * {@inheritdoc}
    */
   public function initValues() {
-    $storage = $this->entityManager->getStorage($this->getEntityType());
+    $storage = $this->entityTypeManager->getStorage($this->getEntityType());
     if ($this->getMachineName()) {
       $values = $this->getTempstore()->get($this->getMachineName());
       if (!$values) {
@@ -89,24 +91,25 @@ public function initValues() {
    */
   public function finish(array &$form, FormStateInterface $form_state) {
     $cached_values = $form_state->getTemporaryValue('wizard');
-    /** @var $entity \Drupal\Core\Entity\EntityInterface */
+    /** @var \Drupal\Core\Entity\EntityInterface $entity */
     $entity = $cached_values[$this->getEntityType()];
     $entity->set('id', $cached_values['id']);
     $entity->set('label', $cached_values['label']);
     $status = $entity->save();
-    $definition = $this->entityManager->getDefinition($this->getEntityType());
-    if ($status) {
-      drupal_set_message($this->t('Saved the %label @entity_type.', array(
-        '%label' => $entity->label(),
-        '@entity_type' => $definition->getLabel(),
-      )));
+
+    $arguments = [
+      '@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
+      '%label' => $entity->label(),
+    ];
+    if ($status === SAVED_UPDATED) {
+      $this->messenger()->addMessage($this->t('The @entity-type %label has been updated.', $arguments));
+      $this->logger($entity->getEntityType()->getProvider())->notice('Updated @entity-type %label.', $arguments);
     }
-    else {
-      drupal_set_message($this->t('The %label @entity_type was not saved.', array(
-        '%label' => $entity->label(),
-        '@entity_type' => $definition->getLabel(),
-      )));
+    elseif ($status === SAVED_NEW) {
+      $this->messenger()->addMessage($this->t('The @entity-type %label has been added.', $arguments));
+      $this->logger($entity->getEntityType()->getProvider())->notice('Added @entity-type %label.', $arguments);
     }
+
     $form_state->setRedirectUrl($entity->toUrl('collection'));
     parent::finish($form, $form_state);
   }
@@ -122,7 +125,7 @@ public function finish(array &$form, FormStateInterface $form_state) {
   protected function customizeForm(array $form, FormStateInterface $form_state) {
     $form = parent::customizeForm($form, $form_state);
     if ($this->machine_name) {
-      $entity = $this->entityManager->getStorage($this->getEntityType())
+      $entity = $this->entityTypeManager->getStorage($this->getEntityType())
         ->load($this->machine_name);
     }
     else {
@@ -145,7 +148,7 @@ protected function customizeForm(array $form, FormStateInterface $form_state) {
     $default_operation = reset($operations);
     if ($operation['form'] == $default_operation['form']) {
       // Get the plugin definition of this entity.
-      $definition = $this->entityManager->getDefinition($this->getEntityType());
+      $definition = $this->entityTypeManager->getDefinition($this->getEntityType());
       // Create id and label form elements.
       $form['name'] = array(
         '#type' => 'fieldset',
diff --git a/web/modules/ctools/src/Wizard/FormWizardBase.php b/web/modules/ctools/src/Wizard/FormWizardBase.php
index e537ef804fe2da27e204eaff39dcb07c88842e9c..df8048e6c9bbcce90d844314640be3e9c13bff8f 100644
--- a/web/modules/ctools/src/Wizard/FormWizardBase.php
+++ b/web/modules/ctools/src/Wizard/FormWizardBase.php
@@ -13,7 +13,8 @@
 use Drupal\Core\Url;
 use Drupal\ctools\Ajax\OpenModalWizardCommand;
 use Drupal\ctools\Event\WizardEvent;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
@@ -24,7 +25,7 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface {
   /**
    * Tempstore Factory for keeping track of values in each step of the wizard.
    *
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -71,7 +72,7 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface {
   protected $step;
 
   /**
-   * @param \Drupal\user\SharedTempStoreFactory $tempstore
+   * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
    *   Tempstore Factory for keeping track of values in each step of the
    *   wizard.
    * @param \Drupal\Core\Form\FormBuilderInterface $builder
@@ -103,7 +104,7 @@ public function __construct(SharedTempStoreFactory $tempstore, FormBuilderInterf
    */
   public static function getParameters() {
     return [
-      'tempstore' => \Drupal::service('user.shared_tempstore'),
+      'tempstore' => \Drupal::service('tempstore.shared'),
       'builder' => \Drupal::service('form_builder'),
       'class_resolver' => \Drupal::service('class_resolver'),
       'event_dispatcher' => \Drupal::service('event_dispatcher'),
@@ -131,7 +132,8 @@ public function getTempstoreId() {
    * {@inheritdoc}
    */
   public function getTempstore() {
-    return $this->tempstore->get($this->getTempstoreId());
+    $tempstore = $this->tempstore->get($this->getTempstoreId());
+    return $tempstore;
   }
 
   /**
@@ -393,7 +395,7 @@ protected function actions(FormInterface $form_object, FormStateInterface $form_
       $actions['submit']['#ajax'] = [
         'callback' => '::ajaxSubmit',
         'url' => Url::fromRoute($this->getRouteName(), $parameters),
-        'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
+        'options' => ['query' => $this->getRequest()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
       ];
     }
 
@@ -419,7 +421,7 @@ protected function actions(FormInterface $form_object, FormStateInterface $form_
         $actions['previous']['#ajax'] = [
           'callback' => '::ajaxPrevious',
           'url' => Url::fromRoute($this->getRouteName(), $parameters),
-          'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
+          'options' => ['query' => $this->getRequest()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
         ];
       }
     }
diff --git a/web/modules/ctools/src/Wizard/FormWizardInterface.php b/web/modules/ctools/src/Wizard/FormWizardInterface.php
index 54043c3ba26945e3109b29354e180fac20c9596b..53ccc48fc58274031bea512e71b4a5391f19571a 100644
--- a/web/modules/ctools/src/Wizard/FormWizardInterface.php
+++ b/web/modules/ctools/src/Wizard/FormWizardInterface.php
@@ -39,7 +39,7 @@ public function getTempstoreId();
   /**
    * The active SharedTempStore for this wizard.
    *
-   * @return \Drupal\user\SharedTempStore
+   * @return \Drupal\Core\TempStore\SharedTempStore
    */
   public function getTempstore();
 
diff --git a/web/modules/ctools/src/Wizard/WizardFactory.php b/web/modules/ctools/src/Wizard/WizardFactory.php
index 27522d066c1f8ed1eda7775089a91dbf5efe7dd4..4edfe8a4df9523635596524feb103621c7332ad3 100644
--- a/web/modules/ctools/src/Wizard/WizardFactory.php
+++ b/web/modules/ctools/src/Wizard/WizardFactory.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Form\FormState;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Drupal\Core\Render\Renderer;
 
 class WizardFactory implements WizardFactoryInterface {
 
@@ -23,14 +24,26 @@ class WizardFactory implements WizardFactoryInterface {
   protected $dispatcher;
 
   /**
+   * The object renderer.
+   *
+   * @var \Drupal\Core\Render\Renderer
+   */
+  protected $renderer;
+
+  /**
+   * The construct method.
+   *
    * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
    *   The form builder.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
    *   The event dispatcher.
+   * @param \Drupal\Core\Render\Renderer $renderer
+   *   The object renderer.
    */
-  public function __construct(FormBuilderInterface $form_builder, EventDispatcherInterface $event_dispatcher) {
+  public function __construct(FormBuilderInterface $form_builder, EventDispatcherInterface $event_dispatcher, Renderer $renderer) {
     $this->builder = $form_builder;
     $this->dispatcher = $event_dispatcher;
+    $this->renderer = $renderer;
   }
 
   /**
@@ -42,9 +55,8 @@ public function getWizardForm(FormWizardInterface $wizard, array $parameters = [
 
     if ($ajax) {
       $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
-      $status_messages = array('#type' => 'status_messages');
-      // @todo properly inject the renderer. Core should really be doing this work.
-      if ($messages = \Drupal::service('renderer')->renderRoot($status_messages)) {
+      $status_messages = ['#type' => 'status_messages'];
+      if ($messages = $this->renderer->renderRoot($status_messages)) {
         if (!empty($form['#prefix'])) {
           // Form prefix is expected to be a string. Prepend the messages to
           // that string.
diff --git a/web/modules/ctools/src/Wizard/WizardFactoryInterface.php b/web/modules/ctools/src/Wizard/WizardFactoryInterface.php
index 34b00a8f8aebaf34444469df81c1eece9b174c72..d9b3c8e7163156f89d667e506734d4da18f7cc51 100644
--- a/web/modules/ctools/src/Wizard/WizardFactoryInterface.php
+++ b/web/modules/ctools/src/Wizard/WizardFactoryInterface.php
@@ -1,4 +1,5 @@
 <?php
+
 namespace Drupal\ctools\Wizard;
 
 interface WizardFactoryInterface {
diff --git a/web/modules/ctools/tests/modules/ctools_block_display_test/ctools_block_display_test.info.yml b/web/modules/ctools/tests/modules/ctools_block_display_test/ctools_block_display_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bfc9fb5587eeb1f0ebfbb7fd6f9be2d4a621dc46
--- /dev/null
+++ b/web/modules/ctools/tests/modules/ctools_block_display_test/ctools_block_display_test.info.yml
@@ -0,0 +1,14 @@
+name: Block Display Variant Test
+type: module
+description: 'Testing infrastructure for CTools block display variants.'
+package: Testing
+# version: 3.x
+# core: 8.x
+dependencies:
+  - ctools:ctools
+
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
+core: '8.x'
+project: 'ctools'
+datestamp: 1550728390
diff --git a/web/modules/ctools/tests/modules/ctools_block_display_test/src/Plugin/DisplayVariant/BlockDisplayVariant.php b/web/modules/ctools/tests/modules/ctools_block_display_test/src/Plugin/DisplayVariant/BlockDisplayVariant.php
new file mode 100644
index 0000000000000000000000000000000000000000..2aa254b3541cd36a93a0ff89a2832972b82b7e59
--- /dev/null
+++ b/web/modules/ctools/tests/modules/ctools_block_display_test/src/Plugin/DisplayVariant/BlockDisplayVariant.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\ctools_block_display_test\Plugin\DisplayVariant;
+
+use Drupal\ctools\Plugin\DisplayVariant\BlockDisplayVariant as BaseBlockDisplayVariant;
+
+class BlockDisplayVariant extends BaseBlockDisplayVariant {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRegionNames() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [];
+  }
+
+}
diff --git a/web/modules/ctools/tests/modules/ctools_wizard_test/ctools_wizard_test.info.yml b/web/modules/ctools/tests/modules/ctools_wizard_test/ctools_wizard_test.info.yml
index 76302ba10f20e1b2b7a4c2bfe0b89245d619b4e1..2a351d203331dcfb3c5305ab0827c5d657c48931 100644
--- a/web/modules/ctools/tests/modules/ctools_wizard_test/ctools_wizard_test.info.yml
+++ b/web/modules/ctools/tests/modules/ctools_wizard_test/ctools_wizard_test.info.yml
@@ -5,8 +5,8 @@ package: Testing
 # version: 3.x
 # core: 8.x
 
-# Information added by Drupal.org packaging script on 2017-04-28
-version: '8.x-3.0'
+# Information added by Drupal.org packaging script on 2019-02-21
+version: '8.x-3.2'
 core: '8.x'
 project: 'ctools'
-datestamp: 1493401747
+datestamp: 1550728390
diff --git a/web/modules/ctools/tests/modules/ctools_wizard_test/src/ExampleConfigEntityListBuilder.php b/web/modules/ctools/tests/modules/ctools_wizard_test/src/ExampleConfigEntityListBuilder.php
index 47d3ecf663db11286ca1367c37c0eef0053ad1e9..d99c6b5f1fb57dd9ab49587b6ff8ff8e3baa8eb4 100644
--- a/web/modules/ctools/tests/modules/ctools_wizard_test/src/ExampleConfigEntityListBuilder.php
+++ b/web/modules/ctools/tests/modules/ctools_wizard_test/src/ExampleConfigEntityListBuilder.php
@@ -22,7 +22,7 @@ public function buildHeader() {
    * {@inheritdoc}
    */
   public function buildRow(EntityInterface $entity) {
-    $row['label'] = $this->getLabel($entity);
+    $row['label'] = $entity->label();
     $row['id'] = $entity->id();
     // You probably want a few more properties here...
     return $row + parent::buildRow($entity);
diff --git a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php
index 487751d92f47f6f921fee70acf56b4f210eff709..035034ca474f8e01b82a669be9e6880d7a6defc8 100644
--- a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php
+++ b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php
@@ -36,8 +36,7 @@ public function getConfirmText() {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $this->entity->delete();
-
-    drupal_set_message(
+    $this->messenger()->addMessage(
       $this->t('content @type: deleted @label.',
         [
           '@type' => $this->entity->bundle(),
diff --git a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php
index 2ba871c283fecb9fdcebbbccd4b2c3b022939e1d..d82b80c4c10e5e74926f055baaf70661df691ad3 100644
--- a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php
+++ b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php
@@ -2,18 +2,20 @@
 
 namespace Drupal\ctools_wizard_test\Form;
 
-
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\user\SharedTempStoreFactory;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
+/**
+ * Example form config entity.
+ */
 class ExampleConfigEntityExternalForm extends FormBase {
 
   /**
    * Tempstore factory.
    *
-   * @var \Drupal\user\SharedTempStoreFactory
+   * @var \Drupal\Core\TempStore\SharedTempStoreFactory
    */
   protected $tempstore;
 
@@ -21,8 +23,9 @@ class ExampleConfigEntityExternalForm extends FormBase {
    * Constructs a new ExampleConfigEntityExternalForm.
    *
    * @param \Drupal\ctools_wizard_test\Form\SharedTempStoreFactory $tempstore
+   *   Creates a shared temporary storage for a collection.
    */
-  function __construct(SharedTempStoreFactory $tempstore) {
+  public function __construct(SharedTempStoreFactory $tempstore) {
     $this->tempstore = $tempstore;
   }
 
@@ -30,7 +33,7 @@ function __construct(SharedTempStoreFactory $tempstore) {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('user.shared_tempstore'));
+    return new static($container->get('tempstore.shared'));
   }
 
   /**
@@ -63,4 +66,3 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
   }
 
 }
-
diff --git a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php
index 037505637dd0be0b4efd653f226c85d9d8610d43..6ee5d67ef4067a208550d8a509e5db77c0c450ca 100644
--- a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php
+++ b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php
@@ -64,8 +64,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       $cached_values[$key] = $form_state->getValue($key);
     }
     $form_state->setTemporaryValue('wizard', $cached_values);
-
-    drupal_set_message($this->t('Dynamic value submitted: @value', ['@value' => $cached_values['dynamic']]));;
+    $this->messenger()->addMessage($this->t('Dynamic value submitted: @value', ['@value' => $cached_values['dynamic']]));
   }
 
 }
diff --git a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php
index 539df331d241bd1b5006cacaba970fd7cb2b963c..49a3ce597f5a97ac2cf6cca4f8779a2e8e1e60ff 100644
--- a/web/modules/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php
+++ b/web/modules/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php
@@ -74,8 +74,8 @@ public function getRouteName() {
    */
   public function finish(array &$form, FormStateInterface $form_state) {
     $cached_values = $form_state->getTemporaryValue('wizard');
-    drupal_set_message($this->t('Value One: @one', ['@one' => $cached_values['one']]));
-    drupal_set_message($this->t('Value Two: @two', ['@two' => $cached_values['two']]));
+    $this->messenger()->addMessage($this->t('Value One: @one', ['@one' => $cached_values['one']]));
+    $this->messenger()->addMessage($this->t('Value Two: @two', ['@two' => $cached_values['two']]));
     parent::finish($form, $form_state);
   }
 
diff --git a/web/modules/ctools/tests/src/Kernel/BlockDisplayVariantTest.php b/web/modules/ctools/tests/src/Kernel/BlockDisplayVariantTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..01e4626650a6f1cb7162e33dea87e95ecd3244d0
--- /dev/null
+++ b/web/modules/ctools/tests/src/Kernel/BlockDisplayVariantTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\Tests\ctools\Kernel;
+
+use Drupal\ctools\Event\BlockVariantEvent;
+use Drupal\ctools\Event\BlockVariantEvents;
+use Drupal\ctools_block_display_test\Plugin\DisplayVariant\BlockDisplayVariant;
+use Drupal\KernelTests\KernelTestBase;
+use Prophecy\Argument;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @coversDefaultClass \Drupal\ctools\Plugin\DisplayVariant\BlockDisplayVariant
+ * @group CTools
+ */
+class BlockDisplayVariantTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ctools', 'ctools_block_display_test', 'system', 'user'];
+
+  /**
+   * Tests that events are fired when manipulating a block variant.
+   */
+  public function testBlockDisplayVariantEvents() {
+    /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */
+    $event_dispatcher = $this->prophesize(EventDispatcherInterface::class);
+    // Swap in a mock event dispatcher so we can spy on method calls.
+    $this->container->set('event_dispatcher', $event_dispatcher->reveal());
+
+    $variant = BlockDisplayVariant::create(
+      $this->container,
+      [],
+      'foobar',
+      []
+    );
+    // Set up the expected calls to the event dispatcher.
+    $event = Argument::type(BlockVariantEvent::class);
+
+    $event_dispatcher->dispatch(BlockVariantEvents::ADD_BLOCK, $event)
+      ->shouldBeCalled();
+    $event_dispatcher->dispatch(BlockVariantEvents::UPDATE_BLOCK, $event)
+      ->shouldBeCalled();
+    $event_dispatcher->dispatch(BlockVariantEvents::DELETE_BLOCK, $event)
+      ->shouldBeCalled();
+
+    $block_id = $variant->addBlock(['id' => 'system_powered_by_block']);
+    $variant->updateBlock($block_id, []);
+    $variant->removeBlock($block_id);
+  }
+
+}
diff --git a/web/modules/ctools/tests/src/Kernel/RelationshipManagerTest.php b/web/modules/ctools/tests/src/Kernel/RelationshipManagerTest.php
index 94ca6d9c062a249a49c43478e96b855d7ed39056..909c52c683f6ccdb6ab73a299cdc6de3d550ba84 100644
--- a/web/modules/ctools/tests/src/Kernel/RelationshipManagerTest.php
+++ b/web/modules/ctools/tests/src/Kernel/RelationshipManagerTest.php
@@ -6,7 +6,7 @@
 use Drupal\Core\Plugin\Context\ContextDefinition;
 
 /**
- * @coversDefaultClass \Drupal\ctools\Plugin\RelationshipManagerInterface
+ * @coversDefaultClass \Drupal\ctools\Plugin\RelationshipManager
  * @group CTools
  */
 class RelationshipManagerTest extends RelationshipsTestBase {
diff --git a/web/modules/ctools/views_content/views_content.admin.inc b/web/modules/ctools/views_content/views_content.admin.inc
new file mode 100644
index 0000000000000000000000000000000000000000..589cf42b67cbcb3e580190c4bc0144a9a794bdf7
--- /dev/null
+++ b/web/modules/ctools/views_content/views_content.admin.inc
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * File for admin helper functions for views_content module.
+ */
diff --git a/web/modules/link_attributes/README.md b/web/modules/link_attributes/README.md
index 734f823de49f024aa68211f14a5b1216c0885121..64067f2a463f815c76334838e7d2fb15bf2a18a5 100644
--- a/web/modules/link_attributes/README.md
+++ b/web/modules/link_attributes/README.md
@@ -1,9 +1,57 @@
-## Link attributes
+CONTENTS OF THIS FILE
+---------------------
 
-Provides an alternate widget for the link field that allows setting attributes.
+ * Introduction
+ * Requirements
+ * Installation
+ * Configuration
+ * Maintainers
 
-By default adds class, rel and target attributes to menu link content entitites.
 
-## Known issues
+INTRODUCTION
+------------
 
-You need [this core patch](https://www.drupal.org/files/issues/2760557-link-options.pass_.patch).
+The link attributes module provides a widget that allows users to add
+attributes to links. It overtakes the core default widget for menu link
+content entities, allowing you to set attributes on menu links.
+
+ * For a full description of the module, visit the project page:
+   https://www.drupal.org/project/link_attributes
+
+
+REQUIREMENTS
+------------
+
+This module requires no modules outside of Drupal core.
+
+
+INSTALLATION
+------------
+
+ * Install as you would normally install a contributed Drupal module. Visit:
+   https://www.drupal.org/documentation/install/modules-themes/modules-8 for
+   further information.
+
+
+CONFIGURATION
+-------------
+
+The module has no menu or modifiable settings. There is no configuration. Once
+enabled, the module will add class, rel and target attributes to menu link
+content entities by default.
+
+In order to use this functionality, follow the following steps:
+
+ * Enable the module like normal
+ * It will immediately take effect on *menu link content* entities.
+ * For other link fields, edit the widget using 'Manage form display'
+   and select the 'Link (with attributes)' widget
+
+You can also follow along with this [screencast](https://vimeo.com/233507094) to
+configure the field settings.
+
+
+MAINTAINERS
+-----------
+
+ * Lee Rowlands (larowlan) - https://www.drupal.org/u/larowlan
diff --git a/web/modules/link_attributes/link_attributes.api.php b/web/modules/link_attributes/link_attributes.api.php
new file mode 100644
index 0000000000000000000000000000000000000000..783d616df2192c6b0925f5e85a4905045b38a531
--- /dev/null
+++ b/web/modules/link_attributes/link_attributes.api.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Hooks related to the Link Attributes module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Modify the definitions of link attribute plugins.
+ *
+ * @param array[] $plugins
+ *   Link attribute plugin definitions.
+ */
+function hook_link_attributes_plugin_alter(array &$plugins) {
+  // Set a default value for the target attribute.
+  $plugins['target']['default_value'] = '_blank';
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/web/modules/link_attributes/link_attributes.info.yml b/web/modules/link_attributes/link_attributes.info.yml
index d0309d7860bd02dba885506ef973a0d3cc390850..4609f004dd3eb629a7ef740cab2e70381f22ff29 100644
--- a/web/modules/link_attributes/link_attributes.info.yml
+++ b/web/modules/link_attributes/link_attributes.info.yml
@@ -1,13 +1,13 @@
 name: Link attributes
 type: module
 description: Provides a widget to allow settings of link attributes for menu links.
-# core: 8.x
+core: 8.x
+core_version_requirement: ^8 || ^9
 dependencies:
   - drupal:link
-  - drupal:menu_link_content
+  - drupal:system (>=8.4)
 
-# Information added by Drupal.org packaging script on 2016-12-16
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2020-01-29
+version: '8.x-1.10'
 project: 'link_attributes'
-datestamp: 1481849585
+datestamp: 1580259499
diff --git a/web/modules/link_attributes/link_attributes.link_attributes.yml b/web/modules/link_attributes/link_attributes.link_attributes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0ebe2e7cf1e31a9ced4f6422f63ff32a9ca95b05
--- /dev/null
+++ b/web/modules/link_attributes/link_attributes.link_attributes.yml
@@ -0,0 +1,21 @@
+id:
+  title: ID
+name:
+  title: Name
+target:
+  title: Target
+  type: select
+  empty_value: ''
+  options:
+    _self: Same window (_self)
+    _blank: New window (_blank)
+rel:
+  title: Rel
+class:
+  title: Class
+accesskey:
+  title: Accesskey
+aria-label:
+  title: ARIA Label
+title:
+  title: Title
diff --git a/web/modules/link_attributes/link_attributes.module b/web/modules/link_attributes/link_attributes.module
index c98ff917c23b7340a66f4c8442f8a57f183813e7..69acd9ca238b57403b608cd1e224ea602ae9532b 100644
--- a/web/modules/link_attributes/link_attributes.module
+++ b/web/modules/link_attributes/link_attributes.module
@@ -4,6 +4,7 @@
  * @file
  * Contains main module functions.
  */
+
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 
diff --git a/web/modules/link_attributes/link_attributes.services.yml b/web/modules/link_attributes/link_attributes.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8a2e34c48d2f71515e9f62234f3e32b6bc886165
--- /dev/null
+++ b/web/modules/link_attributes/link_attributes.services.yml
@@ -0,0 +1,4 @@
+services:
+  plugin.manager.link_attributes:
+    class: Drupal\link_attributes\LinkAttributesManager
+    arguments: ['@module_handler', '@cache.discovery']
diff --git a/web/modules/link_attributes/src/LinkAttributesManager.php b/web/modules/link_attributes/src/LinkAttributesManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..9fbe801348c85a2476de346a8a7cca4a91b26edf
--- /dev/null
+++ b/web/modules/link_attributes/src/LinkAttributesManager.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\link_attributes;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\YamlDiscovery;
+
+/**
+ * Provides the link_attributes plugin manager.
+ */
+class LinkAttributesManager extends DefaultPluginManager implements PluginManagerInterface {
+
+  /**
+   * Provides default values for all link_attributes plugins.
+   *
+   * @var array
+   */
+  protected $defaults = [
+    'title' => '',
+    'type' => '',
+    'description' => '',
+  ];
+
+  /**
+   * Constructs a LinkAttributesManager object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend) {
+    $this->alterInfo('link_attributes_plugin');
+    $this->moduleHandler = $module_handler;
+    $this->setCacheBackend($cache_backend, 'link_attributes', ['link_attributes']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDiscovery() {
+    if (!isset($this->discovery)) {
+      $this->discovery = new YamlDiscovery('link_attributes', $this->moduleHandler->getModuleDirectories());
+      $this->discovery->addTranslatableProperty('title');
+      $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
+    }
+    return $this->discovery;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    parent::processDefinition($definition, $plugin_id);
+
+    // Make sure each plugin definition had at least a field type.
+    if (empty($definition['type'])) {
+      $definition['type'] = 'textfield';
+    }
+  }
+
+}
diff --git a/web/modules/link_attributes/src/Plugin/Field/FieldWidget/LinkWithAttributesWidget.php b/web/modules/link_attributes/src/Plugin/Field/FieldWidget/LinkWithAttributesWidget.php
index 3f628a5af36d930baa067e5995faf9d374903482..e298e3d4b7c11c3299d930b146dfd04da0b18c6b 100644
--- a/web/modules/link_attributes/src/Plugin/Field/FieldWidget/LinkWithAttributesWidget.php
+++ b/web/modules/link_attributes/src/Plugin/Field/FieldWidget/LinkWithAttributesWidget.php
@@ -2,9 +2,13 @@
 
 namespace Drupal\link_attributes\Plugin\Field\FieldWidget;
 
+use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\link\Plugin\Field\FieldWidget\LinkWidget;
+use Drupal\link_attributes\LinkAttributesManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Plugin implementation of the 'link' widget.
@@ -17,13 +21,55 @@
  *   }
  * )
  */
-class LinkWithAttributesWidget extends LinkWidget {
+class LinkWithAttributesWidget extends LinkWidget implements ContainerFactoryPluginInterface {
+
+  /**
+   * The link attributes manager.
+   *
+   * @var \Drupal\link_attributes\LinkAttributesManager
+   */
+  protected $linkAttributesManager;
+
+  /**
+   * Constructs a LinkWithAttributesWidget object.
+   *
+   * @param string $plugin_id
+   *   The plugin_id for the widget.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the widget is associated.
+   * @param array $settings
+   *   The widget settings.
+   * @param array $third_party_settings
+   *   Any third party settings.
+   * @param \Drupal\link_attributes\LinkAttributesManager $link_attributes_manager
+   *   The link attributes manager.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, LinkAttributesManager $link_attributes_manager) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+    $this->linkAttributesManager = $link_attributes_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['third_party_settings'],
+      $container->get('plugin.manager.link_attributes')
+    );
+  }
 
   /**
    * {@inheritdoc}
    */
   public static function defaultSettings() {
-    return array(
+    return [
       'placeholder_url' => '',
       'placeholder_title' => '',
       'enabled_attributes' => [
@@ -34,7 +80,7 @@ public static function defaultSettings() {
         'class' => TRUE,
         'accesskey' => FALSE,
       ],
-    ) + parent::defaultSettings();
+    ] + parent::defaultSettings();
   }
 
   /**
@@ -44,7 +90,6 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     $element = parent::formElement($items, $delta, $element, $form, $form_state);
     // Add each of the enabled attributes.
     // @todo move this to plugins that nominate form and label.
-
     $item = $items[$delta];
 
     $options = $item->get('options')->getValue();
@@ -55,13 +100,23 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#tree' => TRUE,
       '#open' => count($attributes),
     ];
+    $plugin_definitions = $this->linkAttributesManager->getDefinitions();
     foreach (array_keys(array_filter($this->getSetting('enabled_attributes'))) as $attribute) {
-      $element['options']['attributes'][$attribute] = [
-        '#type' => 'textfield',
-        '#title' => $attribute,
-        '#description' => $this->t('Enter value for the @attribute attribute', ['@attribute' => $attribute]),
-        '#default_value' => isset($attributes[$attribute]) ? $attributes[$attribute] : '',
-      ];
+      if (isset($plugin_definitions[$attribute])) {
+        foreach ($plugin_definitions[$attribute] as $property => $value) {
+          $element['options']['attributes'][$attribute]['#' . $property] = $value;
+        }
+
+        // Set the default value, in case of a class that is stored as array,
+        // convert it back to a string.
+        $default_value = isset($attributes[$attribute]) ? $attributes[$attribute] : NULL;
+        if ($attribute === 'class' && is_array($default_value)) {
+          $default_value = implode(' ', $default_value);
+        }
+        if (isset($default_value)) {
+          $element['options']['attributes'][$attribute]['#default_value'] = $default_value;
+        }
+      }
     }
     return $element;
   }
@@ -71,18 +126,41 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
    */
   public function settingsForm(array $form, FormStateInterface $form_state) {
     $element = parent::settingsForm($form, $form_state);
-    $options = ['id', 'name', 'target', 'rel', 'class', 'accesskey'];
+    $options = array_map(function ($plugin_definition) {
+      return $plugin_definition['title'];
+    }, $this->linkAttributesManager->getDefinitions());
     $selected = array_keys(array_filter($this->getSetting('enabled_attributes')));
     $element['enabled_attributes'] = [
       '#type' => 'checkboxes',
       '#title' => $this->t('Enabled attributes'),
-      '#options' => array_combine($options, $options),
+      '#options' => $options,
       '#default_value' => array_combine($selected, $selected),
       '#description' => $this->t('Select the attributes to allow the user to edit.'),
     ];
     return $element;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
+    // Convert a class string to an array so that it can be merged reliable.
+    foreach ($values as $delta => $value) {
+      if (isset($value['options']['attributes']['class']) && is_string($value['options']['attributes']['class'])) {
+        $values[$delta]['options']['attributes']['class'] = explode(' ', $value['options']['attributes']['class']);
+      }
+    }
+
+    return array_map(function (array $value) {
+      if (isset($value['options']['attributes'])) {
+        $value['options']['attributes'] = array_filter($value['options']['attributes'], function ($attribute) {
+          return $attribute !== "";
+        });
+      }
+      return $value;
+    }, $values);
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -90,7 +168,7 @@ public function settingsSummary() {
     $summary = parent::settingsSummary();
     $enabled_attributes = array_filter($this->getSetting('enabled_attributes'));
     if ($enabled_attributes) {
-      $summary[] = $this->t('With attributes: @attributes', array('@attributes' => implode(', ', array_keys($enabled_attributes))));
+      $summary[] = $this->t('With attributes: @attributes', ['@attributes' => implode(', ', array_keys($enabled_attributes))]);
     }
     return $summary;
   }
diff --git a/web/modules/link_attributes/tests/modules/link_attributes_test_alterinfo/link_attributes_test_alterinfo.info.yml b/web/modules/link_attributes/tests/modules/link_attributes_test_alterinfo/link_attributes_test_alterinfo.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a449282d9f69e82229349b396f5fc5e78b891c8d
--- /dev/null
+++ b/web/modules/link_attributes/tests/modules/link_attributes_test_alterinfo/link_attributes_test_alterinfo.info.yml
@@ -0,0 +1,12 @@
+name: 'Link attributes alterInfo Test'
+description: 'Implements hook_link_attributes_plugin_alter'
+type: module
+core: 8.x
+core_version_requirement: ^8 || ^9
+dependencies:
+  - link_attributes:link_attributes
+
+# Information added by Drupal.org packaging script on 2020-01-29
+version: '8.x-1.10'
+project: 'link_attributes'
+datestamp: 1580259499
diff --git a/web/modules/link_attributes/tests/modules/link_attributes_test_alterinfo/link_attributes_test_alterinfo.module b/web/modules/link_attributes/tests/modules/link_attributes_test_alterinfo/link_attributes_test_alterinfo.module
new file mode 100644
index 0000000000000000000000000000000000000000..aff63f3ad952fa27a25a8bafe7344b02cb1369e0
--- /dev/null
+++ b/web/modules/link_attributes/tests/modules/link_attributes_test_alterinfo/link_attributes_test_alterinfo.module
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Link attributes test module.
+ */
+
+/**
+ * Implements hook_link_attributes_plugin_alter().
+ */
+function link_attributes_test_alterinfo_link_attributes_plugin_alter(array &$definitions) {
+  // Alter only if our state flag is set.
+  if (\Drupal::state()->get('link_attributes_test_alterinfo.hook_link_attributes_plugin_alter')) {
+    $definitions['class']['title'] = t('Link style');
+    $definitions['class']['description'] = t('Select how the link should be displayed.');
+    $definitions['class']['type'] = 'select';
+    $definitions['class']['options'] = [
+      'link' => 'Link',
+      'button' => 'Button',
+    ];
+    $definitions['class']['default_value'] = 'button';
+    $definitions['target']['default_value'] = '_blank';
+  }
+}
diff --git a/web/modules/link_attributes/tests/src/Functional/LinkAttributesFieldTest.php b/web/modules/link_attributes/tests/src/Functional/LinkAttributesFieldTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1a9301f8a4f709d3e231bd123727b309c07f72fe
--- /dev/null
+++ b/web/modules/link_attributes/tests/src/Functional/LinkAttributesFieldTest.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\Tests\link_attributes\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
+
+/**
+ * Tests link attributes functionality.
+ *
+ * @group link_attributes
+ */
+class LinkAttributesFieldTest extends BrowserTestBase {
+
+  use FieldUiTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'node',
+    'link_attributes',
+    'field_ui',
+    'block',
+    'link_attributes_test_alterinfo',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * A user that can edit content types.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->adminUser = $this->drupalCreateUser([
+      'administer content types',
+      'administer node fields',
+      'administer node display',
+    ]);
+    $this->drupalLogin($this->adminUser);
+    // Breadcrumb is required for FieldUiTestTrait::fieldUIAddNewField.
+    $this->drupalPlaceBlock('system_breadcrumb_block');
+    \Drupal::state()->set('link_attributes_test_alterinfo.hook_link_attributes_plugin_alter', TRUE);
+  }
+
+  /**
+   * Tests the display of attributes in the widget.
+   */
+  public function testWidget() {
+    // Add a content type.
+    $type = $this->drupalCreateContentType();
+    $type_path = 'admin/structure/types/manage/' . $type->id();
+    $add_path = 'node/add/' . $type->id();
+
+    // Add a link field to the newly-created type.
+    $label = $this->randomMachineName();
+    $field_name = mb_strtolower($label);
+    $storage_settings = ['cardinality' => 'number', 'cardinality_number' => 2];
+    $this->fieldUIAddNewField($type_path, $field_name, $label, 'link', $storage_settings);
+
+    // Manually clear cache on the tester side.
+    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
+
+    // Change the link widget and enable some attributes.
+    \Drupal::entityTypeManager()
+      ->getStorage('entity_form_display')
+      ->load('node.' . $type->id() . '.default')
+      ->setComponent('field_' . $field_name, [
+        'type' => 'link_attributes',
+        'settings' => [
+          'enabled_attributes' => [
+            'rel' => TRUE,
+            'class' => TRUE,
+            'target' => TRUE,
+          ],
+        ],
+      ])
+      ->save();
+
+    // Check if the link field have the attributes displayed on node add page.
+    $this->drupalGet($add_path);
+    $web_assert = $this->assertSession();
+    // Link attributes.
+    $web_assert->elementExists('css', '.field--widget-link-attributes');
+
+    // Rel attribute.
+    $attribute_rel = 'field_' . $field_name . '[0][options][attributes][rel]';
+    $web_assert->fieldExists($attribute_rel);
+
+    // Class attribute.
+    $attribute_class = 'field_' . $field_name . '[0][options][attributes][class]';
+    $web_assert->fieldExists($attribute_class);
+
+    // Target attribute.
+    $attribute_target = 'field_' . $field_name . '[0][options][attributes][target]';
+    $web_assert->fieldExists($attribute_target);
+    $web_assert->fieldValueEquals($attribute_target, '_blank');
+
+    \Drupal::state()->set('link_attributes_test_alterinfo.hook_link_attributes_plugin_alter', FALSE);
+    \Drupal::service('plugin.manager.link_attributes')->clearCachedDefinitions();
+    // Create a node.
+    $edit = [
+      'title[0][value]' => 'A multi field link test',
+      'field_' . $field_name . '[0][title]' => 'Link One',
+      'field_' . $field_name . '[0][uri]' => '<front>',
+      'field_' . $field_name . '[0][options][attributes][class]' => 'class-one class-two',
+      'field_' . $field_name . '[1][title]' => 'Link Two',
+      'field_' . $field_name . '[1][uri]' => '<front>',
+      'field_' . $field_name . '[1][options][attributes][class]' => 'class-three class-four',
+    ];
+    $this->drupalPostForm($add_path, $edit, t('Save'));
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+
+    // Load the field values.
+    $field_values = $node->get('field_' . $field_name)->getValue();
+
+    $expected_link_one = [
+      'class-one',
+      'class-two',
+    ];
+    $this->assertEquals($expected_link_one, $field_values[0]['options']['attributes']['class']);
+
+    $expected_link_two = [
+      'class-three',
+      'class-four',
+    ];
+    $this->assertEquals($expected_link_two, $field_values[1]['options']['attributes']['class']);
+  }
+
+  /**
+   * Tests saving a node without any attributes enabled in the widget settings.
+   */
+  public function testWidgetWithoutAttributes() {
+    // Add a content type.
+    $type = $this->drupalCreateContentType();
+    $type_path = 'admin/structure/types/manage/' . $type->id();
+    $add_path = 'node/add/' . $type->id();
+
+    // Add a link field to the newly-created type.
+    $label = $this->randomMachineName();
+    $field_name = mb_strtolower($label);
+    $storage_settings = ['cardinality' => 'number', 'cardinality_number' => 2];
+    $this->fieldUIAddNewField($type_path, $field_name, $label, 'link', $storage_settings);
+
+    // Manually clear cache on the tester side.
+    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
+
+    \Drupal::entityTypeManager()
+      ->getStorage('entity_form_display')
+      ->load('node.' . $type->id() . '.default')
+      ->setComponent('field_' . $field_name, [
+        'type' => 'link_attributes',
+        'settings' => [
+          'enabled_attributes' => [],
+        ],
+      ])
+      ->save();
+
+    $this->drupalGet($add_path);
+    $web_assert = $this->assertSession();
+    // Link attributes.
+    $web_assert->elementExists('css', '.field--widget-link-attributes');
+    // Create a node.
+    $edit = [
+      'title[0][value]' => 'A multi field link test',
+      'field_' . $field_name . '[0][title]' => 'Link One',
+      'field_' . $field_name . '[0][uri]' => '<front>',
+    ];
+    $this->drupalPostForm($add_path, $edit, t('Save'));
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $this->drupalGet($node->toUrl()->toString());
+    $web_assert->linkExists('Link One');
+  }
+
+}
diff --git a/web/modules/link_attributes/tests/src/Functional/LinkAttributesMenuTest.php b/web/modules/link_attributes/tests/src/Functional/LinkAttributesMenuTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1f5b599ad916f9dae53e2ae4da45cd08e9876020
--- /dev/null
+++ b/web/modules/link_attributes/tests/src/Functional/LinkAttributesMenuTest.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Drupal\Tests\link_attributes\Functional;
+
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\Tests\block\Traits\BlockCreationTrait;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests link attributes functionality.
+ *
+ * @group link_attributes
+ */
+class LinkAttributesMenuTest extends BrowserTestBase {
+
+  use BlockCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'link_attributes',
+    'menu_ui',
+    'menu_link_content',
+    'block',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->placeBlock('system_menu_block:footer');
+  }
+
+  /**
+   * Test attributes.
+   */
+  public function testMenuLinkAdmin() {
+    // Login as a super-admin.
+    $this->drupalLogin($this->drupalCreateUser(array_keys(\Drupal::service('user.permissions')->getPermissions())));
+
+    $this->drupalGet('admin/structure/menu/manage/footer/add');
+    $this->submitForm([
+      'title[0][value]' => 'A menu link',
+      'link[0][uri]' => '<front>',
+      // This is enough to check the fields are there.
+      'link[0][options][attributes][target]' => '_blank',
+      'link[0][options][attributes][class]' => 'menu__link--really_special menu__link--another-class',
+    ], t('Save'));
+    $this->drupalGet('user');
+    $page = $this->getSession()->getPage();
+    // The link should exist and contain the required attributes.
+    $link = $page->findLink('A menu link');
+    $this->assertNotNull($link);
+    $this->assertEquals('_blank', $link->getAttribute('target'));
+    $this->assertEquals('menu__link--really_special menu__link--another-class', $link->getAttribute('class'));
+    // No rel attribute was added, so none should be present.
+    $this->assertFalse($link->hasAttribute('rel'));
+
+    // Load the menu link, make sure that the classes were stored as an array.
+    $id = \Drupal::entityQuery('menu_link_content')
+      ->condition('title', 'A menu link')
+      ->execute();
+
+    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
+    $menu_link = MenuLinkContent::load(reset($id));
+
+    $expected = [
+      'menu__link--really_special',
+      'menu__link--another-class',
+    ];
+    $this->assertEquals($expected, $menu_link->getUrlObject()->getOption('attributes')['class']);
+
+    // Edit the link, make sure the default value for class is set correctly.
+    $this->drupalGet($menu_link->toUrl('edit-form'));
+    $this->assertSession()->fieldValueEquals('link[0][options][attributes][class]', 'menu__link--really_special menu__link--another-class');
+
+    // Add another link to assert that the target can be empty.
+    $this->drupalGet('admin/structure/menu/manage/footer/add');
+    $this->submitForm([
+      'title[0][value]' => 'No target menu link',
+      'link[0][uri]' => '<front>',
+      'link[0][options][attributes][target]' => '',
+      'link[0][options][attributes][rel]' => 'author',
+    ], t('Save'));
+    $this->drupalGet('user');
+    $page = $this->getSession()->getPage();
+    // The link should exist and contain the set rel attribute.
+    $link = $page->findLink('No target menu link');
+    $this->assertNotNull($link);
+    $this->assertEquals('author', $link->getAttribute('rel'));
+    // No class or target was specified, these shouldn't be rendered.
+    $this->assertFalse($link->hasAttribute('target'));
+    $this->assertFalse($link->hasAttribute('class'));
+  }
+
+}
diff --git a/web/modules/link_attributes/tests/src/Functional/LinkAttributesTest.php b/web/modules/link_attributes/tests/src/Functional/LinkAttributesTest.php
deleted file mode 100644
index f6be709a6f50a6dc968646ef7f72b614cbe3cd32..0000000000000000000000000000000000000000
--- a/web/modules/link_attributes/tests/src/Functional/LinkAttributesTest.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-namespace Drupal\Tests\link_attributes\Functional;
-
-use Drupal\simpletest\BlockCreationTrait;
-use Drupal\Tests\BrowserTestBase;
-
-/**
- * Tests link attributes functionality.
- *
- * @group link_attributes
- */
-class LinkAttributesTest extends BrowserTestBase {
-
-  use BlockCreationTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = [
-    'link',
-    'link_attributes',
-    'menu_ui',
-    'menu_link_content',
-    'system',
-    'block',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    $this->placeBlock('system_menu_block:footer');
-  }
-
-  /**
-   * Test attributes.
-   */
-  public function testMenuLinkAdmin() {
-    // Login as a super-admin.
-    $this->drupalLogin($this->drupalCreateUser(array_keys(\Drupal::service('user.permissions')->getPermissions())));
-
-    $this->drupalGet('admin/structure/menu/manage/footer/add');
-    $this->submitForm([
-      'title[0][value]' => 'A menu link',
-      'link[0][uri]' => '<front>',
-      // This is enough to check the fields are there.
-      'link[0][options][attributes][target]' => '_blank',
-      'link[0][options][attributes][class]' => 'menu__link--really_special',
-    ], t('Save'));
-    $this->drupalGet('user');
-    $page = $this->getSession()->getPage();
-    // The link should exist and contain the required attributes.
-    $link = $page->findLink('A menu link');
-    $this->assertNotNull($link);
-    $this->assertEquals('_blank', $link->getAttribute('target'));
-    $this->assertEquals('menu__link--really_special', $link->getAttribute('class'));
-  }
-
-}
diff --git a/web/modules/link_attributes/tests/src/Kernel/InfoAlterTest.php b/web/modules/link_attributes/tests/src/Kernel/InfoAlterTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f49bd9e0ab104494c69c256ff40659b502afd19e
--- /dev/null
+++ b/web/modules/link_attributes/tests/src/Kernel/InfoAlterTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\Tests\link_attributes\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests link_attributes_plugin alterInfo.
+ *
+ * @group link_attributes
+ */
+class InfoAlterTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'system',
+    'link_attributes',
+    'link_attributes_test_alterinfo',
+  ];
+
+  /**
+   * Tests that plugin definition is changed with alterInfo.
+   *
+   * Tests that info data is changed after a module that implements
+   * hook_link_attributes_plugin_alter() is enabled.
+   */
+  public function testLinkAttributesManagerInfoAlter() {
+    /** @var \Drupal\link_attributes\LinkAttributesManager $linkAttributesManager */
+    $linkAttributesManager = $this->container->get('plugin.manager.link_attributes');
+    $definition = $linkAttributesManager->getDefinitions();
+    $this->assertTrue($definition['class']['type'] == 'textfield', 'Without altering the plugin definition the class attribute is a textfield.');
+
+    // Set our flag to alter the plugin definition in
+    // link_attributes_test_alterinfo module.
+    \Drupal::state()->set('link_attributes_test_alterinfo.hook_link_attributes_plugin_alter', TRUE);
+    $linkAttributesManager->clearCachedDefinitions();
+    $definition = $linkAttributesManager->getDefinitions();
+    $this->assertTrue($definition['class']['type'] == 'select', 'After altering the plugin definition the class attribute is a select.');
+    $this->assertTrue(isset($definition['class']['options']['button']), 'After altering the plugin definition the class attribute has a "button" option.');
+  }
+
+}
diff --git a/web/modules/menu_breadcrumb/README.txt b/web/modules/menu_breadcrumb/README.txt
index 8b1b8fe27e266e7592ffe5cca896c4b457319f55..ea24b8ae24b1773fd8e0e5ee9e0540fc11276d2a 100644
--- a/web/modules/menu_breadcrumb/README.txt
+++ b/web/modules/menu_breadcrumb/README.txt
@@ -8,7 +8,7 @@ The Drupal 7 version of this module implemented the Drupal 6 behaviour of
 using the menu position of the current page for the breadcrumb.  It also
 added an option to append the page title to the breadcrumb (either as
 a clickable url or not), saving the trouble of doing this in the theme,
-and hiding the breadcrumb if it only contains the link to the front page.
+and hiding the breadcrumb if it only contained the link to the front page.
 
 The Drupal 8 version also supports "Taxonomy Attachment" for each
 menu, which provides the same breadcrumb trail to the current
@@ -22,23 +22,39 @@ add the current page title, linked or not, as an additional breadcrumb
 when a taxonomy attachment has taken place.  More detailed options are
 given for dealing with the front page, allowing it to be added or removed.
 
-Installation
-------------
+Installation & Upgrading - RECOMMENDED
+--------------------------------------
+Follow current instructions on this Drupal documentation page
+to install Menu Breadcrumb as a site dependency and upgrade it
+along with other site dependencies (as well as Drupal Core itself):
+
+https://www.drupal.org/docs/develop/using-composer/using-composer-to-manage-drupal-site-dependencies
+
+IF YOU SEE AN ERROR PAGE similar to the following after up- or down-grading the module:
+
+TypeError: Argument <X> passed to Drupal\menu_breadcrumb\MenuBasedBreadcrumbBuilder::__construct() ...
+
+... you probably just need to rebuild the cache between two versions that use
+different argument lists (i.e. system services).  See here for how to rebuild cache:
+
+https://www.drupal.org/docs/user_guide/en/prevent-cache-clear.html
+
+Installation & Upgrading - without Composer
+-------------------------------------------
+
+Installation, on older Drupal versions & sites:
 1. Copy the menu_breadcrumb folder to your modules/contrib directory.
 2. At Administer -> Extend (admin/modules) enable the module.
 3. Configure the module settings at Administer -> Configuration ->
      User Interface (admin/config/user-interface/menu-breadcrumb).
 
-Upgrading
----------
+Upgrading on older Drupal versions & sites:
 Recommended: install drush and run "drush up"
 
 Manually: replace the older menu_breadcrumb folder with the newer version;
 then run "update.php" if present (to install any configuration changes).
 
-NOTES if you have installed a Drupal 8 development version:
-If upgrading from the Alpha to the Beta version of the module, or to a
-newer Beta, if option settings are not producing the desired effect:
+Since different versions of the module may use different services, if you see errors:
 
 - Try clearing the cache, which fixes breacrumb on taxonomy pages (since this
   module's breadcrumb builder needs to be acknolwedged as higher priority).
diff --git a/web/modules/menu_breadcrumb/config/install/menu_breadcrumb.settings.yml b/web/modules/menu_breadcrumb/config/install/menu_breadcrumb.settings.yml
index 9387c5ba5463423ee610f97d6dae65e10af8547a..38cff6d618040436002e378cc5151b5415367dcc 100644
--- a/web/modules/menu_breadcrumb/config/install/menu_breadcrumb.settings.yml
+++ b/web/modules/menu_breadcrumb/config/install/menu_breadcrumb.settings.yml
@@ -2,14 +2,14 @@ determine_menu: true
 disable_admin_page: true
 append_current_page: true
 current_page_as_link: false
+stop_on_first_match: false
 append_member_page: false
 member_page_as_link: false
 remove_home: false
 add_home: false
-home_as_site_name: false
+front_title: 0
 exclude_empty_url: false
-# as of Beta, this option is obsolete:
-# hide_on_single_item: false
+derived_active_trail: false
 menu_breadcrumb_menus:
   main:
     enabled: 1
diff --git a/web/modules/menu_breadcrumb/config/schema/menu_breadcrumb.schema.yml b/web/modules/menu_breadcrumb/config/schema/menu_breadcrumb.schema.yml
index 5dbbc9009bea61b513e53859dd33e53d711abb17..73ba426f664ff3d5b2d00391f2f8881f5fd3c2a9 100644
--- a/web/modules/menu_breadcrumb/config/schema/menu_breadcrumb.schema.yml
+++ b/web/modules/menu_breadcrumb/config/schema/menu_breadcrumb.schema.yml
@@ -14,6 +14,9 @@ menu_breadcrumb.settings:
     current_page_as_link:
       type: boolean
       label: 'If the current page in the breadcrumb trail should be a link.'
+    stop_on_first_match:
+      type: boolean
+      label: 'End the breadcrumb trail when the first matching found in the menu.'
     append_member_page:
       type: boolean
       label: 'This option affects breadcrumb display when the current page is a member of a taxonomy whose term is on a menu with "Taxonomy Attachment" selected.'
@@ -26,12 +29,15 @@ menu_breadcrumb.settings:
     add_home:
       type: boolean
       label: 'Ensure that the very first link is the designated <front> page link.'
-    home_as_site_name:
-      type: boolean
-      label: 'Use the site name from the configuration settings.'
+    front_title:
+      type: integer
+      label: 'Choose how to show home title in breadcrumb'
     exclude_empty_url:
       type: boolean
       label: 'Exclude menu items with empty URLs.'
+    derived_active_trail:
+      type: boolean
+      label: 'Derive MenuActiveTrail from RouteMatch.'
     menu_breadcrumb_menus:
       type: sequence
       label: 'Menu Breadcrumb Menu Configurations'
diff --git a/web/modules/menu_breadcrumb/menu_breadcrumb.info.yml b/web/modules/menu_breadcrumb/menu_breadcrumb.info.yml
index a828765403a897b15144d6aefa2299d9410559c2..620fdd694b5f0997c5393af17d0c2ff1b355cb2b 100644
--- a/web/modules/menu_breadcrumb/menu_breadcrumb.info.yml
+++ b/web/modules/menu_breadcrumb/menu_breadcrumb.info.yml
@@ -1,11 +1,11 @@
 name: Menu Breadcrumb
 type: module
 description: 'Create breadcrumbs from nested menu titles and/or taxonomy membership.'
-# core: '8.x'
+core: '8.x'
+core_version_requirement: ^8 || ^9
 configure: menu_breadcrumb.settings
 
-# Information added by Drupal.org packaging script on 2018-10-10
-version: '8.x-1.7'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2020-02-12
+version: '8.x-1.12'
 project: 'menu_breadcrumb'
-datestamp: 1539197292
+datestamp: 1581496469
diff --git a/web/modules/menu_breadcrumb/menu_breadcrumb.install b/web/modules/menu_breadcrumb/menu_breadcrumb.install
new file mode 100644
index 0000000000000000000000000000000000000000..4793ddc770cc239298b6985efa57afb2004ddad3
--- /dev/null
+++ b/web/modules/menu_breadcrumb/menu_breadcrumb.install
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the menu breadcrumb module.
+ */
+
+/**
+ * Set new front_title config key from home_as_site_name.
+ */
+function menu_breadcrumb_update_8101() {
+  $config = \Drupal::configFactory()->getEditable('menu_breadcrumb.settings');
+
+  $home_as_site_name = $config->get('home_as_site_name');
+  // We suppose that if home_as_site_name is null it is because the settings
+  // form had been resaved or a custom update path had been done on the website.
+  if (!is_null($home_as_site_name)) {
+    // If the front_title key has already been set, do nothing.
+    if (is_null($config->get('front_title'))) {
+      if ($home_as_site_name == FALSE) {
+        $config->set('front_title', 1);
+      }
+      else {
+        $config->set('front_title', 0);
+      }
+    }
+
+    // Remove the previous key.
+    $config->clear('home_as_site_name');
+    $config->save(TRUE);
+  }
+}
diff --git a/web/modules/menu_breadcrumb/menu_breadcrumb.module b/web/modules/menu_breadcrumb/menu_breadcrumb.module
index bb82e8a07edd89c851c1856c516e4e9929a9b8d0..5cfaa5a53f8d8939b3fc46d567e11d6f7e913176 100644
--- a/web/modules/menu_breadcrumb/menu_breadcrumb.module
+++ b/web/modules/menu_breadcrumb/menu_breadcrumb.module
@@ -28,7 +28,7 @@ function menu_breadcrumb_help($route_name, RouteMatchInterface $route_match) {
 <ul>
   <li>
 If the "Taxonomy Attachment" option is selected for a menu,
-and if the current page belongs to a taxonomy that is on that menu,
+and if the current page belongs to a taxonomy on that menu,
 it will inherit the taxonomy page's menu breadcrumbs (e.g., to provide
 breadcrumbs for blog entries that aren't on menus themselves).
   </li>
@@ -65,15 +65,15 @@ function menu_breadcrumb_help($route_name, RouteMatchInterface $route_match) {
 Taxonomy terms are searched in the order that their entity reference fields
 appear in the current page's field listing.
 One way to change an unwanted taxonomy term taking precendence over the one
-you want to "attach" on would be to change the order of those fields.
+on which you want to "attach" would be to change the order of those fields.
 </p>
 <p>
-When the current page is displaying the breadcrumbs of its taxonomy parent,
+When the current page displays the breadcrumbs of its taxonomy parent,
 the breadcrumb for that taxonomy is <u>always</u> a link regardless of the
 "Show current page as link" option.  (This can be confusing until we remember
 that this breadcrumb <u>is not</u> for the current page, and must be a link
 because the current page is no anchored at all in the menu&hellip; unlike a
-breacrumb that isn't taxonomy-attached.)
+breadcrumb that isn't taxonomy-attached.)
 </p>
 EOT;
       break;
@@ -83,7 +83,7 @@ function menu_breadcrumb_help($route_name, RouteMatchInterface $route_match) {
       $output = <<<EOT
 <p>
 Select the first option to enable the Menu Breadcrumb module,
-which will generate a breadcrumb trail the first match
+which will generate a breadcrumb trail according to the first match
 in the selected menu below: first looking for the current page on a menu
 (unless avoiding that menu due to language settings),
 and then seeing if the current page belongs to a taxonomy on that menu
diff --git a/web/modules/menu_breadcrumb/menu_breadcrumb.services.yml b/web/modules/menu_breadcrumb/menu_breadcrumb.services.yml
index f1cc4e3a71c42452b714750f19af1b295f9d2557..7d0702d3c58ab12d73c8b5955d961eac7ef4182d 100644
--- a/web/modules/menu_breadcrumb/menu_breadcrumb.services.yml
+++ b/web/modules/menu_breadcrumb/menu_breadcrumb.services.yml
@@ -1,7 +1,7 @@
 services:
   menu_breadcrumb.breadcrumb.default:
     class: Drupal\menu_breadcrumb\MenuBasedBreadcrumbBuilder
-    arguments: ['@config.factory', '@menu.active_trail', '@plugin.manager.menu.link', '@router.admin_context', '@title_resolver', '@request_stack', '@language_manager', '@entity_type.manager']
+    arguments: ['@config.factory', '@menu.active_trail', '@plugin.manager.menu.link', '@router.admin_context', '@title_resolver', '@request_stack', '@language_manager', '@entity_type.manager', '@cache.menu', '@lock']
     tags:
       # The priority must be higher than core taxonomy builder (priority: 1002)
       # see https://www.drupal.org/node/1495510
diff --git a/web/modules/menu_breadcrumb/src/Form/SettingsForm.php b/web/modules/menu_breadcrumb/src/Form/SettingsForm.php
index 53e82400808837e07757e039ff6abc6577b62d0c..23058274d0d931b363dcb9eb31c646d3df0b0430 100644
--- a/web/modules/menu_breadcrumb/src/Form/SettingsForm.php
+++ b/web/modules/menu_breadcrumb/src/Form/SettingsForm.php
@@ -59,7 +59,6 @@ protected function getEditableConfigNames() {
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
     $config = $this->config('menu_breadcrumb.settings');
-    // Renamed from the now meaningless option "determine_menu":
     $form['determine_menu'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Enable the Menu Breadcrumb module'),
@@ -95,10 +94,17 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       ],
     ];
 
+    $form['stop_on_first_match'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Stop on the first matching'),
+      '#description' => $this->t('End the breadcrumb trail when the first matching found in the menu.'),
+      '#default_value' => $config->get('stop_on_first_match'),
+    ];
+
     $form['append_member_page'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Attach taxonomy member page to breadcrumb'),
-      '#description' => $this->t('This option affects breadcrumb display when the current page is a member of a taxonomy whose term is on a menu with "Taxonomy Attachment" selected, when it "attaches" to the menu-based breadcrumbs of that taxonomy term. In this case that term\'s menu title will show as a link regardless of the "current page" options above. Set this option TRUE to also show the the current ("attached") <i>page</i> title as the final breadcrumb.'),
+      '#description' => $this->t('This option affects breadcrumb display when the current page is a member of a taxonomy whose term is on a menu with "Taxonomy Attachment" selected, when it "attaches" to the menu-based breadcrumbs of that taxonomy term. In this case that term\'s menu title will show as a link regardless of the "current page" options above. Set this option TRUE to also show the current ("attached") <i>page</i> title as the final breadcrumb.'),
       '#default_value' => $config->get('append_member_page'),
     ];
 
@@ -114,12 +120,6 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       ],
     ];
 
-    // Removed option "hide_on_single_item" - makes no sense when the taxonomy
-    // attachment feature is added, especially now that this module reverts to
-    // other breadcrumb builders (e.g., the path-based system breadcrumb) when
-    // it doesn't apply (can be reconsidered if there is a valid use case).
-    // $form['hide_on_single_item'] ...
-    //
     $form['remove_home'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Remove "Home" link'),
@@ -134,11 +134,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#description' => $this->t('If TRUE will add a link to the &lt;front&gt; page if it doesn\'t already begin the breadcrumb trail: ensuring that the first breadcrumb of every page is the site home. If both "add" and "remove" are set, when displaying the &lt;front&gt; page and its menu children the "remove" option will take precedence.'),
     ];
 
-    $form['home_as_site_name'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Use site name instead of "Home" link'),
-      '#description' => $this->t('Uses the site name from the configuration settings: if this option is not set, a translated value for "Home" will be used.'),
-      '#default_value' => $config->get('home_as_site_name'),
+    $form['front_title'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Home title'),
+      '#default_value' => $config->get('front_title'),
+      '#options' => [
+        0 => $this->t('Use "Home" as title'),
+        1 => $this->t('Uses the site name from the configuration settings: if this option is not set, a translated value for "Home" will be used.'),
+        2 => $this->t('Use title menu parent'),
+      ],
     ];
 
     $form['exclude_empty_url'] = [
@@ -148,6 +152,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#default_value' => $config->get('exclude_empty_url'),
     ];
 
+    $form['derived_active_trail'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Derive MenuActiveTrail from RouteMatch'),
+      '#description' => $this->t('If FALSE, the injected @menu.active_trail service will be used. If you have a multilingual site with unusually large menus, see <a target="_blank" href="https://www.drupal.org/project/menu_breadcrumb/issues/3083028">issue 3083028</a>.'),
+      '#default_value' => $config->get('derived_active_trail'),
+    ];
+
     $form['include_exclude'] = [
       '#type' => 'fieldset',
       '#title' => $this->t('Enable / Disable Menus'),
@@ -206,10 +217,6 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         ],
       ];
     }
-    // Removed description of a "Default setting" selection which was never
-    // implemeneted in the current D8 version of Menu Breadcrumb.
-    // TODO perhaps find out if this option would be worth preserving
-    // (applied to default tick boxes for new menus added in the future).
     return parent::buildForm($form, $form_state);
   }
 
@@ -222,13 +229,15 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       ->set('disable_admin_page', (boolean) $form_state->getValue('disable_admin_page'))
       ->set('append_current_page', (boolean) $form_state->getValue('append_current_page'))
       ->set('current_page_as_link', (boolean) $form_state->getValue('current_page_as_link'))
+      ->set('stop_on_first_match', (boolean) $form_state->getValue('stop_on_first_match'))
       ->set('append_member_page', (boolean) $form_state->getValue('append_member_page'))
       ->set('member_page_as_link', (boolean) $form_state->getValue('member_page_as_link'))
-      ->set('home_as_site_name', (boolean) $form_state->getValue('home_as_site_name'))
+      ->set('front_title', $form_state->getValue('front_title'))
       ->set('remove_home', (boolean) $form_state->getValue('remove_home'))
       ->set('add_home', (boolean) $form_state->getValue('add_home'))
       ->set('menu_breadcrumb_menus', $form_state->getValue('menu_breadcrumb_menus'))
       ->set('exclude_empty_url', $form_state->getValue('exclude_empty_url'))
+      ->set('derived_active_trail', $form_state->getValue('derived_active_trail'))
       ->save();
 
     parent::submitForm($form, $form_state);
diff --git a/web/modules/menu_breadcrumb/src/MenuBasedBreadcrumbBuilder.php b/web/modules/menu_breadcrumb/src/MenuBasedBreadcrumbBuilder.php
index 315ad43c813a29b238b8b4d29bd5f728635913c0..3c8904899106f1baa9c531e4de63fc8b8c9c7c55 100644
--- a/web/modules/menu_breadcrumb/src/MenuBasedBreadcrumbBuilder.php
+++ b/web/modules/menu_breadcrumb/src/MenuBasedBreadcrumbBuilder.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Utility\SortArray;
 use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Controller\TitleResolverInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -13,6 +14,8 @@
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Link;
+use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\Core\Menu\MenuActiveTrail;
 use Drupal\Core\Menu\MenuActiveTrailInterface;
 use Drupal\Core\Menu\MenuLinkManagerInterface;
 use Drupal\Core\Routing\AdminContext;
@@ -84,6 +87,20 @@ class MenuBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
    */
   protected $entityTypeManager;
 
+  /**
+   * The caching backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheMenu;
+
+  /**
+   * The locking backend.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
   /**
    * The Menu Breadcrumbs configuration.
    *
@@ -130,7 +147,9 @@ public function __construct(
     TitleResolverInterface $title_resolver,
     RequestStack $request_stack,
     LanguageManagerInterface $language_manager,
-    EntityTypeManagerInterface $entity_type_manager
+    EntityTypeManagerInterface $entity_type_manager,
+    CacheBackendInterface $cache_menu,
+    LockBackendInterface $lock
   ) {
     $this->configFactory = $config_factory;
     $this->menuActiveTrail = $menu_active_trail;
@@ -140,6 +159,8 @@ public function __construct(
     $this->currentRequest = $request_stack->getCurrentRequest();
     $this->languageManager = $language_manager;
     $this->entityTypeManager = $entity_type_manager;
+    $this->cacheMenu = $cache_menu;
+    $this->lock = $lock;
     $this->config = $this->configFactory->get('menu_breadcrumb.settings');
   }
 
@@ -202,7 +223,17 @@ public function applies(RouteMatchInterface $route_match) {
           }
         }
 
-        $trail_ids = $this->menuActiveTrail->getActiveTrailIds($menu_name);
+        if ($this->config->get('derived_active_trail')) {
+          // Do not use the global MenuActiveTrail service because we need one
+          // which is aware of the given routeMatch, not of the global one.
+          $menuActiveTrail = new MenuActiveTrail($this->menuLinkManager, $route_match, $this->cacheMenu, $this->lock);
+          $trail_ids = $menuActiveTrail->getActiveTrailIds($menu_name);
+        }
+        else {
+          // Default, for the majority & compatibility with historical use and
+          // other modules: use the global (injected) MenuActiveTrail service.
+          $trail_ids = $this->menuActiveTrail->getActiveTrailIds($menu_name);
+        }
         $trail_ids = array_filter($trail_ids);
         if ($trail_ids) {
           $this->menuName = $menu_name;
@@ -226,7 +257,7 @@ public function applies(RouteMatchInterface $route_match) {
               $route_links = $this->menuLinkManager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters(), $menu_name);
               if (!empty($route_links)) {
                 // Successfully found taxonomy attachment, so pass to build():
-                // - the menu in in which we have found the attachment
+                // - the menu in which we have found the attachment
                 // - the effective menu trail of the taxonomy-attached node
                 // - the node itself (in build() we will find its title & URL)
                 $taxonomy_term_link = reset($route_links);
@@ -281,6 +312,11 @@ public function build(RouteMatchInterface $route_match) {
         continue;
       }
 
+      // Stop items when the first url matching occurs.
+      if ($this->config->get('stop_on_first_match') && $plugin->getUrlObject()->toString() == Url::fromRoute('<current>')->toString()) {
+        break;
+      }
+
       $links[] = Link::fromTextAndUrl($plugin->getTitle(), $plugin->getUrlObject());
       $breadcrumb->addCacheableDependency($plugin);
       // In the last line, MenuLinkContent plugin is not providing cache tags.
@@ -297,7 +333,7 @@ public function build(RouteMatchInterface $route_match) {
 
     // Create a breadcrumb for <front> which may be either added or replaced:
     $langcode = $this->contentLanguage;
-    $label = $this->config->get('home_as_site_name') ?
+    $label = $this->config->get('front_title') ?
       $this->configFactory->get('system.site')->get('name') :
       $this->t('Home', [], ['langcode' => $langcode]);
     // (https://www.drupal.org/docs/develop/standards/coding-standards#array)
@@ -322,7 +358,7 @@ public function build(RouteMatchInterface $route_match) {
       if ($this->config->get('remove_home')) {
         array_shift($links);
       }
-      else {
+      elseif ($this->config->get('front_title') != 2) {
         $links[0] = $home_link;
       }
     }
@@ -364,7 +400,7 @@ protected function addMissingCurrentPage(array &$links, RouteMatchInterface $rou
         $last_url->getRouteName() === $route_match->getRouteName() &&
         $last_url->getRouteParameters() === $route_match->getRawParameters()->all()
       ) {
-        // We already have a link, no need to add one.
+        // We already have a link, so no need to add one.
         return;
       }
     }
diff --git a/web/modules/scheduler/.travis.yml b/web/modules/scheduler/.travis.yml
index 19c0dac9c9761d62437899ff38e7fd3b34ff37ed..8871a6f56cdf4783a07a292932131107c196382c 100644
--- a/web/modules/scheduler/.travis.yml
+++ b/web/modules/scheduler/.travis.yml
@@ -3,22 +3,32 @@ language: php
 # sudo here.
 sudo: true
 
+notifications:
+  email:
+    recipients:
+      - jonathan1055@sandfordsolutions.com
+
 php:
-  - 5.5
   - 5.6
   - 7.0
 
+services:
+  - mysql
+
 env:
   global:
-    # Make the script re-usable for other modules.
     - MODULE=scheduler
   matrix:
-    - DRUPAL_CORE=8.3.x
-    - DRUPAL_CORE=8.4.x
-    - DRUPAL_CORE=8.5.x
+    - DRUPAL_CORE=8.6.x
+    - DRUPAL_CORE=8.7.x
+    - DRUPAL_CORE=8.8.x
 
 matrix:
   fast_finish: true
+  # Core 8.8 no longer supports PHP5, so don't run that combination.
+  exclude:
+    - php: 5.6
+      env: DRUPAL_CORE=8.8.x
 
 # Be sure to cache composer downloads.
 cache:
@@ -49,18 +59,22 @@ before_script:
   - DRUPAL_ROOT=$(pwd)
   - echo $DRUPAL_ROOT
 
-  # Make a directory for module and copy the built source into it.
+  # Make a directory for our module and copy the built source into it.
   - mkdir $DRUPAL_ROOT/modules/$MODULE
   - cp -R $TRAVIS_BUILD_DIR/* $DRUPAL_ROOT/modules/$MODULE/
 
   # Install dependencies.
   - travis_retry git clone --branch 8.x-3.x --depth 1 https://github.com/fago/rules.git modules/rules
-  - travis_retry git clone --branch 8.x-1.x --depth 1 http://git.drupal.org/project/devel.git modules/devel
-  - travis_retry git clone --branch 8.x-1.x --depth 1 http://git.drupal.org/project/typed_data.git modules/typed_data
+  - travis_retry git clone --branch 8.x-2.1 --depth 1 https://git.drupalcode.org/project/devel.git modules/devel
+  - travis_retry git clone --branch 8.x-1.x --depth 1 https://git.drupalcode.org/project/typed_data.git modules/typed_data
 
-  # Run composer install for Drupal 8.1 and up. We need an up-to-date composer
-  # when installing Drupal 8.1.
+  # Run composer self-update and install.
   - travis_retry composer self-update && travis_retry composer install
+  # If running Core 8.6 or 8.7 the following script will upgrade to phpunit 6
+  # which is required in PHP7. The script has now been deleted from Core 8.8.
+  - if [[ $DRUPAL_CORE == "8.6.x" || $DRUPAL_CORE == "8.7.x" ]]; then
+      travis_retry composer run-script drupal-phpunit-upgrade;
+    fi
 
   # Coder is already installed as part of composer install. We just need to set
   # the installed_paths to pick up the Drupal standards.
@@ -76,14 +90,14 @@ script:
   # Run the PHPUnit tests.
   - ./vendor/bin/phpunit -c ./core/phpunit.xml.dist ./modules/$MODULE/tests/
 
-  # Change directory to our module.
+  # Check for coding standards. First change directory to our module.
   - cd $DRUPAL_ROOT/modules/$MODULE
 
-  # Check for coding standards. First list all the sniffs to be used.
+  # List all the sniffs that were used.
   - $DRUPAL_ROOT/vendor/bin/phpcs -e
 
-  # Show the coding standards violations in detail.
-  - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1
+  # Show the violations in detail and do not fail for any errors or warnings.
+  - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_warnings_on_exit 1 --runtime-set ignore_errors_on_exit 1
 
-  # Run again to give a summary and total count.
-  - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 --report=summary
+  # Run again to give a summary and total count, and fail for errors (not warnings).
+  - $DRUPAL_ROOT/vendor/bin/phpcs --report=summary --runtime-set ignore_warnings_on_exit 1
diff --git a/web/modules/scheduler/composer.json b/web/modules/scheduler/composer.json
index 0085b59527999be0aef702bcd00fb9ab45c138f9..6a1534ff36c01a61d76acd0294fa736ea4e1b138 100644
--- a/web/modules/scheduler/composer.json
+++ b/web/modules/scheduler/composer.json
@@ -1,33 +1,44 @@
 {
-  "name": "drupal/scheduler",
-  "description": "Automatically publish and unpublish content at specified dates and times.",
-  "type": "drupal-module",
-  "homepage": "https://drupal.org/project/scheduler",
-  "authors": [
-    {
-      "name": "Eric Schaefer (Eric Schaefer)",
-      "homepage": "https://www.drupal.org/u/eric-schaefer",
-      "role": "Maintainer"
+    "name": "drupal/scheduler",
+    "description": "Automatically publish and unpublish content at specified dates and times.",
+    "type": "drupal-module",
+    "license": "GPL-2.0+",
+    "homepage": "https://drupal.org/project/scheduler",
+    "require-dev": {
+        "drupal/devel_generate": "^2.0",
+        "drupal/coder": "^8.3.3"
     },
-    {
-      "name": "Jonathan Smith (jonathan1055)",
-      "homepage": "https://www.drupal.org/u/jonathan1055",
-      "role": "Maintainer"
+    "authors": [
+        {
+            "name": "Eric Schaefer (Eric Schaefer)",
+            "homepage": "https://www.drupal.org/u/eric-schaefer",
+            "role": "Maintainer"
+        },
+        {
+            "name": "Jonathan Smith (jonathan1055)",
+            "homepage": "https://www.drupal.org/u/jonathan1055",
+            "role": "Maintainer"
+        },
+        {
+            "name": "Pieter Frenssen (pfrenssen)",
+            "homepage": "https://www.drupal.org/u/pfrenssen",
+            "role": "Maintainer"
+        },
+        {
+            "name": "Rick Manelius (rickmanelius)",
+            "homepage": "https://www.drupal.org/u/rickmanelius",
+            "role": "Maintainer"
+        }
+    ],
+    "support": {
+        "issues": "https://www.drupal.org/project/issues/scheduler",
+        "source": "http://cgit.drupalcode.org/scheduler"
     },
-    {
-      "name": "Pieter Frenssen (pfrenssen)",
-      "homepage": "https://www.drupal.org/u/pfrenssen",
-      "role": "Maintainer"
-    },
-    {
-      "name": "Rick Manelius (rickmanelius)",
-      "homepage": "https://www.drupal.org/u/rickmanelius",
-      "role": "Maintainer"
+    "extra": {
+        "drush": {
+            "services": {
+                "drush.services.yml": "^9"
+            }
+        }
     }
-  ],
-  "support": {
-    "issues": "https://www.drupal.org/project/issues/scheduler",
-    "source": "http://cgit.drupalcode.org/scheduler"
-  },
-  "license": "GPL-2.0+"
 }
diff --git a/web/modules/scheduler/config/install/scheduler.settings.yml b/web/modules/scheduler/config/install/scheduler.settings.yml
index 4c82158c868d4f2e62f8fd6a40864f230efdc5e4..1af27d4cadec13e93a576acb355ca8ed3be8bac1 100644
--- a/web/modules/scheduler/config/install/scheduler.settings.yml
+++ b/web/modules/scheduler/config/install/scheduler.settings.yml
@@ -6,6 +6,7 @@ default_expand_fieldset: 'when_required'
 default_fields_display_mode: 'vertical_tab'
 default_publish_enable: false
 default_publish_past_date: 'error'
+default_publish_past_date_created: false
 default_publish_required: false
 default_publish_revision: false
 default_publish_touch: false
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 2b1c39d21fa089768d8d64d151f83317264b65ae..98549cb439f871d17a4091df0bb504db415b6157 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
@@ -7,11 +7,11 @@ dependencies:
     - user
 id: scheduler_scheduled_content
 label: 'Scheduled content'
-module: node
+module: views
 description: 'Find and manage scheduled content.'
-tag: default
-base_table: node_field_data
-base_field: nid
+tag: ''
+base_table: node_field_revision
+base_field: vid
 core: 8.x
 display:
   default:
@@ -20,8 +20,15 @@ display:
         type: scheduler
       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:
@@ -36,6 +43,9 @@ display:
         type: full
         options:
           items_per_page: 50
+          offset: 0
+          id: 0
+          total_pages: null
           tags:
             previous: '‹ previous'
             next: 'next ›'
@@ -110,8 +120,6 @@ display:
               empty_column: false
               responsive: ''
             operations:
-              sortable: false
-              default_sort_order: asc
               align: ''
               separator: ''
               empty_column: false
@@ -125,27 +133,80 @@ display:
           id: node_bulk_form
           table: node
           field: node_bulk_form
+          relationship: nid
+          group_type: group
+          admin_label: ''
           label: ''
           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: {  }
           plugin_id: node_bulk_form
           entity_type: node
         title:
           id: title
-          table: node_field_data
+          table: node_field_revision
           field: title
+          relationship: none
+          group_type: group
+          admin_label: ''
           label: Title
           exclude: false
           alter:
             alter_text: false
+            make_link: false
+            absolute: false
+            trim: false
+            word_boundary: false
+            ellipsis: false
+            strip_tags: false
+            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
@@ -156,12 +217,23 @@ display:
           type: string
           settings:
             link_to_entity: true
+          click_sort_column: value
+          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
           plugin_id: field
         type:
           id: type
           table: node_field_data
           field: type
-          relationship: none
+          relationship: nid
           group_type: group
           admin_label: ''
           label: 'Content Type'
@@ -226,12 +298,45 @@ display:
           id: name
           table: users_field_data
           field: name
-          relationship: uid
+          relationship: revision_uid
+          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
@@ -239,33 +344,91 @@ display:
           hide_alter_empty: true
           plugin_id: field
           type: user_name
+          click_sort_column: value
+          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: user
           entity_field: name
         status:
           id: status
-          table: node_field_data
+          table: node_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
           plugin_id: field
           entity_type: node
           entity_field: status
         publish_on:
           id: publish_on
-          table: node_field_data
+          table: node_field_revision
           field: publish_on
           relationship: none
           group_type: group
@@ -311,15 +474,28 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
-          date_format: short
-          custom_date_format: ''
-          timezone: ''
+          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: node
           entity_field: publish_on
-          plugin_id: date
+          plugin_id: field
         unpublish_on:
           id: unpublish_on
-          table: node_field_data
+          table: node_field_revision
           field: unpublish_on
           relationship: none
           group_type: group
@@ -365,15 +541,28 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
-          date_format: short
-          custom_date_format: ''
-          timezone: ''
+          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: node
           entity_field: unpublish_on
-          plugin_id: date
+          plugin_id: field
         operations:
           id: operations
-          table: node
+          table: node_revision
           field: operations
           relationship: none
           group_type: group
@@ -420,59 +609,94 @@ display:
           empty_zero: false
           hide_alter_empty: true
           destination: true
+          entity_type: node
           plugin_id: entity_operations
       filters:
-        status:
-          id: status
-          table: node_field_data
-          field: status
+        latest_revision:
+          id: latest_revision
+          table: node_revision
+          field: latest_revision
           relationship: none
           group_type: group
           admin_label: ''
           operator: '='
-          value: '1'
+          value: ''
           group: 1
-          exposed: true
+          exposed: false
           expose:
             operator_id: ''
-            label: Status
+            label: ''
             description: ''
             use_operator: false
-            operator: status_op
-            identifier: status
+            operator: ''
+            identifier: ''
             required: false
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
-          is_grouped: true
+              anonymous: '0'
+              authenticated: '0'
+              administrator: '0'
+          is_grouped: false
           group_info:
-            label: 'Published status'
+            label: ''
             description: ''
-            identifier: status
+            identifier: ''
             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'
-          plugin_id: boolean
+            group_items: {  }
           entity_type: node
-          entity_field: status
+          plugin_id: latest_revision
+        title:
+          id: title
+          table: node_field_revision
+          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
+            identifier: title
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              anonymous: '0'
+              authenticated: '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: {  }
+          plugin_id: string
+          entity_type: node
+          entity_field: title
         type:
           id: type
           table: node_field_data
           field: type
-          relationship: none
+          relationship: nid
           group_type: group
           admin_label: ''
           operator: in
@@ -481,7 +705,7 @@ display:
           exposed: true
           expose:
             operator_id: type_op
-            label: Type
+            label: 'Content type'
             description: ''
             use_operator: false
             operator: type_op
@@ -490,8 +714,8 @@ display:
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
             reduce: false
           is_grouped: false
@@ -509,49 +733,57 @@ display:
           plugin_id: bundle
           entity_type: node
           entity_field: type
-        title:
-          id: title
-          table: node_field_data
-          field: title
+        status:
+          id: status
+          table: node_field_revision
+          field: status
           relationship: none
           group_type: group
           admin_label: ''
-          operator: contains
+          operator: '='
           value: ''
           group: 1
           exposed: true
           expose:
-            operator_id: title_op
-            label: Title
+            operator_id: ''
+            label: Status
             description: ''
             use_operator: false
-            operator: title_op
-            identifier: title
+            operator: status_op
+            identifier: status
             required: false
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
-          is_grouped: false
+          is_grouped: true
           group_info:
-            label: ''
+            label: 'Published status'
             description: ''
-            identifier: ''
+            identifier: status
             optional: true
             widget: select
             multiple: false
             remember: false
             default_group: All
             default_group_multiple: {  }
-            group_items: {  }
-          plugin_id: string
+            group_items:
+              1:
+                title: Published
+                operator: '='
+                value: '1'
+              2:
+                title: Unpublished
+                operator: '='
+                value: '0'
+          plugin_id: boolean
           entity_type: node
-          entity_field: title
+          entity_field: status
         langcode:
           id: langcode
-          table: node_field_data
+          table: node_field_revision
           field: langcode
           relationship: none
           group_type: group
@@ -571,8 +803,8 @@ display:
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
             reduce: false
           is_grouped: false
@@ -592,7 +824,7 @@ display:
           entity_field: langcode
         publish_on:
           id: publish_on
-          table: node_field_data
+          table: node_field_revision
           field: publish_on
           relationship: none
           group_type: group
@@ -606,40 +838,40 @@ display:
           group: 2
           exposed: false
           expose:
-            operator_id: publish_on_op
-            label: 'Publish on'
+            operator_id: ''
+            label: ''
             description: ''
-            use_operator: true
-            operator: publish_on_op
-            identifier: publish_on
+            use_operator: false
+            operator: ''
+            identifier: ''
             required: false
             remember: false
             multiple: false
+            placeholder: ''
+            min_placeholder: ''
+            max_placeholder: ''
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
           is_grouped: false
           group_info:
-            label: 'Publish on'
-            description: null
-            identifier: publish_on
+            label: ''
+            description: ''
+            identifier: ''
             optional: true
             widget: select
             multiple: false
             remember: false
             default_group: All
             default_group_multiple: {  }
-            group_items:
-              1: {  }
-              2: {  }
-              3: {  }
+            group_items: {  }
           entity_type: node
           entity_field: publish_on
           plugin_id: date
         unpublish_on:
           id: unpublish_on
-          table: node_field_data
+          table: node_field_revision
           field: unpublish_on
           relationship: none
           group_type: group
@@ -653,18 +885,21 @@ display:
           group: 2
           exposed: false
           expose:
-            operator_id: unpublish_on_op
-            label: 'Unpublish on'
+            operator_id: ''
+            label: ''
             description: ''
             use_operator: false
-            operator: unpublish_on_op
-            identifier: unpublish_on
+            operator: ''
+            identifier: ''
             required: false
             remember: false
             multiple: false
+            placeholder: ''
+            min_placeholder: ''
+            max_placeholder: ''
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
           is_grouped: false
           group_info:
@@ -683,6 +918,8 @@ display:
           plugin_id: date
       sorts: {  }
       title: 'Scheduled Content'
+      header: {  }
+      footer: {  }
       empty:
         area_text_custom:
           id: area_text_custom
@@ -697,14 +934,28 @@ display:
           plugin_id: text_custom
       arguments: {  }
       relationships:
-        uid:
-          id: uid
-          table: node_field_data
-          field: uid
-          admin_label: author
-          required: true
+        nid:
+          id: nid
+          table: node_field_revision
+          field: nid
+          admin_label: 'node id'
+          required: false
+          relationship: none
+          group_type: group
+          entity_type: node
+          entity_field: nid
+          plugin_id: standard
+        revision_uid:
+          id: revision_uid
+          table: node_revision
+          field: revision_uid
+          relationship: none
+          group_type: group
+          admin_label: 'revision user id'
+          required: false
+          entity_type: node
+          entity_field: revision_uid
           plugin_id: standard
-      show_admin_links: false
       filter_groups:
         operator: AND
         groups:
@@ -723,7 +974,6 @@ display:
         - url.query_args
         - user
         - 'user.node_grants:view'
-      cacheable: false
       max-age: 0
       tags: {  }
   overview:
@@ -733,6 +983,7 @@ display:
         type: tab
         title: Scheduled
         description: ''
+        expanded: false
         parent: system.admin_content
         weight: -10
         context: '0'
@@ -745,6 +996,7 @@ display:
         weight: -10
       display_extenders: {  }
       display_description: 'Overview of all scheduled content, as a tab on main ''content admin'' page'
+      display_comment: 'Revision nid relationship is required because the content type is only stored at ''content'' level, not ''content revision'' level.'
     display_plugin: page
     display_title: 'Content Overview'
     id: overview
@@ -757,7 +1009,6 @@ display:
         - url.query_args
         - user
         - 'user.node_grants:view'
-      cacheable: false
       max-age: 0
       tags: {  }
   user_page:
@@ -788,7 +1039,7 @@ display:
       arguments:
         uid:
           id: uid
-          table: node_field_data
+          table: node_field_revision
           field: uid
           relationship: none
           group_type: group
@@ -813,11 +1064,16 @@ display:
             sort_order: asc
             number_of_records: 0
             format: default_summary
-          specify_validation: false
+          specify_validation: true
           validate:
-            type: none
+            type: 'entity:user'
             fail: 'not found'
-          validate_options: {  }
+          validate_options:
+            operation: view
+            multiple: 0
+            access: false
+            restrict_roles: false
+            roles: {  }
           break_phrase: false
           not: false
           entity_type: node
@@ -847,6 +1103,5 @@ display:
         - url.query_args
         - user
         - 'user.node_grants:view'
-      cacheable: false
       max-age: 0
       tags: {  }
diff --git a/web/modules/scheduler/config/schema/scheduler.schema.yml b/web/modules/scheduler/config/schema/scheduler.schema.yml
index 10ee7b07ce30d15861f570a9a5dcb44458167a8a..765edb456015259e342b0e2052accf45c55e663a 100644
--- a/web/modules/scheduler/config/schema/scheduler.schema.yml
+++ b/web/modules/scheduler/config/schema/scheduler.schema.yml
@@ -28,6 +28,9 @@ scheduler.settings:
     default_publish_past_date:
       type: string
       label: 'Default value for nodetype setting publish_past_date'
+    default_publish_past_date_created:
+      type: boolean
+      label: 'Default value for nodetype setting publish_past_date_created'
     default_publish_required:
       type: boolean
       label: 'Default value for nodetype setting publish_required'
@@ -78,6 +81,9 @@ node.type.*.third_party.scheduler:
     publish_past_date:
       type: string
       label: 'Action to be taken for publication dates in the past'
+    publish_past_date_created:
+      type: boolean
+      label: 'Change content creation date for past dates to avoid "changed" being earlier than "created"'
     publish_required:
       type: boolean
       label: 'Require scheduled publishing'
diff --git a/web/modules/scheduler/drupalci.yml b/web/modules/scheduler/drupalci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e9aa20ae9f317d049cb01b043f2cfc06c083397
--- /dev/null
+++ b/web/modules/scheduler/drupalci.yml
@@ -0,0 +1,12 @@
+build:
+  assessment:
+    validate_codebase:
+      phplint:
+      container_composer:
+      csslint:
+      eslint:
+      phpcs:
+    testing:
+      run_tests.standard:
+        types: 'PHPUnit-Functional'
+        suppress-deprecations: false
diff --git a/web/modules/scheduler/drush.services.yml b/web/modules/scheduler/drush.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..53a9056f4c4dcc59ec03e118b032766c3b2c2571
--- /dev/null
+++ b/web/modules/scheduler/drush.services.yml
@@ -0,0 +1,8 @@
+services:
+  scheduler.commands:
+    class: \Drupal\scheduler\Commands\SchedulerCommands
+    arguments:
+      - '@scheduler.manager'
+      - '@messenger'
+    tags:
+      - { name: drush.command }
diff --git a/web/modules/scheduler/scheduler.admin.inc b/web/modules/scheduler/scheduler.admin.inc
index 94fb146f9033c2146f011cabd73064f5114211c6..26654cf45d1d3fc991119ae95d3bd7a1a5c9f2a1 100644
--- a/web/modules/scheduler/scheduler.admin.inc
+++ b/web/modules/scheduler/scheduler.admin.inc
@@ -92,6 +92,21 @@ function _scheduler_form_node_type_form_alter(array &$form, FormStateInterface $
       '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'] = [
@@ -144,7 +159,6 @@ function _scheduler_form_node_type_form_alter(array &$form, FormStateInterface $
       ],
     ],
   ];
-  // @todo Worthwhile to port this to D8 now form displays are configurable?
   $form['scheduler']['node_edit_layout']['scheduler_fields_display_mode'] = [
     '#type' => 'radios',
     '#title' => t('Display scheduling options as'),
@@ -176,6 +190,7 @@ function scheduler_form_node_type_form_builder($entity_type, NodeTypeInterface $
   $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'));
diff --git a/web/modules/scheduler/scheduler.api.php b/web/modules/scheduler/scheduler.api.php
index e55004c24326498341c6743bfaf928489b1d803d..3304947102555660676f30bf2ab9ea009b3c6d2f 100644
--- a/web/modules/scheduler/scheduler.api.php
+++ b/web/modules/scheduler/scheduler.api.php
@@ -78,7 +78,7 @@ function hook_scheduler_allow_publishing(NodeInterface $node) {
     // If publication is denied then inform the user why. This message will be
     // displayed during node edit and save.
     if (!$allowed) {
-      drupal_set_message(t('The content will only be published after approval by the CEO.'), 'status', FALSE);
+      \Drupal::messenger()->addMessage(t('The content will only be published after approval by the CEO.'), 'status', FALSE);
     }
   }
 
@@ -111,13 +111,129 @@ function hook_scheduler_allow_unpublishing(NodeInterface $node) {
     // If unpublication is denied then inform the user why. This message will be
     // displayed during node edit and save.
     if (!$allowed) {
-      drupal_set_message(t('The competition will only be unpublished after all prizes have been claimed by the winners.'));
+      \Drupal::messenger()->addMessage(t('The competition will only be unpublished after all prizes have been claimed by the winners.'));
     }
   }
 
   return $allowed;
 }
 
+/**
+ * Hook function to hide the Publish On 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.
+ *
+ * @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\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_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_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.
+ *
+ * @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\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_on_field(array $form, FormStateInterface $form_state, NodeInterface $node) {
+  return FALSE;
+}
+
+/**
+ * Hook function to process the publish action for a node.
+ *
+ * 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 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.
+ *
+ * @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 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
+ *     node with no further action and move on to the next one.
+ */
+function hook_scheduler_publish_action(NodeInterface $node) {
+  return 0;
+}
+
+/**
+ * Hook function to process the unpublish action for a node.
+ *
+ * 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.
+ *
+ * @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 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
+ *     node with no further action and move on to the next one.
+ */
+function hook_scheduler_unpublish_action(NodeInterface $node) {
+  return 0;
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/web/modules/scheduler/scheduler.drush.inc b/web/modules/scheduler/scheduler.drush.inc
index cba3f479259d3d098d6fe6777416a65ba3f57c23..09ebbfe3d8d0f006c9943e7564c98d3d02156fd0 100644
--- a/web/modules/scheduler/scheduler.drush.inc
+++ b/web/modules/scheduler/scheduler.drush.inc
@@ -12,7 +12,7 @@ function scheduler_drush_command() {
   $items = [];
 
   $items['scheduler-cron'] = [
-    'description' => 'Lighweight cron to process scheduler tasks.',
+    'description' => 'Lightweight cron to process scheduler tasks.',
     'core' => ['8+'],
     'aliases' => ['sch-cron'],
     'category' => 'scheduler',
@@ -25,10 +25,10 @@ function scheduler_drush_command() {
 }
 
 /**
- * Run lighweight scheduler cron.
+ * Run lightweight scheduler cron.
  */
 function drush_scheduler_cron() {
   \Drupal::service('scheduler.manager')->runLightweightCron();
   $nomsg = drush_get_option('nomsg', NULL);
-  $nomsg ? NULL : drupal_set_message(t('Scheduler lightweight cron completed'));
+  $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 f034a7684287d2387339690b8ebc4782cf77a596..72a5f50c0cf31076b7e31cc2ac0d92c38b0742c7 100644
--- a/web/modules/scheduler/scheduler.info.yml
+++ b/web/modules/scheduler/scheduler.info.yml
@@ -1,9 +1,10 @@
 name: Scheduler
 type: module
 description: 'Publish and unpublish content automatically on specified dates and times.'
-# core: 8.x
+core: 8.x
 configure: scheduler.admin_form
 dependencies:
+  - drupal:system (>= 8.5)
   - drupal:datetime
   - drupal:field
   - drupal:node
@@ -14,8 +15,7 @@ libraries:
   - scheduler/admin
   - vertical-tabs
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
diff --git a/web/modules/scheduler/scheduler.install b/web/modules/scheduler/scheduler.install
index 2e4982b896e2f91587236c8be62526724a76373f..08970676e340c18ab333bb61cedb61b843367da8 100644
--- a/web/modules/scheduler/scheduler.install
+++ b/web/modules/scheduler/scheduler.install
@@ -17,7 +17,7 @@ function scheduler_requirements($phase) {
   if ($phase === 'runtime') {
     $user = \Drupal::currentUser();
 
-    $now = REQUEST_TIME;
+    $now = \Drupal::time()->getRequestTime();
     $system_date = \Drupal::config('system.date');
     $date_default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
     $date_formatter = \Drupal::service('date.formatter');
@@ -71,8 +71,6 @@ function scheduler_requirements($phase) {
  * Implements hook_install().
  */
 function scheduler_install() {
-  // Add our base fields to the schema.
-  \Drupal::service('entity.definition_update_manager')->applyUpdates();
   // Set cron access key value, as this is now required in SchedulerCronForm.
   $config = \Drupal::service('config.factory')->getEditable('scheduler.settings');
   $config->set('lightweight_cron_access_key', substr(md5(rand()), 0, 20))
@@ -83,8 +81,6 @@ function scheduler_install() {
  * Implements hook_uninstall().
  */
 function scheduler_uninstall() {
-  // Remove our base fields from the schema.
-  \Drupal::service('entity.definition_update_manager')->applyUpdates();
   // Delete the scheduled content view.
   \Drupal::configFactory()->getEditable('views.view.scheduler_scheduled_content')->delete();
 }
diff --git a/web/modules/scheduler/scheduler.module b/web/modules/scheduler/scheduler.module
index 9f86b4d2ffcb2e8d2748e3088dd371dbc00552c9..a6d797966924ed5641b0bd6b110d6a5bc5b14dd7 100644
--- a/web/modules/scheduler/scheduler.module
+++ b/web/modules/scheduler/scheduler.module
@@ -72,9 +72,37 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
   $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 neither publishing nor unpublishing are enabled for this node type then
-  // the only thing to do is remove the fields from the form, then exit.
-  if (!$publishing_enabled && !$unpublishing_enabled) {
+  // 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'));
+
+  /* @var $node \Drupal\node\NodeInterface */
+  $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 = '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_on_field() to
+  // allow other modules to hide the field on the node edit form.
+  if ($unpublishing_enabled && $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 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']);
     return;
@@ -82,9 +110,6 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
 
   $date_only_allowed = $config->get('allow_date_only');
 
-  /* @var $node \Drupal\node\NodeInterface */
-  $node = $form_state->getFormObject()->getEntity();
-
   // A publish_on date is required if the content type option is set and the
   // node is being created or it currently has a scheduled publishing date.
   $publishing_required = $type->getThirdPartySetting('scheduler', 'publish_required', $config->get('default_publish_required'))
@@ -145,7 +170,7 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
     $descriptions['blank'] = t('Leave the date blank for no scheduled publishing.');
   }
 
-  $form['publish_on']['#access'] = $publishing_enabled;
+  $form['publish_on']['#access'] = $publishing_enabled && $publishing_displayed;
   $form['publish_on']['widget'][0]['value']['#required'] = $publishing_required;
   $form['publish_on']['widget'][0]['value']['#description'] = Xss::filter(implode(' ', $descriptions));
 
@@ -156,16 +181,23 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
     unset($descriptions['blank']);
   }
 
-  $form['unpublish_on']['#access'] = $unpublishing_enabled;
+  $form['unpublish_on']['#access'] = $unpublishing_enabled && $unpublishing_displayed;
   $form['unpublish_on']['widget'][0]['value']['#required'] = $unpublishing_required;
   $form['unpublish_on']['widget'][0]['value']['#description'] = Xss::filter(implode(' ', $descriptions));
 
   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 '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_settings']['#access'] = FALSE;
+    $form['publish_on']['#access'] = FALSE;
+    $form['unpublish_on']['#access'] = FALSE;
 
     // @todo Find a more elegant solution for bypassing the validation of
     // scheduler fields when the user does not have permission.
+    // Note: This scenario is NOT yet covered by any tests, neither in
+    // SchedulerPermissionsTest.php nor SchedulerRequiredTest.php
     // @see https://www.drupal.org/node/2651448
     $form['publish_on']['widget'][0]['value']['#required'] = FALSE;
     $form['unpublish_on']['widget'][0]['value']['#required'] = FALSE;
@@ -173,14 +205,18 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
 
   // 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.
-  $storage_form_display = $form_state->getStorage()['form_display'];
-  $content = $storage_form_display->get('content');
-  $pluginDefinitions = $storage_form_display->get('pluginManager')->getDefinitions();
+  $pluginDefinitions = $display->get('pluginManager')->getDefinitions();
+  if ($publishing_enabled && $publishing_displayed) {
+    $fields_to_check[] = 'publish_on';
+  }
+  if ($unpublishing_enabled && $unpublishing_displayed) {
+    $fields_to_check[] = 'unpublish_on';
+  }
   $correct_widget_id = 'datetime_timestamp_no_default';
-  foreach (['publish_on' => $publishing_enabled, 'unpublish_on' => $unpublishing_enabled] as $field => $enabled) {
-    $actual_widget_id = $content[$field]['type'];
-    if ($enabled && $actual_widget_id != $correct_widget_id) {
-      drupal_set_message(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', [
+  foreach ($fields_to_check as $field) {
+    $actual_widget_id = $display->getComponent($field)['type'];
+    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'],
@@ -298,6 +334,7 @@ function scheduler_node_view(array &$build, EntityInterface $node, EntityViewDis
 function scheduler_node_presave(EntityInterface $node) {
   $config = \Drupal::config('scheduler.settings');
   $entity = $node->type->entity;
+  $request_time = \Drupal::time()->getRequestTime();
 
   // 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.
@@ -327,14 +364,14 @@ function scheduler_node_presave(EntityInterface $node) {
       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.
-        $node->set('publish_on', rand($node->created->value + 1, REQUEST_TIME + $time_range));
+        $node->set('publish_on', rand($node->created->value + 1, $request_time + $time_range));
       }
     }
     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.
-        $node->set('unpublish_on', rand(max($node->created->value, $node->publish_on->value), REQUEST_TIME + $time_range));
+        $node->set('unpublish_on', rand(max($node->created->value, $node->publish_on->value), $request_time + $time_range));
       }
     }
   }
@@ -349,19 +386,23 @@ function scheduler_node_presave(EntityInterface $node) {
     // 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 && $node->publish_on->value <= REQUEST_TIME) {
+    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);
       \Drupal::service('event_dispatcher')->dispatch(SchedulerEvents::PRE_PUBLISH_IMMEDIATELY, $event);
       $node = $event->getNode();
 
+      // Set the 'changed' timestamp to match what would have been done had this
+      // content been published via cron.
+      $node->setChangedTime($node->publish_on->value);
       // If required, set the created date to match published date.
-      if ($entity->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch'))) {
+      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);
       }
       $node->publish_on->value = NULL;
-      $node->setPublished(TRUE);
+      $node->setPublished();
 
       // Trigger the PUBLISH_IMMEDIATELY event so that modules can react after
       // the node has been published.
@@ -371,14 +412,14 @@ function scheduler_node_presave(EntityInterface $node) {
     }
     else {
       // Ensure the node is unpublished as it will be published by cron later.
-      $node->setPublished(FALSE);
+      $node->setUnpublished();
 
       // 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.
       if ($publication_allowed) {
         $date_formatter = \Drupal::service('date.formatter');
-        drupal_set_message(t('This post is unpublished and will be published @publish_time.', [
+        \Drupal::messenger()->addMessage(t('This post is unpublished and will be published @publish_time.', [
           '@publish_time' => $date_formatter->format($node->publish_on->value, 'long'),
         ]), 'status', FALSE);
       }
@@ -462,7 +503,7 @@ function scheduler_entity_extra_field_info() {
   // content type. This allows admins to adjust the weight of the group, and it
   // works for vertical tabs and separate fieldsets.
   $fields = [];
-  foreach (node_type_get_types() as $type) {
+  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'));
 
diff --git a/web/modules/scheduler/scheduler.services.yml b/web/modules/scheduler/scheduler.services.yml
index 00ab742e8e2ba576f3ed9154cd47a0109bbdb39f..512a67a0ccce53b287d5a9be8f79ea6a464e5a50 100644
--- a/web/modules/scheduler/scheduler.services.yml
+++ b/web/modules/scheduler/scheduler.services.yml
@@ -1,7 +1,14 @@
 services:
   scheduler.manager:
     class: Drupal\scheduler\SchedulerManager
-    arguments: ['@date.formatter', '@logger.channel.scheduler', '@module_handler', '@entity.manager', '@config.factory' ]
+    arguments:
+      - '@date.formatter'
+      - '@logger.channel.scheduler'
+      - '@module_handler'
+      - '@entity_type.manager'
+      - '@config.factory'
+      - '@event_dispatcher'
+      - '@datetime.time'
   logger.channel.scheduler:
     class: Drupal\Core\Logger\LoggerChannel
     factory: logger.factory:get
diff --git a/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc b/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc
deleted file mode 100644
index 9794a588d9ce880da5d4c7d758d2820fd219f639..0000000000000000000000000000000000000000
--- a/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-
-namespace Drupal\scheduler;
-
-use Drupal\Core\Form\FormStateInterface;
-
-/**
- * Display scheduled dates in 'countdown' format for use in Views.
- *
- * Field handler to display a countdown until a scheduled action.
- *
- * @TODO Is this file actually used and/or needed in Drupal 8?
- *
- * @see scheduler.views.inc
- * @see http://www.ericschaefer.org/blog/2011/01/09/custom-field-handlers-for-views-2-drupal
- */
-class SchedulerHandlerFieldSchedulerCountdown extends views_handler_field {
-  const SECOND_SCALE = 1;
-  const MINUTE_SCALE = 60;
-  const HOUR_SCALE = 3600;
-  const DAY_SCALE = 86400;
-  const WEEK_SCALE = 604800;
-
-  /**
-   * Add the timestamp_field into the SQL query.
-   *
-   * It is calculated as publish_on - REQUEST_TIME so the result is the number
-   * of seconds from now until publishing. If publish_on is in the past then
-   * NULL is returned.
-   */
-  public function query() {
-    $this->ensure_my_table();
-    $this->node_table = $this->query->ensure_table('node', $this->relationship);
-    $time_field = $this->definition['timestamp_field'];
-    $this->field_alias = $this->query->add_field(NULL, 'CASE WHEN (' . $time_field . ' > ' . REQUEST_TIME . ') THEN (' . $time_field . ' - ' . REQUEST_TIME . ') ELSE NULL END', $this->table_alias . '_' . $this->field);
-  }
-
-  /**
-   * Define our display options and provide defaults.
-   *
-   * @return array
-   *   An associative array containing the options.
-   */
-  public function optionDefinition() {
-    $options = parent::option_definition();
-    $options['countdown_display'] = ['default' => 'smart'];
-    $options['units_display'] = ['default' => 'long'];
-    return $options;
-  }
-
-  /**
-   * Defines the form for the user to select the display options.
-   */
-  public function optionsForm(array &$form, FormStateInterface $form_state) {
-    parent::options_form($form, $form_state);
-    $form['countdown_display'] = [
-      '#title' => t('Display countdown as'),
-      '#type' => 'radios',
-      '#options' => [
-        'smart' => t('Smart mode'),
-        'seconds' => t('Seconds'),
-        'minutes' => t('Minutes'),
-        'hours' => t('Hours'),
-        'days' => t('Days'),
-        'weeks' => t('Weeks'),
-      ],
-      '#default_value' => $this->options['countdown_display'],
-    ];
-    $form['units_display'] = [
-      '#title' => t('Display time units'),
-      '#type' => 'radios',
-      '#options' => [
-        'long' => t('Long (for example 3 days)'),
-        'short' => t('Short (for example 3d)'),
-        'none' => t('No units at all'),
-      ],
-      '#default_value' => $this->options['units_display'],
-    ];
-  }
-
-  /**
-   * Callback function to filter out unwanted values.
-   *
-   * Keep only the array scale values which are smaller than the countdown value
-   * being displayed.
-   */
-  public function scaleFilterCallback($array_value) {
-    return ($this->raw_value >= $array_value);
-  }
-
-  /**
-   * Renders the countdown value in the units required.
-   */
-  public function render($values) {
-    $countdown_display = $this->options['countdown_display'];
-    $this->raw_value = $values->{$this->field_alias};
-
-    $scales = [
-      'weeks' => self::WEEK_SCALE,
-      'days' => self::DAY_SCALE,
-      'hours' => self::HOUR_SCALE,
-      'minutes' => self::MINUTE_SCALE,
-      'seconds' => self::SECOND_SCALE,
-    ];
-    // If the field has been set to 'Smart', determine the right timescale.
-    if ($countdown_display == 'smart') {
-      $scales = array_filter($scales, [$this, 'scale_filter_callback']);
-      $scale = empty($scales) ? self::SECOND_SCALE : reset($scales);
-    }
-    // Otherwise use the fixed display requested.
-    else {
-      $scale = $scales[$countdown_display];
-    }
-
-    // Get the display value by dividing the original value by the scale.
-    $scaled_value = round($this->raw_value / $scale);
-
-    switch ($scale) {
-      case self::SECOND_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 second', '@count seconds', ['@count' => $scaled_value]);
-        $short = t('@counts', ['@count' => $scaled_value]);
-        break;
-
-      case self::MINUTE_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 minute', '@count minutes', ['@count' => $scaled_value]);
-        $short = t('@countm', ['@count' => $scaled_value]);
-        break;
-
-      case self::HOUR_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 hour', '@count hours', ['@count' => $scaled_value]);
-        $short = t('@counth', ['@count' => $scaled_value]);
-        break;
-
-      case self::DAY_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 day', '@count days', ['@count' => $scaled_value]);
-        $short = t('@countd', ['@count' => $scaled_value]);
-        break;
-
-      case self::WEEK_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 week', '@count weeks', ['@count' => $scaled_value]);
-        $short = t('@countw', ['@count' => $scaled_value]);
-        break;
-    }
-
-    switch ($this->options['units_display']) {
-      case 'long':
-        return $long;
-
-      case 'short':
-        return $short;
-
-      default:
-        return $scaled_value;
-    }
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/composer.json b/web/modules/scheduler/scheduler_rules_integration/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..c33f5aea34d40ece49cefc8d787f6cef313e8c12
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/composer.json
@@ -0,0 +1,10 @@
+{
+    "name": "drupal/scheduler_rules_integration",
+    "description": "Scheduler sub-module providing conditions, actions and events for use with the Rules module.",
+    "type": "drupal-module",
+    "license": "GPL-2.0+",
+    "require": {
+        "drupal/rules": "^3.x",
+        "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 973d9edba1b46939230e00fe3397289bdecade87..ef4c01c6e6c6159d5718355f1c5e3879d7eecc03 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,13 +1,12 @@
 name: Scheduler Rules Integration
 type: module
 description: 'Scheduler sub-module providing conditions, actions and events for use with the Rules module.'
-# core: 8.x
+core: 8.x
 dependencies:
   - rules:rules
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
index d1c7a02fb7fc52bb2646dbb9938e51691d89a738..5706b208ea036bbfd95b193111664e1390071572 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
@@ -18,7 +18,7 @@ class ExistingNodeIsScheduledForPublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
index b51a412bdd0bd0936c7c3d3a016b4deb99e690f4..10a3163440ef8d04767b211210cc506de9b524e8 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
@@ -18,7 +18,7 @@ class ExistingNodeIsScheduledForUnpublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
index 4bafa013fe3079b3a93214bc13292ebc5e6fa82f..8010e1df5cccc238fb81f95452e139725063e6d0 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
@@ -18,7 +18,7 @@ class NewNodeIsScheduledForPublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
index d002da6c3fc17acd0b33ebb3c841413ae1c6e739..2e638595b60faaa8c1679323f2e73cfc9d57d055 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
@@ -18,7 +18,7 @@ class NewNodeIsScheduledForUnpublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
index 266b7650b306bf9e8aa357f6b26c60b2c5411862..da4afa62df8a5a9e6fc5a854c7eb6beb6fd8a7db 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
@@ -17,7 +17,7 @@ class SchedulerHasPublishedThisNodeEvent extends Event {
   /**
    * The node which has been processed.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
index 2ba13099ea7b51ee965f6ec143a57118a640629e..08b6937117a05c104a86dc86f2e3b7d275db7108 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
@@ -17,7 +17,7 @@ class SchedulerHasUnpublishedThisNodeEvent extends Event {
   /**
    * The node which has been processed..
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
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 3727a96662e40d7e0ee42d3391d125e494cd011b..b15e96237144d0dee32a35207b0f5caaec6fe3b2 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
@@ -30,7 +30,7 @@ class PublishNow extends RulesActionBase {
    */
   public function doExecute() {
     $node = $this->getContextValue('node');
-    $node->setPublished(TRUE);
+    $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 7969a8e3d12facbbfaae7c5a4d45d7cefcb51b02..24951f0297a40c71e020efdf5d52334fa5f8c6cf 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
@@ -37,7 +37,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Remove publishing date" - Scheduled publishing is not enabled for %type content. To prevent this message add the condition "Scheduled publishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
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 2d48c8cd4380a5e96969a017886fefa02d8f0fba..25b4f9852c0465d90d8495794e1cf175f48438c3 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
@@ -37,7 +37,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Remove unpublishing date" -  Scheduled unpublishing is not enabled for %type content. To prevent this message add the condition "Scheduled unpublishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
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 6d3d676f1e51689825bbc3945623a6bfeb474a8d..cdea76dd95cd26fe97ed37da5059f69694a405fb 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
@@ -46,7 +46,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Set publishing date" - Scheduled publishing is not enabled for %type content. To prevent this message add the condition "Scheduled publishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
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 5bfc93f68fbef237a5901fc87fc011538deefc16..aa352bb73349115ef565fb592b400436021a0086 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
@@ -46,7 +46,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Set unpublishing date" - Scheduled unpublishing is not enabled for %type content. To prevent this message add the condition "Scheduled unpublishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
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 2be4278806c2406f956b8ae1670faa9081904371..498e9d66eb274c692cc47f9cfe1a45ee765e30de 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
@@ -30,7 +30,7 @@ class UnpublishNow extends RulesActionBase {
    */
   public function doExecute() {
     $node = $this->getContextValue('node');
-    $node->setPublished(FALSE);
+    $node->setUnpublished();
   }
 
 }
diff --git a/web/modules/scheduler/src/Commands/SchedulerCommands.php b/web/modules/scheduler/src/Commands/SchedulerCommands.php
new file mode 100644
index 0000000000000000000000000000000000000000..b453ba6f5f363b10307d78c1fc2475ea85c35f85
--- /dev/null
+++ b/web/modules/scheduler/src/Commands/SchedulerCommands.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\scheduler\Commands;
+
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\scheduler\SchedulerManager;
+use Drush\Commands\DrushCommands;
+
+/**
+ * Drush 9 Scheduler commands for Drupal Core 8.4+.
+ */
+class SchedulerCommands extends DrushCommands {
+
+  /**
+   * The Scheduler manager service.
+   *
+   * @var \Drupal\scheduler\SchedulerManager
+   */
+  protected $schedulerManager;
+
+  /**
+   * The Messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
+  /**
+   * SchedulerCommands constructor.
+   *
+   * @param \Drupal\scheduler\SchedulerManager $schedulerManager
+   *   Scheduler manager service.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   Messenger service.
+   */
+  public function __construct(SchedulerManager $schedulerManager, MessengerInterface $messenger) {
+    parent::__construct();
+    $this->schedulerManager = $schedulerManager;
+    $this->messenger = $messenger;
+  }
+
+  /**
+   * Lightweight cron to process Scheduler module tasks.
+   *
+   * @param array $options
+   *   An associative array of options whose values come from cli, aliases,
+   *   config, etc.
+   *
+   * @option nomsg
+   *   to avoid the "cron completed" message being written to the terminal.
+   * @option nolog
+   *   to overide the site setting and not write 'started' and 'completed'
+   *   messages to the dblog.
+   *
+   * @command scheduler:cron
+   * @aliases sch-cron, scheduler-cron
+   */
+  public function cron(array $options = ['nomsg' => NULL, 'nolog' => NULL]) {
+    $this->manager->runLightweightCron($options);
+
+    $options['nomsg'] ? NULL : $this->messenger->addMessage(dt('Scheduler lightweight cron completed.'));
+  }
+
+}
diff --git a/web/modules/scheduler/src/Controller/LightweightCronController.php b/web/modules/scheduler/src/Controller/LightweightCronController.php
index 44d5c0b34653af68614fbe375234515e0fcd0201..896497c1736b2d520daba40aa492b23db60146a4 100644
--- a/web/modules/scheduler/src/Controller/LightweightCronController.php
+++ b/web/modules/scheduler/src/Controller/LightweightCronController.php
@@ -4,6 +4,8 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\scheduler\SchedulerManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Response;
 
 /**
@@ -13,18 +15,42 @@
  */
 class LightweightCronController extends ControllerBase {
 
+  /**
+   * The scheduler manager.
+   *
+   * @var \Drupal\scheduler\SchedulerManager
+   */
+  protected $schedulerManager;
+
+  /**
+   * LightweightCronController constructor.
+   *
+   * @param \Drupal\scheduler\SchedulerManager $scheduler_manager
+   *   The scheduler manager.
+   */
+  public function __construct(SchedulerManager $scheduler_manager) {
+    $this->schedulerManager = $scheduler_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('scheduler.manager')
+    );
+  }
+
   /**
    * Index.
    *
-   * @return \Symfony\Component\HttpFoundation\RedirectResponse
-   *   RedirectResponse.
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The http response.
    */
   public function index() {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::service with dependency injection?
-    \Drupal::service('scheduler.manager')->runLightweightCron();
+    $this->schedulerManager->runLightweightCron();
 
-    return new Response('', 204);
+    return new Response('', Response::HTTP_NO_CONTENT);
   }
 
   /**
@@ -37,9 +63,7 @@ public function index() {
    *   The access result.
    */
   public function access($cron_key) {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::config with dependency injection?
-    $valid_cron_key = \Drupal::config('scheduler.settings')
+    $valid_cron_key = $this->config('scheduler.settings')
       ->get('lightweight_cron_access_key');
     return AccessResult::allowedIf($valid_cron_key == $cron_key);
   }
diff --git a/web/modules/scheduler/src/Form/SchedulerAdminForm.php b/web/modules/scheduler/src/Form/SchedulerAdminForm.php
index 5ae7061bbdcc5c4370efb3a79fb4ce757adda56e..4d00198157e12010176f77d2ac2675623e5895f4 100644
--- a/web/modules/scheduler/src/Form/SchedulerAdminForm.php
+++ b/web/modules/scheduler/src/Form/SchedulerAdminForm.php
@@ -100,7 +100,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#description' => $this->t('This is the time that will be used if the user does not enter a value. Format: HH:MM:SS.'),
       '#states' => [
         'visible' => [
-          ':input[name="scheduler_allow_date_only"]' => ['checked' => TRUE],
+          ':input[name="allow_date_only"]' => ['checked' => TRUE],
         ],
       ],
     ];
diff --git a/web/modules/scheduler/src/Form/SchedulerCronForm.php b/web/modules/scheduler/src/Form/SchedulerCronForm.php
index f9abb55880337c1c56d92eb80d4c1fe94048d7cb..41eef3bd0f20516edbb976549f163d1c60e85b63 100644
--- a/web/modules/scheduler/src/Form/SchedulerCronForm.php
+++ b/web/modules/scheduler/src/Form/SchedulerCronForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\scheduler\Form;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
@@ -24,19 +25,22 @@ class SchedulerCronForm extends ConfigFormBase {
   /**
    * The scheduler manager service.
    *
-   * @var Drupal\scheduler\SchedulerManager
+   * @var \Drupal\scheduler\SchedulerManager
    */
   protected $schedulerManager;
 
   /**
    * Creates a SchedulerCronForm instance.
    *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The factory for configuration objects.
    * @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler service.
-   * @var Drupal\scheduler\SchedulerManager $scheduler_manager
+   * @var \Drupal\scheduler\SchedulerManager $scheduler_manager
    *   The scheduler manager service.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, SchedulerManager $scheduler_manager) {
+  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, SchedulerManager $scheduler_manager) {
+    parent::__construct($config_factory);
     $this->moduleHandler = $module_handler;
     $this->schedulerManager = $scheduler_manager;
   }
@@ -45,7 +49,11 @@ public function __construct(ModuleHandlerInterface $module_handler, SchedulerMan
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('module_handler'), $container->get('scheduler.manager'));
+    return new static(
+      $container->get('config.factory'),
+      $container->get('module_handler'),
+      $container->get('scheduler.manager')
+    );
   }
 
   /**
@@ -147,7 +155,7 @@ public function generateRandomKey(array &$form, FormStateInterface $form_state)
    *   The current state of the form.
    */
   public function runLightweightCron(array &$form, FormStateInterface $form_state) {
-    $this->schedulerManager->runLightweightCron();
+    $this->schedulerManager->runLightweightCron(['admin_form' => TRUE]);
 
     if ($this->moduleHandler->moduleExists('dblog')) {
       $url = Url::fromRoute('dblog.overview')->toString();
@@ -158,9 +166,7 @@ public function runLightweightCron(array &$form, FormStateInterface $form_state)
       // overview does not exist. Show a simple status message.
       $message = $this->t('Lightweight cron run completed.');
     }
-    // @todo Replace drupal_set_message() with an injectable service in 8.1.x.
-    // @see https://www.drupal.org/node/2278383
-    drupal_set_message($message);
+    $this->messenger()->addMessage($message);
   }
 
 }
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
index 51ccff9c5a35edd0cd0ccc157e533e2d08978322..b4963e1dd940ca127fa873c059e842633ebbd078 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
@@ -18,7 +18,7 @@ public function validate($entity, Constraint $constraint) {
     $default_publish_past_date = \Drupal::config('scheduler.settings')->get('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 < REQUEST_TIME) {
+    if ($publish_on && $scheduler_publish_past_date == 'error' && $publish_on < \Drupal::time()->getRequestTime()) {
       $this->context->buildViolation($constraint->messagePublishOnDateNotInFuture)
         ->atPath('publish_on')
         ->addViolation();
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
index 05da8014ff94fe7cc695ae82b8c19b9c78ddf002..62464450b322fb2cadce8f7399b41970a5d6fb01 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
@@ -40,7 +40,7 @@ public function validate($entity, Constraint $constraint) {
     // Check that the unpublish-on date is in the future. Unlike the publish-on
     // field, there is no option to use a past date, as this is not relevant for
     // unpublshing. The date must ALWAYS be in the future if it is entered.
-    if ($unpublish_on && $unpublish_on < REQUEST_TIME) {
+    if ($unpublish_on && $unpublish_on < \Drupal::time()->getRequestTime()) {
       $this->context->buildViolation($constraint->messageUnpublishOnDateNotInFuture)
         ->atPath('unpublish_on')
         ->addViolation();
diff --git a/web/modules/scheduler/src/SchedulerEvent.php b/web/modules/scheduler/src/SchedulerEvent.php
index ca857e47501633f1168ae89f2052bb9eb224cfbd..b5d8fac9b9ff7b8f08c32e12f85d76ee3ce6f412 100644
--- a/web/modules/scheduler/src/SchedulerEvent.php
+++ b/web/modules/scheduler/src/SchedulerEvent.php
@@ -13,7 +13,7 @@ class SchedulerEvent extends Event {
   /**
    * Node object.
    *
-   * @var Drupal\Core\Entity\EntityInterface
+   * @var \Drupal\Core\Entity\EntityInterface
    */
   protected $node;
 
diff --git a/web/modules/scheduler/src/SchedulerManager.php b/web/modules/scheduler/src/SchedulerManager.php
index de17b181a3187614c20e04bf5daf0845a13379b5..2cc5dccbda7926631f54ad56a151e5690ab8890c 100644
--- a/web/modules/scheduler/src/SchedulerManager.php
+++ b/web/modules/scheduler/src/SchedulerManager.php
@@ -2,26 +2,31 @@
 
 namespace Drupal\scheduler;
 
-use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Datetime\DateFormatter;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Datetime\DateFormatterInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Link;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
-use Drupal\Core\Extension\ModuleHandler;
-use Drupal\node\Entity\Node;
 use Drupal\node\NodeInterface;
 use Drupal\scheduler\Exception\SchedulerMissingDateException;
 use Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException;
 use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Defines a scheduler manager.
  */
 class SchedulerManager {
 
+  use StringTranslationTrait;
+
   /**
    * Date formatter service object.
    *
-   * @var \Drupal\Core\Datetime\DateFormatter
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
    */
   protected $dateFormatter;
 
@@ -35,33 +40,49 @@ class SchedulerManager {
   /**
    * Module handler service object.
    *
-   * @var \Drupal\Core\Extension\ModuleHandler
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
    */
   protected $moduleHandler;
 
   /**
-   * Entity Manager service object.
+   * Entity Type Manager service object.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
 
   /**
    * Config Factory service object.
    *
-   * @var \Drupal\Core\Config\ConfigFactory
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
    */
   protected $configFactory;
 
+  /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $eventDispatcher;
+
+  /**
+   * The time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
   /**
    * Constructs a SchedulerManager object.
    */
-  public function __construct(DateFormatter $dateFormatter, LoggerInterface $logger, ModuleHandler $moduleHandler, EntityManager $entityManager, ConfigFactory $configFactory) {
+  public function __construct(DateFormatterInterface $dateFormatter, LoggerInterface $logger, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, ConfigFactoryInterface $configFactory, EventDispatcherInterface $eventDispatcher, TimeInterface $time) {
     $this->dateFormatter = $dateFormatter;
     $this->logger = $logger;
     $this->moduleHandler = $moduleHandler;
-    $this->entityManager = $entityManager;
+    $this->entityTypeManager = $entityTypeManager;
     $this->configFactory = $configFactory;
+    $this->eventDispatcher = $eventDispatcher;
+    $this->time = $time;
   }
 
   /**
@@ -74,11 +95,6 @@ public function __construct(DateFormatter $dateFormatter, LoggerInterface $logge
    * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException
    */
   public function publish() {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::service with dependency injection?
-    /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
-    $dispatcher = \Drupal::service('event_dispatcher');
-
     $result = FALSE;
     $action = 'publish';
 
@@ -87,12 +103,11 @@ public function publish() {
     $nids = [];
     $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action));
     if (!empty($scheduler_enabled_types)) {
-      // @TODO: \Drupal calls should be avoided in classes.
-      // Replace \Drupal::entityQuery with dependency injection?
-      $query = \Drupal::entityQuery('node')
+      $query = $this->entityTypeManager->getStorage('node')->getQuery()
         ->exists('publish_on')
-        ->condition('publish_on', REQUEST_TIME, '<=')
+        ->condition('publish_on', $this->time->getRequestTime(), '<=')
         ->condition('type', $scheduler_enabled_types, 'IN')
+        ->latestRevision()
         ->sort('publish_on')
         ->sort('nid');
       // Disable access checks for this query.
@@ -111,9 +126,8 @@ public function publish() {
     // 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.
-    $nodes = Node::loadMultiple($nids);
-    // @TODO: Node::loadMultiple calls should be avoided in classes.
-    // Replace with dependency injection?
+    /** @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
@@ -131,7 +145,7 @@ public function publish() {
         // 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 > REQUEST_TIME) {
+        if (empty($publish_on) || $publish_on > $this->time->getRequestTime()) {
           continue;
         }
 
@@ -140,13 +154,13 @@ public function publish() {
           continue;
         }
 
-        // $node->set('changed', $publish_on) will fail badly if an API call has
+        // $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->entityManager->getFieldDefinitions('node', $node->getType());
+          $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));
         }
@@ -154,46 +168,79 @@ public function publish() {
         // Trigger the PRE_PUBLISH event so that modules can react before the
         // node is published.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::PRE_PUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::PRE_PUBLISH, $event);
         $node = $event->getNode();
 
-        // Update timestamps.
-        $node->set('changed', $publish_on);
+        // Update 'changed' timestamp.
+        $node->setChangedTime($publish_on);
         $old_creation_date = $node->getCreatedTime();
-        if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_touch', $this->setting('default_publish_touch'))) {
+        $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.
-          // @TODO: 't' calls should be avoided in classes.
-          // Replace with dependency injection?
-          $node->revision_log = t('Node published by Scheduler on @now. Previous creation date was @date.', [
-            '@now' => $this->dateFormatter->format(REQUEST_TIME, 'short'),
-            '@date' => $this->dateFormatter->format($old_creation_date, 'short'),
-          ]);
+          $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->link(t('View node'));
-        $nodetype_url = Url::fromRoute('entity.node_type.edit_form', ['node_type' => $node->getType()]);
-        // @TODO: \Drupal calls should be avoided in classes.
-        // Replace \Drupal::l with dependency injection?
-        $nodetype_link = \Drupal::l(node_get_type_label($node) . ' ' . t('settings'), $nodetype_url);
+        $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_get_type_label($node),
+          '@type' => $node_type->label(),
           '%title' => $node->getTitle(),
-          'link' => $nodetype_link . ' ' . $view_link,
+          'link' => $node_type_link->toString() . ' ' . $view_link->toString(),
+          '@hook' => 'hook_' . $hook,
         ];
-        $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables);
 
-        // Use the actions system to publish the node.
-        $this->entityManager->getStorage('action')->load('node_publish_action')->getPlugin()->execute($node);
+        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 fall back to the standard actions system to
+          // publish the node.
+          $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables);
+          $this->entityTypeManager->getStorage('action')->load('node_publish_action')->getPlugin()->execute($node);
+        }
 
         // Invoke the event to tell Rules that Scheduler has published the node.
         if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) {
@@ -203,7 +250,7 @@ public function publish() {
         // Trigger the PUBLISH event so that modules can react after the node is
         // published.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::PUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::PUBLISH, $event);
         $event->getNode()->save();
 
         $result = TRUE;
@@ -223,11 +270,6 @@ public function publish() {
    * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException
    */
   public function unpublish() {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::service with dependency injection?
-    /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
-    $dispatcher = \Drupal::service('event_dispatcher');
-
     $result = FALSE;
     $action = 'unpublish';
 
@@ -236,12 +278,11 @@ public function unpublish() {
     $nids = [];
     $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action));
     if (!empty($scheduler_enabled_types)) {
-      // @TODO: \Drupal calls should be avoided in classes.
-      // Replace \Drupal::entityQuery with dependency injection?
-      $query = \Drupal::entityQuery('node')
+      $query = $this->entityTypeManager->getStorage('node')->getQuery()
         ->exists('unpublish_on')
-        ->condition('unpublish_on', REQUEST_TIME, '<=')
+        ->condition('unpublish_on', $this->time->getRequestTime(), '<=')
         ->condition('type', $scheduler_enabled_types, 'IN')
+        ->latestRevision()
         ->sort('unpublish_on')
         ->sort('nid');
       // Disable access checks for this query.
@@ -256,9 +297,8 @@ public function unpublish() {
     // Allow other modules to alter the list of nodes to be unpublished.
     $this->moduleHandler->alter('scheduler_nid_list', $nids, $action);
 
-    // @TODO: Node::loadMultiple calls should be avoided in classes.
-    // Replace with dependency injection?
-    $nodes = Node::loadMultiple($nids);
+    /** @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.
@@ -274,7 +314,7 @@ public function unpublish() {
         // 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 > REQUEST_TIME) {
+        if (empty($unpublish_on) || $unpublish_on > $this->time->getRequestTime()) {
           continue;
         }
 
@@ -283,7 +323,7 @@ public function unpublish() {
         // 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 <= REQUEST_TIME) {
+        if (!empty($publish_on) && $publish_on <= $this->time->getRequestTime()) {
           continue;
         }
 
@@ -292,13 +332,13 @@ public function unpublish() {
           continue;
         }
 
-        // $node->set('changed', $unpublish_on) will fail badly if an API call
+        // $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->entityManager->getFieldDefinitions('node', $node->getType());
+          $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));
         }
@@ -306,53 +346,79 @@ public function unpublish() {
         // Trigger the PRE_UNPUBLISH event so that modules can react before the
         // node is unpublished.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::PRE_UNPUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::PRE_UNPUBLISH, $event);
         $node = $event->getNode();
 
-        // Update timestamps.
-        $old_change_date = $node->getChangedTime();
-        $node->set('changed', $unpublish_on);
+        // 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.
-          // @TODO: 't' calls should be avoided in classes.
-          // Replace with dependency injection?
-          $node->revision_log = t('Node unpublished by Scheduler on @now. Previous change date was @date.', [
-            '@now' => $this->dateFormatter->format(REQUEST_TIME, 'short'),
-            '@date' => $this->dateFormatter->format($old_change_date, 'short'),
+          $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(). Save the value for use when calling Rules.
+        // calls to $node->save().
         $node->unpublish_on->value = NULL;
 
-        // Log the fact that a scheduled unpublication is about to take place.
-        $view_link = $node->link(t('View node'));
-        $nodetype_url = Url::fromRoute('entity.node_type.edit_form', ['node_type' => $node->getType()]);
-        // @TODO: \Drupal calls should be avoided in classes.
-        // Replace \Drupal::l with dependency injection?
-        $nodetype_link = \Drupal::l(node_get_type_label($node) . ' ' . t('settings'), $nodetype_url);
+        // 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_get_type_label($node),
+          '@type' => $node_type->label(),
           '%title' => $node->getTitle(),
-          'link' => $nodetype_link . ' ' . $view_link,
+          'link' => $node_type_link->toString() . ' ' . $view_link->toString(),
+          '@hook' => 'hook_' . $hook,
         ];
-        $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables);
 
-        // Use the actions system to publish the node.
-        $this->entityManager->getStorage('action')->load('node_unpublish_action')->getPlugin()->execute($node);
+        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 fall back to the standard actions system to
+          // unpublish the node.
+          $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables);
+          $this->entityTypeManager->getStorage('action')->load('node_unpublish_action')->getPlugin()->execute($node);
+        }
 
         // 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');
         }
 
-        // Trigger the UNPUBLISH event so that modules can react before the node
+        // Trigger the UNPUBLISH event so that modules can react after the node
         // is unpublished.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::UNPUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::UNPUBLISH, $event);
         $event->getNode()->save();
 
         $result = TRUE;
@@ -426,11 +492,25 @@ public function nidList($action) {
    * This function is called from the external crontab job via url
    * /scheduler/cron/{access key} or it can be run interactively from the
    * Scheduler configuration page at /admin/config/content/scheduler/cron.
+   * It is also executed when running Scheduler Cron via drush.
+   *
+   * @param array $options
+   *   Options passed from drush command or admin form.
    */
-  public function runLightweightCron() {
-    $log = $this->setting('log');
+  public function runLightweightCron(array $options = []) {
+    // When calling via drush the log messages can be avoided by using --nolog.
+    $log = $this->setting('log') && empty($options['nolog']);
     if ($log) {
-      $this->logger->notice('Lightweight cron run activated.');
+      if (array_key_exists('nolog', $options)) {
+        $trigger = 'drush command';
+      }
+      elseif (array_key_exists('admin_form', $options)) {
+        $trigger = 'admin user form';
+      }
+      else {
+        $trigger = 'url';
+      }
+      $this->logger->notice('Lightweight cron run activated by @trigger.', ['@trigger' => $trigger]);
     }
     scheduler_cron();
     if (ob_get_level() > 0) {
@@ -440,9 +520,8 @@ public function runLightweightCron() {
       }
     }
     if ($log) {
-      // @TODO: \Drupal calls should be avoided in classes.
-      // Replace \Drupal::l with dependency injection?
-      $this->logger->notice('Lightweight cron run completed.', ['link' => \Drupal::l(t('settings'), Url::fromRoute('scheduler.cron_form'))]);
+      $link = Link::fromTextAndUrl($this->t('settings'), Url::fromRoute('scheduler.cron_form'));
+      $this->logger->notice('Lightweight cron run completed.', ['link' => $link->toString()]);
     }
   }
 
@@ -459,4 +538,31 @@ protected function setting($key) {
     return $this->configFactory->get('scheduler.settings')->get($key);
   }
 
+  /**
+   * Helper method to load latest revision of each node.
+   *
+   * @param array $nids
+   *   Array of node ids.
+   *
+   * @return array
+   *   Array of loaded nodes.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  protected function loadNodes(array $nids) {
+    $node_storage = $this->entityTypeManager->getStorage('node');
+    $nodes = [];
+
+    // 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);
+    }
+
+    return $nodes;
+  }
+
 }
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 e23f609c9cc82e0721f16952e1629dcdfec7e519..2a67f10fe1255fe53e1e1c2e2afc072fa42ca4be 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
@@ -2,12 +2,11 @@ name: 'Scheduler Node Access Test'
 type: module
 description: 'Support module for Scheduler restricted node access testing.'
 package: Testing
-# core: 8.x
+core: 8.x
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
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 1427f62a705cb49cee7c6531dc7c140572505f6f..28cf6d1a031b81df6fbbf0bdb70c72dbb08afa97 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,12 +2,11 @@ name: 'Scheduler API Test'
 type: module
 description: 'Support module for Scheduler API-related testing.'
 package: Testing
-# core: 8.x
+core: 8.x
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
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 d1509db9223a00c49ff8d6e3ac76d5b99f124ad9..b3861648cf1b7633b25b2fc7e4ddb07729be4cf2 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
@@ -22,7 +22,7 @@ function scheduler_api_test_uninstall() {
     ->execute();
   if ($nids = $nids_query->fetchCol()) {
     entity_delete_multiple('node', $nids);
-    drupal_set_message(t('@number %type node(s) have been deleted.', [
+    \Drupal::messenger()->addMessage(t('@number %type node(s) have been deleted.', [
       '@number' => count($nids),
       '%type' => 'scheduler_api_test',
     ]));
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 70f0286c2aa3e2d25c19f16e9978fd2c303297a5..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
@@ -6,6 +6,7 @@
  */
 
 use Drupal\node\Entity\Node;
+use Drupal\node\NodeInterface;
 
 /**
  * Implements hook_scheduler_nid_list().
@@ -16,18 +17,18 @@ function scheduler_api_test_scheduler_nid_list($action) {
   // Check to see what test nodes exist.
   $query = \Drupal::entityQuery('node');
   $nodes = Node::loadMultiple($query->execute());
-
+  $request_time = \Drupal::time()->getRequestTime();
   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();
+      $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();
+      $node->set('unpublish_on', $request_time)->save();
       $nids[] = $nid;
     }
   }
@@ -40,7 +41,7 @@ function scheduler_api_test_scheduler_nid_list($action) {
 function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
   $query = \Drupal::entityQuery('node');
   $nodes = Node::loadMultiple($query->execute());
-
+  $request_time = \Drupal::time()->getRequestTime();
   foreach ($nodes as $nid => $node) {
     if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter do not publish me') {
       // Remove the node id.
@@ -48,7 +49,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
     }
     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();
+      $node->set('publish_on', $request_time)->save();
       $nids[] = $nid;
     }
     if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter do not unpublish me') {
@@ -57,7 +58,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
     }
     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();
+      $node->set('unpublish_on', $request_time)->save();
       $nids[] = $nid;
     }
   }
@@ -67,7 +68,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
 /**
  * Implements hook_scheduler_allow_publishing().
  */
-function scheduler_api_test_scheduler_allow_publishing($node) {
+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;
@@ -77,14 +78,14 @@ function scheduler_api_test_scheduler_allow_publishing($node) {
     $allowed = $node->field_approved_publishing->value;
     // If publication is denied then inform the user why.
     if (!$allowed) {
-      drupal_set_message(t('%title is scheduled for publishing, but will not be published until approved.', ['%title' => $node->title->value]), '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 node but
       // cater for no nid as the node may be new and not yet saved.
-      if ($node->publish_on->value <= REQUEST_TIME) {
+      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->link(t('View node')) : '',
+          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
         ]);
       }
     }
@@ -95,7 +96,7 @@ function scheduler_api_test_scheduler_allow_publishing($node) {
 /**
  * Implements hook_scheduler_allow_unpublishing().
  */
-function scheduler_api_test_scheduler_allow_unpublishing($node) {
+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;
@@ -105,17 +106,89 @@ function scheduler_api_test_scheduler_allow_unpublishing($node) {
     $allowed = $node->field_approved_unpublishing->value;
     // If unpublication is denied then inform the user why.
     if (!$allowed) {
-      drupal_set_message(t('%title is scheduled for unpublishing, but will not be unpublished until approved.', ['%title' => $node->title->value]), '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 node but
       // cater for no nid as the node may be new and not yet saved.
-      if ($node->unpublish_on->value <= REQUEST_TIME) {
+      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->link(t('View node')) : '',
+          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
         ]);
       }
     }
   }
   return $allowed;
 }
+
+/**
+ * Implements hook_scheduler_hide_publish_on_field().
+ */
+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 {
+    return FALSE;
+  }
+}
+
+/**
+ * Implements hook_scheduler_hide_unpublish_on_field().
+ */
+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 {
+    return FALSE;
+  }
+}
+
+/**
+ * Implements hook_scheduler_publish_action().
+ */
+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;
+  }
+  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_unpublish_action().
+ */
+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;
+  }
+  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;
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
index 7c8a99979377e04453e4c4bcc8bf39e06d6bcc7d..4c90cc3466b9f58927c666e8fd88321814172157 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
@@ -29,20 +29,20 @@ public function testAdminSettings() {
       'allow_date_only' => TRUE,
       'default_time' => '6:30',
     ];
-    $this->drupalPostForm('admin/config/content/scheduler', $settings, t('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->assertEqual($this->config('scheduler.settings')->get('default_time'), $this->seconds_formatted, 'The config setting for default_time is stored correctly.');
+    $this->assertEquals($this->seconds_formatted, $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time is stored correctly.');
 
     // Try to save an invalid time value.
     $settings = [
       'allow_date_only' => TRUE,
       'default_time' => '123',
     ];
-    $this->drupalPostForm('admin/config/content/scheduler', $settings, t('Save configuration'));
+    $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration');
     // Verify that an error is displayed and the value has not been saved.
-    $this->assertEqual($this->config('scheduler.settings')->get('default_time'), $this->seconds_formatted, 'The config setting for default_time has not changed.');
+    $this->assertEquals($this->seconds_formatted, $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time has not changed.');
     $this->assertText('The default time should be in the format HH:MM:SS', 'When an invalid default time is entered the correct error message is displayed.');
 
     // Show the status report, which includes the Scheduler timecheck.
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
index 278beaadceb2c15d89b0fa67def25d60766e7150..53fc962796513f42edfd696f3f6f89631bd5b95e 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
@@ -68,7 +68,7 @@ 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->drupalPostForm('node/add/' . $this->customName, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save');
     $this->assertText('is scheduled for publishing, but will not be published until approved.', 'The message is shown when scheduling a node which is not yet allowed to be published.');
 
     // Create a node that is scheduled but not approved for publication. Then
@@ -105,7 +105,7 @@ public function testAllowedPublishing() {
 
     // Check that a node can be approved and published via edit form.
     $node = $this->createUnapprovedNode('publish_on');
-    $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved_publishing[value]' => '1'], t('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(), 'An approved node with a date in the past is published immediately after saving via edit form.');
@@ -135,7 +135,7 @@ 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->drupalPostForm('node/add/' . $this->customName, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save');
     $this->assertText('is scheduled for unpublishing, but will not be unpublished until approved.', 'The message is shown when scheduling a node which is not yet allowed to be unpublished.');
 
     // Create a node that is scheduled but not approved for unpublication. Then
@@ -254,16 +254,16 @@ public function testApiNodeAction() {
 
     // 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', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', REQUEST_TIME)),
+      '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, t('Save'));
+    $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->assertEqual($node->title->value, 'Published immediately', 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.');
+    $this->assertEquals('Published immediately', $node->title->value, 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.');
   }
 
   /**
@@ -377,4 +377,120 @@ public function testNidListAlter() {
     $this->assertFalse($node4->isPublished(), 'After cron, node 4 "' . $node4->title->value . '" is unpublished.');
   }
 
+  /**
+   * Test the hooks which allow hiding of scheduler input fields.
+   *
+   * This covers hook_scheduler_hide_publish_on_field and
+   * hook_scheduler_hide_unpublish_on_field.
+   */
+  public function testHideField() {
+    $this->drupalLogin($this->schedulerUser);
+
+    // Create test nodes.
+    $node1 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Red will not have either field hidden',
+    ]);
+    $node2 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Orange will have the publish-on field hidden',
+    ]);
+    $node3 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Yellow will have the unpublish-on field hidden',
+    ]);
+    $node4 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Green will have both Scheduler fields hidden',
+    ]);
+
+    // Node 1 'red' should have both fields displayed.
+    $this->drupalGet('node/' . $node1->id() . '/edit');
+    $this->assertTrue($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The red publish-on field is shown.');
+    $this->assertTrue($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The red unpublish-on field is shown.');
+
+    // Node 2 'orange' should have only the publish-on field hidden.
+    $this->drupalGet('node/' . $node2->id() . '/edit');
+    $this->assertFalse($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The orange publish-on field is hidden.');
+    $this->assertTrue($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The orange unpublish-on field is shown.');
+
+    // Node 3 'yellow' should have only the unpublish-on field hidden.
+    $this->drupalGet('node/' . $node3->id() . '/edit');
+    $this->assertTrue($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The yellow publish-on field is shown.');
+    $this->assertFalse($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The yellow unpublish-on field is hidden.');
+
+    // Node 4 'green' should have both publish-on and unpublish-on hidden.
+    $this->drupalGet('node/' . $node4->id() . '/edit');
+    $this->assertFalse($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The green publish-on field is hidden.');
+    $this->assertFalse($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The green unpublish-on field is hidden.');
+  }
+
+  /**
+   * Test when other modules process the publish and unpublish actions.
+   *
+   * This covers hook_scheduler_publish_action and
+   * hook_scheduler_unpublish_action.
+   */
+  public function testHookPublishUnpublishAction() {
+    $this->drupalLogin($this->schedulerUser);
+
+    // Create test nodes.
+    $node1 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'title' => 'Red will cause a failure on publishing',
+      'publish_on' => strtotime('-1 day'),
+    ]);
+    $node2 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      '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 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.
+    // Therefore introduce a different colour.
+    $node4 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'title' => 'Blue will cause a failure on unpublishing',
+      'unpublish_on' => strtotime('-1 day'),
+    ]);
+
+    // Simulate a cron run.
+    scheduler_cron();
+
+    // Check the red node.
+    $this->nodeStorage->resetCache([$node1->id()]);
+    $node1 = $this->nodeStorage->load($node1->id());
+    $this->assertFalse($node1->isPublished(), 'The red node is still unpublished.');
+    $this->assertTrue($node1->publish_on->value, 'The red node still has a publish-on date.');
+
+    // Check the orange node.
+    $this->nodeStorage->resetCache([$node2->id()]);
+    $node2 = $this->nodeStorage->load($node2->id());
+    $this->assertFalse($node2->isPublished(), 'The orange node was unpublished by the API test module.');
+    $this->assertTrue(stristr($node2->title->value, 'unpublishing processed by API test module'), 'The orange node was processed by the API test module.');
+    $this->assertFalse($node2->unpublish_on->value, 'The orange node no longer has an unpublish-on date.');
+
+    // Check the yellow node.
+    $this->nodeStorage->resetCache([$node3->id()]);
+    $node3 = $this->nodeStorage->load($node3->id());
+    $this->assertTrue($node3->isPublished(), 'The yellow node was published by the API test module.');
+    $this->assertTrue(stristr($node3->title->value, 'publishing processed by API test module'), 'The yellow node was processed by the API test module.');
+    $this->assertFalse($node3->publish_on->value, 'The yellow node no longer has a publish-on date.');
+
+    // Check the blue node.
+    $this->nodeStorage->resetCache([$node4->id()]);
+    $node4 = $this->nodeStorage->load($node4->id());
+    $this->assertTrue($node4->isPublished(), 'The green node is still published.');
+    $this->assertTrue($node4->unpublish_on->value, 'The green node still has an unpublish-on date.');
+
+  }
+
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
index 3e7920f6965c4e905cd01ede2d53db8449e4e6e8..3c569a7be3ff298a8be958337e010ab3661d2ed1 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
@@ -14,16 +14,16 @@ class SchedulerBasicTest extends SchedulerBrowserTestBase {
    */
   public function testPublishingAndUnpublishing() {
     // Login is required here before creating the publish_on date and time
-    // values so that date.formatter can utilise the current users timezone. The
-    // constraints receive values which have been converted using the users
-    // timezone so they need to be consistent.
+    // values so that $this->dateFormatter can utilise the current users
+    // timezone. The constraints receive values which have been converted using
+    // the users timezone so they need to be consistent.
     $this->drupalLogin($this->schedulerUser);
 
     // Create node values. Set time to one hour in the future.
     $edit = [
       'title[0][value]' => 'Publish This Node',
-      'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(time() + 3600, 'custom', 'Y-m-d'),
-      'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(time() + 3600, 'custom', 'H:i:s'),
+      'publish_on[0][value][date]' => $this->dateFormatter->format(time() + 3600, 'custom', 'Y-m-d'),
+      'publish_on[0][value][time]' => $this->dateFormatter->format(time() + 3600, 'custom', 'H:i:s'),
     ];
     $this->helpTestScheduler($edit);
 
@@ -43,7 +43,7 @@ public function testPublishingAndUnpublishing() {
    * Schedules content, runs cron and asserts status.
    */
   protected function helpTestScheduler($edit) {
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     // Verify that the node was created.
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertTrue($node, sprintf('"%s" was created sucessfully.', $edit['title[0][value]']));
@@ -63,7 +63,7 @@ protected function helpTestScheduler($edit) {
     }
 
     // Modify the scheduler field data to a time in the past, then run cron.
-    $node->$key = REQUEST_TIME - 1;
+    $node->$key = $this->requestTime - 1;
     $node->save();
     $this->cronRun();
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
index 828554a893b4d71531a8e92e5af19f82b93c18ed..2ee9876e57a9fa3e0ac887e06f402de0ce825064 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
@@ -54,14 +54,14 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase {
   protected $typeName;
 
   /**
-   * The node type.
+   * The node type object.
    *
    * @var \Drupal\node\Entity\NodeType
    */
   protected $nodetype;
 
   /**
-   * The node storage.
+   * The node storage object.
    *
    * @var \Drupal\Core\Entity\EntityStorageInterface
    */
@@ -74,18 +74,32 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase {
    */
   protected $database;
 
+  /**
+   * The request time stored as interger for direct re-use in many tests.
+   *
+   * @var int
+   */
+  protected $requestTime;
+
+  /**
+   * The date formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
+   */
+  protected $dateFormatter;
+
   /**
    * {@inheritdoc}
    */
   public function setUp() {
     parent::setUp();
 
-    // Create a 'Basic Page' content type, with 'page' as the identifier.
-    // The test files 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 'page' and 'Basic page'.
-    $this->type = 'page';
-    $this->typeName = 'Basic page';
+    // 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,
@@ -99,17 +113,17 @@ public function setUp() {
 
     // Define nodeStorage for use in many tests.
     /** @var EntityStorageInterface $nodeStorage */
-    $this->nodeStorage = $this->container->get('entity.manager')->getStorage('node');
+    $this->nodeStorage = $this->container->get('entity_type.manager')->getStorage('node');
 
     // Create an administrator user having the main admin permissions, full
-    // rights on the 'page' content type and all of the Scheduler permissions.
+    // 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.
     $this->adminUser = $this->drupalCreateUser([
-      'administer nodes',
       'access content',
       'access content overview',
       'access site reports',
+      'administer nodes',
       'administer site configuration',
       'create ' . $this->type . ' content',
       'edit own ' . $this->type . ' content',
@@ -134,6 +148,12 @@ public function setUp() {
     // Store the database connection for re-use in the actual tests.
     $this->database = $this->container->get('database');
 
+    // Determine the request time and save for re-use in the actual tests.
+    $this->requestTime = $this->container->get('datetime.time')->getRequestTime();
+
+    // Store the core dateFormatter service for re-use in the actual tests.
+    $this->dateFormatter = $this->container->get('date.formatter');
+
   }
 
   /**
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
index a0d0a22de9e4ab67f37408f521d91a838d5a7285..a90807e8bb81d6b23502f69ed34321b09e3b9223 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
+use DateTime;
+use DateInterval;
+
 /**
  * Tests the default time functionality.
  *
@@ -15,23 +18,25 @@ class SchedulerDefaultTimeTest extends SchedulerBrowserTestBase {
   public function testDefaultTime() {
     $this->drupalLogin($this->schedulerUser);
     $config = $this->config('scheduler.settings');
-    $date_formatter = \Drupal::service('date.formatter');
 
-    // For this test we use an offset of 6 hours 30 minutes (23400 seconds).
-    $seconds = 23400;
+    // For this test we use a default time of 6:30am.
+    $default_time = '06:30:00';
+    $config->set('default_time', $default_time)->save();
+
+    // Create DateTime objects to hold the two scheduling dates. This is better
+    // than using raw unix timestamps because it caters for daylight-saving
+    // shifts properly.
+    // @see https://www.drupal.org/project/scheduler/issues/2957490
+    $publish_time = new DateTime();
+    $publish_time->add(new DateInterval('P1D'))->setTime(6, 30);
 
-    // If the test happens to be run at a time when '+1 day' puts the calculated
-    // publishing date into a different daylight-saving period then formatted
-    // time can be an hour different. To avoid these failures we use a fixed
-    // string when asserting the message and looking for field values.
-    // @see https://www.drupal.org/node/2809627
-    $seconds_formatted = '06:30:00';
-    $config->set('default_time', $seconds_formatted)->save();
+    $unpublish_time = new DateTime();
+    $unpublish_time->add(new DateInterval('P2D'))->setTime(6, 30);
 
-    // We cannot easily test the exact validation messages as they contain the
-    // REQUEST_TIME, which can be one or more seconds in the past. Best we can
+    // 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
     // do is check the fixed part of the message as it is when passed to t() in
-    // Datetime::validateDatetime. This will only work in English.
+    // Datetime::validateDatetime. Tests only needs to work in English anyway.
     $publish_validation_message = 'The Publish on date is invalid.';
     $unpublish_validation_message = 'The Unpublish on date is invalid.';
 
@@ -41,11 +46,11 @@ public function testDefaultTime() {
     // Test that entering a time is required.
     $edit = [
       'title[0][value]' => 'No time ' . $this->randomString(15),
-      'publish_on[0][value][date]' => $date_formatter->format(strtotime('+1 day', REQUEST_TIME), 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][date]' => $date_formatter->format(strtotime('+2 day', REQUEST_TIME), 'custom', 'Y-m-d'),
+      'publish_on[0][value][date]' => $publish_time->format('Y-m-d'),
+      'unpublish_on[0][value][date]' => $unpublish_time->format('Y-m-d'),
     ];
     // Create a node and check that the expected error messages are shown.
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $this->assertSession()->pageTextContains($publish_validation_message, 'By default it is required to enter a time when scheduling content for publication.');
     $this->assertSession()->pageTextContains($unpublish_validation_message, 'By default it is required to enter a time when scheduling content for unpublication.');
 
@@ -53,26 +58,27 @@ public function testDefaultTime() {
     $config->set('allow_date_only', TRUE)->save();
 
     // Create a node and check that the expected error messages are not shown.
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $this->assertSession()->pageTextNotContains($publish_validation_message, 'If the default time option is enabled the user can skip the time when scheduling content for publication.');
     $this->assertSession()->pageTextNotContains($unpublish_validation_message, 'If the default time option is enabled the user can skip the time when scheduling content for unpublication.');
 
+    // Get the pattern of the 'long' default date format.
+    $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.
-    $publish_time = strtotime('+1 day midnight', REQUEST_TIME) + $seconds;
-    $unpublish_time = strtotime('+2 day midnight', REQUEST_TIME) + $seconds;
-    $args = ['@publish_time' => $date_formatter->format($publish_time, 'long')];
-    $this->assertRaw(t('This post is unpublished and will be published @publish_time.', $args), 'The user is informed that the content will be published on the requested date, on the default time.');
+    $this->assertText(sprintf('This post is unpublished and will be published %s', $publish_time->format($long_pattern)), 'The user is informed that the content will be published on the requested date, on the default time.');
 
-    // Protect in case the node was not created.
+    // 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->assertEqual($node->publish_on->value, $publish_time, 'The node publish_on value is stored correctly.');
-      $this->assertEqual($node->unpublish_on->value, $unpublish_time, 'The node unpublish_on value is stored correctly.');
+      $this->assertEquals($publish_time->getTimestamp(), (int) $node->publish_on->value, 'The node publish_on value is stored correctly.');
+      $this->assertEquals($unpublish_time->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('node/' . $node->id() . '/edit');
-      $this->assertFieldByName('publish_on[0][value][time]', $seconds_formatted, 'The default time offset has been added to the date field when scheduling content for publication.');
-      $this->assertFieldByName('unpublish_on[0][value][time]', $seconds_formatted, 'The default time offset has been added to the date field when scheduling content for unpublication.');
+      $this->assertFieldByName('publish_on[0][value][time]', $default_time, 'The default time offset has been added to the date field when scheduling content for publication.');
+      $this->assertFieldByName('unpublish_on[0][value][time]', $default_time, 'The default time offset has been added to the date field when scheduling content for unpublication.');
 
     }
     else {
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
index 1ca309ac0ed4e247a9e1b5301fdf4a5d1b6f0b5e..46356e9415dd69c2362dfa5ad1522dd050630b2a 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\node\Entity\NodeType;
-
 /**
  * Tests deletion of nodes enabled for Scheduler.
  *
@@ -52,14 +50,14 @@ public function testDeleteNodeWhenSchedulingIsRequired() {
     // 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->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a published node with no scheduling information.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a published node with no scheduling information.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete a published node with no scheduling information.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a published node with no scheduling information.');
 
     // Do the same test for the unpublished node.
     $this->drupalGet('node/' . $unpublished_node->id() . '/edit');
     $this->clickLink('Delete');
-    $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete an unpublished node with no scheduling information.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete an unpublished node with no scheduling information.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete an unpublished node with no scheduling information.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete an unpublished node with no scheduling information.');
   }
 
   /**
@@ -89,14 +87,14 @@ public function testDeleteNodeWithPastDates() {
     // Attempt to delete the published node and check for no validation error.
     $this->drupalGet('node/' . $published_node->id() . '/edit');
     $this->clickLink('Delete');
-    $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a node with an unpublish date in the past.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a node with an unpublish date in the past.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete a node with an unpublish date in the past.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a node with an unpublish date in the past.');
 
     // Attempt to delete the unpublished node and check for no validation error.
     $this->drupalGet('node/' . $unpublished_node->id() . '/edit');
     $this->clickLink('Delete');
-    $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a node with a publish date in the past.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a node with a publish date in the past.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete a node with a publish date in the past.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a node with a publish date in the past.');
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php
index f1f5fc1a1cb73b3a5ae6aa7213cf163ce709f740..3b0f7228ae2df1454d5ee20cf4f65867f13c70fd 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 {
 
@@ -57,19 +61,19 @@ public function setUp() {
    */
   protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled, $time_range = NULL) {
     // Check that the expected number of nodes have been created.
-    $count = \Drupal::entityQuery('node')
+    $count = $this->nodeStorage->getQuery()
       ->condition('type', $type)
       ->count()
       ->execute();
-    $this->assertEqual($count, $num_nodes, sprintf('The expected number of %s is %s, found %s', $type, $num_nodes, $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 nodes have been scheduled.
-    $count = \Drupal::entityQuery('node')
+    $count = $this->nodeStorage->getQuery()
       ->condition('type', $type)
       ->exists($field)
       ->count()
       ->execute();
-    $this->assertEqual($count, $num_scheduled, sprintf('The expected number of scheduled %s is %s, found %s', $field, $num_scheduled, $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)) {
       // Define the minimum and maximum times that we expect the scheduled dates
@@ -78,10 +82,10 @@ protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled
       // slowly creep forward during sucessive calls. Tests can fail incorrectly
       // for this reason, hence the best approximation is to use time() when
       // calculating the upper end of the range.
-      $min = REQUEST_TIME - $time_range;
+      $min = $this->requestTime - $time_range;
       $max = time() + $time_range;
 
-      $query = \Drupal::entityQueryAggregate('node');
+      $query = $this->nodeStorage->getAggregateQuery();
       $result = $query
         ->condition('type', $type)
         ->aggregate($field, 'min')
@@ -91,8 +95,8 @@ protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled
       $max_found = $result[0]["{$field}_max"];
 
       // 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, format_date($min_found, 'custom', 'j M, H:i:s'), format_date($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, format_date($max_found, 'custom', 'j M, H:i:s'), format_date($max, 'custom', 'j M, H:i:s')));
+      $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')));
     }
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
index ceba0168f104e7b37b090efd00f7e37a46a863a6..3781fdad6fc58c5fb58378a2f33bcaf3cffd03c8 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\node\Entity\NodeType;
-
 /**
  * Tests the display of the date entry fields (vertical tab, fieldset).
  *
@@ -18,19 +16,38 @@ class SchedulerFieldsDisplayTest extends SchedulerBrowserTestBase {
    */
   public static $modules = ['field_ui'];
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    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 scheduler_form_node_form_alter().
    */
-  public function testFieldsDisplay() {
+  public function testVerticalTabOrFieldset() {
     $this->drupalLogin($this->adminUser);
 
     // Check that the dates are shown in a vertical tab by default.
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'By default the scheduler dates are shown in a vertical tab.');
 
     // Check that the dates are shown as a fieldset when configured to do so.
     $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'fieldset')->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertFalse($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are not shown in a vertical tab when they are configured to show as a fieldset.');
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are shown in a fieldset when they are configured to show as a fieldset.');
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and not(@open = "open")]'), 'The scheduler dates fieldset is collapsed by default.');
@@ -38,19 +55,19 @@ public function testFieldsDisplay() {
     // Check that the fieldset is expanded if either of the scheduling dates
     // are required.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', TRUE)->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the publish-on date is required.');
 
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the unpublish-on date is required.');
 
     // Check that the fieldset is expanded if the 'always' option is set.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the option to always expand is turned on.');
 
     // Check that the fieldset is expanded if the node already has a publish-on
@@ -74,6 +91,12 @@ public function testFieldsDisplay() {
     $node = $this->drupalCreateNode($options);
     $this->drupalGet('node/' . $node->id() . '/edit');
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when an unpublish-on date already exists.');
+
+    // Check that the display reverts to a vertical tab again when specifically
+    // configured to do so.
+    $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'vertical_tab')->save();
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are shown in a vertical tab when that option is set.');
   }
 
   /**
@@ -82,14 +105,7 @@ public function testFieldsDisplay() {
    * This test covers scheduler_entity_extra_field_info().
    */
   public function testManageFormDisplay() {
-
-    // Create a custom administrator user with permissions to use the field_ui
-    // module 'node form display' tab.
-    $this->adminUser = $this->drupalCreateUser([
-      'administer content types',
-      'administer node form display',
-    ]);
-    $this->drupalLogin($this->adminUser);
+    $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.
@@ -104,4 +120,54 @@ public function testManageFormDisplay() {
     $this->assertNoFieldById('edit-fields-scheduler-settings-weight', NULL, 'The scheduler settings row is not shown when the content type is not enabled for scheduling.');
   }
 
+  /**
+   * Tests the edit form when scheduler fields have been disabled.
+   *
+   * This test covers scheduler_form_node_form_alter().
+   */
+  public function testDisabledFields() {
+    $this->drupalLogin($this->adminUser2);
+
+    // 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 a scheduler vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler input is a vertical tab.');
+    // Check the publish_on field is not shown, but the unpublish_on field is.
+    $this->assertNoFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is not shown - 1');
+    $this->assertFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is shown - 1');
+
+    // 2. Set publish_on to be displayed but hide the unpublish_on field.
+    $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 a scheduler vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler input is a vertical tab.');
+    // Check the publish_on field is not shown, but the unpublish_on field is.
+    $this->assertFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is shown - 2');
+    $this->assertNoFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is not shown - 2');
+
+    // 3. Set both fields to be hidden.
+    $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 no vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertFalse($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler vertical tab is not shown.');
+    // Check the neither field is displayed.
+    $this->assertNoFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is not shown - 3');
+    $this->assertNoFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is not shown - 3');
+  }
+
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
index 1bad0e0e4383c19509f355317119becd4c6fc441..e2878457f192c168062db6936a86e6134a0fa7cb 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
@@ -5,7 +5,7 @@
 use Drupal\Core\Url;
 
 /**
- * Tests the Scheduler lighweight cron urls and admin form.
+ * Tests the Scheduler lightweight cron urls and admin form.
  *
  * @group scheduler
  */
@@ -56,7 +56,7 @@ public function testLightweightCronSettingsForm() {
     $this->assertTrue(strlen($key) == 20, 'Default lightweight cron key string length is 20');
 
     // Check that a new random key can be generated.
-    $this->drupalPostForm($this->routeCronForm, [], t('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->assertTrue(!empty($new_key), 'Lightweight cron key field is not empty after generating new key');
@@ -64,11 +64,11 @@ public function testLightweightCronSettingsForm() {
     $this->assertNotEqual($key, $new_key, 'Lightweight cron key has changed.');
 
     // Check that the 'run lightweight cron' button works.
-    $this->drupalPostForm($this->routeCronForm, [], t("Run Scheduler's lightweight cron now"));
+    $this->drupalPostForm($this->routeCronForm, [], "Run Scheduler's lightweight cron now");
     $this->assertText('Lightweight cron run completed.', 'Lightweight cron runs OK manually');
 
     // Check that the form cannot be saved if the cron key is blank.
-    $this->drupalPostForm($this->routeCronForm, ['lightweight_access_key' => ''], t('Save configuration'));
+    $this->drupalPostForm($this->routeCronForm, ['lightweight_access_key' => ''], 'Save configuration');
     $this->assertText('Lightweight cron access key field is required.', 'Saving configuration with a blank cron key throws the expected validation message');
     $this->assertNoText('The configuration options have been saved.', 'Saving configuration with a blank cron key is not possible');
   }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
index 859f0c558b1438e41785b5b848853a967e1ffb06..f918d2a487f27b0c46859ed1067123f1eb7babb0 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
@@ -34,10 +34,10 @@ public function testMetaInformation() {
     // Set a scheduler unpublish date on the node.
     $unpublish_date = strtotime('+1 day');
     $edit = [
-      'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format($unpublish_date, 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format($unpublish_date, 'custom', 'H:i:s'),
+      '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, t('Save'));
+    $this->drupalPostForm('node/' . $published_node->id() . '/edit', $edit, 'Save');
 
     // The node page should now have an X-Robots-Tag header with an
     // unavailable_after-directive and RFC850 date- and time-value.
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
index 7823bc79f5464efb76559ad9a1f18df7b1f685d2..e434e9f613cd45096c9b731c597bf5c1321502dc 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
@@ -89,19 +89,19 @@ private function checkStatus($nid, $description, array $st) {
     $this->nodeStorage->resetCache([$nid]);
     $node = $this->nodeStorage->load($nid);
 
-    foreach ($st as $key => $status) {
+    foreach ($st as $key => $expected_status) {
       if ($key == 0) {
         // Key 0 is the original, so we just check $node.
-        $this->assertEqual($node->isPublished(), $status,
-          sprintf('%s: The original content (%s) is %s', $description, $this->languages[$key]['name'], ($status ? 'published' : 'unpublished')));
+        $this->assertEquals($expected_status, $node->isPublished(),
+          sprintf('%s: The original content (%s) is %s', $description, $this->languages[$key]['name'], ($expected_status ? 'published' : 'unpublished')));
       }
       else {
         // Key > 0 are the translations, which we get using the Content
         // Translation Manager getTranslationMetadata() function.
         $trans = $this->ctm->getTranslationMetadata($node->getTranslation($this->languages[$key]['code']));
         $trans = $node->getTranslation($this->languages[$key]['code']);
-        $this->assertEqual($trans->isPublished(), $status,
-          sprintf('%s: Translation %d (%s) is %s', $description, $key, $this->languages[$key]['name'], ($status ? 'published' : 'unpublished')));
+        $this->assertEquals($expected_status, $trans->isPublished(),
+          sprintf('%s: Translation %d (%s) is %s', $description, $key, $this->languages[$key]['name'], ($expected_status ? 'published' : 'unpublished')));
       }
     }
   }
@@ -117,9 +117,9 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     // parameters passed in.
     $this->drupalGet('admin/config/regional/content-language');
     $settings = [
-      'edit-settings-node-page-settings-language-language-alterable' => TRUE,
-      'edit-settings-node-page-fields-publish-on' => $publish_on_translatable,
-      'edit-settings-node-page-fields-unpublish-on' => $unpublish_on_translatable,
+      'edit-settings-node-' . $this->type . '-settings-language-language-alterable' => TRUE,
+      'edit-settings-node-' . $this->type . '-fields-publish-on' => $publish_on_translatable,
+      'edit-settings-node-' . $this->type . '-fields-unpublish-on' => $unpublish_on_translatable,
     ];
     // The submit shows the updated values, so no need for second get.
     $this->submitForm($settings, 'Save configuration');
@@ -160,8 +160,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     $this->drupalGet('node/' . $node->id() . '/translations/add/' . $this->languages[0]['code'] . '/' . $this->languages[2]['code']);
     $edit = [
       'title[0][value]' => $this->languages[2]['name'] . '(2) - Publish in the future',
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', REQUEST_TIME)),
+      '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->submitForm($edit, $save_button_text);
 
@@ -169,8 +169,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     $this->drupalGet('node/' . $node->id() . '/translations/add/' . $this->languages[0]['code'] . '/' . $this->languages[3]['code']);
     $edit = [
       'title[0][value]' => $this->languages[3]['name'] . '(3) - Publish in the past',
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', REQUEST_TIME)),
+      '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->submitForm($edit, $save_button_text);
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
index 7f5f25f3daa775db041991370b269282107c85b2..f9a56f58b038cda189c765f4394796ae4936529d 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
@@ -57,7 +57,7 @@ public function testNodeAccess() {
         'type' => $this->type,
         'status' => $data['status'],
         'title' => 'Test node to be ' . $data['after'],
-        $field => REQUEST_TIME + 1,
+        $field => $this->requestTime + 1,
       ];
       $node = $this->drupalCreateNode($settings);
       $this->drupalGet('node/' . $node->id());
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
index 79b162a38d676b78d2aea6c301190177ed6bd5a5..d4988fa45e80417194f738edaaf287d4bd9a7cc4 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
@@ -19,15 +19,13 @@ public function setUp() {
     $this->contentName = 'not_for_scheduler';
     $this->contentType = $this->drupalCreateContentType(['type' => $this->contentName, 'name' => 'Not for Scheduler']);
 
-    // Create an administrator user.
-    $this->adminUser = $this->drupalCreateUser([
+    // Create an admin user with permission to create the new content type.
+    $this->adminUser2 = $this->drupalCreateUser([
       'access content',
+      'administer nodes',
       'administer scheduler',
       'create ' . $this->contentName . ' content',
-      'edit own ' . $this->contentName . ' content',
-      'delete own ' . $this->contentName . ' content',
       'view own unpublished content',
-      'administer nodes',
       'schedule publishing of nodes',
       'access site reports',
     ]);
@@ -80,7 +78,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl
       'title' => $title,
       'status' => 0,
       'type' => $this->contentName,
-      'publish_on' => REQUEST_TIME - 2,
+      'publish_on' => $this->requestTime - 2,
     ];
     $node = $this->drupalCreateNode($edit);
 
@@ -107,7 +105,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl
       'title' => $title,
       'status' => 1,
       'type' => $this->contentName,
-      'unpublish_on' => REQUEST_TIME - 1,
+      'unpublish_on' => $this->requestTime - 1,
     ];
     $node = $this->drupalCreateNode($edit);
 
@@ -138,7 +136,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl
    */
   public function testNonEnabledNodeType() {
     // Log in.
-    $this->drupalLogin($this->adminUser);
+    $this->drupalLogin($this->adminUser2);
 
     // 1. By default check that the scheduler date fields are not displayed.
     $this->checkNonEnabledTypes(FALSE, FALSE, 1);
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
index e4c83058fd455e61b5afa676742a8b7222c7fefd..b5c8c5e9b7ce57f09ad7381eb298663dc10905e7 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Html;
 
 /**
  * Tests the options and processing when dates are entered in the past.
@@ -18,70 +18,105 @@ public function testSchedulerPastDates() {
     // Log in.
     $this->drupalLogin($this->schedulerUser);
 
-    // Ensure that neither of the scheduling dates are set to be required.
-    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
-      ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE)->save();
-
     // 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 = [
       'title[0][value]' => 'Past ' . $this->randomString(10),
-      'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(strtotime('-1 day', REQUEST_TIME), 'custom', 'Y-m-d'),
-      'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(strtotime('-1 day', REQUEST_TIME), 'custom', 'H:i:s'),
+      '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'),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $this->assertRaw(t("The 'publish on' date must be in the future"), 'An error message is shown by default when the publication date is in the past.');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertText("The 'publish on' date must be in the future", 'An error message is shown by default when the publication date is in the past.');
 
     // Test the 'error' behavior explicitly.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'error')->save();
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $this->assertRaw(t("The 'publish on' date must be in the future"), 'An error message is shown when the publication date is in the past and the "error" behavior is chosen.');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertText("The 'publish on' date must be in the future", 'An error message is shown when the publication date is in the past and the "error" behavior is chosen.');
 
     // 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, t('Save'));
-    $this->assertNoText(t("The 'publish on' date must be in the future"), 'No error message is shown when the publication date is in the past and the "publish" behavior is chosen.');
-    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, SafeMarkup::checkPlain($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "publish" behavior is chosen.');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertNoText("The 'publish on' date must be in the future", 'No error message is shown when the publication date is in the past and the "publish" behavior is chosen.');
+    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, Html::escape($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "publish" behavior is chosen.');
 
-    // Reload the changed node and check that it is published.
+    // Reload the node.
     $this->nodeStorage->resetCache([$node->id()]);
-
-    /** @var NodeInterface $node */
     $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.
+    // published on the next cron run. Use a new unpublished node.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $this->assertNoText(t("The 'publish on' date must be in the future"), 'No error message is shown when the publication date is in the past and the "schedule" behavior is chosen.');
-    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, SafeMarkup::checkPlain($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "schedule" behavior is chosen.');
+    $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]);
+    $created_time = $node->getCreatedTime();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertNoText("The 'publish on' date must be in the future", 'No error message is shown when the publication date is in the past and the "schedule" behavior is chosen.');
+    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, Html::escape($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "schedule" behavior is chosen.');
     $this->assertText('will be published', 'The node is scheduled to be published when the publication date is in the past and the "schedule" behavior is chosen.');
 
-    // Reload the node and check that it is unpublished but scheduled correctly.
+    // Reload the node.
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
+
+    // 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->assertEqual($node->publish_on->value, strtotime('-1 day', REQUEST_TIME), 'The node has the correct publish_on date stored.');
+    $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 node is published.
     scheduler_cron();
     $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 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 = [
       'title[0][value]' => 'Unpublish in the past ' . $this->randomString(10),
-      'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME - 3600, 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME - 3600, 'custom', 'H:i:s'),
+      '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->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
-    $this->assertRaw(t("The 'unpublish on' date must be in the future"), 'An error message is shown when the unpublish date is in the past.');
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $this->assertText("The 'unpublish on' date must be in the future", 'An error message is shown when the unpublish date is in the past.');
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
index c0632580ba453e0d5333d41c1922297d4835a327..2dbb46bf394318ff7dedfd822b1fe39271a871dc 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Html;
 
 /**
  * Tests the permissions of the Scheduler module.
@@ -14,7 +14,7 @@ class SchedulerPermissionsTest extends SchedulerBrowserTestBase {
   /**
    * Tests that users without permission do not see the scheduler date fields.
    */
-  public function testUserPermissions() {
+  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([
@@ -51,7 +51,7 @@ public function testUserPermissions() {
       $edit['status[value]'] = TRUE;
     }
     $this->drupalPostForm('node/add/' . $this->type, $edit, $checkbox ? 'Save' : 'Save and publish');
-    $this->assertText(sprintf('%s %s has been created.', $this->typeName, SafeMarkup::checkPlain($title)), 'A node can be created and published when the user does not have scheduler permissions.');
+    $this->assertText(sprintf('%s %s has been created.', $this->typeName, Html::escape($title)), 'A node can be created and published when the user does not have scheduler permissions.');
     $this->assertTrue($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is published');
 
     // Check that a new node can be saved as unpublished.
@@ -61,7 +61,7 @@ public function testUserPermissions() {
       $edit['status[value]'] = FALSE;
     }
     $this->drupalPostForm('node/add/' . $this->type, $edit, $checkbox ? 'Save' : 'Save as unpublished');
-    $this->assertText(sprintf('%s %s has been created.', $this->typeName, SafeMarkup::checkPlain($title)), 'A node can be created and saved as unpublished when the user does not have scheduler permissions.');
+    $this->assertText(sprintf('%s %s has been created.', $this->typeName, Html::escape($title)), 'A node can be created and saved as unpublished when the user does not have scheduler permissions.');
     $this->assertFalse($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is unpublished');
 
     // Set publishing and unpublishing to required, to make it a stronger test.
@@ -76,4 +76,66 @@ public function testUserPermissions() {
     // permission"
   }
 
+  /**
+   * Tests that users without permission can edit existing scheduled content.
+   */
+  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 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->assertEqual($unpublished_node->publish_on->value, $publish_time, 'The publish_on value is stored correctly before edit.');
+
+    // Edit the unpublished node and save.
+    $title = 'For Publishing ' . $this->randomString(10);
+    $this->drupalPostForm('node/' . $unpublished_node->id() . '/edit', ['title[0][value]' => $title], 'Save');
+
+    // Check the updated title, to verify that edit and save was sucessful.
+    $unpublished_node = $this->nodeStorage->load($unpublished_node->id());
+    $this->assertEqual($unpublished_node->title->value, $title, 'The unpublished node title has been updated correctly after edit.');
+
+    // Test that the publish_on date is still stored and is unchanged.
+    $this->assertEqual($unpublished_node->publish_on->value, $publish_time, '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->assertEqual($published_node->unpublish_on->value, $unpublish_time, 'The unpublish_on value is stored correctly before edit.');
+
+    // Edit the published node and save.
+    $title = 'For Unpublishing ' . $this->randomString(10);
+    $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_node = $this->nodeStorage->load($published_node->id());
+    $this->assertEqual($published_node->title->value, $title, 'The published node title has been updated correctly after edit.');
+
+    // Test that the unpublish_on date is still stored and is unchanged.
+    $this->assertEqual($published_node->unpublish_on->value, $unpublish_time, 'The node unpublish_on value is still stored correctly after edit.');
+
+  }
+
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
index 00ec4c478d5df923220c4cd88341386c85f46edb..ed7a5d1d25ba40fa1247f81c0d180634dc3d093c 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
@@ -2,9 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\node\Entity\NodeType;
-use Drupal\Component\Utility\SafeMarkup;
-
 /**
  * Tests the options for scheduling dates to be required during add/edit.
  *
@@ -154,7 +151,8 @@ public function testRequiredScheduling() {
       ],
     ];
 
-    $fields = \Drupal::entityManager()->getFieldDefinitions('node', $this->type);
+    $fields = $this->container->get('entity_field.manager')
+      ->getFieldDefinitions('node', $this->type);
 
     foreach ($test_cases as $test_case) {
       // Set required (un)publishing as stipulated by the test case.
@@ -207,7 +205,7 @@ public function testRequiredScheduling() {
         'unpublish_on[0][value][time]' => '',
       ];
       // Add or edit the node.
-      $this->drupalPostForm($path, $values, t('Save'));
+      $this->drupalPostForm($path, $values, 'Save');
 
       // Check for the expected result.
       switch ($test_case['expected']) {
@@ -217,7 +215,7 @@ public function testRequiredScheduling() {
           break;
 
         case 'not required':
-          $string = sprintf('%s %s has been %s.', $this->typeName, SafeMarkup::checkPlain($title), ($test_case['operation'] == 'add' ? 'created' : 'updated'));
+          $string = sprintf('%s %s has been %s.', $this->typeName, $title, ($test_case['operation'] == 'add' ? 'created' : 'updated'));
           $this->assertText($string, $test_case['id'] . '. ' . $test_case['message']);
           break;
       }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
index 8810e8df96ce30404ee618f74f4d0b1bc1ab77a5..a4d7711fd685aaab64a8747b931a32c58b45f3a2 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
@@ -26,7 +26,7 @@ class SchedulerRevisioningTest extends SchedulerBrowserTestBase {
   protected function schedule(NodeInterface $node, $action = 'publish') {
     // Simulate scheduling by setting the (un)publication date in the past and
     // running cron.
-    $node->{$action . '_on'} = strtotime('-1 day', REQUEST_TIME);
+    $node->{$action . '_on'} = strtotime('-5 hour', $this->requestTime);
     $node->save();
     scheduler_cron();
     $this->nodeStorage->resetCache([$node->id()]);
@@ -34,56 +34,51 @@ protected function schedule(NodeInterface $node, $action = 'publish') {
   }
 
   /**
-   * Check if the latest revision log message of a node matches a given string.
+   * Check if the number of revisions for a node matches a given value.
    *
    * @param int $nid
    *   The node id of the node to check.
    * @param string $value
-   *   The value with which the log message will be compared.
+   *   The value with which the number of revisions will be compared.
    * @param string $message
    *   The message to display along with the assertion.
-   * @param string $group
-   *   The type of assertion - examples are "Browser", "PHP".
    *
    * @return bool
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
-  protected function assertRevisionLogMessage($nid, $value, $message = '', $group = 'Other') {
-    // Retrieve the latest revision log message for this node.
-    $log_message = $this->database->select('node_revision', 'r')
-      ->fields('r', ['revision_log'])
+  protected function assertRevisionCount($nid, $value, $message = '') {
+    $count = \Drupal::database()->select('node_revision', 'r')
       ->condition('nid', $nid)
-      ->orderBy('vid', 'DESC')
-      ->range(0, 1)
+      ->countQuery()
       ->execute()
       ->fetchColumn();
-
-    return $this->assertEqual($log_message, $value, $message, $group);
+    return $this->assertEquals($value, (int) $count, $message);
   }
 
   /**
-   * Check if the number of revisions for a node matches a given value.
+   * Check if the latest revision log message of a node matches a given string.
    *
    * @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.
+   *   The value with which the log message will be compared.
    * @param string $message
    *   The message to display along with the assertion.
-   * @param string $group
-   *   The type of assertion - examples are "Browser", "PHP".
    *
    * @return bool
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
-  protected function assertRevisionCount($nid, $value, $message = '', $group = 'Other') {
-    $count = \Drupal::database()->select('node_revision', 'r')
+  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)
-      ->countQuery()
+      ->orderBy('vid', 'DESC')
+      ->range(0, 1)
       ->execute()
       ->fetchColumn();
 
-    return $this->assertEqual($count, $value, $message, $group);
+    return $this->assertEquals($value, $log_message, $message);
   }
 
   /**
@@ -91,7 +86,7 @@ protected function assertRevisionCount($nid, $value, $message = '', $group = 'Ot
    */
   public function testRevisioning() {
     // Create a scheduled node that is not automatically revisioned.
-    $created = strtotime('-2 day', REQUEST_TIME);
+    $created = strtotime('-2 day', $this->requestTime);
     $settings = [
       'type' => $this->type,
       'revision' => 0,
@@ -118,17 +113,15 @@ public function testRevisioning() {
     // Test scheduled publication with revisioning enabled.
     $node = $this->schedule($node);
     $this->assertRevisionCount($node->id(), 2, 'A new revision was created when revisioning is enabled.');
-    $expected_message = sprintf('Node published by Scheduler on %s. Previous creation date was %s.',
-      \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'),
-      \Drupal::service('date.formatter')->format($created, 'short'));
+    $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.
     $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('Node unpublished by Scheduler on %s. Previous change date was %s.',
-      \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'),
-      \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'));
+    $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.');
   }
 
@@ -140,7 +133,7 @@ public function testAlterCreationDate() {
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
 
     // Create a node with a 'created' date two days in the past.
-    $created = strtotime('-2 day', REQUEST_TIME);
+    $created = strtotime('-2 day', $this->requestTime);
     $settings = [
       'type' => $this->type,
       'created' => $created,
@@ -155,7 +148,7 @@ public function testAlterCreationDate() {
     // 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->assertEqual($created, $created_after_cron, 'The node creation date is not changed by default.');
+    $this->assertEquals($created_after_cron, $created, 'The node creation date is not changed by default.');
 
     // Set option to change the created date to match the publish_on date.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_touch', TRUE)->save();
@@ -164,7 +157,8 @@ public function testAlterCreationDate() {
     $node = $this->schedule($node, 'publish');
     // Check that the created date has changed to match the publish_on date.
     $created_after_cron = $node->created->value;
-    $this->assertEqual(strtotime('-1 day', REQUEST_TIME), $created_after_cron, "With 'touch' option set, the node creation date is changed to match the publishing date.");
+    $this->assertEqual(strtotime('-5 hour', $this->requestTime), $created_after_cron, "With 'touch' option set, the node creation date is changed to match the publishing date.");
+
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
index 9bd335f9f3a3cdff2d4cf346ad3bd518a13bfc7b..63ed424e6743e73a13802347bbbc4a944f2b0e3f 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
@@ -8,6 +8,9 @@
  * Tests the six actions that Scheduler provides for use in Rules module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code.
+ * @see https://www.drupal.org/project/scheduler/issues/2924353
  */
 class SchedulerRulesActionsTest extends SchedulerBrowserTestBase {
 
@@ -57,7 +60,7 @@ public function testPublishOnActions() {
     $rule1->addAction('scheduler_set_publishing_date_action',
       ContextConfig::create()
         ->map('node', 'node')
-        ->setValue('date', REQUEST_TIME + 1800)
+        ->setValue('date', $this->requestTime + 1800)
       )
       ->addAction('rules_system_message',
         ContextConfig::create()
@@ -113,7 +116,7 @@ public function testPublishOnActions() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $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
@@ -129,7 +132,7 @@ public function testPublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 1',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 1 is triggered and rule 2 is not. Check that a publishing
@@ -145,7 +148,7 @@ public function testPublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 2',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 2 is triggered and rule 1 is not. Check that the
@@ -179,7 +182,7 @@ public function testUnpublishOnActions() {
     $rule3->addAction('scheduler_set_unpublishing_date_action',
       ContextConfig::create()
         ->map('node', 'node')
-        ->setValue('date', REQUEST_TIME + 1800)
+        ->setValue('date', $this->requestTime + 1800)
       )
       ->addAction('rules_system_message',
         ContextConfig::create()
@@ -235,7 +238,7 @@ public function testUnpublishOnActions() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $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
@@ -251,7 +254,7 @@ public function testUnpublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 3',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 3 is triggered and rule 4 is not. Check that an
@@ -267,7 +270,7 @@ public function testUnpublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 4',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 4 is triggered and rule 3 is not. Check that the
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
index 62a07efc3645c0793d7614530e05f21cbc18b3d2..7a246a99ce05fa8e833248e7086346758653c387 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
@@ -8,6 +8,9 @@
  * Tests the four conditions that Scheduler provides for use in Rules module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code.
+ * @see https://www.drupal.org/project/scheduler/issues/2924353
  */
 class SchedulerRulesConditionsTest extends SchedulerBrowserTestBase {
 
@@ -225,7 +228,7 @@ public function testNodeIsScheduledConditions() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
 
     $this->assertText($message5, '"' . $message5 . '" is shown');
     $this->assertText($message6, '"' . $message6 . '" is shown');
@@ -234,10 +237,10 @@ public function testNodeIsScheduledConditions() {
 
     // Edit the node and set a publish_on date.
     $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', REQUEST_TIME)),
+      '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, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
 
     $this->assertNoText($message5, '"' . $message5 . '" is not shown');
     $this->assertText($message6, '"' . $message6 . '" is shown');
@@ -246,10 +249,10 @@ public function testNodeIsScheduledConditions() {
 
     // Edit the node and set an unpublish_on date.
     $edit = [
-      'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', REQUEST_TIME)),
-      'unpublish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', REQUEST_TIME)),
+      '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, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
 
     $this->assertNoText($message5, '"' . $message5 . '" is not shown');
     $this->assertNoText($message6, '"' . $message6 . '" is not shown');
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
index f7276ad61dabf0bb6c58e63470d38fd711d3a43c..f6002464427809659a8854191b627354e67a63ec 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
@@ -8,6 +8,9 @@
  * Tests the six events that Scheduler provides for use in Rules module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code.
+ * @see https://www.drupal.org/project/scheduler/issues/2924353
  */
 class SchedulerRulesEventsTest extends SchedulerBrowserTestBase {
 
@@ -49,6 +52,11 @@ public function testRulesEvents() {
       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();
       $message[$i] = 'RULES message ' . $i . '. ' . $description;
@@ -63,6 +71,7 @@ public function testRulesEvents() {
       ]);
       $config_entity->save();
     }
+    // @codingStandardsIgnoreEnd
 
     $this->drupalLogin($this->schedulerUser);
 
@@ -72,7 +81,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Test for no events',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -85,7 +94,7 @@ public function testRulesEvents() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
     $this->assertNoText($message[3], '"' . $message[3] . '" is not shown');
@@ -101,7 +110,7 @@ public function testRulesEvents() {
       'publish_on[0][value][time]' => date('H:i:s', time() + 3),
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertText($message[1], '"' . $message[1] . '" IS shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -115,7 +124,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Edit node with publish-on date',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertText($message[2], '"' . $message[2] . '" IS shown');
     $this->assertNoText($message[3], '"' . $message[3] . '" is not shown');
@@ -143,7 +152,7 @@ public function testRulesEvents() {
       'unpublish_on[0][value][time]' => date('H:i:s', time() + 3),
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -157,7 +166,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Edit node with unpublish-on date',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
     $this->assertNoText($message[3], '"' . $message[3] . '" is not shown');
@@ -187,7 +196,7 @@ public function testRulesEvents() {
       'unpublish_on[0][value][time]' => date('H:i:s', time() + 4),
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertText($message[1], '"' . $message[1] . '" IS shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -201,7 +210,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Edit node with both dates',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertText($message[2], '"' . $message[2] . '" IS shown');
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php
index b58c1f169bfdf84f2a27cb14c627f6eaaf345cde..ef4196825d510f0e45500dab563b8145c4a0fb64 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
+use Drupal\Core\Url;
+
 /**
  * Tests the Scheduler section of the status report.
  *
@@ -19,13 +21,13 @@ public function testStatusReport() {
     $this->assertText('Scheduler Time Check');
     $this->assertText('In most cases the server time should match Coordinated Universal Time (UTC) / Greenwich Mean Time (GMT)');
 
-    $admin_regional_settings = \Drupal::url('system.regional_settings');
+    $admin_regional_settings = Url::fromRoute('system.regional_settings');
     $this->assertLink('changed by admin users');
-    $this->assertLinkByHref($admin_regional_settings);
+    $this->assertLinkByHref($admin_regional_settings->toString());
 
-    $account_edit = \Drupal::url('entity.user.edit_form', ['user' => $this->adminUser->id()]);
+    $account_edit = Url::fromRoute('entity.user.edit_form', ['user' => $this->adminUser->id()]);
     $this->assertLink('user account');
-    $this->assertLinkByHref($account_edit);
+    $this->assertLinkByHref($account_edit->toString());
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
index ceb44874667ee2ed58fc5b21a0043d7479dffeab..784f960034b221687413a9664c6057f7d6590f84 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
@@ -14,11 +14,9 @@ class SchedulerTokenReplaceTest extends SchedulerBrowserTestBase {
    */
   public function testSchedulerTokenReplacement() {
     $this->drupalLogin($this->schedulerUser);
-    $date_formatter = \Drupal::service('date.formatter');
-
     // Define timestamps for consistent use when repeated throughout this test.
-    $publish_on_timestamp = REQUEST_TIME + 3600;
-    $unpublish_on_timestamp = REQUEST_TIME + 7200;
+    $publish_on_timestamp = $this->requestTime + 3600;
+    $unpublish_on_timestamp = $this->requestTime + 7200;
 
     // Create an unpublished page with scheduled dates.
     $node = $this->drupalCreateNode([
@@ -48,7 +46,7 @@ public function testSchedulerTokenReplacement() {
       $edit = [
         'body[0][value]' => 'Publish on: [node:scheduler-publish' . $test_data['token_format'] . ']. Unpublish on: [node:scheduler-unpublish' . $test_data['token_format'] . '].',
       ];
-      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
       $this->drupalGet('node/' . $node->id());
 
       // Refresh the node and get the body output value.
@@ -57,11 +55,11 @@ public function testSchedulerTokenReplacement() {
       $body_output = \Drupal::token()->replace($node->body->value, ['node' => $node]);
 
       // Create the expected text for the body.
-      $publish_on_date = $date_formatter->format($publish_on_timestamp, $test_data['date_format'], $test_data['custom']);
-      $unpublish_on_date = $date_formatter->format($unpublish_on_timestamp, $test_data['date_format'], $test_data['custom']);
+      $publish_on_date = $this->dateFormatter->format($publish_on_timestamp, $test_data['date_format'], $test_data['custom']);
+      $unpublish_on_date = $this->dateFormatter->format($unpublish_on_timestamp, $test_data['date_format'], $test_data['custom']);
       $expected_output = 'Publish on: ' . $publish_on_date . '. Unpublish on: ' . $unpublish_on_date . '.';
       // Check that the actual text matches the expected value.
-      $this->assertEqual($body_output, $expected_output, 'Scheduler tokens replaced correctly for ' . $test_data['token_format'] . ' format.');
+      $this->assertEquals($expected_output, $body_output, 'Scheduler tokens replaced correctly for ' . $test_data['token_format'] . ' format.');
     }
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
index 346ac015360059c30f23039042759288689e3e6c..fa5dc21b45f5b3de011a42bbfc1bfbeddab9b636 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Component\Utility\SafeMarkup;
-
 /**
  * Tests the validation when editing a node.
  *
@@ -30,8 +28,8 @@ public function testValidationDuringEdit() {
       'status' => FALSE,
     ]);
     $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', REQUEST_TIME)),
+      '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('node/' . $node->id() . '/edit');
 
@@ -43,8 +41,8 @@ public function testValidationDuringEdit() {
     $checkbox = $this->xpath('//input[@type="checkbox" and @id="edit-status-value"]');
 
     $this->submitForm($edit, $checkbox ? 'Save' : 'Save and keep unpublished');
-    $this->assertRaw(t("If you set a 'publish on' date then you must also set an 'unpublish on' date."), 'Validation prevents entering a publish-on date with no unpublish-on date if unpublishing is required.');
-    $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.');
+    $this->assertText("If you set a 'publish on' date then you must also set an 'unpublish on' date.", 'Validation prevents entering a publish-on date with no unpublish-on date if unpublishing is required.');
+    $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.');
 
     // 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.
@@ -59,8 +57,8 @@ public function testValidationDuringEdit() {
       $edit = [];
     }
     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, $checkbox ? 'Save' : 'Save and publish');
-    $this->assertRaw(t("Either you must set an 'unpublish on' date or save this node as unpublished."), 'Validation prevents publishing the node directly without an unpublish-on date if unpublishing is required.');
-    $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.');
+    $this->assertText("Either you must set an 'unpublish on' date or save this node as unpublished.", 'Validation prevents publishing the node directly without an unpublish-on date if unpublishing is required.');
+    $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.');
 
     // 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.
@@ -69,14 +67,14 @@ public function testValidationDuringEdit() {
       'status' => FALSE,
     ]);
     $edit = [
-      'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 8100, 'custom', 'Y-m-d'),
-      'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 8100, 'custom', 'H:i:s'),
-      'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 1800, 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 1800, '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->drupalPostForm('node/' . $node->id() . '/edit', $edit, $checkbox ? 'Save' : 'Save and keep unpublished');
-    $this->assertRaw(t("The 'unpublish on' date must be later than the 'publish on' date."), 'Validation prevents entering an unpublish-on date which is earlier than the publish-on date.');
-    $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.');
+    $this->assertText("The 'unpublish on' date must be later than the 'publish on' date.", 'Validation prevents entering an unpublish-on date which is earlier than the publish-on date.');
+    $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.');
   }
 
 }
diff --git a/web/modules/social_media/composer.json b/web/modules/social_media/composer.json
index 8761c1d9af8079d8897ff9fc0091b1a81eea54b5..87f90ff60b1282b592d0d505182e07a40a7583dc 100644
--- a/web/modules/social_media/composer.json
+++ b/web/modules/social_media/composer.json
@@ -8,6 +8,10 @@
       "name": "Takim Islam",
       "homepage": "http://drupalsharing.com/",
       "role": "Maintainer"
+    },
+    {
+      "name": "Jack Over",
+      "role": "Maintainer"
     }
   ],
   "support": {
diff --git a/web/modules/social_media/config/install/social_media.settings.yml b/web/modules/social_media/config/install/social_media.settings.yml
index 1e0b3c997fe87d90a88b8edbfdeccf4824bb2854..f8e985e87916b0b0f54663f26b596d171de34ea5 100644
--- a/web/modules/social_media/config/install/social_media.settings.yml
+++ b/web/modules/social_media/config/install/social_media.settings.yml
@@ -6,7 +6,7 @@ social_media:
     api_event : "href"
     default_img: 1
     weight: 1
-    attributes : "target|_blank\nclass|facebook-share"
+    attributes : "target|_blank\nclass|facebook-share\nrel|nooopener noreffer"
   facebook_msg:
     enable: 1
     api_url: "FB.ui({method: 'send',link: '[current-page:url]'})"
@@ -16,7 +16,7 @@ social_media:
     text: "Facebook messenger"
     default_img: 1
     weight: 2
-    attributes : "target|_blank\nclass|facebook-msg"
+    attributes : "target|_blank\nclass|facebook-msg\nrel|nooopener noreffer"
   linkedin:
     enable: 1
     api_url: "http://www.linkedin.com/shareArticle?mini=true&url=[current-page:url]&title=[current-page:title]&source=[current-page:url]"
@@ -24,15 +24,15 @@ social_media:
     text: "Linkedin"
     default_img: 1
     weight: 3
-    attributes : "target|_blank\nclass|linkedin"
+    attributes : "target|_blank\nclass|linkedin\nrel|nooopener noreffer"
   twitter:
     enable: 1
-    api_url: "https://twitter.com/intent/tweet?url=[current-page:url]&status=[current-page:title]+[current-page:url]" 
+    api_url: "https://twitter.com/intent/tweet?url=[current-page:url]&url=[current-page:url]&hashtags=Hashtag"
     api_event : "href"
     text: "Twitter"
     default_img: 1
-    weight: 4 
-    attributes : "target|_blank\nclass|twitter"
+    weight: 4
+    attributes : "target|_blank\nclass|twitter\nrel|nooopener noreffer"
   pinterest:
     enable: 1
     api_url: "https://www.pinterest.com/pin/create/button/?url=[current-page:url]&description=[current-page:title]"
@@ -40,15 +40,14 @@ social_media:
     text: "Pinterest"
     default_img: 1
     weight: 5
-    attributes : "target|_blank\nclass|pinterest"
-  google_plus:
-    enable: 1
-    text: "Google Plus"
-    api_url: "https://plus.google.com/share?url=[current-page:url]"
-    api_event : "href"
+    attributes : "target|_blank\nclass|pinterest\nrel|nooopener noreffer"
+  whatsapp:
+    enable: 0
+    api_url: "https://wa.me/?text=[current-page:url]"
+    api_event: "href"
     default_img: 1
+    attributes: "class|whatsapp show-for-small-only\ndata-action|share/whatsapp/share"
     weight: 6
-    attributes : "target|_blank\nclass|google-plus"
   email:
     enable: 1
     api_url: "mailto:?subject=[current-page:title]&body=Check out this site [current-page:url]"
@@ -56,5 +55,13 @@ social_media:
     text: "Email"
     default_img: 1
     weight: 7
-    attributes : "class|email"    
+    attributes : "class|email"
+  print:
+    enable: 0
+    api_url: "window.print()"
+    api_event : "onclick"
+    text: "Print"
+    default_img: 1
+    attributes : "class|print"
+    weight: 8
 langcode: en
diff --git a/web/modules/social_media/css/social_media.css b/web/modules/social_media/css/social_media.css
index dda3cbc41e7b7e325880b59ad1af74c88d2be115..64cce49aa95515638f057c13908dcb5ddc3c352b 100644
--- a/web/modules/social_media/css/social_media.css
+++ b/web/modules/social_media/css/social_media.css
@@ -1,20 +1,20 @@
-.social-media-sharing{
-  display:block;
+.social-media-sharing {
+  display: block;
 }
 .social-media-sharing ul {
-  padding-left:0px;
+  padding-left: 0px;
   display: inline-flex;
   text-align: center;
 }
 .social-media-sharing li {
-    display: inline-block;
-    margin-right: 15px;
-    list-style: none;
-    list-style-type: none;
+  display: inline-block;
+  margin-right: 15px;
+  list-style: none;
+  list-style-type: none;
 }
 .social-media-sharing .share {
   cursor: pointer;
 }
 .social-media-sharing .share img {
-  width:40px;
-}
\ No newline at end of file
+  width: 40px;
+}
diff --git a/web/modules/social_media/icons/google_plus.svg b/web/modules/social_media/icons/google_plus.svg
deleted file mode 100644
index 4e27bc2e7c391708a3c7b5e14a3a2d4eb5aaec61..0000000000000000000000000000000000000000
--- a/web/modules/social_media/icons/google_plus.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version="1.0" ?><!DOCTYPE svg  PUBLIC '-//W3C//DTD SVG 1.1//EN'  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 512 512" height="512px" id="Layer_1" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="social_x5F_0"><g><circle cx="255.672" cy="255.93" fill="#E7725D" r="256"/><path d="M255.672,511.93c141.386,0,256-114.611,256-256c0-68.156-26.688-130.043-70.116-175.917L77.414,439.582    C123.511,484.332,186.352,511.93,255.672,511.93z" fill="#D36955"/><path d="M343.103,121.873H225.67c-30.743,0-69.476,4.518-102.057,30.961    c-24.385,20.822-36.477,49.602-36.477,75.501c0,43.919,34.222,88.433,94.678,88.433c5.742,0,11.89-0.596,18.246-1.211    c-2.872,6.688-5.743,12.338-5.743,21.86c0,17.59,9.223,28.313,17.212,38.436c-25.816,1.62-73.564,4.469-109.023,25.916    c-15.194,8.883-25.584,19.624-32.441,30.602C82,444.921,95.179,456.263,109.43,466.193c-0.326-2.479-0.542-4.99-0.542-7.571    c0-40.074,36.475-53.629,48.972-58.078c23.976-7.879,54.717-8.91,59.838-8.91c5.731,0,8.604,0,13.114,0.418    c43.443,30.557,62.094,45.729,62.094,74.465c0,18.402-8.088,34.3-23.786,45.209c11.943-0.618,23.652-2.104,35.11-4.307    c17.857-18.404,26.994-40.718,26.994-62.959c0-38.861-22.742-58.086-47.962-78.729l-20.482-15.775    c-6.369-5.059-14.764-11.955-14.764-24.3c0-12.327,8.396-20.224,15.982-27.521c23.771-18.604,47.753-38.228,47.753-79.918    c0-42.854-27.266-65.381-40.371-76.082h35.246L343.103,121.873z M247.394,285.207c-9.213,8.902-24.586,15.782-38.724,15.782    c-49.188,0-71.321-62.521-71.321-100.368c0-14.565,2.872-29.744,12.504-41.564c9.215-11.271,25.196-18.6,39.955-18.6    c47.345,0,71.729,63,71.729,103.67C261.536,254.25,260.517,272.26,247.394,285.207z" fill="#FFFFFF"/><path d="M247.394,285.207c-7.006,6.768-17.571,12.348-28.45,14.641l-21.072,20.812    c-1.979,4.793-3.554,9.718-3.554,16.76c0,17.59,9.223,28.313,17.212,38.438c-20.092,1.261-53.453,3.308-83.979,14.242    l-50.238,49.606c9.942,9.652,20.707,18.451,32.104,26.404c-0.318-2.451-0.528-4.938-0.528-7.486    c0-40.074,36.475-53.629,48.972-58.078c23.976-7.879,54.718-8.91,59.839-8.91c5.73,0,8.604,0,13.113,0.418    c43.443,30.558,62.094,45.73,62.094,74.466c0,18.401-8.088,34.3-23.785,45.21c11.942-0.619,23.651-2.105,35.109-4.309    c17.857-18.404,26.994-40.717,26.994-62.959c0-38.86-22.742-58.086-47.962-78.729l-20.481-15.777    c-6.37-5.057-14.765-11.955-14.765-24.299c0-12.328,8.396-20.225,15.982-27.521c23.771-18.604,47.753-38.227,47.753-79.918    c0-3.297-0.21-6.436-0.518-9.497l-50.855,50.219C258.852,267.632,255.359,277.348,247.394,285.207z" fill="#F1F2F2"/><path d="M505.073,198.282c-0.375-1.631-0.72-3.271-1.128-4.888c-0.64-2.555-1.359-5.072-2.077-7.595    c-0.397-1.399-0.771-2.813-1.192-4.202c-0.308-1.008-0.653-1.996-0.979-2.996h-44.623v-56.729h-25.826v56.729h-55.078v24.461    h55.078v57.25h25.826v-57.25h51.064C505.787,201.469,505.439,199.872,505.073,198.282z" fill="#FFFFFF"/></g></g><g id="Layer_1_1_"/></svg>
\ No newline at end of file
diff --git a/web/modules/social_media/icons/print.svg b/web/modules/social_media/icons/print.svg
new file mode 100644
index 0000000000000000000000000000000000000000..68a7f114c81d4779c535d03b69f3083f68677807
--- /dev/null
+++ b/web/modules/social_media/icons/print.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#470A68;}
+	.st1{fill:#FFFFFF;}
+</style>
+<title>Shape</title>
+<desc>Created with Sketch.</desc>
+<circle class="st0" cx="20" cy="20" r="20"/>
+<g>
+	<path class="st1" d="M26.4,17.3c0.6,0,1,0.2,1.4,0.6c0.4,0.4,0.6,0.9,0.6,1.4v3.5c0,0.1,0,0.3-0.1,0.4s-0.2,0.1-0.4,0.1h-1.5v3
+		c0,0.3-0.1,0.5-0.3,0.7c-0.2,0.2-0.4,0.3-0.7,0.3h-10c-0.3,0-0.5-0.1-0.7-0.3c-0.2-0.2-0.3-0.4-0.3-0.7v-3h-1.5
+		c-0.1,0-0.3,0-0.4-0.1s-0.1-0.2-0.1-0.4v-3.5c0-0.6,0.2-1,0.6-1.4s0.9-0.6,1.4-0.6v-5c0-0.3,0.1-0.5,0.3-0.7s0.4-0.3,0.7-0.3H24
+		c0.3,0,0.5,0.1,0.7,0.3l1.4,1.4c0.2,0.2,0.3,0.4,0.3,0.7V17.3z M24.4,18.3v-3h-1.5c-0.1,0-0.3,0-0.4-0.1s-0.1-0.2-0.1-0.4v-1.5h-6
+		v5H24.4z M24.4,25.3v-3h-8v3H24.4z M25.9,20.6c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.5s-0.1-0.4-0.2-0.5
+		c-0.1-0.1-0.3-0.2-0.5-0.2c-0.2,0-0.4,0.1-0.5,0.2c-0.1,0.1-0.2,0.3-0.2,0.5s0.1,0.4,0.2,0.5C25.5,20.5,25.7,20.6,25.9,20.6z"/>
+</g>
+</svg>
diff --git a/web/modules/social_media/icons/whatsapp.svg b/web/modules/social_media/icons/whatsapp.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8f4e24ddcd20f86d2b8bdd3f64df89041527260e
--- /dev/null
+++ b/web/modules/social_media/icons/whatsapp.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" id="whatsapp-logo-notext" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 39 39" style="enable-background:new 0 0 39 39;" xml:space="preserve"><style type="text/css">
+                            .logo_white{fill:#FFFFFF;}
+                            .logo_green{fill:#00E676;}
+                        </style><path class="logo_green" d="M10.7,32.8l0.6,0.3c2.5,1.5,5.3,2.2,8.1,2.2l0,0c8.8,0,16-7.2,16-16c0-4.2-1.7-8.3-4.7-11.3&#10;                            s-7-4.7-11.3-4.7c-8.8,0-16,7.2-15.9,16.1c0,3,0.9,5.9,2.4,8.4l0.4,0.6l-1.6,5.9L10.7,32.8z"/><path class="logo_white" d="M32.4,6.4C29,2.9,24.3,1,19.5,1C9.3,1,1.1,9.3,1.2,19.4c0,3.2,0.9,6.3,2.4,9.1L1,38l9.7-2.5&#10;                            c2.7,1.5,5.7,2.2,8.7,2.2l0,0c10.1,0,18.3-8.3,18.3-18.4C37.7,14.4,35.8,9.8,32.4,6.4z M19.5,34.6L19.5,34.6c-2.7,0-5.4-0.7-7.7-2.1&#10;                            l-0.6-0.3l-5.8,1.5L6.9,28l-0.4-0.6c-4.4-7.1-2.3-16.5,4.9-20.9s16.5-2.3,20.9,4.9s2.3,16.5-4.9,20.9C25.1,33.8,22.3,34.6,19.5,34.6&#10;                            z M28.3,23.5L27.2,23c0,0-1.6-0.7-2.6-1.2c-0.1,0-0.2-0.1-0.3-0.1c-0.3,0-0.5,0.1-0.7,0.2l0,0c0,0-0.1,0.1-1.5,1.7&#10;                            c-0.1,0.2-0.3,0.3-0.5,0.3h-0.1c-0.1,0-0.3-0.1-0.4-0.2l-0.5-0.2l0,0c-1.1-0.5-2.1-1.1-2.9-1.9c-0.2-0.2-0.5-0.4-0.7-0.6&#10;                            c-0.7-0.7-1.4-1.5-1.9-2.4L15,18.4c-0.1-0.1-0.1-0.2-0.2-0.4c0-0.2,0-0.4,0.1-0.5c0,0,0.4-0.5,0.7-0.8c0.2-0.2,0.3-0.5,0.5-0.7&#10;                            c0.2-0.3,0.3-0.7,0.2-1c-0.1-0.5-1.3-3.2-1.6-3.8c-0.2-0.3-0.4-0.4-0.7-0.5h-0.3c-0.2,0-0.5,0-0.8,0c-0.2,0-0.4,0.1-0.6,0.1&#10;                            l-0.1,0.1c-0.2,0.1-0.4,0.3-0.6,0.4c-0.2,0.2-0.3,0.4-0.5,0.6c-0.7,0.9-1.1,2-1.1,3.1l0,0c0,0.8,0.2,1.6,0.5,2.3l0.1,0.3&#10;                            c0.9,1.9,2.1,3.6,3.7,5.1l0.4,0.4c0.3,0.3,0.6,0.5,0.8,0.8c2.1,1.8,4.5,3.1,7.2,3.8c0.3,0.1,0.7,0.1,1,0.2l0,0c0.3,0,0.7,0,1,0&#10;                            c0.5,0,1.1-0.2,1.5-0.4c0.3-0.2,0.5-0.2,0.7-0.4l0.2-0.2c0.2-0.2,0.4-0.3,0.6-0.5s0.4-0.4,0.5-0.6c0.2-0.4,0.3-0.9,0.4-1.4&#10;                            c0-0.2,0-0.5,0-0.7C28.6,23.7,28.5,23.6,28.3,23.5z"/></svg>
\ No newline at end of file
diff --git a/web/modules/social_media/js/facebook.js b/web/modules/social_media/js/facebook.js
index c271b894be5e4fc1253a957bc1a0721afa89b643..331dea199165cae2f0e27bcb73f86173c835e399 100644
--- a/web/modules/social_media/js/facebook.js
+++ b/web/modules/social_media/js/facebook.js
@@ -1,3 +1,7 @@
+/**
+ * @file
+ */
+
 (function ($) {
 
   'use strict';
@@ -23,4 +27,3 @@
     }
   };
 })(jQuery);
-
diff --git a/web/modules/social_media/social_media.api.php b/web/modules/social_media/social_media.api.php
index 84069513cb3dff13c359d5df47c13f2a5b235550..4bf0a5e0b3dbfa5bddbb804b47a262ebb9941e82 100644
--- a/web/modules/social_media/social_media.api.php
+++ b/web/modules/social_media/social_media.api.php
@@ -7,7 +7,7 @@
 /**
  * Listens to the dynamic route events.
  */
-class socialMediaSubscriber implements EventSubscriberInterface {
+class SocialMediaSubscriber implements EventSubscriberInterface {
 
   /**
    * Public static method used by Drupals event.
@@ -16,18 +16,16 @@ class socialMediaSubscriber implements EventSubscriberInterface {
    *   Array of events we subscribe to.
    */
   public static function getSubscribedEvents() {
-    $events = array();
-    $events['social_media.add_more_social_media'][] = array('addMoreSocialMedia', 39);
-    $events['social_media.pre_execute'][] = array('pre_executeSocialMedia', 39);
-    $events['social_media.pre_render'][] = array('pre_rednerSocialMedia', 39);
+    $events = [];
+    $events['social_media.add_more_social_media'][] = ['addMoreSocialMedia', 39];
+    $events['social_media.pre_execute'][] = ['executeSocialMedia', 39];
+    $events['social_media.pre_render'][] = ['renderSocialMedia', 39];
 
     return $events;
   }
 
-
   /**
    * Subscribes to  social_media.add_more_social_media event.
-   *
    */
   public function addMoreSocialMedia($event) {
     $element = $event->getElement();
@@ -37,20 +35,17 @@ public function addMoreSocialMedia($event) {
 
   /**
    * Subscribes to social_media.pre_execute event.
-   *
    */
-  public function pre_executeSocialMedia($event) {
+  public function executeSocialMedia($event) {
     $element = $event->getElement();
     $element['facebook_share']['weight'] = 8;
     $event->setElement($element);
   }
 
-
   /**
    * Subscribes to social_media.pre_render event.
-   *
    */
-  public function pre_rednerSocialMedia($event) {
+  public function renderSocialMedia($event) {
     $element = $event->getElement();
     $element['facebook_msg']['text'] = 'New facebook msg text';
     $event->setElement($element);
diff --git a/web/modules/social_media/social_media.info.yml b/web/modules/social_media/social_media.info.yml
index 509ed972a43653eb323aaad73cf7910a5ee01547..dec179f82d40c2a6e11f490440fa283a269fce8e 100644
--- a/web/modules/social_media/social_media.info.yml
+++ b/web/modules/social_media/social_media.info.yml
@@ -1,16 +1,14 @@
 name: Social media share
 type: module
 description: Share current page to social media
-# core: 8.x
+core: 8.x
 package: Others
 configure: social_media.admin_form
 dependencies:
-- drupal:token
+- token:token
 - drupal:field
 
-
-# Information added by Drupal.org packaging script on 2018-03-22
-version: '8.x-1.3'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2020-02-13
+version: '8.x-1.8'
 project: 'social_media'
-datestamp: 1521748695
+datestamp: 1581591839
diff --git a/web/modules/social_media/social_media.install b/web/modules/social_media/social_media.install
new file mode 100644
index 0000000000000000000000000000000000000000..ffe09b43633fb349d8d310ea51613f82ff96e5dd
--- /dev/null
+++ b/web/modules/social_media/social_media.install
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Remove google plus configuration.
+ */
+function social_media_update_8001() {
+  \Drupal::configFactory()->getEditable('social_media.settings')->clear('google_plus')->save();
+}
+
+/**
+ * Implements hook_install().
+ */
+function social_media_install() {
+  social_media_update_8001();
+}
diff --git a/web/modules/social_media/social_media.module b/web/modules/social_media/social_media.module
index 8f842b33b902af901b6faa8465874ed56a4f8558..37f994ee373f7b4b1b4123885e1808f2e463476a 100644
--- a/web/modules/social_media/social_media.module
+++ b/web/modules/social_media/social_media.module
@@ -5,19 +5,20 @@
  * Contains social_media.module..
  */
 
+use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_theme().
  */
 function social_media_theme($existing, $type, $theme, $path) {
-  return array(
-    'social_media_links' => array(
-      'variables' => array(
+  return [
+    'social_media_links' => [
+      'variables' => [
         'elements' => NULL,
-      ),
+      ],
       'template' => 'social-media-links',
-    )
-  );
+    ],
+  ];
 }
 
 /**
@@ -33,4 +34,21 @@ function social_media_mail($key, &$message, $params) {
       $message['body'][] = $params['message'];
       break;
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Implements hook_help().
+ */
+function social_media_help($route_name, RouteMatchInterface $route_match) {
+
+  if ($route_name != 'help.page.social_media') {
+    return '';
+  }
+  $output = '';
+  $output .= '<h3>' . t('About') . '</h3>';
+  $output .= '<p>' . t('The social media share module allows the user to share current page to different social media platforms. It is rendered as block, you can place it anywhere of your site.') . '</p>';
+  $output .= '<p>' . t('Module provides social media field type so that you can add it as field in entity and take all benefits from field API.') . '</p>';
+  $output .= '<p>' . t('It is flexible to share any page of the site whether it is node, term , panels, view pages so on.') . '</p>';
+  $output .= '<p>' . t('Analyse the <a href="https://git.drupalcode.org/project/social_media/blob/8.x-1.x/README.txt">Read more</a> to get more information about this module.') . '</p>';
+  return $output;
+}
diff --git a/web/modules/social_media/social_media.routing.yml b/web/modules/social_media/social_media.routing.yml
index bb6961f80d259f66c009dcf187a698650d829ae2..e8aced26b1cfaa3c69af0f0a3185e195ddae938e 100644
--- a/web/modules/social_media/social_media.routing.yml
+++ b/web/modules/social_media/social_media.routing.yml
@@ -14,4 +14,4 @@ social_media.forward:
     _form: '\Drupal\social_media\Form\ForwardEmailForm'
     _title: 'Forward email'
   requirements:
-   _access: 'TRUE'
\ No newline at end of file
+    _custom_access: '\Drupal\social_media\Controller\AccessController::access'
diff --git a/web/modules/social_media/src/Controller/AccessController.php b/web/modules/social_media/src/Controller/AccessController.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d76f2601d13f2418cb15a03642fd4f554ee8b5b
--- /dev/null
+++ b/web/modules/social_media/src/Controller/AccessController.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\social_media\Controller;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Class AccessController.
+ *
+ * @package Drupal\social_media\Controller
+ */
+class AccessController extends ControllerBase {
+
+  /**
+   * Check if email option is enabled, if not do not allow this path.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   Return allowed if the social media email is enabled.
+   */
+  public function access() {
+    $config = $this->config('social_media.settings');
+    if ($config->get('social_media.email.enable') && $config->get('social_media.email.enable_forward')) {
+      return AccessResult::allowed()->addCacheableDependency($config);
+    }
+    return AccessResult::forbidden()->addCacheableDependency($config);
+  }
+
+}
diff --git a/web/modules/social_media/src/Event/SocialMediaEvent.php b/web/modules/social_media/src/Event/SocialMediaEvent.php
index 4230f9e13aea68f70c11e9bb8c417408cb8c9f81..1e9504fc426f75810d54c06af874dd7309fb287a 100644
--- a/web/modules/social_media/src/Event/SocialMediaEvent.php
+++ b/web/modules/social_media/src/Event/SocialMediaEvent.php
@@ -5,33 +5,44 @@
 use Symfony\Component\EventDispatcher\Event;
 
 /**
- * Class SocialMediaEvent
+ * Class SocialMediaEvent.
  */
 class SocialMediaEvent extends Event {
 
   /**
-   * @var array.
+   * TODO describe element.
+   *
+   * @var array
    */
   protected $element;
 
   /**
    * Constructor.
+   *
+   * @param array $element
+   *   TODO describe what element is.
    */
-  public function __construct($element) {
+  public function __construct(array $element) {
     $this->element = $element;
   }
 
   /**
    * Return the element.
-   * @return array()
+   *
+   * @return array
+   *   The element.
    */
   public function getElement() {
     return $this->element;
   }
 
   /**
+   * Element setter.
+   *
+   * @param array $element
+   *   TODO describe what element is.
    */
-  public function setElement($element) {
+  public function setElement(array $element) {
     $this->element = $element;
   }
 
diff --git a/web/modules/social_media/src/Form/ForwardEmailForm.php b/web/modules/social_media/src/Form/ForwardEmailForm.php
index 99bae9c732bffe914c68afee71a1a07c22801b18..a59288550a55c78f7ea9ef7a9d57285509da0909 100644
--- a/web/modules/social_media/src/Form/ForwardEmailForm.php
+++ b/web/modules/social_media/src/Form/ForwardEmailForm.php
@@ -13,10 +13,14 @@
 
 /**
  * Class ForwardEmailForm.
- *
  */
 class ForwardEmailForm extends FormBase {
 
+  /**
+   * The config Factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
   protected $configFactory;
 
   /**
@@ -27,7 +31,7 @@ class ForwardEmailForm extends FormBase {
   protected $requestStack;
 
   /**
-   * The mail manager
+   * The mail manager.
    *
    * @var \Drupal\Core\Mail\MailManagerInterface
    */
@@ -107,31 +111,24 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#title' => $this->t('Subject'),
       '#required' => TRUE,
       '#default_value' => $this->requestStack->getCurrentRequest()
-        ->get('subject')
+        ->get('subject'),
     ];
 
     $form['body'] = [
       '#type' => 'textarea',
       '#title' => $this->t('Body'),
       '#required' => TRUE,
-      '#default_value' => $this->requestStack->getCurrentRequest()->get('body')
+      '#default_value' => $this->requestStack->getCurrentRequest()->get('body'),
     ];
 
     $form['submit'] = [
       '#type' => 'submit',
-      '#value' => $this->t('Send')
+      '#value' => $this->t('Send'),
     ];
 
     return $form;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    parent::validateForm($form, $form_state);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -145,11 +142,12 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $result = $this->mailManager->mail('social_media', 'forward_email', $recipient, $langcode, $params, NULL, TRUE);
     if ($result['result'] !== TRUE) {
       $this->logger->notice('Sent email to %recipient', ['%recipient' => $recipient]);
-      drupal_set_message(t('There was a problem sending your message and it was not sent.'), 'error');
+      $this->messenger()->addError($this->t('There was a problem sending your message and it was not sent.'));
     }
     else {
       $this->logger->notice('Sent email to %recipient', ['%recipient' => $recipient]);
-      drupal_set_message($this->t('Your message has been send to @email', ['@email' => $recipient]));
+      $this->messenger()->addMessage($this->t('Your message has been send to @email', ['@email' => $recipient]));
     }
   }
+
 }
diff --git a/web/modules/social_media/src/Form/SocialMediaAdminForm.php b/web/modules/social_media/src/Form/SocialMediaAdminForm.php
index 5c10d6bd94f25cc03e1b2e331b5800830c2082c8..182a40e6f21dececd688fe4d1549c10c1e21d578 100644
--- a/web/modules/social_media/src/Form/SocialMediaAdminForm.php
+++ b/web/modules/social_media/src/Form/SocialMediaAdminForm.php
@@ -11,7 +11,6 @@
 
 /**
  * Class SocialMediaAdminForm.
- *
  */
 class SocialMediaAdminForm extends ConfigFormBase {
 
@@ -26,7 +25,8 @@ class SocialMediaAdminForm extends ConfigFormBase {
    * {@inheritdoc}
    */
   public function __construct(
-  ConfigFactoryInterface $config_factory, EventDispatcherInterface $event_dispatcher) {
+  ConfigFactoryInterface $config_factory,
+  EventDispatcherInterface $event_dispatcher) {
     parent::__construct($config_factory);
     $this->eventDispatcher = $event_dispatcher;
   }
@@ -65,16 +65,16 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     $social_medias = $this->getSocialMedias();
     $token_types = ['current_page'];
     foreach ($social_medias as $key => $label) {
-      $form[$key] = array(
+      $form[$key] = [
         '#type' => 'details',
-        '#title' => t('@social_media settings', array('@social_media' => $label)),
+        '#title' => t('@social_media settings', ['@social_media' => $label]),
         '#open' => TRUE,
-      );
+      ];
       $form[$key][$key . '_enable'] = [
         '#type' => 'checkbox',
         '#title' => $this->t('Enable'),
         '#default_value' => $config->get('social_media.' . $key . '.enable'),
-        '#description' => t('Globally disabled the settings')
+        '#description' => t('Globally disabled the settings'),
       ];
 
       $form[$key][$key . '_api_url'] = [
@@ -83,6 +83,11 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         '#default_value' => $config->get('social_media.' . $key . '.api_url'),
       ];
 
+      // Handle some extra help text for the Twitter service.
+      if ($key == 'twitter') {
+        $form[$key][$key . '_api_url']['#description'] = t('To include Hashtags format your URL like this: https://twitter.com/intent/tweet?url=[current-page:url]&text=[text to tweet]&hashtags=[comma separated list of hashtags, with no # on them]');
+      }
+
       $form[$key]['token_browser'] = [
         '#theme' => 'token_tree_link',
         '#token_types' => $token_types,
@@ -91,11 +96,11 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       ];
 
       // Handle some extra configuration for email service.
-      if($key == 'email'){
+      if ($key == 'email') {
 
         $form[$key][$key . '_api_url']['#states'] = [
           'invisible' => [
-            ':input[name="'.$key . '_enable_forward'.'"]' => array('checked' => TRUE),
+            ':input[name="' . $key . '_enable_forward' . '"]' => ['checked' => TRUE],
           ],
         ];
 
@@ -103,26 +108,26 @@ public function buildForm(array $form, FormStateInterface $form_state) {
           '#type' => 'checkbox',
           '#title' => $this->t('Enable forward instead for client email service'),
           '#default_value' => $config->get('social_media.' . $key . '.enable_forward'),
-          '#description' => t('If it is checked then forward email form will open as model dialog.')
+          '#description' => t('If it is checked then forward email form will open as model dialog.'),
         ];
         $form[$key][$key . '_show_forward'] = [
           '#type' => 'radios',
-          '#options' => [1 => $this->t('Model dialog'),2 => $this->t('Separate page')],
+          '#options' => [1 => $this->t('Model dialog'), 2 => $this->t('Separate page')],
           '#title' => $this->t('Choose how you want to show forward email form'),
-          '#default_value' => $config->get('social_media.' . $key . '.show_forward')?$config->get('social_media.' . $key . '.show_forward'):1,
+          '#default_value' => $config->get('social_media.' . $key . '.show_forward') ? $config->get('social_media.' . $key . '.show_forward') : 1,
           '#states' => [
             'visible' => [
-              ':input[name="'.$key . '_enable_forward'.'"]' => array('checked' => TRUE),
-             ],
+              ':input[name="' . $key . '_enable_forward' . '"]' => ['checked' => TRUE],
             ],
-          '#description' => t('default set as dialog popup, you can change it to show in separate page')
+          ],
+          '#description' => t('default set as dialog popup, you can change it to show in separate page'),
         ];
       }
 
       $form[$key][$key . '_api_event'] = [
         '#type' => 'select',
         '#title' => $this->t('Event'),
-        '#options' => array('href' => 'href', 'onclick' => 'onclick'),
+        '#options' => ['href' => 'href', 'onclick' => 'onclick'],
         '#default_value' => $config->get('social_media.' . $key . '.api_event'),
       ];
 
@@ -130,39 +135,39 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         '#type' => 'textarea',
         '#title' => $this->t('drupalSettings variables'),
         '#default_value' => $config->get('social_media.' . $key . '.drupalSettings'),
-        '#description' => t('Defines different drupalSettings variable.Each settings in new line.eg:<br/>application_id|343434434<br/>' .
-          'you can get those variables in js.eg drupalSettings.social_media.application_id')
+        '#description' => t('Defines different drupalSettings variable.Each settings in new line.eg:<br/>application_id|343434434<br/> you can get those variables in js.eg drupalSettings.social_media.application_id'),
       ];
 
       $form[$key][$key . '_library'] = [
         '#type' => 'textfield',
         '#title' => $this->t('Drupal library'),
         '#default_value' => $config->get('social_media.' . $key . '.library'),
-        '#description' => t('Add drupal custom library.eg: social_media/facebook')
+        '#description' => t('Add drupal custom library.eg: social_media/facebook'),
       ];
 
-
       $form[$key][$key . '_text'] = [
         '#type' => 'textfield',
         '#title' => $this->t('Link text'),
         '#default_value' => $config->get('social_media.' . $key . '.text'),
-        '#description' => t('Text of the link')
+        '#description' => t('Text of the link'),
       ];
 
       $form[$key][$key . '_default_img'] = [
         '#type' => 'checkbox',
         '#title' => $this->t('Default image'),
         '#default_value' => $config->get('social_media.' . $key . '.default_img'),
-        '#description' => t('If it is checked default image will be loaded. Make service name with icon name. eg:facebook_share.svg')
+        '#description' => t('If it is checked default image will be loaded. Make service name with icon name. eg:facebook_share.svg'),
       ];
       $form[$key][$key . '_img'] = [
         '#type' => 'textfield',
         '#title' => $this->t('Alternative image link'),
-        '#states' => array('visible' => array(
-            ':input[name="' . $key . '_default_img' . '"]' => array('checked' => FALSE),
-          )),
+        '#states' => [
+          'visible' => [
+            ':input[name="' . $key . '_default_img' . '"]' => ['checked' => FALSE],
+          ],
+        ],
         '#default_value' => $config->get('social_media.' . $key . '.img'),
-        '#description' => t('If you want to have your custom image, give image path.')
+        '#description' => t('If you want to have your custom image, give image path.'),
       ];
       $form[$key][$key . '_weight'] = [
         '#type' => 'number',
@@ -170,27 +175,19 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         '#max' => 10,
         '#min' => 0,
         '#default_value' => $config->get('social_media.' . $key . '.weight'),
-        '#description' => t('Order of the share link to render')
+        '#description' => t('Order of the share link to render'),
       ];
 
       $form[$key][$key . '_attributes'] = [
         '#type' => 'textarea',
         '#title' => $this->t('Attributes'),
         '#default_value' => $config->get('social_media.' . $key . '.attributes'),
-        '#description' => t('Defines different attributes of link. Each attribute in new line.eg:<br/>target|blank<br/>'
-          . 'class|facebook-share js-share')
+        '#description' => t('Defines different attributes of link. Each attribute in new line.eg:<br/>target|blank<br/> class|facebook-share js-share'),
       ];
     }
     return parent::buildForm($form, $form_state);
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    parent::validateForm($form, $form_state);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -238,29 +235,32 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       }
     }
     $config->save();
-    drupal_set_message($this->t('Your configuration has been saved'));
+    $this->messenger()->addMessage($this->t('Your configuration has been saved'));
   }
 
   /**
+   * Get social media elements.
    *
-   * @return type
+   * @return array
+   *   Social media elements.
    */
   protected function getSocialMedias() {
-    $element = array(
+    $elements = [
       'facebook_share' => 'Facebook share',
       'facebook_msg' => 'Facebook messenger',
       'linkedin' => 'Linkedin',
       'twitter' => 'Twitter',
       'pinterest' => 'Pinterest',
-      'google_plus' => 'Google Plus',
       'email' => 'Email',
-    );
+      'whatsapp' => 'whatsapp',
+      'print' => 'Print',
+    ];
 
-    $event = new SocialMediaEvent($element);
+    $event = new SocialMediaEvent($elements);
     $this->eventDispatcher->dispatch('social_media.add_more_link', $event);
-    $element = $event->getElement();
+    $elements = $event->getElement();
 
-    return $element;
+    return $elements;
   }
 
 }
diff --git a/web/modules/social_media/src/Plugin/Block/SocialSharingBlock.php b/web/modules/social_media/src/Plugin/Block/SocialSharingBlock.php
index fd0a193a481efbe5f15acf7faed81c46d2154221..02470aaa0775edbeb46c4b39b5b172f2de36fcdd 100644
--- a/web/modules/social_media/src/Plugin/Block/SocialSharingBlock.php
+++ b/web/modules/social_media/src/Plugin/Block/SocialSharingBlock.php
@@ -11,6 +11,7 @@
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Path\CurrentPathStack;
+use Drupal\Core\Cache\Cache;
 
 /**
  * Provides a 'SocialSharingBlock' block.
@@ -79,11 +80,11 @@ public function build() {
     $library = ['social_media/basic'];
     $settings = [];
     $icon_path = $base_url . '/' . drupal_get_path('module', 'social_media') . '/icons/';
-    $elements = array();
+    $elements = [];
     $social_medias = $this->configFactory->get('social_media.settings')
       ->get('social_media');
 
-    // call pre_execute event before doing anything.
+    // Call pre_execute event before doing anything.
     $event = new SocialMediaEvent($social_medias);
     $this->eventDispatcher->dispatch('social_media.pre_execute', $event);
     $social_medias = $event->getElement();
@@ -91,7 +92,7 @@ public function build() {
     $social_medias = $this->sortSocialMedias($social_medias);
     foreach ($social_medias as $name => $social_media) {
 
-      // replace api url with different link.
+      // Replace api url with different link.
       if ($name == "email" && isset($social_media['enable_forward']) && $social_media['enable_forward']) {
         $social_media['api_url'] = str_replace('mailto:', '/social-media-forward', $social_media['api_url']);
         $social_media['api_url'] .= '&destination=' . $this->currentPath->getPath();
@@ -102,16 +103,16 @@ public function build() {
 
       if ($social_media['enable'] == 1 && !empty($social_media['api_url'])) {
         $elements[$name]['text'] = $social_media['text'];
-        $elements[$name]['api'] = new Attribute(array($social_media['api_event'] => $this->token->replace($social_media['api_url'])));
+        $elements[$name]['api'] = new Attribute([$social_media['api_event'] => $this->token->replace($social_media['api_url'])]);
 
         if (isset($social_media['library']) && !empty($social_media['library'])) {
           $library[] = $social_media['library'];
         }
         if (isset($social_media['attributes']) && !empty($social_media['attributes'])) {
-          $elements[$name]['attr'] = $this->social_media_convert_attributes($social_media['attributes']);
+          $elements[$name]['attr'] = $this->socialMediaConvertAttributes($social_media['attributes']);
         }
         if (isset($social_media['drupalSettings']) && !empty($social_media['drupalSettings'])) {
-          $settings['social_media'] = $this->social_media_convert_drupalSettings($social_media['drupalSettings']);
+          $settings['social_media'] = $this->socialMediaConvertDrupalSettings($social_media['drupalSettings']);
         }
 
         if (isset($social_media['default_img']) && $social_media['default_img']) {
@@ -133,31 +134,25 @@ public function build() {
 
     $build = [];
 
-    // call prerender event before render.
+    // Call prerender event before render.
     $event = new SocialMediaEvent($elements);
     $this->eventDispatcher->dispatch('social_media.pre_render', $event);
     $elements = $event->getElement();
 
-    $build['social_sharing_block'] = array(
+    $build['social_sharing_block'] = [
       '#theme' => 'social_media_links',
       '#elements' => $elements,
       '#attached' => [
         'library' => $library,
-        'drupalSettings' => $settings
+        'drupalSettings' => $settings,
       ],
-      '#cache' => [
-        'tags' => [
-          'social_media:' . $this->currentPath->getPath(),
-        ],
-        'contexts' => [
-          'url',
-        ],
-      ],
-    );
-    //dsm($build);
+    ];
     return $build;
   }
 
+  /**
+   * TODO describe what this does and what $element is.
+   */
   protected function sortSocialMedias(&$element) {
     $weight = [];
     foreach ($element as $key => $row) {
@@ -167,11 +162,17 @@ protected function sortSocialMedias(&$element) {
     return $element;
   }
 
-  protected function social_media_convert_attributes($variables) {
+  /**
+   * TODO describe what this does and what $variables is.
+   */
+  protected function socialMediaConvertAttributes($variables) {
     $variable = explode("\n", $variables);
     $attributes = [];
     if (count($variable)) {
       foreach ($variable as $each) {
+        if ($each === '') {
+          continue;
+        }
         $var = explode("|", $each);
         $value = str_replace(["\r\n", "\n", "\r"], "", $var[1]);
         $attributes[$var[0]] = new Attribute([$var[0] => $value]);
@@ -180,7 +181,10 @@ protected function social_media_convert_attributes($variables) {
     return $attributes;
   }
 
-  protected function social_media_convert_drupalSettings($variables) {
+  /**
+   * TODO describe what this does and what $variables is.
+   */
+  protected function socialMediaConvertDrupalSettings($variables) {
     $variable = explode("\n", $variables);
     $settings = [];
     if (count($variable)) {
@@ -193,4 +197,21 @@ protected function social_media_convert_drupalSettings($variables) {
     return $settings;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return Cache::mergeTags(parent::getCacheTags(), [
+      'social_media:' . $this->currentPath->getPath(),
+      'config:social_media.settings',
+    ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return Cache::mergeContexts(parent::getCacheContexts(), ['url.path']);
+  }
+
 }
diff --git a/web/modules/social_media/src/Plugin/Field/FieldFormatter/SocialMediaFormatter.php b/web/modules/social_media/src/Plugin/Field/FieldFormatter/SocialMediaFormatter.php
index 1eb28da3982be9221f9c7c2695ffb8024d471520..560c3cba404e426eb5a04448033f70cc6750603b 100644
--- a/web/modules/social_media/src/Plugin/Field/FieldFormatter/SocialMediaFormatter.php
+++ b/web/modules/social_media/src/Plugin/Field/FieldFormatter/SocialMediaFormatter.php
@@ -21,7 +21,7 @@
  *   }
  * )
  */
-class SocialMediaFormatter extends FormatterBase  implements ContainerFactoryPluginInterface  {
+class SocialMediaFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
 
   /**
    * The block manager.
@@ -46,6 +46,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['label'], $configuration['view_mode'], $configuration['third_party_settings'], $container->get('plugin.manager.block')
     );
   }
+
   /**
    * {@inheritdoc}
    */
@@ -56,10 +57,10 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
 
       $config = [];
       $block_instance = $this->blockManager->createInstance('social_sharing_block', $config);
-    // Some blocks might implement access check.
+      // Some blocks might implement access check.
       $access_result = $block_instance->access(\Drupal::currentUser());
-    // Return empty render array if user doesn't have access.
-    // $access_result can be boolean or an AccessResult class
+      // Return empty render array if user doesn't have access.
+      // $access_result can be boolean or an AccessResult class.
       if (is_object($access_result) && $access_result->isForbidden() || is_bool($access_result) && !$access_result) {
         return [];
       }
diff --git a/web/modules/social_media/src/Plugin/Field/FieldType/SocialMediaItem.php b/web/modules/social_media/src/Plugin/Field/FieldType/SocialMediaItem.php
index a4e0cfe2dc7e96166b80d59c6ece6c1508c7aaae..beeb7234a3aed9b854cbb9068798026ce51fd49e 100644
--- a/web/modules/social_media/src/Plugin/Field/FieldType/SocialMediaItem.php
+++ b/web/modules/social_media/src/Plugin/Field/FieldType/SocialMediaItem.php
@@ -23,7 +23,7 @@
  *   default_formatter = "social_media_default"
  * )
  */
-class SocialMediaItem extends FieldItemBase implements OptionsProviderInterface  {
+class SocialMediaItem extends FieldItemBase implements OptionsProviderInterface {
 
   /**
    * {@inheritdoc}
@@ -60,7 +60,6 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
     return $properties;
   }
 
-
   /**
    * {@inheritdoc}
    */
diff --git a/web/modules/social_media/src/Plugin/Field/FieldWidget/SocialMediaWidget.php b/web/modules/social_media/src/Plugin/Field/FieldWidget/SocialMediaWidget.php
index 58f1e0b391b6f96cb2081ac21d323e193f3d99bd..ffbbaaa1eedfaaeab94f56059580077d720ca9dc 100644
--- a/web/modules/social_media/src/Plugin/Field/FieldWidget/SocialMediaWidget.php
+++ b/web/modules/social_media/src/Plugin/Field/FieldWidget/SocialMediaWidget.php
@@ -59,9 +59,9 @@ public function settingsSummary() {
    */
   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
     $element['value'] = $element + [
-        '#type' => 'checkbox',
-        '#default_value' => !empty($items[0]->value),
-      ];
+      '#type' => 'checkbox',
+      '#default_value' => !empty($items[0]->value),
+    ];
 
     // Override the title from the incoming $element.
     if ($this->getSetting('display_label')) {
diff --git a/web/modules/social_media/templates/social-media-links.html.twig b/web/modules/social_media/templates/social-media-links.html.twig
index 85c1e4a5e7f437e10c5e090f5f7e82a6930442db..fa03431c320646342e3a1422c26d8a0cc680f075 100644
--- a/web/modules/social_media/templates/social-media-links.html.twig
+++ b/web/modules/social_media/templates/social-media-links.html.twig
@@ -20,7 +20,7 @@
         {% set dialogOptions = "data-dialog-options=" ~ {'width': '600'}|json_encode %}
       {% endif %}
       <li>
-        <a {{ dialogType }} {{ dialogOptions }} {{ element.attr.target }} {{ element.attr.class.addClass(classes) }}  {{ element.api }}
+        <a {{ dialogType }} {{ dialogOptions }} {{ element.attr.target }} {{ element.attr.rel }} {{ element.attr.class.addClass(classes) }}  {{ element.api }}
           title="{{ element.text }}">
           {% if element.img %}
             <img alt="{{ element.text }}" src="{{ element.img }}">
diff --git a/web/modules/video_embed_field/composer.json b/web/modules/video_embed_field/composer.json
index d5ceb51ff0990447ce38213940246ef3d178fb9c..ad7fdca15dbe6d257632ff5eb9ab20362747ec87 100644
--- a/web/modules/video_embed_field/composer.json
+++ b/web/modules/video_embed_field/composer.json
@@ -3,11 +3,5 @@
   "description": "A pluggable field type for storing videos from external video hosts such as Vimeo and YouTube.",
   "type": "drupal-module",
   "homepage": "https://www.drupal.org/project/video_embed_field",
-  "license": "GPL-2.0+",
-  "extra": {
-    "drupal": {
-      "package": "Field types",
-      "version": "8.x-1.x"
-    }
-  }
+  "license": "GPL-2.0+"
 }
diff --git a/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/drush.services.yml b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/drush.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..719a80c8b38bf03c5a2fed118757bddb48fa6117
--- /dev/null
+++ b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/drush.services.yml
@@ -0,0 +1,6 @@
+services:
+  vem_migrate_oembed.commands:
+    class: \Drupal\vem_migrate_oembed\Commands\VemCommands
+    arguments: ['@vem_migrate_oembed.migrate']
+    tags:
+      - { name: drush.command }
diff --git a/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/src/Commands/VemCommands.php b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/src/Commands/VemCommands.php
new file mode 100644
index 0000000000000000000000000000000000000000..0608d5908735feead21f27cc79c6ee5438d933ed
--- /dev/null
+++ b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/src/Commands/VemCommands.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\vem_migrate_oembed\Commands;
+
+use Drupal\vem_migrate_oembed\VemMigrate;
+use Drush\Commands\DrushCommands;
+
+/**
+ * A Drush commandfile.
+ *
+ * In addition to this file, you need a drush.services.yml
+ * in root of your module, and a composer.json file that provides the name
+ * of the services file to use.
+ *
+ * See these files for an example of injecting Drupal services:
+ *   - http://cgit.drupalcode.org/devel/tree/src/Commands/DevelCommands.php
+ *   - http://cgit.drupalcode.org/devel/tree/drush.services.yml
+ */
+class VemCommands extends DrushCommands {
+
+  /**
+   * The migrate service.
+   *
+   * @var \Drupal\vem_migrate_oembed\VemMigrate
+   */
+  protected $migrator;
+
+  /**
+   * SamplerCommands constructor.
+   *
+   * @param \Drupal\vem_migrate_oembed\VemMigrate $migrator
+   *   The migrate service.
+   */
+  public function __construct(VemMigrate $migrator) {
+    parent::__construct();
+    $this->migrator = $migrator;
+  }
+
+  /**
+   * Migrates from video_embed_media to core media.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   *
+   * @command vem:migrate_oembed
+   */
+  public function migrate() {
+    $this->migrator->migrate();
+  }
+
+}
diff --git a/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/src/VemMigrate.php b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/src/VemMigrate.php
new file mode 100644
index 0000000000000000000000000000000000000000..8db940ba49e15214bd5c3de67ca8af58253ea8ed
--- /dev/null
+++ b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/src/VemMigrate.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Drupal\vem_migrate_oembed;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\media\Entity\MediaType;
+
+/**
+ * Class VemMigrate.
+ */
+class VemMigrate {
+
+  /**
+   * The config factory service.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The last installed schema repository service.
+   *
+   * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
+   */
+  protected $lastInstalledSchemaRepository;
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
+   */
+  protected $keyValue;
+
+  /**
+   * The entity field manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * VemMigrate constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The config factory service.
+   * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository
+   *   The last installed schema repository service.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $keyValueFactory
+   *   The key value store.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
+   *   The entity field manager service.
+   */
+  public function __construct(ConfigFactoryInterface $configFactory, EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository, Connection $connection, KeyValueFactoryInterface $keyValueFactory, EntityFieldManagerInterface $entityFieldManager) {
+    $this->configFactory = $configFactory;
+    $this->lastInstalledSchemaRepository = $entityLastInstalledSchemaRepository;
+    $this->database = $connection;
+    $this->keyValue = $keyValueFactory;
+    $this->entityFieldManager = $entityFieldManager;
+  }
+
+  /**
+   * Migrates from video_embed_media to core media.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function migrate() {
+
+    foreach ($this->configFactory->listAll('media.type.') as $media_type) {
+      $media_type = $this->configFactory->getEditable($media_type);
+
+      if ($media_type->get('source') !== 'video_embed_field') {
+        continue;
+      }
+
+      $media_type->set('source', 'oembed:video');
+      $media_type->set('source_configuration.thumbnails_directory', 'public://oembed_thumbnails');
+
+      $media_type->save(TRUE);
+
+      MediaType::load($media_type->get('id'))->calculateDependencies()->save();
+    }
+
+    $media_definition = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('media');
+    foreach ($this->configFactory->listAll('field.storage.media') as $field_storage) {
+      $field_storage = $this->configFactory->getEditable($field_storage);
+      $field_name = $field_storage->get('field_name');
+
+      if ($field_storage->get('type') !== 'video_embed_field') {
+        continue;
+      }
+
+      $this->database->schema()->changeField("media__$field_name", "${field_name}_value", "${field_name}_value", ['type' => 'varchar', 'length' => 255]);
+      $this->database->schema()->changeField("media_revision__$field_name", "${field_name}_value", "${field_name}_value", ['type' => 'varchar', 'length' => 255]);
+
+      $store = $this->keyValue->get("entity.storage_schema.sql");
+      $data = $store->get("media.field_schema_data.$field_name");
+      $data["media__$field_name"]['fields']["${field_name}_value"]['length'] = 255;
+      $data["media_revision__$field_name"]['fields']["${field_name}_value"]['length'] = 255;
+      $store->set("media.field_schema_data.$field_name", $data);
+
+      $media_definition[$field_name]->set('type', 'string');
+      $this->lastInstalledSchemaRepository->setLastInstalledFieldStorageDefinition($media_definition[$field_name]);
+
+      $this->entityFieldManager->clearCachedFieldDefinitions();
+
+      $field_storage->set('type', 'string');
+      $field_storage->set('module', 'core');
+      $field_storage->set('settings.max_length', 255);
+      $field_storage->set('settings.is_ascii', FALSE);
+      $field_storage->set('settings.case_sensitive', FALSE);
+      $field_storage->save(TRUE);
+
+      FieldStorageConfig::loadByName('media', $field_name)->calculateDependencies()->save();
+    }
+
+    foreach ($this->configFactory->listAll('field.field.media') as $field_config) {
+      $field_config = $this->configFactory->getEditable($field_config);
+
+      if ($field_config->get('field_type') !== 'video_embed_field') {
+        continue;
+      }
+
+      $field_config->set('field_type', 'string');
+      $field_config->set('settings', []);
+      $field_config->save(TRUE);
+
+      FieldConfig::loadByName('media', $field_config->get('bundle'), $field_config->get('field_name'))->calculateDependencies()->save();
+    }
+
+    foreach ($this->configFactory->listAll('core.entity_view_display.media') as $entity_view_display) {
+      $entity_view_display = $this->configFactory->getEditable($entity_view_display);
+
+      $fields = array_keys($entity_view_display->get('content'));
+      foreach ($fields as $field) {
+        if ($entity_view_display->get("content.$field.type") !== 'video_embed_field_video') {
+          continue;
+        }
+
+        $entity_view_display->set("content.$field.type", 'oembed');
+        $entity_view_display->set("content.$field.settings.max_width", $entity_view_display->get("content.$field.settings.width"));
+        $entity_view_display->set("content.$field.settings.max_height", $entity_view_display->get("content.$field.settings.height"));
+        $entity_view_display->clear("content.$field.settings.autoplay");
+        $entity_view_display->clear("content.$field.settings.responsive");
+        $entity_view_display->save(TRUE);
+
+        $bundle = $entity_view_display->get('bundle');
+        $mode = $entity_view_display->get('mode');
+        EntityViewDisplay::load("media.$bundle.$mode")->calculateDependencies()->save();
+      }
+
+    }
+
+    foreach ($this->configFactory->listAll('core.entity_form_display.media') as $entity_form_display) {
+      $entity_form_display = $this->configFactory->getEditable($entity_form_display);
+
+      $fields = array_keys($entity_form_display->get('content'));
+      foreach ($fields as $field) {
+        if ($entity_form_display->get("content.$field.type") !== 'video_embed_field_textfield') {
+          continue;
+        }
+
+        $entity_form_display->set("content.$field.type", 'oembed_textfield');
+        $entity_form_display->set("content.$field.settings.size", 60);
+        $entity_form_display->set("content.$field.settings.placeholder", '');
+        $entity_form_display->save(TRUE);
+
+        $bundle = $entity_form_display->get('bundle');
+        $mode = $entity_form_display->get('mode');
+        EntityFormDisplay::load("media.$bundle.$mode")->calculateDependencies()->save();
+      }
+
+    }
+  }
+
+}
diff --git a/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/tests/src/Functional/oEmbedUpdateTest.php b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/tests/src/Functional/oEmbedUpdateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ed942555a5f1f3ab501a40d364fb87a72c9028b
--- /dev/null
+++ b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/tests/src/Functional/oEmbedUpdateTest.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\Tests\vem_embed_media\Functional;
+
+use Drupal\media\Entity\MediaType;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
+
+/**
+ * Tests the VEM to OEmbed migration.
+ *
+ * @group vem_embed_media
+ */
+class oEmbedUpdateTest extends BrowserTestBase {
+
+  use MediaTypeCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['vem_migrate_oembed'];
+
+  /**
+   * Tests the VEM to OEmbed migration.
+   */
+  public function testOEmbedUpdate() {
+
+    $mediaType = $this->createMediaType('video_embed_field');
+    $this->assertEqual($mediaType->getSource()->getPluginId(), 'video_embed_field');
+
+    $sourceField = $mediaType->getSource()->getSourceFieldDefinition($mediaType);
+    $this->assertEqual($sourceField->getType(), 'video_embed_field');
+
+    $formDisplay = entity_get_form_display('media', $mediaType->id(), 'default');
+    $formField = $formDisplay->getComponent($sourceField->getName());
+
+    $this->assertEqual($formField['type'], 'video_embed_field_textfield');
+
+    /** @var \Drupal\vem_migrate_oembed\VemMigrate $vemService */
+    $vemService = \Drupal::service('vem_migrate_oembed.migrate');
+    $vemService->migrate();
+
+    /** @var \Drupal\media\Entity\MediaType $mediaType */
+    $mediaType = MediaType::load($mediaType->id());
+    $this->assertEqual($mediaType->getSource()->getPluginId(), 'oembed:video');
+
+    $sourceField = $mediaType->getSource()->getSourceFieldDefinition($mediaType);
+    $this->assertEqual($sourceField->getType(), 'string');
+
+    $formDisplay = entity_get_form_display('media', $mediaType->id(), 'default');
+    $formField = $formDisplay->getComponent($sourceField->getName());
+
+    $this->assertEqual($formField['type'], 'oembed_textfield');
+  }
+
+}
diff --git a/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.drush.inc b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.drush.inc
new file mode 100644
index 0000000000000000000000000000000000000000..69bf2112e62b38f2b84ae39e119cc5b1035325e8
--- /dev/null
+++ b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.drush.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Drush integration for vem_migrate_oembed.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function vem_migrate_oembed_drush_command() {
+
+  $items['video-embed-media-migrate-oembed'] = [
+    'description' => 'Migrate from VEF to oEmbed in core.',
+    'aliases' => ['vemmo'],
+    'core' => ['8+'],
+    'examples' => [
+      "drush vemmo" => "Migrate from VEF to oEmbed in core",
+    ],
+  ];
+
+  return $items;
+}
+
+/**
+ * Callback for drush commmand "video-embed-media-migrate-oembed" (vemmo).
+ */
+function drush_vem_migrate_oembed_video_embed_media_migrate_oembed() {
+
+  /** @var \Drupal\vem_migrate_oembed\VemMigrate $migrate */
+  $migrate = \Drupal::service('vem_migrate_oembed.migrate');
+  $migrate->migrate();
+}
diff --git a/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.info.yml b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b8a65a11f537e3a6d46b91612eaa3368b56a0b4a
--- /dev/null
+++ b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.info.yml
@@ -0,0 +1,14 @@
+name: Video Embed Media - Migrate to core oEmbed
+type: module
+description: 'Provides a drush command to migrate to cores oEmbed'
+package: Video Embed Field
+# core: 8.x
+dependencies:
+  - drupal:media (>= 8.6)
+  - video_embed_field:video_embed_media
+
+# Information added by Drupal.org packaging script on 2019-07-26
+version: '8.x-2.2'
+core: '8.x'
+project: 'video_embed_field'
+datestamp: 1564103587
diff --git a/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.services.yml b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7c2db2d1ef6dfc100e6fa54f31246f0150989e7d
--- /dev/null
+++ b/web/modules/video_embed_field/modules/video_embed_media/modules/vem_migrate_oembed/vem_migrate_oembed.services.yml
@@ -0,0 +1,4 @@
+services:
+  vem_migrate_oembed.migrate:
+    class: Drupal\vem_migrate_oembed\VemMigrate
+    arguments: ['@config.factory', '@entity.last_installed_schema.repository', '@database', '@keyvalue', '@entity_field.manager']
diff --git a/web/modules/video_embed_field/modules/video_embed_media/tests/src/Functional/BundleTest.php b/web/modules/video_embed_field/modules/video_embed_media/tests/src/Functional/BundleTest.php
index ece13725ce52b5bb7ead41ad0ae1f988124b32c5..7f958a2f56454854f2b8d31d7fe4c137f51e112b 100644
--- a/web/modules/video_embed_field/modules/video_embed_media/tests/src/Functional/BundleTest.php
+++ b/web/modules/video_embed_field/modules/video_embed_media/tests/src/Functional/BundleTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\Tests\video_embed_media\Functional;
 
 use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
-use Drupal\Tests\media\Functional\MediaFunctionalTestCreateMediaTypeTrait;
 use Drupal\Tests\video_embed_field\Functional\AdminUserTrait;
 
 /**
@@ -39,7 +38,7 @@ public function testMediaBundleCreation() {
     $this->drupalLogin($this->adminUser);
 
     // Create bundle and modify form display.
-    $media_type = $this->createMediaType(['bundle' => 'video_bundle'], 'video_embed_field');
+    $media_type = $this->createMediaType('video_embed_field', ['bundle' => 'video_bundle']);
     $source = $media_type->getSource();
     $source_field = $source->getSourceFieldDefinition($media_type);
     if ($source_field->isDisplayConfigurable('form')) {
@@ -49,7 +48,7 @@ public function testMediaBundleCreation() {
 
       // @todo Replace entity_get_form_display() when #2367933 is done.
       // https://www.drupal.org/node/2872159.
-      entity_get_form_display('media', $media_type->id(), 'default')
+      $this->container->get('entity_display.repository')->getFormDisplay('media', $media_type->id(), 'default')
         ->setComponent($source_field->getName(), $component)
         ->save();
     }
diff --git a/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/DefaultNameTest.php b/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/DefaultNameTest.php
index e51b806940e3f7cc2ab9b59b8e927e4a6711cb00..71503730dbb477b96da3055cdf0a9b35fe7dd241 100644
--- a/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/DefaultNameTest.php
+++ b/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/DefaultNameTest.php
@@ -3,9 +3,7 @@
 namespace Drupal\Tests\video_embed_media\Kernel;
 
 use Drupal\media\Entity\Media;
-use Drupal\media\Entity\MediaType;
 use Drupal\Tests\media\Kernel\MediaKernelTestBase;
-use Drupal\video_embed_media\Plugin\media\Source\VideoEmbedField;
 
 /**
  * Test the media bundle default names.
diff --git a/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/ProvidedFieldsTest.php b/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/ProvidedFieldsTest.php
index cd90463256a957524b4e120cb702542227f29059..b7ef9e8883b20fe8be705f83ab208dae005dfd15 100644
--- a/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/ProvidedFieldsTest.php
+++ b/web/modules/video_embed_field/modules/video_embed_media/tests/src/Kernel/ProvidedFieldsTest.php
@@ -2,10 +2,9 @@
 
 namespace Drupal\Tests\video_embed_media\Kernel;
 
+use Drupal\Core\File\FileSystemInterface;
 use Drupal\media\Entity\Media;
-use Drupal\media\Entity\MediaType;
 use Drupal\Tests\media\Kernel\MediaKernelTestBase;
-use Drupal\video_embed_media\Plugin\media\Source\VideoEmbedField;
 
 /**
  * Test the provided fields.
@@ -122,7 +121,7 @@ public function setUp() {
     $this->plugin = $this->entityType->getSource();
 
     $dir = 'public://video_thumbnails';
-    file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+    $this->container->get('file_system')->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
   }
 
 }
diff --git a/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.info.yml b/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.info.yml
index bc5073d7ed4db3806c184e876108521d902a44b3..42dd220d8a9ce2cd951f8d096f978a59205ca0d5 100644
--- a/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.info.yml
+++ b/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.info.yml
@@ -7,8 +7,8 @@ dependencies:
   - drupal:media (>= 8.4)
   - video_embed_field:video_embed_field
 
-# Information added by Drupal.org packaging script on 2018-04-10
-version: '8.x-2.0'
+# Information added by Drupal.org packaging script on 2019-07-26
+version: '8.x-2.2'
 core: '8.x'
 project: 'video_embed_field'
-datestamp: 1523338087
+datestamp: 1564103587
diff --git a/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.install b/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.install
index fe4475310fc842365a245fe00095c0c8b86ab24d..843637edfe844fd70c0c06c9a5d5e70b49f6d0be 100644
--- a/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.install
+++ b/web/modules/video_embed_field/modules/video_embed_media/video_embed_media.install
@@ -5,14 +5,18 @@
  * Install file for video_embed_media.
  */
 
+use Drupal\Core\File\FileSystemInterface;
+
 /**
  * Implements hook_install().
  */
 function video_embed_media_install() {
+  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+  $file_system = \Drupal::service('file_system');
   // Copy the video icons into the right place.
   $icon_folder = drupal_get_path('module', 'video_embed_media') . '/images/icons';
   $destination = \Drupal::config('media.settings')->get('icon_base_uri');
-  file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+  $file_system->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
 
   $files = file_scan_directory($icon_folder, '/.*\.(svg|png|jpg|jpeg|gif)$/');
   foreach ($files as $file) {
@@ -22,7 +26,7 @@ function video_embed_media_install() {
     // are referenced somewhere else. Since showing an error that it was not
     // possible to copy the files is also confusing, we silently do nothing.
     if (!file_exists($destination . DIRECTORY_SEPARATOR . $file->filename)) {
-      file_unmanaged_copy($file->uri, $destination, FILE_EXISTS_ERROR);
+      $file_system->copy($file->uri, $destination, FileSystemInterface::EXISTS_ERROR);
     }
   }
 }
diff --git a/web/modules/video_embed_field/modules/video_embed_wysiwyg/tests/src/FunctionalJavascript/EmbedDialogTest.php b/web/modules/video_embed_field/modules/video_embed_wysiwyg/tests/src/FunctionalJavascript/EmbedDialogTest.php
index c80d66328031e626d4e0d857671b95ee4d35c140..4be4bc6f4ace5cf4f2e8fb650c82c40b8ad483ff 100644
--- a/web/modules/video_embed_field/modules/video_embed_wysiwyg/tests/src/FunctionalJavascript/EmbedDialogTest.php
+++ b/web/modules/video_embed_field/modules/video_embed_wysiwyg/tests/src/FunctionalJavascript/EmbedDialogTest.php
@@ -2,15 +2,15 @@
 
 namespace Drupal\Tests\video_embed_wysiwyg\FunctionalJavascript;
 
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
-use Drupal\simpletest\ContentTypeCreationTrait;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
 
 /**
  * Test the dialog form.
  *
  * @group video_embed_wysiwyg
  */
-class EmbedDialogTest extends JavascriptTestBase {
+class EmbedDialogTest extends WebDriverTestBase {
 
   use ContentTypeCreationTrait;
 
@@ -48,12 +48,13 @@ public function setUp() {
 
     // Assert access is denied without enabling the filter.
     $this->drupalGet('video-embed-wysiwyg/dialog/plain_text');
-    $this->assertEquals(403, $this->getSession()->getStatusCode());
+    $this->assertSession()->pageTextContains('Access denied');
 
     // Enable the filter.
     $this->drupalGet('admin/config/content/formats/manage/plain_text');
     $this->find('[name="editor[editor]"]')->setValue('ckeditor');
     $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->getSession()->getDriver()->executeScript("jQuery('.form-item-editor-settings-toolbar-button-groups').show();");
     $this->submitForm([
       'filters[video_embed_wysiwyg][status]' => TRUE,
       'filters[filter_html_escape][status]' => FALSE,
@@ -62,7 +63,7 @@ public function setUp() {
 
     // Visit the modal again.
     $this->drupalGet('video-embed-wysiwyg/dialog/plain_text');
-    $this->assertEquals(200, $this->getSession()->getStatusCode());
+    $this->assertSession()->pageTextNotContains('Access denied');
   }
 
   /**
@@ -77,8 +78,6 @@ public function testEmbedDialog() {
     // Assert all the form fields appear on the modal.
     $this->assertSession()->pageTextContains('Autoplay');
     $this->assertSession()->pageTextContains('Responsive Video');
-    $this->assertSession()->pageTextContains('Width');
-    $this->assertSession()->pageTextContains('Height');
     $this->assertSession()->pageTextContains('Video URL');
 
     // Attempt to submit the modal with no values.
diff --git a/web/modules/video_embed_field/modules/video_embed_wysiwyg/video_embed_wysiwyg.info.yml b/web/modules/video_embed_field/modules/video_embed_wysiwyg/video_embed_wysiwyg.info.yml
index bf4ef98bb3d6c62b66e6bcd1be6fbab1c069cdbc..a2cb4b778c553be0552697816b6c810c7d68e89b 100644
--- a/web/modules/video_embed_field/modules/video_embed_wysiwyg/video_embed_wysiwyg.info.yml
+++ b/web/modules/video_embed_field/modules/video_embed_wysiwyg/video_embed_wysiwyg.info.yml
@@ -8,8 +8,8 @@ dependencies:
   - drupal:ckeditor
   - video_embed_field:video_embed_field
 
-# Information added by Drupal.org packaging script on 2018-04-10
-version: '8.x-2.0'
+# Information added by Drupal.org packaging script on 2019-07-26
+version: '8.x-2.2'
 core: '8.x'
 project: 'video_embed_field'
-datestamp: 1523338087
+datestamp: 1564103587
diff --git a/web/modules/video_embed_field/src/Plugin/Field/FieldFormatter/LazyLoad.php b/web/modules/video_embed_field/src/Plugin/Field/FieldFormatter/LazyLoad.php
index 4376a68be6ebc792d6c61abc33307e374028d9e5..5bba79a378e43e4500abe0fbb6acc14aec3b6b80 100644
--- a/web/modules/video_embed_field/src/Plugin/Field/FieldFormatter/LazyLoad.php
+++ b/web/modules/video_embed_field/src/Plugin/Field/FieldFormatter/LazyLoad.php
@@ -107,7 +107,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
       // Add a play button.
       $itemThumb[] = [
         '#type' => 'html_tag',
-        '#tag' => 'div',
+        '#tag' => 'button',
         '#attributes' => [
           'class' => ['video-embed-field-lazy-play']
         ],
diff --git a/web/modules/video_embed_field/src/Plugin/migrate/cckfield/EmvideoField.php b/web/modules/video_embed_field/src/Plugin/migrate/cckfield/EmvideoField.php
index 5774d852a658d565be9f3fb584d783dfb778cc39..37491c10fae78ce07261e2cd64d5741086b34cc5 100644
--- a/web/modules/video_embed_field/src/Plugin/migrate/cckfield/EmvideoField.php
+++ b/web/modules/video_embed_field/src/Plugin/migrate/cckfield/EmvideoField.php
@@ -4,7 +4,7 @@
 
 use Drupal\migrate\Plugin\MigrationInterface;
 use Drupal\migrate\Row;
-use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
+use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
 
 /**
  * Plugin to migrate from the Drupal 6 emfield module.
@@ -16,7 +16,7 @@
  *   destination_module = "video_embed_field",
  * )
  */
-class EmvideoField extends CckFieldPluginBase {
+class EmvideoField extends FieldPluginBase {
 
   /**
    * {@inheritdoc}
@@ -48,9 +48,9 @@ public function getFieldWidgetMap() {
   /**
    * {@inheritdoc}
    */
-  public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
+  public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
     $process = [
-      'plugin' => 'iterator',
+      'plugin' => 'sub_process',
       'source' => $field_name,
       'process' => [
         'value' => 'embed',
diff --git a/web/modules/video_embed_field/src/Plugin/migrate/cckfield/VideoEmbedField.php b/web/modules/video_embed_field/src/Plugin/migrate/cckfield/VideoEmbedField.php
index 67c4487d0ab1524c867d8d850bd4c65982b154b7..97e0f216b814942cbd3fae76a3182f02698cfbd7 100644
--- a/web/modules/video_embed_field/src/Plugin/migrate/cckfield/VideoEmbedField.php
+++ b/web/modules/video_embed_field/src/Plugin/migrate/cckfield/VideoEmbedField.php
@@ -4,7 +4,7 @@
 
 use Drupal\migrate\Plugin\MigrationInterface;
 use Drupal\migrate\Row;
-use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
+use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
 
 /**
  * Plugin to migrate from the Drupal 7 video_embed_field module.
@@ -16,7 +16,7 @@
  *   destination_module = "video_embed_field",
  * )
  */
-class VideoEmbedField extends CckFieldPluginBase {
+class VideoEmbedField extends FieldPluginBase {
 
   /**
    * {@inheritdoc}
@@ -48,9 +48,9 @@ public function getFieldWidgetMap() {
   /**
    * {@inheritdoc}
    */
-  public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
+  public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
     $process = [
-      'plugin' => 'iterator',
+      'plugin' => 'sub_process',
       'source' => $field_name,
       'process' => [
         'value' => 'video_url',
diff --git a/web/modules/video_embed_field/src/Plugin/video_embed_field/Provider/YouTube.php b/web/modules/video_embed_field/src/Plugin/video_embed_field/Provider/YouTube.php
index aa11aeac98a65e24336e0ef21515d84cc4b95866..224edd2e2d6f0157b77968877489ae05f953101d 100644
--- a/web/modules/video_embed_field/src/Plugin/video_embed_field/Provider/YouTube.php
+++ b/web/modules/video_embed_field/src/Plugin/video_embed_field/Provider/YouTube.php
@@ -47,8 +47,13 @@ public function renderEmbedCode($width, $height, $autoplay) {
    *   The time index where the video should start based on the URL.
    */
   protected function getTimeIndex() {
-    preg_match('/[&\?]t=(?<timeindex>\d+)/', $this->getInput(), $matches);
-    return isset($matches['timeindex']) ? $matches['timeindex'] : 0;
+    preg_match('/[&\?]t=((?<hours>\d+)h)?((?<minutes>\d+)m)?(?<seconds>\d+)s?/', $this->getInput(), $matches);
+
+    $hours = !empty($matches['hours']) ? $matches['hours'] : 0;
+    $minutes = !empty($matches['minutes']) ? $matches['minutes'] : 0;
+    $seconds = !empty($matches['seconds']) ? $matches['seconds'] : 0;
+
+    return $hours * 3600 + $minutes * 60 + $seconds;
   }
 
   /**
diff --git a/web/modules/video_embed_field/src/ProviderPluginBase.php b/web/modules/video_embed_field/src/ProviderPluginBase.php
index aeb82a5f994a8581185883836ca90a3858b56f96..c3c4b0da719ed514037381c2368f127332204ffd 100644
--- a/web/modules/video_embed_field/src/ProviderPluginBase.php
+++ b/web/modules/video_embed_field/src/ProviderPluginBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\video_embed_field;
 
+use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase;
 use GuzzleHttp\ClientInterface;
@@ -40,10 +41,15 @@ abstract class ProviderPluginBase extends PluginBase implements ProviderPluginIn
    */
   protected $httpClient;
 
+  /**
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
   /**
    * Create a plugin with the given input.
    *
-   * @param string $configuration
+   * @param array $configuration
    *   The configuration of the plugin.
    * @param string $plugin_id
    *   The plugin id.
@@ -74,6 +80,19 @@ protected function getVideoId() {
     return $this->videoId;
   }
 
+  /**
+   * Get the file system service.
+   *
+   * @return \Drupal\Core\File\FileSystemInterface
+   *   The file system service.
+   */
+  protected function getFileSystem() {
+    if (!isset($this->fileSystem)) {
+      $this->fileSystem = \Drupal::service('file_system');
+    }
+    return $this->fileSystem;
+  }
+
   /**
    * Get the input which caused this plugin to be selected.
    *
@@ -122,10 +141,10 @@ public function renderThumbnail($image_style, $link_url) {
   public function downloadThumbnail() {
     $local_uri = $this->getLocalThumbnailUri();
     if (!file_exists($local_uri)) {
-      file_prepare_directory($this->thumbsDirectory, FILE_CREATE_DIRECTORY);
+      $this->getFileSystem()->prepareDirectory($this->thumbsDirectory, FileSystemInterface::CREATE_DIRECTORY);
       try {
         $thumbnail = $this->httpClient->request('GET', $this->getRemoteThumbnailUrl());
-        file_unmanaged_save_data((string) $thumbnail->getBody(), $local_uri);
+        $this->getFileSystem()->saveData((string) $thumbnail->getBody(), $local_uri);
       }
       catch (\Exception $e) {
       }
diff --git a/web/modules/video_embed_field/tests/modules/video_embed_field_mock_provider/video_embed_field_mock_provider.info.yml b/web/modules/video_embed_field/tests/modules/video_embed_field_mock_provider/video_embed_field_mock_provider.info.yml
index 04d6d5b4781731dde2266684bcfb4cb787fc5563..9ebc61a57f4d926b87e1376424ddfc9e59741297 100644
--- a/web/modules/video_embed_field/tests/modules/video_embed_field_mock_provider/video_embed_field_mock_provider.info.yml
+++ b/web/modules/video_embed_field/tests/modules/video_embed_field_mock_provider/video_embed_field_mock_provider.info.yml
@@ -5,8 +5,8 @@ hidden: true
 package: Testing
 # core: 8.x
 
-# Information added by Drupal.org packaging script on 2018-04-10
-version: '8.x-2.0'
+# Information added by Drupal.org packaging script on 2019-07-26
+version: '8.x-2.2'
 core: '8.x'
 project: 'video_embed_field'
-datestamp: 1523338087
+datestamp: 1564103587
diff --git a/web/modules/video_embed_field/tests/src/Functional/AdminUserTrait.php b/web/modules/video_embed_field/tests/src/Functional/AdminUserTrait.php
index f9a45a1c8ad6e2434943e1caab64773691dce7c9..a29bc127125eca8b1e0fca06ba9babfb459ce561 100644
--- a/web/modules/video_embed_field/tests/src/Functional/AdminUserTrait.php
+++ b/web/modules/video_embed_field/tests/src/Functional/AdminUserTrait.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\video_embed_field\Functional;
 
-use Drupal\simpletest\UserCreationTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
 
 /**
  * Create admin users.
diff --git a/web/modules/video_embed_field/tests/src/Functional/EntityDisplaySetupTrait.php b/web/modules/video_embed_field/tests/src/Functional/EntityDisplaySetupTrait.php
index d7953e263d0230a10a576021a5533123d96dc4e1..1673e7a69d6604c08708a7259c61bf8f26f21ea7 100644
--- a/web/modules/video_embed_field/tests/src/Functional/EntityDisplaySetupTrait.php
+++ b/web/modules/video_embed_field/tests/src/Functional/EntityDisplaySetupTrait.php
@@ -3,8 +3,8 @@
 namespace Drupal\Tests\video_embed_field\Functional;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\simpletest\ContentTypeCreationTrait;
-use Drupal\simpletest\NodeCreationTrait;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
 
 /**
  * A trait for manipulating entity display.
@@ -63,8 +63,8 @@ protected function setupEntityDisplays() {
       'bundle' => $this->contentTypeName,
       'settings' => [],
     ])->save();
-    $this->entityDisplay = entity_get_display('node', $this->contentTypeName, 'default');
-    $this->entityFormDisplay = entity_get_form_display('node', $this->contentTypeName, 'default');
+    $this->entityDisplay = $this->container->get('entity_display.repository')->getViewDisplay('node', $this->contentTypeName, 'default');
+    $this->entityFormDisplay = $this->container->get('entity_display.repository')->getFormDisplay('node', $this->contentTypeName, 'default');
   }
 
   /**
diff --git a/web/modules/video_embed_field/tests/src/FunctionalJavascript/ColorboxFormatterTest.php b/web/modules/video_embed_field/tests/src/FunctionalJavascript/ColorboxFormatterTest.php
index 685b1a135e3020707351765272ed02598dc3ce43..87c26bbbf96049043f5a3827334dc976e824b2c1 100644
--- a/web/modules/video_embed_field/tests/src/FunctionalJavascript/ColorboxFormatterTest.php
+++ b/web/modules/video_embed_field/tests/src/FunctionalJavascript/ColorboxFormatterTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\video_embed_field\FunctionalJavascript;
 
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\Tests\video_embed_field\Functional\EntityDisplaySetupTrait;
 
 /**
@@ -10,7 +10,7 @@
  *
  * @group video_embed_field
  */
-class ColorboxFormatterTest extends JavascriptTestBase {
+class ColorboxFormatterTest extends WebDriverTestBase {
 
   use EntityDisplaySetupTrait;
 
@@ -52,8 +52,8 @@ public function testColorboxFormatter() {
     $this->getSession()->wait(static::COLORBOX_LAUNCH_TIME);
     $this->assertSession()->elementExists('css', '#colorbox .video-embed-field-responsive-video');
     // Make sure the right library files are loaded on the page.
-    $this->assertSession()->elementContains('css', 'style', 'colorbox/styles/default/colorbox_style.css');
-    $this->assertSession()->elementContains('css', 'style', 'video_embed_field/css/video_embed_field.responsive-video.css');
+    $this->assertSession()->elementExists('css', 'link[href*="colorbox_style.css"]');
+    $this->assertSession()->elementExists('css', 'link[href*="video_embed_field.responsive-video.css"]');
   }
 
 }
diff --git a/web/modules/video_embed_field/tests/src/FunctionalJavascript/LazyLoadFormatterTest.php b/web/modules/video_embed_field/tests/src/FunctionalJavascript/LazyLoadFormatterTest.php
index b7bc67342517a4e44edc619e007219cbe68e31ef..b1de102b85850a5d98668dab445d8c4a2d114730 100644
--- a/web/modules/video_embed_field/tests/src/FunctionalJavascript/LazyLoadFormatterTest.php
+++ b/web/modules/video_embed_field/tests/src/FunctionalJavascript/LazyLoadFormatterTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\video_embed_field\FunctionalJavascript;
 
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\Tests\video_embed_field\Functional\EntityDisplaySetupTrait;
 
 /**
@@ -10,7 +10,7 @@
  *
  * @group video_embed_field
  */
-class LazyLoadFormatterTest extends JavascriptTestBase {
+class LazyLoadFormatterTest extends WebDriverTestBase {
 
   use EntityDisplaySetupTrait;
 
@@ -44,7 +44,7 @@ public function testColorboxFormatter() {
     $this->click('.video-embed-field-lazy');
     $this->assertSession()->elementExists('css', '.video-embed-field-lazy .video-embed-field-responsive-video');
     // Make sure the right library files are loaded on the page.
-    $this->assertSession()->elementContains('css', 'style', 'video_embed_field/css/video_embed_field.responsive-video.css');
+    $this->assertSession()->elementExists('css', 'link[href*="video_embed_field.responsive-video.css"]');
   }
 
 }
diff --git a/web/modules/video_embed_field/tests/src/Kernel/ConstraintTest.php b/web/modules/video_embed_field/tests/src/Kernel/ConstraintTest.php
index 1ed6323eb82c8c1d5024b30679f69b66ed8cad30..b96e432944123dc85ec44467a7994282d11e1d91 100644
--- a/web/modules/video_embed_field/tests/src/Kernel/ConstraintTest.php
+++ b/web/modules/video_embed_field/tests/src/Kernel/ConstraintTest.php
@@ -3,7 +3,7 @@
 namespace Drupal\Tests\video_embed_field\Kernel;
 
 use Drupal\entity_test\Entity\EntityTest;
-use Drupal\simpletest\UserCreationTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
 use Drupal\video_embed_field\Plugin\Validation\Constraint\VideoEmbedConstraint;
 
 /**
diff --git a/web/modules/video_embed_field/tests/src/Kernel/FieldOutputTest.php b/web/modules/video_embed_field/tests/src/Kernel/FieldOutputTest.php
index 5ce00a5221f8917a2f8f56b63fe51ad0ee361fdb..e3bf7f21ce3eb5dc2b6d5dd514a2406efcdad437 100644
--- a/web/modules/video_embed_field/tests/src/Kernel/FieldOutputTest.php
+++ b/web/modules/video_embed_field/tests/src/Kernel/FieldOutputTest.php
@@ -413,7 +413,7 @@ public function renderedFieldTestCases() {
             ],
             [
               '#type' => 'html_tag',
-              '#tag' => 'div',
+              '#tag' => 'button',
               '#attributes' => [
                 'class' => [
                   'video-embed-field-lazy-play',
diff --git a/web/modules/video_embed_field/tests/src/Unit/ProviderUrlParseTest.php b/web/modules/video_embed_field/tests/src/Unit/ProviderUrlParseTest.php
index 57908066f1f9dfd225fa24223697650896efb71e..6f64b970c1c95d500dc554252ab84e41e8ea6138 100644
--- a/web/modules/video_embed_field/tests/src/Unit/ProviderUrlParseTest.php
+++ b/web/modules/video_embed_field/tests/src/Unit/ProviderUrlParseTest.php
@@ -254,15 +254,31 @@ public function youTubeTimeIndexTestCases() {
     return [
       'Simple Timeindex' => [
         'https://www.youtube.com/watch?v=fdbFVWupSsw&t=15',
-        '15',
+        15,
+      ],
+      'Timeindex with Seconds' => [
+        'https://www.youtube.com/watch?v=q25eaJHpXFo&t=15s',
+        15,
+      ],
+      'Timeindex with Minutes and Seconds' => [
+        'https://www.youtube.com/watch?v=q25eaJHpXFo&t=42m25s',
+        2545,
+      ],
+      'Timeindex with Hours, Minutes and Seconds' => [
+        'https://www.youtube.com/watch?v=q25eaJHpXFo&t=1h11m14s',
+        4274,
+      ],
+      'Timeindex with other params' => [
+        'https://www.youtube.com/watch?v=q25eaJHpXFo&t=1h11m14s&some_param=nothing',
+        4274,
       ],
       'No Timeindex' => [
         'https://www.youtube.com/watch?v=fdbFVWupSsw',
-        '0',
+        0,
       ],
       'Invalid Timeindex' => [
         'https://www.youtube.com/watch?v=fdbFVWupSsw&t=time',
-        '0',
+        0,
       ],
     ];
   }
diff --git a/web/modules/video_embed_field/video_embed_field.info.yml b/web/modules/video_embed_field/video_embed_field.info.yml
index 3804cbe15f5029f09c97d6225e09217e33a6d2b6..acd8ed9cd01e14eb3b239ff188de0766d3814081 100644
--- a/web/modules/video_embed_field/video_embed_field.info.yml
+++ b/web/modules/video_embed_field/video_embed_field.info.yml
@@ -6,14 +6,15 @@ package: Video Embed Field
 dependencies:
   - drupal:field
   - drupal:image
+  - drupal:system (>=8.7.0)
 
 test_dependencies:
   - media_entity:media_entity
   - media_entity_embeddable_video:media_entity_embeddable_video
   - colorbox:colorbox
 
-# Information added by Drupal.org packaging script on 2018-04-10
-version: '8.x-2.0'
+# Information added by Drupal.org packaging script on 2019-07-26
+version: '8.x-2.2'
 core: '8.x'
 project: 'video_embed_field'
-datestamp: 1523338087
+datestamp: 1564103587
diff --git a/web/modules/views_ajax_history/js/views_ajax_history.js b/web/modules/views_ajax_history/js/views_ajax_history.js
index 7b08a493fe60fdf808d9becca32ba75a45062266..a5058acde628f9a4e18190076a991d36c30e5165 100644
--- a/web/modules/views_ajax_history/js/views_ajax_history.js
+++ b/web/modules/views_ajax_history/js/views_ajax_history.js
@@ -113,7 +113,7 @@
       }
     });
 
-    url = url.split('?');
+    url = window.location.href.split('?');
     return url[0] + (query.length ? '?' + query.join('&') : '');
   };
 
@@ -204,7 +204,7 @@
   Drupal.Ajax.prototype.beforeSerialize = function (element, options) {
     // Check that we handle a click on a link, not a form submission.
     if (options.data.view_name && element && $(element).is('a')) {
-      addState(options, window.location.href);
+      addState(options, $(element).attr('href'));
     }
 
     // Call the original Drupal method with the right context.
@@ -222,7 +222,7 @@
    *   Object containing AJAX options.
    */
   Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) {
-    if (options.data.view_name) {
+    if (options && options.data && options.data.view_name) {
       var url = original.path + '?' + element.formSerialize();
       var currentQuery = parseQueryString(window.location.href);
 
diff --git a/web/modules/views_ajax_history/views_ajax_history.info.yml b/web/modules/views_ajax_history/views_ajax_history.info.yml
index 5d35456f3f5622cbcbae3fea37420690815e3d92..5595d6d6bbd67d61c9a60aa0cbcff1dfb11e859e 100644
--- a/web/modules/views_ajax_history/views_ajax_history.info.yml
+++ b/web/modules/views_ajax_history/views_ajax_history.info.yml
@@ -6,8 +6,8 @@ package: Views
 dependencies:
   - drupal:views
 
-# Information added by Drupal.org packaging script on 2019-05-31
-version: '8.x-1.1'
+# Information added by Drupal.org packaging script on 2019-07-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'views_ajax_history'
-datestamp: 1559296090
+datestamp: 1562339889
diff --git a/web/modules/views_infinite_scroll/README.txt b/web/modules/views_infinite_scroll/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3c3737aba10aff0f782145594c470d2d1f99dd59
--- /dev/null
+++ b/web/modules/views_infinite_scroll/README.txt
@@ -0,0 +1,55 @@
+CONTENTS OF THIS FILE
+--------------------
+
+* Introduction
+* Requirements
+* Installation
+* Configuration
+* Maintainers
+
+
+INTRODUCTION
+------------
+
+The Views Infinite Scroll module provides a pager, which allows an infinite scroll effect for views. It can work on any view: block, page, etc.
+
+* For a full description of the module visit https://www.drupal.org/project/views_infinite_scroll
+
+* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/views_infinite_scroll
+
+
+REQUIREMENTS
+------------
+
+This module requires no modules outside of Drupal core.
+
+
+INSTALLATION
+------------
+
+* Install the Views Infinite Scroll module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/node/1897420 for further information.
+
+
+CONFIGURATION
+-------------
+
+1. Navigate to Administration > Extend and enable the Views Infinite Scroll module.
+2. Navigate to Administration > Structure > Views and  click + Add view to create a new view, when done creating the new view, click Save and edit.
+3. In the "Page Settings" fieldset, enable "Use a page" by checking the appropriate box. Select "Save and edit".
+4. In the "Pager" fieldset, click on the link after "Use pager", which defaults to Mini.
+5. Select "Infinite Scroll". Apply the changes.
+6. In the field set "Infinite Scroll Options" edit the button text.
+7. Check the "Automatically Load Content" box if the functionality of automatically loading subsequent pages as the user scrolls is desired. 
+8. Select and amount of Items to display. Apply changes.
+9. Save the view.
+
+
+MAINTAINERS
+-----------
+
+* Sam Becker (Sam152) - https://www.drupal.org/u/sam152
+* Remon Georgy (Remon) - https://www.drupal.org/u/remon
+
+Supporting organization:
+
+* PreviousNext - https://www.drupal.org/previousnext
diff --git a/web/modules/views_infinite_scroll/js/infinite-scroll.js b/web/modules/views_infinite_scroll/js/infinite-scroll.js
index f42b19529b492fdb90d498433c77065d944c93a6..942a59522635db18b519c1877ad065c041e3f25a 100644
--- a/web/modules/views_infinite_scroll/js/infinite-scroll.js
+++ b/web/modules/views_infinite_scroll/js/infinite-scroll.js
@@ -41,7 +41,7 @@
     $existingPager.removeOnce('infinite-scroll');
 
     var $newRows = $newView.find(contentWrapperSelector).children();
-    var $newPager = $newView.find('.js-pager__items');
+    var $newPager = $newView.find(pagerSelector);
 
     // Add the new rows to existing view.
     view.$view.find(contentWrapperSelector).append($newRows);
diff --git a/web/modules/views_infinite_scroll/src/Plugin/views/pager/InfiniteScroll.php b/web/modules/views_infinite_scroll/src/Plugin/views/pager/InfiniteScroll.php
index e3e294370c12e17a2d843baaf78943a41312c797..fa8f99b41ed79b33d2a4b5d47c09489eae6bb316 100644
--- a/web/modules/views_infinite_scroll/src/Plugin/views/pager/InfiniteScroll.php
+++ b/web/modules/views_infinite_scroll/src/Plugin/views/pager/InfiniteScroll.php
@@ -23,6 +23,16 @@ class InfiniteScroll extends SqlBase {
    * {@inheritdoc}
    */
   public function render($input) {
+    // Replace tokens in the button text.
+    $text = $this->options['views_infinite_scroll']['button_text'];
+    if (!empty($text) && strpos($text, '@') !== FALSE) {
+      $replacements = [
+        '@next_page_count' => $this->getNumberItemsLeft(),
+        '@total' => (int) $this->getTotalItems(),
+      ];
+      $this->options['views_infinite_scroll']['button_text'] = strtr($text, $replacements);
+    }
+
     return [
       '#theme' => $this->themeFunctions(),
       '#options' => $this->options['views_infinite_scroll'],
@@ -80,6 +90,14 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
         '#type' => 'textfield',
         '#title' => $this->t('Button Text'),
         '#default_value' => $options['button_text'],
+        '#description' => [
+          '#theme' => 'item_list',
+          '#items' => [
+            '@next_page_count -- the next page record count',
+            '@total -- the total amount of results returned from the view',
+          ],
+          '#prefix' => $this->t('The following tokens are supported:'),
+        ],
       ],
       'automatically_load_content' => [
         '#type' => 'checkbox',
@@ -90,4 +108,25 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     ];
   }
 
+  /**
+   * Returns the number of items in the next page.
+   *
+   * @return int
+   *   The number of items in the next page.
+   */
+  protected function getNumberItemsLeft() {
+    $items_per_page = (int) $this->view->getItemsPerPage();
+    $total = (int) $this->getTotalItems();
+    $current_page = (int) $this->getCurrentPage() + 1;
+
+    // Default to the pager amount.
+    $next_page_count = $items_per_page;
+    // Calculate the remaining items if we are at the 2nd to last page.
+    if ($current_page >= ceil($total / $items_per_page) - 1) {
+      $next_page_count = $total - ($current_page * $items_per_page);
+      return $next_page_count;
+    }
+    return $next_page_count;
+  }
+
 }
diff --git a/web/modules/views_infinite_scroll/tests/src/FunctionalJavascript/InfiniteScrollTest.php b/web/modules/views_infinite_scroll/tests/src/FunctionalJavascript/InfiniteScrollTest.php
index 3ffa73bc0daa09789aa2ef0146f5b6418e39cf32..1a0f43df429145d83d6a4f08b5b4a6b14011d5e0 100644
--- a/web/modules/views_infinite_scroll/tests/src/FunctionalJavascript/InfiniteScrollTest.php
+++ b/web/modules/views_infinite_scroll/tests/src/FunctionalJavascript/InfiniteScrollTest.php
@@ -2,9 +2,9 @@
 
 namespace Drupal\Tests\views_infinite_scroll\FunctionalJavascript;
 
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
-use Drupal\simpletest\ContentTypeCreationTrait;
-use Drupal\simpletest\NodeCreationTrait;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
 use Drupal\views\Entity\View;
 
 /**
@@ -12,7 +12,7 @@
  *
  * @group views_infinite_scroll
  */
-class InfiniteScrollTest extends JavascriptTestBase {
+class InfiniteScrollTest extends WebDriverTestBase {
 
   use NodeCreationTrait;
   use ContentTypeCreationTrait;
@@ -20,18 +20,13 @@ class InfiniteScrollTest extends JavascriptTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = [
+  protected static $modules = [
     'views',
     'views_ui',
     'views_infinite_scroll',
     'node',
   ];
 
-  /**
-   * How long to wait for AJAX requests to complete.
-   */
-  const ajaxWaitDelay = 500;
-
   /**
    * {@inheritdoc}
    */
@@ -40,7 +35,8 @@ protected function setUp() {
     $this->createContentType([
       'type' => 'page',
     ]);
-    foreach (range(0, 10) as $i) {
+    // Create 11 nodes.
+    for ($i = 1; $i <= 11; $i++) {
       $this->createNode([
         'status' => TRUE,
         'type' => 'page',
@@ -60,7 +56,7 @@ public function testInfiniteScroll() {
     $this->drupalGet('click-to-load');
     $this->assertTotalNodes(3);
     $this->getSession()->getPage()->clickLink('Load More');
-    $this->getSession()->wait(static::ajaxWaitDelay);
+    $this->assertSession()->waitForElement('css', '.node--type-page:nth-child(4)');
     $this->assertTotalNodes(6);
 
     // Test the view automatically loading.
@@ -72,8 +68,18 @@ public function testInfiniteScroll() {
     $this->drupalGet('automatic-load');
     $this->assertTotalNodes(3);
     $this->scrollTo(500);
-    $this->getSession()->wait(static::ajaxWaitDelay);
+    $this->assertSession()->waitForElement('css', '.node--type-page:nth-child(4)');
     $this->assertTotalNodes(6);
+
+    // Test @next_page_count and @total token.
+    $this->createView('next-page-count', [
+      'button_text' => 'Load @next_page_count more of @total',
+      'automatically_load_content' => FALSE,
+    ], 6);
+    $this->drupalGet('next-page-count');
+    $this->getSession()->getPage()->clickLink('Load 5 more of 11');
+    $this->assertSession()->waitForElement('css', '.node--type-page:nth-child(7)');
+    $this->assertTotalNodes(11);
   }
 
   /**
@@ -103,8 +109,10 @@ protected function scrollTo($pixels) {
    *   The path for the view.
    * @param array $settings
    *   The VIS settings.
+   * @param int $items_per_page
+   *   The number of items per page to display.
    */
-  protected function createView($path, $settings) {
+  protected function createView($path, $settings, $items_per_page = 3) {
     View::create([
       'label' => 'VIS Test',
       'id' => $this->randomMachineName(),
@@ -123,7 +131,7 @@ protected function createView($path, $settings) {
             'pager' => [
               'type' => 'infinite_scroll',
               'options' => [
-                'items_per_page' => 3,
+                'items_per_page' => $items_per_page,
                 'offset' => 0,
                 'views_infinite_scroll' => $settings,
               ],
diff --git a/web/modules/views_infinite_scroll/views_infinite_scroll.info.yml b/web/modules/views_infinite_scroll/views_infinite_scroll.info.yml
index f75ea446432a5ea9d93993a899056eb604209bb4..649d613e15f93a9526c5338d9887d6e121410880 100644
--- a/web/modules/views_infinite_scroll/views_infinite_scroll.info.yml
+++ b/web/modules/views_infinite_scroll/views_infinite_scroll.info.yml
@@ -5,10 +5,10 @@ package: Views
 # core: 8.x
 
 dependencies:
- - views
+ - drupal:views
 
-# Information added by Drupal.org packaging script on 2017-08-19
-version: '8.x-1.5'
+# Information added by Drupal.org packaging script on 2019-07-01
+version: '8.x-1.6'
 core: '8.x'
 project: 'views_infinite_scroll'
-datestamp: 1503109450
+datestamp: 1561996387
diff --git a/web/modules/views_infinite_scroll/views_infinite_scroll.libraries.yml b/web/modules/views_infinite_scroll/views_infinite_scroll.libraries.yml
index abe5b2993fc6e59ba5e3f88c824be9d961ebdfe8..249790b406f2a115c977af5b39c1db1993e6b941 100644
--- a/web/modules/views_infinite_scroll/views_infinite_scroll.libraries.yml
+++ b/web/modules/views_infinite_scroll/views_infinite_scroll.libraries.yml
@@ -4,5 +4,7 @@ views-infinite-scroll:
     js/infinite-scroll.js : {}
   dependencies:
     - core/jquery
+    - core/jquery.once
     - core/drupal
     - core/drupal.debounce
+    - views/views.ajax
diff --git a/web/modules/views_infinite_scroll/views_infinite_scroll.module b/web/modules/views_infinite_scroll/views_infinite_scroll.module
index 290e446935d60c6a0c32107586a94fda8c7bdb24..c16914e8bf6c2914aa02e61194390ff228289a5d 100644
--- a/web/modules/views_infinite_scroll/views_infinite_scroll.module
+++ b/web/modules/views_infinite_scroll/views_infinite_scroll.module
@@ -22,7 +22,7 @@ function views_infinite_scroll_preprocess_views_infinite_scroll_pager(&$vars) {
     $options = array(
       'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
     );
-    $vars['items']['next']['href'] = Url::fromRoute('<current>', [], $options);
+    $vars['items']['next']['href'] = Url::fromRoute('<none>', [], $options);
   }
   $vars['#cache']['contexts'][] = 'url.query_args';
   $vars['attributes'] = new Attribute([
@@ -40,12 +40,16 @@ function views_infinite_scroll_preprocess_views_view(&$vars) {
   if ($view->getDisplay()->isPagerEnabled() && !empty($vars['rows'])) {
     $pager = $view->getPager();
     if ($pager && $pager instanceof InfiniteScroll) {
-      $vars['rows']['#theme_wrappers'] = ['container'];
-      $vars['rows']['#attributes'] = [
-        'data-drupal-views-infinite-scroll-content-wrapper' => TRUE,
-        'class' => [
-          'views-infinite-scroll-content-wrapper',
-          'clearfix',
+      if (!isset($vars['rows']['#theme_wrappers'])) {
+        $vars['rows']['#theme_wrappers'] = [];
+      }
+      $vars['rows']['#theme_wrappers']['container'] = [
+        '#attributes' => [
+          'data-drupal-views-infinite-scroll-content-wrapper' => TRUE,
+          'class' => [
+            'views-infinite-scroll-content-wrapper',
+            'clearfix',
+          ],
         ],
       ];
     }