diff --git a/composer.json b/composer.json index 0456d05..e6ce38a 100644 --- a/composer.json +++ b/composer.json @@ -6,16 +6,17 @@ "require": { "php": ">=5.4.5", "sdboyer/gliph": "0.1.*", - "symfony/class-loader": "2.5.*", - "symfony/css-selector": "2.5.*", - "symfony/dependency-injection": "2.5.*", - "symfony/event-dispatcher": "2.5.*", - "symfony/http-foundation": "2.5.*", - "symfony/http-kernel": "2.5.*", - "symfony/routing": "2.5.*", - "symfony/serializer": "2.5.*", - "symfony/validator": "2.5.*", - "symfony/yaml": "dev-master#499f7d7aa96747ad97940089bd7a1fb24ad8182a", + "symfony/class-loader": "2.6.0-beta1", + "symfony/css-selector": "2.6.0-beta1", + "symfony/debug": "2.6.0-beta1", + "symfony/dependency-injection": "2.6.0-beta1", + "symfony/event-dispatcher": "2.6.0-beta1", + "symfony/http-foundation": "2.6.0-beta1", + "symfony/http-kernel": "2.6.0-beta1", + "symfony/routing": "2.6.0-beta1", + "symfony/serializer": "2.6.0-beta1", + "symfony/validator": "2.6.0-beta1", + "symfony/yaml": "2.6.0-beta1", "twig/twig": "1.16.*", "doctrine/common": "dev-master#a45d110f71c323e29f41eb0696fa230e3fa1b1b5", "doctrine/annotations": "1.2.*", diff --git a/composer.lock b/composer.lock index 5541389..972f8a1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2bd5814ec010b13a6997359736b58695", + "hash": "34a4f5a56891e51217c305b97e8cc675", "packages": [ { "name": "doctrine/annotations", @@ -518,16 +518,16 @@ }, { "name": "egulias/email-validator", - "version": "1.2.2", + "version": "1.2.5", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "39b451bb2bb0655d83d82a38a0bba7189298cfc5" + "reference": "518f80a0ff7c1a35780e7702f4262c8c6f2b807f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/39b451bb2bb0655d83d82a38a0bba7189298cfc5", - "reference": "39b451bb2bb0655d83d82a38a0bba7189298cfc5", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/518f80a0ff7c1a35780e7702f4262c8c6f2b807f", + "reference": "518f80a0ff7c1a35780e7702f4262c8c6f2b807f", "shasum": "" }, "require": { @@ -540,7 +540,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { @@ -564,7 +564,7 @@ "validation", "validator" ], - "time": "2014-09-01 22:35:48" + "time": "2014-11-06 08:59:44" }, { "name": "guzzlehttp/guzzle", @@ -626,16 +626,16 @@ }, { "name": "guzzlehttp/ringphp", - "version": "1.0.0", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/RingPHP.git", - "reference": "9e44b565d726d9614cd970319e6eea70ee15bff3" + "reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/9e44b565d726d9614cd970319e6eea70ee15bff3", - "reference": "9e44b565d726d9614cd970319e6eea70ee15bff3", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/e7c28f96c5ac12ab0e63412cfc15989756fcb964", + "reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964", "shasum": "" }, "require": { @@ -672,7 +672,7 @@ "homepage": "https://github.com/mtdowling" } ], - "time": "2014-10-13 00:59:38" + "time": "2014-11-04 07:01:14" }, { "name": "guzzlehttp/streams", @@ -1451,16 +1451,16 @@ }, { "name": "sebastian/environment", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6288ebbf6fa3ed2b2ff2d69c356fbaaf4f0971a7" + "reference": "0d9bf79554d2a999da194a60416c15cf461eb67d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6288ebbf6fa3ed2b2ff2d69c356fbaaf4f0971a7", - "reference": "6288ebbf6fa3ed2b2ff2d69c356fbaaf4f0971a7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/0d9bf79554d2a999da194a60416c15cf461eb67d", + "reference": "0d9bf79554d2a999da194a60416c15cf461eb67d", "shasum": "" }, "require": { @@ -1472,7 +1472,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -1497,7 +1497,7 @@ "environment", "hhvm" ], - "time": "2014-10-07 09:23:16" + "time": "2014-10-22 06:38:05" }, { "name": "sebastian/exporter", @@ -1708,17 +1708,17 @@ }, { "name": "symfony/class-loader", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/ClassLoader", "source": { "type": "git", "url": "https://github.com/symfony/ClassLoader.git", - "reference": "432561f655123b003b32f370ca812fed9a9340c6" + "reference": "d1a16139ea522ec3cc20801b7e19cd3cafd12d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/432561f655123b003b32f370ca812fed9a9340c6", - "reference": "432561f655123b003b32f370ca812fed9a9340c6", + "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/d1a16139ea522ec3cc20801b7e19cd3cafd12d8c", + "reference": "d1a16139ea522ec3cc20801b7e19cd3cafd12d8c", "shasum": "" }, "require": { @@ -1730,7 +1730,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -1754,21 +1754,21 @@ ], "description": "Symfony ClassLoader Component", "homepage": "http://symfony.com", - "time": "2014-09-22 09:14:18" + "time": "2014-11-03 03:55:50" }, { "name": "symfony/css-selector", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/CssSelector", "source": { "type": "git", "url": "https://github.com/symfony/CssSelector.git", - "reference": "caf5ecc3face1f22884fb74b8edab65ac5ba9976" + "reference": "41953ad30ffc5cd710d106cf01eff79f6effa117" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/CssSelector/zipball/caf5ecc3face1f22884fb74b8edab65ac5ba9976", - "reference": "caf5ecc3face1f22884fb74b8edab65ac5ba9976", + "url": "https://api.github.com/repos/symfony/CssSelector/zipball/41953ad30ffc5cd710d106cf01eff79f6effa117", + "reference": "41953ad30ffc5cd710d106cf01eff79f6effa117", "shasum": "" }, "require": { @@ -1777,7 +1777,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -1805,25 +1805,26 @@ ], "description": "Symfony CssSelector Component", "homepage": "http://symfony.com", - "time": "2014-09-22 09:14:18" + "time": "2014-10-26 07:46:28" }, { "name": "symfony/debug", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/Debug", "source": { "type": "git", "url": "https://github.com/symfony/Debug.git", - "reference": "4a3dd4ef3fc0cee2fd9faaae12bd7af43afcf648" + "reference": "3548595c26175fdaca19cbec204668c22cda41f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Debug/zipball/4a3dd4ef3fc0cee2fd9faaae12bd7af43afcf648", - "reference": "4a3dd4ef3fc0cee2fd9faaae12bd7af43afcf648", + "url": "https://api.github.com/repos/symfony/Debug/zipball/3548595c26175fdaca19cbec204668c22cda41f0", + "reference": "3548595c26175fdaca19cbec204668c22cda41f0", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.3.3", + "psr/log": "~1.0" }, "require-dev": { "symfony/http-foundation": "~2.1", @@ -1836,7 +1837,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -1860,21 +1861,21 @@ ], "description": "Symfony Debug Component", "homepage": "http://symfony.com", - "time": "2014-09-28 15:22:14" + "time": "2014-10-28 10:06:58" }, { "name": "symfony/dependency-injection", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/DependencyInjection", "source": { "type": "git", "url": "https://github.com/symfony/DependencyInjection.git", - "reference": "1f01a64c9047909e40700a14ee34e8c446300618" + "reference": "926500fe0b8a6562c4e8b8166a1cb664733804aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/1f01a64c9047909e40700a14ee34e8c446300618", - "reference": "1f01a64c9047909e40700a14ee34e8c446300618", + "url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/926500fe0b8a6562c4e8b8166a1cb664733804aa", + "reference": "926500fe0b8a6562c4e8b8166a1cb664733804aa", "shasum": "" }, "require": { @@ -1893,7 +1894,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -1917,21 +1918,21 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "http://symfony.com", - "time": "2014-09-27 08:35:39" + "time": "2014-11-03 03:55:50" }, { "name": "symfony/event-dispatcher", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/EventDispatcher", "source": { "type": "git", "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "f6281337bf5f985f585d1db6a83adb05ce531f46" + "reference": "dcf345d5ed96bc6c3b4521c1989670d2c9e5014e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/f6281337bf5f985f585d1db6a83adb05ce531f46", - "reference": "f6281337bf5f985f585d1db6a83adb05ce531f46", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/dcf345d5ed96bc6c3b4521c1989670d2c9e5014e", + "reference": "dcf345d5ed96bc6c3b4521c1989670d2c9e5014e", "shasum": "" }, "require": { @@ -1940,7 +1941,8 @@ "require-dev": { "psr/log": "~1.0", "symfony/config": "~2.0", - "symfony/dependency-injection": "~2.0,<2.6.0", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", "symfony/stopwatch": "~2.2" }, "suggest": { @@ -1950,7 +1952,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -1974,21 +1976,21 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "http://symfony.com", - "time": "2014-09-28 15:56:11" + "time": "2014-11-03 03:55:50" }, { "name": "symfony/http-foundation", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/HttpFoundation", "source": { "type": "git", "url": "https://github.com/symfony/HttpFoundation.git", - "reference": "650e115af152d7a5e857d01c2cdb9a22809de9b4" + "reference": "4cd6c807598e560db7b3da50c4330fdb4808cfa1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/650e115af152d7a5e857d01c2cdb9a22809de9b4", - "reference": "650e115af152d7a5e857d01c2cdb9a22809de9b4", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/4cd6c807598e560db7b3da50c4330fdb4808cfa1", + "reference": "4cd6c807598e560db7b3da50c4330fdb4808cfa1", "shasum": "" }, "require": { @@ -2000,7 +2002,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -2027,27 +2029,27 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "http://symfony.com", - "time": "2014-09-25 09:52:29" + "time": "2014-11-03 03:55:50" }, { "name": "symfony/http-kernel", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/HttpKernel", "source": { "type": "git", "url": "https://github.com/symfony/HttpKernel.git", - "reference": "6a3595611229def14d5e644f060cf372235532ec" + "reference": "7fa0bd9220cd529ee78d8565bbf8d5a854bd72d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/6a3595611229def14d5e644f060cf372235532ec", - "reference": "6a3595611229def14d5e644f060cf372235532ec", + "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/7fa0bd9220cd529ee78d8565bbf8d5a854bd72d2", + "reference": "7fa0bd9220cd529ee78d8565bbf8d5a854bd72d2", "shasum": "" }, "require": { "php": ">=5.3.3", "psr/log": "~1.0", - "symfony/debug": "~2.5", + "symfony/debug": "~2.6", "symfony/event-dispatcher": "~2.5", "symfony/http-foundation": "~2.5" }, @@ -2062,7 +2064,9 @@ "symfony/process": "~2.0", "symfony/routing": "~2.2", "symfony/stopwatch": "~2.2", - "symfony/templating": "~2.2" + "symfony/templating": "~2.2", + "symfony/translation": "~2.0", + "symfony/var-dumper": "~2.6" }, "suggest": { "symfony/browser-kit": "", @@ -2070,12 +2074,13 @@ "symfony/config": "", "symfony/console": "", "symfony/dependency-injection": "", - "symfony/finder": "" + "symfony/finder": "", + "symfony/var-dumper": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -2099,68 +2104,21 @@ ], "description": "Symfony HttpKernel Component", "homepage": "http://symfony.com", - "time": "2014-09-28 17:33:53" - }, - { - "name": "symfony/process", - "version": "v2.5.5", - "target-dir": "Symfony/Component/Process", - "source": { - "type": "git", - "url": "https://github.com/symfony/Process.git", - "reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/8a1ec96c4e519cee0fb971ea48a1eb7369dda54b", - "reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Process\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Process Component", - "homepage": "http://symfony.com", - "time": "2014-09-23 05:25:11" + "time": "2014-11-03 20:15:26" }, { "name": "symfony/routing", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/Routing", "source": { "type": "git", "url": "https://github.com/symfony/Routing.git", - "reference": "9bc38fe72e0eff61611e7cd4df3accbce20b1d36" + "reference": "f0bb6f818f9a7ece41c7dfe14e08b13c2de55b0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Routing/zipball/9bc38fe72e0eff61611e7cd4df3accbce20b1d36", - "reference": "9bc38fe72e0eff61611e7cd4df3accbce20b1d36", + "url": "https://api.github.com/repos/symfony/Routing/zipball/f0bb6f818f9a7ece41c7dfe14e08b13c2de55b0c", + "reference": "f0bb6f818f9a7ece41c7dfe14e08b13c2de55b0c", "shasum": "" }, "require": { @@ -2183,7 +2141,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -2213,21 +2171,21 @@ "uri", "url" ], - "time": "2014-09-22 15:28:36" + "time": "2014-11-03 19:16:49" }, { "name": "symfony/serializer", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/Serializer", "source": { "type": "git", "url": "https://github.com/symfony/Serializer.git", - "reference": "a95c0471682778da2e02169fb2644d3b08d4470f" + "reference": "e96b7ac54b3d75a458f76eab11b7cd2d757f09f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Serializer/zipball/a95c0471682778da2e02169fb2644d3b08d4470f", - "reference": "a95c0471682778da2e02169fb2644d3b08d4470f", + "url": "https://api.github.com/repos/symfony/Serializer/zipball/e96b7ac54b3d75a458f76eab11b7cd2d757f09f1", + "reference": "e96b7ac54b3d75a458f76eab11b7cd2d757f09f1", "shasum": "" }, "require": { @@ -2236,7 +2194,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -2260,21 +2218,21 @@ ], "description": "Symfony Serializer Component", "homepage": "http://symfony.com", - "time": "2014-09-22 09:14:18" + "time": "2014-11-03 03:55:50" }, { "name": "symfony/translation", - "version": "v2.5.5", + "version": "v2.5.6", "target-dir": "Symfony/Component/Translation", "source": { "type": "git", "url": "https://github.com/symfony/Translation.git", - "reference": "170c0d895616e1a6a35681ffb0b9e339f58ab928" + "reference": "362fe4da2cfe587f72d57aaa2f89e6b61c05dedf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Translation/zipball/170c0d895616e1a6a35681ffb0b9e339f58ab928", - "reference": "170c0d895616e1a6a35681ffb0b9e339f58ab928", + "url": "https://api.github.com/repos/symfony/Translation/zipball/362fe4da2cfe587f72d57aaa2f89e6b61c05dedf", + "reference": "362fe4da2cfe587f72d57aaa2f89e6b61c05dedf", "shasum": "" }, "require": { @@ -2316,21 +2274,21 @@ ], "description": "Symfony Translation Component", "homepage": "http://symfony.com", - "time": "2014-09-23 05:25:11" + "time": "2014-10-01 05:50:18" }, { "name": "symfony/validator", - "version": "v2.5.5", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/Validator", "source": { "type": "git", "url": "https://github.com/symfony/Validator.git", - "reference": "64f61505843ca5e6c647244f5a4b6812c1279427" + "reference": "6e521cdbc963cef7daf9931a7bde48b56d67d10a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Validator/zipball/64f61505843ca5e6c647244f5a4b6812c1279427", - "reference": "64f61505843ca5e6c647244f5a4b6812c1279427", + "url": "https://api.github.com/repos/symfony/Validator/zipball/6e521cdbc963cef7daf9931a7bde48b56d67d10a", + "reference": "6e521cdbc963cef7daf9931a7bde48b56d67d10a", "shasum": "" }, "require": { @@ -2362,7 +2320,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -2386,21 +2344,21 @@ ], "description": "Symfony Validator Component", "homepage": "http://symfony.com", - "time": "2014-09-28 15:22:14" + "time": "2014-11-03 03:55:50" }, { "name": "symfony/yaml", - "version": "dev-master", + "version": "v2.6.0-BETA1", "target-dir": "Symfony/Component/Yaml", "source": { "type": "git", "url": "https://github.com/symfony/Yaml.git", - "reference": "499f7d7aa96747ad97940089bd7a1fb24ad8182a" + "reference": "9da3813f36985a4089f7e83c601a1034d125ff69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/499f7d7aa96747ad97940089bd7a1fb24ad8182a", - "reference": "499f7d7aa96747ad97940089bd7a1fb24ad8182a", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/9da3813f36985a4089f7e83c601a1034d125ff69", + "reference": "9da3813f36985a4089f7e83c601a1034d125ff69", "shasum": "" }, "require": { @@ -2433,7 +2391,7 @@ ], "description": "Symfony Yaml Component", "homepage": "http://symfony.com", - "time": "2014-10-05 13:53:50" + "time": "2014-11-03 03:55:50" }, { "name": "twig/twig", @@ -2629,7 +2587,17 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "symfony/yaml": 20, + "symfony/class-loader": 10, + "symfony/css-selector": 10, + "symfony/debug": 10, + "symfony/dependency-injection": 10, + "symfony/event-dispatcher": 10, + "symfony/http-foundation": 10, + "symfony/http-kernel": 10, + "symfony/routing": 10, + "symfony/serializer": 10, + "symfony/validator": 10, + "symfony/yaml": 10, "doctrine/common": 20, "phpunit/phpunit-mock-objects": 20 }, diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index f9c2f18..a877d15 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -333,7 +333,6 @@ Menu UI module - ? Menu Link module -- Andrei Mateescu 'amateescu' http://drupal.org/user/729614 - Peter Wolanin 'pwolanin' http://drupal.org/user/49851 Migrate module @@ -443,7 +442,7 @@ Theme maintainers Bartik theme - Jen Simmons 'jensimmons' http://drupal.org/user/140882 -- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393 +- Emma Maria Karayiannis 'emma.maria' https://drupal.org/user/2104556 Seven theme - Lewis Nyman 'LewisNyman' https://drupal.org/user/751964 diff --git a/core/authorize.php b/core/authorize.php index 22bede6..fe4b7d0 100644 --- a/core/authorize.php +++ b/core/authorize.php @@ -22,6 +22,7 @@ use Drupal\Core\DrupalKernel; use Drupal\Core\Url; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Drupal\Core\Site\Settings; @@ -54,9 +55,16 @@ function authorize_access_allowed() { return Settings::get('allow_authorize_operations', TRUE) && \Drupal::currentUser()->hasPermission('administer software updates'); } -$request = Request::createFromGlobals(); -$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod'); -$kernel->prepareLegacyRequest($request); +try { + $request = Request::createFromGlobals(); + $kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod'); + $kernel->prepareLegacyRequest($request); +} +catch (HttpExceptionInterface $e) { + $response = new Response('', $e->getStatusCode()); + $response->prepare($request)->send(); + exit; +} // We have to enable the user and system modules, even to check access and // display errors via the maintenance theme. @@ -149,7 +157,7 @@ function authorize_access_allowed() { if (!empty($output)) { $response->headers->set('Content-Type', 'text/html; charset=utf-8'); - $response->setContent(\Drupal::service('bare_html_page_renderer')->renderMaintenancePage($output, $page_title, array( + $response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $output], $page_title, 'maintenance_page', array( '#show_messages' => $show_messages, ))); $response->send(); diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 1154ba3..b28eb75 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -300,12 +300,6 @@ block_settings: view_mode: type: string label: 'View mode' - visibility: - type: sequence - label: 'Visibility Conditions' - sequence: - - type: condition.plugin.[id] - label: 'Visibility Condition' provider: type: string label: 'Provider' @@ -628,11 +622,15 @@ field.email.value: # Schema for the configuration of the Integer field type. field.integer.storage_settings: - type: sequence + type: mapping label: 'Integer settings' - sequence: - - type: string - label: 'setting' + mapping: + unsigned: + type: boolean + label: 'Unsigned' + size: + type: string + label: 'Database storage size' field.integer.field_settings: type: mapping diff --git a/core/config/schema/core.entity.data_types.schema.yml b/core/config/schema/core.entity.data_types.schema.yml index f0b87ee..36e59fb 100644 --- a/core/config/schema/core.entity.data_types.schema.yml +++ b/core/config/schema/core.entity.data_types.schema.yml @@ -18,6 +18,9 @@ entity_field_view_display_base: label: type: string label: 'Label setting machine name' + settings: + type: sequence + label: 'Settings' # Schema for the base of the form mode display format settings. entity_field_form_display_base: @@ -34,3 +37,6 @@ entity_field_form_display_base: label: 'Third party settings' sequence: - type: entity_form_display.third_party.[%key] + settings: + type: sequence + label: 'Settings' diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml index 63a35eb..06a4215 100644 --- a/core/config/schema/core.entity.schema.yml +++ b/core/config/schema/core.entity.schema.yml @@ -145,35 +145,52 @@ entity_form_display.field.string_textfield: type: label label: 'Placeholder' -entity_form_display.field.datetime_timestamp: +entity_form_display.field.string_textarea: type: entity_field_form_display_base - label: 'Datetime timestamp display format settings' + label: 'Textarea display format settings' mapping: settings: - type: sequence + type: mapping label: 'Settings' - sequence: - - type: string + mapping: + rows: + type: integer + label: 'Rows' + placeholder: + type: label + label: 'Placeholder' + +entity_form_display.field.email_default: + type: entity_field_form_display_base + label: 'Email field display format settings' + mapping: + settings: + type: mapping + label: 'Settings' + mapping: + placeholder: + type: label + label: 'Placeholder' + +entity_form_display.field.datetime_timestamp: + type: entity_field_form_display_base + label: 'Datetime timestamp display format settings' entity_form_display.field.boolean_checkbox: type: entity_field_form_display_base label: 'Boolean checkbox display format settings' mapping: settings: - type: sequence + type: mapping label: 'Settings' - sequence: - - type: string + mapping: + display_label: + type: boolean + label: 'Display label' entity_form_display.field.hidden: type: entity_field_form_display_base label: '- Hidden - format settings' - mapping: - settings: - type: sequence - label: 'Settings' - sequence: - - type: string entity_form_display.field.number: type: entity_field_form_display_base @@ -238,29 +255,11 @@ entity_view_display.field.number_integer: entity_view_display.field.number_unformatted: type: entity_field_view_display_base label: 'Number unformatted display format settings' - mapping: - settings: - type: sequence - label: 'Settings' - sequence: - - type: string entity_view_display.field.uri_link: type: entity_field_view_display_base label: 'URI as link display format settings' - mapping: - settings: - type: sequence - label: 'Settings' - sequence: - - type: string entity_view_display.field.timestamp_ago: type: entity_field_view_display_base label: 'Timestamp ago display format settings' - mapping: - settings: - type: sequence - label: 'Settings' - sequence: - - type: string diff --git a/core/core.services.yml b/core/core.services.yml index f306a0d..ee98820 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -42,6 +42,7 @@ services: arguments: ['@database'] cache.backend.apcu: class: Drupal\Core\Cache\ApcuBackendFactory + arguments: ['@app.root'] cache.backend.php: class: Drupal\Core\Cache\PhpBackendFactory cache.bootstrap: @@ -162,7 +163,7 @@ services: arguments: ['@typed_data_manager'] cron: class: Drupal\Core\Cron - arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager', '@logger.channel.cron', '@plugin.manager.queue_worker'] + arguments: ['@module_handler', '@lock', '@queue', '@state', '@account_switcher', '@logger.channel.cron', '@plugin.manager.queue_worker'] diff.formatter: class: Drupal\Core\Diff\DiffFormatter arguments: ['@config.factory'] @@ -182,7 +183,7 @@ services: arguments: ['@request_stack', '@url_generator'] form_cache: class: Drupal\Core\Form\FormCache - arguments: ['@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@config.factory', '@request_stack', '@page_cache_request_policy'] + arguments: ['@app.root', '@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@config.factory', '@request_stack', '@page_cache_request_policy'] public: false # Private to form_builder keyvalue: class: Drupal\Core\KeyValueStore\KeyValueFactory @@ -287,10 +288,10 @@ services: arguments: ['@container.namespaces', '@cache.discovery', '@module_handler'] module_handler: class: Drupal\Core\Extension\ModuleHandler - arguments: ['%container.modules%', '@kernel', '@cache.bootstrap'] + arguments: ['@app.root', '%container.modules%', '@kernel', '@cache.bootstrap'] theme_handler: class: Drupal\Core\Extension\ThemeHandler - arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder_indicator'] + arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder_indicator'] entity.manager: class: Drupal\Core\Entity\EntityManager arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed', '@event_dispatcher'] @@ -382,6 +383,13 @@ services: event_dispatcher: class: Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher arguments: ['@service_container'] + app.root: + class: SplString + factory_service: 'app.root.factory' + factory_method: 'get' + app.root.factory: + class: Drupal\Core\AppRootFactory + arguments: ['@kernel'] controller_resolver: class: Drupal\Core\Controller\ControllerResolver arguments: ['@class_resolver', '@logger.channel.default'] @@ -659,7 +667,7 @@ services: - { name: event_subscriber } main_content_renderer.html: class: Drupal\Core\Render\MainContent\HtmlRenderer - arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler'] + arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler', '@renderer'] tags: - { name: render.main_content_renderer, format: html } main_content_renderer.ajax: @@ -692,6 +700,7 @@ services: arguments: ['@content_negotiation', '@title_resolver'] bare_html_page_renderer: class: Drupal\Core\Render\BareHtmlPageRenderer + arguments: ['@renderer'] private_key: class: Drupal\Core\PrivateKey arguments: ['@state'] @@ -986,13 +995,13 @@ services: class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry theme.manager: class: Drupal\Core\Theme\ThemeManager - arguments: ['@theme.registry', '@theme.negotiator', '@theme.initialization', '@request_stack'] + arguments: ['@app.root', '@theme.registry', '@theme.negotiator', '@theme.initialization', '@request_stack', '@module_handler'] theme.initialization: class: Drupal\Core\Theme\ThemeInitialization - arguments: ['@theme_handler', '@state'] + arguments: ['@app.root', '@theme_handler', '@state'] theme.registry: class: Drupal\Core\Theme\Registry - arguments: ['@cache.default', '@lock', '@module_handler'] + arguments: ['@app.root', '@cache.default', '@lock', '@module_handler'] tags: - { name: needs_destruction } authentication: @@ -1004,6 +1013,9 @@ services: tags: - { name: event_subscriber } arguments: ['@authentication'] + account_switcher: + class: Drupal\Core\Session\AccountSwitcher + arguments: ['@current_user', '@session_manager'] current_user: class: Drupal\Core\Session\AccountProxy arguments: ['@authentication', '@request_stack'] @@ -1041,7 +1053,7 @@ services: class: Drupal\Core\Asset\AssetDumper library.discovery: class: Drupal\Core\Asset\LibraryDiscovery - arguments: ['@library.discovery.collector'] + arguments: ['@library.discovery.collector', '@module_handler'] library.discovery.collector: class: Drupal\Core\Asset\LibraryDiscoveryCollector arguments: ['@cache.discovery', '@lock', '@library.discovery.parser'] @@ -1049,12 +1061,12 @@ services: - { name: needs_destruction } library.discovery.parser: class: Drupal\Core\Asset\LibraryDiscoveryParser - arguments: ['@module_handler'] + arguments: ['@app.root', '@module_handler'] info_parser: class: Drupal\Core\Extension\InfoParser twig: class: Drupal\Core\Template\TwigEnvironment - arguments: ['@twig.loader', '@module_handler', '@theme_handler', '%twig.config%'] + arguments: ['@app.root', '@twig.loader', '@module_handler', '@theme_handler', '%twig.config%'] tags: - { name: service_collector, tag: 'twig.extension', call: addExtension } twig.extension: @@ -1074,7 +1086,7 @@ services: alias: twig.loader.filesystem twig.loader.filesystem: class: Twig_Loader_Filesystem - arguments: ['%app.root%'] + arguments: ['@app.root'] element_info: alias: plugin.manager.element_info file.mime_type.guesser: @@ -1086,3 +1098,6 @@ services: arguments: ['@module_handler'] tags: - { name: mime_type_guesser } + renderer: + class: Drupal\Core\Render\Renderer + arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info'] diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 4f527b3..088889d 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -135,7 +135,7 @@ function _batch_progress_page() { // additional HTML output by PHP shows up inside the page rather than below // it. While this causes invalid HTML, the same would be true if we didn't, // as content is not allowed to appear after anyway. - $fallback = \Drupal::service('bare_html_page_renderer')->renderMaintenancePage($fallback, $current_set['title'], array( + $fallback = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', array( '#show_messages' => FALSE, )); list($fallback) = explode('', $fallback); @@ -235,7 +235,7 @@ function _batch_process() { // request, we check if it requires an additional file for functions // definitions. if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { - include_once DRUPAL_ROOT . '/' . $current_set['file']; + include_once \Drupal::root() . '/' . $current_set['file']; } $task_message = $label = ''; @@ -408,7 +408,7 @@ function _batch_finished() { if (isset($batch_set['finished'])) { // Check if the set requires an additional file for function definitions. if (isset($batch_set['file']) && is_file($batch_set['file'])) { - include_once DRUPAL_ROOT . '/' . $batch_set['file']; + include_once \Drupal::root() . '/' . $batch_set['file']; } if (is_callable($batch_set['finished'])) { $queue = _batch_queue($batch_set); diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 5f6c9d4..43d7368 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -277,7 +277,7 @@ function drupal_get_filename($type, $name, $filename = NULL) { } // If still unknown, perform a filesystem scan. if (!isset($files[$type][$name])) { - $listing = new ExtensionDiscovery(); + $listing = new ExtensionDiscovery(DRUPAL_ROOT); // Prevent an infinite recursion by this legacy function. if ($original_type == 'profile') { $listing->setProfileDirectories(array()); @@ -1190,7 +1190,7 @@ function _current_path($path = NULL) { */ function drupal_classloader_register($name, $path) { $loader = \Drupal::service('class_loader'); - $loader->addPsr4('Drupal\\' . $name . '\\', DRUPAL_ROOT . '/' . $path . '/src'); + $loader->addPsr4('Drupal\\' . $name . '\\', \Drupal::root() . '/' . $path . '/src'); } /** diff --git a/core/includes/common.inc b/core/includes/common.inc index 001c9e7..e7d19ed 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -22,7 +22,6 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Cache\Cache; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Render\RenderStackFrame; use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; @@ -2024,10 +2023,6 @@ function _drupal_add_library($library_name, $every_page = NULL) { // Only process the library if it exists and it was not added already. if (!isset($added[$extension][$name])) { if ($library = $library_discovery->getLibraryByName($extension, $name)) { - // Allow modules and themes to dynamically attach request and context - // specific data for this library; e.g., localization. - \Drupal::moduleHandler()->alter('library', $library, $library_name); - // Add all components within the library. $elements['#attached'] = array( 'library' => $library['dependencies'], @@ -2365,509 +2360,25 @@ function drupal_pre_render_links($element) { /** * Renders final HTML given a structured array tree. * - * Calls drupal_render() in such a way that #post_render_cache callbacks are - * applied. - * - * Should therefore only be used in occasions where the final rendering is - * happening, just before sending a Response: - * - system internals that are responsible for rendering the final HTML - * - render arrays for non-HTML responses, such as feeds - * - * @param array $elements - * The structured array describing the data to be rendered. - * - * @return string - * The rendered HTML. + * @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use the + * 'renderer' service instead. * - * @see drupal_render() + * @see \Drupal\Core\Render\RendererInterface::renderRoot() */ function drupal_render_root(&$elements) { - return drupal_render($elements, TRUE); + return \Drupal::service('renderer')->renderRoot($elements); } /** * Renders HTML given a structured array tree. * - * Renderable arrays have two kinds of key/value pairs: properties and children. - * Properties have keys starting with '#' and their values influence how the - * array will be rendered. Children are all elements whose keys do not start - * with a '#'. Their values should be renderable arrays themselves, which will - * be rendered during the rendering of the parent array. The markup provided by - * the children is typically inserted into the markup generated by the parent - * array. - * - * An important aspect of rendering is the bubbling of rendering metadata: cache - * tags, attached assets and #post_render_cache metadata all need to be bubbled - * up. That information is needed once the rendering to a HTML string is - * completed: the resulting HTML for the page must know by which cache tags it - * should be invalidated, which (CSS and JavaScript) assets must be loaded, and - * which #post_render_cache callbacks should be executed. A stack data structure - * is used to perform this bubbling. - * - * The process of rendering an element is recursive unless the element defines - * an implemented theme hook in #theme. During each call to drupal_render(), the - * outermost renderable array (also known as an "element") is processed using - * the following steps: - * - If this element has already been printed (#printed = TRUE) or the user - * does not have access to it (#access = FALSE), then an empty string is - * returned. - * - If no stack data structure has been created yet, it is done now. Next, - * an empty \Drupal\Core\Render\RenderStackFrame is pushed onto the stack. - * - If this element has #cache defined then the cached markup for this - * element will be returned if it exists in drupal_render()'s cache. To use - * drupal_render() caching, set the element's #cache property to an - * associative array with one or several of the following keys: - * - 'keys': An array of one or more keys that identify the element. If - * 'keys' is set, the cache ID is created automatically from these keys. - * Cache keys may either be static (just strings) or tokens (placeholders - * that are converted to static keys by the @cache_contexts service, - * depending on the request). See drupal_render_cid_create(). - * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is - * required. If 'cid' is set, 'keys' is ignored. Use only if you have - * special requirements. - * - 'expire': Set to one of the cache lifetime constants. - * - 'bin': Specify a cache bin to cache the element in. Default is - * 'default'. - * When there is a render cache hit, there is no rendering work left to be - * done, so the stack must be updated. The empty (and topmost) frame that - * was just pushed onto the stack is updated with all bubbleable rendering - * metadata from the element retrieved from render cache. Then, this stack - * frame is bubbled: the two topmost frames are popped from the stack, they - * are merged, and the result is pushed back onto the stack. - * - If this element has #type defined and the default attributes for this - * element have not already been merged in (#defaults_loaded = TRUE) then - * the defaults for this type of element, defined in hook_element_info(), - * are merged into the array. #defaults_loaded is set by functions that - * process render arrays and call element_info() before passing the array to - * drupal_render(), such as \Drupal::formBuilder()->doBuildForm() in the - * Form API. - * - If this element has an array of #pre_render functions defined, they are - * called sequentially to modify the element before rendering. After all the - * #pre_render functions have been called, #printed is checked a second time - * in case a #pre_render function flags the element as printed. - * If #printed is set, we return early and hence no rendering work is left - * to be done, similarly to a render cache hit. Once again, the empty (and - * topmost) frame that was just pushed onto the stack is updated with all - * bubbleable rendering metadata from the element whose #printed = TRUE. - * Then, this stack frame is bubbled: the two topmost frames are popped from - * the stack, they are merged, and the result is pushed back onto the stack. - * - The child elements of this element are sorted by weight using uasort() in - * \Drupal\Core\Render\Element::children(). Since this is expensive, when - * passing already sorted elements to drupal_render(), for example from a - * database query, set $elements['#sorted'] = TRUE to avoid sorting them a - * second time. - * - The main render phase to produce #children for this element takes place: - * - If this element has #theme defined and #theme is an implemented theme - * hook/suggestion then _theme() is called and must render both the element - * and its children. If #render_children is set, _theme() will not be - * called. #render_children is usually only set internally by _theme() so - * that we can avoid the situation where drupal_render() called from - * within a theme preprocess function creates an infinite loop. - * - If this element does not have a defined #theme, or the defined #theme - * hook is not implemented, or #render_children is set, then - * drupal_render() is called recursively on each of the child elements of - * this element, and the result of each is concatenated onto #children. - * This is skipped if #children is not empty at this point. - * - Once #children has been rendered for this element, if #theme is not - * implemented and #markup is set for this element, #markup will be - * prepended to #children. - * - If this element has #states defined then JavaScript state information is - * added to this element's #attached attribute by drupal_process_states(). - * - If this element has #attached defined then any required libraries, - * JavaScript, CSS, or other custom data are added to the current page by - * drupal_process_attached(). - * - If this element has an array of #theme_wrappers defined and - * #render_children is not set, #children is then re-rendered by passing the - * element in its current state to _theme() successively for each item in - * #theme_wrappers. Since #theme and #theme_wrappers hooks often define - * variables with the same names it is possible to explicitly override each - * attribute passed to each #theme_wrappers hook by setting the hook name as - * the key and an array of overrides as the value in #theme_wrappers array. - * For example, if we have a render element as follows: - * @code - * array( - * '#theme' => 'image', - * '#attributes' => array('class' => array('foo')), - * '#theme_wrappers' => array('container'), - * ); - * @endcode - * and we need to pass the class 'bar' as an attribute for 'container', we - * can rewrite our element thus: - * @code - * array( - * '#theme' => 'image', - * '#attributes' => array('class' => array('foo')), - * '#theme_wrappers' => array( - * 'container' => array( - * '#attributes' => array('class' => array('bar')), - * ), - * ), - * ); - * @endcode - * - If this element has an array of #post_render functions defined, they are - * called sequentially to modify the rendered #children. Unlike #pre_render - * functions, #post_render functions are passed both the rendered #children - * attribute as a string and the element itself. - * - If this element has #prefix and/or #suffix defined, they are concatenated - * to #children. - * - The rendering of this element is now complete. The next step will be - * render caching. So this is the perfect time to update the the stack. At - * this point, children of this element (if any), have been rendered also, - * and if there were any, their bubbleable rendering metadata will have been - * bubbled up into the stack frame for the element that is currently being - * rendered. The render cache item for this element must contain the - * bubbleable rendering metadata for this element and all of its children. - * However, right now, the topmost stack frame (the one for this element) - * currently only contains the metadata for the children. Therefore, the - * topmost stack frame is updated with this element's metadata, and then the - * element's metadata is replaced with the metadata in the topmost stack - * frame. This element now contains all bubbleable rendering metadata for - * this element and all its children, so it's now ready for render caching. - * - If this element has #cache defined, the rendered output of this element - * is saved to drupal_render()'s internal cache. This includes the changes - * made by #post_render. - * - If this element has an array of #post_render_cache functions defined, or - * any of its children has (which we would know thanks to the stack having - * been updated just before the render caching step), they are called - * sequentially to replace placeholders in the final #markup and extend - * #attached. Placeholders must contain a unique token, to guarantee that - * e.g. samples of placeholders are not replaced also. - * But, since #post_render_cache callbacks add attach additional assets, the - * correct bubbling of those must once again be taken into account. This - * final stage of rendering should be considered as if it were the parent of - * the current element, because it takes that as its input, and then alters - * its #markup. Hence, just before calling the #post_render_cache callbacks, - * a new empty frame is pushed onto the stack, where all assets #attached - * during the execution of those callbacks will end up in. Then, after the - * execution of those callbacks, we merge that back into the element. - * Note that these callbacks run always: when hitting the render cache, when - * missing, or when render caching is not used at all. This is done to allow - * any Drupal module to customize other render arrays without breaking the - * render cache if it is enabled, and to not require it to use other logic - * when render caching is disabled. - * - Just before finishing the rendering of this element, this element's stack - * frame (the topmost one) is bubbled: the two topmost frames are popped - * from the stack, they are merged and the result is pushed back onto the - * stack. - * So if this element e.g. was a child element, then a new frame was pushed - * onto the stack element at the beginning of rendering this element, it was - * updated when the rendering was completed, and now we merge it with the - * frame for the parent, so that the parent now has the bubbleable rendering - * metadata for its child. - * - #printed is set to TRUE for this element to ensure that it is only - * rendered once. - * - The final value of #children for this element is returned as the rendered - * output. - * - * @param array $elements - * The structured array describing the data to be rendered. - * @param bool $is_root_call - * (Internal use only.) Whether this is a recursive call or not. See - * drupal_render_root(). - * - * @return string - * The rendered HTML. + * @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use the + * 'renderer' service instead. * - * @throws \LogicException - * If a root call to drupal_render() does not result in an empty stack, this - * indicates an erroneous drupal_render() root call (a root call within a root - * call, which makes no sense). Therefore, a logic exception is thrown. - * @throws \Exception - * If a #pre_render callback throws an exception, it is caught to reset the - * stack used for bubbling rendering metadata, and then the exception is re- - * thrown. - * - * @see element_info() - * @see _theme() - * @see drupal_process_states() - * @see drupal_process_attached() - * @see drupal_render_root() - */ -function drupal_render(&$elements, $is_root_call = FALSE) { - static $stack; - - $update_stack = function(&$element) use (&$stack) { - // The latest frame represents the bubbleable data for the subtree. - $frame = $stack->top(); - // Update the frame, but also update the current element, to ensure it - // contains up-to-date information in case it gets render cached. - $frame->tags = $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $frame->tags); - $frame->attached = $element['#attached'] = drupal_merge_attached($element['#attached'], $frame->attached); - $frame->postRenderCache = $element['#post_render_cache'] = NestedArray::mergeDeep($element['#post_render_cache'], $frame->postRenderCache); - }; - - $bubble_stack = function() use (&$stack) { - // If there's only one frame on the stack, then this is the root call, and - // we can't bubble up further. Reset the stack for the next root call. - if ($stack->count() === 1) { - $stack = NULL; - return; - } - - // Merge the current and the parent stack frame. - $current = $stack->pop(); - $parent = $stack->pop(); - $current->tags = Cache::mergeTags($current->tags, $parent->tags); - $current->attached = drupal_merge_attached($current->attached, $parent->attached); - $current->postRenderCache = NestedArray::mergeDeep($current->postRenderCache, $parent->postRenderCache); - $stack->push($current); - }; - /** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */ - $controller_resolver = \Drupal::service('controller_resolver'); - if (!isset($elements['#access']) && isset($elements['#access_callback'])) { - if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) { - $elements['#access_callback'] = $controller_resolver->getControllerFromDefinition($elements['#access_callback']); - } - $elements['#access'] = call_user_func($elements['#access_callback'], $elements); - } - - // Early-return nothing if user does not have access. - if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) { - return ''; - } - - // Do not print elements twice. - if (!empty($elements['#printed'])) { - return ''; - } - - if (!isset($stack)) { - $stack = new \SplStack(); - } - $stack->push(new RenderStackFrame()); - - // Try to fetch the prerendered element from cache, run any #post_render_cache - // callbacks and return the final markup. - if (isset($elements['#cache'])) { - $cached_element = drupal_render_cache_get($elements); - if ($cached_element !== FALSE) { - $elements = $cached_element; - // Only when we're not in a root (non-recursive) drupal_render() call, - // #post_render_cache callbacks must be executed, to prevent breaking the - // render cache in case of nested elements with #cache set. - if ($is_root_call) { - _drupal_render_process_post_render_cache($elements); - } - $elements['#markup'] = SafeMarkup::set($elements['#markup']); - // The render cache item contains all the bubbleable rendering metadata for - // the subtree. - $update_stack($elements); - // Render cache hit, so rendering is finished, all necessary info collected! - $bubble_stack(); - return $elements['#markup']; - } - } - - // If the default values for this element have not been loaded yet, populate - // them. - if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) { - $elements += element_info($elements['#type']); - } - - // Make any final changes to the element before it is rendered. This means - // that the $element or the children can be altered or corrected before the - // element is rendered into the final text. - if (isset($elements['#pre_render'])) { - foreach ($elements['#pre_render'] as $callable) { - if (is_string($callable) && strpos($callable, '::') === FALSE) { - $callable = $controller_resolver->getControllerFromDefinition($callable); - } - // Since #pre_render callbacks may be used for generating a render array's - // content, and we might be rendering the main content for the page, it is - // possible that a #pre_render callback throws an exception that will - // cause a different page to be rendered (e.g. throwing - // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will - // cause the 404 page to be rendered). That page might also use - // drupal_render(), but if exceptions aren't caught here, the stack will - // be left in an inconsistent state. - // Hence, catch all exceptions and reset the stack and re-throw them. - try { - $elements = call_user_func($callable, $elements); - } - catch (\Exception $e) { - // Reset stack and re-throw exception. - $stack = NULL; - throw $e; - } - } - } - - // Defaults for bubbleable rendering metadata. - $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array(); - $elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array(); - $elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array(); - - // Allow #pre_render to abort rendering. - if (!empty($elements['#printed'])) { - // The #printed element contains all the bubbleable rendering metadata for - // the subtree. - $update_stack($elements); - // #printed, so rendering is finished, all necessary info collected! - $bubble_stack(); - return ''; - } - - // Add any JavaScript state information associated with the element. - if (!empty($elements['#states'])) { - drupal_process_states($elements); - } - - // Get the children of the element, sorted by weight. - $children = Element::children($elements, TRUE); - - // Initialize this element's #children, unless a #pre_render callback already - // preset #children. - if (!isset($elements['#children'])) { - $elements['#children'] = ''; - } - - // @todo Simplify after https://drupal.org/node/2273925 - if (isset($elements['#markup'])) { - $elements['#markup'] = SafeMarkup::set($elements['#markup']); - } - - // Assume that if #theme is set it represents an implemented hook. - $theme_is_implemented = isset($elements['#theme']); - // Check the elements for insecure HTML and pass through sanitization. - if (isset($elements)) { - $markup_keys = array( - '#description', - '#field_prefix', - '#field_suffix', - ); - foreach ($markup_keys as $key) { - if (!empty($elements[$key]) && is_scalar($elements[$key])) { - $elements[$key] = SafeMarkup::checkAdminXss($elements[$key]); - } - } - } - - // Call the element's #theme function if it is set. Then any children of the - // element have to be rendered there. If the internal #render_children - // property is set, do not call the #theme function to prevent infinite - // recursion. - if ($theme_is_implemented && !isset($elements['#render_children'])) { - $elements['#children'] = \Drupal::theme()->render($elements['#theme'], $elements); - - // If _theme() returns FALSE this means that the hook in #theme was not - // found in the registry and so we need to update our flag accordingly. This - // is common for theme suggestions. - $theme_is_implemented = ($elements['#children'] !== FALSE); - } - - // If #theme is not implemented or #render_children is set and the element has - // an empty #children attribute, render the children now. This is the same - // process as drupal_render_children() but is inlined for speed. - if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) { - foreach ($children as $key) { - $elements['#children'] .= drupal_render($elements[$key]); - } - $elements['#children'] = SafeMarkup::set($elements['#children']); - } - - // If #theme is not implemented and the element has raw #markup as a - // fallback, prepend the content in #markup to #children. In this case - // #children will contain whatever is provided by #pre_render prepended to - // what is rendered recursively above. If #theme is implemented then it is - // the responsibility of that theme implementation to render #markup if - // required. Eventually #theme_wrappers will expect both #markup and - // #children to be a single string as #children. - if (!$theme_is_implemented && isset($elements['#markup'])) { - $elements['#children'] = SafeMarkup::set($elements['#markup'] . $elements['#children']); - } - - // Let the theme functions in #theme_wrappers add markup around the rendered - // children. - // #states and #attached have to be processed before #theme_wrappers, because - // the #type 'page' render array from drupal_prepare_page() would render the - // $page and wrap it into the html.html.twig template without the attached - // assets otherwise. - // If the internal #render_children property is set, do not call the - // #theme_wrappers function(s) to prevent infinite recursion. - if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) { - foreach ($elements['#theme_wrappers'] as $key => $value) { - // If the value of a #theme_wrappers item is an array then the theme hook - // is found in the key of the item and the value contains attribute - // overrides. Attribute overrides replace key/value pairs in $elements for - // only this _theme() call. This allows #theme hooks and #theme_wrappers - // hooks to share variable names without conflict or ambiguity. - $wrapper_elements = $elements; - if (is_string($key)) { - $wrapper_hook = $key; - foreach ($value as $attribute => $override) { - $wrapper_elements[$attribute] = $override; - } - } - else { - $wrapper_hook = $value; - } - - $elements['#children'] = \Drupal::theme()->render($wrapper_hook, $wrapper_elements); - } - } - - // Filter the outputted content and make any last changes before the - // content is sent to the browser. The changes are made on $content - // which allows the outputted text to be filtered. - if (isset($elements['#post_render'])) { - foreach ($elements['#post_render'] as $callable) { - if (is_string($callable) && strpos($callable, '::') === FALSE) { - $callable = $controller_resolver->getControllerFromDefinition($callable); - } - $elements['#children'] = call_user_func($callable, $elements['#children'], $elements); - } - } - - // We store the resulting output in $elements['#markup'], to be consistent - // with how render cached output gets stored. This ensures that - // #post_render_cache callbacks get the same data to work with, no matter if - // #cache is disabled, #cache is enabled, there is a cache hit or miss. - $prefix = isset($elements['#prefix']) ? SafeMarkup::checkAdminXss($elements['#prefix']) : ''; - $suffix = isset($elements['#suffix']) ? SafeMarkup::checkAdminXss($elements['#suffix']) : ''; - - $elements['#markup'] = $prefix . $elements['#children'] . $suffix; - - // We've rendered this element (and its subtree!), now update the stack. - $update_stack($elements); - - // Cache the processed element if #cache is set. - if (isset($elements['#cache'])) { - drupal_render_cache_set($elements['#markup'], $elements); - } - - // Only when we're in a root (non-recursive) drupal_render() call, - // #post_render_cache callbacks must be executed, to prevent breaking the - // render cache in case of nested elements with #cache set. - // - // By running them here, we ensure that: - // - they run when #cache is disabled, - // - they run when #cache is enabled and there is a cache miss. - // Only the case of a cache hit when #cache is enabled, is not handled here, - // that is handled earlier in drupal_render(). - if ($is_root_call) { - // We've already called $update_stack() earlier, which updated both the - // element and current stack frame. However, - // _drupal_render_process_post_render_cache() can both change the element - // further and create and render new child elements, so provide a fresh - // stack frame to collect those additions, merge them back to the element, - // and then update the current frame to match the modified element state. - $stack->push(new RenderStackFrame()); - _drupal_render_process_post_render_cache($elements); - $post_render_additions = $stack->pop(); - $elements['#cache']['tags'] = Cache::mergeTags($elements['#cache']['tags'], $post_render_additions->tags); - $elements['#attached'] = drupal_merge_attached($elements['#attached'], $post_render_additions->attached); - $elements['#post_render_cache'] = NestedArray::mergeDeep($elements['#post_render_cache'], $post_render_additions->postRenderCache); - if ($stack->count() !== 1) { - throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.'); - } - } - - // Rendering is finished, all necessary info collected! - $bubble_stack(); - - $elements['#printed'] = TRUE; - $elements['#markup'] = SafeMarkup::set($elements['#markup']); - return $elements['#markup']; + * @see \Drupal\Core\Render\RendererInterface::render() + */ +function drupal_render(&$elements, $is_recursive_call = FALSE) { + return \Drupal::service('renderer')->render($elements, $is_recursive_call); } /** @@ -3106,43 +2617,6 @@ function drupal_render_cache_generate_placeholder($callback, array &$context) { } /** - * Processes #post_render_cache callbacks. - * - * #post_render_cache callbacks may modify: - * - #markup: to replace placeholders - * - #attached: to add libraries or JavaScript settings - * - * Note that in either of these cases, #post_render_cache callbacks are - * implicitly idempotent: a placeholder that has been replaced can't be replaced - * again, and duplicate attachments are ignored. - * - * @param array &$elements - * The structured array describing the data being rendered. - * - * @see drupal_render() - * @see drupal_render_collect_post_render_cache - */ -function _drupal_render_process_post_render_cache(array &$elements) { - if (isset($elements['#post_render_cache'])) { - /** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */ - $controller_resolver = \Drupal::service('controller_resolver'); - - // Call all #post_render_cache callbacks, passing the provided context. - foreach (array_keys($elements['#post_render_cache']) as $callback) { - if (strpos($callback, '::') === FALSE) { - $callable = $controller_resolver->getControllerFromDefinition($callback); - } - else { - $callable = $callback; - } - foreach ($elements['#post_render_cache'][$callback] as $context) { - $elements = call_user_func_array($callable, array($elements, $context)); - } - } - } -} - -/** * Creates the cache ID for a renderable element. * * This creates the cache ID string, either by returning the #cache['cid'] diff --git a/core/includes/errors.inc b/core/includes/errors.inc index be30937..fbdb68c 100644 --- a/core/includes/errors.inc +++ b/core/includes/errors.inc @@ -199,8 +199,8 @@ function _drupal_log_error($error, $fatal = FALSE) { // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path // in the message. This does not happen for (false) security. - $root_length = strlen(DRUPAL_ROOT); - if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) { + $root_length = strlen(\Drupal::root()); + if (substr($error['%file'], 0, $root_length) == \Drupal::root()) { $error['%file'] = substr($error['%file'], $root_length + 1); } // Should not translate the string to avoid errors producing more errors. @@ -235,7 +235,7 @@ function _drupal_log_error($error, $fatal = FALSE) { install_display_output($output, $GLOBALS['install_state']); } else { - $output = \Drupal::service('bare_html_page_renderer')->renderMaintenancePage($message, 'Error'); + $output = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page'); } $response = new Response($output, 500); diff --git a/core/includes/form.inc b/core/includes/form.inc index 0bffd39..075e37b 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -71,18 +71,6 @@ function form_state_values_clean(FormStateInterface $form_state) { } /** - * Changes submitted form values during form validation. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use $form_state->setValueForElement(). - * - * @see \Drupal\Core\Form\FormStateInterface::setValueForElement(). - */ -function form_set_value($element, $value, FormStateInterface $form_state) { - $form_state->setValueForElement($element, $value); -} - -/** * Allows PHP array processing of multiple select options with the same value. * * Used for form select elements which need to validate HTML option groups diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 2adc576..70ea333 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -287,7 +287,7 @@ function install_begin_request(&$install_state) { $site_path = DrupalKernel::findSitePath($request, FALSE); $class_loader = require __DIR__ . '/../vendor/autoload.php'; - Settings::initialize($site_path, $class_loader); + Settings::initialize(dirname(dirname(__DIR__)), $site_path, $class_loader); // Ensure that procedural dependencies are loaded as early as possible, // since the error/exception handlers depend on them. @@ -333,7 +333,7 @@ function install_begin_request(&$install_state) { \Drupal::setContainer($container); // Determine whether base system services are ready to operate. - $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY); + $install_state['config_verified'] = install_ensure_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_ensure_config_directory(CONFIG_STAGING_DIRECTORY); $install_state['database_verified'] = install_verify_database_settings(); $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified']; @@ -389,7 +389,7 @@ function install_begin_request(&$install_state) { } // Add list of all available profiles to the installation state. - $listing = new ExtensionDiscovery(); + $listing = new ExtensionDiscovery($container->get('app.root')); $listing->setProfileDirectories(array()); $install_state['profiles'] += $listing->scan('profile'); @@ -717,7 +717,7 @@ function install_tasks($install_state) { $profile = $install_state['parameters']['profile']; $profile_install_file = $install_state['profiles'][$profile]->getPath() . '/' . $profile . '.install'; if (file_exists($profile_install_file)) { - include_once DRUPAL_ROOT . '/' . $profile_install_file; + include_once \Drupal::root() . '/' . $profile_install_file; } $function = $install_state['parameters']['profile'] . '_install_tasks'; if (function_exists($function)) { @@ -933,7 +933,7 @@ function install_display_output($output, $install_state) { 'ETag' => '"' . REQUEST_TIME . '"', ); $response->headers->add($default_headers); - $response->setContent(\Drupal::service('bare_html_page_renderer')->renderInstallPage($output, $output['#title'], $regions)); + $response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage($output, $output['#title'], 'install_page', $regions)); $response->send(); exit; } diff --git a/core/includes/install.inc b/core/includes/install.inc index ec6283b..ead2104 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -515,41 +515,6 @@ function drupal_install_config_directories() { } /** - * Checks whether a config directory exists and is writable. - * - * This partially duplicates install_ensure_config_directory(), but is required - * since the installer would create the config directory too early in the - * installation process otherwise (e.g., when only visiting install.php when - * there is a settings.php already, but not actually executing the - * installation). - * - * @param string $type - * Type of config directory to return. Drupal core provides 'active' and - * 'staging'. - * - * @return bool - * TRUE if the config directory exists and is writable. - */ -function install_verify_config_directory($type) { - global $config_directories; - if (!isset($config_directories[$type])) { - return FALSE; - } - // config_get_config_directory() throws an exception when the passed $type - // does not exist in $config_directories. This can happen if there is a - // prepared settings.php that defines $config_directories already. - try { - $config_directory = config_get_config_directory($type); - if (is_dir($config_directory) && is_writable($config_directory)) { - return TRUE; - } - } - catch (\Exception $e) { - } - return FALSE; -} - -/** * Ensures that the config directory exists and is writable, or can be made so. * * @param string $type @@ -593,7 +558,7 @@ function drupal_verify_profile($install_state) { $info = $install_state['profile_info']; // Get the list of available modules for the selected installation profile. - $listing = new ExtensionDiscovery(); + $listing = new ExtensionDiscovery(DRUPAL_ROOT); $present_modules = array(); foreach ($listing->scan('module') as $present_module) { $present_modules[] = $present_module->getName(); diff --git a/core/includes/mail.inc b/core/includes/mail.inc index 3ad7be2..b240421 100644 --- a/core/includes/mail.inc +++ b/core/includes/mail.inc @@ -14,10 +14,10 @@ * and possibly email headers) and the replacement values to use in the * appropriate places in the template. Processed email templates are * requested from hook_mail() from the module sending the email. Any module - * can modify the composed email message array using hook_mail_alter(). - * Finally drupal_mail_system()->mail() sends the email, which can - * be reused if the exact same composed email is to be sent to multiple - * recipients. + * can modify the composed email message array using hook_mail_alter(). Finally + * \Drupal::service('plugin.manager.mail')->getInstance()->mail() sends the + * email, which can be reused if the exact same composed email is to be sent to + * multiple recipients. * * Finding out what language to send the email with needs some consideration. * If you send email to a user, her preferred language should be fine, so @@ -101,7 +101,8 @@ * @param string|null $reply * Optional email address to be used to answer. * @param bool $send - * If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver + * If TRUE, drupal_mail() will call + * \Drupal::service('plugin.manager.mail')->getInstance()->mail() to deliver * the message, and store the result in $message['result']. Modules * implementing hook_mail_alter() may cancel sending by setting * $message['send'] to FALSE. @@ -146,27 +147,3 @@ function drupal_mail($module, $key, $to, $langcode, $params = array(), $reply = function drupal_mail_system($module, $key) { return \Drupal::service('plugin.manager.mail')->getInstance(array('module' => $module, 'key' => $key)); } - -/** - * Performs format=flowed soft wrapping for mail (RFC 3676). - * - * We use delsp=yes wrapping, but only break non-spaced languages when - * absolutely necessary to avoid compatibility issues. - * - * We deliberately use LF rather than CRLF, see drupal_mail(). - * - * @param $text - * The plain text to process. - * @param $indent (optional) - * A string to indent the text with. Only '>' characters are repeated on - * subsequent wrapped lines. Others are replaced by spaces. - * - * @return - * The content of the email as a string with formatting applied. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use \Drupal\Core\Utility\Mail::wrapMail(). - */ -function drupal_wrap_mail($text, $indent = '') { - return MailFormatHelper::wrapMail($text, $indent); -} diff --git a/core/includes/menu.inc b/core/includes/menu.inc index b42ff1c..00a2a0a 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -54,7 +54,7 @@ * dblog.overview: * path: '/admin/reports/dblog' * defaults: - * _content: '\Drupal\dblog\Controller\DbLogController::overview' + * _controller: '\Drupal\dblog\Controller\DbLogController::overview' * _title: 'Recent log messages' * requirements: * _permission: 'access site reports' @@ -69,8 +69,6 @@ * and can also give other information, such as the page title and additional * arguments for the route controller method. There are several possibilities * for how to build the main content, including: - * - _content: A callable, usually a method on a page controller class - * (see @ref sec_controller below for details). * - _controller: A callable, usually a method on a page controller class * (see @ref sec_controller below for details). * - _form: A form controller class. See the diff --git a/core/includes/module.inc b/core/includes/module.inc index ac19a62..606d813 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -179,7 +179,7 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) * Returns an array of modules required by core. */ function drupal_required_modules() { - $listing = new ExtensionDiscovery(); + $listing = new ExtensionDiscovery(\Drupal::root()); $files = $listing->scan('module'); $required = array(); diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 493ceab..34dbdc2 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -157,307 +157,6 @@ function list_themes($refresh = FALSE) { } /** - * Generates themed output (internal use only). - * - * _theme() is an internal function. Do not call this function directly as it - * will prevent the following items from working correctly: - * - Render caching. - * - JavaScript and CSS asset attachment. - * - Pre / post render hooks. - * - Defaults provided by hook_element_info(), including attached assets. - * Instead, build a render array with a #theme key, and either return the - * array (where possible) or call drupal_render() to convert it to HTML. - * - * All requests for themed output must go through this function, which is - * invoked as part of the @link theme_render drupal_render() process @endlink. - * The appropriate theme function is indicated by the #theme property - * of a renderable array. _theme() examines the request and routes it to the - * appropriate @link themeable theme function or template @endlink, by checking - * the theme registry. - * - * @param $hook - * The name of the theme hook to call. If the name contains a - * double-underscore ('__') and there isn't an implementation for the full - * name, the part before the '__' is checked. This allows a fallback to a - * more generic implementation. For example, if _theme('links__node', ...) is - * called, but there is no implementation of that theme hook, then the - * 'links' implementation is used. This process is iterative, so if - * _theme('links__contextual__node', ...) is called, _theme() checks for the - * following implementations, and uses the first one that exists: - * - links__contextual__node - * - links__contextual - * - links - * This allows themes to create specific theme implementations for named - * objects and contexts of otherwise generic theme hooks. The $hook parameter - * may also be an array, in which case the first theme hook that has an - * implementation is used. This allows for the code that calls _theme() to - * explicitly specify the fallback order in a situation where using the '__' - * convention is not desired or is insufficient. - * @param $variables - * An associative array of variables to merge with defaults from the theme - * registry, pass to preprocess functions for modification, and finally, pass - * to the function or template implementing the theme hook. Alternatively, - * this can be a renderable array, in which case, its properties are mapped to - * variables expected by the theme hook implementations. - * - * @return string|false - * An HTML string representing the themed output or FALSE if the passed $hook - * is not implemented. - * - * @see drupal_render() - * @see themeable - * @see hook_theme() - * @see template_preprocess() - */ -function _theme($hook, $variables = array()) { - static $default_attributes; - - $module_handler = \Drupal::moduleHandler(); - $active_theme = \Drupal::theme()->getActiveTheme(); - - // If called before all modules are loaded, we do not necessarily have a full - // theme registry to work with, and therefore cannot process the theme - // request properly. See also \Drupal\Core\Theme\Registry::get(). - if (!$module_handler->isLoaded() && !defined('MAINTENANCE_MODE')) { - throw new Exception(t('_theme() may not be called until all modules are loaded.')); - } - - /** @var \Drupal\Core\Utility\ThemeRegistry $theme_registry */ - $theme_registry = \Drupal::service('theme.registry')->getRuntime(); - - // If an array of hook candidates were passed, use the first one that has an - // implementation. - if (is_array($hook)) { - foreach ($hook as $candidate) { - if ($theme_registry->has($candidate)) { - break; - } - } - $hook = $candidate; - } - // Save the original theme hook, so it can be supplied to theme variable - // preprocess callbacks. - $original_hook = $hook; - - // If there's no implementation, check for more generic fallbacks. If there's - // still no implementation, log an error and return an empty string. - if (!$theme_registry->has($hook)) { - // Iteratively strip everything after the last '__' delimiter, until an - // implementation is found. - while ($pos = strrpos($hook, '__')) { - $hook = substr($hook, 0, $pos); - if ($theme_registry->has($hook)) { - break; - } - } - if (!$theme_registry->has($hook)) { - // Only log a message when not trying theme suggestions ($hook being an - // array). - if (!isset($candidate)) { - \Drupal::logger('theme')->warning('Theme hook %hook not found.', array('%hook' => $hook)); - } - // There is no theme implementation for the hook passed. Return FALSE so - // the function calling _theme() can differentiate between a hook that - // exists and renders an empty string and a hook that is not implemented. - return FALSE; - } - } - - $info = $theme_registry->get($hook); - - // If a renderable array is passed as $variables, then set $variables to - // the arguments expected by the theme function. - if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) { - $element = $variables; - $variables = array(); - if (isset($info['variables'])) { - foreach (array_keys($info['variables']) as $name) { - if (isset($element["#$name"]) || array_key_exists("#$name", $element)) { - $variables[$name] = $element["#$name"]; - } - } - } - else { - $variables[$info['render element']] = $element; - // Give a hint to render engines to prevent infinite recursion. - $variables[$info['render element']]['#render_children'] = TRUE; - } - } - - // Merge in argument defaults. - if (!empty($info['variables'])) { - $variables += $info['variables']; - } - elseif (!empty($info['render element'])) { - $variables += array($info['render element'] => array()); - } - // Supply original caller info. - $variables += array( - 'theme_hook_original' => $original_hook, - ); - - // Set base hook for later use. For example if '#theme' => 'node__article' - // is called, we run hook_theme_suggestions_node_alter() rather than - // hook_theme_suggestions_node__article_alter(), and also pass in the base - // hook as the last parameter to the suggestions alter hooks. - if (isset($info['base hook'])) { - $base_theme_hook = $info['base hook']; - } - else { - $base_theme_hook = $hook; - } - - // Invoke hook_theme_suggestions_HOOK(). - $suggestions = $module_handler->invokeAll('theme_suggestions_' . $base_theme_hook, array($variables)); - // If _theme() was invoked with a direct theme suggestion like - // '#theme' => 'node__article', add it to the suggestions array before - // invoking suggestion alter hooks. - if (isset($info['base hook'])) { - $suggestions[] = $hook; - } - - // Invoke hook_theme_suggestions_alter() and - // hook_theme_suggestions_HOOK_alter(). - $hooks = array( - 'theme_suggestions', - 'theme_suggestions_' . $base_theme_hook, - ); - $module_handler->alter($hooks, $suggestions, $variables, $base_theme_hook); - \Drupal::theme()->alter($hooks, $suggestions, $variables, $base_theme_hook); - - // Check if each suggestion exists in the theme registry, and if so, - // use it instead of the hook that _theme() was called with. For example, a - // function may call _theme('node', ...), but a module can add - // 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(), - // enabling a theme to have an alternate template file for article nodes. - foreach (array_reverse($suggestions) as $suggestion) { - if ($theme_registry->has($suggestion)) { - $info = $theme_registry->get($suggestion); - break; - } - } - - // Include a file if the theme function or variable preprocessor is held - // elsewhere. - if (!empty($info['includes'])) { - foreach ($info['includes'] as $include_file) { - include_once DRUPAL_ROOT . '/' . $include_file; - } - } - - // Invoke the variable preprocessors, if any. - if (isset($info['base hook'])) { - $base_hook = $info['base hook']; - $base_hook_info = $theme_registry->get($base_hook); - // Include files required by the base hook, since its variable preprocessors - // might reside there. - if (!empty($base_hook_info['includes'])) { - foreach ($base_hook_info['includes'] as $include_file) { - include_once DRUPAL_ROOT . '/' . $include_file; - } - } - // Replace the preprocess functions with those from the base hook. - if (isset($base_hook_info['preprocess functions'])) { - // Set a variable for the 'theme_hook_suggestion'. This is used to - // maintain backwards compatibility with template engines. - $theme_hook_suggestion = $hook; - $info['preprocess functions'] = $base_hook_info['preprocess functions']; - } - } - if (isset($info['preprocess functions'])) { - foreach ($info['preprocess functions'] as $preprocessor_function) { - if (function_exists($preprocessor_function)) { - $preprocessor_function($variables, $hook, $info); - } - } - // Allow theme preprocess functions to set $variables['#attached'] and use - // it like the #attached property on render arrays. In Drupal 8, this is the - // (only) officially supported method of attaching assets from preprocess - // functions. Assets attached here should be associated with the template - // that we're preprocessing variables for. - if (isset($variables['#attached'])) { - $preprocess_attached = ['#attached' => $variables['#attached']]; - drupal_render($preprocess_attached); - } - } - - // Generate the output using either a function or a template. - $output = ''; - if (isset($info['function'])) { - if (function_exists($info['function'])) { - $output = SafeMarkup::set($info['function']($variables)); - } - } - else { - $render_function = 'twig_render_template'; - $extension = '.html.twig'; - - // The theme engine may use a different extension and a different renderer. - $theme_engine = $active_theme->getEngine(); - if (isset($theme_engine)) { - if ($info['type'] != 'module') { - if (function_exists($theme_engine . '_render_template')) { - $render_function = $theme_engine . '_render_template'; - } - $extension_function = $theme_engine . '_extension'; - if (function_exists($extension_function)) { - $extension = $extension_function(); - } - } - } - - // In some cases, a template implementation may not have had - // template_preprocess() run (for example, if the default implementation is - // a function, but a template overrides that default implementation). In - // these cases, a template should still be able to expect to have access to - // the variables provided by template_preprocess(), so we add them here if - // they don't already exist. We don't want the overhead of running - // template_preprocess() twice, so we use the 'directory' variable to - // determine if it has already run, which while not completely intuitive, - // is reasonably safe, and allows us to save on the overhead of adding some - // new variable to track that. - if (!isset($variables['directory'])) { - $default_template_variables = array(); - template_preprocess($default_template_variables, $hook, $info); - $variables += $default_template_variables; - } - if (!isset($default_attributes)) { - $default_attributes = new Attribute(); - } - foreach (array('attributes', 'title_attributes', 'content_attributes') as $key) { - if (isset($variables[$key]) && !($variables[$key] instanceof Attribute)) { - if ($variables[$key]) { - $variables[$key] = new Attribute($variables[$key]); - } - else { - // Create empty attributes. - $variables[$key] = clone $default_attributes; - } - } - } - - // Render the output using the template file. - $template_file = $info['template'] . $extension; - if (isset($info['path'])) { - $template_file = $info['path'] . '/' . $template_file; - } - // Add the theme suggestions to the variables array just before rendering - // the template for backwards compatibility with template engines. - $variables['theme_hook_suggestions'] = $suggestions; - // For backwards compatibility, pass 'theme_hook_suggestion' on to the - // template engine. This is only set when calling a direct suggestion like - // '#theme' => 'menu__shortcut_default' when the template exists in the - // current theme. - if (isset($theme_hook_suggestion)) { - $variables['theme_hook_suggestion'] = $theme_hook_suggestion; - } - $output = $render_function($template_file, $variables); - } - - return (string) $output; -} - -/** * Allows themes and/or theme engines to discover overridden theme functions. * * @param $cache @@ -1608,17 +1307,6 @@ function _template_preprocess_default_variables() { 'logged_in' => FALSE, ); - // drupal_is_front_page() might throw an exception. - try { - $variables['is_front'] = drupal_is_front_page(); - } - catch (Exception $e) { - // If the database is not yet available, set default values for these - // variables. - $variables['is_front'] = FALSE; - $variables['db_is_active'] = FALSE; - } - // Give modules a chance to alter the default template variables. \Drupal::moduleHandler()->alter('template_preprocess_default_variables', $variables); @@ -1774,6 +1462,16 @@ function template_preprocess_page(&$variables) { $variables['site_name'] = (theme_get_setting('features.name') ? String::checkPlain($site_config->get('name')) : ''); $variables['site_slogan'] = (theme_get_setting('features.slogan') ? Xss::filterAdmin($site_config->get('slogan')) : ''); + // An exception might be thrown. + try { + $variables['is_front'] = \Drupal::service('path.matcher')->isFrontPage(); + } + catch (Exception $e) { + // If the database is not yet available, set default values for these + // variables. + $variables['is_front'] = FALSE; + $variables['db_is_active'] = FALSE; + } if (!defined('MAINTENANCE_MODE')) { $variables['action_links'] = menu_get_local_actions(); $variables['tabs'] = menu_local_tabs(); diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc index e40498c..965cccd 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -158,10 +158,10 @@ function theme_authorize_message($variables) { $message = $variables['message']; $success = $variables['success']; if ($success) { - $item = array('data' => $message, 'class' => array('authorize-results__success')); + $item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__success')); } else { - $item = array('data' => $message, 'class' => array('authorize-results__failure')); + $item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__failure')); } return $item; } diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 363cf12..141686f 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -153,6 +153,15 @@ class Drupal { } /** + * Gets the app root. + * + * @return string + */ + public static function root() { + return static::$container->get('app.root'); + } + + /** * Indicates if there is a currently active request object. * * @return bool diff --git a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php index e88cefa..b22355d 100644 --- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php +++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginInterface.php @@ -125,4 +125,28 @@ */ public function validateContexts(); + /** + * Returns a mapping of the expected assignment names to their context names. + * + * @return array + * A mapping of the expected assignment names to their context names. For + * example, if one of the $contexts is named 'user.current_user', but the + * plugin expects a context named 'user', then this map would contain + * 'user' => 'user.current_user'. + */ + public function getContextMapping(); + + /** + * Sets a mapping of the expected assignment names to their context names. + * + * @param array $context_mapping + * A mapping of the expected assignment names to their context names. For + * example, if one of the $contexts is named 'user.current_user', but the + * plugin expects a context named 'user', then this map would contain + * 'user' => 'user.current_user'. + * + * @return $this + */ + public function setContextMapping(array $context_mapping); + } diff --git a/core/lib/Drupal/Component/README.txt b/core/lib/Drupal/Component/README.txt index 5b06318..9e0e6a3 100644 --- a/core/lib/Drupal/Component/README.txt +++ b/core/lib/Drupal/Component/README.txt @@ -1,7 +1,13 @@ Drupal Components are independent libraries that do not depend on the rest of -Drupal in order to function. Components MAY depend on other Components, but -that is discouraged. Components MAY NOT depend on any code that is not part of -PHP itself or another Drupal Component. +Drupal in order to function. + +Components MAY depend on other Drupal Components or external libraries/packages, +but MUST NOT depend on any other Drupal code. + +In other words, only dependencies that can be specified in a composer.json file +of the Component are acceptable dependencies. Every Drupal Component presents a +valid dependency, because it is assumed to contain a composer.json file (even +if it may not exist yet). Each Component should be in its own namespace, and should be as self-contained as possible. It should be possible to split a Component off to its own diff --git a/core/lib/Drupal/Core/Annotation/ContextDefinition.php b/core/lib/Drupal/Core/Annotation/ContextDefinition.php index 1a6b00a..987bab9 100644 --- a/core/lib/Drupal/Core/Annotation/ContextDefinition.php +++ b/core/lib/Drupal/Core/Annotation/ContextDefinition.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Annotation; use Drupal\Component\Annotation\Plugin; +use Drupal\Core\StringTranslation\TranslationWrapper; /** * @defgroup plugin_context Annotation for context definition @@ -94,9 +95,18 @@ class ContextDefinition extends Plugin { $values += array( 'required' => TRUE, 'multiple' => FALSE, - 'label' => NULL, - 'description' => NULL, ); + // Annotation classes extract data from passed annotation classes directly + // used in the classes they pass to. + foreach (['label', 'description'] as $key) { + // @todo Remove this workaround in https://www.drupal.org/node/2362727. + if (isset($values[$key]) && $values[$key] instanceof TranslationWrapper) { + $values[$key] = (string) $values[$key]->get(); + } + else { + $values[$key] = NULL; + } + } if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) { throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.'); } diff --git a/core/lib/Drupal/Core/AppRootFactory.php b/core/lib/Drupal/Core/AppRootFactory.php new file mode 100644 index 0000000..f57d262 --- /dev/null +++ b/core/lib/Drupal/Core/AppRootFactory.php @@ -0,0 +1,42 @@ +drupalKernel = $drupal_kernel; + } + + /** + * Gets the app root. + * + * @return string + */ + public function get() { + return $this->drupalKernel->getAppRoot(); + } + +} + diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php index b0c3696..9c28e33 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Asset; use Drupal\Core\Cache\CacheCollectorInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; /** * Discovers available asset libraries in Drupal. @@ -22,22 +23,52 @@ class LibraryDiscovery implements LibraryDiscoveryInterface { protected $collector; /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The final library definitions, statically cached. + * + * hook_library_alter() allows modules and themes to dynamically alter a + * library definition (once per request). + * + * @var array + */ + protected $libraryDefinitions = []; + + /** * Constructs a new LibraryDiscovery instance. * - * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend - * The cache backend. + * @param \Drupal\Core\Cache\CacheCollectorInterface $library_discovery_collector + * The library discovery cache collector. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ - public function __construct(CacheCollectorInterface $library_discovery_collector) { + public function __construct(CacheCollectorInterface $library_discovery_collector, ModuleHandlerInterface $module_handler) { $this->collector = $library_discovery_collector; + $this->moduleHandler = $module_handler; } /** * {@inheritdoc} */ public function getLibrariesByExtension($extension) { - return $this->collector->get($extension); + if (!isset($this->libraryDefinitions[$extension])) { + $libraries = $this->collector->get($extension); + $this->libraryDefinitions[$extension] = []; + foreach ($libraries as $name => $definition) { + // Allow modules and themes to dynamically attach request and context + // specific data for this library; e.g., localization. + $library_name = "$extension/$name"; + $this->moduleHandler->alter('library', $definition, $library_name); + $this->libraryDefinitions[$extension][$name] = $definition; + } + } + + return $this->libraryDefinitions[$extension]; } /** diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php index bd962bd..a28ca6e 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php @@ -27,10 +27,22 @@ class LibraryDiscoveryParser { protected $moduleHandler; /** + * The app root. + * + * @var string + */ + protected $root; + + /** + * Constructs a new LibraryDiscoveryParser instance. + * + * @param string $root + * The app root. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ - public function __construct(ModuleHandlerInterface $module_handler) { + public function __construct($root, ModuleHandlerInterface $module_handler) { + $this->root = $root; $this->moduleHandler = $module_handler; } @@ -67,7 +79,7 @@ class LibraryDiscoveryParser { $library_file = $path . '/' . $extension . '.libraries.yml'; - if ($library_file && file_exists(DRUPAL_ROOT . '/' . $library_file)) { + if ($library_file && file_exists($this->root . '/' . $library_file)) { $libraries = $this->parseLibraryInfo($extension, $library_file); } @@ -222,7 +234,7 @@ class LibraryDiscoveryParser { */ protected function parseLibraryInfo($extension, $library_file) { try { - $libraries = Yaml::decode(file_get_contents(DRUPAL_ROOT . '/' . $library_file)); + $libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file)); } catch (InvalidDataTypeException $e) { // Rethrow a more helpful exception to provide context. diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php index 0553d09..c4c1df4 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php @@ -7,9 +7,9 @@ namespace Drupal\Core\Authentication; +use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Session\AnonymousUserSession; use Symfony\Component\HttpFoundation\Request; -use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; /** @@ -89,8 +89,8 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti $account = NULL; - // Iterate the available providers. - foreach ($this->getSortedProviders() as $provider_id => $provider) { + // Iterate the allowed providers. + foreach ($this->filterProviders($this->getSortedProviders(), $request) as $provider_id => $provider) { if ($provider->applies($request)) { // Try to authenticate with this provider, skipping all others. $account = $provider->authenticate($request); @@ -156,6 +156,36 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti } /** + * Filters a list of providers and only return those allowed on the request. + * + * @param \Drupal\Core\Authentication\AuthenticationProviderInterface[] $providers + * An array of authentication provider objects. + * @param Request $request + * The request object. + * + * @return \Drupal\Core\Authentication\AuthenticationProviderInterface[] + * The filtered array authentication provider objects. + */ + protected function filterProviders(array $providers, Request $request) { + $route = RouteMatch::createFromRequest($request)->getRouteObject(); + $allowed_providers = array(); + if ($route && $route->hasOption('_auth')) { + $allowed_providers = $route->getOption('_auth'); + } + elseif ($default_provider = $this->defaultProviderId()) { + // @todo Mirrors the defective behavior of AuthenticationEnhancer and + // restricts the list of allowed providers to the default provider if no + // _auth was specified on the current route. + // + // This restriction will be removed by https://www.drupal.org/node/2286971 + // See also https://www.drupal.org/node/2283637 + $allowed_providers = array($default_provider); + } + + return array_intersect_key($providers, array_flip($allowed_providers)); + } + + /** * Cleans up the authentication. * * Allow the triggered provider to clean up before the response is sent, e.g. @@ -177,18 +207,11 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti * {@inheritdoc} */ public function handleException(GetResponseForExceptionEvent $event) { - $request = $event->getRequest(); - - $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT); - $active_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->defaultProviderId()); - - // Get the sorted list of active providers for the given route. - $providers = array_intersect($active_providers, array_keys($this->getSortedProviders())); - - foreach ($providers as $provider_id) { - if ($this->providers[$provider_id]->handleException($event) == TRUE) { + foreach ($this->filterProviders($this->getSortedProviders(), $event->getRequest()) as $provider) { + if ($provider->handleException($event) === TRUE) { break; } } } + } diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php index 2be3670..ba1026b 100644 --- a/core/lib/Drupal/Core/Block/BlockBase.php +++ b/core/lib/Drupal/Core/Block/BlockBase.php @@ -8,13 +8,7 @@ namespace Drupal\Core\Block; use Drupal\block\BlockInterface; -use Drupal\block\Event\BlockConditionContextEvent; -use Drupal\block\Event\BlockEvents; -use Drupal\Component\Plugin\ContextAwarePluginInterface; use Drupal\Core\Access\AccessResult; -use Drupal\Core\Condition\ConditionAccessResolverTrait; -use Drupal\Core\Condition\ConditionPluginCollection; -use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\Component\Utility\Unicode; @@ -35,22 +29,6 @@ */ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface { - use ConditionAccessResolverTrait; - - /** - * The condition plugin collection. - * - * @var \Drupal\Core\Condition\ConditionPluginCollection - */ - protected $conditionCollection; - - /** - * The condition plugin manager. - * - * @var \Drupal\Core\Executable\ExecutableManagerInterface - */ - protected $conditionPluginManager; - /** * The transliteration service. * @@ -84,9 +62,7 @@ * {@inheritdoc} */ public function getConfiguration() { - return array( - 'visibility' => $this->getVisibilityConditions()->getConfiguration(), - ) + $this->configuration; + return $this->configuration; } /** @@ -107,13 +83,6 @@ * An associative array with the default configuration. */ protected function baseConfigurationDefaults() { - // @todo Allow list of conditions to be configured in - // https://drupal.org/node/2284687. - $visibility = array_map(function ($definition) { - return array('id' => $definition['id']); - }, $this->conditionPluginManager()->getDefinitions()); - unset($visibility['current_theme']); - return array( 'id' => $this->getPluginId(), 'label' => '', @@ -123,7 +92,6 @@ 'max_age' => 0, 'contexts' => array(), ), - 'visibility' => $visibility, ); } @@ -152,25 +120,10 @@ * {@inheritdoc} */ public function access(AccountInterface $account) { - // @todo Add in a context mapping until the UI supports configuring them, - // see https://drupal.org/node/2284687. - $mappings['user_role']['current_user'] = 'user'; - - $conditions = $this->getVisibilityConditions(); - $contexts = $this->getConditionContexts(); - foreach ($conditions as $condition_id => $condition) { - if ($condition instanceof ContextAwarePluginInterface) { - if (!isset($mappings[$condition_id])) { - $mappings[$condition_id] = array(); - } - $this->contextHandler()->applyContextMapping($condition, $contexts, $mappings[$condition_id]); - } - } - // This should not be hardcoded to an uncacheable access check result, but - // in order to fix that, we need condition plugins to return cache contexts, - // otherwise it will be impossible to determine by which cache contexts the - // result should be varied. - if ($this->resolveConditions($conditions, 'and', $contexts, $mappings) !== FALSE && $this->blockAccess($account)) { + // @todo Remove self::blockAccess() and force individual plugins to return + // their own AccessResult logic. Until that is done in + // https://www.drupal.org/node/2375689 the access will be set uncacheable. + if ($this->blockAccess($account)) { $access = AccessResult::allowed(); } else { @@ -180,18 +133,6 @@ } /** - * Gets the values for all defined contexts. - * - * @return \Drupal\Component\Plugin\Context\ContextInterface[] - * An array of set contexts, keyed by context name. - */ - protected function getConditionContexts() { - $conditions = $this->getVisibilityConditions(); - $this->eventDispatcher()->dispatch(BlockEvents::CONDITION_CONTEXT, new BlockConditionContextEvent($conditions)); - return $conditions->getConditionContexts(); - } - - /** * Indicates whether the block should be shown. * * @param \Drupal\Core\Session\AccountInterface $account @@ -287,53 +228,6 @@ $form['cache']['contexts']['#description'] .= ' ' . t('This block is always varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list)); } - $form['visibility_tabs'] = array( - '#type' => 'vertical_tabs', - '#title' => $this->t('Visibility'), - '#parents' => array('visibility_tabs'), - '#attached' => array( - 'library' => array( - 'block/drupal.block', - ), - ), - ); - foreach ($this->getVisibilityConditions() as $condition_id => $condition) { - $condition_form = $condition->buildConfigurationForm(array(), $form_state); - $condition_form['#type'] = 'details'; - $condition_form['#title'] = $condition->getPluginDefinition()['label']; - $condition_form['#group'] = 'visibility_tabs'; - $form['visibility'][$condition_id] = $condition_form; - } - - // @todo Determine if there is a better way to rename the conditions. - if (isset($form['visibility']['node_type'])) { - $form['visibility']['node_type']['#title'] = $this->t('Content types'); - $form['visibility']['node_type']['bundles']['#title'] = $this->t('Content types'); - $form['visibility']['node_type']['negate']['#type'] = 'value'; - $form['visibility']['node_type']['negate']['#title_display'] = 'invisible'; - $form['visibility']['node_type']['negate']['#value'] = $form['visibility']['node_type']['negate']['#default_value']; - } - if (isset($form['visibility']['user_role'])) { - $form['visibility']['user_role']['#title'] = $this->t('Roles'); - unset($form['visibility']['user_role']['roles']['#description']); - $form['visibility']['user_role']['negate']['#type'] = 'value'; - $form['visibility']['user_role']['negate']['#value'] = $form['visibility']['user_role']['negate']['#default_value']; - } - if (isset($form['visibility']['request_path'])) { - $form['visibility']['request_path']['#title'] = $this->t('Pages'); - $form['visibility']['request_path']['negate']['#type'] = 'radios'; - $form['visibility']['request_path']['negate']['#title_display'] = 'invisible'; - $form['visibility']['request_path']['negate']['#default_value'] = (int) $form['visibility']['request_path']['negate']['#default_value']; - $form['visibility']['request_path']['negate']['#options'] = array( - $this->t('Show for the listed pages'), - $this->t('Hide for the listed pages'), - ); - } - if (isset($form['visibility']['language'])) { - $form['visibility']['language']['negate']['#type'] = 'value'; - $form['visibility']['language']['negate']['#value'] = $form['visibility']['language']['negate']['#default_value']; - } - // Add plugin-specific settings for this block type. $form += $this->blockForm($form, $form_state); return $form; @@ -362,15 +256,6 @@ $contexts = $form_state->getValue(array('cache', 'contexts')); $form_state->setValue(array('cache', 'contexts'), array_values(array_filter($contexts))); - foreach ($this->getVisibilityConditions() as $condition_id => $condition) { - // Allow the condition to validate the form. - $condition_values = (new FormState()) - ->setValues($form_state->getValue(['visibility', $condition_id])); - $condition->validateConfigurationForm($form, $condition_values); - // Update the original form values. - $form_state->setValue(['visibility', $condition_id], $condition_values->getValues()); - } - $this->blockValidate($form, $form_state); } @@ -394,14 +279,6 @@ $this->configuration['label_display'] = $form_state->getValue('label_display'); $this->configuration['provider'] = $form_state->getValue('provider'); $this->configuration['cache'] = $form_state->getValue('cache'); - foreach ($this->getVisibilityConditions() as $condition_id => $condition) { - // Allow the condition to submit the form. - $condition_values = (new FormState()) - ->setValues($form_state->getValue(['visibility', $condition_id])); - $condition->submitConfigurationForm($form, $condition_values); - // Update the original form values. - $form_state->setValue(['visibility', $condition_id], $condition_values->getValues()); - } $this->blockSubmit($form, $form_state); } } @@ -503,61 +380,4 @@ return $max_age === Cache::PERMANENT || $max_age > 0; } - /** - * {@inheritdoc} - */ - public function getVisibilityConditions() { - if (!isset($this->conditionCollection)) { - $this->conditionCollection = new ConditionPluginCollection($this->conditionPluginManager(), $this->configuration['visibility']); - } - return $this->conditionCollection; - } - - /** - * {@inheritdoc} - */ - public function getVisibilityCondition($instance_id) { - return $this->getVisibilityConditions()->get($instance_id); - } - - /** - * {@inheritdoc} - */ - public function setVisibilityConfig($instance_id, array $configuration) { - $this->getVisibilityConditions()->setInstanceConfiguration($instance_id, $configuration); - return $this; - } - - /** - * Gets the condition plugin manager. - * - * @return \Drupal\Core\Executable\ExecutableManagerInterface - * The condition plugin manager. - */ - protected function conditionPluginManager() { - if (!isset($this->conditionPluginManager)) { - $this->conditionPluginManager = \Drupal::service('plugin.manager.condition'); - } - return $this->conditionPluginManager; - } - - /** - * Wraps the event dispatcher. - * - * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface - * The event dispatcher. - */ - protected function eventDispatcher() { - return \Drupal::service('event_dispatcher'); - } - - /** - * Wraps the context handler. - * - * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface - */ - protected function contextHandler() { - return \Drupal::service('context.handler'); - } - } diff --git a/core/lib/Drupal/Core/Block/BlockPluginInterface.php b/core/lib/Drupal/Core/Block/BlockPluginInterface.php index 01f97ad..f63a955 100644 --- a/core/lib/Drupal/Core/Block/BlockPluginInterface.php +++ b/core/lib/Drupal/Core/Block/BlockPluginInterface.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Block; -use Drupal\Component\Plugin\Context\ContextInterface; use Drupal\Component\Plugin\DerivativeInspectionInterface; use Drupal\Core\Cache\CacheableInterface; use Drupal\Component\Plugin\PluginInspectionInterface; @@ -143,35 +142,4 @@ */ public function getMachineNameSuggestion(); - /** - * Gets conditions for this block. - * - * @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginCollection - * An array or collection of configured condition plugins. - */ - public function getVisibilityConditions(); - - /** - * Gets a visibility condition plugin instance. - * - * @param string $instance_id - * The condition plugin instance ID. - * - * @return \Drupal\Core\Condition\ConditionInterface - * A condition plugin. - */ - public function getVisibilityCondition($instance_id); - - /** - * Sets the visibility condition configuration. - * - * @param string $instance_id - * The condition instance ID. - * @param array $configuration - * The condition configuration. - * - * @return $this - */ - public function setVisibilityConfig($instance_id, array $configuration); - } diff --git a/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php b/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php index 341203c..ffe5993 100644 --- a/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php +++ b/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php @@ -20,9 +20,12 @@ class ApcuBackendFactory implements CacheFactoryInterface { /** * Constructs an ApcuBackendFactory object. + * + * @param string $root + * The app root. */ - public function __construct() { - $this->sitePrefix = Crypt::hashBase64(DRUPAL_ROOT . '/' . conf_path()); + public function __construct($root) { + $this->sitePrefix = Crypt::hashBase64($root . '/' . conf_path()); } /** diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php index ed28d69..4b4a07b 100644 --- a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php +++ b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php @@ -9,6 +9,7 @@ use Drupal\Core\Executable\ExecutablePluginBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait; /** * Provides a basis for fulfilling contexts for condition plugins. @@ -21,6 +22,8 @@ */ abstract class ConditionPluginBase extends ExecutablePluginBase implements ConditionInterface { + use ContextAwarePluginAssignmentTrait; + /** * {@inheritdoc} */ @@ -41,6 +44,8 @@ * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $contexts = $form_state->getTemporaryValue('gathered_contexts') ?: []; + $form['context_mapping'] = $this->addContextAssignmentElement($this, $contexts); $form['negate'] = array( '#type' => 'checkbox', '#title' => $this->t('Negate the condition'), diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php b/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php index c2e532f..97392fe 100644 --- a/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php +++ b/core/lib/Drupal/Core/Condition/ConditionPluginCollection.php @@ -41,6 +41,12 @@ class ConditionPluginCollection extends DefaultLazyPluginCollection { $default_config = array(); $default_config['id'] = $instance_id; $default_config += $this->get($instance_id)->defaultConfiguration(); + // In order to determine if a plugin is configured, we must compare it to + // its default configuration. The default configuration of a plugin does + // not contain context_mapping and it is not used when the plugin is not + // configured, so remove the context_mapping from the instance config to + // compare the remaining values. + unset($instance_config['context_mapping']); if ($default_config === $instance_config) { unset($configuration[$instance_id]); } diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php index e9bd690..d4e7d5c 100644 --- a/core/lib/Drupal/Core/Config/InstallStorage.php +++ b/core/lib/Drupal/Core/Config/InstallStorage.php @@ -157,7 +157,7 @@ class InstallStorage extends FileStorage { if ($profile = drupal_get_profile()) { $this->folders += $this->getComponentNames('profile', array($profile)); } - $listing = new ExtensionDiscovery(); + $listing = new ExtensionDiscovery(DRUPAL_ROOT); $this->folders += $this->getComponentNames('module', array_keys($listing->scan('module'))); $this->folders += $this->getComponentNames('theme', array_keys($listing->scan('theme'))); } diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 3767c9f..249944f 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -41,8 +41,6 @@ class CoreServiceProvider implements ServiceProviderInterface { * {@inheritdoc} */ public function register(ContainerBuilder $container) { - $container->setParameter('app.root', DRUPAL_ROOT); - $this->registerUuid($container); $this->registerTest($container); diff --git a/core/lib/Drupal/Core/Cron.php b/core/lib/Drupal/Core/Cron.php index bc256f8..1c17c94 100644 --- a/core/lib/Drupal/Core/Cron.php +++ b/core/lib/Drupal/Core/Cron.php @@ -12,9 +12,8 @@ use Drupal\Core\State\StateInterface; use Drupal\Core\Lock\LockBackendInterface; use Drupal\Core\Queue\QueueFactory; -use Drupal\Core\Session\AccountProxyInterface; use Drupal\Core\Session\AnonymousUserSession; -use Drupal\Core\Session\SessionManagerInterface; +use Drupal\Core\Session\AccountSwitcherInterface; use Drupal\Core\Queue\SuspendQueueException; use Psr\Log\LoggerInterface; @@ -52,18 +51,11 @@ class Cron implements CronInterface { protected $state; /** - * The current user. + * The account switcher service. * - * @var \Drupal\Core\Session\AccountProxyInterface + * @var \Drupal\Core\Session\AccountSwitcherInterface */ - protected $currentUser; - - /** - * The session manager. - * - * @var \Drupal\Core\Session\SessionManagerInterface - */ - protected $sessionManager; + protected $accountSwitcher; /** * A logger instance. @@ -90,22 +82,19 @@ class Cron implements CronInterface { * The queue service. * @param \Drupal\Core\State\StateInterface $state * The state service. - * @param \Drupal\Core\Session\AccountProxyInterface $current_user - * The current user. - * @param \Drupal\Core\Session\SessionManagerInterface $session_manager - * The session manager. + * @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher + * The account switching service. * @param \Psr\Log\LoggerInterface $logger * A logger instance. * @param \Drupal\Core\Queue\QueueWorkerManagerInterface * The queue plugin manager. */ - public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountProxyInterface $current_user, SessionManagerInterface $session_manager, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager) { + public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountSwitcherInterface $account_switcher, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager) { $this->moduleHandler = $module_handler; $this->lock = $lock; $this->queueFactory = $queue_factory; $this->state = $state; - $this->currentUser = $current_user; - $this->sessionManager = $session_manager; + $this->accountSwitcher = $account_switcher; $this->logger = $logger; $this->queueManager = $queue_manager; } @@ -117,14 +106,9 @@ class Cron implements CronInterface { // Allow execution to continue even if the request gets cancelled. @ignore_user_abort(TRUE); - // Prevent session information from being saved while cron is running. - $original_session_saving = $this->sessionManager->isEnabled(); - $this->sessionManager->disable(); - // Force the current user to anonymous to ensure consistent permissions on // cron runs. - $original_user = $this->currentUser->getAccount(); - $this->currentUser->setAccount(new AnonymousUserSession()); + $this->accountSwitcher->switchTo(new AnonymousUserSession()); // Try to allocate enough time to run all the hook_cron implementations. drupal_set_time_limit(240); @@ -151,10 +135,7 @@ class Cron implements CronInterface { $this->processQueues(); // Restore the user. - $this->currentUser->setAccount($original_user); - if ($original_session_saving) { - $this->sessionManager->enable(); - } + $this->accountSwitcher->switchBack(); return $return; } diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 600fd21..dc40e4f 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -29,6 +29,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\TerminableInterface; use Composer\Autoload\ClassLoader; @@ -180,6 +181,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { protected $sitePath; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Create a DrupalKernel object from a request. * * @param \Symfony\Component\HttpFoundation\Request $request @@ -198,7 +206,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { */ public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE) { // Include our bootstrap file. - require_once dirname(dirname(dirname(__DIR__))) . '/includes/bootstrap.inc'; + $core_root = dirname(dirname(dirname(__DIR__))); + require_once $core_root . '/includes/bootstrap.inc'; $kernel = new static($environment, $class_loader, $allow_dumping); @@ -206,7 +215,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { static::bootEnvironment(); // Get our most basic settings setup. - $kernel->initializeSettings($request); + $site_path = static::findSitePath($request); + $kernel->setSitePath($site_path); + Settings::initialize(dirname($core_root), $site_path, $class_loader); // Redirect the user to the installation script if Drupal has not been // installed yet (i.e., if no $databases array has been defined in the @@ -220,18 +231,6 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { } /** - * Initializes the kernel's site path and the Settings singleton. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request that will be used to determine the site path. - */ - protected function initializeSettings(Request $request) { - $site_path = static::findSitePath($request); - $this->setSitePath($site_path); - Settings::initialize($site_path, $this->classLoader); - } - - /** * Constructs a DrupalKernel object. * * @param string $environment @@ -248,6 +247,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { $this->environment = $environment; $this->classLoader = $class_loader; $this->allowDumping = $allow_dumping; + $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))); } /** @@ -293,12 +293,19 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { * @return string * The path of the matching directory. * + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * In case the host name in the request is invalid. + * * @see \Drupal\Core\DrupalKernelInterface::getSitePath() * @see \Drupal\Core\DrupalKernelInterface::setSitePath() * @see default.settings.php * @see example.sites.php */ public static function findSitePath(Request $request, $require_settings = TRUE) { + if (static::validateHostname($request) === FALSE) { + throw new BadRequestHttpException(); + } + // Check for a simpletest override. if ($test_prefix = drupal_valid_test_ua()) { return 'sites/simpletest/' . substr($test_prefix, 10); @@ -314,7 +321,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { if (!$script_name) { $script_name = $request->server->get('SCRIPT_FILENAME'); } - $http_host = $request->server->get('HTTP_HOST'); + $http_host = $request->getHost(); $sites = array(); include DRUPAL_ROOT . '/sites/sites.php'; @@ -352,6 +359,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { /** * {@inheritdoc} */ + public function getAppRoot() { + return $this->root; + } + + /** + * {@inheritdoc} + */ public function boot() { if ($this->booted) { return $this; @@ -361,21 +375,21 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { Timer::start('page'); // Load legacy and other functional code. - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - require_once DRUPAL_ROOT . '/core/includes/database.inc'; - require_once DRUPAL_ROOT . '/core/includes/path.inc'; - require_once DRUPAL_ROOT . '/core/includes/module.inc'; - require_once DRUPAL_ROOT . '/core/includes/theme.inc'; - require_once DRUPAL_ROOT . '/core/includes/pager.inc'; - require_once DRUPAL_ROOT . '/core/includes/menu.inc'; - require_once DRUPAL_ROOT . '/core/includes/tablesort.inc'; - require_once DRUPAL_ROOT . '/core/includes/file.inc'; - require_once DRUPAL_ROOT . '/core/includes/unicode.inc'; - require_once DRUPAL_ROOT . '/core/includes/form.inc'; - require_once DRUPAL_ROOT . '/core/includes/mail.inc'; - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - require_once DRUPAL_ROOT . '/core/includes/schema.inc'; - require_once DRUPAL_ROOT . '/core/includes/entity.inc'; + require_once $this->root . '/core/includes/common.inc'; + require_once $this->root . '/core/includes/database.inc'; + require_once $this->root . '/core/includes/path.inc'; + require_once $this->root . '/core/includes/module.inc'; + require_once $this->root . '/core/includes/theme.inc'; + require_once $this->root . '/core/includes/pager.inc'; + require_once $this->root . '/core/includes/menu.inc'; + require_once $this->root . '/core/includes/tablesort.inc'; + require_once $this->root . '/core/includes/file.inc'; + require_once $this->root . '/core/includes/unicode.inc'; + require_once $this->root . '/core/includes/form.inc'; + require_once $this->root . '/core/includes/mail.inc'; + require_once $this->root . '/core/includes/errors.inc'; + require_once $this->root . '/core/includes/schema.inc'; + require_once $this->root . '/core/includes/entity.inc'; // Ensure that findSitePath is set. if (!$this->sitePath) { @@ -588,7 +602,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { protected function moduleData($module) { if (!$this->moduleData) { // First, find profiles. - $listing = new ExtensionDiscovery(); + $listing = new ExtensionDiscovery($this->root); $listing->setProfileDirectories(array()); $all_profiles = $listing->scan('profile'); $profiles = array_intersect_key($all_profiles, $this->moduleList); @@ -809,8 +823,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { } else { // Create base URL. - $http_protocol = $request->isSecure() ? 'https' : 'http'; - $base_root = $http_protocol . '://' . $request->server->get('HTTP_HOST'); + $base_root = $request->getSchemeAndHttpHost(); $base_url = $base_root; @@ -892,16 +905,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { // Replace "core" out of session_name so core scripts redirect properly, // specifically install.php. $session_name = preg_replace('/\/core$/', '', $session_name); - // HTTP_HOST can be modified by a visitor, but has been sanitized already - // in DrupalKernel::bootEnvironment(). - if ($cookie_domain = $request->server->get('HTTP_HOST')) { - // Strip leading periods, www., and port numbers from cookie domain. + if ($cookie_domain = $request->getHost()) { + // Strip leading periods and www. from cookie domain. $cookie_domain = ltrim($cookie_domain, '.'); if (strpos($cookie_domain, 'www.') === 0) { $cookie_domain = substr($cookie_domain, 4); } - $cookie_domain = explode(':', $cookie_domain); - $cookie_domain = '.' . $cookie_domain[0]; + $cookie_domain = '.' . $cookie_domain; } } // Per RFC 2109, cookie domains must contain at least one dot other than the @@ -1017,7 +1027,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { // - Entity // - Plugin foreach (array('Core', 'Component') as $parent_directory) { - $path = DRUPAL_ROOT . '/core/lib/Drupal/' . $parent_directory; + $path = $this->root . '/core/lib/Drupal/' . $parent_directory; $parent_namespace = 'Drupal\\' . $parent_directory; foreach (new \DirectoryIterator($path) as $component) { /** @var $component \DirectoryIterator */ @@ -1230,7 +1240,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { protected function getModuleNamespacesPsr4($module_file_names) { $namespaces = array(); foreach ($module_file_names as $module => $filename) { - $namespaces["Drupal\\$module"] = DRUPAL_ROOT . '/' . dirname($filename) . '/src'; + $namespaces["Drupal\\$module"] = $this->root . '/' . dirname($filename) . '/src'; } return $namespaces; } @@ -1249,4 +1259,51 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { } } + /** + * Validates a hostname length. + * + * @param string $host + * A hostname. + * + * @return bool + * TRUE if the length is appropriate, or FALSE otherwise. + */ + protected static function validateHostnameLength($host) { + // Limit the length of the host name to 1000 bytes to prevent DoS attacks + // with long host names. + return strlen($host) <= 1000 + // Limit the number of subdomains and port separators to prevent DoS attacks + // in findSitePath(). + && substr_count($host, '.') <= 100 + && substr_count($host, ':') <= 100; + } + + /** + * Validates the hostname supplied from the HTTP request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object + * + * @return bool + * TRUE if the hostmame is valid, or FALSE otherwise. + * + * @todo Adjust per resolution to https://github.com/symfony/symfony/issues/12349 + */ + public static function validateHostname(Request $request) { + // $request->getHost() can throw an UnexpectedValueException if it + // detects a bad hostname, but it does not validate the length. + try { + $http_host = $request->getHost(); + } + catch (\UnexpectedValueException $e) { + return FALSE; + } + + if (static::validateHostnameLength($http_host) === FALSE) { + return FALSE; + } + + return TRUE; + } + } diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index a5eb3b9..e7c468f 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -74,6 +74,13 @@ public function getSitePath(); /** + * Gets the app root. + * + * @return string + */ + public function getAppRoot(); + + /** * Updates the kernel's list of modules to the new list. * * The kernel needs to update its bundle list and container to match the new diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php index 2b55e24..156e749 100644 --- a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php +++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Render\RendererInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -26,13 +27,23 @@ class EntityViewController implements ContainerInjectionInterface { protected $entityManager; /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** * Creates an EntityViewController object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. */ - public function __construct(EntityManagerInterface $entity_manager) { + public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer) { $this->entityManager = $entity_manager; + $this->renderer = $renderer; } /** @@ -40,7 +51,8 @@ class EntityViewController implements ContainerInjectionInterface { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager') + $container->get('entity.manager'), + $container->get('renderer') ); } @@ -79,7 +91,7 @@ class EntityViewController implements ContainerInjectionInterface { $build = $this->entityManager->getTranslationFromContext($_entity) ->get($label_field) ->view($view_mode); - $page['#title'] = drupal_render($build); + $page['#title'] = $this->renderer->render($build); } } diff --git a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php index 0bbbef8..d761556 100644 --- a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php +++ b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php @@ -61,18 +61,18 @@ class EntityRouteEnhancer implements RouteEnhancerInterface { * {@inheritdoc} */ public function enhance(array $defaults, Request $request) { - if (empty($defaults['_content'])) { + if (empty($defaults['_controller'])) { if (!empty($defaults['_entity_form'])) { $wrapper = new HtmlEntityFormController($this->resolver, $this->manager, $this->formBuilder, $defaults['_entity_form']); - $defaults['_content'] = array($wrapper, 'getContentResult'); + $defaults['_controller'] = array($wrapper, 'getContentResult'); } elseif (!empty($defaults['_entity_list'])) { - $defaults['_content'] = '\Drupal\Core\Entity\Controller\EntityListController::listing'; + $defaults['_controller'] = '\Drupal\Core\Entity\Controller\EntityListController::listing'; $defaults['entity_type'] = $defaults['_entity_list']; unset($defaults['_entity_list']); } elseif (!empty($defaults['_entity_view'])) { - $defaults['_content'] = '\Drupal\Core\Entity\Controller\EntityViewController::view'; + $defaults['_controller'] = '\Drupal\Core\Entity\Controller\EntityViewController::view'; if (strpos($defaults['_entity_view'], '.') !== FALSE) { // The _entity_view entry is of the form entity_type.view_mode. list($entity_type, $view_mode) = explode('.', $defaults['_entity_view']); diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index fa9626a..e582f0e 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -431,6 +431,9 @@ /** * {@inheritdoc} + * + * @return static + * The entity object or NULL if there is no entity with the given ID. */ public static function load($id) { $entity_manager = \Drupal::entityManager(); @@ -439,6 +442,9 @@ /** * {@inheritdoc} + * + * @return static[] + * An array of entity objects indexed by their IDs. */ public static function loadMultiple(array $ids = NULL) { $entity_manager = \Drupal::entityManager(); @@ -447,6 +453,9 @@ /** * {@inheritdoc} + * + * @return static + * The entity object. */ public static function create(array $values = array()) { $entity_manager = \Drupal::entityManager(); diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php index 301b5ae..a078aee 100644 --- a/core/lib/Drupal/Core/Entity/EntityForm.php +++ b/core/lib/Drupal/Core/Entity/EntityForm.php @@ -291,10 +291,17 @@ class EntityForm extends FormBase implements EntityFormInterface { * The current state of the form. */ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + + if ($this->entity instanceof EntityWithPluginCollectionInterface) { + // Do not manually update values represented by plugin collections. + $values = array_diff_key($values, $this->entity->getPluginCollections()); + } + // @todo: This relies on a method that only exists for config and content // entities, in a different way. Consider moving this logic to a config // entity specific implementation. - foreach ($form_state->getValues() as $key => $value) { + foreach ($values as $key => $value) { $entity->set($key, $value); } } diff --git a/core/lib/Drupal/Core/Entity/EntityResolverManager.php b/core/lib/Drupal/Core/Entity/EntityResolverManager.php index 3b94d23..36a0abf 100644 --- a/core/lib/Drupal/Core/Entity/EntityResolverManager.php +++ b/core/lib/Drupal/Core/Entity/EntityResolverManager.php @@ -49,8 +49,7 @@ class EntityResolverManager { * Gets the controller class using route defaults. * * By design we cannot support all possible routes, but just the ones which - * use the defaults provided by core, which are _content, _controller - * and _form. + * use the defaults provided by core, which are _controller and _form. * * Rather than creating an instance of every controller determine the class * and method that would be used. This is not possible for the service:method @@ -67,9 +66,6 @@ class EntityResolverManager { */ protected function getControllerClass(array $defaults) { $controller = NULL; - if (isset($defaults['_content'])) { - $controller = $defaults['_content']; - } if (isset($defaults['_controller'])) { $controller = $defaults['_controller']; } diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php index 88fac22..c8bc8b1 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php @@ -133,7 +133,7 @@ class DefaultExceptionSubscriber implements EventSubscriberInterface { } $content = $this->t('The website has encountered an error. Please try again later.'); - $output = $this->bareHtmlPageRenderer->renderMaintenancePage($content, $this->t('Error')); + $output = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Error'), 'maintenance_page'); $response = new Response($output); if ($exception instanceof HttpExceptionInterface) { diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php index 164dec0..bdd05c7 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -105,7 +105,7 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { $content = Xss::filterAdmin(String::format($this->config->get('system.maintenance')->get('message'), array( '@site' => $this->config->get('system.site')->get('name'), ))); - $output = $this->bareHtmlPageRenderer->renderMaintenancePage($content, $this->t('Site under maintenance')); + $output = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Site under maintenance'), 'maintenance_page'); $response = new Response($output, 503); $event->setResponse($response); } diff --git a/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php index b288a5c..e2e7137 100644 --- a/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php @@ -30,6 +30,7 @@ class SpecialAttributesRouteSubscriber extends RouteSubscriberBase { RouteObjectInterface::ROUTE_OBJECT, RouteObjectInterface::ROUTE_NAME, '_content', + '_controller', '_form', ); foreach ($collection->all() as $name => $route) { diff --git a/core/lib/Drupal/Core/Extension/Extension.php b/core/lib/Drupal/Core/Extension/Extension.php index b335495..3c765fe 100644 --- a/core/lib/Drupal/Core/Extension/Extension.php +++ b/core/lib/Drupal/Core/Extension/Extension.php @@ -43,8 +43,17 @@ class Extension implements \Serializable { protected $splFileInfo; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a new Extension object. * + * @param string $root + * The app root. * @param string $type * The type of the extension; e.g., 'module'. * @param string $pathname @@ -53,7 +62,8 @@ class Extension implements \Serializable { * @param string $filename * (optional) The filename of the main extension file; e.g., 'node.module'. */ - public function __construct($type, $pathname, $filename = NULL) { + public function __construct($root, $type, $pathname, $filename = NULL) { + $this->root = $root; $this->type = $type; $this->pathname = $pathname; $this->filename = $filename; @@ -132,7 +142,7 @@ class Extension implements \Serializable { */ public function load() { if ($this->filename) { - include_once DRUPAL_ROOT . '/' . $this->getPath() . '/' . $this->filename; + include_once $this->root . '/' . $this->getPath() . '/' . $this->filename; return TRUE; } return FALSE; @@ -157,6 +167,7 @@ class Extension implements \Serializable { */ public function serialize() { $data = array( + 'root' => $this->root, 'type' => $this->type, 'pathname' => $this->pathname, 'filename' => $this->filename, @@ -177,6 +188,7 @@ class Extension implements \Serializable { */ public function unserialize($data) { $data = unserialize($data); + $this->root = $data['root']; $this->type = $data['type']; $this->pathname = $data['pathname']; $this->filename = $data['filename']; diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php index f51cbb3..9681162 100644 --- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php +++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php @@ -74,13 +74,30 @@ class ExtensionDiscovery { protected $profileDirectories; /** + * The app root. + * + * @var string + */ + protected $root; + + /** + * Constructs a new ExtensionDiscovery object. + * + * @param string $root + * The app root. + */ + public function __construct($root) { + $this->root = $root; + } + + /** * Discovers available extensions of a given type. * * Finds all extensions (modules, themes, etc) that exist on the site. It * searches in several locations. For instance, to discover all available * modules: * @code - * $listing = new ExtensionDiscovery(); + * $listing = new ExtensionDiscovery(\Drupal::root()); * $modules = $listing->scan('module'); * @endcode * @@ -304,9 +321,9 @@ class ExtensionDiscovery { // be used (which also improves performance, since any configured PHP // include_paths will not be consulted). Retain the relative originating // directory being scanned, so relative paths can be reconstructed below - // (all paths are expected to be relative to DRUPAL_ROOT). + // (all paths are expected to be relative to $this->root). $dir_prefix = ($dir == '' ? '' : "$dir/"); - $absolute_dir = ($dir == '' ? DRUPAL_ROOT : DRUPAL_ROOT . "/$dir"); + $absolute_dir = ($dir == '' ? $this->root : $this->root . "/$dir"); if (!is_dir($absolute_dir)) { return $files; @@ -370,7 +387,7 @@ class ExtensionDiscovery { $filename = NULL; } - $extension = new Extension($type, $pathname, $filename); + $extension = new Extension($this->root, $type, $pathname, $filename); // Track the originating directory for sorting purposes. $extension->subpath = $fileinfo->getSubPath(); diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 028d7d4..12d2805 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -93,8 +93,17 @@ class ModuleHandler implements ModuleHandlerInterface { protected $alterFunctions; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a ModuleHandler object. * + * @param string $root + * The app root. * @param array $module_list * An associative array whose keys are the names of installed modules and * whose values are Extension class parameters. This is normally the @@ -107,10 +116,11 @@ class ModuleHandler implements ModuleHandlerInterface { * @see \Drupal\Core\DrupalKernel * @see \Drupal\Core\CoreServiceProvider */ - public function __construct(array $module_list = array(), DrupalKernelInterface $kernel, CacheBackendInterface $cache_backend) { + public function __construct($root, array $module_list = array(), DrupalKernelInterface $kernel, CacheBackendInterface $cache_backend) { + $this->root = $root; $this->moduleList = array(); foreach ($module_list as $name => $module) { - $this->moduleList[$name] = new Extension($module['type'], $module['pathname'], $module['filename']); + $this->moduleList[$name] = new Extension($this->root, $module['type'], $module['pathname'], $module['filename']); } $this->kernel = $kernel; $this->cacheBackend = $cache_backend; @@ -212,8 +222,8 @@ class ModuleHandler implements ModuleHandlerInterface { */ protected function add($type, $name, $path) { $pathname = "$path/$name.info.yml"; - $filename = file_exists(DRUPAL_ROOT . "/$path/$name.$type") ? "$name.$type" : NULL; - $this->moduleList[$name] = new Extension($type, $pathname, $filename); + $filename = file_exists($this->root . "/$path/$name.$type") ? "$name.$type" : NULL; + $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename); $this->resetImplementations(); } @@ -262,12 +272,12 @@ class ModuleHandler implements ModuleHandlerInterface { public function loadInclude($module, $type, $name = NULL) { if ($type == 'install') { // Make sure the installation API is available - include_once DRUPAL_ROOT . '/core/includes/install.inc'; + include_once $this->root . '/core/includes/install.inc'; } $name = $name ?: $module; if (isset($this->moduleList[$module])) { - $file = DRUPAL_ROOT . '/' . $this->moduleList[$module]->getPath() . "/$name.$type"; + $file = $this->root . '/' . $this->moduleList[$module]->getPath() . "/$name.$type"; if (is_file($file)) { require_once $file; return $file; @@ -736,7 +746,7 @@ class ModuleHandler implements ModuleHandlerInterface { } // Required for module installation checks. - include_once DRUPAL_ROOT . '/core/includes/install.inc'; + include_once $this->root . '/core/includes/install.inc'; /** @var \Drupal\Core\Config\ConfigInstaller $config_installer */ $config_installer = \Drupal::service('config.installer'); @@ -783,7 +793,7 @@ class ModuleHandler implements ModuleHandlerInterface { $module_path = drupal_get_path('module', $name); $pathname = "$module_path/$name.info.yml"; $filename = file_exists($module_path . "/$name.module") ? "$name.module" : NULL; - $module_filenames[$name] = new Extension('module', $pathname, $filename); + $module_filenames[$name] = new Extension($this->root, 'module', $pathname, $filename); } } @@ -1076,7 +1086,7 @@ class ModuleHandler implements ModuleHandlerInterface { public function getModuleDirectories() { $dirs = array(); foreach ($this->getModuleList() as $name => $module) { - $dirs[$name] = DRUPAL_ROOT . '/' . $module->getPath(); + $dirs[$name] = $this->root . '/' . $module->getPath(); } return $dirs; } diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index a316f36..02100fb 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -115,8 +115,17 @@ class ThemeHandler implements ThemeHandlerInterface { protected $configManager; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a new ThemeHandler. * + * @param string $root + * The app root. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory to get the installed themes. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler @@ -141,7 +150,8 @@ class ThemeHandler implements ThemeHandlerInterface { * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery * (optional) A extension discovery instance (for unit tests). */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser,LoggerInterface $logger, AssetCollectionOptimizerInterface $css_collection_optimizer = NULL, ConfigInstallerInterface $config_installer = NULL, ConfigManagerInterface $config_manager = NULL, RouteBuilderIndicatorInterface $route_builder_indicator = NULL, ExtensionDiscovery $extension_discovery = NULL) { + public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser,LoggerInterface $logger, AssetCollectionOptimizerInterface $css_collection_optimizer = NULL, ConfigInstallerInterface $config_installer = NULL, ConfigManagerInterface $config_manager = NULL, RouteBuilderIndicatorInterface $route_builder_indicator = NULL, ExtensionDiscovery $extension_discovery = NULL) { + $this->root = $root; $this->configFactory = $config_factory; $this->moduleHandler = $module_handler; $this->state = $state; @@ -639,7 +649,7 @@ class ThemeHandler implements ThemeHandlerInterface { */ protected function getExtensionDiscovery() { if (!isset($this->extensionDiscovery)) { - $this->extensionDiscovery = new ExtensionDiscovery(); + $this->extensionDiscovery = new ExtensionDiscovery($this->root); } return $this->extensionDiscovery; } @@ -700,7 +710,7 @@ class ThemeHandler implements ThemeHandlerInterface { public function getThemeDirectories() { $dirs = array(); foreach ($this->listInfo() as $name => $theme) { - $dirs[$name] = DRUPAL_ROOT . '/' . $theme->getPath(); + $dirs[$name] = $this->root . '/' . $theme->getPath(); } return $dirs; } diff --git a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php index a47e23b..3d1e33e 100644 --- a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php +++ b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php @@ -23,27 +23,29 @@ class EntityReferenceFieldItemList extends FieldItemList implements EntityRefere return array(); } - // Get a list of items having non-empty target ids. - $list = array_filter($this->list, function($item) { - return (bool) $item->target_id; - }); - - $ids = array(); - foreach ($list as $delta => $item) { - $ids[$delta] = $item->target_id; - } - if (empty($ids)) { - return array(); + // Collect the IDs of existing entities to load, and directly grab the + // "autocreate" entities that are already populated in $item->entity. + $target_entities = $ids = array(); + foreach ($this->list as $delta => $item) { + if ($item->target_id !== NULL) { + $ids[$delta] = $item->target_id; + } + elseif ($item->hasNewEntity()) { + $target_entities[$delta] = $item->entity; + } } - $target_type = $this->getFieldDefinition()->getSetting('target_type'); - $entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids); - - $target_entities = array(); - foreach ($ids as $delta => $target_id) { - if (isset($entities[$target_id])) { - $target_entities[$delta] = $entities[$target_id]; + // Load and add the existing entities. + if ($ids) { + $target_type = $this->getFieldDefinition()->getSetting('target_type'); + $entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids); + foreach ($ids as $delta => $target_id) { + if (isset($entities[$target_id])) { + $target_entities[$delta] = $entities[$target_id]; + } } + // Ensure the returned array is ordered by deltas. + ksort($target_entities); } return $target_entities; diff --git a/core/lib/Drupal/Core/Field/FormatterPluginManager.php b/core/lib/Drupal/Core/Field/FormatterPluginManager.php index b7b297b..e6054a9 100644 --- a/core/lib/Drupal/Core/Field/FormatterPluginManager.php +++ b/core/lib/Drupal/Core/Field/FormatterPluginManager.php @@ -154,8 +154,9 @@ class FormatterPluginManager extends DefaultPluginManager { $field_type = $this->fieldTypeManager->getDefinition($field_type); $configuration['type'] = $field_type['default_formatter']; } - // Fill in default settings values for the formatter. - $configuration['settings'] += $this->getDefaultSettings($configuration['type']); + // Filter out unknown settings, and fill in defaults for missing settings. + $default_settings = $this->getDefaultSettings($configuration['type']); + $configuration['settings'] = array_intersect_key($configuration['settings'], $default_settings) + $default_settings; return $configuration; } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index a14fe00..a6c6bed 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -8,7 +8,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldType; use Drupal\Core\Config\Entity\ConfigEntityType; -use Drupal\Core\Entity\Entity; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\TypedData\EntityDataDefinition; use Drupal\Core\Field\FieldDefinitionInterface; @@ -166,7 +166,7 @@ class EntityReferenceItem extends FieldItemBase { // If there is an unsaved entity, return it as part of the field item values // to ensure idempotency of getValue() / setValue(). - if ($this->hasUnsavedEntity()) { + if ($this->hasNewEntity()) { $values['entity'] = $this->entity; } return $values; @@ -191,11 +191,10 @@ class EntityReferenceItem extends FieldItemBase { */ public function isEmpty() { // Avoid loading the entity by first checking the 'target_id'. - $target_id = $this->target_id; - if ($target_id !== NULL) { + if ($this->target_id !== NULL) { return FALSE; } - if ($this->entity && $this->entity instanceof Entity) { + if ($this->entity && $this->entity instanceof EntityInterface) { return FALSE; } return TRUE; @@ -205,12 +204,12 @@ class EntityReferenceItem extends FieldItemBase { * {@inheritdoc} */ public function preSave() { - if ($this->hasUnsavedEntity()) { + if ($this->hasNewEntity()) { $this->entity->save(); } // Handle the case where an unsaved entity was directly set using the public // 'entity' property and then saved before this entity. In this case - // ::hasUnsavedEntity() will return FALSE but $this->target_id will still be + // ::hasNewEntity() will return FALSE but $this->target_id will still be // empty. if (empty($this->target_id) && $this->entity) { $this->target_id = $this->entity->id(); @@ -239,7 +238,7 @@ class EntityReferenceItem extends FieldItemBase { * @return bool * TRUE if the item holds an unsaved entity. */ - public function hasUnsavedEntity() { + public function hasNewEntity() { return $this->target_id === NULL && ($entity = $this->entity) && $entity->isNew(); } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php index 02d204a..a2f569b 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php @@ -30,6 +30,9 @@ class IntegerItem extends NumericItemBase { public static function defaultStorageSettings() { return array( 'unsigned' => FALSE, + // Valid size property values include: 'tiny', 'small', 'medium', 'normal' + // and 'big'. + 'size' => 'normal', ) + parent::defaultStorageSettings(); } @@ -42,9 +45,6 @@ class IntegerItem extends NumericItemBase { 'max' => '', 'prefix' => '', 'suffix' => '', - // Valid size property values include: 'tiny', 'small', 'medium', 'normal' - // and 'big'. - 'size' => 'normal', ) + parent::defaultFieldSettings(); } diff --git a/core/lib/Drupal/Core/Field/WidgetPluginManager.php b/core/lib/Drupal/Core/Field/WidgetPluginManager.php index fb7abdd..c251135 100644 --- a/core/lib/Drupal/Core/Field/WidgetPluginManager.php +++ b/core/lib/Drupal/Core/Field/WidgetPluginManager.php @@ -153,8 +153,9 @@ class WidgetPluginManager extends DefaultPluginManager { $field_type = $this->fieldTypeManager->getDefinition($field_type); $configuration['type'] = $field_type['default_widget']; } - // Fill in default settings values for the widget. - $configuration['settings'] += $this->getDefaultSettings($configuration['type']); + // Filter out unknown settings, and fill in defaults for missing settings. + $default_settings = $this->getDefaultSettings($configuration['type']); + $configuration['settings'] = array_intersect_key($configuration['settings'], $default_settings) + $default_settings; return $configuration; } diff --git a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php index e163c04..12a0572 100644 --- a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php +++ b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php @@ -10,6 +10,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides the file transfer authorization form. @@ -17,6 +18,30 @@ class FileTransferAuthorizeForm extends FormBase { /** + * The app root. + * + * @var string + */ + protected $root; + + /** + * Constructs a new FileTransferAuthorizeForm object. + * + * @param string $root + * The app root. + */ + public function __construct($root) { + $this->root = $root; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static ($container->get('app.root')); + } + + /** * {@inheritdoc} */ public function getFormID() { @@ -241,7 +266,7 @@ class FileTransferAuthorizeForm extends FormBase { if (!empty($_SESSION['authorize_filetransfer_info'][$backend])) { $backend_info = $_SESSION['authorize_filetransfer_info'][$backend]; if (class_exists($backend_info['class'])) { - $filetransfer = $backend_info['class']::factory(DRUPAL_ROOT, $settings); + $filetransfer = $backend_info['class']::factory($this->root, $settings); } } return $filetransfer; @@ -316,7 +341,7 @@ class FileTransferAuthorizeForm extends FormBase { $operation = $_SESSION['authorize_operation']; unset($_SESSION['authorize_operation']); - require_once DRUPAL_ROOT . '/' . $operation['file']; + require_once $this->root . '/' . $operation['file']; call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments'])); } diff --git a/core/lib/Drupal/Core/Form/FormCache.php b/core/lib/Drupal/Core/Form/FormCache.php index 9eba042..ea38fd2 100644 --- a/core/lib/Drupal/Core/Form/FormCache.php +++ b/core/lib/Drupal/Core/Form/FormCache.php @@ -82,8 +82,17 @@ class FormCache implements FormCacheInterface { protected $requestPolicy; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a new FormCache. * + * @param string $root + * The app root. * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory * The key value expirable factory, used to create key value expirable * stores for the form cache and form state cache. @@ -102,7 +111,8 @@ class FormCache implements FormCacheInterface { * @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy * A policy rule determining the cacheability of a request. */ - public function __construct(KeyValueExpirableFactoryInterface $key_value_expirable_factory, ModuleHandlerInterface $module_handler, AccountInterface $current_user, CsrfTokenGenerator $csrf_token, LoggerInterface $logger, ConfigFactoryInterface $config_factory, RequestStack $request_stack, RequestPolicyInterface $request_policy) { + public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, ModuleHandlerInterface $module_handler, AccountInterface $current_user, CsrfTokenGenerator $csrf_token, LoggerInterface $logger, ConfigFactoryInterface $config_factory, RequestStack $request_stack, RequestPolicyInterface $request_policy) { + $this->root = $root; $this->keyValueExpirableFactory = $key_value_expirable_factory; $this->moduleHandler = $module_handler; $this->currentUser = $current_user; @@ -160,7 +170,7 @@ class FormCache implements FormCacheInterface { $this->moduleHandler->loadInclude($file['module'], $file['type'], $file['name']); } elseif (file_exists($file)) { - require_once DRUPAL_ROOT . '/' . $file; + require_once $this->root . '/' . $file; } } // Retrieve the list of previously known safe strings and store it for diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php index b204d77..c6266d7 100644 --- a/core/lib/Drupal/Core/Form/FormState.php +++ b/core/lib/Drupal/Core/Form/FormState.php @@ -354,7 +354,7 @@ class FormState implements FormStateInterface { * * @var array */ - protected $temporary; + protected $temporary = []; /** * Tracks if the form has finished validation. @@ -714,6 +714,31 @@ class FormState implements FormStateInterface { /** * {@inheritdoc} */ + public function &getTemporaryValue($key) { + $value = &NestedArray::getValue($this->temporary, (array) $key); + return $value; + } + + /** + * {@inheritdoc} + */ + public function setTemporaryValue($key, $value) { + NestedArray::setValue($this->temporary, (array) $key, $value, TRUE); + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasTemporaryValue($key) { + $exists = NULL; + NestedArray::getValue($this->temporary, (array) $key, $exists); + return $exists; + } + + /** + * {@inheritdoc} + */ public function setTriggeringElement($triggering_element) { $this->triggering_element = $triggering_element; return $this; diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php index 8a544df..8b6a02a 100644 --- a/core/lib/Drupal/Core/Form/FormStateInterface.php +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php @@ -934,6 +934,47 @@ public function getTemporary(); /** + * Gets an arbitrary value from temporary storage. + * + * @param string|array $key + * Properties are often stored as multi-dimensional associative arrays. If + * $key is a string, it will return $temporary[$key]. If $key is an array, + * each element of the array will be used as a nested key. If + * $key = ['foo', 'bar'] it will return $temporary['foo']['bar']. + * + * @return mixed + * A reference to the value for that key, or NULL if the property does + * not exist. + */ + public function &getTemporaryValue($key); + + /** + * Sets an arbitrary value in temporary storage. + * + * @param string|array $key + * Properties are often stored as multi-dimensional associative arrays. If + * $key is a string, it will use $temporary[$key] = $value. If $key is an + * array, each element of the array will be used as a nested key. If + * $key = ['foo', 'bar'] it will use $temporary['foo']['bar'] = $value. + * @param mixed $value + * The value to set. + * + * @return $this + */ + public function setTemporaryValue($key, $value); + + /** + * Determines if a temporary value is present. + * + * @param string $key + * Properties are often stored as multi-dimensional associative arrays. If + * $key is a string, it will return isset($temporary[$key]). If $key is an + * array, each element of the array will be used as a nested key. If + * $key = ['foo', 'bar'] it will return isset($temporary['foo']['bar']). + */ + public function hasTemporaryValue($key); + + /** * Sets the form element that triggered submission. * * @param array|null $triggering_element diff --git a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php index db3fd15..05c9c23 100644 --- a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php +++ b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php @@ -49,8 +49,17 @@ class SiteConfigureForm extends FormBase { protected $countryManager; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a new SiteConfigureForm. * + * @param string $root + * The app root. * @param \Drupal\user\UserStorageInterface $user_storage * The user storage. * @param \Drupal\Core\State\StateInterface $state @@ -60,7 +69,8 @@ class SiteConfigureForm extends FormBase { * @param \Drupal\Core\Locale\CountryManagerInterface $country_manager * The country manager. */ - public function __construct(UserStorageInterface $user_storage, StateInterface $state, ModuleHandlerInterface $module_handler, CountryManagerInterface $country_manager) { + public function __construct($root, UserStorageInterface $user_storage, StateInterface $state, ModuleHandlerInterface $module_handler, CountryManagerInterface $country_manager) { + $this->root = $root; $this->userStorage = $user_storage; $this->state = $state; $this->moduleHandler = $module_handler; @@ -72,6 +82,7 @@ class SiteConfigureForm extends FormBase { */ public static function create(ContainerInterface $container) { return new static( + $container->get('app.root'), $container->get('entity.manager')->getStorage('user'), $container->get('state'), $container->get('module_handler'), @@ -103,7 +114,7 @@ class SiteConfigureForm extends FormBase { // distract from the message that the Drupal installation has completed // successfully.) $post_params = $this->getRequest()->request->all(); - if (empty($post_params) && (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_dir, FILE_NOT_WRITABLE, 'dir'))) { + if (empty($post_params) && (!drupal_verify_install_file($this->root . '/' . $settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file($this->root . '/' . $settings_dir, FILE_NOT_WRITABLE, 'dir'))) { drupal_set_message(t('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, consult the online handbook.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'warning'); } diff --git a/core/lib/Drupal/Core/Mail/MailInterface.php b/core/lib/Drupal/Core/Mail/MailInterface.php index 64573ad..22eb8d1 100644 --- a/core/lib/Drupal/Core/Mail/MailInterface.php +++ b/core/lib/Drupal/Core/Mail/MailInterface.php @@ -53,8 +53,8 @@ * - subject: Subject of the email to be sent. This must not contain any * newline characters, or the mail may not be sent properly. * - body: Message to be sent. Accepts both CRLF and LF line-endings. - * Email bodies must be wrapped. You can use drupal_wrap_mail() for - * smart plain text wrapping. + * Email bodies must be wrapped. For smart plain text wrapping you can use + * \Drupal\Core\Mail\MailFormatHelper::wrapMail() . * - headers: Associative array containing all additional mail headers not * defined by one of the other parameters. PHP's mail() looks for Cc and * Bcc headers and sends the mail to addresses in these headers too. diff --git a/core/lib/Drupal/Core/Mail/MailManagerInterface.php b/core/lib/Drupal/Core/Mail/MailManagerInterface.php index 89916c1..50645ab 100644 --- a/core/lib/Drupal/Core/Mail/MailManagerInterface.php +++ b/core/lib/Drupal/Core/Mail/MailManagerInterface.php @@ -22,8 +22,9 @@ * appropriate places in the template. Processed email templates are requested * from hook_mail() from the module sending the email. Any module can modify * the composed email message array using hook_mail_alter(). Finally - * drupal_mail_system()->mail() sends the email, which can be reused if the - * exact same composed email is to be sent to multiple recipients. + * \Drupal::service('plugin.manager.mail')->mail() sends the email, which can + * be reused if the exact same composed email is to be sent to multiple + * recipients. * * Finding out what language to send the email with needs some consideration. * If you send email to a user, her preferred language should be fine, so use @@ -108,9 +109,10 @@ * Optional email address to be used to answer. * @param bool $send * If TRUE, \Drupal::service('plugin.manager.mail')->mail() will call - * drupal_mail_system()->mail() to deliver the message, and store the result - * in $message['result']. Modules implementing hook_mail_alter() may cancel - * sending by setting $message['send'] to FALSE. + * \Drupal::service('plugin.manager.mail')->mail() to deliver the message, + * and store the result in $message['result']. Modules implementing + * hook_mail_alter() may cancel sending by setting $message['send'] to + * FALSE. * * @return string * The $message array structure containing all details of the message. If diff --git a/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php index 1a97b66..3390a4a 100644 --- a/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php @@ -38,7 +38,7 @@ class PhpMail implements MailInterface { // Convert any HTML to plain-text. $message['body'] = MailFormatHelper::htmlToText($message['body']); // Wrap the mail body for sending. - $message['body'] = drupal_wrap_mail($message['body']); + $message['body'] = MailFormatHelper::wrapMail($message['body']); return $message; } diff --git a/core/lib/Drupal/Core/Path/AliasStorage.php b/core/lib/Drupal/Core/Path/AliasStorage.php index 3922e23..2719331 100644 --- a/core/lib/Drupal/Core/Path/AliasStorage.php +++ b/core/lib/Drupal/Core/Path/AliasStorage.php @@ -193,16 +193,7 @@ class AliasStorage implements AliasStorageInterface { } /** - * Checks if alias already exists. - * - * @param string $alias - * Alias to check against. - * @param string $langcode - * Language of the alias. - * @param string $source - * Path that alias is to be assigned to (optional). - * @return boolean - * TRUE if alias already exists and FALSE otherwise. + * {@inheritdoc} */ public function aliasExists($alias, $langcode, $source = NULL) { $query = $this->connection->select('url_alias') @@ -217,24 +208,14 @@ class AliasStorage implements AliasStorageInterface { } /** - * Checks if there are any aliases with language defined. - * - * @return bool - * TRUE if aliases with language exist. + * {@inheritdoc} */ public function languageAliasExists() { return (bool) $this->connection->queryRange('SELECT 1 FROM {url_alias} WHERE langcode <> :langcode', 0, 1, array(':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED))->fetchField(); } /** - * Loads aliases for admin listing. - * - * @param array $header - * Table header. - * @param string $keys - * Search keys. - * @return array - * Array of items to be displayed on the current page. + * {@inheritdoc} */ public function getAliasesForAdminListing($header, $keys = NULL) { $query = $this->connection->select('url_alias') @@ -253,13 +234,7 @@ class AliasStorage implements AliasStorageInterface { } /** - * Check if any alias exists starting with $initial_substring. - * - * @param $initial_substring - * Initial path substring to test against. - * - * @return - * TRUE if any alias exists, FALSE otherwise. + * {@inheritdoc} */ public function pathHasMatchingAlias($initial_substring) { $query = $this->connection->select('url_alias', 'u'); diff --git a/core/lib/Drupal/Core/Path/AliasStorageInterface.php b/core/lib/Drupal/Core/Path/AliasStorageInterface.php index 30364fd..8d82846 100644 --- a/core/lib/Drupal/Core/Path/AliasStorageInterface.php +++ b/core/lib/Drupal/Core/Path/AliasStorageInterface.php @@ -22,11 +22,11 @@ * @param string $alias * The URL alias. * @param string $langcode - * The language code of the alias. + * (optional) The language code of the alias. * @param int|null $pid - * Unique path alias identifier. + * (optional) Unique path alias identifier. * - * @return mixed[]|bool + * @return array|false * FALSE if the path could not be saved or an associative array containing * the following keys: * - source (string): The internal system path. @@ -41,10 +41,10 @@ /** * Fetches a specific URL alias from the database. * - * @param $conditions + * @param array $conditions * An array of query conditions. * - * @return mixed[]|bool + * @return array|false * FALSE if no alias was found or an associative array containing the * following keys: * - source (string): The internal system path. @@ -65,8 +65,9 @@ /** * Pre-loads path alias information for a given list of source paths. * - * @param $preloaded - * @param $langcode + * @param array $preloaded + * Paths that need preloading of aliases. + * @param string $langcode * Language code to search the path with. If there's no path defined for * that language it will search paths without language. * @@ -102,4 +103,51 @@ * A Drupal system path, or FALSE if no path was found. */ public function lookupPathSource($path, $langcode); + + /** + * Checks if alias already exists. + * + * @param string $alias + * Alias to check against. + * @param string $langcode + * Language of the alias. + * @param string|null $source + * (optional) Path that alias is to be assigned to. + * + * @return bool + * TRUE if alias already exists and FALSE otherwise. + */ + public function aliasExists($alias, $langcode, $source = NULL); + + /** + * Checks if there are any aliases with language defined. + * + * @return bool + * TRUE if aliases with language exist. + */ + public function languageAliasExists(); + + /** + * Loads aliases for admin listing. + * + * @param array $header + * Table header. + * @param string[]|null $keys + * (optional) Search keys. + * + * @return array + * Array of items to be displayed on the current page. + */ + public function getAliasesForAdminListing($header, $keys = NULL); + + /** + * Check if any alias exists starting with $initial_substring. + * + * @param string $initial_substring + * Initial path substring to test against. + * + * @return bool + * TRUE if any alias exists, FALSE otherwise. + */ + public function pathHasMatchingAlias($initial_substring); } diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php index a5a5a3a..a5f4aba 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php @@ -7,10 +7,9 @@ namespace Drupal\Core\Plugin\Context; -use Drupal\Component\Plugin\ConfigurablePluginInterface; -use Drupal\Component\Plugin\ContextAwarePluginInterface; use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Component\Utility\String; +use Drupal\Core\Plugin\ContextAwarePluginInterface; /** * Provides methods to handle sets of contexts. @@ -73,28 +72,21 @@ class ContextHandler implements ContextHandlerInterface { * {@inheritdoc} */ public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) { - if ($plugin instanceof ConfigurablePluginInterface) { - $configuration = $plugin->getConfiguration(); - if (isset($configuration['context_mapping'])) { - $mappings += array_flip($configuration['context_mapping']); - } - } - $plugin_contexts = $plugin->getContextDefinitions(); - // Loop through each context and set it on the plugin if it matches one of - // the contexts expected by the plugin. - foreach ($contexts as $name => $context) { + $mappings += $plugin->getContextMapping(); + // Loop through each of the expected contexts. + foreach (array_keys($plugin->getContextDefinitions()) as $plugin_context_id) { // If this context was given a specific name, use that. - $assigned_name = isset($mappings[$name]) ? $mappings[$name] : $name; - if (isset($plugin_contexts[$assigned_name])) { + $context_id = isset($mappings[$plugin_context_id]) ? $mappings[$plugin_context_id] : $plugin_context_id; + if (!empty($contexts[$context_id])) { // This assignment has been used, remove it. - unset($mappings[$name]); - $plugin->setContextValue($assigned_name, $context->getContextValue()); + unset($mappings[$plugin_context_id]); + $plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextValue()); } } // If there are any mappings that were not satisfied, throw an exception. if (!empty($mappings)) { - throw new ContextException(String::format('Assigned contexts were not satisfied: @mappings', array('@mappings' => implode(',', $mappings)))); + throw new ContextException(String::format('Assigned contexts were not satisfied: @mappings', ['@mappings' => implode(',', array_keys($mappings))])); } } diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php index bd0ade5..3b8b2db 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Plugin\Context; -use Drupal\Component\Plugin\ContextAwarePluginInterface; +use Drupal\Core\Plugin\ContextAwarePluginInterface; /** * Provides an interface for handling sets of contexts. @@ -63,16 +63,16 @@ /** * Prepares a plugin for evaluation. * - * @param \Drupal\Component\Plugin\ContextAwarePluginInterface $plugin + * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin * A plugin about to be evaluated. * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts * An array of contexts to set on the plugin. They will only be set if they * match the plugin's context definitions. * @param array $mappings * (optional) A mapping of the expected assignment names to their context - * names. For example, if one of the $contexts is named 'entity', but the - * plugin expects a context named 'node', then this map would contain - * 'entity' => 'node'. + * names. For example, if one of the $contexts is named 'current_user', but the + * plugin expects a context named 'user', then this map would contain + * 'user' => 'current_user'. * * @throws \Drupal\Component\Plugin\Exception\ContextException * Thrown when a context assignment was not satisfied. diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginAssignmentTrait.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginAssignmentTrait.php new file mode 100644 index 0000000..19d756c --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginAssignmentTrait.php @@ -0,0 +1,70 @@ +getContextDefinitions() as $context_slot => $definition) { + $valid_contexts = $this->contextHandler()->getMatchingContexts($contexts, $definition); + $options = []; + foreach ($valid_contexts as $context_id => $context) { + $element['#tree'] = TRUE; + $options[$context_id] = $context->getContextDefinition()->getLabel(); + $element[$context_slot] = [ + '#type' => 'value', + '#value' => $context_id, + ]; + } + + if (count($options) > 1) { + $assignments = $plugin->getContextMapping(); + $element[$context_slot] = [ + '#title' => $this->t('Select a @context value:', ['@context' => $context_slot]), + '#type' => 'select', + '#options' => $options, + '#required' => $definition->isRequired(), + '#default_value' => !empty($assignments[$context_slot]) ? $assignments[$context_slot] : '', + ]; + } + } + return $element; + } + +} diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php index 1fec193..df86cf0 100644 --- a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Plugin; +use Drupal\Component\Plugin\ConfigurablePluginInterface; use Drupal\Component\Plugin\ContextAwarePluginBase as ComponentContextAwarePluginBase; use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Core\DependencyInjection\DependencySerializationTrait; @@ -19,7 +20,7 @@ /** * Base class for plugins that are context aware. */ -abstract class ContextAwarePluginBase extends ComponentContextAwarePluginBase { +abstract class ContextAwarePluginBase extends ComponentContextAwarePluginBase implements ContextAwarePluginInterface { use TypedDataTrait; use StringTranslationTrait; use DependencySerializationTrait; @@ -49,4 +50,45 @@ parent::setContext($name, $context); } + /** + * {@inheritdoc} + */ + public function getContextMapping() { + $configuration = $this instanceof ConfigurablePluginInterface ? $this->getConfiguration() : $this->configuration; + return isset($configuration['context_mapping']) ? $configuration['context_mapping'] : []; + } + + /** + * {@inheritdoc} + */ + public function setContextMapping(array $context_mapping) { + if ($this instanceof ConfigurablePluginInterface) { + $configuration = $this->getConfiguration(); + $configuration['context_mapping'] = $context_mapping; + $this->setConfiguration($configuration); + } + else { + $this->configuration['context_mapping'] = $context_mapping; + } + return $this; + } + + /** + * {@inheritdoc} + * + * @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface[] + */ + public function getContextDefinitions() { + return parent::getContextDefinitions(); + } + + /** + * Wraps the context handler. + * + * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface + */ + protected function contextHandler() { + return \Drupal::service('context.handler'); + } + } diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginInterface.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginInterface.php new file mode 100644 index 0000000..cc2f9a3 --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginInterface.php @@ -0,0 +1,42 @@ + $content]; - } - $attributes = [ - 'class' => [ - 'maintenance-page', - ], - ]; - return $this->renderBarePage($content, $title, $page_additions, $attributes, 'maintenance_page'); + protected $renderer; + + /** + * Constructs a new BareHtmlPageRenderer. + * + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. + */ + public function __construct(RendererInterface $renderer) { + $this->renderer = $renderer; } /** * {@inheritdoc} */ - public function renderInstallPage($content, $title, array $page_additions = []) { + public function renderBarePage(array $content, $title, $page_theme_property, array $page_additions = []) { $attributes = [ 'class' => [ - 'install-page', + str_replace('_', '-', $page_theme_property), ], ]; - return $this->renderBarePage($content, $title, $page_additions, $attributes, 'install_page'); - } - - /** - * Renders a bare page. - * - * @param string|array $content - * The main content to render in the 'content' region. - * @param string $title - * The title for this maintenance page. - * @param array $page_additions - * Additional regions to add to the page. May also be used to pass the - * #show_messages property for #type 'page'. - * @param array $attributes - * Attributes to set on #type 'html'. - * @param string $page_theme_property - * The #theme property to set on #type 'page'. - * - * @return string - * The rendered HTML page. - */ - protected function renderBarePage(array $content, $title, array $page_additions, array $attributes, $page_theme_property) { $html = [ '#type' => 'html', '#attributes' => $attributes, @@ -73,8 +53,12 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface { // \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse() for more // information about this; the exact same pattern is used there and // explained in detail there. - drupal_render_root($html['page']); - return drupal_render($html); + $this->renderer->render($html['page'], TRUE); + + // Add the bare minimum of attachments from the system module and the + // current maintenance theme. + system_page_attachments($html['page']); + return $this->renderer->render($html); } } diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php b/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php index 7450199..da15eea 100644 --- a/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php @@ -25,9 +25,10 @@ * - exception handlers * * i.e. use this when rendering HTML pages in limited environments. Otherwise, - * use a @code _content @endcode route, this will cause a main content renderer - * (\Drupal\Core\Render\MainContent\MainContentRendererInterface) to be used, - * and in case of a HTML request that will be + * use a @code _controller @endcode route, and return a render array. + * This will cause a main content renderer + * (\Drupal\Core\Render\MainContent\MainContentRendererInterface) to be + * used, and in case of a HTML request that will be * \Drupal\Core\Render\MainContent\HtmlRenderer. * * In fact, this is not only *typically* used in a limited environment, it even @@ -45,12 +46,14 @@ interface BareHtmlPageRendererInterface { /** - * Renders a "maintenance" page, styled as such. + * Renders a bare page. * - * @param string|array $content + * @param array $content * The main content to render in the 'content' region. * @param string $title * The title for this maintenance page. + * @param string $page_theme_property + * The #theme property to set on #type 'page'. * @param array $page_additions * Additional regions to add to the page. May also be used to pass the * #show_messages property for #type 'page'. @@ -58,22 +61,6 @@ * @return string * The rendered HTML page. */ - public function renderMaintenancePage($content, $title, array $page_additions = []); - - /** - * Renders an "install" page, styled as such. - * - * @param string|array $content - * The main content to render in the 'content' region. - * @param string $title - * The title for this maintenance page. - * @param array $page_additions - * Additional regions to add to the page. May also be used to pass the - * #show_messages property for #type 'page'. - * - * @return string - * The rendered HTML page. - */ - public function renderInstallPage($content, $title, array $page_additions = []); + public function renderBarePage(array $content, $title, $page_theme_property, array $page_additions = []); } diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index 4bd0f2e..323fd87 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -14,6 +14,7 @@ use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Render\PageDisplayVariantSelectionEvent; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RenderEvents; use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; @@ -55,6 +56,13 @@ class HtmlRenderer implements MainContentRendererInterface { protected $moduleHandler; /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** * Constructs a new HtmlRenderer. * * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver @@ -65,12 +73,15 @@ class HtmlRenderer implements MainContentRendererInterface { * The event dispatcher. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. */ - public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler) { + public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer) { $this->titleResolver = $title_resolver; $this->displayVariantManager = $display_variant_manager; $this->eventDispatcher = $event_dispatcher; $this->moduleHandler = $module_handler; + $this->renderer = $renderer; } /** @@ -108,14 +119,14 @@ class HtmlRenderer implements MainContentRendererInterface { // and hence may not execute any #post_render_cache_callbacks (because they // might add yet more assets to be attached), and therefore it must be // rendered with drupal_render(), not drupal_render_root(). - drupal_render_root($html['page']); + $this->renderer->render($html['page'], TRUE); if (isset($html['page_top'])) { - drupal_render_root($html['page_top']); + $this->renderer->render($html['page_top'], TRUE); } if (isset($html['page_bottom'])) { - drupal_render_root($html['page_bottom']); + $this->renderer->render($html['page_bottom'], TRUE); } - $content = drupal_render($html); + $content = $this->renderer->render($html); // Store the cache tags associated with this page in a X-Drupal-Cache-Tags // header. Also associate the "rendered" cache tag. This allows us to @@ -155,8 +166,9 @@ class HtmlRenderer implements MainContentRendererInterface { * If the selected display variant does not implement PageVariantInterface. */ protected function prepare(array $main_content, Request $request, RouteMatchInterface $route_match) { - // If the _content result already is #type => page, we have no work to do: - // the "main content" already is an entire "page" (see html.html.twig). + // If the _controller result already is #type => page, + // we have no work to do: The "main content" already is an entire "page" + // (see html.html.twig). if (isset($main_content['#type']) && $main_content['#type'] === 'page') { $page = $main_content; } @@ -176,7 +188,7 @@ class HtmlRenderer implements MainContentRendererInterface { // ::renderContentIntoResponse(). // @todo Remove this once https://www.drupal.org/node/2359901 lands. if (!empty($main_content)) { - drupal_render($main_content, FALSE); + $this->renderer->render($main_content, FALSE); $main_content = [ '#markup' => $main_content['#markup'], '#attached' => $main_content['#attached'], diff --git a/core/lib/Drupal/Core/Render/MainContent/MainContentRendererInterface.php b/core/lib/Drupal/Core/Render/MainContent/MainContentRendererInterface.php index ca957c8..10cd71a 100644 --- a/core/lib/Drupal/Core/Render/MainContent/MainContentRendererInterface.php +++ b/core/lib/Drupal/Core/Render/MainContent/MainContentRendererInterface.php @@ -11,10 +11,10 @@ use Symfony\Component\HttpFoundation\Request; /** - * The interface for "main content" (@code _content @endcode) renderers. + * The interface for "main content" (@code _controller @endcode) renderers. * * Classes implementing this interface are able to render the main content (as - * received from "_content" controllers) into a response of a certain format + * received from controllers) into a response of a certain format * (HTML, JSON …) and/or in a certain decorated manner (e.g. in the case of the * default HTML main content renderer: with a page display variant applied). */ diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php new file mode 100644 index 0000000..5fb494e --- /dev/null +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -0,0 +1,390 @@ +controllerResolver = $controller_resolver; + $this->theme = $theme; + $this->elementInfo = $element_info; + } + + /** + * {@inheritdoc} + */ + public function renderRoot(&$elements) { + return $this->render($elements, TRUE); + } + + /** + * {@inheritdoc} + */ + public function render(&$elements, $is_root_call = FALSE) { + static $stack; + + $update_stack = function(&$element) use (&$stack) { + // The latest frame represents the bubbleable data for the subtree. + $frame = $stack->top(); + // Update the frame, but also update the current element, to ensure it + // contains up-to-date information in case it gets render cached. + $frame->tags = $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $frame->tags); + $frame->attached = $element['#attached'] = drupal_merge_attached($element['#attached'], $frame->attached); + $frame->postRenderCache = $element['#post_render_cache'] = NestedArray::mergeDeep($element['#post_render_cache'], $frame->postRenderCache); + }; + + $bubble_stack = function() use (&$stack) { + // If there's only one frame on the stack, then this is the root call, and + // we can't bubble up further. Reset the stack for the next root call. + if ($stack->count() === 1) { + $stack = NULL; + return; + } + + // Merge the current and the parent stack frame. + $current = $stack->pop(); + $parent = $stack->pop(); + $current->tags = Cache::mergeTags($current->tags, $parent->tags); + $current->attached = drupal_merge_attached($current->attached, $parent->attached); + $current->postRenderCache = NestedArray::mergeDeep($current->postRenderCache, $parent->postRenderCache); + $stack->push($current); + }; + + if (!isset($elements['#access']) && isset($elements['#access_callback'])) { + if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) { + $elements['#access_callback'] = $this->controllerResolver->getControllerFromDefinition($elements['#access_callback']); + } + $elements['#access'] = call_user_func($elements['#access_callback'], $elements); + } + + // Early-return nothing if user does not have access. + if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) { + return ''; + } + + // Do not print elements twice. + if (!empty($elements['#printed'])) { + return ''; + } + + if (!isset($stack)) { + $stack = new \SplStack(); + } + $stack->push(new RenderStackFrame()); + + // Try to fetch the prerendered element from cache, run any + // #post_render_cache callbacks and return the final markup. + if (isset($elements['#cache'])) { + $cached_element = drupal_render_cache_get($elements); + if ($cached_element !== FALSE) { + $elements = $cached_element; + // Only when we're not in a root (non-recursive) drupal_render() call, + // #post_render_cache callbacks must be executed, to prevent breaking + // the render cache in case of nested elements with #cache set. + if ($is_root_call) { + $this->processPostRenderCache($elements); + } + $elements['#markup'] = SafeMarkup::set($elements['#markup']); + // The render cache item contains all the bubbleable rendering metadata + // for the subtree. + $update_stack($elements); + // Render cache hit, so rendering is finished, all necessary info + // collected! + $bubble_stack(); + return $elements['#markup']; + } + } + + // If the default values for this element have not been loaded yet, populate + // them. + if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) { + $elements += $this->elementInfo->getInfo($elements['#type']); + } + + // Make any final changes to the element before it is rendered. This means + // that the $element or the children can be altered or corrected before the + // element is rendered into the final text. + if (isset($elements['#pre_render'])) { + foreach ($elements['#pre_render'] as $callable) { + if (is_string($callable) && strpos($callable, '::') === FALSE) { + $callable = $this->controllerResolver->getControllerFromDefinition($callable); + } + // Since #pre_render callbacks may be used for generating a render + // array's content, and we might be rendering the main content for the + // page, it is possible that a #pre_render callback throws an exception + // that will cause a different page to be rendered (e.g. throwing + // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will + // cause the 404 page to be rendered). That page might also use + // drupal_render(), but if exceptions aren't caught here, the stack will + // be left in an inconsistent state. + // Hence, catch all exceptions and reset the stack and re-throw them. + try { + $elements = call_user_func($callable, $elements); + } + catch (\Exception $e) { + // Reset stack and re-throw exception. + $stack = NULL; + throw $e; + } + } + } + + // Defaults for bubbleable rendering metadata. + $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array(); + $elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array(); + $elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array(); + + // Allow #pre_render to abort rendering. + if (!empty($elements['#printed'])) { + // The #printed element contains all the bubbleable rendering metadata for + // the subtree. + $update_stack($elements); + // #printed, so rendering is finished, all necessary info collected! + $bubble_stack(); + return ''; + } + + // Add any JavaScript state information associated with the element. + if (!empty($elements['#states'])) { + drupal_process_states($elements); + } + + // Get the children of the element, sorted by weight. + $children = Element::children($elements, TRUE); + + // Initialize this element's #children, unless a #pre_render callback + // already preset #children. + if (!isset($elements['#children'])) { + $elements['#children'] = ''; + } + + // @todo Simplify after https://drupal.org/node/2273925 + if (isset($elements['#markup'])) { + $elements['#markup'] = SafeMarkup::set($elements['#markup']); + } + + // Assume that if #theme is set it represents an implemented hook. + $theme_is_implemented = isset($elements['#theme']); + // Check the elements for insecure HTML and pass through sanitization. + if (isset($elements)) { + $markup_keys = array( + '#description', + '#field_prefix', + '#field_suffix', + ); + foreach ($markup_keys as $key) { + if (!empty($elements[$key]) && is_scalar($elements[$key])) { + $elements[$key] = SafeMarkup::checkAdminXss($elements[$key]); + } + } + } + + // Call the element's #theme function if it is set. Then any children of the + // element have to be rendered there. If the internal #render_children + // property is set, do not call the #theme function to prevent infinite + // recursion. + if ($theme_is_implemented && !isset($elements['#render_children'])) { + $elements['#children'] = $this->theme->render($elements['#theme'], $elements); + + // If ThemeManagerInterface::render() returns FALSE this means that the + // hook in #theme was not found in the registry and so we need to update + // our flag accordingly. This is common for theme suggestions. + $theme_is_implemented = ($elements['#children'] !== FALSE); + } + + // If #theme is not implemented or #render_children is set and the element + // has an empty #children attribute, render the children now. This is the + // same process as Renderer::render() but is inlined for speed. + if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) { + foreach ($children as $key) { + $elements['#children'] .= $this->render($elements[$key]); + } + $elements['#children'] = SafeMarkup::set($elements['#children']); + } + + // If #theme is not implemented and the element has raw #markup as a + // fallback, prepend the content in #markup to #children. In this case + // #children will contain whatever is provided by #pre_render prepended to + // what is rendered recursively above. If #theme is implemented then it is + // the responsibility of that theme implementation to render #markup if + // required. Eventually #theme_wrappers will expect both #markup and + // #children to be a single string as #children. + if (!$theme_is_implemented && isset($elements['#markup'])) { + $elements['#children'] = SafeMarkup::set($elements['#markup'] . $elements['#children']); + } + + // Let the theme functions in #theme_wrappers add markup around the rendered + // children. + // #states and #attached have to be processed before #theme_wrappers, + // because the #type 'page' render array from drupal_prepare_page() would + // render the $page and wrap it into the html.html.twig template without the + // attached assets otherwise. + // If the internal #render_children property is set, do not call the + // #theme_wrappers function(s) to prevent infinite recursion. + if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) { + foreach ($elements['#theme_wrappers'] as $key => $value) { + // If the value of a #theme_wrappers item is an array then the theme + // hook is found in the key of the item and the value contains attribute + // overrides. Attribute overrides replace key/value pairs in $elements + // for only this ThemeManagerInterface::render() call. This allows + // #theme hooks and #theme_wrappers hooks to share variable names + // without conflict or ambiguity. + $wrapper_elements = $elements; + if (is_string($key)) { + $wrapper_hook = $key; + foreach ($value as $attribute => $override) { + $wrapper_elements[$attribute] = $override; + } + } + else { + $wrapper_hook = $value; + } + + $elements['#children'] = $this->theme->render($wrapper_hook, $wrapper_elements); + } + } + + // Filter the outputted content and make any last changes before the content + // is sent to the browser. The changes are made on $content which allows the + // outputted text to be filtered. + if (isset($elements['#post_render'])) { + foreach ($elements['#post_render'] as $callable) { + if (is_string($callable) && strpos($callable, '::') === FALSE) { + $callable = $this->controllerResolver->getControllerFromDefinition($callable); + } + $elements['#children'] = call_user_func($callable, $elements['#children'], $elements); + } + } + + // We store the resulting output in $elements['#markup'], to be consistent + // with how render cached output gets stored. This ensures that + // #post_render_cache callbacks get the same data to work with, no matter if + // #cache is disabled, #cache is enabled, there is a cache hit or miss. + $prefix = isset($elements['#prefix']) ? SafeMarkup::checkAdminXss($elements['#prefix']) : ''; + $suffix = isset($elements['#suffix']) ? SafeMarkup::checkAdminXss($elements['#suffix']) : ''; + + $elements['#markup'] = $prefix . $elements['#children'] . $suffix; + + // We've rendered this element (and its subtree!), now update the stack. + $update_stack($elements); + + // Cache the processed element if #cache is set. + if (isset($elements['#cache'])) { + drupal_render_cache_set($elements['#markup'], $elements); + } + + // Only when we're in a root (non-recursive) drupal_render() call, + // #post_render_cache callbacks must be executed, to prevent breaking the + // render cache in case of nested elements with #cache set. + // + // By running them here, we ensure that: + // - they run when #cache is disabled, + // - they run when #cache is enabled and there is a cache miss. + // Only the case of a cache hit when #cache is enabled, is not handled here, + // that is handled earlier in Renderer::render(). + if ($is_root_call) { + // We've already called $update_stack() earlier, which updated both the + // element and current stack frame. However, + // Renderer::processPostRenderCache() can both change the element + // further and create and render new child elements, so provide a fresh + // stack frame to collect those additions, merge them back to the element, + // and then update the current frame to match the modified element state. + $stack->push(new RenderStackFrame()); + $this->processPostRenderCache($elements); + $post_render_additions = $stack->pop(); + $elements['#cache']['tags'] = Cache::mergeTags($elements['#cache']['tags'], $post_render_additions->tags); + $elements['#attached'] = drupal_merge_attached($elements['#attached'], $post_render_additions->attached); + $elements['#post_render_cache'] = NestedArray::mergeDeep($elements['#post_render_cache'], $post_render_additions->postRenderCache); + if ($stack->count() !== 1) { + throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.'); + } + } + + // Rendering is finished, all necessary info collected! + $bubble_stack(); + + $elements['#printed'] = TRUE; + $elements['#markup'] = SafeMarkup::set($elements['#markup']); + return $elements['#markup']; + } + + /** + * Processes #post_render_cache callbacks. + * + * #post_render_cache callbacks may modify: + * - #markup: to replace placeholders + * - #attached: to add libraries or JavaScript settings + * + * Note that in either of these cases, #post_render_cache callbacks are + * implicitly idempotent: a placeholder that has been replaced can't be + * replaced again, and duplicate attachments are ignored. + * + * @param array &$elements + * The structured array describing the data being rendered. + * + * @see drupal_render_collect_post_render_cache + */ + protected function processPostRenderCache(array &$elements) { + if (isset($elements['#post_render_cache'])) { + + // Call all #post_render_cache callbacks, passing the provided context. + foreach (array_keys($elements['#post_render_cache']) as $callback) { + if (strpos($callback, '::') === FALSE) { + $callable = $this->controllerResolver->getControllerFromDefinition($callback); + } + else { + $callable = $callback; + } + foreach ($elements['#post_render_cache'][$callback] as $context) { + $elements = call_user_func_array($callable, array($elements, $context)); + } + } + } + } + +} diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php new file mode 100644 index 0000000..a6ec2ed --- /dev/null +++ b/core/lib/Drupal/Core/Render/RendererInterface.php @@ -0,0 +1,244 @@ + 'image', + * '#attributes' => array('class' => array('foo')), + * '#theme_wrappers' => array('container'), + * ); + * @endcode + * and we need to pass the class 'bar' as an attribute for 'container', we + * can rewrite our element thus: + * @code + * array( + * '#theme' => 'image', + * '#attributes' => array('class' => array('foo')), + * '#theme_wrappers' => array( + * 'container' => array( + * '#attributes' => array('class' => array('bar')), + * ), + * ), + * ); + * @endcode + * - If this element has an array of #post_render functions defined, they + * are called sequentially to modify the rendered #children. Unlike + * #pre_render functions, #post_render functions are passed both the + * rendered #children attribute as a string and the element itself. + * - If this element has #prefix and/or #suffix defined, they are + * concatenated to #children. + * - The rendering of this element is now complete. The next step will be + * render caching. So this is the perfect time to update the the stack. At + * this point, children of this element (if any), have been rendered also, + * and if there were any, their bubbleable rendering metadata will have + * been bubbled up into the stack frame for the element that is currently + * being rendered. The render cache item for this element must contain the + * bubbleable rendering metadata for this element and all of its children. + * However, right now, the topmost stack frame (the one for this element) + * currently only contains the metadata for the children. Therefore, the + * topmost stack frame is updated with this element's metadata, and then + * the element's metadata is replaced with the metadata in the topmost + * stack frame. This element now contains all bubbleable rendering + * metadata for this element and all its children, so it's now ready for + * render caching. + * - If this element has #cache defined, the rendered output of this element + * is saved to Renderer::render()'s internal cache. This includes the + * changes made by #post_render. + * - If this element has an array of #post_render_cache functions defined, + * or any of its children has (which we would know thanks to the stack + * having been updated just before the render caching step), they are + * called sequentially to replace placeholders in the final #markup and + * extend #attached. Placeholders must contain a unique token, to + * guarantee that e.g. samples of placeholders are not replaced also. But, + * since #post_render_cache callbacks add attach additional assets, the + * correct bubbling of those must once again be taken into account. This + * final stage of rendering should be considered as if it were the parent + * of the current element, because it takes that as its input, and then + * alters its #markup. Hence, just before calling the #post_render_cache + * callbacks, a new empty frame is pushed onto the stack, where all assets + * #attached during the execution of those callbacks will end up in. Then, + * after the execution of those callbacks, we merge that back into the + * element. Note that these callbacks run always: when hitting the render + * cache, when missing, or when render caching is not used at all. This is + * done to allow any Drupal module to customize other render arrays + * without breaking the render cache if it is enabled, and to not require + * it to use other logic when render caching is disabled. + * - Just before finishing the rendering of this element, this element's + * stack frame (the topmost one) is bubbled: the two topmost frames are + * popped from the stack, they are merged and the result is pushed back + * onto the stack. + * So if this element e.g. was a child element, then a new frame was + * pushed onto the stack element at the beginning of rendering this + * element, it was updated when the rendering was completed, and now we + * merge it with the frame for the parent, so that the parent now has the + * bubbleable rendering metadata for its child. + * - #printed is set to TRUE for this element to ensure that it is only + * rendered once. + * - The final value of #children for this element is returned as the + * rendered output. + * + * @param array $elements + * The structured array describing the data to be rendered. + * @param bool $is_root_call + * (Internal use only.) Whether this is a recursive call or not. See + * ::renderRoot(). + * + * @return string + * The rendered HTML. + * + * @throws \LogicException + * If a root call to ::render() does not result in an empty stack, this + * indicates an erroneous ::render() root call (a root call within a + * root call, which makes no sense). Therefore, a logic exception is thrown. + * @throws \Exception + * If a #pre_render callback throws an exception, it is caught to reset the + * stack used for bubbling rendering metadata, and then the exception is re- + * thrown. + * + * @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo() + * @see \Drupal\Core\Theme\ThemeManagerInterface::render() + * @see drupal_process_states() + * @see drupal_process_attached() + * @see ::renderRoot() + */ + public function render(&$elements, $is_root_call = FALSE); + +} diff --git a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php index 1cf937c..93a31c3 100644 --- a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php +++ b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php @@ -88,6 +88,10 @@ class AccessAwareRouter implements AccessAwareRouterInterface { public function matchRequest(Request $request) { $parameters = $this->chainRouter->matchRequest($request); $request->attributes->add($parameters); + // Trigger a session start and authentication by accessing any property of + // the current user. + // @todo This will be removed in https://www.drupal.org/node/2229145. + $this->account->id(); $this->checkAccess($request); // We can not return $parameters because the access check can change the // request attributes. diff --git a/core/lib/Drupal/Core/Routing/CompiledRoute.php b/core/lib/Drupal/Core/Routing/CompiledRoute.php index 45aa22c..7354e12 100644 --- a/core/lib/Drupal/Core/Routing/CompiledRoute.php +++ b/core/lib/Drupal/Core/Routing/CompiledRoute.php @@ -173,4 +173,30 @@ class CompiledRoute extends SymfonyCompiledRoute { return $this->route->getRequirements(); } + /** + * {@inheritdoc} + */ + public function serialize() { + $data = unserialize(parent::serialize()); + $data['fit'] = $this->fit; + $data['patternOutline'] = $this->patternOutline; + $data['numParts'] = $this->numParts; + + return serialize($data); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + parent::unserialize($serialized); + $data = unserialize($serialized); + + $this->fit = $data['fit']; + $this->patternOutline = $data['patternOutline']; + $this->numParts = $data['numParts']; + } + + } diff --git a/core/lib/Drupal/Core/Session/AccountProxyInterface.php b/core/lib/Drupal/Core/Session/AccountProxyInterface.php index 11eee58..017bd8b 100644 --- a/core/lib/Drupal/Core/Session/AccountProxyInterface.php +++ b/core/lib/Drupal/Core/Session/AccountProxyInterface.php @@ -18,7 +18,11 @@ * Sets the currently wrapped account. * * Setting the current account is highly discouraged! Instead, make sure to - * inject the desired user object into the dependent code directly + * inject the desired user object into the dependent code directly. + * + * A preferable method of account impersonation is to use + * \Drupal\Core\Session\AccountSwitcherInterface::switchTo() and + * \Drupal\Core\Session\AccountSwitcherInterface::switchBack(). * * @param \Drupal\Core\Session\AccountInterface $account * The current account. diff --git a/core/lib/Drupal/Core/Session/AccountSwitcher.php b/core/lib/Drupal/Core/Session/AccountSwitcher.php new file mode 100644 index 0000000..0737258 --- /dev/null +++ b/core/lib/Drupal/Core/Session/AccountSwitcher.php @@ -0,0 +1,96 @@ +currentUser = $current_user; + $this->sessionManager = $session_manager; + } + + /** + * {@inheritdoc} + */ + public function switchTo(AccountInterface $account) { + // Prevent session information from being saved and push previous account. + if (!isset($this->originalSessionSaving)) { + // Ensure that only the first session saving status is saved. + $this->originalSessionSaving = $this->sessionManager->isEnabled(); + } + $this->sessionManager->disable(); + array_push($this->accountStack, $this->currentUser->getAccount()); + $this->currentUser->setAccount($account); + return $this; + } + + /** + * {@inheritdoc} + */ + public function switchBack() { + // Restore the previous account from the stack. + if (!empty($this->accountStack)) { + $this->currentUser->setAccount(array_pop($this->accountStack)); + } + else { + throw new \RuntimeException('No more accounts to revert to.'); + } + // Restore original session saving status if all account switches are + // reverted. + if (empty($this->accountStack)) { + if ($this->originalSessionSaving) { + $this->sessionManager->enable(); + } + } + return $this; + } + +} diff --git a/core/lib/Drupal/Core/Session/AccountSwitcherInterface.php b/core/lib/Drupal/Core/Session/AccountSwitcherInterface.php new file mode 100644 index 0000000..15ee497 --- /dev/null +++ b/core/lib/Drupal/Core/Session/AccountSwitcherInterface.php @@ -0,0 +1,43 @@ +sessionManager->getInsecureName(); $cookies = $this->requestStack->getCurrentRequest()->cookies; - if (!$cookies->has($this->getName()) && !$cookies->has($insecure_session_name)) { + if (empty($sid) || (!$cookies->has($this->getName()) && !$cookies->has($insecure_session_name))) { $user = new UserSession(); return ''; } diff --git a/core/lib/Drupal/Core/Site/Settings.php b/core/lib/Drupal/Core/Site/Settings.php index def307f..4fe80b3 100644 --- a/core/lib/Drupal/Core/Site/Settings.php +++ b/core/lib/Drupal/Core/Site/Settings.php @@ -85,6 +85,8 @@ /** * Bootstraps settings.php and the Settings singleton. * + * @param string $app_root + * The app root. * @param string $site_path * The current site path. * @param \Composer\Autoload\ClassLoader $class_loader @@ -94,7 +96,7 @@ * * @see default.settings.php */ - public static function initialize($site_path, &$class_loader) { + public static function initialize($app_root, $site_path, &$class_loader) { // Export these settings.php variables to the global namespace. global $base_url, $cookie_domain, $config_directories, $config; $settings = array(); @@ -102,8 +104,8 @@ $databases = array(); // Make conf_path() available as local variable in settings.php. - if (is_readable(DRUPAL_ROOT . '/' . $site_path . '/settings.php')) { - require DRUPAL_ROOT . '/' . $site_path . '/settings.php'; + if (is_readable($app_root . '/' . $site_path . '/settings.php')) { + require $app_root . '/' . $site_path . '/settings.php'; } // Initialize Database. diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php index 97341ac..3716277 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -33,14 +33,17 @@ class TwigEnvironment extends \Twig_Environment { /** * Constructs a TwigEnvironment object and stores cache and storage * internally. + * + * @param string $root + * The app root; */ - public function __construct(\Twig_LoaderInterface $loader = NULL, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $options = array()) { + public function __construct($root, \Twig_LoaderInterface $loader = NULL, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $options = array()) { // @todo Pass as arguments from the DIC. $this->cache_object = \Drupal::cache(); // Ensure that twig.engine is loaded, given that it is needed to render a // template because functions like twig_drupal_escape_filter are called. - require_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine'; + require_once $root . '/core/themes/engines/twig/twig.engine'; // Set twig path namespace for themes and modules. $namespaces = array(); diff --git a/core/lib/Drupal/Core/Test/TestRunnerKernel.php b/core/lib/Drupal/Core/Test/TestRunnerKernel.php index 8829113..9fa7430 100644 --- a/core/lib/Drupal/Core/Test/TestRunnerKernel.php +++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php @@ -42,8 +42,8 @@ class TestRunnerKernel extends DrupalKernel { 'simpletest' => 0, ); $this->moduleData = array( - 'system' => new Extension('module', 'core/modules/system/system.info.yml', 'system.module'), - 'simpletest' => new Extension('module', 'core/modules/simpletest/simpletest.info.yml', 'simpletest.module'), + 'system' => new Extension(DRUPAL_ROOT, 'module', 'core/modules/system/system.info.yml', 'system.module'), + 'simpletest' => new Extension(DRUPAL_ROOT, 'module', 'core/modules/simpletest/simpletest.info.yml', 'simpletest.module'), ); } diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 8c9efb3..ff4f678 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -130,8 +130,17 @@ class Registry implements DestructableInterface { protected $themeName; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a \Drupal\Core\\Theme\Registry object. * + * @param string $root + * The app root. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend interface to use for the complete theme registry data. * @param \Drupal\Core\Lock\LockBackendInterface $lock @@ -141,7 +150,8 @@ class Registry implements DestructableInterface { * @param string $theme_name * (optional) The name of the theme for which to construct the registry. */ - public function __construct(CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, $theme_name = NULL) { + public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, $theme_name = NULL) { + $this->root = $root; $this->cache = $cache; $this->lock = $lock; $this->moduleHandler = $module_handler; @@ -180,14 +190,14 @@ class Registry implements DestructableInterface { $ancestor = $themes[$ancestor]->base_theme; $this->baseThemes[] = $themes[$ancestor]; if (!empty($themes[$ancestor]->owner)) { - include_once DRUPAL_ROOT . '/' . $themes[$ancestor]->owner; + include_once $this->root . '/' . $themes[$ancestor]->owner; } } $this->baseThemes = array_reverse($this->baseThemes); if (isset($this->theme->engine)) { $this->engine = $this->theme->engine; - include_once DRUPAL_ROOT . '/' . $this->theme->owner; + include_once $this->root . '/' . $this->theme->owner; if (function_exists($this->theme->engine . '_init')) { foreach ($this->baseThemes as $base) { call_user_func($this->theme->engine . '_init', $base); @@ -444,7 +454,7 @@ class Registry implements DestructableInterface { if (isset($info['file'])) { $include_file = isset($info['path']) ? $info['path'] : $path; $include_file .= '/' . $info['file']; - include_once DRUPAL_ROOT . '/' . $include_file; + include_once $this->root . '/' . $include_file; $result[$hook]['includes'][] = $include_file; } diff --git a/core/lib/Drupal/Core/Theme/ThemeInitialization.php b/core/lib/Drupal/Core/Theme/ThemeInitialization.php index d4e1d95..bbe5cc7 100644 --- a/core/lib/Drupal/Core/Theme/ThemeInitialization.php +++ b/core/lib/Drupal/Core/Theme/ThemeInitialization.php @@ -32,14 +32,24 @@ class ThemeInitialization implements ThemeInitializationInterface { protected $state; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a new ThemeInitialization object. * + * @param string $root + * The app root. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. * @param \Drupal\Core\State\StateInterface $state * The state. */ - public function __construct(ThemeHandlerInterface $theme_handler, StateInterface $state) { + public function __construct($root, ThemeHandlerInterface $theme_handler, StateInterface $state) { + $this->root = $root; $this->themeHandler = $theme_handler; $this->state = $state; } @@ -77,7 +87,7 @@ class ThemeInitialization implements ThemeInitializationInterface { $theme_name = 'core'; // /core/core.info.yml does not actually exist, but is required because // Extension expects a pathname. - $active_theme = $this->getActiveTheme(new Extension('theme', 'core/core.info.yml')); + $active_theme = $this->getActiveTheme(new Extension($this->root, 'theme', 'core/core.info.yml')); // Early-return and do not set state, because the initialized $theme_name // differs from the original $theme_name. @@ -105,7 +115,7 @@ class ThemeInitialization implements ThemeInitializationInterface { // Initialize the theme. if ($theme_engine = $active_theme->getEngine()) { // Include the engine. - include_once DRUPAL_ROOT . '/' . $active_theme->getOwner(); + include_once $this->root . '/' . $active_theme->getOwner(); if (function_exists($theme_engine . '_init')) { foreach ($active_theme->getBaseThemes() as $base) { @@ -119,17 +129,17 @@ class ThemeInitialization implements ThemeInitializationInterface { foreach ($active_theme->getBaseThemes() as $base) { // Include the theme file or the engine. if ($base->getOwner()) { - include_once DRUPAL_ROOT . '/' . $base->getOwner(); + include_once $this->root . '/' . $base->getOwner(); } } // and our theme gets one too. if ($active_theme->getOwner()) { - include_once DRUPAL_ROOT . '/' . $active_theme->getOwner(); + include_once $this->root . '/' . $active_theme->getOwner(); } } // Always include Twig as the default theme engine. - include_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine'; + include_once $this->root . '/core/themes/engines/twig/twig.engine'; } /** diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php index f9e983a..a100374 100644 --- a/core/lib/Drupal/Core/Theme/ThemeManager.php +++ b/core/lib/Drupal/Core/Theme/ThemeManager.php @@ -10,6 +10,9 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\StackedRouteMatchInterface; use Symfony\Component\HttpFoundation\RequestStack; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Template\Attribute; +use Drupal\Component\Utility\SafeMarkup; /** * Provides the default implementation of a theme manager. @@ -48,8 +51,17 @@ class ThemeManager implements ThemeManagerInterface { protected $requestStack; /** + * The app root. + * + * @var string + */ + protected $root; + + /** * Constructs a new ThemeManager object. * + * @param string $root + * The app root. * @param \Drupal\Core\Theme\Registry $theme_registry * The theme registry. * @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator @@ -58,19 +70,22 @@ class ThemeManager implements ThemeManagerInterface { * The theme initialization. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack * The request stack. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ - public function __construct(Registry $theme_registry, ThemeNegotiatorInterface $theme_negotiator, ThemeInitialization $theme_initialization, RequestStack $request_stack) { + public function __construct($root, Registry $theme_registry, ThemeNegotiatorInterface $theme_negotiator, ThemeInitialization $theme_initialization, RequestStack $request_stack, ModuleHandlerInterface $module_handler) { + $this->root = $root; $this->themeNegotiator = $theme_negotiator; $this->themeRegistry = $theme_registry; $this->themeInitialization = $theme_initialization; $this->requestStack = $request_stack; + $this->moduleHandler = $module_handler; } /** * {@inheritdoc} */ public function render($hook, array $variables) { - return _theme($hook, $variables); + return $this->theme($hook, $variables); } /** @@ -110,6 +125,260 @@ class ThemeManager implements ThemeManagerInterface { } /** + * Generates themed output (internal use only). + * + * @see \Drupal\Core\Render\RendererInterface::render(); + */ + protected function theme($hook, $variables = array()) { + static $default_attributes; + + $active_theme = $this->getActiveTheme(); + + // If called before all modules are loaded, we do not necessarily have a full + // theme registry to work with, and therefore cannot process the theme + // request properly. See also \Drupal\Core\Theme\Registry::get(). + if (!$this->moduleHandler->isLoaded() && !defined('MAINTENANCE_MODE')) { + throw new \Exception(t('_theme() may not be called until all modules are loaded.')); + } + + $theme_registry = $this->themeRegistry->getRuntime(); + + // If an array of hook candidates were passed, use the first one that has an + // implementation. + if (is_array($hook)) { + foreach ($hook as $candidate) { + if ($theme_registry->has($candidate)) { + break; + } + } + $hook = $candidate; + } + // Save the original theme hook, so it can be supplied to theme variable + // preprocess callbacks. + $original_hook = $hook; + + // If there's no implementation, check for more generic fallbacks. + // If there's still no implementation, log an error and return an empty + // string. + if (!$theme_registry->has($hook)) { + // Iteratively strip everything after the last '__' delimiter, until an + // implementation is found. + while ($pos = strrpos($hook, '__')) { + $hook = substr($hook, 0, $pos); + if ($theme_registry->has($hook)) { + break; + } + } + if (!$theme_registry->has($hook)) { + // Only log a message when not trying theme suggestions ($hook being an + // array). + if (!isset($candidate)) { + \Drupal::logger('theme')->warning('Theme hook %hook not found.', array('%hook' => $hook)); + } + // There is no theme implementation for the hook passed. Return FALSE so + // the function calling _theme() can differentiate between a hook that + // exists and renders an empty string and a hook that is not + // implemented. + return FALSE; + } + } + + $info = $theme_registry->get($hook); + + // If a renderable array is passed as $variables, then set $variables to + // the arguments expected by the theme function. + if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) { + $element = $variables; + $variables = array(); + if (isset($info['variables'])) { + foreach (array_keys($info['variables']) as $name) { + if (isset($element["#$name"]) || array_key_exists("#$name", $element)) { + $variables[$name] = $element["#$name"]; + } + } + } + else { + $variables[$info['render element']] = $element; + // Give a hint to render engines to prevent infinite recursion. + $variables[$info['render element']]['#render_children'] = TRUE; + } + } + + // Merge in argument defaults. + if (!empty($info['variables'])) { + $variables += $info['variables']; + } + elseif (!empty($info['render element'])) { + $variables += array($info['render element'] => array()); + } + // Supply original caller info. + $variables += array( + 'theme_hook_original' => $original_hook, + ); + + // Set base hook for later use. For example if '#theme' => 'node__article' + // is called, we run hook_theme_suggestions_node_alter() rather than + // hook_theme_suggestions_node__article_alter(), and also pass in the base + // hook as the last parameter to the suggestions alter hooks. + if (isset($info['base hook'])) { + $base_theme_hook = $info['base hook']; + } + else { + $base_theme_hook = $hook; + } + + // Invoke hook_theme_suggestions_HOOK(). + $suggestions = $this->moduleHandler->invokeAll('theme_suggestions_' . $base_theme_hook, array($variables)); + // If _theme() was invoked with a direct theme suggestion like + // '#theme' => 'node__article', add it to the suggestions array before + // invoking suggestion alter hooks. + if (isset($info['base hook'])) { + $suggestions[] = $hook; + } + + // Invoke hook_theme_suggestions_alter() and + // hook_theme_suggestions_HOOK_alter(). + $hooks = array( + 'theme_suggestions', + 'theme_suggestions_' . $base_theme_hook, + ); + $this->moduleHandler->alter($hooks, $suggestions, $variables, $base_theme_hook); + $this->alter($hooks, $suggestions, $variables, $base_theme_hook); + + // Check if each suggestion exists in the theme registry, and if so, + // use it instead of the hook that _theme() was called with. For example, a + // function may call _theme('node', ...), but a module can add + // 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(), + // enabling a theme to have an alternate template file for article nodes. + foreach (array_reverse($suggestions) as $suggestion) { + if ($theme_registry->has($suggestion)) { + $info = $theme_registry->get($suggestion); + break; + } + } + + // Include a file if the theme function or variable preprocessor is held + // elsewhere. + if (!empty($info['includes'])) { + foreach ($info['includes'] as $include_file) { + include_once $this->root . '/' . $include_file; + } + } + + // Invoke the variable preprocessors, if any. + if (isset($info['base hook'])) { + $base_hook = $info['base hook']; + $base_hook_info = $theme_registry->get($base_hook); + // Include files required by the base hook, since its variable + // preprocessors might reside there. + if (!empty($base_hook_info['includes'])) { + foreach ($base_hook_info['includes'] as $include_file) { + include_once $this->root . '/' . $include_file; + } + } + // Replace the preprocess functions with those from the base hook. + if (isset($base_hook_info['preprocess functions'])) { + // Set a variable for the 'theme_hook_suggestion'. This is used to + // maintain backwards compatibility with template engines. + $theme_hook_suggestion = $hook; + $info['preprocess functions'] = $base_hook_info['preprocess functions']; + } + } + if (isset($info['preprocess functions'])) { + foreach ($info['preprocess functions'] as $preprocessor_function) { + if (function_exists($preprocessor_function)) { + $preprocessor_function($variables, $hook, $info); + } + } + // Allow theme preprocess functions to set $variables['#attached'] and use + // it like the #attached property on render arrays. In Drupal 8, this is + // the (only) officially supported method of attaching assets from + // preprocess functions. Assets attached here should be associated with + // the template that we're preprocessing variables for. + if (isset($variables['#attached'])) { + $preprocess_attached = ['#attached' => $variables['#attached']]; + drupal_render($preprocess_attached); + } + } + + // Generate the output using either a function or a template. + $output = ''; + if (isset($info['function'])) { + if (function_exists($info['function'])) { + $output = SafeMarkup::set($info['function']($variables)); + } + } + else { + $render_function = 'twig_render_template'; + $extension = '.html.twig'; + + // The theme engine may use a different extension and a different + // renderer. + $theme_engine = $active_theme->getEngine(); + if (isset($theme_engine)) { + if ($info['type'] != 'module') { + if (function_exists($theme_engine . '_render_template')) { + $render_function = $theme_engine . '_render_template'; + } + $extension_function = $theme_engine . '_extension'; + if (function_exists($extension_function)) { + $extension = $extension_function(); + } + } + } + + // In some cases, a template implementation may not have had + // template_preprocess() run (for example, if the default implementation + // is a function, but a template overrides that default implementation). + // In these cases, a template should still be able to expect to have + // access to the variables provided by template_preprocess(), so we add + // them here if they don't already exist. We don't want the overhead of + // running template_preprocess() twice, so we use the 'directory' variable + // to determine if it has already run, which while not completely + // intuitive, is reasonably safe, and allows us to save on the overhead of + // adding some new variable to track that. + if (!isset($variables['directory'])) { + $default_template_variables = array(); + template_preprocess($default_template_variables, $hook, $info); + $variables += $default_template_variables; + } + if (!isset($default_attributes)) { + $default_attributes = new Attribute(); + } + foreach (array('attributes', 'title_attributes', 'content_attributes') as $key) { + if (isset($variables[$key]) && !($variables[$key] instanceof Attribute)) { + if ($variables[$key]) { + $variables[$key] = new Attribute($variables[$key]); + } + else { + // Create empty attributes. + $variables[$key] = clone $default_attributes; + } + } + } + + // Render the output using the template file. + $template_file = $info['template'] . $extension; + if (isset($info['path'])) { + $template_file = $info['path'] . '/' . $template_file; + } + // Add the theme suggestions to the variables array just before rendering + // the template for backwards compatibility with template engines. + $variables['theme_hook_suggestions'] = $suggestions; + // For backwards compatibility, pass 'theme_hook_suggestion' on to the + // template engine. This is only set when calling a direct suggestion like + // '#theme' => 'menu__shortcut_default' when the template exists in the + // current theme. + if (isset($theme_hook_suggestion)) { + $variables['theme_hook_suggestion'] = $theme_hook_suggestion; + } + $output = $render_function($template_file, $variables); + } + + return (string) $output; + } + + /** * Initializes the active theme for a given route match. * * @param \Drupal\Core\Routing\RouteMatchInterface $route_match diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js index 0707714..7ec93f5 100644 --- a/core/misc/machine-name.js +++ b/core/misc/machine-name.js @@ -29,6 +29,8 @@ attach: function (context, settings) { var self = this; var $context = $(context); + var timeout = null; + var xhr = null; function clickEditHandler(e) { var data = e.data; @@ -47,14 +49,29 @@ var rx = new RegExp(options.replace_pattern, 'g'); var expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength); - if (baseValue.toLowerCase() !== expected) { - self.transliterate(baseValue, options).done(function (machine) { - self.showMachineName(machine.substr(0, options.maxlength), data); - }); + // Abort the last pending request because the label has changed and it + // is no longer valid. + if (xhr && xhr.readystate !== 4) { + xhr.abort(); + xhr = null; } - else { - self.showMachineName(expected, data); + + // Wait 300 milliseconds since the last event to update the machine name + // i.e., after the user has stopped typing. + if (timeout) { + clearTimeout(timeout); + timeout = null; } + timeout = setTimeout(function () { + if (baseValue.toLowerCase() !== expected) { + xhr = self.transliterate(baseValue, options).done(function (machine) { + self.showMachineName(machine.substr(0, options.maxlength), data); + }); + } + else { + self.showMachineName(expected, data); + } + }, 300); } Object.keys(settings.machineName).forEach(function (source_id) { diff --git a/core/modules/action/src/Tests/ActionUninstallTest.php b/core/modules/action/src/Tests/ActionUninstallTest.php index f43bc0c..de8e4e6 100644 --- a/core/modules/action/src/Tests/ActionUninstallTest.php +++ b/core/modules/action/src/Tests/ActionUninstallTest.php @@ -18,7 +18,7 @@ class ActionUninstallTest extends WebTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/action/src/Tests/BulkFormTest.php b/core/modules/action/src/Tests/BulkFormTest.php index 41d3245..8fd634b 100644 --- a/core/modules/action/src/Tests/BulkFormTest.php +++ b/core/modules/action/src/Tests/BulkFormTest.php @@ -19,7 +19,7 @@ class BulkFormTest extends WebTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/action/src/Tests/ConfigurationTest.php b/core/modules/action/src/Tests/ConfigurationTest.php index 42da0a6..7f226dd 100644 --- a/core/modules/action/src/Tests/ConfigurationTest.php +++ b/core/modules/action/src/Tests/ConfigurationTest.php @@ -19,7 +19,7 @@ class ConfigurationTest extends WebTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml index 58d7fcf..3109e9e 100644 --- a/core/modules/aggregator/aggregator.routing.yml +++ b/core/modules/aggregator/aggregator.routing.yml @@ -1,7 +1,7 @@ aggregator.admin_overview: path: '/admin/config/services/aggregator' defaults: - _content: '\Drupal\aggregator\Controller\AggregatorController::adminOverview' + _controller: '\Drupal\aggregator\Controller\AggregatorController::adminOverview' _title: 'Feed aggregator' requirements: _permission: 'administer news feeds' @@ -25,7 +25,7 @@ aggregator.feed_items_delete: aggregator.feed_refresh: path: '/admin/config/services/aggregator/update/{aggregator_feed}' defaults: - _content: '\Drupal\aggregator\Controller\AggregatorController::feedRefresh' + _controller: '\Drupal\aggregator\Controller\AggregatorController::feedRefresh' _title: 'Update items' requirements: _permission: 'administer news feeds' @@ -42,7 +42,7 @@ aggregator.opml_add: aggregator.feed_add: path: '/aggregator/sources/add' defaults: - _content: '\Drupal\aggregator\Controller\AggregatorController::feedAdd' + _controller: '\Drupal\aggregator\Controller\AggregatorController::feedAdd' _title: 'Add feed' requirements: _permission: 'administer news feeds' @@ -80,7 +80,7 @@ entity.aggregator_feed.delete_form: aggregator.page_last: path: '/aggregator' defaults: - _content: '\Drupal\aggregator\Controller\AggregatorController::pageLast' + _controller: '\Drupal\aggregator\Controller\AggregatorController::pageLast' _title: 'Feed aggregator' requirements: _permission: 'access news feeds' diff --git a/core/modules/aggregator/src/Tests/AggregatorConfigurationTest.php b/core/modules/aggregator/src/Tests/AggregatorConfigurationTest.php index 4feb6c6..b791d20 100644 --- a/core/modules/aggregator/src/Tests/AggregatorConfigurationTest.php +++ b/core/modules/aggregator/src/Tests/AggregatorConfigurationTest.php @@ -52,7 +52,7 @@ function testSettingsPage() { $this->assertText(t('The configuration options have been saved.')); $this->assertFieldByName('dummy_length', 100, '"dummy_length" has correct default value.'); - // Make sure settings form is still accessible even after disabling a module + // Make sure settings form is still accessible even after uninstalling a module // that provides the selected plugins. $this->container->get('module_handler')->uninstall(array('aggregator_test')); $this->resetAll(); diff --git a/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php b/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php index 844f0b1..efb3d2f 100644 --- a/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php +++ b/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php @@ -17,7 +17,7 @@ class AggregatorRenderingTest extends AggregatorTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/aggregator/src/Tests/AggregatorTestBase.php b/core/modules/aggregator/src/Tests/AggregatorTestBase.php index 57fcf88..616e1e2 100644 --- a/core/modules/aggregator/src/Tests/AggregatorTestBase.php +++ b/core/modules/aggregator/src/Tests/AggregatorTestBase.php @@ -17,7 +17,7 @@ abstract class AggregatorTestBase extends WebTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/aggregator/src/Tests/DeleteFeedTest.php b/core/modules/aggregator/src/Tests/DeleteFeedTest.php index 90cd5f1..6ec7c50 100644 --- a/core/modules/aggregator/src/Tests/DeleteFeedTest.php +++ b/core/modules/aggregator/src/Tests/DeleteFeedTest.php @@ -15,7 +15,7 @@ class DeleteFeedTest extends AggregatorTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/aggregator/src/Tests/FeedLanguageTest.php b/core/modules/aggregator/src/Tests/FeedLanguageTest.php index 09c608c..c3343d3 100644 --- a/core/modules/aggregator/src/Tests/FeedLanguageTest.php +++ b/core/modules/aggregator/src/Tests/FeedLanguageTest.php @@ -17,7 +17,7 @@ class FeedLanguageTest extends AggregatorTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/aggregator/src/Tests/ImportOpmlTest.php b/core/modules/aggregator/src/Tests/ImportOpmlTest.php index 821a34a..5d0ebe7 100644 --- a/core/modules/aggregator/src/Tests/ImportOpmlTest.php +++ b/core/modules/aggregator/src/Tests/ImportOpmlTest.php @@ -15,7 +15,7 @@ class ImportOpmlTest extends AggregatorTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/aggregator/src/Tests/UpdateFeedItemTest.php b/core/modules/aggregator/src/Tests/UpdateFeedItemTest.php index a744180..b929249 100644 --- a/core/modules/aggregator/src/Tests/UpdateFeedItemTest.php +++ b/core/modules/aggregator/src/Tests/UpdateFeedItemTest.php @@ -64,7 +64,7 @@ function testUpdateFeedItem() { $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(); $this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after))); - // Make sure updating items works even after disabling a module + // Make sure updating items works even after uninstalling a module // that provides the selected plugins. $this->enableTestPlugins(); $this->container->get('module_handler')->uninstall(array('aggregator_test')); diff --git a/core/modules/aggregator/src/Tests/Views/IntegrationTest.php b/core/modules/aggregator/src/Tests/Views/IntegrationTest.php index 2bbb41c..4608bd7 100644 --- a/core/modules/aggregator/src/Tests/Views/IntegrationTest.php +++ b/core/modules/aggregator/src/Tests/Views/IntegrationTest.php @@ -20,7 +20,7 @@ class IntegrationTest extends ViewUnitTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/ban/src/Tests/IpAddressBlockingTest.php b/core/modules/ban/src/Tests/IpAddressBlockingTest.php index 0122ceb..16ec458 100644 --- a/core/modules/ban/src/Tests/IpAddressBlockingTest.php +++ b/core/modules/ban/src/Tests/IpAddressBlockingTest.php @@ -17,7 +17,7 @@ class IpAddressBlockingTest extends WebTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ diff --git a/core/modules/block/block.module b/core/modules/block/block.module index a294dc8..6506ab2 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -301,7 +301,7 @@ function block_configurable_language_delete(ConfigurableLanguageInterface $langu $visibility = $block->getVisibility(); if (isset($visibility['language']['langcodes'][$language->id()])) { unset($visibility['language']['langcodes'][$language->id()]); - $block->getPlugin()->setVisibilityConfig('language', $visibility['language']); + $block->setVisibilityConfig('language', $visibility['language']); $block->save(); } } diff --git a/core/modules/block/block.routing.yml b/core/modules/block/block.routing.yml index d4276f5..2f26a38 100644 --- a/core/modules/block/block.routing.yml +++ b/core/modules/block/block.routing.yml @@ -1,7 +1,7 @@ block.admin_demo: path: '/admin/structure/block/demo/{theme}' defaults: - _content: '\Drupal\block\Controller\BlockController::demo' + _controller: '\Drupal\block\Controller\BlockController::demo' _title_callback: 'theme_handler:getName' requirements: _access_theme: 'TRUE' @@ -28,7 +28,7 @@ entity.block.edit_form: block.admin_display: path: '/admin/structure/block' defaults: - _content: '\Drupal\block\Controller\BlockListController::listing' + _controller: '\Drupal\block\Controller\BlockListController::listing' _title: 'Block layout' requirements: _permission: 'administer blocks' @@ -36,7 +36,7 @@ block.admin_display: block.admin_display_theme: path: 'admin/structure/block/list/{theme}' defaults: - _content: '\Drupal\block\Controller\BlockListController::listing' + _controller: '\Drupal\block\Controller\BlockListController::listing' _title: 'Block layout' requirements: _access_theme: 'TRUE' @@ -45,7 +45,7 @@ block.admin_display_theme: block.admin_add: path: '/admin/structure/block/add/{plugin_id}/{theme}' defaults: - _content: '\Drupal\block\Controller\BlockAddController::blockAddConfigureForm' + _controller: '\Drupal\block\Controller\BlockAddController::blockAddConfigureForm' theme: null _title: 'Configure block' requirements: diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml index 6442712..df4d0d7 100644 --- a/core/modules/block/block.services.yml +++ b/core/modules/block/block.services.yml @@ -24,4 +24,4 @@ services: - { name: 'event_subscriber' } block.repository: class: Drupal\block\BlockRepository - arguments: ['@entity.manager', '@theme.manager'] + arguments: ['@entity.manager', '@theme.manager', '@context.handler'] diff --git a/core/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml index c142f89..1e6b823 100644 --- a/core/modules/block/config/schema/block.schema.yml +++ b/core/modules/block/config/schema/block.schema.yml @@ -24,6 +24,12 @@ block.block.*: label: 'Plugin' settings: type: block.settings.[%parent.plugin] + visibility: + type: sequence + label: 'Visibility Conditions' + sequence: + - type: condition.plugin.[id] + label: 'Visibility Condition' block.settings.*: type: block_settings diff --git a/core/modules/block/js/block.js b/core/modules/block/js/block.js index 3affe12..c25842f 100644 --- a/core/modules/block/js/block.js +++ b/core/modules/block/js/block.js @@ -26,10 +26,10 @@ return vals.join(', '); } - $('#edit-settings-visibility-node-type, #edit-settings-visibility-language, #edit-settings-visibility-user-role').drupalSetSummary(checkboxesSummary); + $('#edit-visibility-node-type, #edit-visibility-language, #edit-visibility-user-role').drupalSetSummary(checkboxesSummary); - $('#edit-settings-visibility-request-path').drupalSetSummary(function (context) { - var $pages = $(context).find('textarea[name="settings[visibility][request_path][pages]"]'); + $('#edit-visibility-request-path').drupalSetSummary(function (context) { + var $pages = $(context).find('textarea[name="visibility[request_path][pages]"]'); if (!$pages.val()) { return Drupal.t('Not restricted'); } diff --git a/core/modules/block/src/BlockAccessControlHandler.php b/core/modules/block/src/BlockAccessControlHandler.php index 8bfa365..6160f84 100644 --- a/core/modules/block/src/BlockAccessControlHandler.php +++ b/core/modules/block/src/BlockAccessControlHandler.php @@ -7,23 +7,75 @@ namespace Drupal\block; +use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Condition\ConditionAccessResolverTrait; use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Executable\ExecutableManagerInterface; +use Drupal\Core\Plugin\Context\ContextHandlerInterface; +use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Core\Session\AccountInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines the access control handler for the block entity type. * * @see \Drupal\block\Entity\Block */ -class BlockAccessControlHandler extends EntityAccessControlHandler { +class BlockAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface { + + use ConditionAccessResolverTrait; + + /** + * The condition plugin manager. + * + * @var \Drupal\Core\Executable\ExecutableManagerInterface + */ + protected $manager; + + /** + * The plugin context handler. + * + * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface + */ + protected $contextHandler; + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('plugin.manager.condition'), + $container->get('context.handler') + ); + } + + /** + * Constructs the block access control handler instance + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager + * The ConditionManager for checking visibility of blocks. + * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler + * The ContextHandler for applying contexts to conditions properly. + */ + public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler) { + parent::__construct($entity_type); + $this->manager = $manager; + $this->contextHandler = $context_handler; + } + /** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { - /** @var $entity \Drupal\block\BlockInterface */ + /** @var \Drupal\block\BlockInterface $entity */ if ($operation != 'view') { return parent::checkAccess($entity, $operation, $langcode, $account); } @@ -33,8 +85,33 @@ class BlockAccessControlHandler extends EntityAccessControlHandler { return AccessResult::forbidden()->cacheUntilEntityChanges($entity); } else { - // Delegate to the plugin. - return $entity->getPlugin()->access($account)->cacheUntilEntityChanges($entity); + $contexts = $entity->getContexts(); + $conditions = []; + foreach ($entity->getVisibilityConditions() as $condition_id => $condition) { + if ($condition instanceof ContextAwarePluginInterface) { + try { + $this->contextHandler->applyContextMapping($condition, $contexts); + } + catch (ContextException $e) { + return AccessResult::forbidden()->setCacheable(FALSE); + } + } + $conditions[$condition_id] = $condition; + } + if ($this->resolveConditions($conditions, 'and') !== FALSE) { + // Delegate to the plugin. + $access = $entity->getPlugin()->access($account); + } + else { + $access = AccessResult::forbidden(); + } + // This should not be hardcoded to an uncacheable access check result, but + // in order to fix that, we need condition plugins to return cache contexts, + // otherwise it will be impossible to determine by which cache contexts the + // result should be varied. + // @todo Change this to use $access->cacheUntilEntityChanges($entity) once + // https://www.drupal.org/node/2375695 is resolved. + return $access->setCacheable(FALSE); } } diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php index 73e7ea4..0a5c8de 100644 --- a/core/modules/block/src/BlockForm.php +++ b/core/modules/block/src/BlockForm.php @@ -7,11 +7,17 @@ namespace Drupal\block; +use Drupal\block\Event\BlockContextEvent; +use Drupal\block\Event\BlockEvents; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Executable\ExecutableManagerInterface; use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Plugin\ContextAwarePluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Provides form for block instance forms. @@ -33,13 +39,43 @@ class BlockForm extends EntityForm { protected $storage; /** + * The condition plugin manager. + * + * @var \Drupal\Core\Condition\ConditionManager + */ + protected $manager; + + /** + * The event dispatcher service. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $dispatcher; + + /** + * The language manager service. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $language; + + /** * Constructs a BlockForm object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager + * The ConditionManager for building the visibility UI. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * The EventDispatcher for gathering administrative contexts. + * @param \Drupal\Core\Language\LanguageManagerInterface $language + * The language manager. */ - public function __construct(EntityManagerInterface $entity_manager) { + public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, EventDispatcherInterface $dispatcher, LanguageManagerInterface $language) { $this->storage = $entity_manager->getStorage('block'); + $this->manager = $manager; + $this->dispatcher = $dispatcher; + $this->language = $language; } /** @@ -47,7 +83,10 @@ class BlockForm extends EntityForm { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager') + $container->get('entity.manager'), + $container->get('plugin.manager.condition'), + $container->get('event_dispatcher'), + $container->get('language_manager') ); } @@ -63,8 +102,13 @@ class BlockForm extends EntityForm { } $form_state->set('block_theme', $theme); + // Store the gathered contexts in the form state for other objects to use + // during form building. + $form_state->setTemporaryValue('gathered_contexts', $this->dispatcher->dispatch(BlockEvents::ADMINISTRATIVE_CONTEXT, new BlockContextEvent())->getContexts()); + $form['#tree'] = TRUE; $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state); + $form['visibility'] = $this->buildVisibilityInterface([], $form_state); // If creating a new block, calculate a safe default machine name. $form['id'] = array( @@ -133,6 +177,79 @@ class BlockForm extends EntityForm { } /** + * Helper function for building the visibility UI form. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form array with the visibility UI added in. + */ + protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) { + $form['visibility_tabs'] = [ + '#type' => 'vertical_tabs', + '#title' => $this->t('Visibility'), + '#parents' => ['visibility_tabs'], + '#attached' => [ + 'library' => [ + 'block/drupal.block', + ], + ], + ]; + // @todo Allow list of conditions to be configured in + // https://drupal.org/node/2284687. + $visibility = $this->entity->getVisibility(); + foreach ($this->manager->getDefinitions() as $condition_id => $definition) { + // Don't display the current theme condition. + if ($condition_id == 'current_theme') { + continue; + } + // Don't display the language condition until we have multiple languages. + if ($condition_id == 'language' && !$this->language->isMultilingual()) { + continue; + } + /** @var \Drupal\Core\Condition\ConditionInterface $condition */ + $condition = $this->manager->createInstance($condition_id, isset($visibility[$condition_id]) ? $visibility[$condition_id] : []); + $form_state->set(['conditions', $condition_id], $condition); + $condition_form = $condition->buildConfigurationForm([], $form_state); + $condition_form['#type'] = 'details'; + $condition_form['#title'] = $condition->getPluginDefinition()['label']; + $condition_form['#group'] = 'visibility_tabs'; + $form[$condition_id] = $condition_form; + } + + if (isset($form['node_type'])) { + $form['node_type']['#title'] = $this->t('Content types'); + $form['node_type']['bundles']['#title'] = $this->t('Content types'); + $form['node_type']['negate']['#type'] = 'value'; + $form['node_type']['negate']['#title_display'] = 'invisible'; + $form['node_type']['negate']['#value'] = $form['node_type']['negate']['#default_value']; + } + if (isset($form['user_role'])) { + $form['user_role']['#title'] = $this->t('Roles'); + unset($form['user_role']['roles']['#description']); + $form['user_role']['negate']['#type'] = 'value'; + $form['user_role']['negate']['#value'] = $form['user_role']['negate']['#default_value']; + } + if (isset($form['request_path'])) { + $form['request_path']['#title'] = $this->t('Pages'); + $form['request_path']['negate']['#type'] = 'radios'; + $form['request_path']['negate']['#title_display'] = 'invisible'; + $form['request_path']['negate']['#options'] = [ + $this->t('Show for the listed pages'), + $this->t('Hide for the listed pages'), + ]; + } + if (isset($form['language'])) { + $form['language']['negate']['#type'] = 'value'; + $form['language']['negate']['#value'] = $form['language']['negate']['#default_value']; + } + return $form; + } + + /** * {@inheritdoc} */ protected function actions(array $form, FormStateInterface $form_state) { @@ -154,6 +271,28 @@ class BlockForm extends EntityForm { $this->entity->getPlugin()->validateConfigurationForm($form, $settings); // Update the original form values. $form_state->setValue('settings', $settings->getValues()); + $this->validateVisibility($form, $form_state); + } + + /** + * Helper function to independently validate the visibility UI. + * + * @param array $form + * A nested array form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function validateVisibility(array $form, FormStateInterface $form_state) { + // Validate visibility condition settings. + foreach ($form_state->getValue('visibility') as $condition_id => $values) { + // Allow the condition to validate the form. + $condition = $form_state->get(['conditions', $condition_id]); + $condition_values = (new FormState()) + ->setValues($values); + $condition->validateConfigurationForm($form, $condition_values); + // Update the original form values. + $form_state->setValue(['visibility', $condition_id], $condition_values->getValues()); + } } /** @@ -173,6 +312,24 @@ class BlockForm extends EntityForm { // Update the original form values. $form_state->setValue('settings', $settings->getValues()); + // Submit visibility condition settings. + foreach ($form_state->getValue('visibility') as $condition_id => $values) { + // Allow the condition to submit the form. + $condition = $form_state->get(['conditions', $condition_id]); + $condition_values = (new FormState()) + ->setValues($values); + $condition->submitConfigurationForm($form, $condition_values); + if ($condition instanceof ContextAwarePluginInterface) { + $context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : []; + $condition->setContextMapping($context_mapping); + } + // Update the original form values. + $condition_configuration = $condition->getConfiguration(); + $form_state->setValue(['visibility', $condition_id], $condition_configuration); + // Update the visibility conditions on the block. + $entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration); + } + // Save the settings of the plugin. $entity->save(); diff --git a/core/modules/block/src/BlockInterface.php b/core/modules/block/src/BlockInterface.php index 432e799..d5dd321 100644 --- a/core/modules/block/src/BlockInterface.php +++ b/core/modules/block/src/BlockInterface.php @@ -40,4 +40,53 @@ */ public function getVisibility(); + /** + * Gets conditions for this block. + * + * @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginCollection + * An array or collection of configured condition plugins. + */ + public function getVisibilityConditions(); + + /** + * Gets a visibility condition plugin instance. + * + * @param string $instance_id + * The condition plugin instance ID. + * + * @return \Drupal\Core\Condition\ConditionInterface + * A condition plugin. + */ + public function getVisibilityCondition($instance_id); + + /** + * Sets the visibility condition configuration. + * + * @param string $instance_id + * The condition instance ID. + * @param array $configuration + * The condition configuration. + * + * @return $this + */ + public function setVisibilityConfig($instance_id, array $configuration); + + /** + * Get all available contexts. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of set contexts, keyed by context name. + */ + public function getContexts(); + + /** + * Set the contexts that are available for use within the block entity. + * + * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts + * An array of contexts to set on the block. + * + * @return $this + */ + public function setContexts(array $contexts); + } diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php index 7e17105..c11a4fe 100644 --- a/core/modules/block/src/BlockRepository.php +++ b/core/modules/block/src/BlockRepository.php @@ -8,6 +8,7 @@ namespace Drupal\block; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Theme\ThemeManagerInterface; /** @@ -36,10 +37,13 @@ class BlockRepository implements BlockRepositoryInterface { * The entity manager. * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager * The theme manager. + * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler + * The plugin context handler. */ - public function __construct(EntityManagerInterface $entity_manager, ThemeManagerInterface $theme_manager) { + public function __construct(EntityManagerInterface $entity_manager, ThemeManagerInterface $theme_manager, ContextHandlerInterface $context_handler) { $this->blockStorage = $entity_manager->getStorage('block'); $this->themeManager = $theme_manager; + $this->contextHandler = $context_handler; } /** @@ -65,13 +69,15 @@ class BlockRepository implements BlockRepositoryInterface { /** * {@inheritdoc} */ - public function getVisibleBlocksPerRegion() { + public function getVisibleBlocksPerRegion(array $contexts) { // Build an array of the region names in the right order. $empty = array_fill_keys(array_keys($this->getRegionNames()), array()); $full = array(); foreach ($this->blockStorage->loadByProperties(array('theme' => $this->getTheme())) as $block_id => $block) { - if ($block->access('view')) { + /** @var \Drupal\block\BlockInterface $block */ + // Set the contexts on the block before checking access. + if ($block->setContexts($contexts)->access('view')) { $full[$block->get('region')][$block_id] = $block; } } diff --git a/core/modules/block/src/BlockRepositoryInterface.php b/core/modules/block/src/BlockRepositoryInterface.php index 19cfb5a..082456f 100644 --- a/core/modules/block/src/BlockRepositoryInterface.php +++ b/core/modules/block/src/BlockRepositoryInterface.php @@ -12,10 +12,13 @@ /** * Returns an array of regions and their block entities. * + * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts + * An array of contexts to set on the blocks. + * * @return array * The array is first keyed by region machine name, with the values * containing an array keyed by block ID, with block entities as the values. */ - public function getVisibleBlocksPerRegion(); + public function getVisibleBlocksPerRegion(array $contexts); } diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php index 6f7f276..0edc961 100644 --- a/core/modules/block/src/Entity/Block.php +++ b/core/modules/block/src/Entity/Block.php @@ -8,6 +8,7 @@ namespace Drupal\block\Entity; use Drupal\Core\Cache\Cache; +use Drupal\Core\Condition\ConditionPluginCollection; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\block\BlockPluginCollection; use Drupal\block\BlockInterface; @@ -78,6 +79,13 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin protected $plugin; /** + * The visibility settings for this block. + * + * @var array + */ + protected $visibility = []; + + /** * The plugin collection that holds the block plugin for this entity. * * @var \Drupal\block\BlockPluginCollection @@ -85,6 +93,27 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin protected $pluginCollection; /** + * The available contexts for this block and its visibility conditions. + * + * @var array + */ + protected $contexts = []; + + /** + * The visibility collection. + * + * @var \Drupal\Core\Condition\ConditionPluginCollection + */ + protected $visibilityCollection; + + /** + * The condition plugin manager. + * + * @var \Drupal\Core\Executable\ExecutableManagerInterface + */ + protected $conditionPluginManager; + + /** * {@inheritdoc} */ public function getPlugin() { @@ -108,7 +137,10 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin * {@inheritdoc} */ public function getPluginCollections() { - return array('settings' => $this->getPluginCollection()); + return [ + 'settings' => $this->getPluginCollection(), + 'visibility' => $this->getVisibilityConditions(), + ]; } /** @@ -186,8 +218,69 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin /** * {@inheritdoc} */ + public function setContexts(array $contexts) { + $this->contexts = $contexts; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContexts() { + return $this->contexts; + } + + /** + * {@inheritdoc} + */ public function getVisibility() { - return $this->getPlugin()->getVisibilityConditions()->getConfiguration(); + return $this->getVisibilityConditions()->getConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function setVisibilityConfig($instance_id, array $configuration) { + $conditions = $this->getVisibilityConditions(); + if (!$conditions->has($instance_id)) { + $configuration['id'] = $instance_id; + $conditions->addInstanceId($instance_id, $configuration); + } + else { + $conditions->setInstanceConfiguration($instance_id, $configuration); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function getVisibilityConditions() { + if (!isset($this->visibilityCollection)) { + $this->visibilityCollection = new ConditionPluginCollection($this->conditionPluginManager(), $this->get('visibility')); + } + return $this->visibilityCollection; + } + + /** + * {@inheritdoc} + */ + public function getVisibilityCondition($instance_id) { + return $this->getVisibilityConditions()->get($instance_id); + } + + /** + * Gets the condition plugin manager. + * + * @return \Drupal\Core\Executable\ExecutableManagerInterface + * The condition plugin manager. + */ + protected function conditionPluginManager() { + $this->conditionPluginManager; + if (!isset($this->conditionPluginManager)) { + $this->conditionPluginManager = \Drupal::service('plugin.manager.condition'); + } + return $this->conditionPluginManager; } } diff --git a/core/modules/block/src/Event/BlockConditionContextEvent.php b/core/modules/block/src/Event/BlockConditionContextEvent.php deleted file mode 100644 index af155b7..0000000 --- a/core/modules/block/src/Event/BlockConditionContextEvent.php +++ /dev/null @@ -1,39 +0,0 @@ -conditions = $conditions; - } - - /** - * @return \Drupal\Core\Block\BlockPluginInterface - */ - public function getConditions() { - return $this->conditions; - } - -} diff --git a/core/modules/block/src/Event/BlockContextEvent.php b/core/modules/block/src/Event/BlockContextEvent.php new file mode 100644 index 0000000..99b0bbd --- /dev/null +++ b/core/modules/block/src/Event/BlockContextEvent.php @@ -0,0 +1,53 @@ +contexts[$name] = $context; + return $this; + } + + /** + * Returns the context objects. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of contexts that have been provided. + */ + public function getContexts() { + return $this->contexts; + } + +} diff --git a/core/modules/block/src/Event/BlockEvents.php b/core/modules/block/src/Event/BlockEvents.php index 8d71c07..22d3e90 100644 --- a/core/modules/block/src/Event/BlockEvents.php +++ b/core/modules/block/src/Event/BlockEvents.php @@ -16,8 +16,16 @@ * Name of the event when gathering condition context for a block plugin. * * @see \Drupal\Core\Block\BlockBase::getConditionContexts() - * @see \Drupal\block\Event\BlockConditionContextEvent + * @see \Drupal\block\Event\BlockContextEvent */ - const CONDITION_CONTEXT = 'block.condition_context'; + const ACTIVE_CONTEXT = 'block.active_context'; + + /** + * Name of the event when gathering contexts for plugin configuration. + * + * @see \Drupal\block\BlockForm::form() + * @see \Drupal\block\Event\BlockContextEvent + */ + const ADMINISTRATIVE_CONTEXT = 'block.administrative_context'; } diff --git a/core/modules/block/src/EventSubscriber/BlockConditionContextSubscriberBase.php b/core/modules/block/src/EventSubscriber/BlockConditionContextSubscriberBase.php deleted file mode 100644 index 9fc2e87..0000000 --- a/core/modules/block/src/EventSubscriber/BlockConditionContextSubscriberBase.php +++ /dev/null @@ -1,61 +0,0 @@ -conditions = $event->getConditions(); - $this->determineBlockContext(); - } - - /** - * Determines the contexts for a given block. - */ - abstract protected function determineBlockContext(); - - /** - * Sets the condition context for a given name. - * - * @param string $name - * The name of the context. - * @param \Drupal\Component\Plugin\Context\ContextInterface $context - * The context to add. - * - * @return $this - */ - public function addContext($name, ContextInterface $context) { - $this->conditions->addContext($name, $context); - return $this; - } - -} diff --git a/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php b/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php new file mode 100644 index 0000000..9707edc --- /dev/null +++ b/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php @@ -0,0 +1,75 @@ +setContextValue($node); + * $event->setContext('node', $context); + * @endcode + * + * @param \Drupal\block\Event\BlockContextEvent $event + * The Event to which to register available contexts. + */ + abstract public function onBlockActiveContext(BlockContextEvent $event); + + /** + * Determines the available configuration-time contexts. + * + * When a block is being configured, the configuration UI must know which + * named contexts are potentially available, but does not care about the + * value, since the value can be different for each request, and might not + * be available at all during the configuration UI's request. + * + * For example: + * @code + * // During configuration, there is no specific node to pass as context. + * // However, inform the system that a context named 'node' is available, + * // and provide its definition, so that blocks can be configured to use + * // it. When the block is rendered, the value of this context will be + * // supplied by onBlockActiveContext(). + * $context = new Context(new ContextDefinition('entity:node')); + * $event->setContext('node', $context); + * @endcode + * + * @param \Drupal\block\Event\BlockContextEvent $event + * The Event to which to register available contexts. + * + * @see static::onBlockActiveContext() + */ + abstract public function onBlockAdministrativeContext(BlockContextEvent $event); + +} diff --git a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php index 4584172..023f7cc 100644 --- a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php +++ b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php @@ -7,6 +7,7 @@ namespace Drupal\block\EventSubscriber; +use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; @@ -15,7 +16,7 @@ /** * Sets the current language as a context. */ -class CurrentLanguageContext extends BlockConditionContextSubscriberBase { +class CurrentLanguageContext extends BlockContextSubscriberBase { use StringTranslationTrait; @@ -39,10 +40,24 @@ class CurrentLanguageContext extends BlockConditionContextSubscriberBase { /** * {@inheritdoc} */ - protected function determineBlockContext() { - $context = new Context(new ContextDefinition('language', $this->t('Current language'))); - $context->setContextValue($this->languageManager->getCurrentLanguage()); - $this->addContext('language', $context); + public function onBlockActiveContext(BlockContextEvent $event) { + // Add a context for each language type. + $language_types = $this->languageManager->getLanguageTypes(); + $info = $this->languageManager->getDefinedLanguageTypesInfo(); + foreach ($language_types as $type_key) { + if (isset($info[$type_key]['name'])) { + $context = new Context(new ContextDefinition('language', $info[$type_key]['name'])); + $context->setContextValue($this->languageManager->getCurrentLanguage($type_key)); + $event->setContext('language.' . $type_key, $context); + } + } + } + + /** + * {@inheritdoc} + */ + public function onBlockAdministrativeContext(BlockContextEvent $event) { + $this->onBlockActiveContext($event); } } diff --git a/core/modules/block/src/EventSubscriber/CurrentUserContext.php b/core/modules/block/src/EventSubscriber/CurrentUserContext.php index 2a5a19e..cb70f3d 100644 --- a/core/modules/block/src/EventSubscriber/CurrentUserContext.php +++ b/core/modules/block/src/EventSubscriber/CurrentUserContext.php @@ -7,6 +7,7 @@ namespace Drupal\block\EventSubscriber; +use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; @@ -16,7 +17,7 @@ /** * Sets the current user as a context. */ -class CurrentUserContext extends BlockConditionContextSubscriberBase { +class CurrentUserContext extends BlockContextSubscriberBase { use StringTranslationTrait; @@ -50,12 +51,19 @@ class CurrentUserContext extends BlockConditionContextSubscriberBase { /** * {@inheritdoc} */ - protected function determineBlockContext() { + public function onBlockActiveContext(BlockContextEvent $event) { $current_user = $this->userStorage->load($this->account->id()); $context = new Context(new ContextDefinition('entity:user', $this->t('Current user'))); $context->setContextValue($current_user); - $this->addContext('current_user', $context); + $event->setContext('user.current_user', $context); + } + + /** + * {@inheritdoc} + */ + public function onBlockAdministrativeContext(BlockContextEvent $event) { + $this->onBlockActiveContext($event); } } diff --git a/core/modules/block/src/EventSubscriber/NodeRouteContext.php b/core/modules/block/src/EventSubscriber/NodeRouteContext.php index c1bd2bf..04eb339 100644 --- a/core/modules/block/src/EventSubscriber/NodeRouteContext.php +++ b/core/modules/block/src/EventSubscriber/NodeRouteContext.php @@ -7,6 +7,7 @@ namespace Drupal\block\EventSubscriber; +use Drupal\block\Event\BlockContextEvent; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Routing\RouteMatchInterface; @@ -15,7 +16,7 @@ /** * Sets the current node as a context on node routes. */ -class NodeRouteContext extends BlockConditionContextSubscriberBase { +class NodeRouteContext extends BlockContextSubscriberBase { /** * The route match object. @@ -37,20 +38,28 @@ class NodeRouteContext extends BlockConditionContextSubscriberBase { /** * {@inheritdoc} */ - protected function determineBlockContext() { + public function onBlockActiveContext(BlockContextEvent $event) { if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) { $context = new Context(new ContextDefinition($route_contexts['node']['type'])); if ($node = $this->routeMatch->getParameter('node')) { $context->setContextValue($node); } - $this->addContext('node', $context); + $event->setContext('node.node', $context); } elseif ($this->routeMatch->getRouteName() == 'node.add') { $node_type = $this->routeMatch->getParameter('node_type'); $context = new Context(new ContextDefinition('entity:node')); $context->setContextValue(Node::create(array('type' => $node_type->id()))); - $this->addContext('node', $context); + $event->setContext('node.node', $context); } } + /** + * {@inheritdoc} + */ + public function onBlockAdministrativeContext(BlockContextEvent $event) { + $context = new Context(new ContextDefinition('entity:node')); + $event->setContext('node', $context); + } + } diff --git a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php index fe78cb9..895e2c6 100644 --- a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php +++ b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php @@ -8,12 +8,15 @@ namespace Drupal\block\Plugin\DisplayVariant; use Drupal\block\BlockRepositoryInterface; +use Drupal\block\Event\BlockContextEvent; +use Drupal\block\Event\BlockEvents; use Drupal\Core\Block\MainContentBlockPluginInterface; use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Display\VariantBase; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Provides a page display variant that decorates the main content with blocks. @@ -66,11 +69,14 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont * The block repository. * @param \Drupal\Core\Entity\EntityViewBuilderInterface $block_view_builder * The block view builder. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * The event dispatcher. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->blockRepository = $block_repository; $this->blockViewBuilder = $block_view_builder; + $this->dispatcher = $dispatcher; } /** @@ -82,7 +88,8 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont $plugin_id, $plugin_definition, $container->get('block.repository'), - $container->get('entity.manager')->getViewBuilder('block') + $container->get('entity.manager')->getViewBuilder('block'), + $container->get('event_dispatcher') ); } @@ -102,8 +109,9 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont $main_content_block_displayed = FALSE; $build = array(); + $contexts = $this->getActiveBlockContexts(); // Load all region content assigned via blocks. - foreach ($this->blockRepository->getVisibleBlocksPerRegion() as $region => $blocks) { + foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $blocks) { /** @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $key => $block) { $block_plugin = $block->getPlugin(); @@ -130,4 +138,14 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont return $build; } + /** + * Returns an array of context objects to set on the blocks. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of contexts to set on the blocks. + */ + protected function getActiveBlockContexts() { + return $this->dispatcher->dispatch(BlockEvents::ACTIVE_CONTEXT, new BlockContextEvent())->getContexts(); + } + } diff --git a/core/modules/block/src/Tests/BlockInterfaceTest.php b/core/modules/block/src/Tests/BlockInterfaceTest.php index 7361f96..3c7ecb0 100644 --- a/core/modules/block/src/Tests/BlockInterfaceTest.php +++ b/core/modules/block/src/Tests/BlockInterfaceTest.php @@ -39,7 +39,6 @@ class BlockInterfaceTest extends DrupalUnitTestBase { 'label' => 'Custom Display Message', ); $expected_configuration = array( - 'visibility' => array(), 'id' => 'test_block_instantiation', 'label' => 'Custom Display Message', 'provider' => 'block_test', diff --git a/core/modules/block/src/Tests/BlockLanguageTest.php b/core/modules/block/src/Tests/BlockLanguageTest.php index 380c65c..6f22e68 100644 --- a/core/modules/block/src/Tests/BlockLanguageTest.php +++ b/core/modules/block/src/Tests/BlockLanguageTest.php @@ -28,7 +28,7 @@ class BlockLanguageTest extends WebTestBase { * * @var array */ - public static $modules = array('language', 'block'); + public static $modules = array('language', 'block', 'content_translation'); protected function setUp() { parent::setUp(); @@ -53,11 +53,12 @@ class BlockLanguageTest extends WebTestBase { $default_theme = \Drupal::config('system.theme')->get('default'); $this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme); - $this->assertField('settings[visibility][language][langcodes][en]', 'Language visibility field is visible.'); + $this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.'); + $this->assertNoField('visibility[language][context_mapping][language]', 'Language type field is not visible.'); // Enable a standard block and set the visibility setting for one language. $edit = array( - 'settings[visibility][language][langcodes][en]' => TRUE, + 'visibility[language][langcodes][en]' => TRUE, 'id' => strtolower($this->randomMachineName(8)), 'region' => 'sidebar_first', ); @@ -86,7 +87,6 @@ class BlockLanguageTest extends WebTestBase { $edit = array( 'visibility' => array( 'language' => array( - 'language_type' => 'language_interface', 'langcodes' => array( 'fr' => 'fr', ), @@ -97,8 +97,7 @@ class BlockLanguageTest extends WebTestBase { // Check that we have the language in config after saving the setting. $visibility = $block->getVisibility(); - $language = $visibility['language']['langcodes']['fr']; - $this->assertTrue('fr' === $language, 'Language is set in the block configuration.'); + $this->assertEqual('fr', $visibility['language']['langcodes']['fr'], 'Language is set in the block configuration.'); // Delete the language. $this->drupalPostForm('admin/config/regional/language/delete/fr', array(), t('Delete')); @@ -108,6 +107,73 @@ class BlockLanguageTest extends WebTestBase { $block = Block::load($block->id()); $visibility = $block->getVisibility(); $this->assertTrue(empty($visibility['language']['langcodes']['fr']), 'Language is no longer not set in the block configuration after deleting the block.'); + + // Ensure that the block visibility for language is gone from the UI. + $this->drupalGet('admin/structure/block'); + $this->clickLink('Configure'); + $elements = $this->xpath('//details[@id="edit-visibility-language"]'); + $this->assertTrue(empty($elements)); + } + + /** + * Tests block language visibility with different language types. + */ + public function testMultipleLanguageTypes() { + // Customize content language settings from their defaults. + $edit = [ + 'language_content[configurable]' => TRUE, + 'language_interface[enabled][language-url]' => FALSE, + 'language_interface[enabled][language-session]' => TRUE, + ]; + $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + + // Check if the visibility setting is available with a type setting. + $default_theme = \Drupal::config('system.theme')->get('default'); + $this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme); + $this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.'); + $this->assertField('visibility[language][context_mapping][language]', 'Language type field is visible.'); + + // Enable a standard block and set visibility to French only. + $block_id = strtolower($this->randomMachineName(8)); + $edit = [ + 'visibility[language][context_mapping][language]' => 'language.language_interface', + 'visibility[language][langcodes][fr]' => TRUE, + 'id' => $block_id, + 'region' => 'sidebar_first', + ]; + $this->drupalPostForm('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block')); + + // Interface negotiation depends on request arguments. + $this->drupalGet('node', ['query' => ['language' => 'en']]); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('node', ['query' => ['language' => 'fr']]); + $this->assertText('Powered by Drupal', 'The body of the block appears on the page.'); + + // Content language does not depend on session/request arguments. + // It will fall back on English (site default) and not display the block. + $this->drupalGet('en'); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('fr'); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + + // Change visibility to now depend on content language for this block. + $edit = [ + 'visibility[language][context_mapping][language]' => 'language.language_content' + ]; + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, $edit, t('Save block')); + + // Content language negotiation does not depend on request arguments. + // It will fall back on English (site default) and not display the block. + $this->drupalGet('node', ['query' => ['language' => 'en']]); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('node', ['query' => ['language' => 'fr']]); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + + // Content language negotiation depends on path prefix. + $this->drupalGet('en'); + $this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.'); + $this->drupalGet('fr'); + $this->assertText('Powered by Drupal', 'The body of the block appears on the page.'); } } diff --git a/core/modules/block/src/Tests/BlockStorageUnitTest.php b/core/modules/block/src/Tests/BlockStorageUnitTest.php index 5fb4e8b..6d0c928 100644 --- a/core/modules/block/src/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/src/Tests/BlockStorageUnitTest.php @@ -26,7 +26,7 @@ class BlockStorageUnitTest extends DrupalUnitTestBase { * * @var array */ - public static $modules = array('block', 'block_test', 'system'); + public static $modules = array('block', 'block_test'); /** * The block storage. @@ -94,7 +94,6 @@ class BlockStorageUnitTest extends DrupalUnitTestBase { 'provider' => NULL, 'plugin' => 'test_html', 'settings' => array( - 'visibility' => array(), 'id' => 'test_html', 'label' => '', 'provider' => 'block_test', @@ -104,6 +103,7 @@ class BlockStorageUnitTest extends DrupalUnitTestBase { 'contexts' => array(), ), ), + 'visibility' => array(), ); $this->assertIdentical($actual_properties, $expected_properties); diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php index 881cd2b..3d9a348 100644 --- a/core/modules/block/src/Tests/BlockTest.php +++ b/core/modules/block/src/Tests/BlockTest.php @@ -35,9 +35,9 @@ function testBlockVisibility() { ); // Set the block to be hidden on any user path, and to be shown only to // authenticated users. - $edit['settings[visibility][request_path][pages]'] = 'user*'; - $edit['settings[visibility][request_path][negate]'] = TRUE; - $edit['settings[visibility][user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; + $edit['visibility[request_path][pages]'] = 'user*'; + $edit['visibility[request_path][negate]'] = TRUE; + $edit['visibility[user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block')); $this->assertText('The block configuration has been saved.', 'Block was saved'); @@ -58,6 +58,42 @@ function testBlockVisibility() { } /** + * Tests that visibility can be properly toggled. + */ + public function testBlockToggleVisibility() { + $block_name = 'system_powered_by_block'; + // Create a random title for the block. + $title = $this->randomMachineName(8); + // Enable a standard block. + $default_theme = \Drupal::config('system.theme')->get('default'); + $edit = array( + 'id' => strtolower($this->randomMachineName(8)), + 'region' => 'sidebar_first', + 'settings[label]' => $title, + ); + $block_id = $edit['id']; + // Set the block to be shown only to authenticated users. + $edit['visibility[user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; + $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block')); + $this->clickLink('Configure'); + $this->assertFieldChecked('edit-visibility-user-role-roles-authenticated'); + + $edit = [ + 'visibility[user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']' => FALSE, + ]; + $this->drupalPostForm(NULL, $edit, 'Save block'); + $this->clickLink('Configure'); + $this->assertNoFieldChecked('edit-visibility-user-role-roles-authenticated'); + + // Ensure that no visibility is configured. + /** @var \Drupal\block\BlockInterface $block */ + $block = Block::load($block_id); + $visibility_config = $block->getVisibilityConditions()->getConfiguration(); + $this->assertIdentical([], $visibility_config); + $this->assertIdentical([], $block->get('visibility')); + } + + /** * Test block visibility when leaving "pages" textarea empty. */ function testBlockVisibilityListedEmpty() { @@ -70,7 +106,7 @@ function testBlockVisibilityListedEmpty() { 'id' => strtolower($this->randomMachineName(8)), 'region' => 'sidebar_first', 'settings[label]' => $title, - 'settings[visibility][request_path][negate]' => TRUE, + 'visibility[request_path][negate]' => TRUE, ); // Set the block to be hidden on any user path, and to be shown only to // authenticated users. diff --git a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml index 72510d0..f52790f 100644 --- a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml +++ b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml @@ -6,7 +6,6 @@ langcode: en region: '-1' plugin: test_html settings: - visibility: { } label: 'Test HTML block' provider: block_test label_display: 'hidden' @@ -15,3 +14,4 @@ dependencies: - block_test theme: - stark +visibility: { } diff --git a/core/modules/block/tests/src/Unit/BlockFormTest.php b/core/modules/block/tests/src/Unit/BlockFormTest.php index 0860f93..4f563db 100644 --- a/core/modules/block/tests/src/Unit/BlockFormTest.php +++ b/core/modules/block/tests/src/Unit/BlockFormTest.php @@ -17,6 +17,59 @@ class BlockFormTest extends UnitTestCase { /** + * The condition plugin manager. + * + * @var \Drupal\Core\Executable\ExecutableManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $conditionManager; + + /** + * The block storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storage; + + /** + * The event dispatcher service. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dispatcher; + + /** + * The language manager service. + * + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $language; + + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $entityManager; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->conditionManager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface'); + $this->language = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + + $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $this->storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); + $this->entityManager->expects($this->any()) + ->method('getStorage') + ->will($this->returnValue($this->storage)); + + } + + /** * Tests the unique machine name generator. * * @see \Drupal\block\BlockForm::getUniqueMachineName() @@ -38,22 +91,11 @@ class BlockFormTest extends UnitTestCase { ->method('execute') ->will($this->returnValue(array('test', 'other_test', 'other_test_1', 'other_test_2'))); - $block_storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); - $block_storage->expects($this->exactly(5)) + $this->storage->expects($this->exactly(5)) ->method('getQuery') ->will($this->returnValue($query)); - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); - - $entity_manager->expects($this->any()) - ->method('getStorage') - ->will($this->returnValue($block_storage)); - - $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); - - $config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface'); - - $block_form_controller = new BlockForm($entity_manager, $language_manager, $config_factory); + $block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->dispatcher, $this->language); // Ensure that the block with just one other instance gets the next available // name suggestion. diff --git a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php index 7f7aec1..68a3008 100644 --- a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php +++ b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php @@ -7,6 +7,8 @@ namespace Drupal\Tests\block\Unit; +use Drupal\Core\Block\BlockPluginInterface; +use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Tests\UnitTestCase; /** @@ -16,47 +18,78 @@ class BlockRepositoryTest extends UnitTestCase { /** - * Tests the retrieval of block entities. - * - * @covers ::getVisibleBlocksPerRegion - * - * @dataProvider providerBlocksConfig + * @var \Drupal\block\BlockRepository + */ + protected $blockRepository; + + /** + * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $blockStorage; + + /** + * @var string */ - function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) { - $theme = $this->randomMachineName(); + protected $theme; + + /** + * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $contextHandler; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); $active_theme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme') ->disableOriginalConstructor() ->getMock(); + $this->theme = $this->randomMachineName(); $active_theme->expects($this->atLeastOnce()) ->method('getName') - ->willReturn($theme); + ->willReturn($this->theme); + $theme_manager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface'); $theme_manager->expects($this->once()) ->method('getActiveTheme') ->will($this->returnValue($active_theme)); - $block_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $this->contextHandler = $this->getMock('Drupal\Core\Plugin\Context\ContextHandlerInterface'); + $this->blockStorage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); $entity_manager->expects($this->any()) ->method('getStorage') - ->willReturn($block_storage); + ->willReturn($this->blockStorage); - $block_repository = $this->getMockBuilder('Drupal\block\BlockRepository') - ->setConstructorArgs([$entity_manager, $theme_manager]) + $this->blockRepository = $this->getMockBuilder('Drupal\block\BlockRepository') + ->setConstructorArgs([$entity_manager, $theme_manager, $this->contextHandler]) ->setMethods(['getRegionNames']) ->getMock(); - $block_repository->expects($this->once()) + $this->blockRepository->expects($this->once()) ->method('getRegionNames') ->willReturn([ 'top' => 'Top', 'center' => 'Center', 'bottom' => 'Bottom', ]); + } + /** + * Tests the retrieval of block entities. + * + * @covers ::getVisibleBlocksPerRegion + * + * @dataProvider providerBlocksConfig + */ + public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blocks) { $blocks = []; foreach ($blocks_config as $block_id => $block_config) { $block = $this->getMock('Drupal\block\BlockInterface'); $block->expects($this->once()) + ->method('setContexts') + ->willReturnSelf(); + $block->expects($this->once()) ->method('access') ->will($this->returnValue($block_config[0])); $block->expects($block_config[0] ? $this->atLeastOnce() : $this->never()) @@ -69,12 +102,12 @@ function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blo $blocks[$block_id] = $block; } - $block_storage->expects($this->once()) + $this->blockStorage->expects($this->once()) ->method('loadByProperties') - ->with(['theme' => $theme]) + ->with(['theme' => $this->theme]) ->willReturn($blocks); $result = []; - foreach ($block_repository->getVisibleBlocksPerRegion() as $region => $resulting_blocks) { + foreach ($this->blockRepository->getVisibleBlocksPerRegion([]) as $region => $resulting_blocks) { $result[$region] = []; foreach ($resulting_blocks as $plugin_id => $block) { $result[$region][] = $plugin_id; @@ -83,7 +116,6 @@ function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blo $this->assertSame($result, $expected_blocks); } - public function providerBlocksConfig() { $blocks_config = array( 'block1' => array( @@ -113,4 +145,48 @@ function testGetVisibleBlocksPerRegion(array $blocks_config, array $expected_blo return $test_cases; } + /** + * Tests the retrieval of block entities that are context-aware. + * + * @covers ::getVisibleBlocksPerRegion + */ + public function testGetVisibleBlocksPerRegionWithContext() { + $block = $this->getMock('Drupal\block\BlockInterface'); + $block->expects($this->once()) + ->method('setContexts') + ->willReturnSelf(); + $block->expects($this->once()) + ->method('access') + ->willReturn(TRUE); + $block->expects($this->once()) + ->method('get') + ->with('region') + ->willReturn('top'); + $blocks['block_id'] = $block; + + $contexts = []; + $this->blockStorage->expects($this->once()) + ->method('loadByProperties') + ->with(['theme' => $this->theme]) + ->willReturn($blocks); + $result = []; + foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $resulting_blocks) { + $result[$region] = []; + foreach ($resulting_blocks as $plugin_id => $block) { + $result[$region][] = $plugin_id; + } + } + $expected = [ + 'top' => [ + 'block_id', + ], + 'center' => [], + 'bottom' => [], + ]; + $this->assertSame($expected, $result); + } + +} + +interface TestContextAwareBlockInterface extends BlockPluginInterface, ContextAwarePluginInterface { } diff --git a/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php b/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php index b1a84a4..fcfeec8 100644 --- a/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php +++ b/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php @@ -49,6 +49,7 @@ class BlockLocalTasksTest extends LocalTaskIntegrationTest { $container = new ContainerBuilder(); $container->set('config.factory', $config_factory); $container->set('theme_handler', $theme_handler); + $container->set('app.root', $this->root); \Drupal::setContainer($container); } diff --git a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php index e16d133..0ae38d4 100644 --- a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php +++ b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php @@ -30,18 +30,18 @@ class BlockPageVariantTest extends UnitTestCase { protected $blockViewBuilder; /** - * The current route match. + * The event dispatcher. * - * @var \Drupal\Core\Routing\RouteMatchInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $routeMatch; + protected $dispatcher; /** - * The theme negotiator. + * The plugin context handler. * - * @var \Drupal\Core\Theme\ThemeNegotiatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $themeNegotiator; + protected $contextHandler; /** * Sets up a display variant plugin for testing. @@ -57,10 +57,13 @@ class BlockPageVariantTest extends UnitTestCase { public function setUpDisplayVariant($configuration = array(), $definition = array()) { $this->blockRepository = $this->getMock('Drupal\block\BlockRepositoryInterface'); $this->blockViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface'); - $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); - $this->themeNegotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface'); + $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->dispatcher->expects($this->any()) + ->method('dispatch') + ->willReturnArgument(1); + $this->contextHandler = $this->getMock('Drupal\Core\Plugin\Context\ContextHandlerInterface'); return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant') - ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->routeMatch, $this->themeNegotiator)) + ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, $this->contextHandler)) ->setMethods(array('getRegionNames')) ->getMock(); } diff --git a/core/modules/block_content/block_content.routing.yml b/core/modules/block_content/block_content.routing.yml index 1628b89..90d26ea 100644 --- a/core/modules/block_content/block_content.routing.yml +++ b/core/modules/block_content/block_content.routing.yml @@ -9,7 +9,7 @@ block_content.type_list: block_content.add_page: path: '/block/add' defaults: - _content: '\Drupal\block_content\Controller\BlockContentController::add' + _controller: '\Drupal\block_content\Controller\BlockContentController::add' _title: 'Add custom block' options: _admin_route: TRUE @@ -19,7 +19,7 @@ block_content.add_page: block_content.add_form: path: '/block/add/{block_content_type}' defaults: - _content: '\Drupal\block_content\Controller\BlockContentController::addForm' + _controller: '\Drupal\block_content\Controller\BlockContentController::addForm' _title_callback: 'Drupal\block_content\Controller\BlockContentController::getAddFormTitle' options: _admin_route: TRUE diff --git a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml index b8b7827..a39088a 100644 --- a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml +++ b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml @@ -12,11 +12,6 @@ weight: null provider: null plugin: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e' settings: - visibility: - request_path: - id: request_path - pages: '' - negate: false id: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e' label: 'Foobar Gorilla' provider: block_content @@ -27,3 +22,8 @@ settings: status: true info: '' view_mode: default +visibility: + request_path: + id: request_path + pages: '' + negate: false diff --git a/core/modules/book/book.routing.yml b/core/modules/book/book.routing.yml index 13c0f10..10f9241 100644 --- a/core/modules/book/book.routing.yml +++ b/core/modules/book/book.routing.yml @@ -1,7 +1,7 @@ book.render: path: '/book' defaults: - _content: '\Drupal\book\Controller\BookController::bookRender' + _controller: '\Drupal\book\Controller\BookController::bookRender' _title: 'Books' requirements: _permission: 'access content' @@ -9,7 +9,7 @@ book.render: book.admin: path: '/admin/structure/book' defaults: - _content: '\Drupal\book\Controller\BookController::adminOverview' + _controller: '\Drupal\book\Controller\BookController::adminOverview' _title: 'Books' requirements: _permission: 'administer book outlines' diff --git a/core/modules/book/config/install/core.base_field_override.node.book.promote.yml b/core/modules/book/config/install/core.base_field_override.node.book.promote.yml index f747d77..600d04a 100644 --- a/core/modules/book/config/install/core.base_field_override.node.book.promote.yml +++ b/core/modules/book/config/install/core.base_field_override.node.book.promote.yml @@ -1,4 +1,3 @@ -# Changes the default value of the promote base field on the book node type. langcode: en status: true dependencies: @@ -17,4 +16,5 @@ default_value: value: 0 default_value_callback: '' settings: { } +third_party_settings: { } field_type: boolean diff --git a/core/modules/book/config/install/core.entity_form_display.node.book.default.yml b/core/modules/book/config/install/core.entity_form_display.node.book.default.yml new file mode 100644 index 0000000..ac9c4ee --- /dev/null +++ b/core/modules/book/config/install/core.entity_form_display.node.book.default.yml @@ -0,0 +1,57 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.book.body + - node.type.book + module: + - entity_reference + - text +id: node.book.default +targetEntityType: node +bundle: book +mode: default +content: + title: + type: string_textfield + weight: -5 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + autocomplete_type: tags + placeholder: '' + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: true + weight: 15 + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: true + weight: 16 + third_party_settings: { } + body: + type: text_textarea_with_summary + weight: 26 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } +hidden: { } +third_party_settings: { } diff --git a/core/modules/book/config/install/core.entity_view_display.node.book.default.yml b/core/modules/book/config/install/core.entity_view_display.node.book.default.yml new file mode 100644 index 0000000..e4f76fd --- /dev/null +++ b/core/modules/book/config/install/core.entity_view_display.node.book.default.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.book.body + - node.type.book + module: + - text + - user +id: node.book.default +label: null +targetEntityType: node +bundle: book +mode: default +content: + body: + label: hidden + type: text_default + weight: 100 + settings: { } + third_party_settings: { } + links: + weight: 101 +hidden: + langcode: true +third_party_settings: { } diff --git a/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml b/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml new file mode 100644 index 0000000..4e2dd37 --- /dev/null +++ b/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml @@ -0,0 +1,28 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.book.body + - node.type.book + module: + - text + - user +id: node.book.teaser +label: null +targetEntityType: node +bundle: book +mode: teaser +content: + body: + label: hidden + type: text_summary_or_trimmed + weight: 100 + settings: + trim_length: 600 + third_party_settings: { } + links: + weight: 101 +hidden: + langcode: true +third_party_settings: { } diff --git a/core/modules/book/config/install/field.field.node.book.body.yml b/core/modules/book/config/install/field.field.node.book.body.yml new file mode 100644 index 0000000..4c128b6 --- /dev/null +++ b/core/modules/book/config/install/field.field.node.book.body.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.book + module: + - text +id: node.book.body +field_name: body +entity_type: node +bundle: book +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: true +third_party_settings: { } +field_type: text_with_summary diff --git a/core/modules/book/config/install/node.type.book.yml b/core/modules/book/config/install/node.type.book.yml index 172eaaf..2fb20c3 100644 --- a/core/modules/book/config/install/node.type.book.yml +++ b/core/modules/book/config/install/node.type.book.yml @@ -1,9 +1,11 @@ -type: book +langcode: en +status: true +dependencies: { } name: 'Book page' +type: book description: 'Books have a built-in hierarchical navigation. Use for handbooks or tutorials.' help: '' new_revision: false -display_submitted: true preview_mode: 1 -status: true -langcode: en +display_submitted: true +third_party_settings: { } diff --git a/core/modules/book/src/Tests/BookTest.php b/core/modules/book/src/Tests/BookTest.php index 3097ede..675d070 100644 --- a/core/modules/book/src/Tests/BookTest.php +++ b/core/modules/book/src/Tests/BookTest.php @@ -18,7 +18,7 @@ class BookTest extends WebTestBase { /** - * Modules to enable. + * Modules to install. * * @var array */ @@ -373,9 +373,9 @@ function testBookNavigationBlock() { } /** - * Tests the book navigation block when an access module is enabled. + * Tests the book navigation block when an access module is installed. */ - function testNavigationBlockOnAccessModuleEnabled() { + function testNavigationBlockOnAccessModuleInstalled() { $this->drupalLogin($this->admin_user); $block = $this->drupalPlaceBlock('book_navigation', array('block_mode' => 'book pages')); @@ -633,7 +633,7 @@ function testBookNodeTypeChange() { // Create a new book. $this->createBook(); - // Must be a user with 'node test view' permission since node_access_test is enabled. + // Must be a user with 'node test view' permission since node_access_test is installed. $this->drupalLogin($this->web_user); // Load the book page and assert the created book title is displayed. diff --git a/core/modules/book/templates/book-all-books-block.html.twig b/core/modules/book/templates/book-all-books-block.html.twig index 6160982..a4d0c9a 100644 --- a/core/modules/book/templates/book-all-books-block.html.twig +++ b/core/modules/book/templates/book-all-books-block.html.twig @@ -18,7 +18,7 @@ */ #} {% for book in book_menus %} -