diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5320a0e5a..b0c0eb3e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+# v1.7.25
+## 11/16/2021
+
+1. [](#new)
+ * Updated phpstan to v1.0
+ * Added `FlexObject::getDiff()` to see difference to the saved object
+2. [](#improved)
+ * Use Symfony `dump` instead of PHP's `vardump` in side the `{{ vardump(x) }}` Twig vardump function
+ * Added `route` and `request` to `onPagesInitialized` event
+ * Improved page cloning, added method `Page::initialize()`
+ * Improved `FlexObject::getChanges()`: return changed lists and arrays as whole instead of just changed keys/values
+ * Improved form validation JSON responses to contain list of failed fields with their error messages
+ * Improved redirects: send redirect response in JSON if the request was in JSON
+3. [](#bugfix)
+ * Fixed path traversal vulnerability when using `bin/grav server`
+ * Fixed unescaped error messages in JSON error responses
+ * Fixed `|t(variable)` twig filter in admin
+ * Fixed `FlexObject::getChanges()` always returning empty array
+ * Fixed form validation exceptions to use `400 Bad Request` instead of `500 Internal Server Error`
+
# v1.7.24
## 10/26/2021
diff --git a/composer.json b/composer.json
index e86c6aa06..42535abb0 100644
--- a/composer.json
+++ b/composer.json
@@ -20,9 +20,10 @@
"ext-dom": "*",
"ext-libxml": "*",
"symfony/polyfill-mbstring": "~1.20",
- "symfony/polyfill-iconv": "^1.20",
- "symfony/polyfill-php74": "^1.20",
- "symfony/polyfill-php80": "^1.20",
+ "symfony/polyfill-iconv": "^1.23",
+ "symfony/polyfill-php74": "^1.23",
+ "symfony/polyfill-php80": "^1.23",
+ "symfony/polyfill-php81": "^1.23",
"psr/simple-cache": "^1.0",
"psr/http-message": "^1.0",
"psr/http-server-middleware": "^1.0",
@@ -63,8 +64,8 @@
},
"require-dev": {
"codeception/codeception": "^4.1",
- "phpstan/phpstan": "^0.12",
- "phpstan/phpstan-deprecation-rules": "^0.12",
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
"phpunit/php-code-coverage": "~9.2",
"getgrav/markdowndocs": "^2.0",
"codeception/module-asserts": "^1.3",
diff --git a/composer.lock b/composer.lock
index 5fbc07ac5..3b66c8a46 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "23dd68cea2a3f2d963e57638131f1122",
+ "content-hash": "072f00e1bf64b4ef43f7125fe80b15a7",
"packages": [
{
"name": "antoligy/dom-string-iterators",
@@ -56,16 +56,16 @@
},
{
"name": "composer/ca-bundle",
- "version": "1.2.11",
+ "version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582"
+ "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0b072d51c5a9c6f3412f7ea3ab043d6603cb2582",
- "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b",
+ "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b",
"shasum": ""
},
"require": {
@@ -112,7 +112,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
- "source": "https://github.com/composer/ca-bundle/tree/1.2.11"
+ "source": "https://github.com/composer/ca-bundle/tree/1.3.1"
},
"funding": [
{
@@ -128,7 +128,7 @@
"type": "tidelift"
}
],
- "time": "2021-09-25T20:32:43+00:00"
+ "time": "2021-10-28T20:44:15+00:00"
},
{
"name": "composer/semver",
@@ -888,22 +888,22 @@
},
{
"name": "itsgoingd/clockwork",
- "version": "v5.1.0",
+ "version": "v5.1.1",
"source": {
"type": "git",
"url": "https://github.com/itsgoingd/clockwork.git",
- "reference": "b963dee47429a49c9669981cfa9a8362ce209278"
+ "reference": "2daf30fa6dfc5a1ccfdb2142df59243a72c473d8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/b963dee47429a49c9669981cfa9a8362ce209278",
- "reference": "b963dee47429a49c9669981cfa9a8362ce209278",
+ "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/2daf30fa6dfc5a1ccfdb2142df59243a72c473d8",
+ "reference": "2daf30fa6dfc5a1ccfdb2142df59243a72c473d8",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=5.6",
- "psr/log": "1.*"
+ "psr/log": "1.* || ^2.0"
},
"type": "library",
"extra": {
@@ -945,7 +945,7 @@
],
"support": {
"issues": "https://github.com/itsgoingd/clockwork/issues",
- "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.0"
+ "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.1"
},
"funding": [
{
@@ -953,7 +953,7 @@
"type": "github"
}
],
- "time": "2021-08-07T23:04:17+00:00"
+ "time": "2021-11-01T17:38:35+00:00"
},
{
"name": "league/climate",
@@ -1153,16 +1153,16 @@
},
{
"name": "maximebf/debugbar",
- "version": "v1.17.2",
+ "version": "v1.17.3",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
- "reference": "3541f09f09c003c4a9ff7ddb0eb3361a7f14d418"
+ "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/3541f09f09c003c4a9ff7ddb0eb3361a7f14d418",
- "reference": "3541f09f09c003c4a9ff7ddb0eb3361a7f14d418",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/e8ac3499af0ea5b440908e06cc0abe5898008b3c",
+ "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c",
"shasum": ""
},
"require": {
@@ -1212,9 +1212,9 @@
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
- "source": "https://github.com/maximebf/php-debugbar/tree/v1.17.2"
+ "source": "https://github.com/maximebf/php-debugbar/tree/v1.17.3"
},
- "time": "2021-10-18T09:39:00+00:00"
+ "time": "2021-10-19T12:33:27+00:00"
},
{
"name": "miljar/php-exif",
@@ -2239,16 +2239,16 @@
},
{
"name": "symfony/console",
- "version": "v4.4.30",
+ "version": "v4.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22"
+ "reference": "8dbd23ef7a8884051482183ddee8d9061b5feed0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/a3f7189a0665ee33b50e9e228c46f50f5acbed22",
- "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22",
+ "url": "https://api.github.com/repos/symfony/console/zipball/8dbd23ef7a8884051482183ddee8d9061b5feed0",
+ "reference": "8dbd23ef7a8884051482183ddee8d9061b5feed0",
"shasum": ""
},
"require": {
@@ -2309,7 +2309,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.30"
+ "source": "https://github.com/symfony/console/tree/v4.4.33"
},
"funding": [
{
@@ -2325,7 +2325,7 @@
"type": "tidelift"
}
],
- "time": "2021-08-25T19:27:26+00:00"
+ "time": "2021-10-25T16:36:08+00:00"
},
{
"name": "symfony/contracts",
@@ -2507,16 +2507,16 @@
},
{
"name": "symfony/http-client",
- "version": "v4.4.31",
+ "version": "v4.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "6b900ffa399e25203f30f79f6f4a56b89eee14c2"
+ "reference": "9a5fdf129b522a06a46d13400500d326c41d8a73"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/6b900ffa399e25203f30f79f6f4a56b89eee14c2",
- "reference": "6b900ffa399e25203f30f79f6f4a56b89eee14c2",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/9a5fdf129b522a06a46d13400500d326c41d8a73",
+ "reference": "9a5fdf129b522a06a46d13400500d326c41d8a73",
"shasum": ""
},
"require": {
@@ -2568,7 +2568,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.31"
+ "source": "https://github.com/symfony/http-client/tree/v4.4.33"
},
"funding": [
{
@@ -2584,7 +2584,7 @@
"type": "tidelift"
}
],
- "time": "2021-09-06T10:00:00+00:00"
+ "time": "2021-10-18T16:39:13+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -2988,6 +2988,85 @@
],
"time": "2021-07-28T13:41:28+00:00"
},
+ {
+ "name": "symfony/polyfill-php81",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php81.git",
+ "reference": "e66119f3de95efc359483f810c4c3e6436279436"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436",
+ "reference": "e66119f3de95efc359483f810c4c3e6436279436",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php81\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-05-21T13:25:03+00:00"
+ },
{
"name": "symfony/process",
"version": "v4.4.30",
@@ -3052,16 +3131,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v4.4.31",
+ "version": "v4.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "1f12cc0c2e880a5f39575c19af81438464717839"
+ "reference": "50286e2b7189bfb4f419c0731e86632cddf7c5ee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1f12cc0c2e880a5f39575c19af81438464717839",
- "reference": "1f12cc0c2e880a5f39575c19af81438464717839",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/50286e2b7189bfb4f419c0731e86632cddf7c5ee",
+ "reference": "50286e2b7189bfb4f419c0731e86632cddf7c5ee",
"shasum": ""
},
"require": {
@@ -3121,7 +3200,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v4.4.31"
+ "source": "https://github.com/symfony/var-dumper/tree/v4.4.33"
},
"funding": [
{
@@ -3137,7 +3216,7 @@
"type": "tidelift"
}
],
- "time": "2021-09-24T15:30:11+00:00"
+ "time": "2021-10-25T20:24:58+00:00"
},
{
"name": "symfony/yaml",
@@ -4207,16 +4286,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v4.13.0",
+ "version": "v4.13.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "50953a2691a922aa1769461637869a0a2faa3f53"
+ "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53",
- "reference": "50953a2691a922aa1769461637869a0a2faa3f53",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd",
+ "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"shasum": ""
},
"require": {
@@ -4257,9 +4336,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1"
},
- "time": "2021-09-20T12:20:58+00:00"
+ "time": "2021-11-03T20:52:16+00:00"
},
{
"name": "phar-io/manifest",
@@ -4601,16 +4680,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "0.12.99",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7"
+ "reference": "bcea0ae85868a89d5789c75f012c93129f842934"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4d40f1d759942f523be267a1bab6884f46ca3f7",
- "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bcea0ae85868a89d5789c75f012c93129f842934",
+ "reference": "bcea0ae85868a89d5789c75f012c93129f842934",
"shasum": ""
},
"require": {
@@ -4626,7 +4705,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "0.12-dev"
+ "dev-master": "1.0-dev"
}
},
"autoload": {
@@ -4641,7 +4720,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
- "source": "https://github.com/phpstan/phpstan/tree/0.12.99"
+ "source": "https://github.com/phpstan/phpstan/tree/1.1.2"
},
"funding": [
{
@@ -4661,36 +4740,35 @@
"type": "tidelift"
}
],
- "time": "2021-09-12T20:09:55+00:00"
+ "time": "2021-11-09T12:41:09+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
- "version": "0.12.6",
+ "version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-deprecation-rules.git",
- "reference": "46dbd43c2db973d2876d6653e53f5c2cc3a01fbb"
+ "reference": "e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/46dbd43c2db973d2876d6653e53f5c2cc3a01fbb",
- "reference": "46dbd43c2db973d2876d6653e53f5c2cc3a01fbb",
+ "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682",
+ "reference": "e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
- "phpstan/phpstan": "^0.12.60"
+ "phpstan/phpstan": "^1.0"
},
"require-dev": {
- "phing/phing": "^2.16.3",
"php-parallel-lint/php-parallel-lint": "^1.2",
- "phpstan/phpstan-phpunit": "^0.12",
- "phpunit/phpunit": "^7.5.20"
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
"extra": {
"branch-alias": {
- "dev-master": "0.12-dev"
+ "dev-master": "1.0-dev"
},
"phpstan": {
"includes": [
@@ -4710,29 +4788,29 @@
"description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.",
"support": {
"issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues",
- "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/0.12.6"
+ "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.0.0"
},
- "time": "2020-12-13T10:20:54+00:00"
+ "time": "2021-09-23T11:02:21+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.7",
+ "version": "9.2.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218"
+ "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218",
- "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
+ "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.12.0",
+ "nikic/php-parser": "^4.13.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@@ -4781,7 +4859,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8"
},
"funding": [
{
@@ -4789,7 +4867,7 @@
"type": "github"
}
],
- "time": "2021-09-17T05:39:03+00:00"
+ "time": "2021-10-30T08:01:38+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -5616,16 +5694,16 @@
},
{
"name": "sebastian/exporter",
- "version": "4.0.3",
+ "version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65"
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65",
- "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"shasum": ""
},
"require": {
@@ -5674,14 +5752,14 @@
}
],
"description": "Provides the functionality to export PHP variables for visualization",
- "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
- "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
},
"funding": [
{
@@ -5689,7 +5767,7 @@
"type": "github"
}
],
- "time": "2020-09-28T05:24:23+00:00"
+ "time": "2021-11-11T14:18:36+00:00"
},
{
"name": "sebastian/global-state",
@@ -6620,5 +6698,5 @@
"platform-overrides": {
"php": "7.3.6"
},
- "plugin-api-version": "2.1.0"
+ "plugin-api-version": "2.0.0"
}
diff --git a/system/defines.php b/system/defines.php
index 633534327..f5ccba900 100644
--- a/system/defines.php
+++ b/system/defines.php
@@ -9,7 +9,7 @@
// Some standard defines
define('GRAV', true);
-define('GRAV_VERSION', '1.7.24');
+define('GRAV_VERSION', '1.7.25');
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
define('GRAV_TESTING', false);
diff --git a/system/router.php b/system/router.php
index 187d4d844..d58609c83 100644
--- a/system/router.php
+++ b/system/router.php
@@ -13,8 +13,25 @@ if (PHP_SAPI !== 'cli-server') {
$_SERVER['PHP_CLI_ROUTER'] = true;
-if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) {
- return false;
+$root = $_SERVER['DOCUMENT_ROOT'];
+$path = $_SERVER['SCRIPT_NAME'];
+if ($path !== '/index.php' && is_file($root . $path)) {
+ if (!(
+ // Block all direct access to files and folders beginning with a dot
+ strpos($path, '/.') !== false
+ // Block all direct access for these folders
+ || preg_match('`^/(\.git|cache|bin|logs|backup|webserver-configs|tests)/`ui', $path)
+ // Block access to specific file types for these system folders
+ || preg_match('`^/(system|vendor)/(.*)\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
+ // Block access to specific file types for these user folders
+ || preg_match('`^/(user)/(.*)\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
+ // Block all direct access to .md files
+ || preg_match('`\.md$`ui', $path)
+ // Block access to specific files in the root folder
+ || preg_match('`^/(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$`ui', $path)
+ )) {
+ return false;
+ }
}
$grav_index = 'index.php';
diff --git a/system/src/Grav/Common/Backup/Backups.php b/system/src/Grav/Common/Backup/Backups.php
index b70def96f..4680f8546 100644
--- a/system/src/Grav/Common/Backup/Backups.php
+++ b/system/src/Grav/Common/Backup/Backups.php
@@ -144,9 +144,8 @@ class Backups
public static function getTotalBackupsSize()
{
$backups = static::getAvailableBackups();
- $size = array_sum(array_column($backups, 'size'));
- return $size ?? 0;
+ return array_sum(array_column($backups, 'size'));
}
/**
diff --git a/system/src/Grav/Common/Data/BlueprintSchema.php b/system/src/Grav/Common/Data/BlueprintSchema.php
index 7990bec29..db5e6af7d 100644
--- a/system/src/Grav/Common/Data/BlueprintSchema.php
+++ b/system/src/Grav/Common/Data/BlueprintSchema.php
@@ -83,7 +83,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
}
if (!empty($messages)) {
- throw (new ValidationException())->setMessages($messages);
+ throw (new ValidationException('', 400))->setMessages($messages);
}
}
@@ -199,7 +199,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
/** @var Config $config */
$config = Grav::instance()['config'];
if (!$config->get('system.strict_mode.blueprint_strict_compat', true)) {
- throw new RuntimeException(sprintf('%s is not defined in blueprints', $key));
+ throw new RuntimeException(sprintf('%s is not defined in blueprints', $key), 400);
}
user_error(sprintf('Having extra key %s in your data is deprecated with blueprint having \'validation: strict\'', $key), E_USER_DEPRECATED);
diff --git a/system/src/Grav/Common/Data/ValidationException.php b/system/src/Grav/Common/Data/ValidationException.php
index 2d94ab816..be6a674d7 100644
--- a/system/src/Grav/Common/Data/ValidationException.php
+++ b/system/src/Grav/Common/Data/ValidationException.php
@@ -10,16 +10,18 @@
namespace Grav\Common\Data;
use Grav\Common\Grav;
+use JsonSerializable;
use RuntimeException;
/**
* Class ValidationException
* @package Grav\Common\Data
*/
-class ValidationException extends RuntimeException
+class ValidationException extends RuntimeException implements JsonSerializable
{
/** @var array */
protected $messages = [];
+ protected $escape = true;
/**
* @param array $messages
@@ -32,21 +34,34 @@ class ValidationException extends RuntimeException
$language = Grav::instance()['language'];
$this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
- foreach ($messages as $variable => &$list) {
+ foreach ($messages as $list) {
$list = array_unique($list);
foreach ($list as $message) {
- $this->message .= "
$message";
+ $this->message .= '
' . htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
}
return $this;
}
+ public function setSimpleMessage(bool $escape = true): void
+ {
+ $first = reset($this->messages);
+ $message = reset($first);
+
+ $this->message = $escape ? htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $message;
+ }
+
/**
* @return array
*/
- public function getMessages()
+ public function getMessages(): array
{
return $this->messages;
}
+
+ public function jsonSerialize(): array
+ {
+ return ['validation' => $this->messages];
+ }
}
diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
index f02a64866..5a7d773a4 100644
--- a/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
+++ b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
@@ -554,6 +554,9 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
$filters = array_filter($filters, static function($val) { return $val !== null && $val !== ''; });
if ($page) {
+ $status = 'success';
+ $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
+
if ($page->root() && (!$filter_type || in_array('root', $filter_type, true))) {
if ($field) {
$response[] = [
@@ -593,9 +596,6 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
}
}
- $status = 'success';
- $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
-
/** @var PageIndex $children */
$children = $page->children()->getIndex();
$selectedChildren = $children->filterBy($filters, true);
@@ -721,7 +721,7 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
$response = Utils::arrayFlatten($sorted);
}
- return [$status, $msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED', $response, $path];
+ return [$status, $msg, $response, $path];
}
/**
diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php
index f5919613b..2398c116c 100644
--- a/system/src/Grav/Common/Grav.php
+++ b/system/src/Grav/Common/Grav.php
@@ -464,6 +464,10 @@ class Grav extends Container
}
}
+ if ($uri->extension() === 'json') {
+ return new Response(200, ['Content-Type' => 'application/json'], json_encode(['code' => $code, 'redirect' => $url], JSON_THROW_ON_ERROR));
+ }
+
return new Response($code, ['Location' => $url]);
}
@@ -774,11 +778,9 @@ class Grav extends Container
}
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
}
-
- // Nothing found
- return false;
}
- return $page ?? false;
+ // Nothing found
+ return false;
}
}
diff --git a/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php b/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php
index 5156ede29..d9d3105b6 100644
--- a/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php
+++ b/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php
@@ -58,7 +58,7 @@ interface PageContentInterface
/**
* Needed by the onPageContentProcessed event to set the raw page content
*
- * @param string $content
+ * @param string|null $content
*/
public function setRawContent($content);
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index 2099be212..9e68a44f8 100644
--- a/system/src/Grav/Common/Page/Page.php
+++ b/system/src/Grav/Common/Page/Page.php
@@ -218,6 +218,25 @@ class Page implements PageInterface
return $this;
}
+ public function __clone()
+ {
+ $this->initialized = false;
+ $this->header = $this->header ? clone $this->header : null;
+ }
+
+ /**
+ * @return void
+ */
+ public function initialize(): void
+ {
+ if (!$this->initialized) {
+ $this->initialized = true;
+ $this->route = null;
+ $this->raw_route = null;
+ $this->_forms = null;
+ }
+ }
+
/**
* @return void
*/
@@ -975,7 +994,7 @@ class Page implements PageInterface
/**
* Needed by the onPageContentProcessed event to set the raw page content
*
- * @param string $content
+ * @param string|null $content
* @return void
*/
public function setRawContent($content)
diff --git a/system/src/Grav/Common/Processors/InitializeProcessor.php b/system/src/Grav/Common/Processors/InitializeProcessor.php
index 55cba033c..b31530e80 100644
--- a/system/src/Grav/Common/Processors/InitializeProcessor.php
+++ b/system/src/Grav/Common/Processors/InitializeProcessor.php
@@ -44,7 +44,7 @@ class InitializeProcessor extends ProcessorBase
public $title = 'Initialize';
/** @var bool */
- private static $cli_initialized = false;
+ protected static $cli_initialized = false;
/**
* @param Grav $grav
diff --git a/system/src/Grav/Common/Processors/PagesProcessor.php b/system/src/Grav/Common/Processors/PagesProcessor.php
index 470ca907b..36f6718d2 100644
--- a/system/src/Grav/Common/Processors/PagesProcessor.php
+++ b/system/src/Grav/Common/Processors/PagesProcessor.php
@@ -42,15 +42,29 @@ class PagesProcessor extends ProcessorBase
$this->container['debugger']->addMessage($this->container['cache']->getCacheStatus());
$this->container['pages']->init();
- $this->container->fireEvent('onPagesInitialized', new Event(['pages' => $this->container['pages']]));
- $this->container->fireEvent('onPageInitialized', new Event(['page' => $this->container['page']]));
+
+ $route = $this->container['route'];
+
+ $this->container->fireEvent('onPagesInitialized', new Event(
+ [
+ 'pages' => $this->container['pages'],
+ 'route' => $route,
+ 'request' => $request
+ ]
+ ));
+ $this->container->fireEvent('onPageInitialized', new Event(
+ [
+ 'page' => $this->container['page'],
+ 'route' => $route,
+ 'request' => $request
+ ]
+ ));
/** @var PageInterface $page */
$page = $this->container['page'];
if (!$page->routable()) {
$exception = new RequestException($request, 'Page Not Found', 404);
- $route = $this->container['route'];
// If no page found, fire event
$event = new Event([
'page' => $page,
diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php
index b56f62fde..3a5c2e27b 100644
--- a/system/src/Grav/Common/Service/ConfigServiceProvider.php
+++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php
@@ -179,7 +179,7 @@ class ConfigServiceProvider implements ServiceProviderInterface
* @param string $folder_path
* @return array
*/
- private static function pluginFolderPaths($plugins, $folder_path)
+ protected static function pluginFolderPaths($plugins, $folder_path)
{
$paths = [];
diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php
index 638617c4d..4c0d9fe46 100644
--- a/system/src/Grav/Common/Twig/Extension/GravExtension.php
+++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php
@@ -847,6 +847,12 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
if (($numargs === 3 && is_array($args[1])) || ($numargs === 2 && !is_array($args[1]))) {
$lang = array_pop($args);
+ /** @var Language $language */
+ $language = $this->grav['language'];
+ if (is_string($lang) && !$language->getLanguageCode($lang)) {
+ $args[] = $lang;
+ $lang = null;
+ }
} elseif ($numargs === 2 && is_array($args[1])) {
$subs = array_pop($args);
$args = array_merge($args, $subs);
@@ -1354,7 +1360,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
*/
public function vardumpFunc($var)
{
- var_dump($var);
+ dump($var);
}
/**
@@ -1418,7 +1424,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
* @param array $context Twig Context
* @param string $var variable to be found (using dot notation)
* @param null $default the default value to be used as last resort
- * @param null $page an optional page to use for the current page
+ * @param PageInterface|null $page an optional page to use for the current page
* @param bool $exists toggle to simply return the page where the variable is set, else null
* @return mixed
*/
diff --git a/system/src/Grav/Common/User/Group.php b/system/src/Grav/Common/User/Group.php
index 5be53869e..7dd6d650c 100644
--- a/system/src/Grav/Common/User/Group.php
+++ b/system/src/Grav/Common/User/Group.php
@@ -27,7 +27,7 @@ class Group extends Data
* @return array
* @deprecated 1.7, use $grav['user_groups'] Flex UserGroupCollection instead
*/
- private static function groups()
+ protected static function groups()
{
user_error(__METHOD__ . '() is deprecated since Grav 1.7, use $grav[\'user_groups\'] Flex UserGroupCollection instead', E_USER_DEPRECATED);
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index f843a0c66..5295a6400 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -21,6 +21,7 @@ use Grav\Common\Page\Markdown\Excerpts;
use Grav\Common\Page\Pages;
use Grav\Framework\Flex\Flex;
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
+use Grav\Framework\Media\Interfaces\MediaInterface;
use InvalidArgumentException;
use Negotiation\Accept;
use Negotiation\Negotiator;
@@ -150,7 +151,7 @@ abstract class Utils
$domain = $domain ?: $grav['config']->get('system.absolute_urls', false);
- return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
+ return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?: '');
}
/**
@@ -1566,7 +1567,7 @@ abstract class Utils
switch ($matches[0]) {
case 'self':
- if (null === $object) {
+ if (!$object instanceof MediaInterface) {
throw new RuntimeException(sprintf('Page not available for self@ reference: %s', $path));
}
@@ -1640,7 +1641,7 @@ abstract class Utils
* @param string $path
* @return string[]|null
*/
- private static function resolveTokenPath(string $path): ?array
+ protected static function resolveTokenPath(string $path): ?array
{
if (strpos($path, '@') !== false) {
$regex = '/^(@\w+|\w+@|@\w+@)([^:]*)(.*)$/u';
@@ -1774,7 +1775,7 @@ abstract class Utils
*
* @param string $string
* @param bool $block Block or Line processing
- * @param null $page
+ * @param PageInterface|null $page
* @return string
* @throws Exception
*/
diff --git a/system/src/Grav/Common/Yaml.php b/system/src/Grav/Common/Yaml.php
index 5adbd0c10..330b6c383 100644
--- a/system/src/Grav/Common/Yaml.php
+++ b/system/src/Grav/Common/Yaml.php
@@ -17,8 +17,8 @@ use Grav\Framework\File\Formatter\YamlFormatter;
*/
abstract class Yaml
{
- /** @var YamlFormatter */
- private static $yaml;
+ /** @var YamlFormatter|null */
+ protected static $yaml;
/**
* @param string $data
@@ -51,7 +51,7 @@ abstract class Yaml
/**
* @return void
*/
- private static function init()
+ protected static function init()
{
$config = [
'inline' => 5,
diff --git a/system/src/Grav/Console/Gpm/IndexCommand.php b/system/src/Grav/Console/Gpm/IndexCommand.php
index 64d359781..ecc91feb0 100644
--- a/system/src/Grav/Console/Gpm/IndexCommand.php
+++ b/system/src/Grav/Console/Gpm/IndexCommand.php
@@ -200,7 +200,6 @@ class IndexCommand extends GpmCommand
*/
private function installed(Package $package): string
{
- $package = $list[$package->slug] ?? $package;
$type = ucfirst(preg_replace('/s$/', '', $package->package_type));
$method = 'is' . $type . 'Installed';
$installed = $this->gpm->{$method}($package->slug);
@@ -214,7 +213,6 @@ class IndexCommand extends GpmCommand
*/
private function enabled(Package $package): string
{
- $package = $list[$package->slug] ?? $package;
$type = ucfirst(preg_replace('/s$/', '', $package->package_type));
$method = 'is' . $type . 'Installed';
$installed = $this->gpm->{$method}($package->slug);
diff --git a/system/src/Grav/Framework/Acl/PermissionsReader.php b/system/src/Grav/Framework/Acl/PermissionsReader.php
index 4f4a6a317..b157a5d3b 100644
--- a/system/src/Grav/Framework/Acl/PermissionsReader.php
+++ b/system/src/Grav/Framework/Acl/PermissionsReader.php
@@ -21,7 +21,7 @@ use function is_array;
class PermissionsReader
{
/** @var array */
- private static $types;
+ protected static $types;
/**
* @param string $filename
diff --git a/system/src/Grav/Framework/Collection/AbstractFileCollection.php b/system/src/Grav/Framework/Collection/AbstractFileCollection.php
index f89226747..1be8d129a 100644
--- a/system/src/Grav/Framework/Collection/AbstractFileCollection.php
+++ b/system/src/Grav/Framework/Collection/AbstractFileCollection.php
@@ -25,9 +25,9 @@ use function array_slice;
*
* @package Grav\Framework\Collection
* @template TKey of array-key
- * @template T
+ * @template T of object
* @extends AbstractLazyCollection
- * @mplements FileCollectionInterface
+ * @implements FileCollectionInterface
*/
class AbstractFileCollection extends AbstractLazyCollection implements FileCollectionInterface
{
diff --git a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
index 38d3ab51e..25d8672c0 100644
--- a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
+++ b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
@@ -22,6 +22,7 @@ use function count;
* Abstract Index Collection.
* @template TKey of array-key
* @template T
+ * @template C of CollectionInterface
* @implements CollectionInterface
*/
abstract class AbstractIndexCollection implements CollectionInterface
@@ -184,7 +185,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
- return $this->remove($offset);
+ $this->remove($offset);
}
/**
@@ -365,7 +366,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
* @param int $start
* @param int|null $limit
* @return static
- * @phpstan-return static
+ * @phpstan-return static
*/
public function limit($start, $limit = null)
{
@@ -376,7 +377,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
* Reverse the order of the items.
*
* @return static
- * @phpstan-return static
+ * @phpstan-return static
*/
public function reverse()
{
@@ -387,7 +388,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
* Shuffle items.
*
* @return static
- * @phpstan-return static
+ * @phpstan-return static
*/
public function shuffle()
{
@@ -404,7 +405,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* @param array $keys
* @return static
- * @phpstan-return static
+ * @phpstan-return static
*/
public function select(array $keys)
{
@@ -423,7 +424,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* @param array $keys
* @return static
- * @phpstan-return static
+ * @phpstan-return static
*/
public function unselect(array $keys)
{
@@ -478,7 +479,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* @param array $entries Elements.
* @return static
- * @phpstan-return static
+ * @phpstan-return static
*/
protected function createFrom(array $entries)
{
@@ -531,7 +532,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
/**
* @param array|null $entries
* @return CollectionInterface
- * @phpstan-return T
+ * @phpstan-return C
*/
abstract protected function loadCollection(array $entries = null): CollectionInterface;
diff --git a/system/src/Grav/Framework/Collection/CollectionInterface.php b/system/src/Grav/Framework/Collection/CollectionInterface.php
index 42414cecd..0739109ba 100644
--- a/system/src/Grav/Framework/Collection/CollectionInterface.php
+++ b/system/src/Grav/Framework/Collection/CollectionInterface.php
@@ -26,7 +26,7 @@ interface CollectionInterface extends Collection, JsonSerializable
* Reverse the order of the items.
*
* @return CollectionInterface
- * @phpstan-return CollectionInterface
+ * @phpstan-return static
*/
public function reverse();
@@ -34,7 +34,7 @@ interface CollectionInterface extends Collection, JsonSerializable
* Shuffle items.
*
* @return CollectionInterface
- * @phpstan-return CollectionInterface
+ * @phpstan-return static
*/
public function shuffle();
@@ -53,7 +53,7 @@ interface CollectionInterface extends Collection, JsonSerializable
*
* @param array $keys
* @return CollectionInterface
- * @phpstan-return CollectionInterface
+ * @phpstan-return static
*/
public function select(array $keys);
@@ -62,7 +62,7 @@ interface CollectionInterface extends Collection, JsonSerializable
*
* @param array $keys
* @return CollectionInterface
- * @phpstan-return CollectionInterface
+ * @phpstan-return static
*/
public function unselect(array $keys);
}
diff --git a/system/src/Grav/Framework/Collection/FileCollection.php b/system/src/Grav/Framework/Collection/FileCollection.php
index 59df2210e..f7b4f258b 100644
--- a/system/src/Grav/Framework/Collection/FileCollection.php
+++ b/system/src/Grav/Framework/Collection/FileCollection.php
@@ -9,13 +9,13 @@
namespace Grav\Framework\Collection;
+use stdClass;
+
/**
* Collection of objects stored into a filesystem.
*
* @package Grav\Framework\Collection
- * @template TKey of array-key
- * @template T
- * @extends AbstractFileCollection
+ * @extends AbstractFileCollection
*/
class FileCollection extends AbstractFileCollection
{
diff --git a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php
index 62ed3e103..afa08aa42 100644
--- a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php
+++ b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php
@@ -12,12 +12,14 @@ declare(strict_types=1);
namespace Grav\Framework\Controller\Traits;
use Grav\Common\Config\Config;
+use Grav\Common\Data\ValidationException;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Framework\Psr7\Response;
use Grav\Framework\RequestHandler\Exception\RequestException;
use Grav\Framework\Route\Route;
+use JsonSerializable;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
@@ -203,7 +205,14 @@ trait ControllerResponseTrait
protected function getErrorJson(Throwable $e): array
{
$code = $this->getErrorCode($e instanceof RequestException ? $e->getHttpCode() : $e->getCode());
- $message = $e->getMessage();
+ if ($e instanceof ValidationException) {
+ $message = $e->getMessage();
+ } else {
+ $message = htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8');
+ }
+
+ $extra = $e instanceof JsonSerializable ? $e->jsonSerialize() : [];
+
$response = [
'code' => $code,
'status' => 'error',
@@ -211,7 +220,7 @@ trait ControllerResponseTrait
'error' => [
'code' => $code,
'message' => $message
- ]
+ ] + $extra
];
/** @var Debugger $debugger */
diff --git a/system/src/Grav/Framework/Flex/Flex.php b/system/src/Grav/Framework/Flex/Flex.php
index 87392823d..ce285215c 100644
--- a/system/src/Grav/Framework/Flex/Flex.php
+++ b/system/src/Grav/Framework/Flex/Flex.php
@@ -256,7 +256,7 @@ class Flex implements FlexInterface
}
// Remove missing objects if not asked to keep them.
- if (empty($option['keep_missing'])) {
+ if (empty($options['keep_missing'])) {
$list = array_filter($list);
}
diff --git a/system/src/Grav/Framework/Flex/FlexDirectoryForm.php b/system/src/Grav/Framework/Flex/FlexDirectoryForm.php
index 66b6f3e14..8d608fdf8 100644
--- a/system/src/Grav/Framework/Flex/FlexDirectoryForm.php
+++ b/system/src/Grav/Framework/Flex/FlexDirectoryForm.php
@@ -99,7 +99,7 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
$this->setFlashLookupFolder($directory->getDirectoryBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
$this->form = $options['form'] ?? null;
- if (Utils::isPositive($this->items['disabled'] ?? $this->form['disabled'] ?? false)) {
+ if (Utils::isPositive($this->form['disabled'] ?? false)) {
$this->disable();
}
diff --git a/system/src/Grav/Framework/Flex/FlexIndex.php b/system/src/Grav/Framework/Flex/FlexIndex.php
index 1df5c4d0d..3ce0a46c4 100644
--- a/system/src/Grav/Framework/Flex/FlexIndex.php
+++ b/system/src/Grav/Framework/Flex/FlexIndex.php
@@ -35,7 +35,7 @@ use function in_array;
* @package Grav\Framework\Flex
* @template T of FlexObjectInterface
* @template C of FlexCollectionInterface
- * @extends ObjectIndex
+ * @extends ObjectIndex
* @implements FlexIndexInterface
* @mixin C
*/
diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php
index 945f5adfe..ac51b3461 100644
--- a/system/src/Grav/Framework/Flex/FlexObject.php
+++ b/system/src/Grav/Framework/Flex/FlexObject.php
@@ -12,6 +12,7 @@ namespace Grav\Framework\Flex;
use ArrayAccess;
use Exception;
use Grav\Common\Data\Blueprint;
+use Grav\Common\Data\Data;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Inflector;
@@ -72,8 +73,6 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
private $_meta;
/** @var array */
protected $_original;
- /** @var array */
- protected $_changes;
/** @var string */
protected $storage_key;
/** @var int */
@@ -454,13 +453,50 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
}
/**
- * Get any changes based on data sent to update
+ * Get diff array from the object.
+ *
+ * @return array
+ */
+ public function getDiff(): array
+ {
+ $blueprint = $this->getBlueprint();
+
+ $flattenOriginal = $blueprint->flattenData($this->getOriginalData());
+ $flattenElements = $blueprint->flattenData($this->getElements());
+ $removedElements = array_diff_key($flattenOriginal, $flattenElements);
+ $diff = [];
+
+ // Include all added or changed keys.
+ foreach ($flattenElements as $key => $value) {
+ $orig = $flattenOriginal[$key] ?? null;
+ if ($orig !== $value) {
+ $diff[$key] = ['old' => $orig, 'new' => $value];
+ }
+ }
+
+ // Include all removed keys.
+ foreach ($removedElements as $key => $value) {
+ $diff[$key] = ['old' => $value, 'new' => null];
+ }
+
+ return $diff;
+ }
+
+ /**
+ * Get any changes from the object.
*
* @return array
*/
public function getChanges(): array
{
- return $this->_changes ?? [];
+ $diff = $this->getDiff();
+
+ $data = new Data();
+ foreach ($diff as $key => $change) {
+ $data->set($key, $change['new']);
+ }
+
+ return $data->toArray();
}
/**
@@ -641,14 +677,19 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
public function update(array $data, array $files = [])
{
if ($data) {
+ // Get currently stored data.
+ $elements = $this->getElements();
+
+ // Store original version of the object.
+ if ($this->_original === null) {
+ $this->_original = $elements;
+ }
+
$blueprint = $this->getBlueprint();
// Process updated data through the object filters.
$this->filterElements($data);
- // Get currently stored data.
- $elements = $this->getElements();
-
// Merge existing object to the test data to be validated.
$test = $blueprint->mergeData($elements, $data);
@@ -657,17 +698,14 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
$data = $blueprint->filter($data, true, true);
// Finally update the object.
- foreach ($blueprint->flattenData($data) as $key => $value) {
+ $flattenData = $blueprint->flattenData($data);
+ foreach ($flattenData as $key => $value) {
if ($value === null) {
$this->unsetNestedProperty($key);
} else {
$this->setNestedProperty($key, $value);
}
}
-
- // Store the changes
- $this->_original = $this->getElements();
- $this->_changes = Utils::arrayDiffMultidimensional($this->_original, $elements);
}
if ($files && method_exists($this, 'setUpdatedMedia')) {
diff --git a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
index ff2c94d7a..b2b9533a2 100644
--- a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
+++ b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
@@ -224,6 +224,7 @@ class FolderStorage extends AbstractFilesystemStorage
* @param string $src
* @param string $dst
* @return bool
+ * @throws RuntimeException
*/
public function copyRow(string $src, string $dst): bool
{
@@ -247,6 +248,7 @@ class FolderStorage extends AbstractFilesystemStorage
/**
* {@inheritdoc}
* @see FlexStorageInterface::renameRow()
+ * @throws RuntimeException
*/
public function renameRow(string $src, string $dst): bool
{
diff --git a/system/src/Grav/Framework/Form/Traits/FormTrait.php b/system/src/Grav/Framework/Form/Traits/FormTrait.php
index 4362cf5e4..d77a40274 100644
--- a/system/src/Grav/Framework/Form/Traits/FormTrait.php
+++ b/system/src/Grav/Framework/Form/Traits/FormTrait.php
@@ -711,7 +711,7 @@ trait FormTrait
return [
$data,
- $files ?? []
+ $files
];
}
diff --git a/system/src/Grav/Framework/Logger/Processors/UserProcessor.php b/system/src/Grav/Framework/Logger/Processors/UserProcessor.php
new file mode 100644
index 000000000..885b4f0a8
--- /dev/null
+++ b/system/src/Grav/Framework/Logger/Processors/UserProcessor.php
@@ -0,0 +1,34 @@
+exists()) {
+ $record['extra']['user'] = ['username' => $user->username, 'email' => $user->email];
+ }
+
+ return $record;
+ }
+}
diff --git a/system/src/Grav/Framework/Object/ObjectIndex.php b/system/src/Grav/Framework/Object/ObjectIndex.php
index b7b416d0a..50ac4271b 100644
--- a/system/src/Grav/Framework/Object/ObjectIndex.php
+++ b/system/src/Grav/Framework/Object/ObjectIndex.php
@@ -24,8 +24,9 @@ use function is_object;
* order to use the class.
*
* @template TKey of array-key
- * @template T
- * @extends AbstractIndexCollection
+ * @template T of \Grav\Framework\Object\Interfaces\ObjectInterface
+ * @template C of \Grav\Framework\Collection\CollectionInterface
+ * @extends AbstractIndexCollection
* @implements NestedObjectCollectionInterface
*/
abstract class ObjectIndex extends AbstractIndexCollection implements NestedObjectCollectionInterface
@@ -176,7 +177,7 @@ abstract class ObjectIndex extends AbstractIndexCollection implements NestedObje
* Create a copy from this collection by cloning all objects in the collection.
*
* @return static
- * @return static
+ * @return static
*/
public function copy()
{
diff --git a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php
index a9935eeb0..9eadf72e3 100644
--- a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php
+++ b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php
@@ -11,9 +11,12 @@ declare(strict_types=1);
namespace Grav\Framework\RequestHandler\Middlewares;
+use Grav\Common\Data\ValidationException;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Framework\Psr7\Response;
+use JsonException;
+use JsonSerializable;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
@@ -27,16 +30,34 @@ use function get_class;
*/
class Exceptions implements MiddlewareInterface
{
+ /**
+ * @param ServerRequestInterface $request
+ * @param RequestHandlerInterface $handler
+ * @return ResponseInterface
+ * @throws JsonException
+ */
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
try {
return $handler->handle($request);
} catch (Throwable $exception) {
+ $code = $exception->getCode();
+ if ($exception instanceof ValidationException) {
+ $message = $exception->getMessage();
+ } else {
+ $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8');
+ }
+
+ $extra = $exception instanceof JsonSerializable ? $exception->jsonSerialize() : [];
+
$response = [
+ 'code' => $code,
+ 'status' => 'error',
+ 'message' => $message,
'error' => [
- 'code' => $exception->getCode(),
- 'message' => $exception->getMessage(),
- ]
+ 'code' => $code,
+ 'message' => $message,
+ ] + $extra
];
/** @var Debugger $debugger */
@@ -51,9 +72,9 @@ class Exceptions implements MiddlewareInterface
}
/** @var string $json */
- $json = json_encode($response);
+ $json = json_encode($response, JSON_THROW_ON_ERROR);
- return new Response($exception->getCode() ?: 500, ['Content-Type' => 'application/json'], $json);
+ return new Response($code ?: 500, ['Content-Type' => 'application/json'], $json);
}
}
}
diff --git a/tests/phpstan/phpstan.neon b/tests/phpstan/phpstan.neon
index c41ae9bfe..12dc402a2 100644
--- a/tests/phpstan/phpstan.neon
+++ b/tests/phpstan/phpstan.neon
@@ -8,7 +8,7 @@ parameters:
- dist
bootstrapFiles:
- phpstan-bootstrap.php
- excludes_analyse:
+ excludePaths:
- */system/src/Grav/Common/Errors/Resources/layout.html.php
- */system/src/Twig/DeferredExtension/DeferredNodeVisitor.php
diff --git a/tests/phpstan/plugins.neon b/tests/phpstan/plugins.neon
index 8ef5700f6..ae257f80e 100644
--- a/tests/phpstan/plugins.neon
+++ b/tests/phpstan/plugins.neon
@@ -4,7 +4,7 @@ includes:
parameters:
fileExtensions:
- php
- excludes_analyse:
+ excludePaths:
- %currentWorkingDirectory%/user/plugins/*/vendor/*
- %currentWorkingDirectory%/user/plugins/*/tests/*
- %currentWorkingDirectory%/user/plugins/gantry5/src/platforms