diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a6091c..b0c0eb3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,66 @@
+# 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
+
+1. [](#new)
+ * Added support for image watermarks
+ * Added support to disable a form, making it readonly
+2. [](#improved)
+ * Flex `$user->authorize()` now checks user groups before `admin.super`, allowing deny rules to work properly
+3. [](#bugfix)
+ * Fixed a bug in `PermissionsReader` in PHP 7.3
+ * Fixed `session_store_active` language option (#3464)
+ * Fixed deprecated warnings on `ArrayAccess` in PHP 8.1
+ * Fixed XSS detection with `:`
+
+# v1.7.23
+## 09/29/2021
+
+1. [](#new)
+ * Added method `Pages::referrerRoute()` to get the referrer route and language
+ * Added true unique `Utils::uniqueId()` / `{{ unique_id() }}` utilities with length, prefix, and suffix support
+ * Added `UserObject::isMyself()` method to check if flex user is currently logged in
+ * Added support for custom form field options validation with `validate: options: key|ignore`
+2. [](#improved)
+ * Replaced GPL `SVG-Sanitizer` with MIT licensed `DOM-Sanitizer`
+ * `Uri::referrer()` now accepts third parameter, if set to `true`, it returns route without base or language code [#3411](https://github.com/getgrav/grav/issues/3411)
+ * Updated vendor libs with latest
+ * Updated with latest language strings via Crowdin.com
+3. [](#bugfix)
+ * Fixed `Folder::move()` throwing an error when target folder is changed by only appending characters to the end [#3445](https://github.com/getgrav/grav/issues/3445)
+ * Fixed some phpstan issues (all code back to level 1, Framework level 3)
+ * Fixed form reset causing image uploads to fail when using Flex
+
+# v1.7.22
+## 09/16/2021
+
+1. [](#new)
+ * Register plugin autoloaders into plugin objects
+2. [](#improved)
+ * Improve Twig 2 compatibility
+ * Update to customized version of Twig DeferredExtension (Twig 1/2 compatible)
+3. [](#bugfix)
+ * Fixed conflicting `$_original` variable in `Flex Pages`
+
# v1.7.21
## 09/14/2021
diff --git a/composer.json b/composer.json
index c3fb23e..42535ab 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",
@@ -55,17 +56,16 @@
"miljar/php-exif": "^0.6",
"composer/ca-bundle": "^1.2",
"dragonmantank/cron-expression": "^1.2",
- "phive/twig-extensions-deferred": "^1.0",
"willdurand/negotiation": "^3.0",
"itsgoingd/clockwork": "^5.0",
- "enshrined/svg-sanitize": "~0.13",
"symfony/http-client": "^4.4",
- "composer/semver": "^1.4"
+ "composer/semver": "^1.4",
+ "rhukster/dom-sanitizer": "^1.0"
},
"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",
@@ -93,7 +93,8 @@
},
"autoload": {
"psr-4": {
- "Grav\\": "system/src/Grav"
+ "Grav\\": "system/src/Grav",
+ "Twig\\": "system/src/Twig"
},
"files": [
"system/defines.php"
@@ -107,8 +108,8 @@
"scripts": {
"api-17": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.17.md",
"post-create-project-cmd": "bin/grav install",
- "phpstan": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src",
- "phpstan-framework": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src/Grav/Framework system/src/Grav/Events system/src/Grav/Installer",
+ "phpstan": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=520M system/src",
+ "phpstan-framework": "vendor/bin/phpstan analyse -l 3 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src/Grav/Framework system/src/Grav/Events system/src/Grav/Installer",
"phpstan-plugins": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/plugins.neon --memory-limit=400M user/plugins",
"test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit"
diff --git a/composer.lock b/composer.lock
index 8d2907f..3b66c8a 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": "36375d8a5daf3aef0341f9a2b023f4e9",
+ "content-hash": "072f00e1bf64b4ef43f7125fe80b15a7",
"packages": [
{
"name": "antoligy/dom-string-iterators",
@@ -56,16 +56,16 @@
},
{
"name": "composer/ca-bundle",
- "version": "1.2.10",
+ "version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "9fdb22c2e97a614657716178093cd1da90a64aa8"
+ "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/9fdb22c2e97a614657716178093cd1da90a64aa8",
- "reference": "9fdb22c2e97a614657716178093cd1da90a64aa8",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b",
+ "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b",
"shasum": ""
},
"require": {
@@ -77,7 +77,7 @@
"phpstan/phpstan": "^0.12.55",
"psr/log": "^1.0",
"symfony/phpunit-bridge": "^4.2 || ^5",
- "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
+ "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0"
},
"type": "library",
"extra": {
@@ -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.10"
+ "source": "https://github.com/composer/ca-bundle/tree/1.3.1"
},
"funding": [
{
@@ -128,7 +128,7 @@
"type": "tidelift"
}
],
- "time": "2021-06-07T13:58:28+00:00"
+ "time": "2021-10-28T20:44:15+00:00"
},
{
"name": "composer/semver",
@@ -380,20 +380,20 @@
},
{
"name": "donatj/phpuseragentparser",
- "version": "v1.4.0",
+ "version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/donatj/PhpUserAgent.git",
- "reference": "246c1cf0a44f07168c702203bf30d5f48f17bab0"
+ "reference": "cc9d872cddfc180c52d084d0dff1e4aad653d37f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/246c1cf0a44f07168c702203bf30d5f48f17bab0",
- "reference": "246c1cf0a44f07168c702203bf30d5f48f17bab0",
+ "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/cc9d872cddfc180c52d084d0dff1e4aad653d37f",
+ "reference": "cc9d872cddfc180c52d084d0dff1e4aad653d37f",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=5.4.0"
},
"require-dev": {
"camspiers/json-pretty": "~1.0",
@@ -432,7 +432,7 @@
],
"support": {
"issues": "https://github.com/donatj/PhpUserAgent/issues",
- "source": "https://github.com/donatj/PhpUserAgent/tree/v1.4.0"
+ "source": "https://github.com/donatj/PhpUserAgent/tree/v1.5.0"
},
"funding": [
{
@@ -444,7 +444,7 @@
"type": "github"
}
],
- "time": "2021-03-16T16:25:14+00:00"
+ "time": "2021-09-16T17:05:03+00:00"
},
{
"name": "dragonmantank/cron-expression",
@@ -493,52 +493,6 @@
},
"time": "2017-01-23T04:29:33+00:00"
},
- {
- "name": "enshrined/svg-sanitize",
- "version": "0.14.1",
- "source": {
- "type": "git",
- "url": "https://github.com/darylldoyle/svg-sanitizer.git",
- "reference": "307b42066fb0b76b5119f5e1f0826e18fefabe95"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/307b42066fb0b76b5119f5e1f0826e18fefabe95",
- "reference": "307b42066fb0b76b5119f5e1f0826e18fefabe95",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-libxml": "*",
- "php": "^7.0 || ^8.0"
- },
- "require-dev": {
- "codeclimate/php-test-reporter": "^0.1.2",
- "phpunit/phpunit": "^6.5 || ^8.5"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "enshrined\\svgSanitize\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "GPL-2.0-or-later"
- ],
- "authors": [
- {
- "name": "Daryll Doyle",
- "email": "daryll@enshrined.co.uk"
- }
- ],
- "description": "An SVG sanitizer for PHP",
- "support": {
- "issues": "https://github.com/darylldoyle/svg-sanitizer/issues",
- "source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.14.1"
- },
- "time": "2021-08-09T23:46:54+00:00"
- },
{
"name": "erusev/parsedown",
"version": "1.7.4",
@@ -642,21 +596,21 @@
},
{
"name": "filp/whoops",
- "version": "2.14.1",
+ "version": "2.14.4",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "15ead64e9828f0fc90932114429c4f7923570cb1"
+ "reference": "f056f1fe935d9ed86e698905a957334029899895"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/15ead64e9828f0fc90932114429c4f7923570cb1",
- "reference": "15ead64e9828f0fc90932114429c4f7923570cb1",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/f056f1fe935d9ed86e698905a957334029899895",
+ "reference": "f056f1fe935d9ed86e698905a957334029899895",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
- "psr/log": "^1.0.1"
+ "psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"mockery/mockery": "^0.9 || ^1.0",
@@ -701,7 +655,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.14.1"
+ "source": "https://github.com/filp/whoops/tree/2.14.4"
},
"funding": [
{
@@ -709,7 +663,7 @@
"type": "github"
}
],
- "time": "2021-08-29T12:00:00+00:00"
+ "time": "2021-10-03T12:00:00+00:00"
},
{
"name": "getgrav/cache",
@@ -824,16 +778,16 @@
},
{
"name": "guzzlehttp/psr7",
- "version": "1.8.2",
+ "version": "1.8.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "dc960a912984efb74d0a90222870c72c87f10c91"
+ "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91",
- "reference": "dc960a912984efb74d0a90222870c72c87f10c91",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85",
+ "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85",
"shasum": ""
},
"require": {
@@ -870,13 +824,34 @@
"MIT"
],
"authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
{
"name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
@@ -893,28 +868,42 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/1.8.2"
+ "source": "https://github.com/guzzle/psr7/tree/1.8.3"
},
- "time": "2021-04-26T09:17:50+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-05T13:56:00+00:00"
},
{
"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": {
@@ -956,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": [
{
@@ -964,7 +953,7 @@
"type": "github"
}
],
- "time": "2021-08-07T23:04:17+00:00"
+ "time": "2021-11-01T17:38:35+00:00"
},
{
"name": "league/climate",
@@ -1164,21 +1153,21 @@
},
{
"name": "maximebf/debugbar",
- "version": "v1.17.1",
+ "version": "v1.17.3",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
- "reference": "0a3532556be0145603f8a9de23e76dc28eed7054"
+ "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0a3532556be0145603f8a9de23e76dc28eed7054",
- "reference": "0a3532556be0145603f8a9de23e76dc28eed7054",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/e8ac3499af0ea5b440908e06cc0abe5898008b3c",
+ "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c",
"shasum": ""
},
"require": {
"php": "^7.1|^8",
- "psr/log": "^1.0",
+ "psr/log": "^1|^2|^3",
"symfony/var-dumper": "^2.6|^3|^4|^5"
},
"require-dev": {
@@ -1223,9 +1212,9 @@
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
- "source": "https://github.com/maximebf/php-debugbar/tree/v1.17.1"
+ "source": "https://github.com/maximebf/php-debugbar/tree/v1.17.3"
},
- "time": "2021-08-01T09:19:02+00:00"
+ "time": "2021-10-19T12:33:27+00:00"
},
{
"name": "miljar/php-exif",
@@ -1516,59 +1505,6 @@
],
"time": "2021-05-12T11:11:27+00:00"
},
- {
- "name": "phive/twig-extensions-deferred",
- "version": "v1.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/rybakit/twig-extensions-deferred-legacy.git",
- "reference": "5a2426d622afa74034e754ca5ea1d1ff7887627f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/rybakit/twig-extensions-deferred-legacy/zipball/5a2426d622afa74034e754ca5ea1d1ff7887627f",
- "reference": "5a2426d622afa74034e754ca5ea1d1ff7887627f",
- "shasum": ""
- },
- "require": {
- "twig/twig": "~1.18"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Phive\\Twig\\Extensions\\Deferred\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Eugene Leonovich",
- "email": "gen.work@gmail.com"
- }
- ],
- "description": "An extension for Twig that allows to defer block rendering",
- "homepage": "https://github.com/rybakit/twig-extensions-deferred",
- "keywords": [
- "defer",
- "extension",
- "lazy",
- "twig"
- ],
- "support": {
- "issues": "https://github.com/rybakit/twig-extensions-deferred-legacy/issues",
- "source": "https://github.com/rybakit/twig-extensions-deferred-legacy/tree/v1.0.2"
- },
- "funding": [
- {
- "url": "https://github.com/rybakit",
- "type": "github"
- }
- ],
- "time": "2017-03-17T21:39:21+00:00"
- },
{
"name": "php-http/message-factory",
"version": "v1.0.2",
@@ -2146,17 +2082,62 @@
"time": "2019-03-08T08:55:37+00:00"
},
{
- "name": "rockettheme/toolbox",
- "version": "1.5.9",
+ "name": "rhukster/dom-sanitizer",
+ "version": "1.0.6",
"source": {
"type": "git",
- "url": "https://github.com/rockettheme/toolbox.git",
- "reference": "2d6693235aaca2efaadb61c84dac927aaf4eabfa"
+ "url": "https://github.com/rhukster/dom-sanitizer.git",
+ "reference": "4db3ef1ac3d5505d044c5eb12aa106ba745bf129"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/2d6693235aaca2efaadb61c84dac927aaf4eabfa",
- "reference": "2d6693235aaca2efaadb61c84dac927aaf4eabfa",
+ "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/4db3ef1ac3d5505d044c5eb12aa106ba745bf129",
+ "reference": "4db3ef1ac3d5505d044c5eb12aa106ba745bf129",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Rhukster\\DomSanitizer\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andy Miller",
+ "email": "rhuk@rhuk.net"
+ }
+ ],
+ "description": "A simple but effective DOM/SVG/MathML Sanitizer for PHP 7.4+",
+ "support": {
+ "issues": "https://github.com/rhukster/dom-sanitizer/issues",
+ "source": "https://github.com/rhukster/dom-sanitizer/tree/1.0.6"
+ },
+ "time": "2021-09-30T15:41:33+00:00"
+ },
+ {
+ "name": "rockettheme/toolbox",
+ "version": "1.5.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rockettheme/toolbox.git",
+ "reference": "d9738de013fa12df77754a0f11dded220b246efb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/d9738de013fa12df77754a0f11dded220b246efb",
+ "reference": "d9738de013fa12df77754a0f11dded220b246efb",
"shasum": ""
},
"require": {
@@ -2197,9 +2178,9 @@
],
"support": {
"issues": "https://github.com/rockettheme/toolbox/issues",
- "source": "https://github.com/rockettheme/toolbox/tree/1.5.9"
+ "source": "https://github.com/rockettheme/toolbox/tree/1.5.10"
},
- "time": "2021-04-14T19:52:40+00:00"
+ "time": "2021-09-29T16:50:13+00:00"
},
{
"name": "seld/cli-prompt",
@@ -2258,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": {
@@ -2328,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": [
{
@@ -2344,7 +2325,7 @@
"type": "tidelift"
}
],
- "time": "2021-08-25T19:27:26+00:00"
+ "time": "2021-10-25T16:36:08+00:00"
},
{
"name": "symfony/contracts",
@@ -2526,16 +2507,16 @@
},
{
"name": "symfony/http-client",
- "version": "v4.4.27",
+ "version": "v4.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "ade6979785bb799e08912f3104959fb169739462"
+ "reference": "9a5fdf129b522a06a46d13400500d326c41d8a73"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/ade6979785bb799e08912f3104959fb169739462",
- "reference": "ade6979785bb799e08912f3104959fb169739462",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/9a5fdf129b522a06a46d13400500d326c41d8a73",
+ "reference": "9a5fdf129b522a06a46d13400500d326c41d8a73",
"shasum": ""
},
"require": {
@@ -2587,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.27"
+ "source": "https://github.com/symfony/http-client/tree/v4.4.33"
},
"funding": [
{
@@ -2603,7 +2584,7 @@
"type": "tidelift"
}
],
- "time": "2021-07-23T15:41:52+00:00"
+ "time": "2021-10-18T16:39:13+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -3007,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",
@@ -3071,16 +3131,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v4.4.30",
+ "version": "v4.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c"
+ "reference": "50286e2b7189bfb4f419c0731e86632cddf7c5ee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c",
- "reference": "7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/50286e2b7189bfb4f419c0731e86632cddf7c5ee",
+ "reference": "50286e2b7189bfb4f419c0731e86632cddf7c5ee",
"shasum": ""
},
"require": {
@@ -3140,7 +3200,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v4.4.30"
+ "source": "https://github.com/symfony/var-dumper/tree/v4.4.33"
},
"funding": [
{
@@ -3156,7 +3216,7 @@
"type": "tidelift"
}
],
- "time": "2021-08-04T20:31:23+00:00"
+ "time": "2021-10-25T20:24:58+00:00"
},
{
"name": "symfony/yaml",
@@ -3231,16 +3291,16 @@
},
{
"name": "twig/twig",
- "version": "v1.44.4",
+ "version": "v1.44.5",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "4d400421528e9fa40caaffcf7824c172526dd99d"
+ "reference": "dd4353357c5a116322e92a00d16043a31881a81e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/4d400421528e9fa40caaffcf7824c172526dd99d",
- "reference": "4d400421528e9fa40caaffcf7824c172526dd99d",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/dd4353357c5a116322e92a00d16043a31881a81e",
+ "reference": "dd4353357c5a116322e92a00d16043a31881a81e",
"shasum": ""
},
"require": {
@@ -3293,7 +3353,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
- "source": "https://github.com/twigphp/Twig/tree/v1.44.4"
+ "source": "https://github.com/twigphp/Twig/tree/v1.44.5"
},
"funding": [
{
@@ -3305,7 +3365,7 @@
"type": "tidelift"
}
],
- "time": "2021-05-16T12:11:20+00:00"
+ "time": "2021-09-17T08:35:19+00:00"
},
{
"name": "willdurand/negotiation",
@@ -3367,25 +3427,24 @@
"packages-dev": [
{
"name": "behat/gherkin",
- "version": "v4.8.0",
+ "version": "v4.9.0",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
- "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd"
+ "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2391482cd003dfdc36b679b27e9f5326bd656acd",
- "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4",
+ "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4",
"shasum": ""
},
"require": {
"php": "~7.2|~8.0"
},
"require-dev": {
- "cucumber/cucumber": "dev-gherkin-16.0.0",
+ "cucumber/cucumber": "dev-gherkin-22.0.0",
"phpunit/phpunit": "~8|~9",
- "symfony/phpunit-bridge": "~3|~4|~5",
"symfony/yaml": "~3|~4|~5"
},
"suggest": {
@@ -3394,7 +3453,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.4-dev"
+ "dev-master": "4.x-dev"
}
},
"autoload": {
@@ -3425,9 +3484,9 @@
],
"support": {
"issues": "https://github.com/Behat/Gherkin/issues",
- "source": "https://github.com/Behat/Gherkin/tree/v4.8.0"
+ "source": "https://github.com/Behat/Gherkin/tree/v4.9.0"
},
- "time": "2021-02-04T12:44:21+00:00"
+ "time": "2021-10-12T13:05:09+00:00"
},
{
"name": "codeception/codeception",
@@ -3961,24 +4020,25 @@
},
{
"name": "guzzlehttp/guzzle",
- "version": "7.3.0",
+ "version": "7.4.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "7008573787b430c1c1f650e3722d9bba59967628"
+ "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628",
- "reference": "7008573787b430c1c1f650e3722d9bba59967628",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/868b3571a039f0ebc11ac8f344f4080babe2cb94",
+ "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94",
"shasum": ""
},
"require": {
"ext-json": "*",
- "guzzlehttp/promises": "^1.4",
- "guzzlehttp/psr7": "^1.7 || ^2.0",
+ "guzzlehttp/promises": "^1.5",
+ "guzzlehttp/psr7": "^1.8.3 || ^2.1",
"php": "^7.2.5 || ^8.0",
- "psr/http-client": "^1.0"
+ "psr/http-client": "^1.0",
+ "symfony/deprecation-contracts": "^2.2"
},
"provide": {
"psr/http-client-implementation": "1.0"
@@ -3988,7 +4048,7 @@
"ext-curl": "*",
"php-http/client-integration-tests": "^3.0",
"phpunit/phpunit": "^8.5.5 || ^9.3.5",
- "psr/log": "^1.1"
+ "psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
@@ -3998,7 +4058,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "7.3-dev"
+ "dev-master": "7.4-dev"
}
},
"autoload": {
@@ -4014,19 +4074,43 @@
"MIT"
],
"authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
+ {
+ "name": "Jeremy Lindblom",
+ "email": "jeremeamia@gmail.com",
+ "homepage": "https://github.com/jeremeamia"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
- "homepage": "https://sagikazarmark.hu"
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
- "homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
@@ -4040,7 +4124,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/7.3.0"
+ "source": "https://github.com/guzzle/guzzle/tree/7.4.0"
},
"funding": [
{
@@ -4052,28 +4136,24 @@
"type": "github"
},
{
- "url": "https://github.com/alexeyshockov",
- "type": "github"
- },
- {
- "url": "https://github.com/gmponos",
- "type": "github"
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+ "type": "tidelift"
}
],
- "time": "2021-03-23T11:33:13+00:00"
+ "time": "2021-10-18T09:52:00+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "1.4.1",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
+ "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
- "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
+ "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"shasum": ""
},
"require": {
@@ -4085,7 +4165,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "1.5-dev"
}
},
"autoload": {
@@ -4101,10 +4181,25 @@
"MIT"
],
"authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
@@ -4113,9 +4208,23 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/1.4.1"
+ "source": "https://github.com/guzzle/promises/tree/1.5.1"
},
- "time": "2021-03-07T09:25:29+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-22T20:56:57+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -4177,16 +4286,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v4.12.0",
+ "version": "v4.13.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "6608f01670c3cc5079e18c1dab1104e002579143"
+ "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
- "reference": "6608f01670c3cc5079e18c1dab1104e002579143",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd",
+ "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"shasum": ""
},
"require": {
@@ -4227,9 +4336,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1"
},
- "time": "2021-07-21T10:44:31+00:00"
+ "time": "2021-11-03T20:52:16+00:00"
},
{
"name": "phar-io/manifest",
@@ -4397,16 +4506,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
- "version": "5.2.2",
+ "version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
- "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
"shasum": ""
},
"require": {
@@ -4417,7 +4526,8 @@
"webmozart/assert": "^1.9.1"
},
"require-dev": {
- "mockery/mockery": "~1.3.2"
+ "mockery/mockery": "~1.3.2",
+ "psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@@ -4447,22 +4557,22 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
- "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
},
- "time": "2020-09-03T19:13:55+00:00"
+ "time": "2021-10-19T17:43:47+00:00"
},
{
"name": "phpdocumentor/type-resolver",
- "version": "1.4.0",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+ "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
- "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae",
+ "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae",
"shasum": ""
},
"require": {
@@ -4470,7 +4580,8 @@
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
- "ext-tokenizer": "*"
+ "ext-tokenizer": "*",
+ "psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@@ -4496,39 +4607,39 @@
"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.4.0"
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1"
},
- "time": "2020-09-17T18:55:26+00:00"
+ "time": "2021-10-02T14:08:47+00:00"
},
{
"name": "phpspec/prophecy",
- "version": "1.13.0",
+ "version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
+ "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
- "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
+ "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.2",
- "php": "^7.2 || ~8.0, <8.1",
+ "php": "^7.2 || ~8.0, <8.2",
"phpdocumentor/reflection-docblock": "^5.2",
"sebastian/comparator": "^3.0 || ^4.0",
"sebastian/recursion-context": "^3.0 || ^4.0"
},
"require-dev": {
- "phpspec/phpspec": "^6.0",
+ "phpspec/phpspec": "^6.0 || ^7.0",
"phpunit/phpunit": "^8.0 || ^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11.x-dev"
+ "dev-master": "1.x-dev"
}
},
"autoload": {
@@ -4563,22 +4674,22 @@
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
- "source": "https://github.com/phpspec/prophecy/tree/1.13.0"
+ "source": "https://github.com/phpspec/prophecy/tree/1.14.0"
},
- "time": "2021-03-17T13:42:18+00:00"
+ "time": "2021-09-10T09:02:12+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "0.12.98",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "3bb7cc246c057405dd5e290c3ecc62ab51d57e00"
+ "reference": "bcea0ae85868a89d5789c75f012c93129f842934"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3bb7cc246c057405dd5e290c3ecc62ab51d57e00",
- "reference": "3bb7cc246c057405dd5e290c3ecc62ab51d57e00",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bcea0ae85868a89d5789c75f012c93129f842934",
+ "reference": "bcea0ae85868a89d5789c75f012c93129f842934",
"shasum": ""
},
"require": {
@@ -4594,7 +4705,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "0.12-dev"
+ "dev-master": "1.0-dev"
}
},
"autoload": {
@@ -4609,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.98"
+ "source": "https://github.com/phpstan/phpstan/tree/1.1.2"
},
"funding": [
{
@@ -4629,36 +4740,35 @@
"type": "tidelift"
}
],
- "time": "2021-09-02T12:33:01+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": [
@@ -4678,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.6",
+ "version": "9.2.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "f6293e1b30a2354e8428e004689671b83871edde"
+ "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde",
- "reference": "f6293e1b30a2354e8428e004689671b83871edde",
+ "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.10.2",
+ "nikic/php-parser": "^4.13.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@@ -4749,7 +4859,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8"
},
"funding": [
{
@@ -4757,7 +4867,7 @@
"type": "github"
}
],
- "time": "2021-03-28T07:26:59+00:00"
+ "time": "2021-10-30T08:01:38+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -5002,16 +5112,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.5.9",
+ "version": "9.5.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b"
+ "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b",
- "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
+ "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
"shasum": ""
},
"require": {
@@ -5027,7 +5137,7 @@
"phar-io/version": "^3.0.2",
"php": ">=7.3",
"phpspec/prophecy": "^1.12.1",
- "phpunit/php-code-coverage": "^9.2.3",
+ "phpunit/php-code-coverage": "^9.2.7",
"phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1",
"phpunit/php-text-template": "^2.0.3",
@@ -5089,7 +5199,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.9"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10"
},
"funding": [
{
@@ -5101,7 +5211,7 @@
"type": "github"
}
],
- "time": "2021-08-31T06:47:40+00:00"
+ "time": "2021-09-25T07:38:51+00:00"
},
{
"name": "psr/http-client",
@@ -5584,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": {
@@ -5642,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": [
{
@@ -5657,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",
@@ -6008,7 +6118,6 @@
"type": "github"
}
],
- "abandoned": true,
"time": "2020-09-28T06:45:17+00:00"
},
{
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index dffe099..1aeaf2b 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -1446,6 +1446,10 @@ form:
title: PLUGIN_ADMIN.ADVANCED
underline: true
+ gpm_section:
+ type: section
+ title: PLUGIN_ADMIN.GPM_SECTION
+
gpm.releases:
type: toggle
label: PLUGIN_ADMIN.GPM_RELEASES
@@ -1455,23 +1459,6 @@ form:
stable: PLUGIN_ADMIN.STABLE
testing: PLUGIN_ADMIN.TESTING
- gpm.proxy_url:
- type: text
- size: medium
- placeholder: "e.g. 127.0.0.1:3128"
- label: PLUGIN_ADMIN.PROXY_URL
- help: PLUGIN_ADMIN.PROXY_URL_HELP
-
- gpm.method:
- type: toggle
- label: PLUGIN_ADMIN.GPM_METHOD
- highlight: auto
- help: PLUGIN_ADMIN.GPM_METHOD_HELP
- options:
- auto: PLUGIN_ADMIN.AUTO
- fopen: PLUGIN_ADMIN.FOPEN
- curl: PLUGIN_ADMIN.CURL
-
gpm.official_gpm_only:
type: toggle
label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY
@@ -1484,17 +1471,80 @@ form:
validate:
type: bool
- gpm.verify_peer:
+ http_section:
+ type: section
+ title: PLUGIN_ADMIN.HTTP_SECTION
+
+ http.method:
type: toggle
- label: PLUGIN_ADMIN.GPM_VERIFY_PEER
+ label: PLUGIN_ADMIN.GPM_METHOD
+ highlight: auto
+ help: PLUGIN_ADMIN.GPM_METHOD_HELP
+ options:
+ auto: PLUGIN_ADMIN.AUTO
+ fopen: PLUGIN_ADMIN.FOPEN
+ curl: PLUGIN_ADMIN.CURL
+
+ http.enable_proxy:
+ type: toggle
+ label: PLUGIN_ADMIN.SSL_ENABLE_PROXY
highlight: 1
- help: PLUGIN_ADMIN.GPM_VERIFY_PEER_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: false
+ validate:
+ type: bool
+
+ http.proxy_url:
+ type: text
+ size: medium
+ placeholder: "e.g. 127.0.0.1:3128"
+ label: PLUGIN_ADMIN.PROXY_URL
+ help: PLUGIN_ADMIN.PROXY_URL_HELP
+
+ http.proxy_cert_path:
+ type: text
+ size: medium
+ placeholder: "e.g. /Users/bob/certs/"
+ label: PLUGIN_ADMIN.PROXY_CERT
+ help: PLUGIN_ADMIN.PROXY_CERT_HELP
+
+ http.verify_peer:
+ type: toggle
+ label: PLUGIN_ADMIN.SSL_VERIFY_PEER
+ highlight: 1
+ help: PLUGIN_ADMIN.SSL_VERIFY_PEER_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
+ http.verify_host:
+ type: toggle
+ label: PLUGIN_ADMIN.SSL_VERIFY_HOST
+ highlight: 1
+ help: PLUGIN_ADMIN.SSL_VERIFY_HOST_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ http.concurrent_connections:
+ type: number
+ size: x-small
+ label: PLUGIN_ADMIN.HTTP_CONNECTIONS
+ help: PLUGIN_ADMIN.HTTP_CONNECTIONS_HELP
+ validate:
+ min: 1
+ max: 20
+
+ misc_section:
+ type: section
+ title: PLUGIN_ADMIN.MISC_SECTION
+
reverse_proxy_setup:
type: toggle
label: PLUGIN_ADMIN.REVERSE_PROXY
diff --git a/system/config/system.yaml b/system/config/system.yaml
index fb5f391..652910d 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -96,7 +96,7 @@ cache:
purge_at: '0 4 * * *' # How often to purge old file cache (using new scheduler)
clear_at: '0 3 * * *' # How often to clear cache (using new scheduler)
clear_job_type: 'standard' # Type to clear when processing the scheduled clear job `standard`|`all`
- clear_images_by_default: false # By default grav does not include processed images in cache clear, this can be enabled
+ clear_images_by_default: false # By default grav does not include processed images in cache clear, this can be enabled
cli_compatibility: false # Ensures only non-volatile drivers are used (file, redis, memcache, etc.)
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
gzip: false # GZip compress the page output
@@ -162,6 +162,12 @@ images:
retina_scale: 1 # scale to adjust auto-sizes for better handling of HiDPI resolutions
defaults:
loading: auto # Let browser pick [auto|lazy|eager]
+ watermark:
+ image: 'system://images/watermark.png' # Path to a watermark image
+ position_y: 'center' # top|center|bottom
+ position_x: 'center' # left|center|right
+ scale: 33 # percentage of watermark scale
+ watermark_all: false # automatically watermark all images
media:
enable_media_timestamp: false # Enable media timestamps
@@ -184,11 +190,17 @@ session:
gpm:
releases: stable # Set to either 'stable' or 'testing'
- proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
- method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
- verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
+http:
+ method: auto # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
+ enable_proxy: true # Enable proxy server configuration
+ proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
+ proxy_cert_path: # Local path to proxy certificate folder containing pem files
+ concurrent_connections: 5 # Concurrent HTTP connections when multiplexing
+ verify_peer: true # Enable/Disable SSL verification of peer certificates
+ verify_host: true # Enable/Disable SSL verification of host certificates
+
accounts:
type: regular # EXPERIMENTAL: Account type: regular or flex
storage: file # EXPERIMENTAL: Flex storage type: file or folder
diff --git a/system/defines.php b/system/defines.php
index 9ab384f..f5ccba9 100644
--- a/system/defines.php
+++ b/system/defines.php
@@ -9,7 +9,7 @@
// Some standard defines
define('GRAV', true);
-define('GRAV_VERSION', '1.7.21');
+define('GRAV_VERSION', '1.7.25');
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
define('GRAV_TESTING', false);
diff --git a/system/images/watermark.png b/system/images/watermark.png
new file mode 100644
index 0000000..0f38a75
Binary files /dev/null and b/system/images/watermark.png differ
diff --git a/system/languages/ar.yaml b/system/languages/ar.yaml
index 9605405..53b0a07 100644
--- a/system/languages/ar.yaml
+++ b/system/languages/ar.yaml
@@ -51,6 +51,7 @@ GRAV:
VALIDATION_FAIL: 'فشل التحقق من صحة:'
INVALID_INPUT: 'إدخال غير صحيح في'
MISSING_REQUIRED_FIELD: 'حقل مطلوب مفقود:'
+ XSS_ISSUES: "مشاكل XSS محتملة تم اكتشافها في حقل '%s' '"
MONTHS_OF_THE_YEAR:
- 'كانون الثاني'
- 'شباط'
@@ -72,6 +73,8 @@ GRAV:
- 'الجمعة'
- 'السبت'
- 'الأحد'
+ YES: "نعم"
+ NO: "لا"
CRON:
EVERY: كل
EVERY_HOUR: كل ساعة
@@ -80,3 +83,11 @@ GRAV:
EVERY_DAY_OF_MONTH: كل يوم في الشهر
EVERY_MONTH: ' كل شهر'
TEXT_PERIOD: كل
+ TEXT_MINS: ' في دقيقة(دقائق) بعد الساعة'
+ TEXT_TIME: ' في :'
+ TEXT_DOW: ' في '
+ TEXT_MONTH: ' من '
+ TEXT_DOM: ' في '
+ ERROR1: الوسم %s غير مدعوم!
+ ERROR2: عدد عناصر غير صالح.
+ ERROR4: تعبير غير معروف
diff --git a/system/languages/ca.yaml b/system/languages/ca.yaml
index 7795aef..f5a1940 100644
--- a/system/languages/ca.yaml
+++ b/system/languages/ca.yaml
@@ -15,6 +15,7 @@ GRAV:
BAD_DATE: Data invàlida
AGO: abans
FROM_NOW: des d'ara
+ JUST_NOW: Ara mateix
SECOND: segon
MINUTE: minut
HOUR: hora
@@ -48,6 +49,7 @@ GRAV:
VALIDATION_FAIL: 'Ha fallat la validació:'
INVALID_INPUT: 'Entrada no vàlida a'
MISSING_REQUIRED_FIELD: 'Falta camp obligatori:'
+ XSS_ISSUES: "Detectats potencials problemes XSS al camp '%s'"
MONTHS_OF_THE_YEAR:
- 'Gener'
- 'Febrer'
@@ -69,3 +71,17 @@ GRAV:
- 'Divendres'
- 'Dissabte'
- 'Diumenge'
+ YES: "Sí"
+ NO: "No"
+ CRON:
+ EVERY: cada
+ EVERY_HOUR: cada hora
+ EVERY_MINUTE: cada minut
+ EVERY_DAY_OF_WEEK: cada dia de la setmana
+ EVERY_DAY_OF_MONTH: cada dia del mes
+ EVERY_MONTH: cada mes
+ TEXT_PERIOD: Cada
+ ERROR1: L'etiqueta %s no està suportada!
+ ERROR2: Nombre d'elements incorrecte
+ ERROR3: El jquery_element s'ha d'establir a la configuració de jqCron
+ ERROR4: Expressió no reconeguda
diff --git a/system/languages/fr.yaml b/system/languages/fr.yaml
index d9fa177..6c17e6a 100644
--- a/system/languages/fr.yaml
+++ b/system/languages/fr.yaml
@@ -24,6 +24,7 @@ GRAV:
'/(quiz)zes$/i': '\1'
'/(alias|status)es$/i': '\1'
'/([octop|vir])i$/i': '\1us'
+ '/(n)ews$/i': '\1ouvelles'
INFLECTOR_UNCOUNTABLE:
- 'équipement'
- 'information'
@@ -58,10 +59,10 @@ GRAV:
MONTH: mois
YEAR: année
DECADE: décennie
- SEC: s
- MIN: m
- HR: h
- WK: sem
+ SEC: sec.
+ MIN: min.
+ HR: hr.
+ WK: sem.
MO: m
YR: an
DEC: déc
@@ -84,6 +85,7 @@ GRAV:
VALIDATION_FAIL: 'La validation a échoué :'
INVALID_INPUT: 'Saisie non valide'
MISSING_REQUIRED_FIELD: 'Champ obligatoire manquant :'
+ XSS_ISSUES: "Erreurs XSS probablement détectées dans le champ '%s'"
MONTHS_OF_THE_YEAR:
- 'janvier'
- 'février'
@@ -105,6 +107,8 @@ GRAV:
- 'vendredi'
- 'samedi'
- 'dimanche'
+ YES: "Oui"
+ NO: "Non"
CRON:
EVERY: chaque
EVERY_HOUR: toutes les heures
@@ -118,7 +122,7 @@ GRAV:
TEXT_DOW: ' sur '
TEXT_MONTH: ' de '
TEXT_DOM: ' sur '
- ERROR1: La balise %s n'est pas supportée!
+ ERROR1: La balise %s n'est pas prise en charge !
ERROR2: Nombre invalide d'éléments
ERROR3: L'élément jquery_element doit être défini dans les paramètres jqCron
ERROR4: Expression non reconnue
diff --git a/system/languages/gl.yaml b/system/languages/gl.yaml
index b016c59..b9e581f 100644
--- a/system/languages/gl.yaml
+++ b/system/languages/gl.yaml
@@ -104,6 +104,7 @@ GRAV:
VALIDATION_FAIL: 'Fallou a validación:'
INVALID_INPUT: 'Entrada incorrecta en'
MISSING_REQUIRED_FIELD: 'Falta un campo requirido:'
+ XSS_ISSUES: "Detectáronse posibles problemas XSS no campo '% s'"
MONTHS_OF_THE_YEAR:
- 'xaneiro'
- 'febreiro'
@@ -125,6 +126,8 @@ GRAV:
- 'venres'
- 'sábado'
- 'domingo'
+ YES: "Si"
+ NO: "Non"
CRON:
EVERY: cada
EVERY_HOUR: Cada hora
diff --git a/system/languages/id.yaml b/system/languages/id.yaml
index 690959d..8107235 100644
--- a/system/languages/id.yaml
+++ b/system/languages/id.yaml
@@ -3,26 +3,72 @@ GRAV:
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Frontmatter tidak valid\n\nLokasi: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
INFLECTOR_PLURALS:
'/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ices'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1oes'
+ '/(bu)s$/i': '\1ses'
+ '/(alias|status)/i': '\1es'
+ '/(octop|vir)us$/i': '\1i'
+ '/(ax|test)is$/i': '\1es'
+ '/s$/i': 's'
+ '/$/': 's'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1ix'
+ '/(vert|ind)ices$/i': '\1ex'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ '/(cris|ax|test)es$/i': '\1is'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1ouse'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1ovie'
+ '/(s)eries$/i': '\1eries'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([lr])ves$/i': '\1f'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1fe'
+ '/(^analy)ses$/i': '\1sis'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
+ '/([ti])a$/i': '\1um'
+ '/(n)ews$/i': '\1ews'
INFLECTOR_UNCOUNTABLE:
- - 'peralatan'
- - 'informasi'
- - 'nasi'
- - 'uang'
- - 'spesies'
- - 'rangkaian'
- - 'ikan'
- - 'domba'
+ - 'Peralatan'
+ - 'Informasi '
+ - 'Nasi'
+ - 'Uang'
+ - 'Jenis'
+ - 'Seri'
+ - 'Ikan'
+ - 'Domba'
INFLECTOR_IRREGULAR:
- 'person': 'orang-orang'
- 'man': 'laki-laki'
- 'child': 'anak-anak'
- 'sex': 'jenis kelamin'
+ 'person': 'Orang-orang'
+ 'man': 'Pria'
+ 'child': 'Balita'
+ 'sex': 'Jenis Kelamin'
'move': 'pindahkan'
+ INFLECTOR_ORDINALS:
+ 'default': 'ke'
+ 'first': 'pertama'
+ 'second': 'nd'
+ 'third': 'rd'
NICETIME:
- NO_DATE_PROVIDED: Tanggal tidak tersedia
+ NO_DATE_PROVIDED: Tidak ada tanggal yang disediakan
BAD_DATE: Format tanggal salah
AGO: yang lalu
- FROM_NOW: dari saat ini
+ FROM_NOW: dari sekarang
JUST_NOW: baru saja
SECOND: detik
MINUTE: menit
@@ -32,12 +78,12 @@ GRAV:
MONTH: bulan
YEAR: tahun
DECADE: dekade
- SEC: dtk
- MIN: mnt
- HR: j
- WK: mng
- MO: bln
- YR: thn
+ SEC: detik
+ MIN: menit
+ HR: ' jam'
+ WK: minggu
+ MO: bulan
+ YR: tahun
DEC: desimal
SECOND_PLURAL: detik
MINUTE_PLURAL: menit
@@ -47,17 +93,18 @@ GRAV:
MONTH_PLURAL: bulan
YEAR_PLURAL: tahun
DECADE_PLURAL: dekade
- SEC_PLURAL: dtk
- MIN_PLURAL: mnt
- HR_PLURAL: j
- WK_PLURAL: mgg
- MO_PLURAL: bln
- YR_PLURAL: thn
+ SEC_PLURAL: detik
+ MIN_PLURAL: menit
+ HR_PLURAL: jam
+ WK_PLURAL: minggu
+ MO_PLURAL: bulan
+ YR_PLURAL: tahun
DEC_PLURAL: dekade
FORM:
VALIDATION_FAIL: 'Validasi gagal:'
INVALID_INPUT: 'Input tidak valid di'
MISSING_REQUIRED_FIELD: 'Data yang diperlukan belum terisi:'
+ XSS_ISSUES: "Isu berpotensial XSS terdeteksi dalam baris %s"
MONTHS_OF_THE_YEAR:
- 'Januari'
- 'Februari'
@@ -76,22 +123,25 @@ GRAV:
- 'Selasa'
- 'Rabu'
- 'Kamis'
- - 'Jumat'
+ - 'Jum''at'
- 'Sabtu'
- 'Minggu'
+ YES: "Ya"
+ NO: "Tidak"
CRON:
EVERY: Setiap
EVERY_HOUR: Setiap jam
EVERY_MINUTE: Setiap menit
EVERY_DAY_OF_WEEK: Setiap hari selama seminggu
- EVERY_DAY_OF_MONTH: pada tanggal setiap bulannya
+ EVERY_DAY_OF_MONTH: Setiap hari dalam sebulan
EVERY_MONTH: setiap bulan
TEXT_PERIOD: Setiap
+ TEXT_MINS: 'dalam menit setelah jam yang lalu'
TEXT_TIME: ' pada :'
TEXT_DOW: ' pada '
TEXT_MONTH: ' pada '
TEXT_DOM: ' pada '
ERROR1: Tag %s tidak didukung!
- ERROR2: Jumlah elemen tidak valid
- ERROR3: jquery_element harus ditetapkan ke pengaturan jqCron
- ERROR4: Ekspresi tidak dikenali
+ ERROR2: Jumlah elemen yang buruk
+ ERROR3: jquery_element harus diatur ke dalam pengaturan jqCron
+ ERROR4: Ekspresi tidak dikenal
diff --git a/system/languages/mn.yaml b/system/languages/mn.yaml
new file mode 100644
index 0000000..73cda0e
--- /dev/null
+++ b/system/languages/mn.yaml
@@ -0,0 +1,147 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nГарчиг: %1$s\n---\n\n# Алдаа: Буруу Формат\n\nЗам: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_PLURALS:
+ '/(quiz)$/i': '\1зүүд'
+ '/^(ox)$/i': '\1ууд'
+ '/([m|l])ouse$/i': '\1ууд'
+ '/(matr|vert|ind)ix|ex$/i': '\1иксүүд'
+ '/(x|ch|ss|sh)$/i': '\1үүд'
+ '/([^aeiouy]|qu)ies$/i': '\1үүд'
+ '/([^aeiouy]|qu)y$/i': '\1үүд'
+ '/(hive)$/i': '\1үүд'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2үүд'
+ '/sis$/i': 'үүд'
+ '/([ti])um$/i': '\1үүд'
+ '/(buffal|tomat)o$/i': '\1үүд'
+ '/(bu)s$/i': '\1үүд'
+ '/(alias|status)/i': '\1үүд'
+ '/(octop|vir)us$/i': '\1үүд'
+ '/(ax|test)is$/i': '\1үүд'
+ '/s$/i': 'үүд'
+ '/$/': 'үүд'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1икс'
+ '/(vert|ind)ices$/i': '\1икс'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1'
+ '/(cris|ax|test)es$/i': '\1'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1'
+ '/(s)eries$/i': '\1'
+ '/([^aeiouy]|qu)ies$/i': '\1үүд'
+ '/([lr])ves$/i': '\1'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1'
+ '/(^analy)ses$/i': '\1'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2үүд'
+ '/([ti])a$/i': '\1'
+ '/(n)ews$/i': '\1'
+ INFLECTOR_UNCOUNTABLE:
+ - 'тоног төхөөрөмж'
+ - 'Мэдээлэл'
+ - 'будаа'
+ - 'мөнгө'
+ - 'төрөл зүйл'
+ - 'цуврал'
+ - 'загас'
+ - 'хонь'
+ INFLECTOR_IRREGULAR:
+ 'person': 'хүмүүс'
+ 'man': 'эрчүүд'
+ 'child': 'хүүхэд'
+ 'sex': 'хүйс'
+ 'move': 'хөдөлгөөн'
+ INFLECTOR_ORDINALS:
+ 'default': 'th'
+ 'first': 'st'
+ 'second': 'nd'
+ 'third': 'rd'
+ NICETIME:
+ NO_DATE_PROVIDED: Огноо алга
+ BAD_DATE: Буруу огноо
+ AGO: өмнө
+ FROM_NOW: одооноос
+ JUST_NOW: дөнгөж сая
+ SECOND: секунд
+ MINUTE: минут
+ HOUR: цаг
+ DAY: өдөр
+ WEEK: долоо хоног
+ MONTH: сар
+ YEAR: он
+ DECADE: арван жил
+ SEC: сек
+ MIN: мин
+ HR: цаг
+ WK: д.х.
+ MO: сар
+ YR: он
+ DEC: арван жил
+ SECOND_PLURAL: секунд
+ MINUTE_PLURAL: минут
+ HOUR_PLURAL: цаг
+ DAY_PLURAL: өдрүүд
+ WEEK_PLURAL: долоо хоногууд
+ MONTH_PLURAL: сарууд
+ YEAR_PLURAL: онууд
+ DECADE_PLURAL: арван жилүүд
+ SEC_PLURAL: сек.-үүд
+ MIN_PLURAL: мин.-ууд
+ HR_PLURAL: цагууд
+ WK_PLURAL: д.х.-ууд
+ MO_PLURAL: сарууд
+ YR_PLURAL: жилүүд
+ DEC_PLURAL: арван жилүүд
+ FORM:
+ VALIDATION_FAIL: 'Баталгаажуулалт амжилтгүй боллоо:'
+ INVALID_INPUT: 'Буруу өгөгдөл дараахид'
+ MISSING_REQUIRED_FIELD: 'Шаардлагатай талбар дутуу байна:'
+ XSS_ISSUES: "'%s' талбарт XSS -ийн болзошгүй асуудлууд илэрсэн"
+ MONTHS_OF_THE_YEAR:
+ - '1-р сар'
+ - '2-р сар'
+ - '3-р сар'
+ - '4-р сар'
+ - '5 сар'
+ - '6 сар'
+ - '7 сар'
+ - '8 сар'
+ - '9 сар'
+ - '10 сар'
+ - '11 сар'
+ - '12 сар'
+ DAYS_OF_THE_WEEK:
+ - 'Даваа гараг'
+ - 'Мягмар гараг'
+ - 'Лхагва гараг'
+ - 'Пүрэв гараг'
+ - 'Баасан гараг'
+ - 'Бямба гараг'
+ - 'Ням гараг'
+ YES: "Тийм"
+ NO: "Үгүй"
+ CRON:
+ EVERY: бүрийн
+ EVERY_HOUR: цаг бүрийн
+ EVERY_MINUTE: минут бүрийн
+ EVERY_DAY_OF_WEEK: долоо хоногийн өдөр болгонд
+ EVERY_DAY_OF_MONTH: сарын өдөр болгонд
+ EVERY_MONTH: сар болгон
+ TEXT_PERIOD: Бүрийн
+ TEXT_MINS: ' энэ сүүлийн цагийн минутад'
+ TEXT_TIME: ' : -д'
+ TEXT_DOW: ' -д'
+ TEXT_MONTH: ' -ын'
+ TEXT_DOM: ' -т'
+ ERROR1: '%s -н утга нь дэмжигддэггүй!'
+ ERROR2: Элементүүдийн тоо хэмжээ буруу
+ ERROR3: jquery_element нь jqCron тохиргоонд хийгдсэн байх ёстой
+ ERROR4: Танигдаагүй илэрхийлэл
diff --git a/system/languages/pt.yaml b/system/languages/pt.yaml
index 2da6944..daaa616 100644
--- a/system/languages/pt.yaml
+++ b/system/languages/pt.yaml
@@ -104,6 +104,7 @@ GRAV:
VALIDATION_FAIL: 'Falha na validação:'
INVALID_INPUT: 'Dados inseridos são inválidos em'
MISSING_REQUIRED_FIELD: 'Campo obrigatório em falta:'
+ XSS_ISSUES: "Potenciais problemas de XSS detectados no campo '%s'"
MONTHS_OF_THE_YEAR:
- 'Janeiro'
- 'Fevereiro'
@@ -125,6 +126,8 @@ GRAV:
- 'Sexta-feira'
- 'Sábado'
- 'Domingo'
+ YES: "Sim"
+ NO: "Não"
CRON:
EVERY: cada
EVERY_HOUR: cada hora
diff --git a/system/languages/si.yaml b/system/languages/si.yaml
new file mode 100644
index 0000000..18850a4
--- /dev/null
+++ b/system/languages/si.yaml
@@ -0,0 +1,9 @@
+---
+GRAV:
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/(x|ch|ss|sh)es$/i': '\1'
diff --git a/system/languages/tr.yaml b/system/languages/tr.yaml
index 783674d..47f32db 100644
--- a/system/languages/tr.yaml
+++ b/system/languages/tr.yaml
@@ -82,6 +82,8 @@ GRAV:
- 'Cuma'
- 'Cumartesi'
- 'Pazar'
+ YES: "Evet"
+ NO: "Hayır"
CRON:
EVERY: her
EVERY_HOUR: saatte bir
diff --git a/system/languages/zh-tw.yaml b/system/languages/zh-tw.yaml
index 6cb39c9..fefbc33 100644
--- a/system/languages/zh-tw.yaml
+++ b/system/languages/zh-tw.yaml
@@ -38,7 +38,9 @@ GRAV:
YR_PLURAL: 年
DEC_PLURAL: 十年
FORM:
- MISSING_REQUIRED_FIELD: 遺漏必填欄位:
+ VALIDATION_FAIL: '確驗證失敗:'
+ INVALID_INPUT: '無效輸入:'
+ MISSING_REQUIRED_FIELD: '遺漏必填欄位:'
MONTHS_OF_THE_YEAR:
- '一月'
- '二月'
@@ -60,3 +62,16 @@ GRAV:
- '星期五'
- '星期六'
- '星期日'
+ CRON:
+ EVERY: 每
+ EVERY_HOUR: 每小時
+ EVERY_MINUTE: 每分鐘
+ EVERY_DAY_OF_WEEK: 每一天
+ EVERY_DAY_OF_MONTH: 每一天
+ EVERY_MONTH: 每個月
+ TEXT_PERIOD: 每
+ TEXT_MINS: ' 的 分'
+ TEXT_TIME: ' :'
+ TEXT_DOW: ' 的 '
+ TEXT_MONTH: ' 的 '
+ TEXT_DOM: ' 的 '
diff --git a/system/router.php b/system/router.php
index 187d4d8..d58609c 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/Assets/BaseAsset.php b/system/src/Grav/Common/Assets/BaseAsset.php
index 192ea81..a115ce9 100644
--- a/system/src/Grav/Common/Assets/BaseAsset.php
+++ b/system/src/Grav/Common/Assets/BaseAsset.php
@@ -92,6 +92,10 @@ abstract class BaseAsset extends PropertyObject
*/
public function init($asset, $options)
{
+ if (!$asset) {
+ return false;
+ }
+
$config = Grav::instance()['config'];
$uri = Grav::instance()['uri'];
@@ -259,6 +263,6 @@ abstract class BaseAsset extends PropertyObject
*/
protected function cssRewrite($file, $dir, $local)
{
- return;
+ return '';
}
}
diff --git a/system/src/Grav/Common/Assets/Pipeline.php b/system/src/Grav/Common/Assets/Pipeline.php
index 2e499f9..948104a 100644
--- a/system/src/Grav/Common/Assets/Pipeline.php
+++ b/system/src/Grav/Common/Assets/Pipeline.php
@@ -254,7 +254,7 @@ class Pipeline extends PropertyObject
$old_url = ltrim($old_url, '/');
}
- $new_url = ($local ? $this->base_url: '') . $old_url;
+ $new_url = ($local ? $this->base_url : '') . $old_url;
return str_replace($matches[2], $new_url, $matches[0]);
}, $file);
diff --git a/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php b/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
index ac6e55a..3f5a690 100644
--- a/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
+++ b/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
@@ -68,8 +68,6 @@ trait AssetUtilsTrait
protected function gatherLinks(array $assets, $css = true)
{
$buffer = '';
-
-
foreach ($assets as $id => $asset) {
$local = true;
@@ -135,7 +133,7 @@ trait AssetUtilsTrait
$imports = [];
- $file = (string)preg_replace_callback($regex, function ($matches) use (&$imports) {
+ $file = (string)preg_replace_callback($regex, static function ($matches) use (&$imports) {
$imports[] = $matches[0];
return '';
@@ -200,7 +198,7 @@ trait AssetUtilsTrait
}
if ($this->timestamp) {
- if (Utils::contains($asset, '?') || $querystring) {
+ if ($querystring || Utils::contains($asset, '?')) {
$querystring .= '&' . $this->timestamp;
} else {
$querystring .= '?' . $this->timestamp;
diff --git a/system/src/Grav/Common/Backup/Backups.php b/system/src/Grav/Common/Backup/Backups.php
index 9483b15..4680f85 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'));
}
/**
@@ -222,7 +221,7 @@ class Backups
$backup_root = rtrim(GRAV_ROOT . $backup_root, '/');
}
- if (!file_exists($backup_root)) {
+ if (!$backup_root || !file_exists($backup_root)) {
throw new RuntimeException("Backup location: {$backup_root} does not exist...");
}
diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php
index a961904..a503518 100644
--- a/system/src/Grav/Common/Cache.php
+++ b/system/src/Grav/Common/Cache.php
@@ -141,7 +141,7 @@ class Cache extends Getters
$uniqueness = substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
// Cache key allows us to invalidate all cache on configuration changes.
- $this->key = ($prefix ? $prefix : 'g') . '-' . $uniqueness;
+ $this->key = ($prefix ?: 'g') . '-' . $uniqueness;
$this->cache_dir = $grav['locator']->findResource('cache://doctrine/' . $uniqueness, true, true);
$this->driver_setting = $this->config->get('system.cache.driver');
$this->driver = $this->getCacheDriver();
@@ -618,11 +618,7 @@ class Cache extends Getters
*/
public function isVolatileDriver($setting)
{
- if (in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'])) {
- return true;
- }
-
- return false;
+ return in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'], true);
}
/**
diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php
index 29a2636..2878199 100644
--- a/system/src/Grav/Common/Data/Blueprint.php
+++ b/system/src/Grav/Common/Data/Blueprint.php
@@ -37,7 +37,7 @@ class Blueprint extends BlueprintForm
/** @var string|null */
protected $scope;
- /** @var BlueprintSchema */
+ /** @var BlueprintSchema|null */
protected $blueprintSchema;
/** @var object|null */
@@ -54,7 +54,7 @@ class Blueprint extends BlueprintForm
*/
public function __clone()
{
- if ($this->blueprintSchema) {
+ if (null !== $this->blueprintSchema) {
$this->blueprintSchema = clone $this->blueprintSchema;
}
}
diff --git a/system/src/Grav/Common/Data/BlueprintSchema.php b/system/src/Grav/Common/Data/BlueprintSchema.php
index 60b9581..db5e6af 100644
--- a/system/src/Grav/Common/Data/BlueprintSchema.php
+++ b/system/src/Grav/Common/Data/BlueprintSchema.php
@@ -56,6 +56,15 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
return $this->types[$name] ?? [];
}
+ /**
+ * @param string $name
+ * @return array|null
+ */
+ public function getNestedRules(string $name)
+ {
+ return $this->getNested($name);
+ }
+
/**
* Validate data against blueprints.
*
@@ -74,7 +83,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
}
if (!empty($messages)) {
- throw (new ValidationException())->setMessages($messages);
+ throw (new ValidationException('', 400))->setMessages($messages);
}
}
@@ -190,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/Data.php b/system/src/Grav/Common/Data/Data.php
index 67fb0a8..4de437d 100644
--- a/system/src/Grav/Common/Data/Data.php
+++ b/system/src/Grav/Common/Data/Data.php
@@ -264,7 +264,7 @@ class Data implements DataInterface, ArrayAccess, \Countable, JsonSerializable,
*/
public function blueprints()
{
- if (!$this->blueprints) {
+ if (null === $this->blueprints) {
$this->blueprints = new Blueprint();
} elseif (is_callable($this->blueprints)) {
// Lazy load blueprints.
diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php
index 7ee60e4..bfb4057 100644
--- a/system/src/Grav/Common/Data/Validation.php
+++ b/system/src/Grav/Common/Data/Validation.php
@@ -608,7 +608,7 @@ class Validation
*/
public static function typeColor($value, array $params, array $field)
{
- return preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value);
+ return (bool)preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value);
}
/**
@@ -781,14 +781,22 @@ class Validation
}
// If creating new values is allowed, no further checks are needed.
- if (!empty($field['selectize']['create'])) {
+ $validateOptions = $field['validate']['options'] ?? null;
+ if (!empty($field['selectize']['create']) || $validateOptions === 'ignore') {
return true;
}
$options = $field['options'] ?? [];
$use = $field['use'] ?? 'values';
- if (empty($field['selectize']) || empty($field['multiple'])) {
+ if ($validateOptions) {
+ // Use custom options structure.
+ foreach ($options as &$option) {
+ $option = $option[$validateOptions] ?? null;
+ }
+ unset($option);
+ $options = array_values($options);
+ } elseif (empty($field['selectize']) || empty($field['multiple'])) {
$options = array_keys($options);
}
if ($use === 'keys') {
@@ -1189,7 +1197,7 @@ class Validation
*/
public static function filterItem_List($value, $params)
{
- return array_values(array_filter($value, function ($v) {
+ return array_values(array_filter($value, static function ($v) {
return !empty($v);
}));
}
diff --git a/system/src/Grav/Common/Data/ValidationException.php b/system/src/Grav/Common/Data/ValidationException.php
index 2d94ab8..be6a674 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/Debugger.php b/system/src/Grav/Common/Debugger.php
index 6a1e256..b8143f6 100644
--- a/system/src/Grav/Common/Debugger.php
+++ b/system/src/Grav/Common/Debugger.php
@@ -332,7 +332,7 @@ class Debugger
return new Response(404, $headers, json_encode($response));
}
- $data = is_array($data) ? array_map(function ($item) {
+ $data = is_array($data) ? array_map(static function ($item) {
return $item->toArray();
}, $data) : $data->toArray();
diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php
index 6a3783b..ecbc6ce 100644
--- a/system/src/Grav/Common/Filesystem/Folder.php
+++ b/system/src/Grav/Common/Filesystem/Folder.php
@@ -197,7 +197,7 @@ abstract class Folder
* Shift first directory out of the path.
*
* @param string $path
- * @return string
+ * @return string|null
*/
public static function shift(&$path)
{
@@ -371,7 +371,7 @@ abstract class Folder
return;
}
- if (strpos($target, $source) === 0) {
+ if (strpos($target, $source . '/') === 0) {
throw new RuntimeException('Cannot move folder to itself');
}
@@ -417,7 +417,8 @@ abstract class Folder
if (!$success) {
$error = error_get_last();
- throw new RuntimeException($error['message']);
+
+ throw new RuntimeException($error['message'] ?? 'Unknown error');
}
// Make sure that the change will be detected when caching.
diff --git a/system/src/Grav/Common/Filesystem/ZipArchiver.php b/system/src/Grav/Common/Filesystem/ZipArchiver.php
index 450a581..6e53e70 100644
--- a/system/src/Grav/Common/Filesystem/ZipArchiver.php
+++ b/system/src/Grav/Common/Filesystem/ZipArchiver.php
@@ -57,7 +57,9 @@ class ZipArchiver extends Archiver
throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
}
- if (!file_exists($source)) {
+ // Get real path for our folder
+ $rootPath = realpath($source);
+ if (!$rootPath) {
throw new InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
}
@@ -66,9 +68,6 @@ class ZipArchiver extends Archiver
throw new InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
}
- // Get real path for our folder
- $rootPath = realpath($source);
-
$files = $this->getArchiveFiles($rootPath);
$status && $status([
diff --git a/system/src/Grav/Common/Flex/FlexObject.php b/system/src/Grav/Common/Flex/FlexObject.php
index 66e5412..b64aea1 100644
--- a/system/src/Grav/Common/Flex/FlexObject.php
+++ b/system/src/Grav/Common/Flex/FlexObject.php
@@ -43,7 +43,7 @@ abstract class FlexObject extends \Grav\Framework\Flex\FlexObject implements Med
// Handle media fields.
$settings = $this->getFieldSettings($name);
- if ($settings['media_field'] ?? false === true) {
+ if (($settings['media_field'] ?? false) === true) {
return $this->parseFileProperty($value, $settings);
}
diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageCollection.php b/system/src/Grav/Common/Flex/Types/Pages/PageCollection.php
index 08f192f..7503653 100644
--- a/system/src/Grav/Common/Flex/Types/Pages/PageCollection.php
+++ b/system/src/Grav/Common/Flex/Types/Pages/PageCollection.php
@@ -19,7 +19,6 @@ use Grav\Common\Page\Header;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
-use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
use Grav\Framework\Flex\Pages\FlexPageCollection;
use Collator;
use InvalidArgumentException;
@@ -159,7 +158,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
*/
public function addPage(PageInterface $page)
{
- if (!$page instanceof FlexObjectInterface) {
+ if (!$page instanceof PageObject) {
throw new InvalidArgumentException('$page is not a flex page.');
}
@@ -400,8 +399,8 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
$i = count($manual);
$new_list = [];
foreach ($list as $key => $dummy) {
- $child = $this[$key];
- $order = array_search($child->slug, $manual, true);
+ $child = $this[$key] ?? null;
+ $order = $child ? array_search($child->slug, $manual, true) : false;
if ($order === false) {
$order = $i++;
}
diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
index dd06f58..5a7d773 100644
--- a/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
+++ b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
@@ -109,6 +109,10 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
}
$element = parent::get($key);
+ if (null === $element) {
+ return null;
+ }
+
if (isset($params)) {
$element = $element->getTranslation(ltrim($params, '.'));
}
@@ -331,7 +335,10 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
*/
protected function filterByParent(array $filters)
{
- return parent::filterBy($filters);
+ /** @var static $index */
+ $index = parent::filterBy($filters);
+
+ return $index;
}
/**
@@ -547,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[] = [
@@ -586,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);
@@ -673,12 +680,13 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
$child_count = $tmp->count();
$count = $filters ? $tmp->filterBy($filters, true)->count() : null;
$route = $child->getRoute();
+ $route = $route ? ($route->toString(false) ?: '/') : '';
$payload = [
'item-key' => htmlspecialchars(basename($child->rawRoute() ?? $child->getKey())),
'icon' => $icon,
'title' => htmlspecialchars($child->menu()),
'route' => [
- 'display' => htmlspecialchars(($route ? ($route->toString(false) ?: '/') : null) ?? ''),
+ 'display' => htmlspecialchars($route) ?: null,
'raw' => htmlspecialchars($child->rawRoute()),
],
'modified' => $this->jsDate($child->modified()),
@@ -713,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];
}
/**
@@ -834,12 +842,11 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
/**
* Remove item from the list.
*
- * @param PageInterface|string|null $key
- *
- * @return $this
+ * @param string $key
+ * @return PageObject|null
* @throws InvalidArgumentException
*/
- public function remove($key = null)
+ public function remove($key)
{
return $this->getCollection()->remove($key);
}
diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageObject.php b/system/src/Grav/Common/Flex/Types/Pages/PageObject.php
index 21a03d3..4c83f95 100644
--- a/system/src/Grav/Common/Flex/Types/Pages/PageObject.php
+++ b/system/src/Grav/Common/Flex/Types/Pages/PageObject.php
@@ -104,12 +104,12 @@ class PageObject extends FlexPageObject
*/
public function getRoute($query = []): ?Route
{
- $route = $this->route();
- if (null === $route) {
+ $path = $this->route();
+ if (null === $path) {
return null;
}
- $route = RouteFactory::createFromString($route);
+ $route = RouteFactory::createFromString($path);
if ($lang = $route->getLanguage()) {
$grav = Grav::instance();
if (!$grav['config']->get('system.languages.include_default_lang')) {
@@ -311,7 +311,7 @@ class PageObject extends FlexPageObject
}
// Reset original after save events have all been called.
- $this->_original = null;
+ $this->_originalObject = null;
return $instance;
}
@@ -441,7 +441,8 @@ class PageObject extends FlexPageObject
// Add missing siblings into the end of the list, keeping the previous ordering between them.
foreach ($siblings as $sibling) {
- $basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
+ $folder = (string)$sibling->getProperty('folder');
+ $basename = preg_replace('|^\d+\.|', '', $folder);
if (!in_array($basename, $ordering, true)) {
$ordering[] = $basename;
}
@@ -451,7 +452,8 @@ class PageObject extends FlexPageObject
$ordering = array_flip(array_values($ordering));
$count = count($ordering);
foreach ($siblings as $sibling) {
- $basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
+ $folder = (string)$sibling->getProperty('folder');
+ $basename = preg_replace('|^\d+\.|', '', $folder);
$newOrder = $ordering[$basename] ?? null;
$newOrder = null !== $newOrder ? $newOrder + 1 : (int)$sibling->order() + $count;
$sibling->order($newOrder);
diff --git a/system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php
index d8193df..589ea88 100644
--- a/system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php
+++ b/system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php
@@ -103,7 +103,10 @@ trait PageLegacyTrait
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if (null !== $path && $collection instanceof PageCollectionInterface) {
- return $collection->adjacentSibling($path, $direction);
+ $child = $collection->adjacentSibling($path, $direction);
+ if ($child instanceof PageInterface) {
+ return $child;
+ }
}
return false;
diff --git a/system/src/Grav/Common/Flex/Types/Users/UserCollection.php b/system/src/Grav/Common/Flex/Types/Users/UserCollection.php
index e17525a..9b6490a 100644
--- a/system/src/Grav/Common/Flex/Types/Users/UserCollection.php
+++ b/system/src/Grav/Common/Flex/Types/Users/UserCollection.php
@@ -92,7 +92,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
} else {
$user = parent::find($query, $field);
}
- if ($user) {
+ if ($user instanceof UserObject) {
return $user;
}
}
@@ -123,7 +123,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
* @param string $key
* @return string
*/
- protected function filterUsername(string $key)
+ protected function filterUsername(string $key): string
{
$storage = $this->getFlexDirectory()->getStorage();
if (method_exists($storage, 'normalizeKey')) {
diff --git a/system/src/Grav/Common/Flex/Types/Users/UserIndex.php b/system/src/Grav/Common/Flex/Types/Users/UserIndex.php
index 4a3f587..6e0bc65 100644
--- a/system/src/Grav/Common/Flex/Types/Users/UserIndex.php
+++ b/system/src/Grav/Common/Flex/Types/Users/UserIndex.php
@@ -62,7 +62,7 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
* @param FlexStorageInterface $storage
* @return void
*/
- public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage)
+ public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage): void
{
// Username can also be number and stored as such.
$key = (string)($data['username'] ?? $meta['key'] ?? $meta['storage_key']);
@@ -187,7 +187,7 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
* @param array $updated
* @param array $removed
*/
- protected static function onChanges(array $entries, array $added, array $updated, array $removed)
+ protected static function onChanges(array $entries, array $added, array $updated, array $removed): void
{
$message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', count($entries), count($added), count($updated), count($removed));
diff --git a/system/src/Grav/Common/Flex/Types/Users/UserObject.php b/system/src/Grav/Common/Flex/Types/Users/UserObject.php
index 310c313..424e5eb 100644
--- a/system/src/Grav/Common/Flex/Types/Users/UserObject.php
+++ b/system/src/Grav/Common/Flex/Types/Users/UserObject.php
@@ -230,6 +230,16 @@ class UserObject extends FlexObject implements UserInterface, Countable
return $this;
}
+ /**
+ * @return bool
+ */
+ public function isMyself(): bool
+ {
+ $me = $this->getActiveUser();
+
+ return $me && $me->authenticated && $this->username === $me->username;
+ }
+
/**
* Checks user authorization to the action.
*
@@ -264,6 +274,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
}
}
+ // Check custom application access.
$authorizeCallable = static::$authorizeCallable;
if ($authorizeCallable instanceof Closure) {
$authorizeCallable->bindTo($this);
@@ -280,13 +291,14 @@ class UserObject extends FlexObject implements UserInterface, Countable
return $authorized;
}
- // If specific rule isn't hit, check if user is super user.
- if ($access->authorize('admin.super') === true) {
- return true;
+ // Check group access.
+ $authorized = $this->getGroups()->authorize($action, $scope);
+ if (is_bool($authorized)) {
+ return $authorized;
}
- // Check group access.
- return $this->getGroups()->authorize($action, $scope);
+ // If any specific rule isn't hit, check if user is a superuser.
+ return $access->authorize('admin.super') === true;
}
/**
diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php
index 47cef7c..98654b6 100644
--- a/system/src/Grav/Common/GPM/Response.php
+++ b/system/src/Grav/Common/GPM/Response.php
@@ -1,143 +1,3 @@
'Grav CMS'
- ];
-
- /**
- * Makes a request to the URL by using the preferred method
- *
- * @param string $uri URL to call
- * @param array $overrides An array of parameters for both `curl` and `fopen`
- * @param callable|null $callback Either a function or callback in array notation
- * @return string The response of the request
- * @throws TransportExceptionInterface
- */
- public static function get($uri = '', $overrides = [], $callback = null)
- {
- if (empty($uri)) {
- throw new TransportException('missing URI');
- }
-
- // check if this function is available, if so use it to stop any timeouts
- try {
- if (Utils::functionExists('set_time_limit')) {
- @set_time_limit(0);
- }
- } catch (Exception $e) {
- }
-
- $config = Grav::instance()['config'];
- $referer = defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
- $options = new HttpOptions();
-
- // Set default Headers
- $options->setHeaders(array_merge([ 'Referer' => $referer ], self::$headers));
-
- // Disable verify Peer if required
- $verify_peer = $config->get('system.gpm.verify_peer', true);
- if ($verify_peer !== true) {
- $options->verifyPeer($verify_peer);
- }
-
- // Set proxy url if provided
- $proxy_url = $config->get('system.gpm.proxy_url', false);
- if ($proxy_url) {
- $options->setProxy($proxy_url);
- }
-
- // Use callback if provided
- if ($callback) {
- self::$callback = $callback;
- $options->setOnProgress([Response::class, 'progress']);
- }
-
- $preferred_method = $config->get('system.gpm.method', 'auto');
-
- $settings = array_merge_recursive($options->toArray(), $overrides);
-
- switch ($preferred_method) {
- case 'curl':
- $client = new CurlHttpClient($settings);
- break;
- case 'fopen':
- case 'native':
- $client = new NativeHttpClient($settings);
- break;
- default:
- $client = HttpClient::create($settings);
- }
-
- $response = $client->request('GET', $uri);
-
- return $response->getContent();
- }
-
-
- /**
- * Is this a remote file or not
- *
- * @param string $file
- * @return bool
- */
- public static function isRemote($file)
- {
- return (bool) filter_var($file, FILTER_VALIDATE_URL);
- }
-
- /**
- * Progress normalized for cURL and Fopen
- * Accepts a variable length of arguments passed in by stream method
- *
- * @return void
- */
- public static function progress(int $bytes_transferred, int $filesize, array $info)
- {
-
- if ($bytes_transferred > 0) {
- $percent = $filesize <= 0 ? 0 : (int)(($bytes_transferred * 100) / $filesize);
-
- $progress = [
- 'code' => $info['http_code'],
- 'filesize' => $filesize,
- 'transferred' => $bytes_transferred,
- 'percent' => $percent < 100 ? $percent : 100
- ];
-
- if (self::$callback !== null) {
- call_user_func(self::$callback, $progress);
- }
- }
- }
-}
+// Create alias for the deprecated class.
+class_alias(\Grav\Common\HTTP\Response::class, \Grav\Common\GPM\Response::class);
diff --git a/system/src/Grav/Common/Getters.php b/system/src/Grav/Common/Getters.php
index 8f3a73a..916d524 100644
--- a/system/src/Grav/Common/Getters.php
+++ b/system/src/Grav/Common/Getters.php
@@ -69,6 +69,7 @@ abstract class Getters implements ArrayAccess, Countable
* @param int|string $offset
* @return bool
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
if ($this->gettersVariable) {
@@ -84,6 +85,7 @@ abstract class Getters implements ArrayAccess, Countable
* @param int|string $offset
* @return mixed
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
if ($this->gettersVariable) {
@@ -99,6 +101,7 @@ abstract class Getters implements ArrayAccess, Countable
* @param int|string $offset
* @param mixed $value
*/
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
if ($this->gettersVariable) {
@@ -112,6 +115,7 @@ abstract class Getters implements ArrayAccess, Countable
/**
* @param int|string $offset
*/
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
if ($this->gettersVariable) {
diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php
index ffbb508..2398c11 100644
--- a/system/src/Grav/Common/Grav.php
+++ b/system/src/Grav/Common/Grav.php
@@ -135,7 +135,7 @@ class Grav extends Container
*
* @return void
*/
- public static function resetInstance()
+ public static function resetInstance(): void
{
if (self::$instance) {
// @phpstan-ignore-next-line
@@ -242,7 +242,7 @@ class Grav extends Container
*
* @return void
*/
- public function process()
+ public function process(): void
{
if (isset($this->initialized['process'])) {
return;
@@ -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]);
}
@@ -474,7 +478,7 @@ class Grav extends Container
* @param int $code Redirection code (30x)
* @return void
*/
- public function redirectLangSafe($route, $code = null)
+ public function redirectLangSafe($route, $code = null): void
{
if (!$this['uri']->isExternal($route)) {
$this->redirect($this['pages']->route($route), $code);
@@ -489,7 +493,7 @@ class Grav extends Container
* @param ResponseInterface|null $response
* @return void
*/
- public function header(ResponseInterface $response = null)
+ public function header(ResponseInterface $response = null): void
{
if (null === $response) {
/** @var PageInterface $page */
@@ -514,7 +518,7 @@ class Grav extends Container
*
* @return void
*/
- public function setLocale()
+ public function setLocale(): void
{
// Initialize Locale if set and configured.
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
@@ -575,7 +579,7 @@ class Grav extends Container
*
* @return void
*/
- public function shutdown()
+ public function shutdown(): void
{
// Prevent user abort allowing onShutdown event to run without interruptions.
if (function_exists('ignore_user_abort')) {
@@ -694,7 +698,7 @@ class Grav extends Container
*
* @return void
*/
- protected function registerServices()
+ protected function registerServices(): void
{
foreach (self::$diMap as $serviceKey => $serviceClass) {
if (is_int($serviceKey)) {
@@ -761,12 +765,10 @@ class Grav extends Container
// unsupported media type, try to download it...
if ($uri_extension) {
$extension = $uri_extension;
+ } elseif (isset($path_parts['extension'])) {
+ $extension = $path_parts['extension'];
} else {
- if (isset($path_parts['extension'])) {
- $extension = $path_parts['extension'];
- } else {
- $extension = null;
- }
+ $extension = null;
}
if ($extension) {
@@ -776,11 +778,9 @@ class Grav extends Container
}
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
}
-
- // Nothing found
- return false;
}
- return $page;
+ // Nothing found
+ return false;
}
}
diff --git a/system/src/Grav/Common/HTTP/Client.php b/system/src/Grav/Common/HTTP/Client.php
new file mode 100644
index 0000000..cd4f8db
--- /dev/null
+++ b/system/src/Grav/Common/HTTP/Client.php
@@ -0,0 +1,130 @@
+ 'Grav CMS'
+ ];
+
+ public static function getClient(array $overrides = [], int $connections = 6, callable $callback = null): HttpClientInterface
+ {
+ $config = Grav::instance()['config'];
+ $options = static::getOptions();
+
+ // Use callback if provided
+ if ($callback) {
+ self::$callback = $callback;
+ $options->setOnProgress([Client::class, 'progress']);
+ }
+
+ $settings = array_merge($options->toArray(), $overrides);
+ $preferred_method = $config->get('system.http.method');
+ // Try old GPM setting if value is the same as system default
+ if ($preferred_method === 'auto') {
+ $preferred_method = $config->get('system.gpm.method', 'auto');
+ }
+
+ switch ($preferred_method) {
+ case 'curl':
+ $client = new CurlHttpClient($settings, $connections);
+ break;
+ case 'fopen':
+ case 'native':
+ $client = new NativeHttpClient($settings, $connections);
+ break;
+ default:
+ $client = HttpClient::create($settings, $connections);
+ }
+
+ return $client;
+ }
+
+ /**
+ * Get HTTP Options
+ *
+ * @return HttpOptions
+ */
+ public static function getOptions(): HttpOptions
+ {
+ $config = Grav::instance()['config'];
+ $referer = defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
+
+ $options = new HttpOptions();
+
+ // Set default Headers
+ $options->setHeaders(array_merge([ 'Referer' => $referer ], self::$headers));
+
+ // Disable verify Peer if required
+ $verify_peer = $config->get('system.http.verify_peer');
+ // Try old GPM setting if value is default
+ if ($verify_peer === true) {
+ $verify_peer = $config->get('system.gpm.verify_peer', null) ?? $verify_peer;
+ }
+ $options->verifyPeer($verify_peer);
+
+ // Set verify Host
+ $verify_host = $config->get('system.http.verify_host', true);
+ $options->verifyHost($verify_host);
+
+ // New setting and must be enabled for Proxy to work
+ if ($config->get('system.http.enable_proxy', true)) {
+ // Set proxy url if provided
+ $proxy_url = $config->get('system.http.proxy_url', $config->get('system.gpm.proxy_url', null));
+ if ($proxy_url !== null) {
+ $options->setProxy($proxy_url);
+ }
+
+ // Certificate
+ $proxy_cert = $config->get('system.http.proxy_cert_path', null);
+ if ($proxy_cert !== null) {
+ $options->setCaPath($proxy_cert);
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Progress normalized for cURL and Fopen
+ * Accepts a variable length of arguments passed in by stream method
+ *
+ * @return void
+ */
+ public static function progress(int $bytes_transferred, int $filesize, array $info)
+ {
+
+ if ($bytes_transferred > 0) {
+ $percent = $filesize <= 0 ? 0 : (int)(($bytes_transferred * 100) / $filesize);
+
+ $progress = [
+ 'code' => $info['http_code'],
+ 'filesize' => $filesize,
+ 'transferred' => $bytes_transferred,
+ 'percent' => $percent < 100 ? $percent : 100
+ ];
+
+ if (self::$callback !== null) {
+ call_user_func(self::$callback, $progress);
+ }
+ }
+ }
+}
diff --git a/system/src/Grav/Common/HTTP/Response.php b/system/src/Grav/Common/HTTP/Response.php
new file mode 100644
index 0000000..4a05136
--- /dev/null
+++ b/system/src/Grav/Common/HTTP/Response.php
@@ -0,0 +1,96 @@
+getContent();
+ }
+
+
+ /**
+ * Makes a request to the URL by using the preferred method
+ *
+ * @param string $method method to call such as GET, PUT, etc
+ * @param string $uri URL to call
+ * @param array $overrides An array of parameters for both `curl` and `fopen`
+ * @param callable|null $callback Either a function or callback in array notation
+ * @return ResponseInterface
+ * @throws TransportExceptionInterface
+ */
+ public static function request(string $method, string $uri, array $overrides = [], callable $callback = null): ResponseInterface
+ {
+ if (empty($method)) {
+ throw new TransportException('missing method (GET, PUT, etc.)');
+ }
+
+ if (empty($uri)) {
+ throw new TransportException('missing URI');
+ }
+
+ // check if this function is available, if so use it to stop any timeouts
+ try {
+ if (Utils::functionExists('set_time_limit')) {
+ @set_time_limit(0);
+ }
+ } catch (Exception $e) {}
+
+ $client = Client::getClient($overrides, 6, $callback);
+
+ return $client->request($method, $uri);
+ }
+
+
+ /**
+ * Is this a remote file or not
+ *
+ * @param string $file
+ * @return bool
+ */
+ public static function isRemote($file): bool
+ {
+ return (bool) filter_var($file, FILTER_VALIDATE_URL);
+ }
+
+
+}
diff --git a/system/src/Grav/Common/Helpers/Excerpts.php b/system/src/Grav/Common/Helpers/Excerpts.php
index cd285cf..173850a 100644
--- a/system/src/Grav/Common/Helpers/Excerpts.php
+++ b/system/src/Grav/Common/Helpers/Excerpts.php
@@ -33,6 +33,9 @@ class Excerpts
public static function processImageHtml($html, PageInterface $page = null)
{
$excerpt = static::getExcerptFromHtml($html, 'img');
+ if (null === $excerpt) {
+ return '';
+ }
$original_src = $excerpt['element']['attributes']['src'];
$excerpt['element']['attributes']['href'] = $original_src;
@@ -61,6 +64,9 @@ class Excerpts
public static function processLinkHtml($html, PageInterface $page = null)
{
$excerpt = static::getExcerptFromHtml($html, 'a');
+ if (null === $excerpt) {
+ return '';
+ }
$original_href = $excerpt['element']['attributes']['href'];
$excerpt = static::processLinkExcerpt($excerpt, $page, 'link');
@@ -89,7 +95,6 @@ class Excerpts
$excerpt = null;
$inner = [];
- /** @var DOMElement $element */
foreach ($elements as $element) {
$attributes = [];
foreach ($element->attributes as $name => $value) {
diff --git a/system/src/Grav/Common/Helpers/LogViewer.php b/system/src/Grav/Common/Helpers/LogViewer.php
index a03fde8..187828a 100644
--- a/system/src/Grav/Common/Helpers/LogViewer.php
+++ b/system/src/Grav/Common/Helpers/LogViewer.php
@@ -53,7 +53,6 @@ class LogViewer
*/
public function tail($filepath, $lines = 1)
{
-
$f = $filepath ? @fopen($filepath, 'rb') : false;
if ($f === false) {
return false;
@@ -62,13 +61,12 @@ class LogViewer
$buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
fseek($f, -1, SEEK_END);
- if (fread($f, 1) != "\n") {
- $lines -= 1;
+ if (fread($f, 1) !== "\n") {
+ --$lines;
}
// Start reading
$output = '';
- $chunk = '';
// While we would like more
while (ftell($f) > 0 && $lines >= 0) {
// Figure out how far back we should jump
@@ -76,7 +74,11 @@ class LogViewer
// Do the jump (backwards, relative to where we are)
fseek($f, -$seek, SEEK_CUR);
// Read a chunk and prepend it to our output
- $output = ($chunk = fread($f, $seek)) . $output;
+ $chunk = fread($f, $seek);
+ if ($chunk === false) {
+ throw new \RuntimeException('Cannot read file');
+ }
+ $output = $chunk . $output;
// Jump back to where we started reading
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
// Decrease our line counter
@@ -123,13 +125,13 @@ class LogViewer
*/
public function parse($line)
{
- if (!is_string($line) || strlen($line) === 0) {
- return array();
+ if (!is_string($line) || $line === '') {
+ return [];
}
preg_match($this->pattern, $line, $data);
if (!isset($data['date'])) {
- return array();
+ return [];
}
preg_match('/(.*)- Trace:(.*)/', $data['message'], $matches);
@@ -138,7 +140,7 @@ class LogViewer
$data['trace'] = trim($matches[2]);
}
- return array(
+ return [
'date' => DateTime::createFromFormat('Y-m-d H:i:s', $data['date']),
'logger' => $data['logger'],
'level' => $data['level'],
@@ -146,7 +148,7 @@ class LogViewer
'trace' => isset($data['trace']) ? $this->parseTrace($data['trace']) : null,
'context' => json_decode($data['context'], true),
'extra' => json_decode($data['extra'], true)
- );
+ ];
}
/**
diff --git a/system/src/Grav/Common/Iterator.php b/system/src/Grav/Common/Iterator.php
index cc7cf92..6e80b94 100644
--- a/system/src/Grav/Common/Iterator.php
+++ b/system/src/Grav/Common/Iterator.php
@@ -230,9 +230,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
public function filter(callable $callback = null)
{
foreach ($this->items as $key => $value) {
- if ((!$callback && !(bool)$value) ||
- ($callback && !$callback($value, $key))
- ) {
+ if ((!$callback && !(bool)$value) || ($callback && !$callback($value, $key))) {
unset($this->items[$key]);
}
}
diff --git a/system/src/Grav/Common/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php
index 9282070..e637bdd 100644
--- a/system/src/Grav/Common/Language/LanguageCodes.php
+++ b/system/src/Grav/Common/Language/LanguageCodes.php
@@ -86,12 +86,14 @@ class LanguageCodes
'ja-JP' => [ 'name' => 'Japanese', 'nativeName' => '日本語' ], // not iso-639-1
'ka' => [ 'name' => 'Georgian', 'nativeName' => 'ქართული' ],
'kk' => [ 'name' => 'Kazakh', 'nativeName' => 'Қазақ' ],
+ 'km' => [ 'name' => 'Khmer', 'nativeName' => 'Khmer' ],
'kn' => [ 'name' => 'Kannada', 'nativeName' => 'ಕನ್ನಡ' ],
'ko' => [ 'name' => 'Korean', 'nativeName' => '한국어' ],
'ku' => [ 'name' => 'Kurdish', 'nativeName' => 'Kurdî' ],
'la' => [ 'name' => 'Latin', 'nativeName' => 'Latina' ],
'lb' => [ 'name' => 'Luxembourgish', 'nativeName' => 'Lëtzebuergesch' ],
'lg' => [ 'name' => 'Luganda', 'nativeName' => 'Luganda' ],
+ 'lo' => [ 'name' => 'Lao', 'nativeName' => 'Lao' ],
'lt' => [ 'name' => 'Lithuanian', 'nativeName' => 'Lietuvių' ],
'lv' => [ 'name' => 'Latvian', 'nativeName' => 'Latviešu' ],
'mai' => [ 'name' => 'Maithili', 'nativeName' => 'मैथिली মৈথিলী' ],
@@ -101,6 +103,7 @@ class LanguageCodes
'ml' => [ 'name' => 'Malayalam', 'nativeName' => 'മലയാളം' ],
'mn' => [ 'name' => 'Mongolian', 'nativeName' => 'Монгол' ],
'mr' => [ 'name' => 'Marathi', 'nativeName' => 'मराठी' ],
+ 'my' => [ 'name' => 'Myanmar (Burmese)', 'nativeName' => 'ဗမာी' ],
'no' => [ 'name' => 'Norwegian', 'nativeName' => 'Norsk' ],
'nb' => [ 'name' => 'Norwegian', 'nativeName' => 'Norsk' ],
'nb-NO' => [ 'name' => 'Norwegian (Bokmål)', 'nativeName' => 'Norsk bokmål' ],
@@ -132,6 +135,7 @@ class LanguageCodes
'st' => [ 'name' => 'Southern Sotho', 'nativeName' => 'Sesotho' ],
'sv' => [ 'name' => 'Swedish', 'nativeName' => 'Svenska' ],
'sv-SE' => [ 'name' => 'Swedish', 'nativeName' => 'Svenska' ],
+ 'sw' => [ 'name' => 'Swahili', 'nativeName' => 'Swahili' ],
'ta' => [ 'name' => 'Tamil', 'nativeName' => 'தமிழ்' ],
'ta-IN' => [ 'name' => 'Tamil (India)', 'nativeName' => 'தமிழ் (இந்தியா)' ],
'ta-LK' => [ 'name' => 'Tamil (Sri Lanka)', 'nativeName' => 'தமிழ் (இலங்கை)' ],
@@ -187,12 +191,7 @@ class LanguageCodes
*/
public static function getOrientation($code)
{
- if (isset(static::$codes[$code])) {
- if (isset(static::$codes[$code]['orientation'])) {
- return static::get($code, 'orientation');
- }
- }
- return 'ltr';
+ return static::$codes[$code]['orientation'] ?? 'ltr';
}
/**
@@ -226,11 +225,7 @@ class LanguageCodes
*/
public static function get($code, $type)
{
- if (isset(static::$codes[$code][$type])) {
- return static::$codes[$code][$type];
- }
-
- return false;
+ return static::$codes[$code][$type] ?? false;
}
/**
diff --git a/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php b/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php
index 06056eb..f6e2222 100644
--- a/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php
+++ b/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php
@@ -50,6 +50,8 @@ trait ImageMediaTrait
/** @var integer */
protected $retina_scale;
+ /** @var bool */
+ protected $watermark;
/** @var array */
public static $magic_actions = [
@@ -379,6 +381,8 @@ trait ImageMediaTrait
$this->aspect_ratio = $config->get('system.images.cls.aspect_ratio', false);
$this->retina_scale = $config->get('system.images.cls.retina_scale', 1);
+ $this->watermark = $config->get('system.images.watermark.watermark_all', false);
+
return $this;
}
@@ -415,6 +419,10 @@ trait ImageMediaTrait
$this->image->merge(ImageFile::open($overlay));
}
+ if ($this->watermark) {
+ $this->watermark();
+ }
+
return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
}
}
diff --git a/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php b/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php
index 71431ed..7a1f55d 100644
--- a/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php
+++ b/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php
@@ -108,7 +108,7 @@ trait MediaUploadTrait
*
* @param array $metadata
* @param array|null $settings
- * @return string|null
+ * @return string
* @throws RuntimeException
*/
public function checkFileMetadata(array $metadata, string $filename = null, array $settings = null): string
diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php
index c9a3cdb..930aeda 100644
--- a/system/src/Grav/Common/Page/Collection.php
+++ b/system/src/Grav/Common/Page/Collection.php
@@ -47,7 +47,7 @@ class Collection extends Iterator implements PageCollectionInterface
parent::__construct($items);
$this->params = $params;
- $this->pages = $pages ? $pages : Grav::instance()->offsetGet('pages');
+ $this->pages = $pages ?: Grav::instance()->offsetGet('pages');
}
/**
@@ -187,6 +187,7 @@ class Collection extends Iterator implements PageCollectionInterface
* @param string $offset
* @return PageInterface|null
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->pages->get($offset) ?: null;
diff --git a/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php b/system/src/Grav/Common/Page/Interfaces/PageContentInterface.php
index 5156ede..d9d3105 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/Media.php b/system/src/Grav/Common/Page/Media.php
index 94fd2c4..e182014 100644
--- a/system/src/Grav/Common/Page/Media.php
+++ b/system/src/Grav/Common/Page/Media.php
@@ -63,6 +63,7 @@ class Media extends AbstractMedia
* @param string $offset
* @return bool
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
@@ -72,6 +73,7 @@ class Media extends AbstractMedia
* @param string $offset
* @return MediaObjectInterface|null
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return parent::offsetGet($offset) ?: static::$global[$offset];
diff --git a/system/src/Grav/Common/Page/Medium/GlobalMedia.php b/system/src/Grav/Common/Page/Medium/GlobalMedia.php
index 4097c8f..50d69c7 100644
--- a/system/src/Grav/Common/Page/Medium/GlobalMedia.php
+++ b/system/src/Grav/Common/Page/Medium/GlobalMedia.php
@@ -46,6 +46,7 @@ class GlobalMedia extends AbstractMedia
* @param string $offset
* @return bool
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return parent::offsetExists($offset) ?: !empty($this->resolveStream($offset));
@@ -55,6 +56,7 @@ class GlobalMedia extends AbstractMedia
* @param string $offset
* @return MediaObjectInterface|null
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return parent::offsetGet($offset) ?: $this->addMedium($offset);
diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php
index 70d13f8..46c832e 100644
--- a/system/src/Grav/Common/Page/Medium/ImageMedium.php
+++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php
@@ -17,6 +17,7 @@ use Grav\Common\Media\Interfaces\MediaLinkInterface;
use Grav\Common\Media\Traits\ImageLoadingTrait;
use Grav\Common\Media\Traits\ImageMediaTrait;
use Grav\Common\Utils;
+use Gregwar\Image\Image;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use function func_get_args;
use function in_array;
@@ -325,6 +326,67 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
return $this;
}
+ public function watermark($image = null, $position = null, $scale = null)
+ {
+ $grav = $this->getGrav();
+
+ $locator = $grav['locator'];
+ $config = $grav['config'];
+
+ $args = func_get_args();
+
+ $file = $args[0] ?? '1'; // using '1' because of markdown. doing  returns $args[0]='1';
+ $file = $file === '1' ? $config->get('system.images.watermark.image') : $args[0];
+
+ $watermark = $locator->findResource($file);
+ $watermark = ImageFile::open($watermark);
+
+ // Scaling operations
+ $scale = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100;
+ $wwidth = $this->get('width') * $scale;
+ $wheight = $this->get('height') * $scale;
+ $watermark->resize($wwidth, $wheight);
+
+ // Position operations
+ $position = !empty($args[1]) ? explode('-', $args[1]) : ['center', 'center']; // todo change to config
+ $positionY = $position[0] ?? $config->get('system.images.watermark.position_y', 'center');
+ $positionX = $position[1] ?? $config->get('system.images.watermark.position_x', 'center');
+
+ switch ($positionY)
+ {
+ case 'top':
+ $positionY = 0;
+ break;
+
+ case 'bottom':
+ $positionY = $this->get('height')-$wheight;
+ break;
+
+ case 'center':
+ $positionY = ($this->get('height')/2) - ($wheight/2);
+ break;
+ }
+
+ switch ($positionX)
+ {
+ case 'left':
+ $positionX = 0;
+ break;
+
+ case 'right':
+ $positionX = $this->get('width')-$wwidth;
+ break;
+
+ case 'center':
+ $positionX = ($this->get('width')/2) - ($wwidth/2);
+ break;
+ }
+
+ $this->__call('merge', [$watermark,$positionX, $positionY]);
+
+ return $this;
+ }
+
/**
* Handle this commonly used variant
*
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index a035613..9e68a44 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)
@@ -2274,11 +2293,11 @@ class Page implements PageInterface
{
if ($var !== null) {
// make sure first level are arrays
- array_walk($var, function (&$value) {
+ array_walk($var, static function (&$value) {
$value = (array) $value;
});
// make sure all values are strings
- array_walk_recursive($var, function (&$value) {
+ array_walk_recursive($var, static function (&$value) {
$value = (string) $value;
});
$this->taxonomy = $var;
diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php
index 97f6e18..9e5d47d 100644
--- a/system/src/Grav/Common/Page/Pages.php
+++ b/system/src/Grav/Common/Page/Pages.php
@@ -196,6 +196,58 @@ class Pages
return $this->baseRoute($lang) . $route;
}
+ /**
+ * Get relative referrer route and language code. Returns null if the route isn't within the current base, language (if set) and route.
+ *
+ * @example `$langCode = null; $referrer = $pages->referrerRoute($langCode, '/admin');` returns relative referrer url within /admin and updates $langCode
+ * @example `$langCode = 'en'; $referrer = $pages->referrerRoute($langCode, '/admin');` returns relative referrer url within the /en/admin
+ *
+ * @param string|null $langCode Variable to store the language code. If already set, check only against that language.
+ * @param string $route Optional route within the site.
+ * @return string|null
+ * @since 1.7.23
+ */
+ public function referrerRoute(?string &$langCode, string $route = '/'): ?string
+ {
+ $referrer = $_SERVER['HTTP_REFERER'] ?? null;
+
+ // Start by checking that referrer came from our site.
+ $root = $this->grav['base_url_absolute'];
+ if (!is_string($referrer) || !str_starts_with($referrer, $root)) {
+ return null;
+ }
+
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ // Get all language codes and append no language.
+ if (null === $langCode) {
+ $languages = $language->enabled() ? $language->getLanguages() : [];
+ $languages[] = '';
+ } else {
+ $languages[] = $langCode;
+ }
+
+ $path_base = rtrim($this->base(), '/');
+ $path_route = rtrim($route, '/');
+
+ // Try to figure out the language code.
+ foreach ($languages as $code) {
+ $path_lang = $code ? "/{$code}" : '';
+
+ $base = $path_base . $path_lang . $path_route;
+ if ($referrer === $base || str_starts_with($referrer, "{$base}/")) {
+ if (null === $langCode) {
+ $langCode = $code;
+ }
+
+ return substr($referrer, \strlen($base));
+ }
+ }
+
+ return null;
+ }
+
/**
*
* Get base URL for Grav pages.
@@ -274,7 +326,7 @@ class Pages
*
* @return void
*/
- public function reset()
+ public function reset(): void
{
$this->initialized = false;
@@ -554,7 +606,7 @@ class Pages
if (is_array($sort_flags)) {
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
- $sort_flags = array_reduce($sort_flags, function ($a, $b) {
+ $sort_flags = array_reduce($sort_flags, static function ($a, $b) {
return $a | $b;
}, 0); //merge constant values using bit or
}
@@ -597,7 +649,7 @@ class Pages
$cmd = $value;
$params = [];
} elseif (is_array($value) && count($value) === 1 && !is_int(key($value))) {
- // Format: @command.param: { attr1: value1, attr2: value2 }
+ // Format: @command.param: { attr1: value1, attr2: value2 }
$cmd = (string)key($value);
$params = (array)current($value);
} else {
@@ -663,29 +715,39 @@ class Pages
switch ($type) {
case 'all':
- return $page->children();
+ $collection = $page->children();
+ break;
case 'modules':
case 'modular':
- return $page->children()->modules();
+ $collection = $page->children()->modules();
+ break;
case 'pages':
case 'children':
- return $page->children()->pages();
+ $collection = $page->children()->pages();
+ break;
case 'page':
case 'self':
- return !$page->root() ? (new Collection())->addPage($page) : new Collection();
+ $collection = !$page->root() ? (new Collection())->addPage($page) : new Collection();
+ break;
case 'parent':
$parent = $page->parent();
$collection = new Collection();
- return $parent ? $collection->addPage($parent) : $collection;
+ $collection = $parent ? $collection->addPage($parent) : $collection;
+ break;
case 'siblings':
$parent = $page->parent();
- return $parent ? $parent->children()->remove($page->path()) : new Collection();
+ $collection = $parent ? $parent->children()->remove($page->path()) : new Collection();
+ break;
case 'descendants':
- return $this->all($page)->remove($page->path())->pages();
+ $collection = $this->all($page)->remove($page->path())->pages();
+ break;
default:
// Unknown type; return empty collection.
- return new Collection();
+ $collection = new Collection();
+ break;
}
+
+ return $collection;
}
/**
@@ -1761,7 +1823,7 @@ class Pages
// Build regular expression for all the allowed page extensions.
$page_extensions = $language->getFallbackPageExtensions();
$regex = '/^[^\.]*(' . implode('|', array_map(
- function ($str) {
+ static function ($str) {
return preg_quote($str, '/');
},
$page_extensions
diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php
index 65b11db..3b0d3fb 100644
--- a/system/src/Grav/Common/Plugin.php
+++ b/system/src/Grav/Common/Plugin.php
@@ -10,6 +10,7 @@
namespace Grav\Common;
use ArrayAccess;
+use Composer\Autoload\ClassLoader;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
use Grav\Common\Page\Interfaces\PageInterface;
@@ -42,6 +43,8 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
protected $active = true;
/** @var Blueprint|null */
protected $blueprint;
+ /** @var ClassLoader|null */
+ protected $loader;
/**
* By default assign all methods as listeners using the default priority.
@@ -79,6 +82,24 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
}
}
+ /**
+ * @return ClassLoader|null
+ * @internal
+ */
+ final public function getAutoloader(): ?ClassLoader
+ {
+ return $this->loader;
+ }
+
+ /**
+ * @param ClassLoader|null $loader
+ * @internal
+ */
+ final public function setAutoloader(?ClassLoader $loader): void
+ {
+ $this->loader = $loader;
+ }
+
/**
* @param Config $config
* @return $this
@@ -206,6 +227,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
* @param string $offset An offset to check for.
* @return bool Returns TRUE on success or FALSE on failure.
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
if ($offset === 'title') {
@@ -223,6 +245,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
* @param string $offset The offset to retrieve.
* @return mixed Can return all value types.
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
if ($offset === 'title') {
@@ -241,6 +264,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
* @param mixed $value The value to set.
* @throws LogicException
*/
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
throw new LogicException(__CLASS__ . ' blueprints cannot be modified.');
@@ -252,6 +276,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
* @param string $offset The offset to unset.
* @throws LogicException
*/
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
throw new LogicException(__CLASS__ . ' blueprints cannot be modified.');
diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php
index f9fe32d..88b5e52 100644
--- a/system/src/Grav/Common/Plugins.php
+++ b/system/src/Grav/Common/Plugins.php
@@ -143,7 +143,7 @@ class Plugins extends Iterator
$instance->setConfig($config);
// Register autoloader.
if (method_exists($instance, 'autoload')) {
- $instance->autoload();
+ $instance->setAutoloader($instance->autoload());
}
// Register event listeners.
$events->addSubscriber($instance);
diff --git a/system/src/Grav/Common/Processors/InitializeProcessor.php b/system/src/Grav/Common/Processors/InitializeProcessor.php
index 6114464..b31530e 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
@@ -105,12 +105,12 @@ class InitializeProcessor extends ProcessorBase
// TODO: remove in 2.0.
$this->container['accounts'];
+ // Initialize session (used by URI, see issue #3269).
+ $this->initializeSession($config);
+
// Initialize URI (uses session, see issue #3269).
$this->initializeUri($config);
- // Initialize session.
- $this->initializeSession($config);
-
// Grav may return redirect response right away.
$redirectCode = (int)$config->get('system.pages.redirect_trailing_slash', 1);
if ($redirectCode) {
diff --git a/system/src/Grav/Common/Processors/PagesProcessor.php b/system/src/Grav/Common/Processors/PagesProcessor.php
index 470ca90..36f6718 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/Scheduler/Job.php b/system/src/Grav/Common/Scheduler/Job.php
index 94fbaf1..13059a5 100644
--- a/system/src/Grav/Common/Scheduler/Job.php
+++ b/system/src/Grav/Common/Scheduler/Job.php
@@ -271,7 +271,7 @@ class Job
if ($whenOverlapping) {
$this->whenOverlapping = $whenOverlapping;
} else {
- $this->whenOverlapping = function () {
+ $this->whenOverlapping = static function () {
return false;
};
}
diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php
index c464e43..fe259d8 100644
--- a/system/src/Grav/Common/Security.php
+++ b/system/src/Grav/Common/Security.php
@@ -9,11 +9,11 @@
namespace Grav\Common;
-use enshrined\svgSanitize\Sanitizer;
use Exception;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Page\Pages;
+use Rhukster\DomSanitizer\DOMSanitizer;
use function chr;
use function count;
use function is_array;
@@ -34,7 +34,7 @@ class Security
public static function sanitizeSvgString(string $svg): string
{
if (Grav::instance()['config']->get('security.sanitize_svg')) {
- $sanitizer = new Sanitizer();
+ $sanitizer = new DOMSanitizer(DOMSanitizer::SVG);
$sanitized = $sanitizer->sanitize($svg);
if (is_string($sanitized)) {
$svg = $sanitized;
@@ -53,7 +53,7 @@ class Security
public static function sanitizeSVG(string $file): void
{
if (file_exists($file) && Grav::instance()['config']->get('security.sanitize_svg')) {
- $sanitizer = new Sanitizer();
+ $sanitizer = new DOMSanitizer(DOMSanitizer::SVG);
$original_svg = file_get_contents($file);
$clean_svg = $sanitizer->sanitize($original_svg);
@@ -107,7 +107,7 @@ class Security
$content = $page->value('content');
$data = ['header' => $header, 'content' => $content];
- $results = Security::detectXssFromArray($data);
+ $results = static::detectXssFromArray($data);
if (!empty($results)) {
if ($route) {
@@ -138,7 +138,7 @@ class Security
$options = static::getXssDefaults();
}
- $list = [];
+ $list = [[]];
foreach ($array as $key => $value) {
if (is_array($value)) {
$list[] = static::detectXssFromArray($value, $prefix . $key . '.', $options);
@@ -148,11 +148,7 @@ class Security
}
}
- if (!empty($list)) {
- return array_merge(...$list);
- }
-
- return $list;
+ return array_merge(...$list);
}
/**
@@ -199,7 +195,7 @@ class Security
$string = urldecode($string);
// Convert Hexadecimals
- $string = (string)preg_replace_callback('!(|\\\)[xX]([0-9a-fA-F]+);?!u', function ($m) {
+ $string = (string)preg_replace_callback('!(|\\\)[xX]([0-9a-fA-F]+);?!u', static function ($m) {
return chr(hexdec($m[2]));
}, $string);
@@ -207,7 +203,7 @@ class Security
$string = preg_replace('!(+[0-9]+)!u', '$1;', $string);
// Decode entities
- $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
+ $string = html_entity_decode($string, ENT_NOQUOTES | ENT_HTML5, 'UTF-8');
// Strip whitespace characters
$string = preg_replace('!\s!u', '', $string);
@@ -239,7 +235,7 @@ class Security
}
}
- return false;
+ return null;
}
public static function getXssDefaults(): array
diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php
index b56f62f..3a5c2e2 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/Session.php b/system/src/Grav/Common/Session.php
index 2dbc307..3221627 100644
--- a/system/src/Grav/Common/Session.php
+++ b/system/src/Grav/Common/Session.php
@@ -129,12 +129,12 @@ class Session extends \Grav\Framework\Session\Session
/** @var Uri $uri */
$uri = $grav['uri'];
/** @var Forms|null $form */
- $form = $grav['forms']->getActiveForm(); // @phpstan-ignore-line
+ $form = $grav['forms']->getActiveForm(); // @phpstan-ignore-line (form plugin)
$sessionField = base64_encode($uri->url);
/** @var FormFlash|null $flash */
- $flash = $form ? $form->getFlash() : null; // @phpstan-ignore-line
+ $flash = $form ? $form->getFlash() : null; // @phpstan-ignore-line (form plugin)
$object = $flash && method_exists($flash, 'getLegacyFiles') ? [$sessionField => $flash->getLegacyFiles()] : null;
}
}
diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php
index de3c452..0d0a450 100644
--- a/system/src/Grav/Common/Taxonomy.php
+++ b/system/src/Grav/Common/Taxonomy.php
@@ -105,7 +105,7 @@ class Taxonomy
}
} elseif (is_string($value)) {
if (!empty($key)) {
- $taxonomy = $taxonomy . $key;
+ $taxonomy .= $key;
}
$this->taxonomy_map[$taxonomy][(string) $value][$page->path()] = ['slug' => $page->slug()];
}
diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php
index 5b4a160..4c0d9fe 100644
--- a/system/src/Grav/Common/Twig/Extension/GravExtension.php
+++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php
@@ -90,7 +90,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
*
* @return array
*/
- public function getGlobals()
+ public function getGlobals(): array
{
return [
'grav' => $this->grav,
@@ -102,7 +102,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
*
* @return array
*/
- public function getFilters()
+ public function getFilters(): array
{
return [
new TwigFilter('*ize', [$this, 'inflectorFilter']),
@@ -172,7 +172,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
*
* @return array
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('array', [$this, 'arrayFilter']),
@@ -217,6 +217,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
new TwigFunction('cron', [$this, 'cronFunc']),
new TwigFunction('svg_image', [$this, 'svgImageFunction']),
new TwigFunction('xss', [$this, 'xssFunc']),
+ new TwigFunction('unique_id', [$this, 'uniqueId']),
// Translations
new TwigFunction('t', [$this, 'translate'], ['needs_environment' => true]),
@@ -243,7 +244,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
/**
* @return array
*/
- public function getTokenParsers()
+ public function getTokenParsers(): array
{
return [
new TwigTokenParserRender(),
@@ -654,6 +655,20 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
return implode(', ', $results_parts);
}
+ /**
+ * Generates a random string with configurable length, prefix and suffix.
+ * Unlike the built-in `uniqid()`, this string is non-conflicting and safe
+ *
+ * @param int $length
+ * @param array $options
+ * @return string
+ * @throws \Exception
+ */
+ public function uniqueId(int $length = 9, array $options = ['prefix' => '', 'suffix' => '']): string
+ {
+ return Utils::uniqueId($length, $options);
+ }
+
/**
* @param string $string
* @return string
@@ -823,12 +838,8 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
* @param Environment $twig
* @return string
*/
- public function translate(Environment $twig)
+ public function translate(Environment $twig, ...$args)
{
- // shift off the environment
- $args = func_get_args();
- array_shift($args);
-
// If admin and tu filter provided, use it
if (isset($this->grav['admin'])) {
$numargs = count($args);
@@ -836,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);
@@ -1343,7 +1360,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
*/
public function vardumpFunc($var)
{
- var_dump($var);
+ dump($var);
}
/**
@@ -1407,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/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php
index ef74ce7..19bad01 100644
--- a/system/src/Grav/Common/Twig/Twig.php
+++ b/system/src/Grav/Common/Twig/Twig.php
@@ -22,9 +22,9 @@ use Grav\Common\Twig\Extension\GravExtension;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Event\Event;
-use Phive\Twig\Extensions\Deferred\DeferredExtension;
use RuntimeException;
use Twig\Cache\FilesystemCache;
+use Twig\DeferredExtension\DeferredExtension;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
@@ -33,7 +33,6 @@ use Twig\Extension\DebugExtension;
use Twig\Extension\StringLoaderExtension;
use Twig\Loader\ArrayLoader;
use Twig\Loader\ChainLoader;
-use Twig\Loader\ExistsLoaderInterface;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
use Twig\TwigFilter;
@@ -515,25 +514,21 @@ class Twig
$twig_extension = $extension ? '.'. $extension .TWIG_EXT : TEMPLATE_EXT;
$template_file = $this->template($page->template() . $twig_extension);
- $page_template = null;
-
$loader = $this->twig->getLoader();
- if ($loader instanceof ExistsLoaderInterface) {
- if ($loader->exists($template_file)) {
- // template.xxx.twig
- $page_template = $template_file;
- } elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) {
- // template.html.twig
- $page_template = $template . TEMPLATE_EXT;
- $format = 'html';
- } elseif ($loader->exists($default . $twig_extension)) {
- // default.xxx.twig
- $page_template = $default . $twig_extension;
- } else {
- // default.html.twig
- $page_template = $default . TEMPLATE_EXT;
- $format = 'html';
- }
+ if ($loader->exists($template_file)) {
+ // template.xxx.twig
+ $page_template = $template_file;
+ } elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) {
+ // template.html.twig
+ $page_template = $template . TEMPLATE_EXT;
+ $format = 'html';
+ } elseif ($loader->exists($default . $twig_extension)) {
+ // default.xxx.twig
+ $page_template = $default . $twig_extension;
+ } else {
+ // default.html.twig
+ $page_template = $default . TEMPLATE_EXT;
+ $format = 'html';
}
return $page_template;
diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php
index de325bb..7bef097 100644
--- a/system/src/Grav/Common/Uri.php
+++ b/system/src/Grav/Common/Uri.php
@@ -160,8 +160,8 @@ class Uri
$language = $grav['language'];
// add the port to the base for non-standard ports
- if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
- $this->base .= ':' . (string)$this->port;
+ if ($this->port && $config->get('system.reverse_proxy_setup') === false) {
+ $this->base .= ':' . $this->port;
}
// Handle custom base
@@ -176,8 +176,8 @@ class Uri
if (isset($custom_parts['scheme'])) {
$this->base = $custom_parts['scheme'] . '://' . $custom_parts['host'];
$this->port = $custom_parts['port'] ?? null;
- if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
- $this->base .= ':' . (string)$this->port;
+ if ($this->port && $config->get('system.reverse_proxy_setup') === false) {
+ $this->base .= ':' . $this->port;
}
$this->root = $custom_base;
} else {
@@ -462,8 +462,8 @@ class Uri
public function port($raw = false)
{
$port = $this->port;
- // If not in raw mode and port is not set, figure it out from scheme.
- if (!$raw && $port === null) {
+ // If not in raw mode and port is not set or is 0, figure it out from scheme.
+ if (!$raw && !$port) {
if ($this->scheme === 'http') {
$this->port = 80;
} elseif ($this->scheme === 'https') {
@@ -471,7 +471,7 @@ class Uri
}
}
- return $this->port;
+ return $this->port ?: null;
}
/**
@@ -586,33 +586,38 @@ class Uri
/**
* Return relative path to the referrer defaulting to current or given page.
*
+ * You should set the third parameter to `true` for redirects as long as you came from the same sub-site and language.
+ *
* @param string|null $default
* @param string|null $attributes
+ * @param bool $withoutBaseRoute
* @return string
*/
- public function referrer($default = null, $attributes = null)
+ public function referrer($default = null, $attributes = null, bool $withoutBaseRoute = false)
{
$referrer = $_SERVER['HTTP_REFERER'] ?? null;
// Check that referrer came from our site.
- $root = $this->rootUrl(true);
- if ($referrer) {
- // Referrer should always have host set and it should come from the same base address.
- if (stripos($referrer, $root) !== 0) {
- $referrer = null;
- }
+ if ($withoutBaseRoute) {
+ /** @var Pages $pages */
+ $pages = Grav::instance()['pages'];
+ $base = $pages->baseUrl(null, true);
+ } else {
+ $base = $this->rootUrl(true);
}
- if (!$referrer) {
+ // Referrer should always have host set and it should come from the same base address.
+ if (!is_string($referrer) || !str_starts_with($referrer, $base)) {
$referrer = $default ?: $this->route(true, true);
}
+ // Relative path from grav root.
+ $referrer = substr($referrer, strlen($base));
if ($attributes) {
$referrer .= $attributes;
}
- // Return relative path.
- return substr($referrer, strlen($root));
+ return $referrer;
}
/**
@@ -648,7 +653,7 @@ class Uri
return [
'scheme' => $this->scheme,
'host' => $this->host,
- 'port' => $this->port,
+ 'port' => $this->port ?: null,
'user' => $this->user,
'pass' => $this->password,
'path' => $path,
@@ -1146,11 +1151,8 @@ class Uri
public static function isValidUrl($url)
{
$regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/';
- if (preg_match($regex, $url)) {
- return true;
- }
- return false;
+ return (bool)preg_match($regex, $url);
}
/**
@@ -1301,7 +1303,7 @@ class Uri
*/
protected function hasStandardPort()
{
- return ($this->port === 80 || $this->port === 443);
+ return (!$this->port || $this->port === 80 || $this->port === 443);
}
/**
diff --git a/system/src/Grav/Common/User/DataUser/User.php b/system/src/Grav/Common/User/DataUser/User.php
index 4735342..8fcdbbb 100644
--- a/system/src/Grav/Common/User/DataUser/User.php
+++ b/system/src/Grav/Common/User/DataUser/User.php
@@ -57,6 +57,7 @@ class User extends Data implements UserInterface
* @param string $offset
* @return bool
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
$value = parent::offsetExists($offset);
@@ -73,6 +74,7 @@ class User extends Data implements UserInterface
* @param string $offset
* @return mixed
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
$value = parent::offsetGet($offset);
diff --git a/system/src/Grav/Common/User/Group.php b/system/src/Grav/Common/User/Group.php
index 5be5386..7dd6d65 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 edc093e..5295a64 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 ?: '');
}
/**
@@ -166,9 +167,9 @@ abstract class Utils
if ($locator->isStream($path)) {
$path = $locator->findResource($path, true);
- } elseif (!Utils::startsWith($path, GRAV_ROOT)) {
+ } elseif (!static::startsWith($path, GRAV_ROOT)) {
$base_url = Grav::instance()['base_url'];
- $path = GRAV_ROOT . '/' . ltrim(Utils::replaceFirstOccurrence($base_url, '', $path), '/');
+ $path = GRAV_ROOT . '/' . ltrim(static::replaceFirstOccurrence($base_url, '', $path), '/');
}
return $path;
@@ -628,6 +629,23 @@ abstract class Utils
return substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length);
}
+ /**
+ * Generates a random string with configurable length, prefix and suffix.
+ * Unlike the built-in `uniqid()`, this string is non-conflicting and safe
+ *
+ * @param int $length
+ * @param array $options
+ * @return string
+ * @throws Exception
+ */
+ public static function uniqueId(int $length = 13, array $options = []): string
+ {
+ $options = array_merge(['prefix' => '', 'suffix' => ''], $options);
+ $bytes = random_bytes(ceil($length / 2));
+
+ return $options['prefix'] . substr(bin2hex($bytes), 0, $length) . $options['suffix'];
+ }
+
/**
* Provides the ability to download a file to the browser
*
@@ -750,13 +768,13 @@ abstract class Utils
if (is_string($http_accept)) {
$negotiator = new Negotiator();
- $supported_types = Utils::getSupportPageTypes(['html', 'json']);
- $priorities = Utils::getMimeTypes($supported_types);
+ $supported_types = static::getSupportPageTypes(['html', 'json']);
+ $priorities = static::getMimeTypes($supported_types);
$media_type = $negotiator->getBest($http_accept, $priorities);
$mimetype = $media_type instanceof Accept ? $media_type->getValue() : '';
- return Utils::getExtensionByMime($mimetype);
+ return static::getExtensionByMime($mimetype);
}
return 'html';
@@ -791,13 +809,7 @@ abstract class Utils
$media_types = Grav::instance()['config']->get('media.types');
- if (isset($media_types[$extension])) {
- if (isset($media_types[$extension]['mime'])) {
- return $media_types[$extension]['mime'];
- }
- }
-
- return $default;
+ return $media_types[$extension]['mime'] ?? $default;
}
/**
@@ -1555,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));
}
@@ -1629,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';
@@ -1739,7 +1751,7 @@ abstract class Utils
{
$enc_url = preg_replace_callback(
'%[^:/@?&=#]+%usD',
- function ($matches) {
+ static function ($matches) {
return urlencode($matches[0]);
},
$url
@@ -1763,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 5adbd0c..330b6c3 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/Cli/CleanCommand.php b/system/src/Grav/Console/Cli/CleanCommand.php
index b0c9499..84b46d6 100644
--- a/system/src/Grav/Console/Cli/CleanCommand.php
+++ b/system/src/Grav/Console/Cli/CleanCommand.php
@@ -102,11 +102,10 @@ class CleanCommand extends Command
'vendor/dragonmantank/cron-expression/composer.json',
'vendor/dragonmantank/cron-expression/tests',
'vendor/dragonmantank/cron-expression/CHANGELOG.md',
- 'vendor/enshrined/svg-sanitize/tests',
- 'vendor/enshrined/svg-sanitize/.gitignore',
- 'vendor/enshrined/svg-sanitize/.travis.yml',
- 'vendor/enshrined/svg-sanitize/composer.json',
- 'vendor/enshrined/svg-sanitize/phpunit.xml',
+ 'vendor/rhukster/dom-sanitizer/tests',
+ 'vendor/rhukster/dom-sanitizer/.gitignore',
+ 'vendor/rhukster/dom-sanitizer/composer.json',
+ 'vendor/rhukster/dom-sanitizer/composer.lock',
'vendor/erusev/parsedown/composer.json',
'vendor/erusev/parsedown/phpunit.xml.dist',
'vendor/erusev/parsedown/.travis.yml',
diff --git a/system/src/Grav/Console/Gpm/IndexCommand.php b/system/src/Grav/Console/Gpm/IndexCommand.php
index 64d3597..ecc91fe 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 2c38afb..b157a5d 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
@@ -131,7 +131,7 @@ class PermissionsReader
*/
protected static function getDependencies(array $dependencies): array
{
- $list = [];
+ $list = [[]];
foreach ($dependencies as $name => $deps) {
$current = $deps ? static::getDependencies($deps) : [];
$current[] = $name;
diff --git a/system/src/Grav/Framework/Collection/AbstractFileCollection.php b/system/src/Grav/Framework/Collection/AbstractFileCollection.php
index 50e9051..1be8d12 100644
--- a/system/src/Grav/Framework/Collection/AbstractFileCollection.php
+++ b/system/src/Grav/Framework/Collection/AbstractFileCollection.php
@@ -24,10 +24,10 @@ use function array_slice;
* Collection of objects stored into a filesystem.
*
* @package Grav\Framework\Collection
- * @template TKey
- * @template T
+ * @template TKey of array-key
+ * @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 190f422..25d8672 100644
--- a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
+++ b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
@@ -20,8 +20,9 @@ use function count;
/**
* Abstract Index Collection.
- * @template TKey
+ * @template TKey of array-key
* @template T
+ * @template C of CollectionInterface
* @implements CollectionInterface
*/
abstract class AbstractIndexCollection implements CollectionInterface
@@ -144,6 +145,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* {@inheritDoc}
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return $this->containsKey($offset);
@@ -154,6 +156,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* {@inheritDoc}
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->get($offset);
@@ -164,6 +167,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* {@inheritDoc}
*/
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
if (null === $offset) {
@@ -178,9 +182,10 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* {@inheritDoc}
*/
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
- return $this->remove($offset);
+ $this->remove($offset);
}
/**
@@ -361,6 +366,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
* @param int $start
* @param int|null $limit
* @return static
+ * @phpstan-return static
*/
public function limit($start, $limit = null)
{
@@ -371,6 +377,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
* Reverse the order of the items.
*
* @return static
+ * @phpstan-return static
*/
public function reverse()
{
@@ -381,6 +388,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
* Shuffle items.
*
* @return static
+ * @phpstan-return static
*/
public function shuffle()
{
@@ -397,6 +405,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* @param array $keys
* @return static
+ * @phpstan-return static
*/
public function select(array $keys)
{
@@ -415,6 +424,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* @param array $keys
* @return static
+ * @phpstan-return static
*/
public function unselect(array $keys)
{
@@ -469,6 +479,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
*
* @param array $entries Elements.
* @return static
+ * @phpstan-return static
*/
protected function createFrom(array $entries)
{
@@ -521,6 +532,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
/**
* @param array|null $entries
* @return CollectionInterface
+ * @phpstan-return C
*/
abstract protected function loadCollection(array $entries = null): CollectionInterface;
diff --git a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php
index af7ffe1..9afaab1 100644
--- a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php
+++ b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php
@@ -15,7 +15,7 @@ use Doctrine\Common\Collections\AbstractLazyCollection as BaseAbstractLazyCollec
* General JSON serializable collection.
*
* @package Grav\Framework\Collection
- * @template TKey
+ * @template TKey of array-key
* @template T
* @extends BaseAbstractLazyCollection
* @implements CollectionInterface
diff --git a/system/src/Grav/Framework/Collection/ArrayCollection.php b/system/src/Grav/Framework/Collection/ArrayCollection.php
index 474a3fb..d76aa05 100644
--- a/system/src/Grav/Framework/Collection/ArrayCollection.php
+++ b/system/src/Grav/Framework/Collection/ArrayCollection.php
@@ -15,7 +15,7 @@ use Doctrine\Common\Collections\ArrayCollection as BaseArrayCollection;
* General JSON serializable collection.
*
* @package Grav\Framework\Collection
- * @template TKey
+ * @template TKey of array-key
* @template T
* @extends BaseArrayCollection
* @implements CollectionInterface
diff --git a/system/src/Grav/Framework/Collection/CollectionInterface.php b/system/src/Grav/Framework/Collection/CollectionInterface.php
index e024366..0739109 100644
--- a/system/src/Grav/Framework/Collection/CollectionInterface.php
+++ b/system/src/Grav/Framework/Collection/CollectionInterface.php
@@ -16,7 +16,7 @@ use JsonSerializable;
* Collection Interface.
*
* @package Grav\Framework\Collection
- * @template TKey
+ * @template TKey of array-key
* @template T
* @extends Collection
*/
@@ -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 5dd8d55..f7b4f25 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
- * @template T
- * @extends AbstractFileCollection
+ * @extends AbstractFileCollection
*/
class FileCollection extends AbstractFileCollection
{
diff --git a/system/src/Grav/Framework/Collection/FileCollectionInterface.php b/system/src/Grav/Framework/Collection/FileCollectionInterface.php
index ce6e18f..45c446c 100644
--- a/system/src/Grav/Framework/Collection/FileCollectionInterface.php
+++ b/system/src/Grav/Framework/Collection/FileCollectionInterface.php
@@ -15,7 +15,7 @@ use Doctrine\Common\Collections\Selectable;
* Collection of objects stored into a filesystem.
*
* @package Grav\Framework\Collection
- * @template TKey
+ * @template TKey of array-key
* @template T
* @extends CollectionInterface
* @extends Selectable
diff --git a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php
index 62ed3e1..afa08aa 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 8739282..ce28521 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 dff0836..8d608fd 100644
--- a/system/src/Grav/Framework/Flex/FlexDirectoryForm.php
+++ b/system/src/Grav/Framework/Flex/FlexDirectoryForm.php
@@ -15,6 +15,7 @@ use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
use Grav\Common\Grav;
use Grav\Common\Twig\Twig;
+use Grav\Common\Utils;
use Grav\Framework\Flex\Interfaces\FlexDirectoryFormInterface;
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
use Grav\Framework\Form\Interfaces\FormFlashInterface;
@@ -94,9 +95,14 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
$uniqueId = md5($directory->getFlexType() . '-directory-' . $this->name);
}
$this->setUniqueId($uniqueId);
+
$this->setFlashLookupFolder($directory->getDirectoryBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
$this->form = $options['form'] ?? null;
+ if (Utils::isPositive($this->form['disabled'] ?? false)) {
+ $this->disable();
+ }
+
$this->initialize();
}
@@ -129,6 +135,17 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
return $this;
}
+ /**
+ * @param string $uniqueId
+ * @return void
+ */
+ public function setUniqueId(string $uniqueId): void
+ {
+ if ($uniqueId !== '') {
+ $this->uniqueid = $uniqueId;
+ }
+ }
+
/**
* @param string $name
* @param mixed $default
diff --git a/system/src/Grav/Framework/Flex/FlexForm.php b/system/src/Grav/Framework/Flex/FlexForm.php
index aba0044..d25cefc 100644
--- a/system/src/Grav/Framework/Flex/FlexForm.php
+++ b/system/src/Grav/Framework/Flex/FlexForm.php
@@ -15,6 +15,7 @@ use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
use Grav\Common\Grav;
use Grav\Common\Twig\Twig;
+use Grav\Common\Utils;
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
use Grav\Framework\Flex\Interfaces\FlexObjectFormInterface;
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
@@ -125,10 +126,15 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
$uniqueId = md5($uniqueId);
}
$this->setUniqueId($uniqueId);
+
$directory = $object->getFlexDirectory();
$this->setFlashLookupFolder($options['flash_folder'] ?? $directory->getBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
$this->form = $options['form'] ?? null;
+ if (Utils::isPositive($this->items['disabled'] ?? $this->form['disabled'] ?? false)) {
+ $this->disable();
+ }
+
if (!empty($options['reset'])) {
$this->getFlash()->delete();
}
@@ -172,6 +178,17 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
return $this;
}
+ /**
+ * @param string $uniqueId
+ * @return void
+ */
+ public function setUniqueId(string $uniqueId): void
+ {
+ if ($uniqueId !== '') {
+ $this->uniqueid = $uniqueId;
+ }
+ }
+
/**
* @param string $name
* @param mixed $default
diff --git a/system/src/Grav/Framework/Flex/FlexIndex.php b/system/src/Grav/Framework/Flex/FlexIndex.php
index 08785c2..3ce0a46 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
*/
@@ -540,6 +540,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
*/
protected function createFrom(array $entries, string $keyField = null)
{
+ /** @phpstan-var static $index */
$index = new static($entries, $this->getFlexDirectory());
$index->setKeyField($keyField ?? $this->_keyField);
@@ -630,7 +631,10 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
*/
protected function loadCollection(array $entries = null): CollectionInterface
{
- return $this->getFlexDirectory()->loadCollection($entries ?? $this->getEntries(), $this->_keyField);
+ /** @var C $collection */
+ $collection = $this->getFlexDirectory()->loadCollection($entries ?? $this->getEntries(), $this->_keyField);
+
+ return $collection;
}
/**
diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php
index 945f5ad..ac51b34 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/Interfaces/FlexIndexInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexIndexInterface.php
index d751e4b..71dd727 100644
--- a/system/src/Grav/Framework/Flex/Interfaces/FlexIndexInterface.php
+++ b/system/src/Grav/Framework/Flex/Interfaces/FlexIndexInterface.php
@@ -51,6 +51,7 @@ interface FlexIndexInterface extends FlexCollectionInterface
*
* @param string|null $keyField Switch key field of the collection.
* @return static Returns a new Flex Collection with new key field.
+ * @phpstan-return static
* @api
*/
public function withKeyField(string $keyField = null);
diff --git a/system/src/Grav/Framework/Flex/Pages/FlexPageObject.php b/system/src/Grav/Framework/Flex/Pages/FlexPageObject.php
index 621dae0..ebc26b4 100644
--- a/system/src/Grav/Framework/Flex/Pages/FlexPageObject.php
+++ b/system/src/Grav/Framework/Flex/Pages/FlexPageObject.php
@@ -50,7 +50,7 @@ class FlexPageObject extends FlexObject implements PageInterface, FlexTranslateI
/** @var array|null */
protected $_reorder;
/** @var FlexPageObject|null */
- protected $_original;
+ protected $_originalObject;
/**
* Clone page.
@@ -264,7 +264,7 @@ class FlexPageObject extends FlexObject implements PageInterface, FlexTranslateI
*/
public function getOriginal()
{
- return $this->_original;
+ return $this->_originalObject;
}
/**
@@ -276,8 +276,8 @@ class FlexPageObject extends FlexObject implements PageInterface, FlexTranslateI
*/
public function storeOriginal(): void
{
- if (null === $this->_original) {
- $this->_original = clone $this;
+ if (null === $this->_originalObject) {
+ $this->_originalObject = clone $this;
}
}
diff --git a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
index 5b4f9f3..b2b9533 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
{
@@ -634,7 +636,7 @@ class FolderStorage extends AbstractFilesystemStorage
$flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS;
$iterator = new FilesystemIterator($path, $flags);
- $list = [];
+ $list = [[]];
/** @var SplFileInfo $info */
foreach ($iterator as $filename => $info) {
if (!$info->isDir() || strpos($info->getFilename(), '.') === 0) {
@@ -644,11 +646,7 @@ class FolderStorage extends AbstractFilesystemStorage
$list[] = $this->buildIndexFromFilesystem($filename);
}
- if (!$list) {
- return [];
- }
-
- return count($list) > 1 ? array_merge(...$list) : $list[0];
+ return array_merge(...$list);
}
/**
diff --git a/system/src/Grav/Framework/Form/Traits/FormTrait.php b/system/src/Grav/Framework/Form/Traits/FormTrait.php
index 8dfed19..d77a402 100644
--- a/system/src/Grav/Framework/Form/Traits/FormTrait.php
+++ b/system/src/Grav/Framework/Form/Traits/FormTrait.php
@@ -57,6 +57,8 @@ trait FormTrait
private $name;
/** @var string */
private $id;
+ /** @var bool */
+ private $enabled = true;
/** @var string */
private $uniqueid;
/** @var string */
@@ -90,6 +92,30 @@ trait FormTrait
$this->id = $id;
}
+ /**
+ * @return void
+ */
+ public function disable(): void
+ {
+ $this->enabled = false;
+ }
+
+ /**
+ * @return void
+ */
+ public function enable(): void
+ {
+ $this->enabled = true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabled(): bool
+ {
+ return $this->enabled;
+ }
+
/**
* @return string
*/
@@ -685,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 0000000..885b4f0
--- /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/Access/ArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php
index de5cc7a..affff03 100644
--- a/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php
+++ b/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php
@@ -21,6 +21,7 @@ trait ArrayAccessTrait
* @param mixed $offset An offset to check for.
* @return bool Returns TRUE on success or FALSE on failure.
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return $this->hasProperty($offset);
@@ -32,6 +33,7 @@ trait ArrayAccessTrait
* @param mixed $offset The offset to retrieve.
* @return mixed Can return all value types.
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->getProperty($offset);
@@ -44,6 +46,7 @@ trait ArrayAccessTrait
* @param mixed $value The value to set.
* @return void
*/
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->setProperty($offset, $value);
@@ -55,6 +58,7 @@ trait ArrayAccessTrait
* @param mixed $offset The offset to unset.
* @return void
*/
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->unsetProperty($offset);
diff --git a/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php
index e048565..8365875 100644
--- a/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php
+++ b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php
@@ -21,6 +21,7 @@ trait NestedArrayAccessTrait
* @param mixed $offset An offset to check for.
* @return bool Returns TRUE on success or FALSE on failure.
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return $this->hasNestedProperty($offset);
@@ -32,6 +33,7 @@ trait NestedArrayAccessTrait
* @param mixed $offset The offset to retrieve.
* @return mixed Can return all value types.
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->getNestedProperty($offset);
@@ -44,6 +46,7 @@ trait NestedArrayAccessTrait
* @param mixed $value The value to set.
* @return void
*/
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->setNestedProperty($offset, $value);
@@ -55,6 +58,7 @@ trait NestedArrayAccessTrait
* @param mixed $offset The offset to unset.
* @return void
*/
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->unsetNestedProperty($offset);
diff --git a/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php b/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php
index fa0920b..3c172fe 100644
--- a/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php
+++ b/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php
@@ -207,8 +207,6 @@ trait ObjectCollectionTrait
/**
* Create a copy from this collection by cloning all objects in the collection.
- *
- * @return static
*/
public function copy()
{
diff --git a/system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php b/system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php
index 0e2373e..a2431be 100644
--- a/system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php
+++ b/system/src/Grav/Framework/Object/Interfaces/NestedObjectCollectionInterface.php
@@ -15,7 +15,7 @@ use RuntimeException;
* Common Interface for both Objects and Collections
* @package Grav\Framework\Object
*
- * @template TKey
+ * @template TKey of array-key
* @template T
* @extends ObjectCollectionInterface
*/
diff --git a/system/src/Grav/Framework/Object/Interfaces/ObjectCollectionInterface.php b/system/src/Grav/Framework/Object/Interfaces/ObjectCollectionInterface.php
index 3a43981..8169c24 100644
--- a/system/src/Grav/Framework/Object/Interfaces/ObjectCollectionInterface.php
+++ b/system/src/Grav/Framework/Object/Interfaces/ObjectCollectionInterface.php
@@ -16,7 +16,7 @@ use Serializable;
/**
* ObjectCollection Interface
* @package Grav\Framework\Collection
- * @template TKey
+ * @template TKey of array-key
* @template T
* @extends CollectionInterface
* @extends Selectable
@@ -76,6 +76,7 @@ interface ObjectCollectionInterface extends CollectionInterface, Selectable, Ser
* Create a copy from this collection by cloning all objects in the collection.
*
* @return static
+ * @phpstan-return static
*/
public function copy();
diff --git a/system/src/Grav/Framework/Object/ObjectCollection.php b/system/src/Grav/Framework/Object/ObjectCollection.php
index 3a51ec8..3fdebce 100644
--- a/system/src/Grav/Framework/Object/ObjectCollection.php
+++ b/system/src/Grav/Framework/Object/ObjectCollection.php
@@ -21,7 +21,7 @@ use function array_slice;
/**
* Class contains a collection of objects.
*
- * @template TKey
+ * @template TKey of array-key
* @template T
* @extends ArrayCollection
* @implements NestedObjectCollectionInterface
diff --git a/system/src/Grav/Framework/Object/ObjectIndex.php b/system/src/Grav/Framework/Object/ObjectIndex.php
index 2c13765..50ac427 100644
--- a/system/src/Grav/Framework/Object/ObjectIndex.php
+++ b/system/src/Grav/Framework/Object/ObjectIndex.php
@@ -23,9 +23,10 @@ use function is_object;
* This is an abstract class and has some protected abstract methods to load objects which you need to implement in
* order to use the class.
*
- * @template TKey
- * @template T
- * @extends AbstractIndexCollection
+ * @template TKey of array-key
+ * @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,6 +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
*/
public function copy()
{
diff --git a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php
index a9935ee..9eadf72 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/system/src/Phive/Twig/Extensions/Deferred/DeferredExtension.php b/system/src/Phive/Twig/Extensions/Deferred/DeferredExtension.php
deleted file mode 100644
index 3a41e4a..0000000
--- a/system/src/Phive/Twig/Extensions/Deferred/DeferredExtension.php
+++ /dev/null
@@ -1,70 +0,0 @@
-getTemplateName();
- $this->blocks[$templateName][] = [ob_get_level(), $blockName];
- }
-
- public function resolve(\Twig_Template $template, array $context, array $blocks)
- {
- $templateName = $template->getTemplateName();
- if (empty($this->blocks[$templateName])) {
- return;
- }
-
- while ($block = array_pop($this->blocks[$templateName])) {
- [$level, $blockName] = $block;
- if (ob_get_level() !== $level) {
- continue;
- }
-
- $buffer = ob_get_clean();
-
- $blocks[$blockName] = array($template, 'block_'.$blockName.'_deferred');
- $template->displayBlock($blockName, $context, $blocks);
-
- echo $buffer;
- }
-
- if ($parent = $template->getParent($context)) {
- $this->resolve($parent, $context, $blocks);
- }
- }
-}
diff --git a/system/src/Twig/DeferredExtension/DeferredBlockNode.php b/system/src/Twig/DeferredExtension/DeferredBlockNode.php
new file mode 100644
index 0000000..6ae974f
--- /dev/null
+++ b/system/src/Twig/DeferredExtension/DeferredBlockNode.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Twig\DeferredExtension;
+
+use Twig\Compiler;
+use Twig\Node\BlockNode;
+
+final class DeferredBlockNode extends BlockNode
+{
+ public function compile(Compiler $compiler) : void
+ {
+ $name = $this->getAttribute('name');
+
+ $compiler
+ ->write("public function block_$name(\$context, array \$blocks = [])\n", "{\n")
+ ->indent()
+ ->write("\$this->deferred->defer(\$this, '$name');\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+
+ $compiler
+ ->addDebugInfo($this)
+ ->write("public function block_{$name}_deferred(\$context, array \$blocks = [])\n", "{\n")
+ ->indent()
+ ->subcompile($this->getNode('body'))
+ ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+}
diff --git a/system/src/Twig/DeferredExtension/DeferredExtension.php b/system/src/Twig/DeferredExtension/DeferredExtension.php
new file mode 100644
index 0000000..f27c2a3
--- /dev/null
+++ b/system/src/Twig/DeferredExtension/DeferredExtension.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Twig\DeferredExtension;
+
+use Twig\Environment;
+use Twig\Extension\AbstractExtension;
+use Twig\Template;
+
+final class DeferredExtension extends AbstractExtension
+{
+ private $blocks = [];
+
+ public function getTokenParsers() : array
+ {
+ return [new DeferredTokenParser()];
+ }
+
+ public function getNodeVisitors() : array
+ {
+ if (Environment::VERSION_ID < 20000) {
+ // Twig 1.x support
+ return [new DeferredNodeVisitorCompat()];
+ }
+
+ return [new DeferredNodeVisitor()];
+ }
+
+ public function defer(Template $template, string $blockName) : void
+ {
+ $templateName = $template->getTemplateName();
+ $this->blocks[$templateName][] = $blockName;
+ $index = \count($this->blocks[$templateName]) - 1;
+
+ \ob_start(function (string $buffer) use ($index, $templateName) {
+ unset($this->blocks[$templateName][$index]);
+
+ return $buffer;
+ });
+ }
+
+ public function resolve(Template $template, array $context, array $blocks) : void
+ {
+ $templateName = $template->getTemplateName();
+ if (empty($this->blocks[$templateName])) {
+ return;
+ }
+
+ while ($blockName = \array_pop($this->blocks[$templateName])) {
+ $buffer = \ob_get_clean();
+
+ $blocks[$blockName] = [$template, 'block_'.$blockName.'_deferred'];
+ $template->displayBlock($blockName, $context, $blocks);
+
+ echo $buffer;
+ }
+
+ if ($parent = $template->getParent($context)) {
+ $this->resolve($parent, $context, $blocks);
+ }
+ }
+}
diff --git a/system/src/Twig/DeferredExtension/DeferredExtensionNode.php b/system/src/Twig/DeferredExtension/DeferredExtensionNode.php
new file mode 100644
index 0000000..1b851b4
--- /dev/null
+++ b/system/src/Twig/DeferredExtension/DeferredExtensionNode.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Twig\DeferredExtension;
+
+use Twig\Compiler;
+use Twig\Node\Node;
+
+final class DeferredExtensionNode extends Node
+{
+ public function compile(Compiler $compiler) : void
+ {
+ $compiler
+ ->write("\$this->deferred = \$this->env->getExtension('".DeferredExtension::class."');\n")
+ ;
+ }
+}
diff --git a/system/src/Twig/DeferredExtension/DeferredNode.php b/system/src/Twig/DeferredExtension/DeferredNode.php
new file mode 100644
index 0000000..2ac73bd
--- /dev/null
+++ b/system/src/Twig/DeferredExtension/DeferredNode.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Twig\DeferredExtension;
+
+use Twig\Compiler;
+use Twig\Node\Node;
+
+final class DeferredNode extends Node
+{
+ public function compile(Compiler $compiler) : void
+ {
+ $compiler
+ ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n")
+ ;
+ }
+}
diff --git a/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php b/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php
new file mode 100644
index 0000000..aef7399
--- /dev/null
+++ b/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Twig\DeferredExtension;
+
+use Twig\Environment;
+use Twig\Node\ModuleNode;
+use Twig\Node\Node;
+use Twig\NodeVisitor\NodeVisitorInterface;
+
+final class DeferredNodeVisitor implements NodeVisitorInterface
+{
+ private $hasDeferred = false;
+
+ public function enterNode(Node $node, Environment $env) : Node
+ {
+ if (!$this->hasDeferred && $node instanceof DeferredBlockNode) {
+ $this->hasDeferred = true;
+ }
+
+ return $node;
+ }
+
+ public function leaveNode(Node $node, Environment $env) : ?Node
+ {
+ if ($this->hasDeferred && $node instanceof ModuleNode) {
+ $node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')]));
+ $node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')]));
+ $this->hasDeferred = false;
+ }
+
+ return $node;
+ }
+
+ public function getPriority() : int
+ {
+ return 0;
+ }
+}
diff --git a/system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php b/system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php
new file mode 100644
index 0000000..8c441bb
--- /dev/null
+++ b/system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Twig\DeferredExtension;
+
+use Twig\Environment;
+use Twig\Node\ModuleNode;
+use Twig\Node\Node;
+use Twig\NodeVisitor\NodeVisitorInterface;
+
+final class DeferredNodeVisitorCompat implements NodeVisitorInterface
+{
+ private $hasDeferred = false;
+
+ public function enterNode(\Twig_NodeInterface $node, Environment $env) : Node
+ {
+ if (!$this->hasDeferred && $node instanceof DeferredBlockNode) {
+ $this->hasDeferred = true;
+ }
+
+ return $node;
+ }
+
+ public function leaveNode(\Twig_NodeInterface $node, Environment $env) : ?Node
+ {
+ if ($this->hasDeferred && $node instanceof ModuleNode) {
+ $node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')]));
+ $node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')]));
+ $this->hasDeferred = false;
+ }
+
+ return $node;
+ }
+
+ public function getPriority() : int
+ {
+ return 0;
+ }
+}
diff --git a/system/src/Twig/DeferredExtension/DeferredTokenParser.php b/system/src/Twig/DeferredExtension/DeferredTokenParser.php
new file mode 100644
index 0000000..1870ae0
--- /dev/null
+++ b/system/src/Twig/DeferredExtension/DeferredTokenParser.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Twig\DeferredExtension;
+
+use Twig\Node\BlockNode;
+use Twig\Node\Node;
+use Twig\Parser;
+use Twig\Token;
+use Twig\TokenParser\AbstractTokenParser;
+use Twig\TokenParser\BlockTokenParser;
+
+final class DeferredTokenParser extends AbstractTokenParser
+{
+ private $blockTokenParser;
+
+ public function setParser(Parser $parser) : void
+ {
+ parent::setParser($parser);
+
+ $this->blockTokenParser = new BlockTokenParser();
+ $this->blockTokenParser->setParser($parser);
+ }
+
+ public function parse(Token $token) : Node
+ {
+ $stream = $this->parser->getStream();
+ $nameToken = $stream->next();
+ $deferredToken = $stream->nextIf(Token::NAME_TYPE, 'deferred');
+ $stream->injectTokens([$nameToken]);
+
+ $node = $this->blockTokenParser->parse($token);
+
+ if ($deferredToken) {
+ $this->replaceBlockNode($nameToken->getValue());
+ }
+
+ return $node;
+ }
+
+ public function getTag() : string
+ {
+ return 'block';
+ }
+
+ private function replaceBlockNode(string $name) : void
+ {
+ $block = $this->parser->getBlock($name)->getNode('0');
+ $this->parser->setBlock($name, $this->createDeferredBlockNode($block));
+ }
+
+ private function createDeferredBlockNode(BlockNode $block) : DeferredBlockNode
+ {
+ $name = $block->getAttribute('name');
+ $deferredBlock = new DeferredBlockNode($name, new Node([]), $block->getTemplateLine());
+
+ foreach ($block as $nodeName => $node) {
+ $deferredBlock->setNode($nodeName, $node);
+ }
+
+ if ($sourceContext = $block->getSourceContext()) {
+ $deferredBlock->setSourceContext($sourceContext);
+ }
+
+ return $deferredBlock;
+ }
+}
diff --git a/user/config/versions.yaml b/user/config/versions.yaml
index f9359e7..9480e4c 100644
--- a/user/config/versions.yaml
+++ b/user/config/versions.yaml
@@ -1,7 +1,8 @@
core:
grav:
- version: 1.7.21
+ version: 1.7.25
schema: 1.7.0_2020-11-20_1
history:
- { version: 1.7.16, date: '2021-06-10 14:03:35' }
- { version: 1.7.21, date: '2021-09-16 12:41:14' }
+ - { version: 1.7.25, date: '2021-12-06 12:22:00' }