diff --git a/composer.json b/composer.json
index f1e6d27d65ecb5ea552cd5a37b11ae9219dcdba8..b52660f43fd37fbaeb508afa10ed24b62ccbd35a 100644
--- a/composer.json
+++ b/composer.json
@@ -106,7 +106,7 @@
         "drupal/console": "1.9.7",
         "drupal/content_access": "1.0-alpha1",
         "drupal/core-composer-scaffold": "^9.0",
-        "drupal/core-recommended": "8.9.10",
+        "drupal/core-recommended": "8.9.13",
         "drupal/crop": "2.1",
         "drupal/ctools": "3.4",
         "drupal/devel": "2.0",
@@ -271,7 +271,7 @@
         "patches": {
             "drupal/core": {
                 "2799049": "patches/role_based_email_access-2799049-d87.patch",
-                "2862291": "https://www.drupal.org/files/issues/2019-07-02/2862291-21.patch",
+                "2862291": "https://www.drupal.org/files/issues/2020-12-08/2862291-29.patch",
                 "2949017": "https://www.drupal.org/files/issues/2019-12-12/2949017-59.patch"
             },
             "drupal/addtocalendar": {
diff --git a/composer.lock b/composer.lock
index 62e3c427618a95bcc3169cb340908712a7c41bb1..61ba46e3340f5d5146f8efee7f0f5e592cb30679 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": "5d5f9a310b1ba9a78bdeea9d9f10d611",
+    "content-hash": "026a0ebd2567a6fed694c15d5f98fe44",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -3361,16 +3361,16 @@
         },
         {
             "name": "drupal/core",
-            "version": "8.9.10",
+            "version": "8.9.13",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core.git",
-                "reference": "e725c01cdf6fb6d8b330a27fa75caab91034805a"
+                "reference": "a53db77b55a035453d7229e0c3069f8591cb4cb6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core/zipball/e725c01cdf6fb6d8b330a27fa75caab91034805a",
-                "reference": "e725c01cdf6fb6d8b330a27fa75caab91034805a",
+                "url": "https://api.github.com/repos/drupal/core/zipball/a53db77b55a035453d7229e0c3069f8591cb4cb6",
+                "reference": "a53db77b55a035453d7229e0c3069f8591cb4cb6",
                 "shasum": ""
             },
             "require": {
@@ -3397,8 +3397,8 @@
                 "laminas/laminas-diactoros": "^1.8",
                 "laminas/laminas-feed": "^2.12",
                 "masterminds/html5": "^2.1",
-                "pear/archive_tar": "^1.4.11",
-                "php": ">=7.0.8",
+                "pear/archive_tar": "^1.4.12",
+                "php": "^7.0.8",
                 "psr/log": "^1.0",
                 "stack/builder": "^1.0",
                 "symfony-cmf/routing": "^1.4",
@@ -3568,7 +3568,7 @@
                 },
                 "patches_applied": {
                     "2799049": "patches/role_based_email_access-2799049-d87.patch",
-                    "2862291": "https://www.drupal.org/files/issues/2019-07-02/2862291-21.patch",
+                    "2862291": "https://www.drupal.org/files/issues/2020-12-08/2862291-29.patch",
                     "2949017": "https://www.drupal.org/files/issues/2019-12-12/2949017-59.patch"
                 }
             },
@@ -3593,7 +3593,7 @@
                 "GPL-2.0-or-later"
             ],
             "description": "Drupal is an open source content management platform powering millions of websites and applications.",
-            "time": "2020-11-26T01:49:15+00:00"
+            "time": "2021-01-19T23:11:00+00:00"
         },
         {
             "name": "drupal/core-composer-scaffold",
@@ -3644,16 +3644,16 @@
         },
         {
             "name": "drupal/core-recommended",
-            "version": "8.9.10",
+            "version": "8.9.13",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core-recommended.git",
-                "reference": "106e2a3e6f00f8867d1867e9d7b1376961a264f7"
+                "reference": "7a940fd5b64d2b22366680e2a60d96bf2c10089d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/106e2a3e6f00f8867d1867e9d7b1376961a264f7",
-                "reference": "106e2a3e6f00f8867d1867e9d7b1376961a264f7",
+                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/7a940fd5b64d2b22366680e2a60d96bf2c10089d",
+                "reference": "7a940fd5b64d2b22366680e2a60d96bf2c10089d",
                 "shasum": ""
             },
             "require": {
@@ -3665,7 +3665,7 @@
                 "doctrine/common": "v2.7.3",
                 "doctrine/inflector": "v1.2.0",
                 "doctrine/lexer": "1.0.2",
-                "drupal/core": "8.9.10",
+                "drupal/core": "8.9.13",
                 "easyrdf/easyrdf": "0.9.1",
                 "egulias/email-validator": "2.1.17",
                 "guzzlehttp/guzzle": "6.5.4",
@@ -3678,7 +3678,7 @@
                 "laminas/laminas-zendframework-bridge": "1.0.4",
                 "masterminds/html5": "2.3.0",
                 "paragonie/random_compat": "v9.99.99",
-                "pear/archive_tar": "1.4.11",
+                "pear/archive_tar": "1.4.12",
                 "pear/console_getopt": "v1.4.3",
                 "pear/pear-core-minimal": "v1.10.10",
                 "pear/pear_exception": "v1.0.1",
@@ -3722,7 +3722,7 @@
                 "GPL-2.0-or-later"
             ],
             "description": "Locked core dependencies; require this project INSTEAD OF drupal/core.",
-            "time": "2020-11-26T01:49:15+00:00"
+            "time": "2021-01-19T23:11:00+00:00"
         },
         {
             "name": "drupal/crop",
@@ -10419,16 +10419,16 @@
         },
         {
             "name": "pear/archive_tar",
-            "version": "1.4.11",
+            "version": "1.4.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/pear/Archive_Tar.git",
-                "reference": "17d355cb7d3c4ff08e5729f29cd7660145208d9d"
+                "reference": "19bb8e95490d3e3ad92fcac95500ca80bdcc7495"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/17d355cb7d3c4ff08e5729f29cd7660145208d9d",
-                "reference": "17d355cb7d3c4ff08e5729f29cd7660145208d9d",
+                "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/19bb8e95490d3e3ad92fcac95500ca80bdcc7495",
+                "reference": "19bb8e95490d3e3ad92fcac95500ca80bdcc7495",
                 "shasum": ""
             },
             "require": {
@@ -10481,7 +10481,17 @@
                 "archive",
                 "tar"
             ],
