diff --git a/CHANGELOG.md b/CHANGELOG.md index 992889e48b..0fac5c6947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,20 @@ +# v1.7.28 +## 01/24/2022 + +1. [](#new) + * Added links and modules support to `HtmlBlock` class + * Added module support for twig script tag: `{% script module 'theme://js/module.mjs' %}` + * Added twig tag for links: `{% link icon 'theme://images/favicon.png' priority: 20 with { type: 'image/png' } %}` + * Added `HtmlBlock` support for `{% style %}`, `{% script %}` and `{% link %}` tags + * Support for page-level `redirect_default_route` frontmatter header override +3. [](#bugfix) + * Fixed XSS check not detecting escaped `:` + # v1.7.27.1 ## 01/12/2022 3. [](#bugfix) - * Fixed a typo in CSS Asset pipeline that was erroneously joining files with `;` + * Fixed a typo in CSS Asset pipeline that was erroneously joining files with `;` # v1.7.27 ## 01/12/2022 diff --git a/composer.lock b/composer.lock index 25fad88cc6..7630b5260f 100644 --- a/composer.lock +++ b/composer.lock @@ -548,16 +548,16 @@ }, { "name": "filp/whoops", - "version": "2.14.4", + "version": "2.14.5", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "f056f1fe935d9ed86e698905a957334029899895" + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/f056f1fe935d9ed86e698905a957334029899895", - "reference": "f056f1fe935d9ed86e698905a957334029899895", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", "shasum": "" }, "require": { @@ -607,7 +607,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.4" + "source": "https://github.com/filp/whoops/tree/2.14.5" }, "funding": [ { @@ -615,7 +615,7 @@ "type": "github" } ], - "time": "2021-10-03T12:00:00+00:00" + "time": "2022-01-07T12:00:00+00:00" }, { "name": "getgrav/cache", @@ -840,16 +840,16 @@ }, { "name": "itsgoingd/clockwork", - "version": "v5.1.2", + "version": "v5.1.3", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "4f46a69b123635ba2910a3ca8d100edfadc924e1" + "reference": "e03f8a7f4bcd99ec67e56428e4fc7424de4cefa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/4f46a69b123635ba2910a3ca8d100edfadc924e1", - "reference": "4f46a69b123635ba2910a3ca8d100edfadc924e1", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/e03f8a7f4bcd99ec67e56428e4fc7424de4cefa8", + "reference": "e03f8a7f4bcd99ec67e56428e4fc7424de4cefa8", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.2" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.3" }, "funding": [ { @@ -905,7 +905,7 @@ "type": "github" } ], - "time": "2021-12-07T18:24:19+00:00" + "time": "2021-12-24T12:24:20+00:00" }, { "name": "league/climate", @@ -2191,16 +2191,16 @@ }, { "name": "symfony/console", - "version": "v4.4.34", + "version": "v4.4.36", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "329b3a75cc6b16d435ba1b1a41df54a53382a3f0" + "reference": "621379b62bb19af213b569b60013200b11dd576f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/329b3a75cc6b16d435ba1b1a41df54a53382a3f0", - "reference": "329b3a75cc6b16d435ba1b1a41df54a53382a3f0", + "url": "https://api.github.com/repos/symfony/console/zipball/621379b62bb19af213b569b60013200b11dd576f", + "reference": "621379b62bb19af213b569b60013200b11dd576f", "shasum": "" }, "require": { @@ -2261,7 +2261,7 @@ "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/console/tree/v4.4.34" + "source": "https://github.com/symfony/console/tree/v4.4.36" }, "funding": [ { @@ -2277,7 +2277,7 @@ "type": "tidelift" } ], - "time": "2021-11-04T12:23:33+00:00" + "time": "2021-12-15T10:33:10+00:00" }, { "name": "symfony/contracts", @@ -2459,16 +2459,16 @@ }, { "name": "symfony/http-client", - "version": "v4.4.35", + "version": "v4.4.36", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "6ed0c02fdc21a76966f19b9000de18e688d9ca68" + "reference": "35e2cd1862b9ec2b46ebf050fbb13e285944b6a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/6ed0c02fdc21a76966f19b9000de18e688d9ca68", - "reference": "6ed0c02fdc21a76966f19b9000de18e688d9ca68", + "url": "https://api.github.com/repos/symfony/http-client/zipball/35e2cd1862b9ec2b46ebf050fbb13e285944b6a3", + "reference": "35e2cd1862b9ec2b46ebf050fbb13e285944b6a3", "shasum": "" }, "require": { @@ -2520,7 +2520,7 @@ "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-client/tree/v4.4.35" + "source": "https://github.com/symfony/http-client/tree/v4.4.36" }, "funding": [ { @@ -2536,25 +2536,28 @@ "type": "tidelift" } ], - "time": "2021-11-22T21:43:45+00:00" + "time": "2021-12-29T09:28:53+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "reference": "30885182c981ab175d4d034db0f6f469898070ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-ctype": "*" + }, "suggest": { "ext-ctype": "For best performance" }, @@ -2599,7 +2602,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" }, "funding": [ { @@ -2615,25 +2618,28 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-10-20T20:35:02+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.23.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" + "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", - "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f1aed619e28cb077fc83fac8c4c0383578356e40", + "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-iconv": "*" + }, "suggest": { "ext-iconv": "For best performance" }, @@ -2679,7 +2685,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.24.0" }, "funding": [ { @@ -2695,25 +2701,28 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2022-01-04T09:04:05+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-mbstring": "*" + }, "suggest": { "ext-mbstring": "For best performance" }, @@ -2759,7 +2768,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0" }, "funding": [ { @@ -2775,11 +2784,11 @@ "type": "tidelift" } ], - "time": "2021-05-27T12:26:48+00:00" + "time": "2021-11-30T18:21:41+00:00" }, { "name": "symfony/polyfill-php74", - "version": "v1.23.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php74.git", @@ -2839,7 +2848,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php74/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php74/tree/v1.24.0" }, "funding": [ { @@ -2859,16 +2868,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.23.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/57b712b08eddb97c762a8caa32c84e037892d2e9", + "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9", "shasum": "" }, "require": { @@ -2922,7 +2931,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.24.0" }, "funding": [ { @@ -2938,20 +2947,20 @@ "type": "tidelift" } ], - "time": "2021-07-28T13:41:28+00:00" + "time": "2021-09-13T13:58:33+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.23.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "e66119f3de95efc359483f810c4c3e6436279436" + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", - "reference": "e66119f3de95efc359483f810c4c3e6436279436", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", "shasum": "" }, "require": { @@ -3001,7 +3010,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.24.0" }, "funding": [ { @@ -3017,20 +3026,20 @@ "type": "tidelift" } ], - "time": "2021-05-21T13:25:03+00:00" + "time": "2021-09-13T13:58:11+00:00" }, { "name": "symfony/process", - "version": "v4.4.35", + "version": "v4.4.36", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c2098705326addae6e6742151dfade47ac71da1b" + "reference": "a35d6b8f82e2272504f23a267de49b8717ca0028" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c2098705326addae6e6742151dfade47ac71da1b", - "reference": "c2098705326addae6e6742151dfade47ac71da1b", + "url": "https://api.github.com/repos/symfony/process/zipball/a35d6b8f82e2272504f23a267de49b8717ca0028", + "reference": "a35d6b8f82e2272504f23a267de49b8717ca0028", "shasum": "" }, "require": { @@ -3063,7 +3072,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v4.4.35" + "source": "https://github.com/symfony/process/tree/v4.4.36" }, "funding": [ { @@ -3079,20 +3088,20 @@ "type": "tidelift" } ], - "time": "2021-11-22T22:36:24+00:00" + "time": "2021-12-19T16:27:15+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.4.34", + "version": "v4.4.36", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "2d0c056b2faaa3d785bdbd5adecc593a5be9c16e" + "reference": "02685c62fcbc4262235cc72a54fbd45ab719ce3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2d0c056b2faaa3d785bdbd5adecc593a5be9c16e", - "reference": "2d0c056b2faaa3d785bdbd5adecc593a5be9c16e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/02685c62fcbc4262235cc72a54fbd45ab719ce3c", + "reference": "02685c62fcbc4262235cc72a54fbd45ab719ce3c", "shasum": "" }, "require": { @@ -3152,7 +3161,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v4.4.34" + "source": "https://github.com/symfony/var-dumper/tree/v4.4.36" }, "funding": [ { @@ -3168,20 +3177,20 @@ "type": "tidelift" } ], - "time": "2021-11-12T10:50:54+00:00" + "time": "2021-12-29T09:28:53+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.34", + "version": "v4.4.36", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "2c309e258adeb9970229042be39b360d34986fad" + "reference": "a19f7c44ba665fa9d9d415cc4493361381b93f9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/2c309e258adeb9970229042be39b360d34986fad", - "reference": "2c309e258adeb9970229042be39b360d34986fad", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a19f7c44ba665fa9d9d415cc4493361381b93f9b", + "reference": "a19f7c44ba665fa9d9d415cc4493361381b93f9b", "shasum": "" }, "require": { @@ -3223,7 +3232,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.34" + "source": "https://github.com/symfony/yaml/tree/v4.4.36" }, "funding": [ { @@ -3239,7 +3248,7 @@ "type": "tidelift" } ], - "time": "2021-11-18T18:49:23+00:00" + "time": "2021-11-25T16:40:00+00:00" }, { "name": "twig/twig", @@ -3442,23 +3451,23 @@ }, { "name": "codeception/codeception", - "version": "4.1.23", + "version": "4.1.28", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "27d18cd5d5a1d77d3f1b01ea776238676faf5dcc" + "reference": "e9bc22a3819f9d356068c0e372193ebcc9b06014" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/27d18cd5d5a1d77d3f1b01ea776238676faf5dcc", - "reference": "27d18cd5d5a1d77d3f1b01ea776238676faf5dcc", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/e9bc22a3819f9d356068c0e372193ebcc9b06014", + "reference": "e9bc22a3819f9d356068c0e372193ebcc9b06014", "shasum": "" }, "require": { "behat/gherkin": "^4.4.0", - "codeception/lib-asserts": "^1.0", + "codeception/lib-asserts": "^1.0 | 2.0.*@dev", "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0", - "codeception/stub": "^2.0 | ^3.0", + "codeception/stub": "^2.0 | ^3.0 | ^4.0", "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", @@ -3471,11 +3480,11 @@ "symfony/yaml": ">=2.7 <6.0" }, "require-dev": { - "codeception/module-asserts": "1.*@dev", - "codeception/module-cli": "1.*@dev", - "codeception/module-db": "1.*@dev", - "codeception/module-filesystem": "1.*@dev", - "codeception/module-phpbrowser": "1.*@dev", + "codeception/module-asserts": "^1.0 | 2.0.*@dev", + "codeception/module-cli": "^1.0 | 2.0.*@dev", + "codeception/module-db": "^1.0 | 2.0.*@dev", + "codeception/module-filesystem": "^1.0 | 2.0.*@dev", + "codeception/module-phpbrowser": "^1.0 | 2.0.*@dev", "codeception/specify": "~0.3", "codeception/util-universalframework": "*@dev", "monolog/monolog": "~1.8", @@ -3528,7 +3537,7 @@ ], "support": { "issues": "https://github.com/Codeception/Codeception/issues", - "source": "https://github.com/Codeception/Codeception/tree/4.1.23" + "source": "https://github.com/Codeception/Codeception/tree/4.1.28" }, "funding": [ { @@ -3536,7 +3545,7 @@ "type": "open_collective" } ], - "time": "2021-12-11T18:05:26+00:00" + "time": "2022-01-05T16:41:25+00:00" }, { "name": "codeception/lib-asserts", @@ -4198,9 +4207,6 @@ "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" - }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", @@ -4518,16 +4524,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.5.1", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706", + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706", "shasum": "" }, "require": { @@ -4562,9 +4568,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0" }, - "time": "2021-10-02T14:08:47+00:00" + "time": "2022-01-04T19:58:01+00:00" }, { "name": "phpspec/prophecy", @@ -4635,16 +4641,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.2.0", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee" + "reference": "1dd8f3e40bf7aa30031a75c65cece99220a161b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cbe085f9fdead5b6d62e4c022ca52dc9427a10ee", - "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1dd8f3e40bf7aa30031a75c65cece99220a161b8", + "reference": "1dd8f3e40bf7aa30031a75c65cece99220a161b8", "shasum": "" }, "require": { @@ -4660,7 +4666,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -4675,7 +4681,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.2.0" + "source": "https://github.com/phpstan/phpstan/tree/1.4.2" }, "funding": [ { @@ -4695,7 +4701,7 @@ "type": "tidelift" } ], - "time": "2021-11-18T14:09:01+00:00" + "time": "2022-01-18T16:09:11+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -5067,16 +5073,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.10", + "version": "9.5.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" + "reference": "93d4bf4c37aec6384bb9e5d390d9049a463a7256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/93d4bf4c37aec6384bb9e5d390d9049a463a7256", + "reference": "93d4bf4c37aec6384bb9e5d390d9049a463a7256", "shasum": "" }, "require": { @@ -5154,11 +5160,11 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.12" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { @@ -5166,7 +5172,7 @@ "type": "github" } ], - "time": "2021-09-25T07:38:51+00:00" + "time": "2022-01-21T05:54:47+00:00" }, { "name": "psr/http-client", @@ -6186,16 +6192,16 @@ }, { "name": "symfony/browser-kit", - "version": "v5.4.0", + "version": "v5.4.2", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "d250db364a35ba5d60626b2a6f10f2eaf2073bde" + "reference": "1fb93b0aab42392aa0a742db205173b49afaf80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/d250db364a35ba5d60626b2a6f10f2eaf2073bde", - "reference": "d250db364a35ba5d60626b2a6f10f2eaf2073bde", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/1fb93b0aab42392aa0a742db205173b49afaf80f", + "reference": "1fb93b0aab42392aa0a742db205173b49afaf80f", "shasum": "" }, "require": { @@ -6238,7 +6244,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.4.0" + "source": "https://github.com/symfony/browser-kit/tree/v5.4.2" }, "funding": [ { @@ -6254,20 +6260,20 @@ "type": "tidelift" } ], - "time": "2021-10-26T22:29:18+00:00" + "time": "2021-12-16T21:58:21+00:00" }, { "name": "symfony/css-selector", - "version": "v5.4.0", + "version": "v5.4.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "44b933f98bb4b5220d10bed9ce5662f8c2d13dcc" + "reference": "cfcbee910e159df402603502fe387e8b677c22fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/44b933f98bb4b5220d10bed9ce5662f8c2d13dcc", - "reference": "44b933f98bb4b5220d10bed9ce5662f8c2d13dcc", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/cfcbee910e159df402603502fe387e8b677c22fd", + "reference": "cfcbee910e159df402603502fe387e8b677c22fd", "shasum": "" }, "require": { @@ -6304,7 +6310,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.0" + "source": "https://github.com/symfony/css-selector/tree/v5.4.2" }, "funding": [ { @@ -6320,7 +6326,7 @@ "type": "tidelift" } ], - "time": "2021-09-09T08:06:01+00:00" + "time": "2021-12-16T21:58:21+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6391,16 +6397,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.0", + "version": "v5.4.2", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "5b06626e940a3ad54e573511d64d4e00dc8d0fd8" + "reference": "bb3bc3699779fc6d9646270789026a7e2cec7ec7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5b06626e940a3ad54e573511d64d4e00dc8d0fd8", - "reference": "5b06626e940a3ad54e573511d64d4e00dc8d0fd8", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/bb3bc3699779fc6d9646270789026a7e2cec7ec7", + "reference": "bb3bc3699779fc6d9646270789026a7e2cec7ec7", "shasum": "" }, "require": { @@ -6446,7 +6452,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.0" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.2" }, "funding": [ { @@ -6462,20 +6468,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T10:19:22+00:00" + "time": "2021-12-28T17:15:56+00:00" }, { "name": "symfony/finder", - "version": "v5.4.0", + "version": "v5.4.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590" + "reference": "e77046c252be48c48a40816187ed527703c8f76c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d2f29dac98e96a98be467627bd49c2efb1bc2590", - "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590", + "url": "https://api.github.com/repos/symfony/finder/zipball/e77046c252be48c48a40816187ed527703c8f76c", + "reference": "e77046c252be48c48a40816187ed527703c8f76c", "shasum": "" }, "require": { @@ -6509,7 +6515,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.0" + "source": "https://github.com/symfony/finder/tree/v5.4.2" }, "funding": [ { @@ -6525,7 +6531,7 @@ "type": "tidelift" } ], - "time": "2021-11-28T15:25:38+00:00" + "time": "2021-12-15T11:06:13+00:00" }, { "name": "theseer/tokenizer", @@ -6654,5 +6660,5 @@ "platform-overrides": { "php": "7.3.6" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.2.0" } diff --git a/system/blueprints/pages/default.yaml b/system/blueprints/pages/default.yaml index f3ef29f5e5..a573a83c83 100644 --- a/system/blueprints/pages/default.yaml +++ b/system/blueprints/pages/default.yaml @@ -320,6 +320,18 @@ form: fields: + header.redirect_default_route: + type: toggle + toggleable: true + label: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE + help: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE_HELP + config-highlight@: system.pages.redirect_default_route + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + header.routes.default: type: text toggleable: true diff --git a/system/defines.php b/system/defines.php index 089c40e945..7049854992 100644 --- a/system/defines.php +++ b/system/defines.php @@ -9,7 +9,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.7.27.1'); +define('GRAV_VERSION', '1.7.28'); define('GRAV_SCHEMA', '1.7.0_2020-11-20_1'); define('GRAV_TESTING', false); diff --git a/system/src/Grav/Common/Assets/BaseAsset.php b/system/src/Grav/Common/Assets/BaseAsset.php index 4f74f55f9a..d062d759bf 100644 --- a/system/src/Grav/Common/Assets/BaseAsset.php +++ b/system/src/Grav/Common/Assets/BaseAsset.php @@ -267,4 +267,17 @@ protected function cssRewrite($file, $dir, $local) { return ''; } + + /** + * Finds relative JS urls() and rewrites the URL with an absolute one + * + * @param string $file the css source file + * @param string $dir local relative path to the css file + * @param bool $local is this a local or remote asset + * @return string + */ + protected function jsRewrite($file, $dir, $local) + { + return ''; + } } diff --git a/system/src/Grav/Common/Assets/Pipeline.php b/system/src/Grav/Common/Assets/Pipeline.php index 58ef7d1d05..0010d7bd4d 100644 --- a/system/src/Grav/Common/Assets/Pipeline.php +++ b/system/src/Grav/Common/Assets/Pipeline.php @@ -279,11 +279,11 @@ protected function cssRewrite($file, $dir, $local) return $file; } - /** + /** * Finds relative JS urls() and rewrites the URL with an absolute one * * @param string $file the css source file - * @param string $dir , $local relative path to the css file + * @param string $dir local relative path to the css file * @param bool $local is this a local or remote asset * @return string */ diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php index a3ca3bb626..2d67bda5a7 100644 --- a/system/src/Grav/Common/Security.php +++ b/system/src/Grav/Common/Security.php @@ -214,7 +214,7 @@ public static function detectXss($string, array $options = null): ?string 'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])([\s\/]on|\sxmlns)[a-z].*=>?#iUu', // Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols - 'invalid_protocols' => '#(' . implode('|', array_map('preg_quote', $invalid_protocols, ['#'])) . '):\S.*?#iUu', + 'invalid_protocols' => '#(' . implode('|', array_map('preg_quote', $invalid_protocols, ['#'])) . ')(:|\&\#58)\S.*?#iUu', // Match -moz-bindings 'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u', diff --git a/system/src/Grav/Common/Service/PagesServiceProvider.php b/system/src/Grav/Common/Service/PagesServiceProvider.php index bec635ac83..fa6631eeec 100644 --- a/system/src/Grav/Common/Service/PagesServiceProvider.php +++ b/system/src/Grav/Common/Service/PagesServiceProvider.php @@ -99,7 +99,8 @@ public function register(Container $container) /** @var Language $language */ $language = $grav['language']; - $redirectCode = (int)$config->get('system.pages.redirect_default_route', 0); + $redirect_default_route = $page->header()->redirect_default_route ?? $config->get('system.pages.redirect_default_route', 0); + $redirectCode = (int) $redirect_default_route; // Language-specific redirection scenarios if ($language->enabled() && ($language->isLanguageInUrl() xor $language->isIncludeDefaultLanguage())) { diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php index b6f30906b2..dcff83750d 100644 --- a/system/src/Grav/Common/Twig/Extension/GravExtension.php +++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php @@ -22,6 +22,7 @@ use Grav\Common\Scheduler\Cron; use Grav\Common\Security; use Grav\Common\Twig\TokenParser\TwigTokenParserCache; +use Grav\Common\Twig\TokenParser\TwigTokenParserLink; use Grav\Common\Twig\TokenParser\TwigTokenParserRender; use Grav\Common\Twig\TokenParser\TwigTokenParserScript; use Grav\Common\Twig\TokenParser\TwigTokenParserStyle; @@ -252,6 +253,7 @@ public function getTokenParsers(): array new TwigTokenParserTryCatch(), new TwigTokenParserScript(), new TwigTokenParserStyle(), + new TwigTokenParserLink(), new TwigTokenParserMarkdown(), new TwigTokenParserSwitch(), new TwigTokenParserCache(), diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeLink.php b/system/src/Grav/Common/Twig/Node/TwigNodeLink.php new file mode 100644 index 0000000000..a468633ba3 --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeLink.php @@ -0,0 +1,114 @@ + $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, ['rel' => $rel], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + if (!$this->hasNode('file')) { + return; + } + + $compiler->write('$attributes = [\'rel\' => \'' . $this->getAttribute('rel') . '\'];' . "\n"); + if ($this->hasNode('attributes')) { + $compiler + ->write('$attributes += ') + ->subcompile($this->getNode('attributes')) + ->raw(';' . PHP_EOL) + ->write('if (!is_array($attributes)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } + + if ($this->hasNode('group')) { + $compiler + ->write('$group = ') + ->subcompile($this->getNode('group')) + ->raw(';' . PHP_EOL) + ->write('if (!is_string($group)) {' . PHP_EOL) + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');" . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + $compiler->write('$group = \'head\';' . PHP_EOL); + } + + if ($this->hasNode('priority')) { + $compiler + ->write('$priority = (int)(') + ->subcompile($this->getNode('priority')) + ->raw(');' . PHP_EOL); + } else { + $compiler->write('$priority = 10;' . PHP_EOL); + } + + $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];" . PHP_EOL); + $compiler->write("\$block = \$context['block'] ?? null;" . PHP_EOL); + + $compiler + ->write('$file = (string)(') + ->subcompile($this->getNode('file')) + ->raw(');' . PHP_EOL); + + // Assets support. + $compiler->write('$assets->addLink($file, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->addLink([\'href\'=> $file] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } +} diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeScript.php b/system/src/Grav/Common/Twig/Node/TwigNodeScript.php index d7b4ae79fe..03554bd090 100644 --- a/system/src/Grav/Common/Twig/Node/TwigNodeScript.php +++ b/system/src/Grav/Common/Twig/Node/TwigNodeScript.php @@ -27,6 +27,7 @@ class TwigNodeScript extends Node implements NodeCaptureInterface /** * TwigNodeScript constructor. * @param Node|null $body + * @param string|null $type * @param AbstractExpression|null $file * @param AbstractExpression|null $group * @param AbstractExpression|null $priority @@ -34,12 +35,12 @@ class TwigNodeScript extends Node implements NodeCaptureInterface * @param int $lineno * @param string|null $tag */ - public function __construct(?Node $body, ?AbstractExpression $file, ?AbstractExpression $group, ?AbstractExpression $priority, ?AbstractExpression $attributes, $lineno = 0, $tag = null) + public function __construct(?Node $body, ?string $type, ?AbstractExpression $file, ?AbstractExpression $group, ?AbstractExpression $priority, ?AbstractExpression $attributes, $lineno = 0, $tag = null) { $nodes = ['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes]; $nodes = array_filter($nodes); - parent::__construct($nodes, [], $lineno, $tag); + parent::__construct($nodes, ['type' => $type], $lineno, $tag); } /** @@ -53,52 +54,89 @@ public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); - $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n"); - if ($this->hasNode('attributes')) { $compiler ->write('$attributes = ') ->subcompile($this->getNode('attributes')) - ->raw(";\n") - ->write("if (!is_array(\$attributes)) {\n") + ->raw(';' . PHP_EOL) + ->write('if (!is_array($attributes)) {' . PHP_EOL) ->indent() - ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');\n") + ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');" . PHP_EOL) ->outdent() - ->write("}\n"); + ->write('}' . PHP_EOL); } else { - $compiler->write('$attributes = [];' . "\n"); + $compiler->write('$attributes = [];' . PHP_EOL); } if ($this->hasNode('group')) { $compiler - ->write("\$attributes['group'] = ") + ->write('$group = ') ->subcompile($this->getNode('group')) - ->raw(";\n") - ->write("if (!is_string(\$attributes['group'])) {\n") + ->raw(';' . PHP_EOL) + ->write('if (!is_string($group)) {' . PHP_EOL) ->indent() - ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');\n") + ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');" . PHP_EOL) ->outdent() - ->write("}\n"); + ->write('}' . PHP_EOL); + } else { + $compiler->write('$group = \'head\';' . PHP_EOL); } if ($this->hasNode('priority')) { $compiler - ->write("\$attributes['priority'] = (int)(") + ->write('$priority = (int)(') ->subcompile($this->getNode('priority')) - ->raw(");\n"); + ->raw(');' . PHP_EOL); + } else { + $compiler->write('$priority = 10;' . PHP_EOL); } + $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];" . PHP_EOL); + $compiler->write("\$block = \$context['block'] ?? null;" . PHP_EOL); + if ($this->hasNode('file')) { + // JS file. $compiler - ->write('$assets->addJs(') + ->write('$file = (string)(') ->subcompile($this->getNode('file')) - ->raw(", \$attributes);\n"); + ->raw(');' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addJsModule' : 'addJs'; + + // Assets support. + $compiler->write('$assets->' . $method . '($file, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addModule' : 'addScript'; + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->' . $method . '([\'src\'=> $file] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + // Inline script. $compiler - ->write("ob_start();\n") + ->write('ob_start();' . PHP_EOL) ->subcompile($this->getNode('body')) - ->write('$content = ob_get_clean();' . "\n") - ->write("\$assets->addInlineJs(\$content, \$attributes);\n"); + ->write('$content = ob_get_clean();' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addInlineJsModule' : 'addInlineJs'; + + // Assets support. + $compiler->write('$assets->' . $method . '($content, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + $method = $this->getAttribute('type') === 'module' ? 'addInlineModule' : 'addInlineScript'; + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->' . $method . '([\'content\'=> $content] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); } } } diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php b/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php index 8710bf531f..1c3568c6a9 100644 --- a/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php +++ b/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php @@ -41,6 +41,7 @@ public function __construct(?Node $body, ?AbstractExpression $file, ?AbstractExp parent::__construct($nodes, [], $lineno, $tag); } + /** * Compiles the node to PHP. * @@ -52,52 +53,81 @@ public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); - $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n"); - if ($this->hasNode('attributes')) { $compiler ->write('$attributes = ') ->subcompile($this->getNode('attributes')) - ->raw(";\n") - ->write("if (!is_array(\$attributes)) {\n") + ->raw(';' . PHP_EOL) + ->write('if (!is_array($attributes)) {' . PHP_EOL) ->indent() - ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');\n") + ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');" . PHP_EOL) ->outdent() - ->write("}\n"); + ->write('}' . PHP_EOL); } else { - $compiler->write('$attributes = [];' . "\n"); + $compiler->write('$attributes = [];' . PHP_EOL); } if ($this->hasNode('group')) { $compiler - ->write("\$attributes['group'] = ") + ->write('$group = ') ->subcompile($this->getNode('group')) - ->raw(";\n") - ->write("if (!is_string(\$attributes['group'])) {\n") + ->raw(';' . PHP_EOL) + ->write('if (!is_string($group)) {' . PHP_EOL) ->indent() - ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');\n") + ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');" . PHP_EOL) ->outdent() - ->write("}\n"); + ->write('}' . PHP_EOL); + } else { + $compiler->write('$group = \'head\';' . PHP_EOL); } if ($this->hasNode('priority')) { $compiler - ->write("\$attributes['priority'] = (int)(") + ->write('$priority = (int)(') ->subcompile($this->getNode('priority')) - ->raw(");\n"); + ->raw(');' . PHP_EOL); + } else { + $compiler->write('$priority = 10;' . PHP_EOL); } + $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];" . PHP_EOL); + $compiler->write("\$block = \$context['block'] ?? null;" . PHP_EOL); + if ($this->hasNode('file')) { + // CSS file. $compiler - ->write('$assets->addCss(') + ->write('$file = (string)(') ->subcompile($this->getNode('file')) - ->raw(", \$attributes);\n"); + ->raw(');' . PHP_EOL); + + // Assets support. + $compiler->write('$assets->addCss($file, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->addStyle([\'href\'=> $file] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); + } else { + // Inline style. $compiler - ->write("ob_start();\n") + ->write('ob_start();' . PHP_EOL) ->subcompile($this->getNode('body')) - ->write('$content = ob_get_clean();' . "\n") - ->write("\$assets->addInlineCss(\$content, \$attributes);\n"); + ->write('$content = ob_get_clean();' . PHP_EOL); + + // Assets support. + $compiler->write('$assets->addInlineCss($content, [\'group\' => $group, \'priority\' => $priority] + $attributes);' . PHP_EOL); + + // HtmlBlock support. + $compiler + ->write('if ($block instanceof \Grav\Framework\ContentBlock\HtmlBlock) {' . PHP_EOL) + ->indent() + ->write('$block->addInlineStyle([\'content\'=> $content] + $attributes, $priority, $group);' . PHP_EOL) + ->outdent() + ->write('}' . PHP_EOL); } } } diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php new file mode 100644 index 0000000000..2cb0208eb4 --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php @@ -0,0 +1,109 @@ +getLine(); + + [$rel, $file, $group, $priority, $attributes] = $this->parseArguments($token); + + return new TwigNodeLink($rel, $file, $group, $priority, $attributes, $lineno, $this->getTag()); + } + + /** + * @param Token $token + * @return array + */ + protected function parseArguments(Token $token): array + { + $stream = $this->parser->getStream(); + + + $rel = null; + if ($stream->test(Token::NAME_TYPE, $this->rel)) { + $rel = $stream->getCurrent()->getValue(); + $stream->next(); + } + + $file = null; + if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) { + $file = $this->parser->getExpressionParser()->parseExpression(); + } + + $group = null; + if ($stream->nextIf(Token::NAME_TYPE, 'at')) { + $group = $this->parser->getExpressionParser()->parseExpression(); + } + + $priority = null; + if ($stream->nextIf(Token::NAME_TYPE, 'priority')) { + $stream->expect(Token::PUNCTUATION_TYPE, ':'); + $priority = $this->parser->getExpressionParser()->parseExpression(); + } + + $attributes = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $attributes = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$rel, $file, $group, $priority, $attributes]; + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'link'; + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php index e98365b94b..b943a25406 100644 --- a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php @@ -15,14 +15,20 @@ use Twig\TokenParser\AbstractTokenParser; /** - * Adds a script to head/bottom/custom location in the document. + * Adds a script to head/bottom/custom group location in the document. * - * {% script 'theme://js/something.js' at 'bottom' priority: 20 with { defer: true, async: true } %} + * {% script 'theme://js/something.js' at 'bottom' priority: 20 with { position: 'pipeline', loading: 'async defer' } %} + * {% script module 'theme://js/module.mjs' at 'head' %} * + * {% script 'theme://js/something.js' at 'bottom' priority: 20 with { loading: 'inline' } %} * {% script at 'bottom' priority: 20 %} - * alert('Warning!'); + * alert('Warning!'); + * {% endscript %} + * + * {% script module 'theme://js/module.mjs' at 'bottom' with { loading: 'inline' } %} + * {% script module at 'bottom' %} + * ... * {% endscript %} - */ class TwigTokenParserScript extends AbstractTokenParser { @@ -38,7 +44,7 @@ public function parse(Token $token) $lineno = $token->getLine(); $stream = $this->parser->getStream(); - [$file, $group, $priority, $attributes] = $this->parseArguments($token); + [$type, $file, $group, $priority, $attributes] = $this->parseArguments($token); $content = null; if ($file === null) { @@ -46,7 +52,7 @@ public function parse(Token $token) $stream->expect(Token::BLOCK_END_TYPE); } - return new TwigNodeScript($content, $file, $group, $priority, $attributes, $lineno, $this->getTag()); + return new TwigNodeScript($content, $type, $file, $group, $priority, $attributes, $lineno, $this->getTag()); } /** @@ -73,6 +79,12 @@ protected function parseArguments(Token $token): array } while (true); } + $type = null; + if ($stream->test(Token::NAME_TYPE, 'module')) { + $type = $stream->getCurrent()->getValue(); + $stream->next(); + } + $file = null; if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE, 'in') && !$stream->test(Token::BLOCK_END_TYPE)) { $file = $this->parser->getExpressionParser()->parseExpression(); @@ -96,7 +108,7 @@ protected function parseArguments(Token $token): array $stream->expect(Token::BLOCK_END_TYPE); - return [$file, $group, $priority, $attributes]; + return [$type, $file, $group, $priority, $attributes]; } /** diff --git a/system/src/Grav/Framework/ContentBlock/HtmlBlock.php b/system/src/Grav/Framework/ContentBlock/HtmlBlock.php index 734d5077fd..71cd9367f2 100644 --- a/system/src/Grav/Framework/ContentBlock/HtmlBlock.php +++ b/system/src/Grav/Framework/ContentBlock/HtmlBlock.php @@ -29,6 +29,8 @@ class HtmlBlock extends ContentBlock implements HtmlBlockInterface /** @var array */ protected $scripts = []; /** @var array */ + protected $links = []; + /** @var array */ protected $html = []; /** @@ -40,6 +42,7 @@ public function getAssets() $this->sortAssets($assets['styles']); $this->sortAssets($assets['scripts']); + $this->sortAssets($assets['links']); $this->sortAssets($assets['html']); return $assets; @@ -73,6 +76,15 @@ public function getScripts($location = 'head') return $this->getAssetsInLocation('scripts', $location); } + /** + * @param string $location + * @return array + */ + public function getLinks($location = 'head') + { + return $this->getAssetsInLocation('links', $location); + } + /** * @param string $location * @return array @@ -98,6 +110,9 @@ public function toArray() if ($this->scripts) { $array['scripts'] = $this->scripts; } + if ($this->links) { + $array['links'] = $this->links; + } if ($this->html) { $array['html'] = $this->html; } @@ -117,6 +132,7 @@ public function build(array $serialized) $this->frameworks = isset($serialized['frameworks']) ? (array) $serialized['frameworks'] : []; $this->styles = isset($serialized['styles']) ? (array) $serialized['styles'] : []; $this->scripts = isset($serialized['scripts']) ? (array) $serialized['scripts'] : []; + $this->links = isset($serialized['links']) ? (array) $serialized['links'] : []; $this->html = isset($serialized['html']) ? (array) $serialized['html'] : []; } @@ -229,8 +245,9 @@ public function addScript($element, $priority = 0, $location = 'head') $src = $element['src']; $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript'; - $defer = isset($element['defer']); - $async = isset($element['async']); + $loading = !empty($element['loading']) ? (string) $element['loading'] : null; + $defer = !empty($element['defer']); + $async = !empty($element['async']); $handle = !empty($element['handle']) ? (string) $element['handle'] : ''; $this->scripts[$location][md5($src) . sha1($src)] = [ @@ -238,6 +255,7 @@ public function addScript($element, $priority = 0, $location = 'head') ':priority' => (int) $priority, 'src' => $src, 'type' => $type, + 'loading' => $loading, 'defer' => $defer, 'async' => $async, 'handle' => $handle @@ -266,12 +284,80 @@ public function addInlineScript($element, $priority = 0, $location = 'head') $content = (string) $element['content']; $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript'; + $loading = !empty($element['loading']) ? (string) $element['loading'] : null; $this->scripts[$location][md5($content) . sha1($content)] = [ ':type' => 'inline', ':priority' => (int) $priority, 'content' => $content, - 'type' => $type + 'type' => $type, + 'loading' => $loading + ]; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addModule($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['src' => (string) $element]; + } + + $element['type'] = 'module'; + + return $this->addScript($element, $priority, $location); + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineModule($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['content' => (string) $element]; + } + + $element['type'] = 'module'; + + return $this->addInlineScript($element, $priority, $location); + } + + /** + * @param array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addLink($element, $priority = 0, $location = 'head') + { + if (!is_array($element) || empty($element['rel']) || empty($element['href'])) { + return false; + } + + if (!isset($this->links[$location])) { + $this->links[$location] = []; + } + + $rel = (string) $element['rel']; + $href = (string) $element['href']; + + unset($element['rel'], $element['href']); + + $this->links[$location][md5($href) . sha1($href)] = [ + ':type' => 'file', + ':priority' => (int) $priority, + 'href' => $href, + 'rel' => $rel, + 'element' => $element, ]; return true; @@ -309,6 +395,7 @@ protected function getAssetsFast() 'frameworks' => $this->frameworks, 'styles' => $this->styles, 'scripts' => $this->scripts, + 'links' => $this->links, 'html' => $this->html ]; @@ -333,6 +420,14 @@ protected function getAssetsFast() } } + foreach ($blockAssets['links'] as $location => $links) { + if (!isset($assets['links'][$location])) { + $assets['links'][$location] = $links; + } elseif ($links) { + $assets['links'][$location] += $links; + } + } + foreach ($blockAssets['html'] as $location => $htmls) { if (!isset($assets['html'][$location])) { $assets['html'][$location] = $htmls; @@ -391,7 +486,7 @@ static function ($a, $b) { */ protected function sortAssets(array &$array) { - foreach ($array as $location => &$items) { + foreach ($array as &$items) { $this->sortAssetsInLocation($items); } } diff --git a/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php index 686127f45a..b151d7b8aa 100644 --- a/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php +++ b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php @@ -37,6 +37,13 @@ public function getStyles($location = 'head'); */ public function getScripts($location = 'head'); + + /** + * @param string $location + * @return array + */ + public function getLinks($location = 'head'); + /** * @param string $location * @return array @@ -76,7 +83,6 @@ public function addInlineStyle($element, $priority = 0, $location = 'head'); */ public function addScript($element, $priority = 0, $location = 'head'); - /** * @param string|array $element * @param int $priority @@ -85,6 +91,35 @@ public function addScript($element, $priority = 0, $location = 'head'); */ public function addInlineScript($element, $priority = 0, $location = 'head'); + + /** + * Shortcut for writing addScript(['type' => 'module', 'src' => ...]). + * + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addModule($element, $priority = 0, $location = 'head'); + + /** + * Shortcut for writing addInlineScript(['type' => 'module', 'content' => ...]). + * + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineModule($element, $priority = 0, $location = 'head'); + + /** + * @param array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addLink($element, $priority = 0, $location = 'head'); + /** * @param string $html * @param int $priority diff --git a/tests/phpstan/phpstan.neon b/tests/phpstan/phpstan.neon index ef7a598de6..7d5d7cb008 100644 --- a/tests/phpstan/phpstan.neon +++ b/tests/phpstan/phpstan.neon @@ -130,6 +130,9 @@ parameters: - message: '#Call to deprecated method getLegacyFiles\(\)#' path: '*/system/src/Grav/Common/Session.php' + - + message: '#Call to deprecated method \w+\(\) of class Grav\\Common\\Flex\\Types\\Users\\UserObject#' + path: '*/system/src/Grav/Common/Flex/Types/Users/UserObject.php' - message: '#Call to deprecated method \w+\(\) of class Grav\\Framework\\Flex\\FlexObject#' path: '*/system/src/Grav/Framework/Flex/FlexObject.php'