diff --git a/composer.lock b/composer.lock
index 01cb982e6..d3ff4d2b1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -124,19 +124,20 @@
},
{
"name": "chi-teck/drupal-code-generator",
- "version": "1.26.0",
+ "version": "1.27.0",
"source": {
"type": "git",
"url": "https://github.com/Chi-teck/drupal-code-generator.git",
- "reference": "3090fabdbf3dd4b66e0fa0e4ed5d8adab6deb974"
+ "reference": "a839bc89d385087d8a7a96a9c1c4bd470ffb627e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Chi-teck/drupal-code-generator/zipball/3090fabdbf3dd4b66e0fa0e4ed5d8adab6deb974",
- "reference": "3090fabdbf3dd4b66e0fa0e4ed5d8adab6deb974",
+ "url": "https://api.github.com/repos/Chi-teck/drupal-code-generator/zipball/a839bc89d385087d8a7a96a9c1c4bd470ffb627e",
+ "reference": "a839bc89d385087d8a7a96a9c1c4bd470ffb627e",
"shasum": ""
},
"require": {
+ "ext-json": "*",
"php": ">=5.5.9",
"symfony/console": "~2.7|^3",
"symfony/filesystem": "~2.7|^3",
@@ -146,6 +147,11 @@
"bin/dcg"
],
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
"autoload": {
"files": [
"src/bootstrap.php"
@@ -159,7 +165,7 @@
"GPL-2.0-or-later"
],
"description": "Drupal code generator",
- "time": "2018-08-06T08:54:16+00:00"
+ "time": "2018-10-11T08:05:59+00:00"
},
{
"name": "composer/installers",
@@ -345,16 +351,16 @@
},
{
"name": "consolidation/annotated-command",
- "version": "2.8.5",
+ "version": "2.9.1",
"source": {
"type": "git",
"url": "https://github.com/consolidation/annotated-command.git",
- "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3"
+ "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/1e8ff512072422b850b44aa721b5b303e4a5ebb3",
- "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3",
+ "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac",
+ "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac",
"shasum": ""
},
"require": {
@@ -393,7 +399,7 @@
}
],
"description": "Initialize Symfony Console commands from annotated command class methods.",
- "time": "2018-08-18T23:51:49+00:00"
+ "time": "2018-09-19T17:47:18+00:00"
},
{
"name": "consolidation/config",
@@ -500,19 +506,20 @@
},
{
"name": "consolidation/output-formatters",
- "version": "3.2.1",
+ "version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/consolidation/output-formatters.git",
- "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5"
+ "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5",
- "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5",
+ "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a942680232094c4a5b21c0b7e54c20cce623ae19",
+ "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19",
"shasum": ""
},
"require": {
+ "dflydev/dot-access-data": "^1.1.0",
"php": ">=5.4.0",
"symfony/console": "^2.8|^3|^4",
"symfony/finder": "^2.5|^3|^4"
@@ -551,7 +558,7 @@
}
],
"description": "Format text by applying transformations provided by plug-in formatters.",
- "time": "2018-05-25T18:02:34+00:00"
+ "time": "2018-10-19T22:35:38+00:00"
},
{
"name": "consolidation/robo",
@@ -636,16 +643,16 @@
},
{
"name": "consolidation/self-update",
- "version": "1.1.3",
+ "version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/consolidation/self-update.git",
- "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318"
+ "reference": "4422e52d3fabeca9129ecb1780f198f202debdce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/self-update/zipball/de33822f907e0beb0ffad24cf4b1b4fae5ada318",
- "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318",
+ "url": "https://api.github.com/repos/consolidation/self-update/zipball/4422e52d3fabeca9129ecb1780f198f202debdce",
+ "reference": "4422e52d3fabeca9129ecb1780f198f202debdce",
"shasum": ""
},
"require": {
@@ -682,20 +689,20 @@
}
],
"description": "Provides a self:update command for Symfony Console applications.",
- "time": "2018-08-24T17:01:46+00:00"
+ "time": "2018-10-21T20:17:55+00:00"
},
{
"name": "consolidation/site-alias",
- "version": "1.1.2",
+ "version": "1.1.5",
"source": {
"type": "git",
"url": "https://github.com/consolidation/site-alias.git",
- "reference": "d6fa92e4aaf5ba95cde4454be7ea3165e7e2f17a"
+ "reference": "247f3e52604b9d76a075e6c06d5d1dd73bbae892"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/site-alias/zipball/d6fa92e4aaf5ba95cde4454be7ea3165e7e2f17a",
- "reference": "d6fa92e4aaf5ba95cde4454be7ea3165e7e2f17a",
+ "url": "https://api.github.com/repos/consolidation/site-alias/zipball/247f3e52604b9d76a075e6c06d5d1dd73bbae892",
+ "reference": "247f3e52604b9d76a075e6c06d5d1dd73bbae892",
"shasum": ""
},
"require": {
@@ -738,7 +745,7 @@
}
],
"description": "Template project for PHP libraries.",
- "time": "2018-08-22T01:07:08+00:00"
+ "time": "2018-09-22T04:11:38+00:00"
},
{
"name": "container-interop/container-interop",
@@ -1632,16 +1639,16 @@
},
{
"name": "drupal/core",
- "version": "8.6.1",
+ "version": "8.6.2",
"source": {
"type": "git",
"url": "https://github.com/drupal/core.git",
- "reference": "1c8b96c89288daf4d145c6dbcf1e8f0d0c9d7970"
+ "reference": "356292934802bb1aecc478e88a3cba77442d7c62"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/drupal/core/zipball/1c8b96c89288daf4d145c6dbcf1e8f0d0c9d7970",
- "reference": "1c8b96c89288daf4d145c6dbcf1e8f0d0c9d7970",
+ "url": "https://api.github.com/repos/drupal/core/zipball/356292934802bb1aecc478e88a3cba77442d7c62",
+ "reference": "356292934802bb1aecc478e88a3cba77442d7c62",
"shasum": ""
},
"require": {
@@ -1866,57 +1873,90 @@
"GPL-2.0-or-later"
],
"description": "Drupal is an open source content management platform powering millions of websites and applications.",
- "time": "2018-09-10T11:50:07+00:00"
+ "time": "2018-10-17T22:19:50+00:00"
},
{
"name": "drush/drush",
- "version": "9.4.0",
+ "version": "9.5.2",
"source": {
"type": "git",
"url": "https://github.com/drush-ops/drush.git",
- "reference": "9d46a2a67554ae8b6f6edec234a1272c3b4c6a9e"
+ "reference": "17f0106706391675a281c6d212850853bdbe90f9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/drush-ops/drush/zipball/9d46a2a67554ae8b6f6edec234a1272c3b4c6a9e",
- "reference": "9d46a2a67554ae8b6f6edec234a1272c3b4c6a9e",
+ "url": "https://api.github.com/repos/drush-ops/drush/zipball/17f0106706391675a281c6d212850853bdbe90f9",
+ "reference": "17f0106706391675a281c6d212850853bdbe90f9",
"shasum": ""
},
"require": {
- "chi-teck/drupal-code-generator": "^1.24.0",
+ "chi-teck/drupal-code-generator": "^1.27.0",
"composer/semver": "^1.4",
- "consolidation/annotated-command": "^2.8.1",
+ "consolidation/annotated-command": "^2.9.1",
"consolidation/config": "^1.1.0",
"consolidation/output-formatters": "^3.1.12",
"consolidation/robo": "^1.1.5",
- "consolidation/site-alias": "^1.1.2",
+ "consolidation/site-alias": "^1.1.5",
"ext-dom": "*",
"grasmash/yaml-expander": "^1.1.1",
"league/container": "~2",
"php": ">=5.6.0",
"psr/log": "~1.0",
"psy/psysh": "~0.6",
- "symfony/config": "~2.2|^3",
- "symfony/console": "~2.7|^3",
- "symfony/event-dispatcher": "~2.7|^3",
- "symfony/finder": "~2.7|^3",
- "symfony/process": "~2.7|^3",
- "symfony/var-dumper": "~2.7|^3|^4",
- "symfony/yaml": "~2.3|^3",
+ "symfony/config": "^3.4",
+ "symfony/console": "^3.4",
+ "symfony/event-dispatcher": "^3.4",
+ "symfony/finder": "^3.4",
+ "symfony/process": "^3.4",
+ "symfony/var-dumper": "^3.4",
+ "symfony/yaml": "^3.4",
"webflo/drupal-finder": "^1.1",
"webmozart/path-util": "^2.1.0"
},
"require-dev": {
+ "composer/installers": "^1.2",
+ "cweagans/composer-patches": "~1.0",
+ "drupal/alinks": "1.0.0",
+ "drupal/devel": "^1.0@RC",
+ "drupal/empty_theme": "1.0",
"g1a/composer-test-scenarios": "^2.2.0",
"lox/xhprof": "dev-master",
- "phpunit/phpunit": "^4.8.36|^5.5.4",
- "squizlabs/php_codesniffer": "^2.7"
+ "phpunit/phpunit": "^4.8.36 || ^6.1",
+ "squizlabs/php_codesniffer": "^2.7",
+ "vlucas/phpdotenv": "^2.4",
+ "webflo/drupal-core-strict": "8.6.x-dev"
},
"bin": [
"drush"
],
"type": "library",
"extra": {
+ "installer-paths": {
+ "sut/core": [
+ "type:drupal-core"
+ ],
+ "sut/libraries/{$name}": [
+ "type:drupal-library"
+ ],
+ "sut/modules/unish/{$name}": [
+ "drupal/devel"
+ ],
+ "sut/themes/unish/{$name}": [
+ "drupal/empty_theme"
+ ],
+ "sut/modules/contrib/{$name}": [
+ "type:drupal-module"
+ ],
+ "sut/profiles/contrib/{$name}": [
+ "type:drupal-profile"
+ ],
+ "sut/themes/contrib/{$name}": [
+ "type:drupal-theme"
+ ],
+ "sut/drush/contrib/{$name}": [
+ "type:drupal-drush"
+ ]
+ },
"branch-alias": {
"dev-master": "9.x-dev"
}
@@ -1968,7 +2008,7 @@
],
"description": "Drush is a command line shell and scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those of us who spend some of our working hours hacking away at the command prompt.",
"homepage": "http://www.drush.org",
- "time": "2018-09-04T17:24:36+00:00"
+ "time": "2018-10-17T18:37:53+00:00"
},
{
"name": "easyrdf/easyrdf",
@@ -2034,16 +2074,16 @@
},
{
"name": "egulias/email-validator",
- "version": "1.2.14",
+ "version": "1.2.15",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
- "reference": "5642614492f0ca2064c01d60cc33284cc2f731a9"
+ "reference": "758a77525bdaabd6c0f5669176bd4361cb2dda9e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/5642614492f0ca2064c01d60cc33284cc2f731a9",
- "reference": "5642614492f0ca2064c01d60cc33284cc2f731a9",
+ "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/758a77525bdaabd6c0f5669176bd4361cb2dda9e",
+ "reference": "758a77525bdaabd6c0f5669176bd4361cb2dda9e",
"shasum": ""
},
"require": {
@@ -2082,7 +2122,7 @@
"validation",
"validator"
],
- "time": "2017-02-03T22:48:59+00:00"
+ "time": "2018-09-25T20:59:41+00:00"
},
{
"name": "g1a/composer-test-scenarios",
@@ -2395,32 +2435,32 @@
},
{
"name": "jakub-onderka/php-console-color",
- "version": "0.1",
+ "version": "v0.2",
"source": {
"type": "git",
"url": "https://github.com/JakubOnderka/PHP-Console-Color.git",
- "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1"
+ "reference": "d5deaecff52a0d61ccb613bb3804088da0307191"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1",
- "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1",
+ "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191",
+ "reference": "d5deaecff52a0d61ccb613bb3804088da0307191",
"shasum": ""
},
"require": {
- "php": ">=5.3.2"
+ "php": ">=5.4.0"
},
"require-dev": {
"jakub-onderka/php-code-style": "1.0",
- "jakub-onderka/php-parallel-lint": "0.*",
+ "jakub-onderka/php-parallel-lint": "1.0",
"jakub-onderka/php-var-dump-check": "0.*",
- "phpunit/phpunit": "3.7.*",
+ "phpunit/phpunit": "~4.3",
"squizlabs/php_codesniffer": "1.*"
},
"type": "library",
"autoload": {
- "psr-0": {
- "JakubOnderka\\PhpConsoleColor": "src/"
+ "psr-4": {
+ "JakubOnderka\\PhpConsoleColor\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -2430,41 +2470,41 @@
"authors": [
{
"name": "Jakub Onderka",
- "email": "jakub.onderka@gmail.com",
- "homepage": "http://www.acci.cz"
+ "email": "jakub.onderka@gmail.com"
}
],
- "time": "2014-04-08T15:00:19+00:00"
+ "time": "2018-09-29T17:23:10+00:00"
},
{
"name": "jakub-onderka/php-console-highlighter",
- "version": "v0.3.2",
+ "version": "v0.4",
"source": {
"type": "git",
"url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git",
- "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5"
+ "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5",
- "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5",
+ "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547",
+ "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547",
"shasum": ""
},
"require": {
- "jakub-onderka/php-console-color": "~0.1",
- "php": ">=5.3.0"
+ "ext-tokenizer": "*",
+ "jakub-onderka/php-console-color": "~0.2",
+ "php": ">=5.4.0"
},
"require-dev": {
"jakub-onderka/php-code-style": "~1.0",
- "jakub-onderka/php-parallel-lint": "~0.5",
+ "jakub-onderka/php-parallel-lint": "~1.0",
"jakub-onderka/php-var-dump-check": "~0.1",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~1.5"
},
"type": "library",
"autoload": {
- "psr-0": {
- "JakubOnderka\\PhpConsoleHighlighter": "src/"
+ "psr-4": {
+ "JakubOnderka\\PhpConsoleHighlighter\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -2478,7 +2518,8 @@
"homepage": "http://www.acci.cz/"
}
],
- "time": "2015-04-20T18:58:01+00:00"
+ "description": "Highlight PHP code in terminal",
+ "time": "2018-09-29T18:48:56+00:00"
},
{
"name": "league/container",
@@ -2547,16 +2588,16 @@
},
{
"name": "masterminds/html5",
- "version": "2.3.0",
+ "version": "2.3.1",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
- "reference": "2c37c6c520b995b761674de3be8455a381679067"
+ "reference": "33f8d475d28741398be26cdff7a10a63003324a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/2c37c6c520b995b761674de3be8455a381679067",
- "reference": "2c37c6c520b995b761674de3be8455a381679067",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/33f8d475d28741398be26cdff7a10a63003324a3",
+ "reference": "33f8d475d28741398be26cdff7a10a63003324a3",
"shasum": ""
},
"require": {
@@ -2608,20 +2649,20 @@
"serializer",
"xml"
],
- "time": "2017-09-04T12:26:28+00:00"
+ "time": "2018-10-22T16:58:34+00:00"
},
{
"name": "nikic/php-parser",
- "version": "v4.0.3",
+ "version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "bd088dc940a418f09cda079a9b5c7c478890fb8d"
+ "reference": "d0230c5c77a7e3cfa69446febf340978540958c0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd088dc940a418f09cda079a9b5c7c478890fb8d",
- "reference": "bd088dc940a418f09cda079a9b5c7c478890fb8d",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/d0230c5c77a7e3cfa69446febf340978540958c0",
+ "reference": "d0230c5c77a7e3cfa69446febf340978540958c0",
"shasum": ""
},
"require": {
@@ -2637,7 +2678,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -2659,7 +2700,7 @@
"parser",
"php"
],
- "time": "2018-07-15T17:25:16+00:00"
+ "time": "2018-10-10T09:24:14+00:00"
},
{
"name": "paragonie/random_compat",
@@ -2858,23 +2899,23 @@
},
{
"name": "psy/psysh",
- "version": "v0.9.8",
+ "version": "v0.9.9",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
- "reference": "ed3c32c4304e1a678a6e0f9dc11dd2d927d89555"
+ "reference": "9aaf29575bb8293206bb0420c1e1c87ff2ffa94e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ed3c32c4304e1a678a6e0f9dc11dd2d927d89555",
- "reference": "ed3c32c4304e1a678a6e0f9dc11dd2d927d89555",
+ "url": "https://api.github.com/repos/bobthecow/psysh/zipball/9aaf29575bb8293206bb0420c1e1c87ff2ffa94e",
+ "reference": "9aaf29575bb8293206bb0420c1e1c87ff2ffa94e",
"shasum": ""
},
"require": {
"dnoegel/php-xdg-base-dir": "0.1",
"ext-json": "*",
"ext-tokenizer": "*",
- "jakub-onderka/php-console-highlighter": "0.3.*",
+ "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*",
"nikic/php-parser": "~1.3|~2.0|~3.0|~4.0",
"php": ">=5.4.0",
"symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0",
@@ -2928,7 +2969,7 @@
"interactive",
"shell"
],
- "time": "2018-09-05T11:40:09+00:00"
+ "time": "2018-10-13T15:16:03+00:00"
},
{
"name": "stack/builder",
@@ -3085,16 +3126,16 @@
},
{
"name": "symfony/class-loader",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
- "reference": "31db283fc86d3143e7ff87e922177b457d909c30"
+ "reference": "f31333bdff54c7595f834d510a6d2325573ddb36"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/class-loader/zipball/31db283fc86d3143e7ff87e922177b457d909c30",
- "reference": "31db283fc86d3143e7ff87e922177b457d909c30",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/f31333bdff54c7595f834d510a6d2325573ddb36",
+ "reference": "f31333bdff54c7595f834d510a6d2325573ddb36",
"shasum": ""
},
"require": {
@@ -3137,20 +3178,20 @@
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T11:19:56+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "symfony/config",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "7b08223b7f6abd859651c56bcabf900d1627d085"
+ "reference": "e5389132dc6320682de3643091121c048ff796b3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/7b08223b7f6abd859651c56bcabf900d1627d085",
- "reference": "7b08223b7f6abd859651c56bcabf900d1627d085",
+ "url": "https://api.github.com/repos/symfony/config/zipball/e5389132dc6320682de3643091121c048ff796b3",
+ "reference": "e5389132dc6320682de3643091121c048ff796b3",
"shasum": ""
},
"require": {
@@ -3201,20 +3242,20 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T11:19:56+00:00"
+ "time": "2018-09-08T13:15:14+00:00"
},
{
"name": "symfony/console",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73"
+ "reference": "3b2b415d4c48fbefca7dc742aa0a0171bfae4e0b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/6b217594552b9323bcdcfc14f8a0ce126e84cd73",
- "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73",
+ "url": "https://api.github.com/repos/symfony/console/zipball/3b2b415d4c48fbefca7dc742aa0a0171bfae4e0b",
+ "reference": "3b2b415d4c48fbefca7dc742aa0a0171bfae4e0b",
"shasum": ""
},
"require": {
@@ -3270,20 +3311,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T11:19:56+00:00"
+ "time": "2018-10-02T16:33:53+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "edda5a6155000ff8c3a3f85ee5c421af93cca416"
+ "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/edda5a6155000ff8c3a3f85ee5c421af93cca416",
- "reference": "edda5a6155000ff8c3a3f85ee5c421af93cca416",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/3503415d4aafabc31cd08c3a4ebac7f43fde8feb",
+ "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb",
"shasum": ""
},
"require": {
@@ -3323,20 +3364,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T09:06:28+00:00"
+ "time": "2018-10-02T16:33:53+00:00"
},
{
"name": "symfony/debug",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "c4625e75341e4fb309ce0c049cbf7fb84b8897cd"
+ "reference": "0a612e9dfbd2ccce03eb174365f31ecdca930ff6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/c4625e75341e4fb309ce0c049cbf7fb84b8897cd",
- "reference": "c4625e75341e4fb309ce0c049cbf7fb84b8897cd",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/0a612e9dfbd2ccce03eb174365f31ecdca930ff6",
+ "reference": "0a612e9dfbd2ccce03eb174365f31ecdca930ff6",
"shasum": ""
},
"require": {
@@ -3379,20 +3420,20 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2018-08-03T10:42:44+00:00"
+ "time": "2018-10-02T16:33:53+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "09d7df7bf06c1393b6afc85875993cbdbdf897a0"
+ "reference": "aea20fef4e92396928b5db175788b90234c0270d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/09d7df7bf06c1393b6afc85875993cbdbdf897a0",
- "reference": "09d7df7bf06c1393b6afc85875993cbdbdf897a0",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/aea20fef4e92396928b5db175788b90234c0270d",
+ "reference": "aea20fef4e92396928b5db175788b90234c0270d",
"shasum": ""
},
"require": {
@@ -3450,20 +3491,20 @@
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2018-08-08T11:42:34+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "symfony/dom-crawler",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "452bfc854b60134438e3824b159b0d24a5892331"
+ "reference": "c705bee03ade5b47c087807dd9ffaaec8dda2722"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/452bfc854b60134438e3824b159b0d24a5892331",
- "reference": "452bfc854b60134438e3824b159b0d24a5892331",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c705bee03ade5b47c087807dd9ffaaec8dda2722",
+ "reference": "c705bee03ade5b47c087807dd9ffaaec8dda2722",
"shasum": ""
},
"require": {
@@ -3507,11 +3548,11 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T10:03:52+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
@@ -3574,16 +3615,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c"
+ "reference": "d69930fc337d767607267d57c20a7403d0a822a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c",
- "reference": "285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/d69930fc337d767607267d57c20a7403d0a822a4",
+ "reference": "d69930fc337d767607267d57c20a7403d0a822a4",
"shasum": ""
},
"require": {
@@ -3620,20 +3661,20 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2018-08-10T07:29:05+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "symfony/finder",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a"
+ "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/8a84fcb207451df0013b2c74cbbf1b62d47b999a",
- "reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/54ba444dddc5bd5708a34bd095ea67c6eb54644d",
+ "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d",
"shasum": ""
},
"require": {
@@ -3669,20 +3710,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T11:19:56+00:00"
+ "time": "2018-10-03T08:46:40+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "2fb33cb6eefe6e790e4023f7c534a9e4214252fc"
+ "reference": "3a4498236ade473c52b92d509303e5fd1b211ab1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/2fb33cb6eefe6e790e4023f7c534a9e4214252fc",
- "reference": "2fb33cb6eefe6e790e4023f7c534a9e4214252fc",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3a4498236ade473c52b92d509303e5fd1b211ab1",
+ "reference": "3a4498236ade473c52b92d509303e5fd1b211ab1",
"shasum": ""
},
"require": {
@@ -3723,20 +3764,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2018-08-27T17:45:33+00:00"
+ "time": "2018-10-03T08:48:18+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "2819693b25f480966cbfa13b651abccfed4871ca"
+ "reference": "a0944a9a1d8845da724236cde9a310964acadb1c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2819693b25f480966cbfa13b651abccfed4871ca",
- "reference": "2819693b25f480966cbfa13b651abccfed4871ca",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a0944a9a1d8845da724236cde9a310964acadb1c",
+ "reference": "a0944a9a1d8845da724236cde9a310964acadb1c",
"shasum": ""
},
"require": {
@@ -3812,7 +3853,7 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2018-08-28T06:06:12+00:00"
+ "time": "2018-10-03T12:03:34+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -4051,16 +4092,16 @@
},
{
"name": "symfony/process",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "4d6b125d5293cbceedc2aa10f2c71617e76262e7"
+ "reference": "1dc2977afa7d70f90f3fefbcd84152813558910e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/4d6b125d5293cbceedc2aa10f2c71617e76262e7",
- "reference": "4d6b125d5293cbceedc2aa10f2c71617e76262e7",
+ "url": "https://api.github.com/repos/symfony/process/zipball/1dc2977afa7d70f90f3fefbcd84152813558910e",
+ "reference": "1dc2977afa7d70f90f3fefbcd84152813558910e",
"shasum": ""
},
"require": {
@@ -4096,7 +4137,7 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2018-08-03T10:42:44+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
@@ -4161,16 +4202,16 @@
},
{
"name": "symfony/routing",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "e20f4bb79502c3c0db86d572f7683a30d4143911"
+ "reference": "585f6e2d740393d546978769dd56e496a6233e0b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/e20f4bb79502c3c0db86d572f7683a30d4143911",
- "reference": "e20f4bb79502c3c0db86d572f7683a30d4143911",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/585f6e2d740393d546978769dd56e496a6233e0b",
+ "reference": "585f6e2d740393d546978769dd56e496a6233e0b",
"shasum": ""
},
"require": {
@@ -4234,20 +4275,20 @@
"uri",
"url"
],
- "time": "2018-07-26T11:19:56+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "symfony/serializer",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
- "reference": "40031683816470610af87c2d03ea86d1cf0f0104"
+ "reference": "8bc00ef47a428bfebc4641f29d158e7c56137fcb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/serializer/zipball/40031683816470610af87c2d03ea86d1cf0f0104",
- "reference": "40031683816470610af87c2d03ea86d1cf0f0104",
+ "url": "https://api.github.com/repos/symfony/serializer/zipball/8bc00ef47a428bfebc4641f29d158e7c56137fcb",
+ "reference": "8bc00ef47a428bfebc4641f29d158e7c56137fcb",
"shasum": ""
},
"require": {
@@ -4313,20 +4354,20 @@
],
"description": "Symfony Serializer Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T11:58:24+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "symfony/translation",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "9749930bfc825139aadd2d28461ddbaed6577862"
+ "reference": "94bc3a79008e6640defedf5e14eb3b4f20048352"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/9749930bfc825139aadd2d28461ddbaed6577862",
- "reference": "9749930bfc825139aadd2d28461ddbaed6577862",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/94bc3a79008e6640defedf5e14eb3b4f20048352",
+ "reference": "94bc3a79008e6640defedf5e14eb3b4f20048352",
"shasum": ""
},
"require": {
@@ -4381,20 +4422,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2018-07-26T11:19:56+00:00"
+ "time": "2018-10-02T16:33:53+00:00"
},
{
"name": "symfony/validator",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/validator.git",
- "reference": "5a9ca502663e32aed3302b00121f978d70a09ab9"
+ "reference": "9f8dbf0dceb03815c3160a279bf8cf4f8018a1c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/validator/zipball/5a9ca502663e32aed3302b00121f978d70a09ab9",
- "reference": "5a9ca502663e32aed3302b00121f978d70a09ab9",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/9f8dbf0dceb03815c3160a279bf8cf4f8018a1c5",
+ "reference": "9f8dbf0dceb03815c3160a279bf8cf4f8018a1c5",
"shasum": ""
},
"require": {
@@ -4466,20 +4507,20 @@
],
"description": "Symfony Validator Component",
"homepage": "https://symfony.com",
- "time": "2018-08-07T09:33:53+00:00"
+ "time": "2018-10-02T16:33:53+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "f62a394bd3de96f2f5e8f4c7d685035897fb3cb3"
+ "reference": "ff8ac19e97e5c7c3979236b584719a1190f84181"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f62a394bd3de96f2f5e8f4c7d685035897fb3cb3",
- "reference": "f62a394bd3de96f2f5e8f4c7d685035897fb3cb3",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ff8ac19e97e5c7c3979236b584719a1190f84181",
+ "reference": "ff8ac19e97e5c7c3979236b584719a1190f84181",
"shasum": ""
},
"require": {
@@ -4535,20 +4576,20 @@
"debug",
"dump"
],
- "time": "2018-07-26T11:19:56+00:00"
+ "time": "2018-10-02T16:33:53+00:00"
},
{
"name": "symfony/yaml",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8"
+ "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/c2f4812ead9f847cb69e90917ca7502e6892d6b8",
- "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/640b6c27fed4066d64b64d5903a86043f4a4de7f",
+ "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f",
"shasum": ""
},
"require": {
@@ -4594,7 +4635,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2018-08-10T07:34:36+00:00"
+ "time": "2018-10-02T16:33:53+00:00"
},
{
"name": "twig/twig",
@@ -5238,12 +5279,12 @@
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkSelenium2Driver.git",
- "reference": "f4efaf52fed6a98bf6037a04f3fdef94dc0c8841"
+ "reference": "8684ee4e634db7abda9039ea53545f86fc1e105a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/f4efaf52fed6a98bf6037a04f3fdef94dc0c8841",
- "reference": "f4efaf52fed6a98bf6037a04f3fdef94dc0c8841",
+ "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/8684ee4e634db7abda9039ea53545f86fc1e105a",
+ "reference": "8684ee4e634db7abda9039ea53545f86fc1e105a",
"shasum": ""
},
"require": {
@@ -5291,7 +5332,7 @@
"testing",
"webdriver"
],
- "time": "2018-07-23T10:51:47+00:00"
+ "time": "2018-10-10T12:39:06+00:00"
},
{
"name": "doctrine/instantiator",
@@ -5349,22 +5390,28 @@
},
{
"name": "drupal/coder",
- "version": "8.2.12",
+ "version": "8.3.1",
"source": {
"type": "git",
"url": "https://git.drupal.org/project/coder.git",
- "reference": "984c54a7b1e8f27ff1c32348df69712afd86b17f"
+ "reference": "29a25627e7148b3119c84f18e087fc3b8c85b959"
},
"require": {
"ext-mbstring": "*",
"php": ">=5.4.0",
- "squizlabs/php_codesniffer": ">=2.8.1 <3.0",
+ "squizlabs/php_codesniffer": "^3.0.1",
"symfony/yaml": ">=2.0.0"
},
"require-dev": {
"phpunit/phpunit": ">=3.7 <6"
},
"type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-0": {
+ "Drupal\\": "coder_sniffer/Drupal/",
+ "DrupalPractice\\": "coder_sniffer/Drupal/"
+ }
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0+"
@@ -5376,7 +5423,7 @@
"phpcs",
"standards"
],
- "time": "2017-03-18T10:28:49+00:00"
+ "time": "2018-09-21T14:22:49+00:00"
},
{
"name": "fabpot/goutte",
@@ -6968,64 +7015,37 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "2.9.1",
+ "version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62"
+ "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62",
- "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e",
+ "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
- "php": ">=5.1.2"
+ "php": ">=5.4.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.0"
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"bin": [
- "scripts/phpcs",
- "scripts/phpcbf"
+ "bin/phpcs",
+ "bin/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.x-dev"
+ "dev-master": "3.x-dev"
}
},
- "autoload": {
- "classmap": [
- "CodeSniffer.php",
- "CodeSniffer/CLI.php",
- "CodeSniffer/Exception.php",
- "CodeSniffer/File.php",
- "CodeSniffer/Fixer.php",
- "CodeSniffer/Report.php",
- "CodeSniffer/Reporting.php",
- "CodeSniffer/Sniff.php",
- "CodeSniffer/Tokens.php",
- "CodeSniffer/Reports/",
- "CodeSniffer/Tokenizers/",
- "CodeSniffer/DocGenerators/",
- "CodeSniffer/Standards/AbstractPatternSniff.php",
- "CodeSniffer/Standards/AbstractScopeSniff.php",
- "CodeSniffer/Standards/AbstractVariableSniff.php",
- "CodeSniffer/Standards/IncorrectPatternException.php",
- "CodeSniffer/Standards/Generic/Sniffs/",
- "CodeSniffer/Standards/MySource/Sniffs/",
- "CodeSniffer/Standards/PEAR/Sniffs/",
- "CodeSniffer/Standards/PSR1/Sniffs/",
- "CodeSniffer/Standards/PSR2/Sniffs/",
- "CodeSniffer/Standards/Squiz/Sniffs/",
- "CodeSniffer/Standards/Zend/Sniffs/"
- ]
- },
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
@@ -7042,11 +7062,11 @@
"phpcs",
"standards"
],
- "time": "2017-05-22T02:43:20+00:00"
+ "time": "2018-09-23T23:08:17+00:00"
},
{
"name": "symfony/browser-kit",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
@@ -7103,16 +7123,16 @@
},
{
"name": "symfony/phpunit-bridge",
- "version": "v3.4.15",
+ "version": "v3.4.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
- "reference": "f4fde1ede82c7ca2a4f06cf48521a185b26c0fed"
+ "reference": "76e013a98031356604e5a730c9eb22713dc4dda4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/f4fde1ede82c7ca2a4f06cf48521a185b26c0fed",
- "reference": "f4fde1ede82c7ca2a4f06cf48521a185b26c0fed",
+ "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/76e013a98031356604e5a730c9eb22713dc4dda4",
+ "reference": "76e013a98031356604e5a730c9eb22713dc4dda4",
"shasum": ""
},
"require": {
@@ -7165,7 +7185,7 @@
],
"description": "Symfony PHPUnit Bridge",
"homepage": "https://symfony.com",
- "time": "2018-08-27T15:17:06+00:00"
+ "time": "2018-10-02T12:28:39+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 575ffbd3c..130183e73 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -82,7 +82,7 @@ class Drupal {
/**
* The current system version.
*/
- const VERSION = '8.6.1';
+ const VERSION = '8.6.2';
/**
* Core API compatibility.
diff --git a/core/lib/Drupal/Component/Utility/UrlHelper.php b/core/lib/Drupal/Component/Utility/UrlHelper.php
index 1d133c9d6..9e2365c1a 100644
--- a/core/lib/Drupal/Component/Utility/UrlHelper.php
+++ b/core/lib/Drupal/Component/Utility/UrlHelper.php
@@ -248,6 +248,16 @@ class UrlHelper {
* Exception thrown when a either $url or $bath_url are not fully qualified.
*/
public static function externalIsLocal($url, $base_url) {
+ // Some browsers treat \ as / so normalize to forward slashes.
+ $url = str_replace('\\', '/', $url);
+
+ // Leading control characters may be ignored or mishandled by browsers, so
+ // assume such a path may lead to an non-local location. The \p{C} character
+ // class matches all UTF-8 control, unassigned, and private characters.
+ if (preg_match('/^\p{C}/u', $url) !== 0) {
+ return FALSE;
+ }
+
$url_parts = parse_url($url);
$base_parts = parse_url($base_url);
diff --git a/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
index 8397bdef4..67a4aae42 100644
--- a/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
@@ -8,7 +8,6 @@ use Drupal\Core\Routing\LocalRedirectResponse;
use Drupal\Core\Routing\RequestContext;
use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -129,36 +128,6 @@ class RedirectResponseSubscriber implements EventSubscriberInterface {
return $destination;
}
- /**
- * Sanitize the destination parameter to prevent open redirect attacks.
- *
- * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
- * The Event to process.
- */
- public function sanitizeDestination(GetResponseEvent $event) {
- $request = $event->getRequest();
- // Sanitize the destination parameter (which is often used for redirects) to
- // prevent open redirect attacks leading to other domains. Sanitize both
- // $_GET['destination'] and $_REQUEST['destination'] to protect code that
- // relies on either, but do not sanitize $_POST to avoid interfering with
- // unrelated form submissions. The sanitization happens here because
- // url_is_external() requires the variable system to be available.
- $query_info = $request->query;
- $request_info = $request->request;
- if ($query_info->has('destination') || $request_info->has('destination')) {
- // If the destination is an external URL, remove it.
- if ($query_info->has('destination') && UrlHelper::isExternal($query_info->get('destination'))) {
- $query_info->remove('destination');
- $request_info->remove('destination');
- }
- // If there's still something in $_REQUEST['destination'] that didn't come
- // from $_GET, check it too.
- if ($request_info->has('destination') && (!$query_info->has('destination') || $request_info->get('destination') != $query_info->get('destination')) && UrlHelper::isExternal($request_info->get('destination'))) {
- $request_info->remove('destination');
- }
- }
- }
-
/**
* Registers the methods in this class that should be listeners.
*
@@ -167,7 +136,6 @@ class RedirectResponseSubscriber implements EventSubscriberInterface {
*/
public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = ['checkRedirectUrl'];
- $events[KernelEvents::REQUEST][] = ['sanitizeDestination', 100];
return $events;
}
diff --git a/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php
index e5361492b..27e5c76e5 100644
--- a/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php
+++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php
@@ -18,6 +18,20 @@ use Drupal\Core\Site\Settings;
*/
class PhpMail implements MailInterface {
+ /**
+ * The configuration factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * PhpMail constructor.
+ */
+ public function __construct() {
+ $this->configFactory = \Drupal::configFactory();
+ }
+
/**
* Concatenates and wraps the email body for plain-text mails.
*
@@ -86,7 +100,10 @@ class PhpMail implements MailInterface {
// On most non-Windows systems, the "-f" option to the sendmail command
// is used to set the Return-Path. There is no space between -f and
// the value of the return path.
- $additional_headers = isset($message['Return-Path']) ? '-f' . $message['Return-Path'] : '';
+ // We validate the return path, unless it is equal to the site mail, which
+ // we assume to be safe.
+ $site_mail = $this->configFactory->get('system.site')->get('mail');
+ $additional_headers = isset($message['Return-Path']) && ($site_mail === $message['Return-Path'] || static::_isShellSafe($message['Return-Path'])) ? '-f' . $message['Return-Path'] : '';
$mail_result = @mail(
$message['to'],
$mail_subject,
@@ -112,4 +129,33 @@ class PhpMail implements MailInterface {
return $mail_result;
}
+ /**
+ * Disallows potentially unsafe shell characters.
+ *
+ * Functionally similar to PHPMailer::isShellSafe() which resulted from
+ * CVE-2016-10045. Note that escapeshellarg and escapeshellcmd are inadequate
+ * for this purpose.
+ *
+ * @param string $string
+ * The string to be validated.
+ *
+ * @return bool
+ * True if the string is shell-safe.
+ *
+ * @see https://github.com/PHPMailer/PHPMailer/issues/924
+ * @see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.phpmailer.php#L1430
+ *
+ * @todo Rename to ::isShellSafe() and/or discuss whether this is the correct
+ * location for this helper.
+ */
+ protected static function _isShellSafe($string) {
+ if (escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])) {
+ return FALSE;
+ }
+ if (preg_match('/[^a-zA-Z0-9@_\-.]/', $string) !== 0) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
}
diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php
index b85737f3c..d0690fee2 100644
--- a/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php
+++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php
@@ -43,6 +43,15 @@ class PathProcessorAlias implements InboundPathProcessorInterface, OutboundPathP
if (empty($options['alias'])) {
$langcode = isset($options['language']) ? $options['language']->getId() : NULL;
$path = $this->aliasManager->getAliasByPath($path, $langcode);
+ // Ensure the resulting path has at most one leading slash, to prevent it
+ // becoming an external URL without a protocol like //example.com. This
+ // is done in \Drupal\Core\Routing\UrlGenerator::generateFromRoute()
+ // also, to protect against this problem in arbitrary path processors,
+ // but it is duplicated here to protect any other URL generation code
+ // that might call this method separately.
+ if (strpos($path, '//') === 0) {
+ $path = '/' . ltrim($path, '/');
+ }
}
return $path;
}
diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php
index 3b5c8d256..853a5f7b4 100644
--- a/core/lib/Drupal/Core/Routing/UrlGenerator.php
+++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php
@@ -297,6 +297,11 @@ class UrlGenerator implements UrlGeneratorInterface {
if ($options['path_processing']) {
$path = $this->processPath($path, $options, $generated_url);
}
+ // Ensure the resulting path has at most one leading slash, to prevent it
+ // becoming an external URL without a protocol like //example.com.
+ if (strpos($path, '//') === 0) {
+ $path = '/' . ltrim($path, '/');
+ }
// The contexts base URL is already encoded
// (see Symfony\Component\HttpFoundation\Request).
$path = str_replace($this->decodedChars[0], $this->decodedChars[1], rawurlencode($path));
diff --git a/core/lib/Drupal/Core/Security/RequestSanitizer.php b/core/lib/Drupal/Core/Security/RequestSanitizer.php
index 3f48f3d59..e1626ed38 100644
--- a/core/lib/Drupal/Core/Security/RequestSanitizer.php
+++ b/core/lib/Drupal/Core/Security/RequestSanitizer.php
@@ -90,7 +90,8 @@ class RequestSanitizer {
}
if ($bag->has('destination')) {
- $destination_dangerous_keys = static::checkDestination($bag->get('destination'), $whitelist);
+ $destination = $bag->get('destination');
+ $destination_dangerous_keys = static::checkDestination($destination, $whitelist);
if (!empty($destination_dangerous_keys)) {
// The destination is removed rather than sanitized because the URL
// generator service is not available and this method is called very
@@ -101,6 +102,16 @@ class RequestSanitizer {
trigger_error(sprintf('Potentially unsafe destination removed from %s parameter bag because it contained the following keys: %s', $bag_name, implode(', ', $destination_dangerous_keys)));
}
}
+ // Sanitize the destination parameter (which is often used for redirects)
+ // to prevent open redirect attacks leading to other domains.
+ if (UrlHelper::isExternal($destination)) {
+ // The destination is removed because it is an external URL.
+ $bag->remove('destination');
+ $sanitized = TRUE;
+ if ($log_sanitized_keys) {
+ trigger_error(sprintf('Potentially unsafe destination removed from %s parameter bag because it points to an external URL.', $bag_name));
+ }
+ }
}
return $sanitized;
}
diff --git a/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php b/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php
index 0a18d7fa0..cdb199824 100644
--- a/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php
+++ b/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php
@@ -3,6 +3,8 @@
namespace Drupal\Tests\block\Functional\Views;
use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\Tests\block\Functional\AssertBlockAppearsTrait;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
@@ -360,14 +362,16 @@ class DisplayBlockTest extends ViewTestBase {
$this->drupalGet('test-page');
$id = 'block:block=' . $block->id() . ':langcode=en|entity.view.edit_form:view=test_view_block:location=block&name=test_view_block&display_id=block_1&langcode=en';
+ $id_token = Crypt::hmacBase64($id, Settings::getHashSalt() . $this->container->get('private_key')->get());
$cached_id = 'block:block=' . $cached_block->id() . ':langcode=en|entity.view.edit_form:view=test_view_block:location=block&name=test_view_block&display_id=block_1&langcode=en';
+ $cached_id_token = Crypt::hmacBase64($cached_id, Settings::getHashSalt() . $this->container->get('private_key')->get());
// @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder()
- $this->assertRaw('
$id]) . '>
', format_string('Contextual link placeholder with id @id exists.', ['@id' => $id]));
- $this->assertRaw(' $cached_id]) . '>
', format_string('Contextual link placeholder with id @id exists.', ['@id' => $cached_id]));
+ $this->assertRaw(' $id, 'data-contextual-token' => $id_token]) . '>
', format_string('Contextual link placeholder with id @id exists.', ['@id' => $id]));
+ $this->assertRaw(' $cached_id, 'data-contextual-token' => $cached_id_token]) . '>
', format_string('Contextual link placeholder with id @id exists.', ['@id' => $cached_id]));
// Get server-rendered contextual links.
// @see \Drupal\contextual\Tests\ContextualDynamicContextTest:renderContextualLinks()
- $post = ['ids[0]' => $id, 'ids[1]' => $cached_id];
+ $post = ['ids[0]' => $id, 'ids[1]' => $cached_id, 'tokens[0]' => $id_token, 'tokens[1]' => $cached_id_token];
$url = 'contextual/render?_format=json,destination=test-page';
$this->getSession()->getDriver()->getClient()->request('POST', $url, $post);
$this->assertResponse(200);
diff --git a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraint.php b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraint.php
index 7f7c756b6..4fcde3605 100644
--- a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraint.php
+++ b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraint.php
@@ -16,5 +16,6 @@ class ModerationStateConstraint extends Constraint {
public $message = 'Invalid state transition from %from to %to';
public $invalidStateMessage = 'State %state does not exist on %workflow workflow';
+ public $invalidTransitionAccess = 'You do not have access to transition from %original_state to %new_state';
}
diff --git a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php
index 65fc2a0c5..c3b9c815f 100644
--- a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php
+++ b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php
@@ -2,10 +2,13 @@
namespace Drupal\content_moderation\Plugin\Validation\Constraint;
+use Drupal\content_moderation\StateTransitionValidationInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\content_moderation\ModerationInformationInterface;
+use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
@@ -29,6 +32,20 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
*/
protected $moderationInformation;
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $currentUser;
+
+ /**
+ * The state transition validation service.
+ *
+ * @var \Drupal\content_moderation\StateTransitionValidationInterface
+ */
+ protected $stateTransitionValidation;
+
/**
* Creates a new ModerationStateConstraintValidator instance.
*
@@ -36,10 +53,16 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
* The entity type manager.
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_information
* The moderation information.
+ * @param \Drupal\Core\Session\AccountInterface $current_user
+ * The current user.
+ * @param \Drupal\content_moderation\StateTransitionValidationInterface $state_transition_validation
+ * The state transition validation service.
*/
- public function __construct(EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_information) {
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_information, AccountInterface $current_user, StateTransitionValidationInterface $state_transition_validation) {
$this->entityTypeManager = $entity_type_manager;
$this->moderationInformation = $moderation_information;
+ $this->currentUser = $current_user;
+ $this->stateTransitionValidation = $state_transition_validation;
}
/**
@@ -48,7 +71,9 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
- $container->get('content_moderation.moderation_information')
+ $container->get('content_moderation.moderation_information'),
+ $container->get('current_user'),
+ $container->get('content_moderation.state_transition_validation')
);
}
@@ -76,32 +101,59 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
return;
}
+ $new_state = $workflow->getTypePlugin()->getState($entity->moderation_state->value);
+ $original_state = $this->getOriginalOrInitialState($entity);
+
// If a new state is being set and there is an existing state, validate
// there is a valid transition between them.
+ if (!$original_state->canTransitionTo($new_state->id())) {
+ $this->context->addViolation($constraint->message, [
+ '%from' => $original_state->label(),
+ '%to' => $new_state->label(),
+ ]);
+ }
+ else {
+ // If we're sure the transition exists, make sure the user has permission
+ // to use it.
+ if (!$this->stateTransitionValidation->isTransitionValid($workflow, $original_state, $new_state, $this->currentUser)) {
+ $this->context->addViolation($constraint->invalidTransitionAccess, [
+ '%original_state' => $original_state->label(),
+ '%new_state' => $new_state->label(),
+ ]);
+ }
+ }
+ }
+
+ /**
+ * Gets the original or initial state of the given entity.
+ *
+ * When a state is being validated, the original state is used to validate
+ * that a valid transition exists for target state and the user has access
+ * to the transition between those two states. If the entity has been
+ * moderated before, we can load the original unmodified revision and
+ * translation for this state.
+ *
+ * If the entity is new we need to load the initial state from the workflow.
+ * Even if a value was assigned to the moderation_state field, the initial
+ * state is used to compute an appropriate transition for the purposes of
+ * validation.
+ *
+ * @return \Drupal\workflows\StateInterface
+ * The original or default moderation state.
+ */
+ protected function getOriginalOrInitialState(ContentEntityInterface $entity) {
+ $state = NULL;
+ $workflow_type = $this->moderationInformation->getWorkflowForEntity($entity)->getTypePlugin();
if (!$entity->isNew() && !$this->isFirstTimeModeration($entity)) {
$original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadRevision($entity->getLoadedRevisionId());
if (!$entity->isDefaultTranslation() && $original_entity->hasTranslation($entity->language()->getId())) {
$original_entity = $original_entity->getTranslation($entity->language()->getId());
}
-
- // If the state of the original entity doesn't exist on the workflow,
- // we cannot do any further validation of transitions, because none will
- // be setup for a state that doesn't exist. Instead allow any state to
- // take its place.
- if (!$workflow->getTypePlugin()->hasState($original_entity->moderation_state->value)) {
- return;
- }
-
- $new_state = $workflow->getTypePlugin()->getState($entity->moderation_state->value);
- $original_state = $workflow->getTypePlugin()->getState($original_entity->moderation_state->value);
-
- if (!$original_state->canTransitionTo($new_state->id())) {
- $this->context->addViolation($constraint->message, [
- '%from' => $original_state->label(),
- '%to' => $new_state->label(),
- ]);
+ if ($workflow_type->hasState($original_entity->moderation_state->value)) {
+ $state = $workflow_type->getState($original_entity->moderation_state->value);
}
}
+ return $state ?: $workflow_type->getInitialState($entity);
}
/**
diff --git a/core/modules/content_moderation/src/StateTransitionValidation.php b/core/modules/content_moderation/src/StateTransitionValidation.php
index 01b2ad845..35d657e55 100644
--- a/core/modules/content_moderation/src/StateTransitionValidation.php
+++ b/core/modules/content_moderation/src/StateTransitionValidation.php
@@ -4,7 +4,9 @@ namespace Drupal\content_moderation;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Session\AccountInterface;
+use Drupal\workflows\StateInterface;
use Drupal\workflows\Transition;
+use Drupal\workflows\WorkflowInterface;
/**
* Validates whether a certain state transition is allowed.
@@ -47,4 +49,12 @@ class StateTransitionValidation implements StateTransitionValidationInterface {
});
}
+ /**
+ * {@inheritdoc}
+ */
+ public function isTransitionValid(WorkflowInterface $workflow, StateInterface $original_state, StateInterface $new_state, AccountInterface $user) {
+ $transition = $workflow->getTypePlugin()->getTransitionFromStateToState($original_state->id(), $new_state->id());
+ return $user->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id());
+ }
+
}
diff --git a/core/modules/content_moderation/src/StateTransitionValidationInterface.php b/core/modules/content_moderation/src/StateTransitionValidationInterface.php
index 1acbf052f..c793fe53e 100644
--- a/core/modules/content_moderation/src/StateTransitionValidationInterface.php
+++ b/core/modules/content_moderation/src/StateTransitionValidationInterface.php
@@ -4,6 +4,8 @@ namespace Drupal\content_moderation;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Session\AccountInterface;
+use Drupal\workflows\StateInterface;
+use Drupal\workflows\WorkflowInterface;
/**
* Validates whether a certain state transition is allowed.
@@ -23,4 +25,21 @@ interface StateTransitionValidationInterface {
*/
public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user);
+ /**
+ * Checks if a transition between two states if valid for the given user.
+ *
+ * @param \Drupal\workflows\WorkflowInterface $workflow
+ * The workflow entity.
+ * @param \Drupal\workflows\StateInterface $original_state
+ * The original workflow state.
+ * @param \Drupal\workflows\StateInterface $new_state
+ * The new workflow state.
+ * @param \Drupal\Core\Session\AccountInterface $user
+ * The user to validate.
+ *
+ * @return bool
+ * Returns TRUE if transition is valid, otherwise FALSE.
+ */
+ public function isTransitionValid(WorkflowInterface $workflow, StateInterface $original_state, StateInterface $new_state, AccountInterface $user);
+
}
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php
index 11deaa72c..5fd168d0b 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php
@@ -158,32 +158,15 @@ class ModerationStateNodeTest extends ModerationStateTestBase {
]);
$this->drupalLogin($limited_user);
- // Check the user can add content, but can't see the moderation state
- // select.
+ // Check the user can see the content entity form, but can't see the
+ // moderation state select or save the entity form.
$this->drupalGet('node/add/moderated_content');
$session_assert->statusCodeEquals(200);
$session_assert->fieldNotExists('moderation_state[0][state]');
$this->drupalPostForm(NULL, [
'title[0][value]' => 'moderated content',
], 'Save');
-
- // Manually move the content to archived because the user doesn't have
- // permission to do this.
- $node = $this->getNodeByTitle('moderated content');
- $node->moderation_state->value = 'archived';
- $node->save();
-
- // Check the user can see the current state but not the select.
- $this->drupalGet('node/' . $node->id() . '/edit');
- $session_assert->statusCodeEquals(200);
- $session_assert->pageTextContains('Archived');
- $session_assert->fieldNotExists('moderation_state[0][state]');
- $this->drupalPostForm(NULL, [], 'Save');
-
- // When saving they should still be on the edit form, and see the validation
- // error message.
- $session_assert->pageTextContains('Edit Moderated content moderated content');
- $session_assert->pageTextContains('Invalid state transition from Archived to Archived');
+ $session_assert->pageTextContains('You do not have access to transition from Draft to Draft');
}
}
diff --git a/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php b/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php
index dc1e7f691..df90bf63c 100644
--- a/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php
@@ -7,6 +7,7 @@ use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* @coversDefaultClass \Drupal\content_moderation\Plugin\Validation\Constraint\ModerationStateConstraintValidator
@@ -15,6 +16,7 @@ use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
class EntityStateChangeValidationTest extends KernelTestBase {
use ContentModerationTestTrait;
+ use UserCreationTrait;
/**
* {@inheritdoc}
@@ -29,6 +31,13 @@ class EntityStateChangeValidationTest extends KernelTestBase {
'workflows',
];
+ /**
+ * An admin user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $adminUser;
+
/**
* {@inheritdoc}
*/
@@ -40,6 +49,9 @@ class EntityStateChangeValidationTest extends KernelTestBase {
$this->installEntitySchema('user');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
+ $this->installSchema('system', ['sequences']);
+
+ $this->adminUser = $this->createUser(array_keys($this->container->get('user.permissions')->getPermissions()));
}
/**
@@ -48,6 +60,8 @@ class EntityStateChangeValidationTest extends KernelTestBase {
* @covers ::validate
*/
public function testValidTransition() {
+ $this->setCurrentUser($this->adminUser);
+
$node_type = NodeType::create([
'type' => 'example',
]);
@@ -76,6 +90,8 @@ class EntityStateChangeValidationTest extends KernelTestBase {
* @covers ::validate
*/
public function testInvalidTransition() {
+ $this->setCurrentUser($this->adminUser);
+
$node_type = NodeType::create([
'type' => 'example',
]);
@@ -125,6 +141,7 @@ class EntityStateChangeValidationTest extends KernelTestBase {
* Test validation with content that has no initial state or an invalid state.
*/
public function testInvalidStateWithoutExisting() {
+ $this->setCurrentUser($this->adminUser);
// Create content without moderation enabled for the content type.
$node_type = NodeType::create([
'type' => 'example',
@@ -156,15 +173,24 @@ class EntityStateChangeValidationTest extends KernelTestBase {
// validating.
$workflow->getTypePlugin()->deleteState('deleted_state');
$workflow->save();
+
+ // When there is an invalid state, the content will revert to "draft". This
+ // will allow a draft to draft transition.
$node->moderation_state->value = 'draft';
$violations = $node->validate();
$this->assertCount(0, $violations);
+ // This will disallow a draft to archived transition.
+ $node->moderation_state->value = 'archived';
+ $violations = $node->validate();
+ $this->assertCount(1, $violations);
}
/**
* Test state transition validation with multiple languages.
*/
public function testInvalidStateMultilingual() {
+ $this->setCurrentUser($this->adminUser);
+
ConfigurableLanguage::createFromLangcode('fr')->save();
$node_type = NodeType::create([
'type' => 'example',
@@ -220,6 +246,8 @@ class EntityStateChangeValidationTest extends KernelTestBase {
* Tests that content without prior moderation information can be moderated.
*/
public function testExistingContentWithNoModeration() {
+ $this->setCurrentUser($this->adminUser);
+
$node_type = NodeType::create([
'type' => 'example',
]);
@@ -254,6 +282,8 @@ class EntityStateChangeValidationTest extends KernelTestBase {
* Tests that content without prior moderation information can be translated.
*/
public function testExistingMultilingualContentWithNoModeration() {
+ $this->setCurrentUser($this->adminUser);
+
// Enable French.
ConfigurableLanguage::createFromLangcode('fr')->save();
@@ -293,4 +323,81 @@ class EntityStateChangeValidationTest extends KernelTestBase {
$node_fr->save();
}
+ /**
+ * @dataProvider transitionAccessValidationTestCases
+ */
+ public function testTransitionAccessValidation($permissions, $target_state, $messages) {
+ $node_type = NodeType::create([
+ 'type' => 'example',
+ ]);
+ $node_type->save();
+ $workflow = $this->createEditorialWorkflow();
+ $workflow->getTypePlugin()->addState('foo', 'Foo');
+ $workflow->getTypePlugin()->addTransition('draft_to_foo', 'Draft to foo', ['draft'], 'foo');
+ $workflow->getTypePlugin()->addTransition('foo_to_foo', 'Foo to foo', ['foo'], 'foo');
+ $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
+ $workflow->save();
+
+ $this->setCurrentUser($this->createUser($permissions));
+
+ $node = Node::create([
+ 'type' => 'example',
+ 'title' => 'Test content',
+ 'moderation_state' => $target_state,
+ ]);
+ $this->assertTrue($node->isNew());
+ $violations = $node->validate();
+ $this->assertCount(count($messages), $violations);
+ foreach ($messages as $i => $message) {
+ $this->assertEquals($message, $violations->get($i)->getMessage());
+ }
+ }
+
+ /**
+ * Test cases for ::testTransitionAccessValidation.
+ */
+ public function transitionAccessValidationTestCases() {
+ return [
+ 'Invalid transition, no permissions validated' => [
+ [],
+ 'archived',
+ ['Invalid state transition from Draft to Archived'],
+ ],
+ 'Valid transition, missing permission' => [
+ [],
+ 'published',
+ ['You do not have access to transition from Draft to Published'],
+ ],
+ 'Valid transition, granted published permission' => [
+ ['use editorial transition publish'],
+ 'published',
+ [],
+ ],
+ 'Valid transition, granted draft permission' => [
+ ['use editorial transition create_new_draft'],
+ 'draft',
+ [],
+ ],
+ 'Valid transition, incorrect permission granted' => [
+ ['use editorial transition create_new_draft'],
+ 'published',
+ ['You do not have access to transition from Draft to Published'],
+ ],
+ // Test with an additional state and set of transitions, since the
+ // "published" transition can start from either "draft" or "published", it
+ // does not capture bugs that fail to correctly distinguish the initial
+ // workflow state from the set state of a new entity.
+ 'Valid transition, granted foo permission' => [
+ ['use editorial transition draft_to_foo'],
+ 'foo',
+ [],
+ ],
+ 'Valid transition, incorrect foo permission granted' => [
+ ['use editorial transition foo_to_foo'],
+ 'foo',
+ ['You do not have access to transition from Draft to Foo'],
+ ],
+ ];
+ }
+
}
diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index b9d61b76d..8b9fc36fd 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -191,13 +191,19 @@ function _contextual_links_to_id($contextual_links) {
/**
* Unserializes the result of _contextual_links_to_id().
*
- * @see _contextual_links_to_id
+ * Note that $id is user input. Before calling this method the ID should be
+ * checked against the token stored in the 'data-contextual-token' attribute
+ * which is passed via the 'tokens' request parameter to
+ * \Drupal\contextual\ContextualController::render().
*
* @param string $id
* A serialized representation of a #contextual_links property value array.
*
* @return array
* The value for a #contextual_links property.
+ *
+ * @see _contextual_links_to_id()
+ * @see \Drupal\contextual\ContextualController::render()
*/
function _contextual_id_to_links($id) {
$contextual_links = [];
diff --git a/core/modules/contextual/contextual.post_update.php b/core/modules/contextual/contextual.post_update.php
new file mode 100644
index 000000000..8decad05f
--- /dev/null
+++ b/core/modules/contextual/contextual.post_update.php
@@ -0,0 +1,14 @@
+ {
- const html = storage.getItem(`Drupal.contextual.${contextualID}`);
+ const uncachedIDs = [];
+ const uncachedTokens = [];
+ ids.forEach(contextualID => {
+ const html = storage.getItem(`Drupal.contextual.${contextualID.id}`);
if (html && html.length) {
// Initialize after the current execution cycle, to make the AJAX
// request for retrieving the uncached contextual links as soon as
@@ -182,13 +186,14 @@
// Drupal.contextual.collection.
window.setTimeout(() => {
initContextual(
- $context.find(`[data-contextual-id="${contextualID}"]`),
+ $context.find(`[data-contextual-id="${contextualID.id}"]`),
html,
);
});
- return false;
+ return;
}
- return true;
+ uncachedIDs.push(contextualID.id);
+ uncachedTokens.push(contextualID.token);
});
// Perform an AJAX request to let the server render the contextual links
@@ -197,7 +202,7 @@
$.ajax({
url: Drupal.url('contextual/render'),
type: 'POST',
- data: { 'ids[]': uncachedIDs },
+ data: { 'ids[]': uncachedIDs, 'tokens[]': uncachedTokens },
dataType: 'json',
success(results) {
_.each(results, (html, contextualID) => {
diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js
index 049233b4e..d51eba21a 100644
--- a/core/modules/contextual/js/contextual.js
+++ b/core/modules/contextual/js/contextual.js
@@ -95,25 +95,31 @@
var ids = [];
$placeholders.each(function () {
- ids.push($(this).attr('data-contextual-id'));
+ ids.push({
+ id: $(this).attr('data-contextual-id'),
+ token: $(this).attr('data-contextual-token')
+ });
});
- var uncachedIDs = _.filter(ids, function (contextualID) {
- var html = storage.getItem('Drupal.contextual.' + contextualID);
+ var uncachedIDs = [];
+ var uncachedTokens = [];
+ ids.forEach(function (contextualID) {
+ var html = storage.getItem('Drupal.contextual.' + contextualID.id);
if (html && html.length) {
window.setTimeout(function () {
- initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html);
+ initContextual($context.find('[data-contextual-id="' + contextualID.id + '"]'), html);
});
- return false;
+ return;
}
- return true;
+ uncachedIDs.push(contextualID.id);
+ uncachedTokens.push(contextualID.token);
});
if (uncachedIDs.length > 0) {
$.ajax({
url: Drupal.url('contextual/render'),
type: 'POST',
- data: { 'ids[]': uncachedIDs },
+ data: { 'ids[]': uncachedIDs, 'tokens[]': uncachedTokens },
dataType: 'json',
success: function success(results) {
_.each(results, function (html, contextualID) {
diff --git a/core/modules/contextual/src/ContextualController.php b/core/modules/contextual/src/ContextualController.php
index 58e42ecd6..d05c6a852 100644
--- a/core/modules/contextual/src/ContextualController.php
+++ b/core/modules/contextual/src/ContextualController.php
@@ -2,8 +2,10 @@
namespace Drupal\contextual;
+use Drupal\Component\Utility\Crypt;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Site\Settings;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -63,8 +65,16 @@ class ContextualController implements ContainerInjectionInterface {
throw new BadRequestHttpException(t('No contextual ids specified.'));
}
+ $tokens = $request->request->get('tokens');
+ if (!isset($tokens)) {
+ throw new BadRequestHttpException(t('No contextual ID tokens specified.'));
+ }
+
$rendered = [];
- foreach ($ids as $id) {
+ foreach ($ids as $key => $id) {
+ if (!isset($tokens[$key]) || !Crypt::hashEquals($tokens[$key], Crypt::hmacBase64($id, Settings::getHashSalt() . \Drupal::service('private_key')->get()))) {
+ throw new BadRequestHttpException('Invalid contextual ID specified.');
+ }
$element = [
'#type' => 'contextual_links',
'#contextual_links' => _contextual_id_to_links($id),
diff --git a/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php b/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php
index 97afde9a2..5e993941a 100644
--- a/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php
+++ b/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php
@@ -2,6 +2,8 @@
namespace Drupal\contextual\Element;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Site\Settings;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Render\Element\RenderElement;
use Drupal\Component\Render\FormattableMarkup;
@@ -43,7 +45,12 @@ class ContextualLinksPlaceholder extends RenderElement {
* @see _contextual_links_to_id()
*/
public static function preRenderPlaceholder(array $element) {
- $element['#markup'] = new FormattableMarkup('', ['@attributes' => new Attribute(['data-contextual-id' => $element['#id']])]);
+ $token = Crypt::hmacBase64($element['#id'], Settings::getHashSalt() . \Drupal::service('private_key')->get());
+ $attribute = new Attribute([
+ 'data-contextual-id' => $element['#id'],
+ 'data-contextual-token' => $token,
+ ]);
+ $element['#markup'] = new FormattableMarkup('', ['@attributes' => $attribute]);
return $element;
}
diff --git a/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php b/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php
index 340b60821..74a6d504e 100644
--- a/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php
+++ b/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php
@@ -3,9 +3,10 @@
namespace Drupal\Tests\contextual\Functional;
use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
-use Drupal\Core\Template\Attribute;
use Drupal\Tests\BrowserTestBase;
/**
@@ -140,17 +141,76 @@ class ContextualDynamicContextTest extends BrowserTestBase {
$this->assertRaw('');
}
+ /**
+ * Tests the contextual placeholder content is protected by a token.
+ */
+ public function testTokenProtection() {
+ $this->drupalLogin($this->editorUser);
+
+ // Create a node that will have a contextual link.
+ $node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
+
+ // Now, on the front page, all article nodes should have contextual links
+ // placeholders, as should the view that contains them.
+ $id = 'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en';
+
+ // Editor user: can access contextual links and can edit articles.
+ $this->drupalGet('node');
+ $this->assertContextualLinkPlaceHolder($id);
+
+ $http_client = $this->getHttpClient();
+ $url = Url::fromRoute('contextual.render', [], [
+ 'query' => [
+ '_format' => 'json',
+ 'destination' => 'node',
+ ],
+ ])->setAbsolute()->toString();
+
+ $response = $http_client->request('POST', $url, [
+ 'cookies' => $this->getSessionCookies(),
+ 'form_params' => ['ids' => [$id], 'tokens' => []],
+ 'http_errors' => FALSE,
+ ]);
+ $this->assertEquals('400', $response->getStatusCode());
+ $this->assertContains('No contextual ID tokens specified.', (string) $response->getBody());
+
+ $response = $http_client->request('POST', $url, [
+ 'cookies' => $this->getSessionCookies(),
+ 'form_params' => ['ids' => [$id], 'tokens' => ['wrong_token']],
+ 'http_errors' => FALSE,
+ ]);
+ $this->assertEquals('400', $response->getStatusCode());
+ $this->assertContains('Invalid contextual ID specified.', (string) $response->getBody());
+
+ $response = $http_client->request('POST', $url, [
+ 'cookies' => $this->getSessionCookies(),
+ 'form_params' => ['ids' => [$id], 'tokens' => ['wrong_key' => $this->createContextualIdToken($id)]],
+ 'http_errors' => FALSE,
+ ]);
+ $this->assertEquals('400', $response->getStatusCode());
+ $this->assertContains('Invalid contextual ID specified.', (string) $response->getBody());
+
+ $response = $http_client->request('POST', $url, [
+ 'cookies' => $this->getSessionCookies(),
+ 'form_params' => ['ids' => [$id], 'tokens' => [$this->createContextualIdToken($id)]],
+ 'http_errors' => FALSE,
+ ]);
+ $this->assertEquals('200', $response->getStatusCode());
+ }
+
/**
* Asserts that a contextual link placeholder with the given id exists.
*
* @param string $id
* A contextual link id.
- *
- * @return bool
- * The result of the assertion.
*/
protected function assertContextualLinkPlaceHolder($id) {
- return $this->assertRaw(' $id]) . '>
', format_string('Contextual link placeholder with id @id exists.', ['@id' => $id]));
+ $this->assertSession()->elementAttributeContains(
+ 'css',
+ 'div[data-contextual-id="' . $id . '"]',
+ 'data-contextual-token',
+ $this->createContextualIdToken($id)
+ );
}
/**
@@ -158,12 +218,9 @@ class ContextualDynamicContextTest extends BrowserTestBase {
*
* @param string $id
* A contextual link id.
- *
- * @return bool
- * The result of the assertion.
*/
protected function assertNoContextualLinkPlaceHolder($id) {
- return $this->assertNoRaw(' $id]) . '>
', format_string('Contextual link placeholder with id @id does not exist.', ['@id' => $id]));
+ $this->assertSession()->elementNotExists('css', 'div[data-contextual-id="' . $id . '"]');
}
/**
@@ -178,6 +235,7 @@ class ContextualDynamicContextTest extends BrowserTestBase {
* The response object.
*/
protected function renderContextualLinks($ids, $current_path) {
+ $tokens = array_map([$this, 'createContextualIdToken'], $ids);
$http_client = $this->getHttpClient();
$url = Url::fromRoute('contextual.render', [], [
'query' => [
@@ -188,9 +246,22 @@ class ContextualDynamicContextTest extends BrowserTestBase {
return $http_client->request('POST', $this->buildUrl($url), [
'cookies' => $this->getSessionCookies(),
- 'form_params' => ['ids' => $ids],
+ 'form_params' => ['ids' => $ids, 'tokens' => $tokens],
'http_errors' => FALSE,
]);
}
+ /**
+ * Creates a contextual ID token.
+ *
+ * @param string $id
+ * The contextual ID to create a token for.
+ *
+ * @return string
+ * The contextual ID token.
+ */
+ protected function createContextualIdToken($id) {
+ return Crypt::hmacBase64($id, Settings::getHashSalt() . $this->container->get('private_key')->get());
+ }
+
}
diff --git a/core/modules/node/src/Tests/Views/NodeContextualLinksTest.php b/core/modules/node/src/Tests/Views/NodeContextualLinksTest.php
deleted file mode 100644
index dc23d0ce5..000000000
--- a/core/modules/node/src/Tests/Views/NodeContextualLinksTest.php
+++ /dev/null
@@ -1,118 +0,0 @@
-drupalCreateContentType(['type' => 'page']);
- $this->drupalCreateNode(['promote' => 1]);
- $this->drupalGet('node');
-
- $user = $this->drupalCreateUser(['administer nodes', 'access contextual links']);
- $this->drupalLogin($user);
-
- $response = $this->renderContextualLinks(['node:node=1:'], 'node');
- $this->assertResponse(200);
- $json = Json::decode($response);
- $this->setRawContent($json['node:node=1:']);
-
- // @todo Add these back when the functionality for making Views displays
- // appear in contextual links is working again.
- // $this->assertLinkByHref('node/1/contextual-links', 0, 'The contextual link to the view was found.');
- // $this->assertLink('Test contextual link', 0, 'The contextual link to the view was found.');
- }
-
- /**
- * Get server-rendered contextual links for the given contextual link ids.
- *
- * Copied from \Drupal\contextual\Tests\ContextualDynamicContextTest::renderContextualLinks().
- *
- * @param array $ids
- * An array of contextual link ids.
- * @param string $current_path
- * The Drupal path for the page for which the contextual links are rendered.
- *
- * @return string
- * The response body.
- */
- protected function renderContextualLinks($ids, $current_path) {
- // Build POST values.
- $post = [];
- for ($i = 0; $i < count($ids); $i++) {
- $post['ids[' . $i . ']'] = $ids[$i];
- }
-
- // Serialize POST values.
- foreach ($post as $key => $value) {
- // Encode according to application/x-www-form-urlencoded
- // Both names and values needs to be urlencoded, according to
- // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
- $post[$key] = urlencode($key) . '=' . urlencode($value);
- }
- $post = implode('&', $post);
-
- // Perform HTTP request.
- return $this->curlExec([
- CURLOPT_URL => \Drupal::url('contextual.render', [], ['absolute' => TRUE, 'query' => ['destination' => $current_path]]),
- CURLOPT_POST => TRUE,
- CURLOPT_POSTFIELDS => $post,
- CURLOPT_HTTPHEADER => [
- 'Accept: application/json',
- 'Content-Type: application/x-www-form-urlencoded',
- ],
- ]);
- }
-
- /**
- * Tests if the node page works if Contextual Links is disabled.
- *
- * All views have Contextual links enabled by default, even with the
- * Contextual links module disabled. This tests if no calls are done to the
- * Contextual links module by views when it is disabled.
- *
- * @see https://www.drupal.org/node/2379811
- */
- public function testPageWithDisabledContextualModule() {
- \Drupal::service('module_installer')->uninstall(['contextual']);
- \Drupal::service('module_installer')->install(['views_ui']);
-
- // Ensure that contextual links don't get called for admin users.
- $admin_user = User::load(1);
- $admin_user->setPassword('new_password');
- $admin_user->pass_raw = 'new_password';
- $admin_user->save();
-
- $this->drupalCreateContentType(['type' => 'page']);
- $this->drupalCreateNode(['promote' => 1]);
-
- $this->drupalLogin($admin_user);
- $this->drupalGet('node');
- }
-
-}
diff --git a/core/modules/node/src/Tests/NodeRevisionsTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsTest.php
similarity index 92%
rename from core/modules/node/src/Tests/NodeRevisionsTest.php
rename to core/modules/node/tests/src/Functional/NodeRevisionsTest.php
index fdc929a84..6b16ce8bd 100644
--- a/core/modules/node/src/Tests/NodeRevisionsTest.php
+++ b/core/modules/node/tests/src/Functional/NodeRevisionsTest.php
@@ -1,6 +1,6 @@
assertTrue($node->isDefaultRevision(), 'Third node revision is the default one.');
- // Confirm that the "Edit" and "Delete" contextual links appear for the
- // default revision.
- $ids = ['node:node=' . $node->id() . ':changed=' . $node->getChangedTime()];
- $json = $this->renderContextualLinks($ids, 'node/' . $node->id());
- $this->verbose($json[$ids[0]]);
-
- $expected = 'Edit';
- $this->assertTrue(strstr($json[$ids[0]], $expected), 'The "Edit" contextual link is shown for the default revision.');
- $expected = 'Delete';
- $this->assertTrue(strstr($json[$ids[0]], $expected), 'The "Delete" contextual link is shown for the default revision.');
-
// Confirm that revisions revert properly.
$this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionid() . "/revert", [], t('Revert'));
$this->assertRaw(t('@type %title has been reverted to the revision from %revision-date.', [
@@ -188,15 +177,6 @@ class NodeRevisionsTest extends NodeTestBase {
$node = node_revision_load($node->getRevisionId());
$this->assertFalse($node->isDefaultRevision(), 'Third node revision is not the default one.');
- // Confirm that "Edit" and "Delete" contextual links don't appear for
- // non-default revision.
- $ids = ['node_revision::node=' . $node->id() . '&node_revision=' . $node->getRevisionId() . ':'];
- $json = $this->renderContextualLinks($ids, 'node/' . $node->id() . '/revisions/' . $node->getRevisionId() . '/view');
- $this->verbose($json[$ids[0]]);
-
- $this->assertFalse(strstr($json[$ids[0]], ''), 'The "Edit" contextual link is not shown for a non-default revision.');
- $this->assertFalse(strstr($json[$ids[0]], ''), 'The "Delete" contextual link is not shown for a non-default revision.');
-
// Confirm revisions delete properly.
$this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionId() . "/delete", [], t('Delete'));
$this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.', [
diff --git a/core/modules/node/src/Tests/NodeTypeTest.php b/core/modules/node/tests/src/Functional/NodeTypeTest.php
similarity index 94%
rename from core/modules/node/src/Tests/NodeTypeTest.php
rename to core/modules/node/tests/src/Functional/NodeTypeTest.php
index 9938bb0ab..84c549e8d 100644
--- a/core/modules/node/src/Tests/NodeTypeTest.php
+++ b/core/modules/node/tests/src/Functional/NodeTypeTest.php
@@ -1,11 +1,12 @@
assertSession();
$this->drupalPlaceBlock('system_breadcrumb_block');
$web_user = $this->drupalCreateUser(['bypass node access', 'administer content types', 'administer node fields']);
$this->drupalLogin($web_user);
@@ -96,8 +99,8 @@ class NodeTypeTest extends NodeTestBase {
// Verify that title and body fields are displayed.
$this->drupalGet('node/add/page');
- $this->assertRaw('Title', 'Title field was found.');
- $this->assertRaw('Body', 'Body field was found.');
+ $assert->pageTextContains('Title');
+ $assert->pageTextContains('Body');
// Rename the title field.
$edit = [
@@ -106,8 +109,8 @@ class NodeTypeTest extends NodeTestBase {
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
$this->drupalGet('node/add/page');
- $this->assertRaw('Foo', 'New title label was displayed.');
- $this->assertNoRaw('Title', 'Old title label was not displayed.');
+ $assert->pageTextContains('Foo');
+ $assert->pageTextNotContains('Title');
// Change the name and the description.
$edit = [
@@ -117,11 +120,11 @@ class NodeTypeTest extends NodeTestBase {
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
$this->drupalGet('node/add');
- $this->assertRaw('Bar', 'New name was displayed.');
- $this->assertRaw('Lorem ipsum', 'New description was displayed.');
+ $assert->pageTextContains('Bar');
+ $assert->pageTextContains('Lorem ipsum');
$this->clickLink('Bar');
- $this->assertRaw('Foo', 'Title field was found.');
- $this->assertRaw('Body', 'Body field was found.');
+ $assert->pageTextContains('Foo');
+ $assert->pageTextContains('Body');
// Change the name through the API
/** @var \Drupal\node\NodeTypeInterface $node_type */
@@ -146,7 +149,7 @@ class NodeTypeTest extends NodeTestBase {
]);
// Check that the body field doesn't exist.
$this->drupalGet('node/add/page');
- $this->assertNoRaw('Body', 'Body field was not found.');
+ $assert->pageTextNotContains('Body');
}
/**
diff --git a/core/modules/node/src/Tests/PagePreviewTest.php b/core/modules/node/tests/src/Functional/PagePreviewTest.php
similarity index 97%
rename from core/modules/node/src/Tests/PagePreviewTest.php
rename to core/modules/node/tests/src/Functional/PagePreviewTest.php
index 2bc9cd3ce..70305349d 100644
--- a/core/modules/node/src/Tests/PagePreviewTest.php
+++ b/core/modules/node/tests/src/Functional/PagePreviewTest.php
@@ -1,6 +1,6 @@
drupalPostForm(NULL, ['field_image[0][alt]' => 'Picture of llamas'], t('Preview'));
// Check that the preview is displaying the title, body and term.
- $this->assertTitle(t('@title | Drupal', ['@title' => $edit[$title_key]]), 'Basic page title is preview.');
+ $expected_title = $edit[$title_key] . ' | Drupal';
+ $this->assertSession()->titleEquals($expected_title);
$this->assertEscaped($edit[$title_key], 'Title displayed and escaped.');
$this->assertText($edit[$body_key], 'Body displayed.');
$this->assertText($edit[$term_key], 'Term displayed.');
@@ -210,13 +215,13 @@ class PagePreviewTest extends NodeTestBase {
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
$this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
$this->assertFieldByName('field_image[0][alt]', 'Picture of llamas');
- $this->drupalPostAjaxForm(NULL, [], ['field_test_multi_add_more' => t('Add another item')], NULL, [], [], 'node-page-form');
+ $this->getSession()->getPage()->pressButton('Add another item');
$this->assertFieldByName('field_test_multi[0][value]');
$this->assertFieldByName('field_test_multi[1][value]');
// Return to page preview to check everything is as expected.
$this->drupalPostForm(NULL, [], t('Preview'));
- $this->assertTitle(t('@title | Drupal', ['@title' => $edit[$title_key]]), 'Basic page title is preview.');
+ $this->assertSession()->titleEquals($expected_title);
$this->assertEscaped($edit[$title_key], 'Title displayed and escaped.');
$this->assertText($edit[$body_key], 'Body displayed.');
$this->assertText($edit[$term_key], 'Term displayed.');
@@ -353,8 +358,8 @@ class PagePreviewTest extends NodeTestBase {
$this->assertText('Basic page ' . $title . ' has been created.');
$node = $this->drupalGetNodeByTitle($title);
$this->drupalGet('node/' . $node->id() . '/edit');
- $this->drupalPostAjaxForm(NULL, [], ['field_test_multi_add_more' => t('Add another item')]);
- $this->drupalPostAjaxForm(NULL, [], ['field_test_multi_add_more' => t('Add another item')]);
+ $this->getSession()->getPage()->pressButton('Add another item');
+ $this->getSession()->getPage()->pressButton('Add another item');
$edit = [
'field_test_multi[1][value]' => $example_text_2,
'field_test_multi[2][value]' => $example_text_3,
diff --git a/core/modules/node/tests/src/Functional/Views/NodeContextualLinksTest.php b/core/modules/node/tests/src/Functional/Views/NodeContextualLinksTest.php
new file mode 100644
index 000000000..73ccfef75
--- /dev/null
+++ b/core/modules/node/tests/src/Functional/Views/NodeContextualLinksTest.php
@@ -0,0 +1,47 @@
+uninstall(['contextual']);
+ \Drupal::service('module_installer')->install(['views_ui']);
+
+ // Ensure that contextual links don't get called for admin users.
+ $admin_user = User::load(1);
+ $admin_user->setPassword('new_password');
+ $admin_user->passRaw = 'new_password';
+ $admin_user->save();
+
+ $this->drupalCreateContentType(['type' => 'page']);
+ $this->drupalCreateNode(['promote' => 1]);
+
+ $this->drupalLogin($admin_user);
+ $this->drupalGet('node');
+ }
+
+}
diff --git a/core/modules/node/tests/src/FunctionalJavascript/ContextualLinksTest.php b/core/modules/node/tests/src/FunctionalJavascript/ContextualLinksTest.php
new file mode 100644
index 000000000..98051262e
--- /dev/null
+++ b/core/modules/node/tests/src/FunctionalJavascript/ContextualLinksTest.php
@@ -0,0 +1,117 @@
+drupalCreateContentType([
+ 'type' => 'page',
+ 'name' => 'Basic page',
+ 'display_submitted' => FALSE,
+ ]);
+
+ // Create initial node.
+ $node = $this->drupalCreateNode();
+
+ $nodes = [];
+
+ // Get original node.
+ $nodes[] = clone $node;
+
+ // Create two revisions.
+ $revision_count = 2;
+ for ($i = 0; $i < $revision_count; $i++) {
+
+ // Create revision with a random title and body and update variables.
+ $node->title = $this->randomMachineName();
+ $node->body = [
+ 'value' => $this->randomMachineName(32),
+ 'format' => filter_default_format(),
+ ];
+ $node->setNewRevision();
+
+ $node->save();
+
+ // Make sure we get revision information.
+ $node = Node::load($node->id());
+ $nodes[] = clone $node;
+ }
+
+ $this->nodes = $nodes;
+
+ $this->drupalLogin($this->createUser(
+ [
+ 'view page revisions',
+ 'revert page revisions',
+ 'delete page revisions',
+ 'edit any page content',
+ 'delete any page content',
+ 'access contextual links',
+ 'administer content types',
+ ]
+ ));
+ }
+
+ /**
+ * Tests the contextual links on revisions.
+ */
+ public function testRevisionContextualLinks() {
+ // Confirm that the "Edit" and "Delete" contextual links appear for the
+ // default revision.
+ $this->drupalGet('node/' . $this->nodes[0]->id());
+ $page = $this->getSession()->getPage();
+ $page->waitFor(10, function () use ($page) {
+ return $page->find('css', "main .contextual");
+ });
+
+ $this->toggleContextualTriggerVisibility('main');
+ $page->find('css', 'main .contextual button')->press();
+ $links = $page->findAll('css', "main .contextual-links li a");
+
+ $this->assertEquals('Edit', $links[0]->getText());
+ $this->assertEquals('Delete', $links[1]->getText());
+
+ // Confirm that "Edit" and "Delete" contextual links don't appear for
+ // non-default revision.
+ $this->drupalGet("node/" . $this->nodes[0]->id() . "/revisions/" . $this->nodes[1]->getRevisionId() . "/view");
+ $this->assertSession()->pageTextContains($this->nodes[1]->getTitle());
+ $page->waitFor(10, function () use ($page) {
+ return $page->find('css', "main .contextual");
+ });
+
+ $this->toggleContextualTriggerVisibility('main');
+ $contextual_button = $page->find('css', 'main .contextual button');
+ $this->assertEmpty(0, $contextual_button);
+ }
+
+}
diff --git a/core/modules/path/tests/src/Functional/PathAliasTest.php b/core/modules/path/tests/src/Functional/PathAliasTest.php
index b8ac5968d..19115cd27 100644
--- a/core/modules/path/tests/src/Functional/PathAliasTest.php
+++ b/core/modules/path/tests/src/Functional/PathAliasTest.php
@@ -4,6 +4,7 @@ namespace Drupal\Tests\path\Functional;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Database;
+use Drupal\Core\Url;
/**
* Add, edit, delete, and change alias and verify its consistency in the
@@ -24,7 +25,7 @@ class PathAliasTest extends PathTestBase {
parent::setUp();
// Create test user and log in.
- $web_user = $this->drupalCreateUser(['create page content', 'edit own page content', 'administer url aliases', 'create url aliases']);
+ $web_user = $this->drupalCreateUser(['create page content', 'edit own page content', 'administer url aliases', 'create url aliases', 'access content overview']);
$this->drupalLogin($web_user);
}
@@ -327,6 +328,34 @@ class PathAliasTest extends PathTestBase {
$node5->delete();
$path_alias = \Drupal::service('path.alias_storage')->lookupPathAlias('/node/' . $node5->id(), $node5->language()->getId());
$this->assertFalse($path_alias, 'Alias was successfully deleted when the referenced node was deleted.');
+
+ // Create sixth test node.
+ $node6 = $this->drupalCreateNode();
+
+ // 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.
+ // @todo The user interface should either display an error message or
+ // automatically trim these invalid aliases, rather than allowing them to
+ // be silently created, at which point the functional aspects of this
+ // test will need to be moved elsewhere and switch to using a
+ // programmatically-created alias instead.
+ $alias = $this->randomMachineName(8);
+ $edit = ['path[0][alias]' => '//' . $alias];
+ $this->drupalPostForm($node6->toUrl('edit-form'), $edit, t('Save'));
+ $this->drupalGet(Url::fromRoute('system.admin_content'));
+ // This checks the link href before clicking it, rather than using
+ // \Drupal\Tests\BrowserTestBase::assertSession()->addressEquals() after
+ // clicking it, because the test browser does not always preserve the
+ // correct number of slashes in the URL when it visits internal links;
+ // using \Drupal\Tests\BrowserTestBase::assertSession()->addressEquals()
+ // would actually make the test pass unconditionally on the testbot (or
+ // anywhere else where Drupal is installed in a subdirectory).
+ $link_xpath = $this->xpath('//a[normalize-space(text())=:label]', [':label' => $node6->getTitle()]);
+ $link_href = $link_xpath[0]->getAttribute('href');
+ $this->assertEquals($link_href, base_path() . $alias);
+ $this->clickLink($node6->getTitle());
+ $this->assertResponse(404);
}
/**
diff --git a/core/modules/system/src/Tests/Routing/RouterTest.php b/core/modules/system/src/Tests/Routing/RouterTest.php
index 83a9c55b3..8d7c43e86 100644
--- a/core/modules/system/src/Tests/Routing/RouterTest.php
+++ b/core/modules/system/src/Tests/Routing/RouterTest.php
@@ -320,6 +320,13 @@ class RouterTest extends WebTestBase {
$this->drupalGet($url);
$this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
$this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
+
+ // Ensure that external URLs in destination query params are not redirected
+ // to.
+ $url = $request->getUriForPath('/////////////////////////////////////////////////router_test/test1') . '?qs=test&destination=http://www.example.com%5c@drupal8alt.test';
+ $this->drupalGet($url);
+ $this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
+ $this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
}
}
diff --git a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
index d185219c9..beaa472c2 100644
--- a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
@@ -563,6 +563,10 @@ class UrlHelperTest extends TestCase {
['http://example.com/foo', 'http://example.com/bar', FALSE],
['http://example.com', 'http://example.com/bar', FALSE],
['http://example.com/bar', 'http://example.com/bar/', FALSE],
+ // Ensure \ is normalised to / since some browsers do that.
+ ['http://www.example.ca\@example.com', 'http://example.com', FALSE],
+ // Some browsers ignore or strip leading control characters.
+ ["\x00//www.example.ca", 'http://example.com', FALSE],
];
}
diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/RedirectResponseSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/RedirectResponseSubscriberTest.php
index 8659a6f12..85b3da313 100644
--- a/core/tests/Drupal/Tests/Core/EventSubscriber/RedirectResponseSubscriberTest.php
+++ b/core/tests/Drupal/Tests/Core/EventSubscriber/RedirectResponseSubscriberTest.php
@@ -11,7 +11,6 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
-use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
@@ -192,74 +191,4 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
return $data;
}
- /**
- * Tests that $_GET only contain internal URLs.
- *
- * @covers ::sanitizeDestination
- *
- * @dataProvider providerTestSanitizeDestination
- *
- * @see \Drupal\Component\Utility\UrlHelper::isExternal
- */
- public function testSanitizeDestinationForGet($input, $output) {
- $request = new Request();
- $request->query->set('destination', $input);
-
- $listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
- $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
- $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST);
-
- $dispatcher = new EventDispatcher();
- $dispatcher->addListener(KernelEvents::REQUEST, [$listener, 'sanitizeDestination'], 100);
- $dispatcher->dispatch(KernelEvents::REQUEST, $event);
-
- $this->assertEquals($output, $request->query->get('destination'));
- }
-
- /**
- * Tests that $_REQUEST['destination'] only contain internal URLs.
- *
- * @covers ::sanitizeDestination
- *
- * @dataProvider providerTestSanitizeDestination
- *
- * @see \Drupal\Component\Utility\UrlHelper::isExternal
- */
- public function testSanitizeDestinationForPost($input, $output) {
- $request = new Request();
- $request->request->set('destination', $input);
-
- $listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
- $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
- $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST);
-
- $dispatcher = new EventDispatcher();
- $dispatcher->addListener(KernelEvents::REQUEST, [$listener, 'sanitizeDestination'], 100);
- $dispatcher->dispatch(KernelEvents::REQUEST, $event);
-
- $this->assertEquals($output, $request->request->get('destination'));
- }
-
- /**
- * Data provider for testSanitizeDestination().
- */
- public function providerTestSanitizeDestination() {
- $data = [];
- // Standard internal example node path is present in the 'destination'
- // parameter.
- $data[] = ['node', 'node'];
- // Internal path with one leading slash is allowed.
- $data[] = ['/example.com', '/example.com'];
- // External URL without scheme is not allowed.
- $data[] = ['//example.com/test', ''];
- // Internal URL using a colon is allowed.
- $data[] = ['example:test', 'example:test'];
- // External URL is not allowed.
- $data[] = ['http://example.com', ''];
- // Javascript URL is allowed because it is treated as an internal URL.
- $data[] = ['javascript:alert(0)', 'javascript:alert(0)'];
-
- return $data;
- }
-
}
diff --git a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
index ea523028a..de2e3d943 100644
--- a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
@@ -7,6 +7,7 @@
namespace Drupal\Tests\Core\Mail;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Tests\UnitTestCase;
@@ -103,6 +104,9 @@ class MailManagerTest extends UnitTestCase {
'system.mail' => [
'interface' => $interface,
],
+ 'system.site' => [
+ 'mail' => 'test@example.com',
+ ],
]);
$logger_factory = $this->getMock('\Drupal\Core\Logger\LoggerChannelFactoryInterface');
$string_translation = $this->getStringTranslationStub();
@@ -110,6 +114,11 @@ class MailManagerTest extends UnitTestCase {
// Construct the manager object and override its discovery.
$this->mailManager = new TestMailManager(new \ArrayObject(), $this->cache, $this->moduleHandler, $this->configFactory, $logger_factory, $string_translation, $this->renderer);
$this->mailManager->setDiscovery($this->discovery);
+
+ // @see \Drupal\Core\Plugin\Factory\ContainerFactory::createInstance()
+ $container = new ContainerBuilder();
+ $container->set('config.factory', $this->configFactory);
+ \Drupal::setContainer($container);
}
/**
diff --git a/core/tests/Drupal/Tests/Core/Security/RequestSanitizerTest.php b/core/tests/Drupal/Tests/Core/Security/RequestSanitizerTest.php
index 53147f3b7..e828de086 100644
--- a/core/tests/Drupal/Tests/Core/Security/RequestSanitizerTest.php
+++ b/core/tests/Drupal/Tests/Core/Security/RequestSanitizerTest.php
@@ -197,6 +197,147 @@ class RequestSanitizerTest extends UnitTestCase {
return $tests;
}
+ /**
+ * Tests acceptable destinations are not removed from GET requests.
+ *
+ * @param string $destination
+ * The destination string to test.
+ *
+ * @dataProvider providerTestAcceptableDestinations
+ */
+ public function testAcceptableDestinationGet($destination) {
+ // Set up a GET request.
+ $request = $this->createRequestForTesting(['destination' => $destination]);
+
+ $request = RequestSanitizer::sanitize($request, [], TRUE);
+
+ $this->assertSame($destination, $request->query->get('destination', NULL));
+ $this->assertNull($request->request->get('destination', NULL));
+ $this->assertSame($destination, $_GET['destination']);
+ $this->assertSame($destination, $_REQUEST['destination']);
+ $this->assertArrayNotHasKey('destination', $_POST);
+ $this->assertEquals([], $this->errors);
+ }
+
+ /**
+ * Tests unacceptable destinations are removed from GET requests.
+ *
+ * @param string $destination
+ * The destination string to test.
+ *
+ * @dataProvider providerTestSanitizedDestinations
+ */
+ public function testSanitizedDestinationGet($destination) {
+ // Set up a GET request.
+ $request = $this->createRequestForTesting(['destination' => $destination]);
+
+ $request = RequestSanitizer::sanitize($request, [], TRUE);
+
+ $this->assertNull($request->request->get('destination', NULL));
+ $this->assertNull($request->query->get('destination', NULL));
+ $this->assertArrayNotHasKey('destination', $_POST);
+ $this->assertArrayNotHasKey('destination', $_REQUEST);
+ $this->assertArrayNotHasKey('destination', $_GET);
+ $this->assertError('Potentially unsafe destination removed from query parameter bag because it points to an external URL.', E_USER_NOTICE);
+ }
+
+ /**
+ * Tests acceptable destinations are not removed from POST requests.
+ *
+ * @param string $destination
+ * The destination string to test.
+ *
+ * @dataProvider providerTestAcceptableDestinations
+ */
+ public function testAcceptableDestinationPost($destination) {
+ // Set up a POST request.
+ $request = $this->createRequestForTesting([], ['destination' => $destination]);
+
+ $request = RequestSanitizer::sanitize($request, [], TRUE);
+
+ $this->assertSame($destination, $request->request->get('destination', NULL));
+ $this->assertNull($request->query->get('destination', NULL));
+ $this->assertSame($destination, $_POST['destination']);
+ $this->assertSame($destination, $_REQUEST['destination']);
+ $this->assertArrayNotHasKey('destination', $_GET);
+ $this->assertEquals([], $this->errors);
+ }
+
+ /**
+ * Tests unacceptable destinations are removed from GET requests.
+ *
+ * @param string $destination
+ * The destination string to test.
+ *
+ * @dataProvider providerTestSanitizedDestinations
+ */
+ public function testSanitizedDestinationPost($destination) {
+ // Set up a POST request.
+ $request = $this->createRequestForTesting([], ['destination' => $destination]);
+
+ $request = RequestSanitizer::sanitize($request, [], TRUE);
+
+ $this->assertNull($request->request->get('destination', NULL));
+ $this->assertNull($request->query->get('destination', NULL));
+ $this->assertArrayNotHasKey('destination', $_POST);
+ $this->assertArrayNotHasKey('destination', $_REQUEST);
+ $this->assertArrayNotHasKey('destination', $_GET);
+ $this->assertError('Potentially unsafe destination removed from request parameter bag because it points to an external URL.', E_USER_NOTICE);
+ }
+
+ /**
+ * Creates a request and sets PHP globals for testing.
+ *
+ * @param array $query
+ * (optional) The GET parameters.
+ * @param array $request
+ * (optional) The POST parameters.
+ *
+ * @return \Symfony\Component\HttpFoundation\Request
+ * The request object.
+ */
+ protected function createRequestForTesting(array $query = [], array $request = []) {
+ $request = new Request($query, $request);
+
+ // Set up globals.
+ $_GET = $request->query->all();
+ $_POST = $request->request->all();
+ $_COOKIE = $request->cookies->all();
+ $_REQUEST = array_merge($request->query->all(), $request->request->all());
+ $request->server->set('QUERY_STRING', http_build_query($request->query->all()));
+ $_SERVER['QUERY_STRING'] = $request->server->get('QUERY_STRING');
+ return $request;
+ }
+
+ /**
+ * Data provider for testing acceptable destinations.
+ */
+ public function providerTestAcceptableDestinations() {
+ $data = [];
+ // Standard internal example node path is present in the 'destination'
+ // parameter.
+ $data[] = ['node'];
+ // Internal path with one leading slash is allowed.
+ $data[] = ['/example.com'];
+ // Internal URL using a colon is allowed.
+ $data[] = ['example:test'];
+ // Javascript URL is allowed because it is treated as an internal URL.
+ $data[] = ['javascript:alert(0)'];
+ return $data;
+ }
+
+ /**
+ * Data provider for testing sanitized destinations.
+ */
+ public function providerTestSanitizedDestinations() {
+ $data = [];
+ // External URL without scheme is not allowed.
+ $data[] = ['//example.com/test'];
+ // External URL is not allowed.
+ $data[] = ['http://example.com'];
+ return $data;
+ }
+
/**
* Catches and logs errors to $this->errors.
*