-            "time": "2020-11-19T22:10:24+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/mrook",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.patreon.com/michielrook",
+                    "type": "patreon"
+                }
+            ],
+            "time": "2021-01-18T19:32:54+00:00"
         },
         {
             "name": "pear/console_getopt",
@@ -13352,12 +13362,12 @@
             "version": "1.9.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/webmozart/assert.git",
+                "url": "https://github.com/webmozarts/assert.git",
                 "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
+                "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
                 "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
                 "shasum": ""
             },
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 9bd9a9a89efda60ab5dc518491cc43447b712783..abcc40928a55ff730935ae9ab33d07dcf46cc47e 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -3468,17 +3468,17 @@
     },
     {
         "name": "drupal/core",
-        "version": "8.9.10",
-        "version_normalized": "8.9.10.0",
+        "version": "8.9.13",
+        "version_normalized": "8.9.13.0",
         "source": {
             "type": "git",
             "url": "https://github.com/drupal/core.git",
-            "reference": "e725c01cdf6fb6d8b330a27fa75caab91034805a"
+            "reference": "a53db77b55a035453d7229e0c3069f8591cb4cb6"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/drupal/core/zipball/e725c01cdf6fb6d8b330a27fa75caab91034805a",
-            "reference": "e725c01cdf6fb6d8b330a27fa75caab91034805a",
+            "url": "https://api.github.com/repos/drupal/core/zipball/a53db77b55a035453d7229e0c3069f8591cb4cb6",
+            "reference": "a53db77b55a035453d7229e0c3069f8591cb4cb6",
             "shasum": ""
         },
         "require": {
@@ -3505,8 +3505,8 @@
             "laminas/laminas-diactoros": "^1.8",
             "laminas/laminas-feed": "^2.12",
             "masterminds/html5": "^2.1",
-            "pear/archive_tar": "^1.4.11",
-            "php": ">=7.0.8",
+            "pear/archive_tar": "^1.4.12",
+            "php": "^7.0.8",
             "psr/log": "^1.0",
             "stack/builder": "^1.0",
             "symfony-cmf/routing": "^1.4",
@@ -3645,7 +3645,7 @@
             "drupal/workflows": "self.version",
             "drupal/workspaces": "self.version"
         },
-        "time": "2020-11-26T01:49:15+00:00",
+        "time": "2021-01-19T23:11:00+00:00",
         "type": "drupal-core",
         "extra": {
             "drupal-scaffold": {
@@ -3677,7 +3677,7 @@
             },
             "patches_applied": {
                 "2799049": "patches/role_based_email_access-2799049-d87.patch",
-                "2862291": "https://www.drupal.org/files/issues/2019-07-02/2862291-21.patch",
+                "2862291": "https://www.drupal.org/files/issues/2020-12-08/2862291-29.patch",
                 "2949017": "https://www.drupal.org/files/issues/2019-12-12/2949017-59.patch"
             }
         },
@@ -3755,17 +3755,17 @@
     },
     {
         "name": "drupal/core-recommended",
-        "version": "8.9.10",
-        "version_normalized": "8.9.10.0",
+        "version": "8.9.13",
+        "version_normalized": "8.9.13.0",
         "source": {
             "type": "git",
             "url": "https://github.com/drupal/core-recommended.git",
-            "reference": "106e2a3e6f00f8867d1867e9d7b1376961a264f7"
+            "reference": "7a940fd5b64d2b22366680e2a60d96bf2c10089d"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/drupal/core-recommended/zipball/106e2a3e6f00f8867d1867e9d7b1376961a264f7",
-            "reference": "106e2a3e6f00f8867d1867e9d7b1376961a264f7",
+            "url": "https://api.github.com/repos/drupal/core-recommended/zipball/7a940fd5b64d2b22366680e2a60d96bf2c10089d",
+            "reference": "7a940fd5b64d2b22366680e2a60d96bf2c10089d",
             "shasum": ""
         },
         "require": {
@@ -3777,7 +3777,7 @@
             "doctrine/common": "v2.7.3",
             "doctrine/inflector": "v1.2.0",
             "doctrine/lexer": "1.0.2",
-            "drupal/core": "8.9.10",
+            "drupal/core": "8.9.13",
             "easyrdf/easyrdf": "0.9.1",
             "egulias/email-validator": "2.1.17",
             "guzzlehttp/guzzle": "6.5.4",
@@ -3790,7 +3790,7 @@
             "laminas/laminas-zendframework-bridge": "1.0.4",
             "masterminds/html5": "2.3.0",
             "paragonie/random_compat": "v9.99.99",
-            "pear/archive_tar": "1.4.11",
+            "pear/archive_tar": "1.4.12",
             "pear/console_getopt": "v1.4.3",
             "pear/pear-core-minimal": "v1.10.10",
             "pear/pear_exception": "v1.0.1",
@@ -3828,7 +3828,7 @@
         "conflict": {
             "webflo/drupal-core-strict": "*"
         },
-        "time": "2020-11-26T01:49:15+00:00",
+        "time": "2021-01-19T23:11:00+00:00",
         "type": "metapackage",
         "notification-url": "https://packagist.org/downloads/",
         "license": [
@@ -10752,17 +10752,17 @@
     },
     {
         "name": "pear/archive_tar",
-        "version": "1.4.11",
-        "version_normalized": "1.4.11.0",
+        "version": "1.4.12",
+        "version_normalized": "1.4.12.0",
         "source": {
             "type": "git",
             "url": "https://github.com/pear/Archive_Tar.git",
-            "reference": "17d355cb7d3c4ff08e5729f29cd7660145208d9d"
+            "reference": "19bb8e95490d3e3ad92fcac95500ca80bdcc7495"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/17d355cb7d3c4ff08e5729f29cd7660145208d9d",
-            "reference": "17d355cb7d3c4ff08e5729f29cd7660145208d9d",
+            "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/19bb8e95490d3e3ad92fcac95500ca80bdcc7495",
+            "reference": "19bb8e95490d3e3ad92fcac95500ca80bdcc7495",
             "shasum": ""
         },
         "require": {
@@ -10777,7 +10777,7 @@
             "ext-xz": "Lzma2 compression support.",
             "ext-zlib": "Gzip compression support."
         },
-        "time": "2020-11-19T22:10:24+00:00",
+        "time": "2021-01-18T19:32:54+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -10816,6 +10816,16 @@
         "keywords": [
             "archive",
             "tar"
+        ],
+        "funding": [
+            {
+                "url": "https://github.com/mrook",
+                "type": "github"
+            },
+            {
+                "url": "https://www.patreon.com/michielrook",
+                "type": "patreon"
+            }
         ]
     },
     {
@@ -13784,12 +13794,12 @@
         "version_normalized": "1.9.1.0",
         "source": {
             "type": "git",
-            "url": "https://github.com/webmozart/assert.git",
+            "url": "https://github.com/webmozarts/assert.git",
             "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
+            "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
             "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
             "shasum": ""
         },
diff --git a/vendor/pear/archive_tar/.github/FUNDING.yml b/vendor/pear/archive_tar/.github/FUNDING.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4a0f72b64112633deaec672c0fb66586638dcb2a
--- /dev/null
+++ b/vendor/pear/archive_tar/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: [mrook]
+patreon: michielrook
diff --git a/vendor/pear/archive_tar/.github/dependabot.yml b/vendor/pear/archive_tar/.github/dependabot.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a51bb0bd4a19a46c303e04b92bb8e36f6bab9977
--- /dev/null
+++ b/vendor/pear/archive_tar/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+  - package-ecosystem: "composer" # See documentation for possible values
+    directory: "/" # Location of package manifests
+    schedule:
+      interval: "daily"
diff --git a/vendor/pear/archive_tar/.github/workflows/build.yml b/vendor/pear/archive_tar/.github/workflows/build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b972caea6bbe1cffa3512293daac5aa5a9f446bf
--- /dev/null
+++ b/vendor/pear/archive_tar/.github/workflows/build.yml
@@ -0,0 +1,41 @@
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+jobs:
+  test:
+    runs-on: ${{ matrix.operating-system }}
+    strategy:
+      fail-fast: true
+      matrix:
+        operating-system: [ ubuntu-latest ]
+        php: [ '5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0' ]
+        dependencies: [ 'locked' ]
+
+    name: PHP ${{ matrix.php }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies
+
+    steps:
+      - uses: actions/checkout@v2
+        name: Checkout repository
+
+      - name: Setup PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: ${{ matrix.php }}
+
+      - uses: ramsey/composer-install@v1
+        with:
+          dependency-versions: ${{ matrix.dependencies }}
+
+      - name: Install PEAR
+        run: |
+          sudo apt-get install php-pear
+
+      - name: Run tests
+        run: |
+          sudo pear install -f package.xml
+          pear version
+          pear run-tests -qr tests/ || cat run-tests.log
+          for i in `find tests/ -name '*.out'`; do echo "$i"; cat "$i"; done
diff --git a/vendor/pear/archive_tar/Archive/Tar.php b/vendor/pear/archive_tar/Archive/Tar.php
index 92710741c542eacf7570adf3751ab2714b3bd8bc..76771d5b5481cfe20793ef4e74a2fcd111184d08 100644
--- a/vendor/pear/archive_tar/Archive/Tar.php
+++ b/vendor/pear/archive_tar/Archive/Tar.php
@@ -1397,16 +1397,20 @@ public function _writeHeader($p_filename, $p_stored_filename)
 
         $v_magic = 'ustar ';
         $v_version = ' ';
+        $v_uname = '';
+        $v_gname = '';
 
         if (function_exists('posix_getpwuid')) {
             $userinfo = posix_getpwuid($v_info[4]);
             $groupinfo = posix_getgrgid($v_info[5]);
 
-            $v_uname = $userinfo['name'];
-            $v_gname = $groupinfo['name'];
-        } else {
-            $v_uname = '';
-            $v_gname = '';
+            if (isset($userinfo['name'])) {
+                $v_uname = $userinfo['name'];
+            }
+
+            if (isset($groupinfo['name'])) {
+                $v_gname = $groupinfo['name'];
+            }
         }
 
         $v_devmajor = '';
@@ -2120,6 +2124,14 @@ public function _extractList(
                             }
                         }
                     } elseif ($v_header['typeflag'] == "2") {
+                        if (strpos(realpath(dirname($v_header['link'])), realpath($p_path)) !== 0) {
+                            $this->_error(
+                                 'Out-of-path file extraction {'
+                                 . $v_header['filename'] . ' --> ' .
+                                 $v_header['link'] . '}'
+                            );
+                            return false;
+                        }
                         if (!$p_symlinks) {
                             $this->_warning('Symbolic links are not allowed. '
                                 . 'Unable to extract {'
diff --git a/vendor/pear/archive_tar/package.xml b/vendor/pear/archive_tar/package.xml
index 6edf4fd10351839d0e56b237b4f8784b1fd013ce..5da8ee884a02d722783853e5d8d530ac00a662bc 100644
--- a/vendor/pear/archive_tar/package.xml
+++ b/vendor/pear/archive_tar/package.xml
@@ -32,10 +32,10 @@ Also Lzma2 compressed archives are supported with xz extension.</description>
   <email>stig@php.net</email>
   <active>no</active>
  </helper>
- <date>2020-11-19</date>
- <time>22:06:48</time>
+ <date>2021-01-18</date>
+ <time>19:29:56</time>
  <version>
-  <release>1.4.11</release>
+  <release>1.4.12</release>
   <api>1.4.0</api>
  </version>
  <stability>
@@ -44,8 +44,7 @@ Also Lzma2 compressed archives are supported with xz extension.</description>
  </stability>
  <license uri="http://www.opensource.org/licenses/bsd-license.php">New BSD License</license>
  <notes>
-* Fix Bug #27002: Filename manipulation vulnerabilities (CVE-2020-28948 /
-   CVE-2020-28949) [mrook]
+* Fix Bug #27008: Symlink out-of-path write vulnerability (CVE-2020-36193) [mrook]
  </notes>
  <contents>
   <dir name="/">
@@ -75,7 +74,22 @@ Also Lzma2 compressed archives are supported with xz extension.</description>
  </dependencies>
  <phprelease />
  <changelog>
-   <release>
+  <release>
+   <version>
+    <release>1.4.11</release>
+    <api>1.4.0</api>
+   </version>
+   <stability>
+    <release>stable</release>
+    <api>stable</api>
+   </stability>
+   <date>2020-11-19</date>
+   <license uri="http://www.opensource.org/licenses/bsd-license.php">New BSD License</license>
+   <notes>
+* Fix Bug #27002: Filename manipulation vulnerabilities (CVE-2020-28948 / CVE-2020-28949) [mrook]
+   </notes>
+  </release>
+  <release>
     <version>
      <release>1.4.10</release>
      <api>1.4.0</api>
diff --git a/vendor/pear/archive_tar/tests/out_of_path_fnames.phpt b/vendor/pear/archive_tar/tests/out_of_path_fnames.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..a26100c81223950ed527d42d40ee7f63e1ac6be1
--- /dev/null
+++ b/vendor/pear/archive_tar/tests/out_of_path_fnames.phpt
@@ -0,0 +1,18 @@
+--TEST--
+tests writes to out-of-path filenames
+--SKIPIF--
+--FILE--
+<?php
+require_once dirname(__FILE__) . '/setup.php.inc';
+$tar = new Archive_Tar(dirname(__FILE__) . '/out_of_path_symlink.tar');
+$tar->extract();
+$phpunit->assertErrors(array(array('package' => 'PEAR_Error', 'message' => "Out-of-path file extraction {symlink --> /tmp/}")), 'after 1');
+$phpunit->assertFileNotExists('symlink/whatever-filename', 'Out-of-path filename should not have succeeded');
+echo 'tests done';
+?>
+--CLEAN--
+<?php
+@unlink("symlink");
+?>
+--EXPECT--
+tests done
diff --git a/web/core/MAINTAINERS.txt b/web/core/MAINTAINERS.txt
index 9383214f5664c63e764dd7d9d44e264b1bd76b1b..153666a745188583465dedb7d269a0379dd6b120 100644
--- a/web/core/MAINTAINERS.txt
+++ b/web/core/MAINTAINERS.txt
@@ -541,7 +541,6 @@ participate in mentoring.
 - Lucas Hedding 'heddn' https://www.drupal.org/u/heddn
 - Tara King 'sparklingrobots' https://www.drupal.org/u/sparklingrobots
 - Rachel Lawson 'rachel_norfolk' https://www.drupal.org/u/rachel_norfolk
-- Valery Lourie 'valthebald' https://www.drupal.org/u/valthebald
 - Elli Ludwigson 'ekl1773' https://www.drupal.org/u/ekl1773
 - Jess Myrbo 'xjm' https://www.drupal.org/u/xjm
 - Matthew Radcliffe 'mradcliffe' https://www.drupal.org/u/mradcliffe
diff --git a/web/core/PATCHES.txt b/web/core/PATCHES.txt
index 4edba0f65b17a1582ee4c73ae188c4ef842dea96..19c8c45be314563d452dcae910e886132cd89fc5 100644
--- a/web/core/PATCHES.txt
+++ b/web/core/PATCHES.txt
@@ -6,7 +6,7 @@ Source: patches/role_based_email_access-2799049-d87.patch
 
 
 2862291
-Source: https://www.drupal.org/files/issues/2019-07-02/2862291-21.patch
+Source: https://www.drupal.org/files/issues/2020-12-08/2862291-29.patch
 
 
 2949017
diff --git a/web/core/composer.json b/web/core/composer.json
index fb70c051cf090d12a41df6335c619235af7112da..f7ba3f2403b0503be831a91c460d6b20b06ccf99 100644
--- a/web/core/composer.json
+++ b/web/core/composer.json
@@ -17,7 +17,7 @@
         "ext-SPL": "*",
         "ext-tokenizer": "*",
         "ext-xml": "*",
-        "php": ">=7.0.8",
+        "php": "^7.0.8",
         "symfony/class-loader": "~3.4.0",
         "symfony/console": "~3.4.0",
         "symfony/dependency-injection": "~3.4.26",
@@ -46,7 +46,7 @@
         "laminas/laminas-diactoros": "^1.8",
         "composer/semver": "^1.0",
         "asm89/stack-cors": "^1.1",
-        "pear/archive_tar": "^1.4.11",
+        "pear/archive_tar": "^1.4.12",
         "psr/log": "^1.0"
     },
     "conflict": {
diff --git a/web/core/core.api.php b/web/core/core.api.php
index 23f153f2c0858283b6186b01e35b781ca406fd8d..5c0c86fb208e0e93639cfe76ef6a99d2295d242a 100644
--- a/web/core/core.api.php
+++ b/web/core/core.api.php
@@ -64,10 +64,9 @@
  *
  * @section more_info Further information
  *
- * - @link https://api.drupal.org/api/drupal/groups/8 All topics @endlink
  * - @link https://www.drupal.org/project/examples Examples project (sample modules) @endlink
  * - @link https://www.drupal.org/list-changes API change notices @endlink
- * - @link https://www.drupal.org/developing/api/8 Drupal 8 API longer references @endlink
+ * - @link https://www.drupal.org/docs/drupal-apis Drupal API longer references @endlink
  */
 
 /**
@@ -208,7 +207,7 @@
  * information. See the @link info_types Information types topic @endlink for
  * an overview of the different types of information. The sections below have
  * more information about the configuration API; see
- * https://www.drupal.org/developing/api/8/configuration for more details.
+ * https://www.drupal.org/docs/drupal-apis/configuration-api for more details.
  *
  * @section sec_storage Configuration storage
  * In Drupal, there is a concept of the "active" configuration, which is the
@@ -576,9 +575,9 @@
  *
  * Cache contexts are services tagged with 'cache.context', whose classes
  * implement \Drupal\Core\Cache\Context\CacheContextInterface. See
- * https://www.drupal.org/developing/api/8/cache/contexts for more information
- * on cache contexts, including a list of the contexts that exist in Drupal
- * core, and information on how to define your own contexts. See the
+ * https://www.drupal.org/docs/drupal-apis/cache-api/cache-contexts for more
+ * information on cache contexts, including a list of the contexts that exist in
+ * Drupal core, and information on how to define your own contexts. See the
  * @link container Services and the Dependency Injection Container @endlink
  * topic for more information about services.
  *
@@ -1242,10 +1241,10 @@
  *   site; CSS files, which alter the styling applied to the HTML; and
  *   JavaScript, Flash, images, and other files. For more information, see the
  *   @link theme_render Theme system and render API topic @endlink and
- *   https://www.drupal.org/docs/8/theming
+ *   https://www.drupal.org/docs/theming-drupal
  * - Modules: Modules add to or alter the behavior and functionality of Drupal,
  *   by using one or more of the methods listed below. For more information
- *   about creating modules, see https://www.drupal.org/developing/modules/8
+ *   about creating modules, see https://www.drupal.org/docs/creating-custom-modules
  * - Installation profiles: Installation profiles can be used to
  *   create distributions, which are complete specific-purpose packages of
  *   Drupal including additional modules, themes, and data. For more
diff --git a/web/core/core.libraries.yml b/web/core/core.libraries.yml
index a17ce7c0e0575b72516707557107819a3f310b09..e1384470a8b1d6f38ed9b3069f9caf9d08dc812e 100644
--- a/web/core/core.libraries.yml
+++ b/web/core/core.libraries.yml
@@ -21,7 +21,7 @@ classList:
     gpl-compatible: true
   js:
     assets/vendor/classList/classList.min.js: { weight: -21, browsers: { IE: 'lte IE 9', '!IE': false }, minified: true }
-  deprecated: The "%library_id%" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the the native browser implementation instead. See https://www.drupal.org/node/3089511
+  deprecated: The "%library_id%" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the native browser implementation instead. See https://www.drupal.org/node/3089511
 
 ckeditor:
   remote: https://github.com/ckeditor/ckeditor-dev
diff --git a/web/core/drupalci.yml b/web/core/drupalci.yml
index 1cec41cef3d5113061a47147b7514efdb2014e8b..00baeca1871ccf3940a129de882e42e631271db0 100644
--- a/web/core/drupalci.yml
+++ b/web/core/drupalci.yml
@@ -4,15 +4,13 @@
 build:
   assessment:
     validate_codebase:
-      phplint:
-      eslint:
-        # A test must pass eslinting standards check in order to continue processing.
-        halt-on-fail: false
-      phpcs:
-        # phpcs will use core's specified version of Coder.
-        sniff-all-files: false
-        halt-on-fail: false
+      # Core's code quality is checked by container_command.commit_checks.
     testing:
+      # Run code quality checks.
+      container_command.commit-checks:
+        commands:
+          - "core/scripts/dev/commit-code-check.sh --drupalci"
+        halt-on-fail: true
       # run_tests task is executed several times in order of performance speeds.
       # halt-on-fail can be set on the run_tests tasks in order to fail fast.
       # suppress-deprecations is false in order to be alerted to usages of
diff --git a/web/core/install.php b/web/core/install.php
index 8cc3880c81ce9ee89ff8a4243a25b2a580537f49..f3d04220d8dc9d09f7ff037ea99bf71020051490 100644
--- a/web/core/install.php
+++ b/web/core/install.php
@@ -29,6 +29,10 @@
   print 'Your PHP installation is too old. Drupal requires at least PHP 7.0.8. See the <a href="https://www.drupal.org/requirements">system requirements</a> page for more information.';
   exit;
 }
+elseif (version_compare(PHP_VERSION, '8.0', '>=')) {
+  print 'Update to the latest release of Drupal 9 for improved PHP 8 support, or use PHP 7.4. See the <a href="https://www.drupal.org/requirements">system requirements</a> page for more information.';
+  exit;
+}
 
 // Initialize the autoloader.
 $class_loader = require_once $root_path . '/autoload.php';
diff --git a/web/core/lib/Drupal.php b/web/core/lib/Drupal.php
index ee860ae0ac048946e5c9c97b19f9a9a2969d2cb4..f1561990601d60e5a2b18377bd21ddbedb2d7652 100644
--- a/web/core/lib/Drupal.php
+++ b/web/core/lib/Drupal.php
@@ -82,7 +82,7 @@ class Drupal {
   /**
    * The current system version.
    */
-  const VERSION = '8.9.10';
+  const VERSION = '8.9.13';
 
   /**
    * Core API compatibility.
diff --git a/web/core/lib/Drupal/Core/Annotation/ContextDefinition.php b/web/core/lib/Drupal/Core/Annotation/ContextDefinition.php
index 0284e9230eb515d1dcdb1f8e0db72df723c4dd54..695bf1169affb901508519d3102ad87ac1c2c1ca 100644
--- a/web/core/lib/Drupal/Core/Annotation/ContextDefinition.php
+++ b/web/core/lib/Drupal/Core/Annotation/ContextDefinition.php
@@ -3,7 +3,6 @@
 namespace Drupal\Core\Annotation;
 
 use Drupal\Component\Annotation\Plugin;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * @defgroup plugin_context Annotation for context definition
@@ -106,7 +105,7 @@ public function __construct(array $values) {
     // used in the classes they pass to.
     foreach (['label', 'description'] as $key) {
       // @todo Remove this workaround in https://www.drupal.org/node/2362727.
-      if (isset($values[$key]) && $values[$key] instanceof TranslatableMarkup) {
+      if (isset($values[$key]) && $values[$key] instanceof Translation) {
         $values[$key] = (string) $values[$key]->get();
       }
       else {
diff --git a/web/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php b/web/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php
index 990a5b4fe16bbd90145e803c04065fa440e0a964..3223a41badda353829cd603b1415979c9b01da73 100644
--- a/web/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php
+++ b/web/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php
@@ -6,9 +6,9 @@
  * Interface for configuration entities to store third party information.
  *
  * A third party is a module that needs to store tightly coupled information to
- * the configuration entity. For example, a module alters the node type form
- * can use this to store its configuration so that it will be deployed with the
- * node type.
+ * the configuration entity. For example, a module that alters the node type
+ * form can use this to store its configuration so that it will be deployed
+ * with the node type.
  */
 interface ThirdPartySettingsInterface {
 
diff --git a/web/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/web/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index bb052164da20651f64a09f8320b940dfe19d731c..60918bab47de7402895405d8d04702788045ef19 100644
--- a/web/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/web/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -108,7 +108,7 @@ public function __construct(EntityTypeInterface $entity_type, EntityRepositoryIn
     $this->languageManager = $language_manager;
     $this->themeRegistry = $theme_registry ?: \Drupal::service('theme.registry');
     if (!$entity_display_repository) {
-      @trigger_error('Calling EntityViewBuilder::__construct() with the $entity_repository argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
+      @trigger_error('Calling EntityViewBuilder::__construct() with the $entity_display_repository argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
       $entity_display_repository = \Drupal::service('entity_display.repository');
     }
     $this->entityDisplayRepository = $entity_display_repository;
diff --git a/web/core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php b/web/core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
index 1b561ed48ecb7db8c09342f64d830d784eb121cc..f885a0e82342053746c060ebceb96e882a8308ba 100644
--- a/web/core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
+++ b/web/core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
@@ -18,18 +18,17 @@ public static function translateCondition(&$condition, SelectInterface $sql_quer
       $condition['where'] = 'LOWER(' . $sql_query->escapeField($condition['real_field']) . ') ' . $condition['operator'] . ' (';
       $condition['where_args'] = [];
 
-      $n = 1;
       // Only use the array values in case an associative array is passed as an
       // argument following similar pattern in
       // \Drupal\Core\Database\Connection::expandArguments().
-      foreach ($condition['value'] as $value) {
-        $condition['where'] .= 'LOWER(:value' . $n . '),';
-        $condition['where_args'][':value' . $n] = $value;
-        $n++;
+      $where_prefix = str_replace('.', '_', $condition['real_field']);
+      foreach ($condition['value'] as $key => $value) {
+        $where_id = $where_prefix . $key;
+        $condition['where'] .= 'LOWER(:' . $where_id . '),';
+        $condition['where_args'][':' . $where_id] = $value;
       }
       $condition['where'] = trim($condition['where'], ',');
       $condition['where'] .= ')';
-      return;
     }
     parent::translateCondition($condition, $sql_query, $case_sensitive);
   }
diff --git a/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index b3b085ca684988d52c953028a3a47f83a39b068f..017c76f58824a0782557fec6fab199a7f260ad99 100644
--- a/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -2378,7 +2378,11 @@ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $stor
       // A dedicated table only contain rows for actual field values, and no
       // rows for entities where the field is empty. Thus, we can safely
       // enforce 'not null' on the columns for the field's required properties.
-      $data_schema['fields'][$real_name]['not null'] = $properties[$column_name]->isRequired();
+      // Fields can have dynamic properties, so we need to make sure that the
+      // property is statically defined.
+      if (isset($properties[$column_name])) {
+        $data_schema['fields'][$real_name]['not null'] = $properties[$column_name]->isRequired();
+      }
     }
 
     // Add indexes.
diff --git a/web/core/lib/Drupal/Core/File/FileSystem.php b/web/core/lib/Drupal/Core/File/FileSystem.php
index 0c5e9a19db7c59fb9e4aa0d43dd02d45c3220cab..8732645248938a9876e8faee69529728bdb7cca3 100644
--- a/web/core/lib/Drupal/Core/File/FileSystem.php
+++ b/web/core/lib/Drupal/Core/File/FileSystem.php
@@ -535,12 +535,22 @@ public function prepareDirectory(&$directory, $options = self::MODIFY_PERMISSION
     }
 
     if (!is_dir($directory)) {
+      if (!($options & static::CREATE_DIRECTORY)) {
+        return FALSE;
+      }
+
       // Let mkdir() recursively create directories and use the default
       // directory permissions.
-      if ($options & static::CREATE_DIRECTORY) {
-        return @$this->mkdir($directory, NULL, TRUE);
+      $success = @$this->mkdir($directory, NULL, TRUE);
+      if ($success) {
+        return TRUE;
+      }
+      // If the operation failed, check again if the directory was created
+      // by another process/server, only report a failure if not. In this case
+      // we still need to ensure the directory is writable.
+      if (!is_dir($directory)) {
+        return FALSE;
       }
-      return FALSE;
     }
 
     $writable = is_writable($directory);
diff --git a/web/core/lib/Drupal/Core/Form/form.api.php b/web/core/lib/Drupal/Core/Form/form.api.php
index 976c3fdebc866b8f6f9d4d8931678b2cf4f57077..12adb329659c610b42a00b80d7b79c118537c284 100644
--- a/web/core/lib/Drupal/Core/Form/form.api.php
+++ b/web/core/lib/Drupal/Core/Form/form.api.php
@@ -165,7 +165,7 @@ function hook_ajax_render_alter(array &$data) {
  * $form_state->getFormObject()->getEntity().
  *
  * Implementations are responsible for adding cache contexts/tags/max-age as
- * needed. See https://www.drupal.org/developing/api/8/cache.
+ * needed. See https://www.drupal.org/docs/8/api/cache-api/cache-api.
  *
  * In addition to hook_form_alter(), which is called for all forms, there are
  * two more specific form hooks available. The first,
@@ -216,7 +216,7 @@ function hook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_stat
  * Provide a form-specific alteration instead of the global hook_form_alter().
  *
  * Implementations are responsible for adding cache contexts/tags/max-age as
- * needed. See https://www.drupal.org/developing/api/8/cache.
+ * needed. See https://www.drupal.org/docs/8/api/cache-api/cache-api.
  *
  * Modules can implement hook_form_FORM_ID_alter() to modify a specific form,
  * rather than implementing hook_form_alter() and checking the form ID, or
@@ -259,7 +259,7 @@ function hook_form_FORM_ID_alter(&$form, \Drupal\Core\Form\FormStateInterface $f
  * Provide a form-specific alteration for shared ('base') forms.
  *
  * Implementations are responsible for adding cache contexts/tags/max-age as
- * needed. See https://www.drupal.org/developing/api/8/cache.
+ * needed. See https://www.drupal.org/docs/8/api/cache-api/cache-api.
  *
  * By default, when \Drupal::formBuilder()->getForm() is called, Drupal looks
  * for a function with the same name as the form ID, and uses that function to
diff --git a/web/core/lib/Drupal/Core/Language/LanguageInterface.php b/web/core/lib/Drupal/Core/Language/LanguageInterface.php
index 2568cb33f30341bc611f4bf74136c5618a333ba6..9e9907a0cc8a85000396237215b8e9962a47970e 100644
--- a/web/core/lib/Drupal/Core/Language/LanguageInterface.php
+++ b/web/core/lib/Drupal/Core/Language/LanguageInterface.php
@@ -51,6 +51,13 @@ interface LanguageInterface {
    */
   const LANGCODE_SITE_DEFAULT = 'site_default';
 
+  /**
+   * A regex for validating language codes according to W3C specifications.
+   *
+   * @see https://www.w3.org/International/articles/language-tags/
+   */
+  const VALID_LANGCODE_REGEX = '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*';
+
   /**
    * The language state when referring to configurable languages.
    */
diff --git a/web/core/lib/Drupal/Core/Mail/MailInterface.php b/web/core/lib/Drupal/Core/Mail/MailInterface.php
index fbde1b86dc76ceebe2e01e78f90b9dfef16a7829..910cfa9133d37073f3a94aeab525467a0dc3bc41 100644
--- a/web/core/lib/Drupal/Core/Mail/MailInterface.php
+++ b/web/core/lib/Drupal/Core/Mail/MailInterface.php
@@ -15,12 +15,20 @@ interface MailInterface {
    * Formats a message prior to sending.
    *
    * Allows to preprocess, format, and postprocess a mail message before it is
-   * passed to the sending system. By default, all messages may contain HTML and
-   * are converted to plain-text by the Drupal\Core\Mail\Plugin\Mail\PhpMail
-   * implementation. For example, an alternative implementation could override
-   * the default implementation and also sanitize the HTML for usage in a MIME-
-   * encoded email, but still invoking the Drupal\Core\Mail\Plugin\Mail\PhpMail
-   * implementation to generate an alternate plain-text version for sending.
+   * passed to the sending system. The message body is received as an array of
+   * lines that are either strings or objects implementing
+   * \Drupal\Component\Render\MarkupInterface. It must be converted to the
+   * format expected by mail() which is a single string that can be either
+   * plain text or HTML. In the HTML case an alternate plain-text version can
+   * be returned in $message['plain'].
+   *
+   * The conversion process consists of the following steps:
+   * - If the output is HTML then convert any input line that is a string using
+   *   \Drupal\Component\Utility\Html\Html::Escape().
+   * - If the output is plain text then convert any input line that is markup
+   *   using \Drupal\Core\Mail\MailFormatHelper::htmlToText().
+   * - Join the input lines into a single string.
+   * - Wrap long lines using \Drupal\Core\Mail\MailFormatHelper::wrapMail().
    *
    * @param array $message
    *   A message array, as described in hook_mail_alter().
diff --git a/web/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php b/web/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php
index b096ba390c3fa6b04fcd323acc8a6bbe33c7933e..91f70d2f1d9953667d0dd374e43a11ffe0d2ac26 100644
--- a/web/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php
+++ b/web/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Gettext\PoStreamReader;
 use Drupal\Component\Gettext\PoMemoryWriter;
 use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\Language\LanguageInterface;
 
 /**
  * File based string translation.
@@ -106,7 +107,7 @@ protected function getTranslationFilesPattern($langcode = NULL) {
     // The file name matches: drupal-[release version].[language code].po
     // When provided the $langcode is use as language code. If not provided all
     // language codes will match.
-    return '!drupal-[0-9a-z\.-]+\.' . (!empty($langcode) ? preg_quote($langcode, '!') : '[^\.]+') . '\.po$!';
+    return '!drupal-[0-9]+\.[0-9]+\.([0-9]+|x)(-[a-z]+[0-9]*)?\.' . (!empty($langcode) ? preg_quote($langcode, '!') : LanguageInterface::VALID_LANGCODE_REGEX) . '\.po$!';
   }
 
   /**
diff --git a/web/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestContextAwareBlock.php b/web/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestContextAwareBlock.php
index edc66168d3cc55a7015ae5cdef93e53159a9d751..e73167b4234b636274d5df756fddeba150ca0ec8 100644
--- a/web/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestContextAwareBlock.php
+++ b/web/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestContextAwareBlock.php
@@ -14,7 +14,7 @@
  *   admin_label = @Translation("Test context-aware block"),
  *   context_definitions = {
  *     "user" = @ContextDefinition("entity:user", required = FALSE,
- *       constraints = { "NotNull" = {} }
+ *       label = @Translation("User Context"), constraints = { "NotNull" = {} }
  *     ),
  *   }
  * )
diff --git a/web/core/modules/book/src/BookManager.php b/web/core/modules/book/src/BookManager.php
index c11809a4ec17e9fe5d0dd2e9a6a1cec2354984d0..4245b977fd95fc388e2dd64f7aab672f5bab6f12 100644
--- a/web/core/modules/book/src/BookManager.php
+++ b/web/core/modules/book/src/BookManager.php
@@ -233,7 +233,7 @@ public function addFormElements(array $form, FormStateInterface $form_state, Nod
         // The node can become a new book, if it is not one already.
         $options = [$nid => $this->t('- Create a new book -')] + $options;
       }
-      if (!$node->book['bid']) {
+      if (!$node->book['bid'] || $nid === 'new' || $node->book['original_bid'] === 0) {
         // The node is not currently in the hierarchy.
             $options = [0 => $this->t('- None -')] + $options;
         }
diff --git a/web/core/modules/book/src/Form/BookSettingsForm.php b/web/core/modules/book/src/Form/BookSettingsForm.php
index 19edbad09a0d92476af57a43af3718db69668ee4..3a92ba9a82d459d2daf77d0985ea31a831c1dc57 100644
--- a/web/core/modules/book/src/Form/BookSettingsForm.php
+++ b/web/core/modules/book/src/Form/BookSettingsForm.php
@@ -55,7 +55,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $child_type = array_filter($form_state->getValue('book_child_type'));
+    $child_type = $form_state->getValue('book_child_type');
     if ($form_state->isValueEmpty(['book_allowed_types', $child_type])) {
       $form_state->setErrorByName('book_child_type', $this->t('The content type for the %add-child link must be one of those selected as an allowed book outline type.', ['%add-child' => $this->t('Add child page')]));
     }
@@ -75,7 +75,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $this->config('book.settings')
     // Remove unchecked types.
       ->set('allowed_types', $allowed_types)
-      ->set('child_type', array_filter($form_state->getValue('book_child_type')))
+      ->set('child_type', $form_state->getValue('book_child_type'))
       ->save();
 
     parent::submitForm($form, $form_state);
diff --git a/web/core/modules/book/tests/src/Functional/BookTest.php b/web/core/modules/book/tests/src/Functional/BookTest.php
index 24e35e7551a25847a7a165a804792b5d777e7d6b..3733699975621678d9b3d798a37a45bd7e235ac6 100644
--- a/web/core/modules/book/tests/src/Functional/BookTest.php
+++ b/web/core/modules/book/tests/src/Functional/BookTest.php
@@ -644,4 +644,12 @@ public function testBookNavigationBlockOnUnpublishedBook() {
     $this->assertText($this->book->label(), 'Unpublished book with "Show block only on book pages" book navigation settings.');
   }
 
+  /**
+   * Tests that the book settings form can be saved without error.
+   */
+  public function testSettingsForm() {
+    $this->drupalLogin($this->adminUser);
+    $this->drupalPostForm('admin/structure/book/settings', [], 'Save configuration');
+  }
+
 }
diff --git a/web/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php b/web/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
index 7f42954a4e4bfc293e12af0a14eaa526c9beede1..13537a1695f87636a10cd95d41096aa32185cdc7 100644
--- a/web/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
+++ b/web/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
@@ -162,4 +162,26 @@ protected function assertOrderInPage(array $items) {
     $this->assertSame($items, array_values($strings), "Found strings, ordered as: $ordered.");
   }
 
+  /**
+   * Tests book outline AJAX request.
+   */
+  public function testBookAddOutline() {
+    $this->drupalLogin($this->drupalCreateUser(['create book content', 'create new books', 'add content to books']));
+    $this->drupalGet('node/add/book');
+    $assert_session = $this->assertSession();
+    $session = $this->getSession();
+    $page = $session->getPage();
+
+    $page->find('css', '#edit-book')->click();
+    $book_select = $page->findField("book[bid]");
+    $book_select->setValue('new');
+    $assert_session->waitForText('This will be the top-level page in this book.');
+    $assert_session->pageTextContains('This will be the top-level page in this book.');
+    $assert_session->pageTextNotContains('No book selected.');
+    $book_select->setValue(0);
+    $assert_session->waitForText('No book selected.');
+    $assert_session->pageTextContains('No book selected.');
+    $assert_session->pageTextNotContains('This will be the top-level page in this book.');
+  }
+
 }
diff --git a/web/core/modules/contact/src/MessageViewBuilder.php b/web/core/modules/contact/src/MessageViewBuilder.php
index 5155341d80dde047a5acc9393ac8b66cd8cfd7ea..4aeb2499940e142a7e4c968c43e631a6fdb923d4 100644
--- a/web/core/modules/contact/src/MessageViewBuilder.php
+++ b/web/core/modules/contact/src/MessageViewBuilder.php
@@ -4,7 +4,6 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityViewBuilder;
-use Drupal\Core\Mail\MailFormatHelper;
 use Drupal\Core\Render\Element;
 
 /**
@@ -40,9 +39,6 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N
           $build[$key]['#label_display'] = 'hidden';
         }
       }
-      $build['#post_render'][] = function ($html, array $elements) {
-        return MailFormatHelper::htmlToText($html);
-      };
     }
     return $build;
   }
diff --git a/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php b/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
index 0029b2eb39b861e87b28150e7a4286b247a72169..26f7a1080d786b9d1aa00bcaa723fcb41459e06b 100644
--- a/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
+++ b/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Test\AssertMailTrait;
 use Drupal\Tests\BrowserTestBase;
@@ -25,7 +26,7 @@ class ContactPersonalTest extends BrowserTestBase {
    *
    * @var array
    */
-  public static $modules = ['contact', 'dblog'];
+  public static $modules = ['contact', 'dblog', 'mail_html_test'];
 
   /**
    * {@inheritdoc}
@@ -116,6 +117,20 @@ public function testSendPersonalContactMessage() {
     $this->assertRaw(new FormattableMarkup('@sender_name (@sender_email) sent @recipient_name an email.', $placeholders));
     // Ensure an unescaped version of the email does not exist anywhere.
     $this->assertNoRaw($this->webUser->getEmail());
+
+    // Test HTML mails.
+    $mail_config = $this->config('system.mail');
+    $mail_config->set('interface.default', 'test_html_mail_collector');
+    $mail_config->save();
+
+    $this->drupalLogin($this->webUser);
+    $message['message[0][value]'] = 'This <i>is</i> a more <b>specific</b> <sup>test</sup>, the emails are formatted now.';
+    $message = $this->submitPersonalContact($this->contactUser, $message);
+
+    // Assert mail content.
+    $this->assertMailString('body', 'Hello ' . $variables['@recipient-name'], 1);
+    $this->assertMailString('body', $this->webUser->getDisplayName(), 1);
+    $this->assertMailString('body', Html::Escape($message['message[0][value]']), 1);
   }
 
   /**
@@ -326,8 +341,8 @@ protected function checkContactAccess($response, $contact_value = NULL) {
    */
   protected function submitPersonalContact(AccountInterface $account, array $message = []) {
     $message += [
-      'subject[0][value]' => $this->randomMachineName(16),
-      'message[0][value]' => $this->randomMachineName(64),
+      'subject[0][value]' => $this->randomMachineName(16) . '< " =+ >',
+      'message[0][value]' => $this->randomMachineName(64) . '< " =+ >',
     ];
     $this->drupalPostForm('user/' . $account->id() . '/contact', $message, t('Send message'));
     return $message;
diff --git a/web/core/modules/editor/editor.module b/web/core/modules/editor/editor.module
index 4ccea91890da571414ab3fe5076bba6a6b08d23c..0fb4267e776a5c375ca0ba1b717b3822674c3208 100644
--- a/web/core/modules/editor/editor.module
+++ b/web/core/modules/editor/editor.module
@@ -247,7 +247,9 @@ function editor_form_filter_admin_format_submit($form, FormStateInterface $form_
     $original_editor->delete();
   }
 
-  if ($editor_plugin = $form_state->get('editor_plugin')) {
+  $editor_set = $form_state->getValue(['editor', 'editor']) !== "";
+  $subform_array_exists = (!empty($form['editor']['settings']['subform']) && is_array($form['editor']['settings']['subform']));
+  if (($editor_plugin = $form_state->get('editor_plugin')) && $editor_set && $subform_array_exists) {
     $subform_state = SubformState::createForSubform($form['editor']['settings']['subform'], $form, $form_state);
     $editor_plugin->submitConfigurationForm($form['editor']['settings']['subform'], $subform_state);
   }
diff --git a/web/core/modules/editor/tests/src/Functional/EditorAdminTest.php b/web/core/modules/editor/tests/src/Functional/EditorAdminTest.php
index 3065c3fa6004fd7d617998a9b02e5a54779c440b..431d77c6c915ebde8a2f3d02751916003a2e027a 100644
--- a/web/core/modules/editor/tests/src/Functional/EditorAdminTest.php
+++ b/web/core/modules/editor/tests/src/Functional/EditorAdminTest.php
@@ -155,6 +155,23 @@ public function testDisableFormatWithEditor() {
     $this->assertRaw($text);
   }
 
+  /**
+   * Tests switching text editor to none does not throw a TypeError.
+   */
+  public function testSwitchEditorToNone() {
+    $this->enableUnicornEditor();
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('admin/config/content/formats/manage/filtered_html');
+    $edit = $this->selectUnicornEditor();
+
+    // Switch editor to 'None'.
+    $edit = [
+      'editor[editor]' => '',
+    ];
+    $this->submitForm($edit, 'Configure');
+    $this->submitForm($edit, 'Save configuration');
+  }
+
   /**
    * Adds an editor to a new format using the UI.
    *
diff --git a/web/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/web/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
index 47aa51fa15c7a40dbbf45d2097a42d1805649f1f..c8141874ce60d7fb469ecde1a77abded54759959 100644
--- a/web/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
+++ b/web/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
@@ -206,11 +206,15 @@ public static function process($element, FormStateInterface $form_state, $form)
         'uri' => $file->getFileUri(),
       ];
 
+      $dimension_key = $variables['uri'] . '.image_preview_dimensions';
       // Determine image dimensions.
       if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
         $variables['width'] = $element['#value']['width'];
         $variables['height'] = $element['#value']['height'];
       }
+      elseif ($form_state->has($dimension_key)) {
+        $variables += $form_state->get($dimension_key);
+      }
       else {
         $image = \Drupal::service('image.factory')->get($file->getFileUri());
         if ($image->isValid()) {
@@ -233,14 +237,7 @@ public static function process($element, FormStateInterface $form_state, $form)
 
       // Store the dimensions in the form so the file doesn't have to be
       // accessed again. This is important for remote files.
-      $element['width'] = [
-        '#type' => 'hidden',
-        '#value' => $variables['width'],
-      ];
-      $element['height'] = [
-        '#type' => 'hidden',
-        '#value' => $variables['height'],
-      ];
+      $form_state->set($dimension_key, ['width' => $variables['width'], 'height' => $variables['height']]);
     }
     elseif (!empty($element['#default_image'])) {
       $default_image = $element['#default_image'];
diff --git a/web/core/modules/image/tests/src/FunctionalJavascript/ImageFieldWidgetMultipleTest.php b/web/core/modules/image/tests/src/FunctionalJavascript/ImageFieldWidgetMultipleTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0638c03b5af687dd95453fc9b709e443ebfb3f66
--- /dev/null
+++ b/web/core/modules/image/tests/src/FunctionalJavascript/ImageFieldWidgetMultipleTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\Tests\image\FunctionalJavascript;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\node\Entity\Node;
+use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
+use Drupal\Tests\TestFileCreationTrait;
+
+/**
+ * Tests the image field widget support multiple upload correctly.
+ *
+ * @group image
+ */
+class ImageFieldWidgetMultipleTest extends WebDriverTestBase {
+
+  use ImageFieldCreationTrait;
+  use TestFileCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['node', 'field_ui', 'image'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Tests image widget element support multiple upload correctly.
+   */
+  public function testWidgetElementMultipleUploads(): void {
+    $image_factory = \Drupal::service('image.factory');
+    $file_system = \Drupal::service('file_system');
+    $web_driver = $this->getSession()->getDriver();
+
+    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
+    $field_name = 'images';
+    $storage_settings = ['cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED];
+    $field_settings = ['alt_field_required' => 0];
+    $this->createImageField($field_name, 'article', $storage_settings, $field_settings);
+    $this->drupalLogin($this->drupalCreateUser(['access content', 'create article content']));
+    $this->drupalGet('node/add/article');
+    $this->xpath('//input[@name="title[0][value]"]')[0]->setValue('Test');
+
+    $images = $this->getTestFiles('image');
+    $images = array_slice($images, 0, 5);
+
+    $paths = [];
+    foreach ($images as $image) {
+      $paths[] = $file_system->realpath($image->uri);
+    }
+
+    $remote_paths = [];
+    foreach ($paths as $path) {
+      $remote_paths[] = $web_driver->uploadFileAndGetRemoteFilePath($path);
+    }
+
+    $multiple_field = $this->xpath('//input[@multiple]')[0];
+    $multiple_field->setValue(implode("\n", $remote_paths));
+    $this->assertSession()->waitForElementVisible('css', '[data-drupal-selector="edit-images-4-preview"]');
+    $this->getSession()->getPage()->findButton('Save')->click();
+
+    $node = Node::load(1);
+    foreach ($paths as $delta => $path) {
+      $node_image = $node->{$field_name}[$delta];
+      $original_image = $image_factory->get($path);
+      $this->assertEquals($original_image->getWidth(), $node_image->width, "Correct width of image #$delta");
+      $this->assertEquals($original_image->getHeight(), $node_image->height, "Correct height of image #$delta");
+    }
+  }
+
+}
diff --git a/web/core/modules/language/src/Form/LanguageFormBase.php b/web/core/modules/language/src/Form/LanguageFormBase.php
index d30c4b692285420baf0167f0f9f01a15baf6d6e8..2c96635747887d574241690bd6273dd98f6d78ae 100644
--- a/web/core/modules/language/src/Form/LanguageFormBase.php
+++ b/web/core/modules/language/src/Form/LanguageFormBase.php
@@ -95,7 +95,7 @@ public function commonForm(array &$form) {
    */
   public function validateCommon(array $form, FormStateInterface $form_state) {
     // Ensure sane field values for langcode and name.
-    if (!isset($form['langcode_view']) && !preg_match('@^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$@', $form_state->getValue('langcode'))) {
+    if (!isset($form['langcode_view']) && !preg_match('@^' . LanguageInterface::VALID_LANGCODE_REGEX . '$@', $form_state->getValue('langcode'))) {
       $form_state->setErrorByName('langcode', $this->t('%field must be a valid language tag as <a href=":url">defined by the W3C</a>.', [
         '%field' => $form['langcode']['#title'],
         ':url' => 'http://www.w3.org/International/articles/language-tags/',
diff --git a/web/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php b/web/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php
index effa4f52d07c70f1e391935f13b7332b39d02dd2..42455c45c9d9e70ff41dc7f6aa15c161a33f9e02 100644
--- a/web/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php
+++ b/web/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\node\Functional;
 
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\node\Entity\Node;
 
 /**
@@ -65,4 +67,70 @@ public function testNodeMultipleLoad() {
     }
   }
 
+  /**
+   * Creates four nodes with not case sensitive fields and load them.
+   */
+  public function testNodeMultipleLoadCaseSensitiveFalse() {
+    $field_first_storage = FieldStorageConfig::create([
+      'field_name' => 'field_first',
+      'entity_type' => 'node',
+      'type' => 'string',
+      'settings' => [
+        'case_sensitive' => FALSE,
+      ],
+    ]);
+    $field_first_storage->save();
+
+    FieldConfig::create([
+      'field_storage' => $field_first_storage,
+      'bundle' => 'page',
+    ])->save();
+
+    $field_second_storage = FieldStorageConfig::create([
+      'field_name' => 'field_second',
+      'entity_type' => 'node',
+      'type' => 'string',
+      'settings' => [
+        'case_sensitive' => FALSE,
+      ],
+    ]);
+    $field_second_storage->save();
+
+    FieldConfig::create([
+      'field_storage' => $field_second_storage,
+      'bundle' => 'page',
+    ])->save();
+
+    // Test create nodes with values for field_first and field_second.
+    $node1 = $this->drupalCreateNode([
+      'type' => 'page',
+      'field_first' => '1234',
+      'field_second' => 'test_value_1',
+    ]);
+    $node2 = $this->drupalCreateNode([
+      'type' => 'page',
+      'field_first' => '1234',
+      'field_second' => 'test_value_2',
+    ]);
+    $node3 = $this->drupalCreateNode([
+      'type' => 'page',
+      'field_first' => '5678',
+      'field_second' => 'test_value_1',
+    ]);
+    $node4 = $this->drupalCreateNode([
+      'type' => 'page',
+      'field_first' => '5678',
+      'field_second' => 'test_value_2',
+    ]);
+
+    // Load nodes by two properties (field_first and field_second).
+    $nodes = $this->container->get('entity_type.manager')->getStorage('node')
+      ->loadByProperties(['field_first' => ['1234', '5678'], 'field_second' => 'test_value_1']);
+    $this->assertCount(2, $nodes);
+    $this->assertEqual($node1->field_first->value, $nodes[$node1->id()]->field_first->value);
+    $this->assertEqual($node1->field_second->value, $nodes[$node1->id()]->field_second->value);
+    $this->assertEqual($node3->field_first->value, $nodes[$node3->id()]->field_first->value);
+    $this->assertEqual($node3->field_second->value, $nodes[$node3->id()]->field_second->value);
+  }
+
 }
diff --git a/web/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php b/web/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php
index 5ddc00902d38ef657e78332d77a66cf86b780318..159cca68afc39b1edee8e9167c2353472bcbd135 100644
--- a/web/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php
+++ b/web/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php
@@ -84,7 +84,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
   public static function validateFormElement(array &$element, FormStateInterface $form_state) {
     // Trim the submitted value of whitespace and slashes.
     $alias = rtrim(trim($element['alias']['#value']), " \\/");
-    if (!empty($alias)) {
+    if ($alias !== '') {
       $form_state->setValueForElement($element['alias'], $alias);
 
       /** @var \Drupal\path_alias\PathAliasInterface $path_alias */
diff --git a/web/core/modules/path/tests/src/Functional/PathAliasTest.php b/web/core/modules/path/tests/src/Functional/PathAliasTest.php
index 921653577974fbd2ae70d237b3b32ab1f754ef7a..4bea17dcd1e0f6214f73afe63f00128f3fc442ca 100644
--- a/web/core/modules/path/tests/src/Functional/PathAliasTest.php
+++ b/web/core/modules/path/tests/src/Functional/PathAliasTest.php
@@ -343,6 +343,12 @@ public function testNodeAlias() {
     // Create sixth test node.
     $node6 = $this->drupalCreateNode();
 
+    // Test the special case where the alias is '0'.
+    $edit = ['path[0][alias]' => '0'];
+    $this->drupalGet($node6->toUrl('edit-form'));
+    $this->submitForm($edit, 'Save');
+    $this->assertSession()->pageTextContains('The alias path has to start with a slash.');
+
     // Create an invalid alias with two leading slashes and verify that the
     // extra slash is removed when the link is generated. This ensures that URL
     // aliases cannot be used to inject external URLs.
diff --git a/web/core/modules/taxonomy/migrations/d6_term_node_revision.yml b/web/core/modules/taxonomy/migrations/d6_term_node_revision.yml
index 4b196ac2dfeb55ecc17d2fe7e4b658768cb65416..b202994bf34eecd4145f00fb4d62a8b1ea058fb7 100644
--- a/web/core/modules/taxonomy/migrations/d6_term_node_revision.yml
+++ b/web/core/modules/taxonomy/migrations/d6_term_node_revision.yml
@@ -12,7 +12,7 @@ process:
     -
       plugin: migration_lookup
       migration:
-        - d6_node_copmplete
+        - d6_node_complete
         - d6_node_revision
       source: vid
     -
diff --git a/web/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeComplete.php b/web/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeComplete.php
index f7ce2a008a41948ce5cc4584f62893b4aab7af27..56c2067fb3e665038a943773a7527f178d9d6d78 100644
--- a/web/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeComplete.php
+++ b/web/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeComplete.php
@@ -64,4 +64,16 @@ public function testTermNode() {
     $this->assertSame('3', $node->field_vocabulary_2_i_1_[1]->target_id);
   }
 
+  /**
+   * Tests the Drupal 6 term-node association to Drupal 8 node revisions.
+   */
+  public function testTermNodeRevision() {
+    $this->executeMigrations(['d6_term_node_revision']);
+
+    $node = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(2001);
+    $this->assertCount(2, $node->field_vocabulary_3_i_2_);
+    $this->assertSame('4', $node->field_vocabulary_3_i_2_[0]->target_id);
+    $this->assertSame('5', $node->field_vocabulary_3_i_2_[1]->target_id);
+  }
+
 }
diff --git a/web/core/modules/views/src/Plugin/views/query/PostgresqlDateSql.php b/web/core/modules/views/src/Plugin/views/query/PostgresqlDateSql.php
index c919fadb671f6e6885703ab9aeb9d71e7c7a4558..60b1f298bdc63a31aa79b9969df4cabaa68ebf1c 100644
--- a/web/core/modules/views/src/Plugin/views/query/PostgresqlDateSql.php
+++ b/web/core/modules/views/src/Plugin/views/query/PostgresqlDateSql.php
@@ -66,7 +66,7 @@ public function __construct(Connection $database) {
   public function getDateField($field, $string_date) {
     if ($string_date) {
       // Ensures compatibility with field offset operation below.
-      return "TO_TIMESTAMP($field, 'YYYY-MM-DD HH24:MI:SS')";
+      return "TO_TIMESTAMP($field, 'YYYY-MM-DD\"T\"HH24:MI:SS')";
     }
     return "TO_TIMESTAMP($field)";
   }
diff --git a/web/core/modules/views/tests/src/Unit/Plugin/views/query/PostgresqlDateSqlTest.php b/web/core/modules/views/tests/src/Unit/Plugin/views/query/PostgresqlDateSqlTest.php
index 14f367d261dce4e3d449898f13c4ce35cf4800ce..acb53f35b014e35413a9bcc7f4bb14f7f7d1b515 100644
--- a/web/core/modules/views/tests/src/Unit/Plugin/views/query/PostgresqlDateSqlTest.php
+++ b/web/core/modules/views/tests/src/Unit/Plugin/views/query/PostgresqlDateSqlTest.php
@@ -38,7 +38,7 @@ public function setUp() {
   public function testGetDateField() {
     $date_sql = new PostgresqlDateSql($this->database);
 
-    $expected = "TO_TIMESTAMP(foo.field, 'YYYY-MM-DD HH24:MI:SS')";
+    $expected = "TO_TIMESTAMP(foo.field, 'YYYY-MM-DD\"T\"HH24:MI:SS')";
     $this->assertEquals($expected, $date_sql->getDateField('foo.field', TRUE));
 
     $expected = 'TO_TIMESTAMP(foo.field)';
diff --git a/web/core/scripts/dev/commit-code-check.sh b/web/core/scripts/dev/commit-code-check.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3ad565b1327f4a57987439a38ba9bf101dbc258a
--- /dev/null
+++ b/web/core/scripts/dev/commit-code-check.sh
@@ -0,0 +1,352 @@
+#!/bin/bash
+#
+# This script performs code quality checks.
+#
+# @internal
+#   This script is not covered by Drupal core's backwards compatibility promise.
+#   It exists only for core development purposes.
+#
+# The script makes the following checks:
+# - File modes.
+# - No changes to core/node_modules directory.
+# - PHPCS checks PHP and YAML files.
+# - Eslint checks JavaScript files.
+# - Checks .es6.js and .js files are equivalent.
+# - Stylelint checks CSS files.
+# - Checks .pcss.css and .css files are equivalent.
+
+# Searches an array.
+contains_element() {
+  local e
+  for e in ${@:2}; do [[ "$e" == "$1" ]] && return 0; done
+  return 1
+}
+
+CACHED=0
+DRUPALCI=0
+BRANCH=""
+while test $# -gt 0; do
+  case "$1" in
+    -h|--help)
+      echo "Drupal code quality checks"
+      echo " "
+      echo "options:"
+      echo "-h, --help                show brief help"
+      echo "--branch BRANCH           creates list of files to check by comparing against a branch"
+      echo "--cached                  checks staged files"
+      echo "--drupalci                a special mode for DrupalCI"
+      echo " "
+      echo "Example usage: sh ./core/scripts/dev/commit-code-check.sh --branch 9.2.x"
+      exit 0
+      ;;
+    --branch)
+      BRANCH="$2"
+      if [[ "$BRANCH" == "" ]]; then
+        printf "The --branch option requires a value. For example: --branch 9.2.x\n"
+        exit;
+      fi
+      shift 2
+      ;;
+    --cached)
+      CACHED=1
+      shift
+      ;;
+    --drupalci)
+      DRUPALCI=1
+      shift
+      ;;
+    *)
+      break
+      ;;
+  esac
+done
+
+# Set up variables to make colored output simple. Color output is disabled on
+# DrupalCI because it is breaks reporting.
+# @todo https://www.drupal.org/project/drupalci_testbot/issues/3181869
+if [[ "$DRUPALCI" == "1" ]]; then
+  red=""
+  green=""
+  reset=""
+  DRUPAL_VERSION=$(php -r "include 'vendor/autoload.php'; print preg_replace('#\.[0-9]+-dev#', '.x', \Drupal::VERSION);")
+else
+  red=$(tput setaf 1 && tput bold)
+  green=$(tput setaf 2)
+  reset=$(tput sgr0)
+fi
+
+# Gets list of files to check.
+if [[ "$BRANCH" != "" ]]; then
+  FILES=$(git diff --name-only $BRANCH HEAD);
+elif [[ "$CACHED" == "0" ]]; then
+  # For DrupalCI patch testing or when running without --cached or --branch,
+  # list of all changes in the working directory.
+  FILES=$(git ls-files --other --modified --exclude-standard --exclude=vendor)
+else
+  # Check staged files only.
+  if git rev-parse --verify HEAD >/dev/null 2>&1
+  then
+    AGAINST=HEAD
+  else
+    # Initial commit: diff against an empty tree object
+    AGAINST=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+  fi
+  FILES=$(git diff --cached --name-only $AGAINST);
+fi
+
+if [[ "$FILES" == "" ]] && [[ "$DRUPALCI" == "1" ]]; then
+  # If the FILES is empty we might be testing a merge request on DrupalCI. We
+  # need to diff against the Drupal branch or tag related to the Drupal version.
+  printf "Creating list of files to check by comparing branch to %s\n" "$DRUPAL_VERSION"
+  # On DrupalCI there's a merge commit so we can compare to HEAD~1.
+  FILES=$(git diff --name-only HEAD~1 HEAD);
+fi
+
+TOP_LEVEL=$(git rev-parse --show-toplevel)
+
+# Build up a list of absolute file names.
+ABS_FILES=
+for FILE in $FILES; do
+  ABS_FILES="$ABS_FILES $TOP_LEVEL/$FILE"
+done
+
+# Exit early if there are no files.
+if [[ "$ABS_FILES" == "" ]]; then
+  printf "There are no files to check. If you have staged a commit use the --cached option.\n"
+  exit;
+fi;
+
+# This script assumes that composer install and yarn install have already been
+# run and all dependencies are updated.
+FINAL_STATUS=0
+
+cd "$TOP_LEVEL"
+
+# Add a separator line to make the output easier to read.
+printf "\n"
+printf -- '-%.0s' {1..100}
+printf "\n"
+
+for FILE in $FILES; do
+  STATUS=0;
+  # Print a line to separate output.
+  printf "Checking %s\n" "$FILE"
+  printf "\n"
+
+  # Ensure the file still exists (i.e. is not being deleted).
+  if [ -a $FILE ]; then
+    if [ ${FILE: -3} != ".sh" ]; then
+      # Ensure the file has the correct mode.
+      STAT="$(stat -f "%A" $FILE 2>/dev/null)"
+      if [ $? -ne 0 ]; then
+        STAT="$(stat -c "%a" $FILE 2>/dev/null)"
+      fi
+      if [ "$STAT" -ne "644" ]; then
+        printf "${red}check failed:${reset} file $FILE should be 644 not $STAT\n"
+        STATUS=1
+      fi
+    fi
+  fi
+
+  # Don't commit changes to vendor.
+  if [[ "$FILE" =~ ^vendor/ ]]; then
+    printf "${red}check failed:${reset} file in vendor directory being committed ($FILE)\n"
+    STATUS=1
+  fi
+
+  # Don't commit changes to core/node_modules.
+  if [[ "$FILE" =~ ^core/node_modules/ ]]; then
+    printf "${red}check failed:${reset} file in core/node_modules directory being committed ($FILE)\n"
+    STATUS=1
+  fi
+
+  ############################################################################
+  ### PHP AND YAML FILES
+  ############################################################################
+  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.(inc|install|module|php|profile|test|theme|yml)$ ]]; then
+    # Test files with phpcs rules.
+    vendor/bin/phpcs "$TOP_LEVEL/$FILE" --runtime-set installed_paths "$TOP_LEVEL/vendor/drupal/coder/coder_sniffer" --standard="$TOP_LEVEL/core/phpcs.xml.dist"
+    PHPCS=$?
+    if [ "$PHPCS" -ne "0" ]; then
+      # If there are failures set the status to a number other than 0.
+      STATUS=1
+    else
+      printf "PHPCS: $FILE ${green}passed${reset}\n"
+    fi
+  fi
+
+  ############################################################################
+  ### JAVASCRIPT FILES
+  ############################################################################
+  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]] && [[ ! $FILE =~ ^core/tests/Drupal/Nightwatch ]] && [[ ! $FILE =~ ^core/assets/vendor/jquery.ui/ui ]]; then
+    # Work out the root name of the JavaScript so we can ensure that the ES6
+    # version has been compiled correctly.
+    if [[ $FILE =~ \.es6\.js$ ]]; then
+      BASENAME=${FILE%.es6.js}
+      COMPILE_CHECK=1
+    else
+      BASENAME=${FILE%.js}
+      # We only need to compile check if the .es6.js file is not also
+      # changing. This is because the compile check will occur for the
+      # .es6.js file. This might occur if the compile scripts have changed.
+      contains_element "$BASENAME.es6.js" "${FILES[@]}"
+      HASES6=$?
+      if [ "$HASES6" -ne "0" ]; then
+        COMPILE_CHECK=1
+      else
+        COMPILE_CHECK=0
+      fi
+    fi
+    if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.es6.js" ]]; then
+      cd "$TOP_LEVEL/core"
+      yarn run build:js --check --file "$TOP_LEVEL/$BASENAME.es6.js"
+      CORRECTJS=$?
+      if [ "$CORRECTJS" -ne "0" ]; then
+        # No need to write any output the yarn run command will do this for
+        # us.
+        STATUS=1
+      fi
+      # Check the coding standards.
+      if [[ -f ".eslintrc.passing.json" ]]; then
+        node ./node_modules/eslint/bin/eslint.js --quiet --config=.eslintrc.passing.json "$TOP_LEVEL/$BASENAME.es6.js"
+        CORRECTJS=$?
+        if [ "$CORRECTJS" -ne "0" ]; then
+          # No need to write any output the node command will do this for us.
+          STATUS=1
+        fi
+      fi
+      cd $TOP_LEVEL
+    else
+      # If there is no .es6.js file then there should be unless the .js is
+      # not really Drupal's.
+      if ! [[ "$FILE" =~ ^core/assets/vendor ]] && ! [[ "$FILE" =~ ^core/scripts/js ]] && ! [[ "$FILE" =~ ^core/scripts/css ]] && ! [[ "$FILE" =~ core/postcss.config.js ]] && ! [[ -f "$TOP_LEVEL/$BASENAME.es6.js" ]]; then
+        printf "${red}FAILURE${reset} $FILE does not have a corresponding $BASENAME.es6.js\n"
+        STATUS=1
+      fi
+    fi
+  elif [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]] && [[ $FILE =~ ^core/assets/vendor/jquery.ui/ui ]]; then
+    ## Check for minified file changes.
+    if [[ $FILE =~ -min\.js$ ]]; then
+      BASENAME=${FILE%-min.js}
+      contains_element "$BASENAME.js" "${FILES[@]}"
+      HASSRC=$?
+      if [ "$HASSRC" -ne "0" ]; then
+        COMPILE_CHECK=1
+      else
+        ## Source was also changed and will be checked.
+        COMPILE_CHECK=0
+      fi
+    else
+      ## Check for source changes.
+      BASENAME=${FILE%.js}
+      COMPILE_CHECK=1
+    fi
+    if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.js" ]]; then
+      cd "$TOP_LEVEL/core"
+      yarn run build:jqueryui --check --file "$TOP_LEVEL/$BASENAME.js"
+      CORRECTJS=$?
+      if [ "$CORRECTJS" -ne "0" ]; then
+        # The yarn run command will write any error output.
+        STATUS=1
+      fi
+      cd $TOP_LEVEL
+    else
+      # If there is no .js source file
+      if ! [[ -f "$TOP_LEVEL/$BASENAME.js" ]]; then
+        printf "${red}FAILURE${reset} $FILE does not have a corresponding $BASENAME.js\n"
+        STATUS=1
+      fi
+    fi
+  else
+    # Check coding standards of Nightwatch files.
+    if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.js$ ]]; then
+      cd "$TOP_LEVEL/core"
+      # Check the coding standards.
+      if [[ -f ".eslintrc.passing.json" ]]; then
+        node ./node_modules/eslint/bin/eslint.js --quiet --config=.eslintrc.passing.json "$TOP_LEVEL/$FILE"
+        CORRECTJS=$?
+        if [ "$CORRECTJS" -ne "0" ]; then
+          # No need to write any output the node command will do this for us.
+          STATUS=1
+        fi
+      fi
+      cd $TOP_LEVEL
+    fi
+  fi
+
+  ############################################################################
+  ### CSS FILES
+  ############################################################################
+  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.css$ ]]; then
+    # Work out the root name of the CSS so we can ensure that the PostCSS
+    # version has been compiled correctly.
+    if [[ $FILE =~ \.pcss\.css$ ]]; then
+      BASENAME=${FILE%.pcss.css}
+      COMPILE_CHECK=1
+    else
+      BASENAME=${FILE%.css}
+      # We only need to compile check if the .pcss.css file is not also
+      # changing. This is because the compile check will occur for the
+      # .pcss.css file. This might occur if the compiled stylesheets have
+      # changed.
+      contains_element "$BASENAME.pcss.css" "${FILES[@]}"
+      HASPOSTCSS=$?
+      if [ "$HASPOSTCSS" -ne "0" ]; then
+        COMPILE_CHECK=1
+      else
+        COMPILE_CHECK=0
+      fi
+    fi
+    # PostCSS
+    if [[ "$COMPILE_CHECK" == "1" ]] && [[ -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
+      cd "$TOP_LEVEL/core"
+      yarn run build:css --check --file "$TOP_LEVEL/$BASENAME.pcss.css"
+      CORRECTCSS=$?
+      if [ "$CORRECTCSS" -ne "0" ]; then
+        # No need to write any output the yarn run command will do this for
+        # us.
+        STATUS=1
+      fi
+      cd $TOP_LEVEL
+    fi
+  fi
+  if [[ -f "$TOP_LEVEL/$FILE" ]] && [[ $FILE =~ \.css$ ]] && [[ -f "core/node_modules/.bin/stylelint" ]]; then
+    BASENAME=${FILE%.css}
+    # We only need to use stylelint on the .pcss.css file. So if this CSS file
+    # has a corresponding .pcss don't do stylelint.
+    if [[ $FILE =~ \.pcss\.css$ ]] || [[ ! -f "$TOP_LEVEL/$BASENAME.pcss.css" ]]; then
+      cd "$TOP_LEVEL/core"
+      node_modules/.bin/stylelint "$TOP_LEVEL/$FILE"
+      if [ "$?" -ne "0" ]; then
+        STATUS=1
+      else
+        printf "STYLELINT: $FILE ${green}passed${reset}\n"
+      fi
+      cd $TOP_LEVEL
+    fi
+  fi
+
+  if [[ "$STATUS" == "1" ]]; then
+    FINAL_STATUS=1
+    # There is no need to print a failure message. The fail will be described
+    # already.
+  else
+    printf "%s ${green}passed${reset}\n" "$FILE"
+  fi
+
+  # Print a line to separate each file's checks.
+  printf "\n"
+  printf -- '-%.0s' {1..100}
+  printf "\n"
+done
+
+if [[ "$FINAL_STATUS" == "1" ]] && [[ "$DRUPALCI" == "1" ]]; then
+  printf "${red}Drupal code quality checks failed.${reset}\n"
+  printf "To reproduce this output locally:\n"
+  printf "* Apply the change as a patch\n"
+  printf "* Run this command locally: sh ./core/scripts/dev/commit-code-check.sh\n"
+  printf "OR:\n"
+  printf "* From the merge request branch\n"
+  printf "* Run this command locally: sh ./core/scripts/dev/commit-code-check.sh --branch %s\n" "$DRUPAL_VERSION"
+fi
+exit $FINAL_STATUS
diff --git a/web/core/tests/Drupal/KernelTests/Core/Field/MapBaseFieldTest.php b/web/core/tests/Drupal/KernelTests/Core/Field/MapBaseFieldTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1ff63f89acf66b79456df0ee62eb3ae568f89408
--- /dev/null
+++ b/web/core/tests/Drupal/KernelTests/Core/Field/MapBaseFieldTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Field;
+
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\entity_test_update\Entity\EntityTestUpdate;
+use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
+
+/**
+ * Tests map base fields.
+ *
+ * @group Field
+ */
+class MapBaseFieldTest extends EntityKernelTestBase {
+
+  /**
+   * The entity definition update manager.
+   *
+   * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
+   */
+  protected $entityDefinitionUpdateManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['entity_test_update'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
+
+    // Install every entity type's schema that wasn't installed in the parent
+    // method.
+    foreach (array_diff_key($this->entityTypeManager->getDefinitions(), array_flip(['user', 'entity_test'])) as $entity_type_id => $entity_type) {
+      $this->installEntitySchema($entity_type_id);
+    }
+  }
+
+  /**
+   * Tests uninstalling map item base field.
+   */
+  public function testUninstallMapItemBaseField() {
+    $definitions['data_map'] = BaseFieldDefinition::create('map')
+      ->setLabel(t('Data'))
+      ->setRequired(TRUE);
+
+    $this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
+
+    $this->entityDefinitionUpdateManager->installFieldStorageDefinition('data_map', 'entity_test_update', 'entity_test', $definitions['data_map']);
+
+    $entity = EntityTestUpdate::create([
+      'data_map' => [
+        'key' => 'value',
+      ],
+    ]);
+    $entity->save();
+
+    $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($definitions['data_map']);
+  }
+
+}
diff --git a/web/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php b/web/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php
index 0381e2e9ffb25bdbdd068f993271e75ceabb0e85..2846749f4f611d1a1961791c9acb965201a2ddfd 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php
@@ -20,8 +20,9 @@ public function testInstallerTranslationFiles() {
     // Different translation files would be found depending on which language
     // we are looking for.
     $expected_translation_files = [
-      NULL => ['drupal-8.0.0-beta2.hu.po', 'drupal-8.0.0.de.po'],
+      NULL => ['drupal-8.0.0-beta2.hu.po', 'drupal-8.0.0.de.po', 'drupal-8.0.x.fr-CA.po'],
       'de' => ['drupal-8.0.0.de.po'],
+      'fr-CA' => ['drupal-8.0.x.fr-CA.po'],
       'hu' => ['drupal-8.0.0-beta2.hu.po'],
       'it' => [],
     ];
diff --git a/web/core/tests/Drupal/KernelTests/Core/Plugin/Annotation/ContextDefinitionTest.php b/web/core/tests/Drupal/KernelTests/Core/Plugin/Annotation/ContextDefinitionTest.php
index ecb3c1fb82e4c9bd407df43e17b62d83ae0f8320..3e0e1be7db62e79ab8a0670c5ac6fe4e1d26cc48 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Plugin/Annotation/ContextDefinitionTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Plugin/Annotation/ContextDefinitionTest.php
@@ -25,6 +25,7 @@ public function testConstraints() {
     $this->assertArrayHasKey('user', $definition['context_definitions']);
     $this->assertInstanceOf(ContextDefinition::class, $definition['context_definitions']['user']);
     $this->assertEquals(['NotNull' => []], $definition['context_definitions']['user']->getConstraints());
+    $this->assertEquals("User Context", $definition['context_definitions']['user']->getLabel());
   }
 
 }
diff --git a/web/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php b/web/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php
index 2d95a8dc6a7bd3c2f480038aca1a6694aba17530..f6d4d1960d889477b7d43b4e2dc6772450a02910 100644
--- a/web/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php
+++ b/web/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php
@@ -142,7 +142,7 @@ public static function getSkippedDeprecations() {
       'The "core/html5shiv" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. See https://www.drupal.org/node/3086383',
       'The "core/matchmedia" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. See https://www.drupal.org/node/3086653',
       'The "core/matchmedia.addListener" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. See https://www.drupal.org/node/3086653',
-      'The "core/classList" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the the native browser implementation instead. See https://www.drupal.org/node/3089511',
+      'The "core/classList" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the native browser implementation instead. See https://www.drupal.org/node/3089511',
       'The "core/jquery.ui.datepicker" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. See https://www.drupal.org/node/3081864',
       'The "locale/drupal.locale.datepicker" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. See https://www.drupal.org/node/3081864',
       'The "https://www.drupal.org/link-relations/create" string as a RestResource plugin annotation URI path key is deprecated in Drupal 8.4.0, now a valid link relation type name must be specified, so "create" must be specified instead before Drupal 9.0.0. See https://www.drupal.org/node/2737401.',
diff --git a/web/core/tests/fixtures/files/translations/drupal-8.0.0.fr____CA.po b/web/core/tests/fixtures/files/translations/drupal-8.0.0.fr____CA.po
new file mode 100644
index 0000000000000000000000000000000000000000..e2106954b8b4f1dc2e39c7c7f7b55e1555781989
--- /dev/null
+++ b/web/core/tests/fixtures/files/translations/drupal-8.0.0.fr____CA.po
@@ -0,0 +1,4 @@
+# This file exists to prove that
+# \Drupal\Core\StringTranslation\Translator\FileTranslation::findTranslationFiles()
+# does not find it. See
+# \Drupal\KernelTests\Core\Installer\InstallerLanguageTest::testInstallerTranslationFiles()
diff --git a/web/core/tests/fixtures/files/translations/drupal-8.0.x.fr-CA.po b/web/core/tests/fixtures/files/translations/drupal-8.0.x.fr-CA.po
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391