diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e67e470fd..5320a0e5aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,25 @@ +# 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) diff --git a/composer.lock b/composer.lock index 68dc24c9d0..5fbc07ac55 100644 --- a/composer.lock +++ b/composer.lock @@ -56,16 +56,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.10", + "version": "1.2.11", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "9fdb22c2e97a614657716178093cd1da90a64aa8" + "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582" }, "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/0b072d51c5a9c6f3412f7ea3ab043d6603cb2582", + "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582", "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.2.11" }, "funding": [ { @@ -128,7 +128,7 @@ "type": "tidelift" } ], - "time": "2021-06-07T13:58:28+00:00" + "time": "2021-09-25T20:32:43+00:00" }, { "name": "composer/semver", @@ -596,21 +596,21 @@ }, { "name": "filp/whoops", - "version": "2.14.3", + "version": "2.14.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "89584ce67dd32307f1063cc43846674f4679feda" + "reference": "f056f1fe935d9ed86e698905a957334029899895" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/89584ce67dd32307f1063cc43846674f4679feda", - "reference": "89584ce67dd32307f1063cc43846674f4679feda", + "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", @@ -655,7 +655,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.3" + "source": "https://github.com/filp/whoops/tree/2.14.4" }, "funding": [ { @@ -663,7 +663,7 @@ "type": "github" } ], - "time": "2021-09-19T12:00:00+00:00" + "time": "2021-10-03T12:00:00+00:00" }, { "name": "getgrav/cache", @@ -778,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": { @@ -824,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" } ], @@ -847,9 +868,23 @@ ], "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", @@ -1118,21 +1153,21 @@ }, { "name": "maximebf/debugbar", - "version": "v1.17.1", + "version": "v1.17.2", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "0a3532556be0145603f8a9de23e76dc28eed7054" + "reference": "3541f09f09c003c4a9ff7ddb0eb3361a7f14d418" }, "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/3541f09f09c003c4a9ff7ddb0eb3361a7f14d418", + "reference": "3541f09f09c003c4a9ff7ddb0eb3361a7f14d418", "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": { @@ -1177,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.2" }, - "time": "2021-08-01T09:19:02+00:00" + "time": "2021-10-18T09:39:00+00:00" }, { "name": "miljar/php-exif", @@ -2048,16 +2083,16 @@ }, { "name": "rhukster/dom-sanitizer", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/rhukster/dom-sanitizer.git", - "reference": "836d1dd9808adee0e4bb0ce4cdacc0f9a1497b7e" + "reference": "4db3ef1ac3d5505d044c5eb12aa106ba745bf129" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/836d1dd9808adee0e4bb0ce4cdacc0f9a1497b7e", - "reference": "836d1dd9808adee0e4bb0ce4cdacc0f9a1497b7e", + "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/4db3ef1ac3d5505d044c5eb12aa106ba745bf129", + "reference": "4db3ef1ac3d5505d044c5eb12aa106ba745bf129", "shasum": "" }, "require": { @@ -2087,9 +2122,9 @@ "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.5" + "source": "https://github.com/rhukster/dom-sanitizer/tree/1.0.6" }, - "time": "2021-09-29T20:14:54+00:00" + "time": "2021-09-30T15:41:33+00:00" }, { "name": "rockettheme/toolbox", @@ -3313,25 +3348,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": { @@ -3340,7 +3374,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "4.x-dev" } }, "autoload": { @@ -3371,9 +3405,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", @@ -3907,24 +3941,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" @@ -3934,7 +3969,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", @@ -3944,7 +3979,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -3960,19 +3995,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", @@ -3986,7 +4045,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": [ { @@ -3998,28 +4057,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": { @@ -4031,7 +4086,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -4047,10 +4102,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", @@ -4059,9 +4129,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", @@ -4343,16 +4427,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": { @@ -4363,7 +4447,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": { @@ -4393,22 +4478,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.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f" + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f", - "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", "shasum": "" }, "require": { @@ -4443,9 +4528,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" }, - "time": "2021-09-17T15:28:14+00:00" + "time": "2021-10-02T14:08:47+00:00" }, { "name": "phpspec/prophecy", diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index dffe099020..1aeaf2bf21 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,14 +1459,23 @@ 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.official_gpm_only: + type: toggle + label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY + highlight: 1 + help: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + default: true + validate: + type: bool - gpm.method: + http_section: + type: section + title: PLUGIN_ADMIN.HTTP_SECTION + + http.method: type: toggle label: PLUGIN_ADMIN.GPM_METHOD highlight: auto @@ -1472,29 +1485,66 @@ form: fopen: PLUGIN_ADMIN.FOPEN curl: PLUGIN_ADMIN.CURL - gpm.official_gpm_only: + http.enable_proxy: type: toggle - label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY + label: PLUGIN_ADMIN.SSL_ENABLE_PROXY highlight: 1 - help: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY_HELP options: 1: PLUGIN_ADMIN.YES 0: PLUGIN_ADMIN.NO - default: true + 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 - gpm.verify_peer: + http.verify_host: type: toggle - label: PLUGIN_ADMIN.GPM_VERIFY_PEER + label: PLUGIN_ADMIN.SSL_VERIFY_HOST highlight: 1 - help: PLUGIN_ADMIN.GPM_VERIFY_PEER_HELP + 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 fb5f391f4a..652910d863 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 e3cbb369fe..633534327e 100644 --- a/system/defines.php +++ b/system/defines.php @@ -9,7 +9,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.7.23'); +define('GRAV_VERSION', '1.7.24'); 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 0000000000..0f38a75c40 Binary files /dev/null and b/system/images/watermark.png differ diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php index c5b21ffac4..bfb40578db 100644 --- a/system/src/Grav/Common/Data/Validation.php +++ b/system/src/Grav/Common/Data/Validation.php @@ -781,14 +781,22 @@ public static function typeArray($value, array $params, array $field) } // 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') { diff --git a/system/src/Grav/Common/Flex/Types/Users/UserObject.php b/system/src/Grav/Common/Flex/Types/Users/UserObject.php index 310c313fe6..424e5ebefb 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 @@ public function def($name, $default = null, $separator = null) 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 @@ public function authorize(string $action, string $scope = null): ?bool } } + // Check custom application access. $authorizeCallable = static::$authorizeCallable; if ($authorizeCallable instanceof Closure) { $authorizeCallable->bindTo($this); @@ -280,13 +291,14 @@ public function authorize(string $action, string $scope = null): ?bool 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 47cef7c096..98654b6a1b 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 8f3a73a2ba..916d5249a2 100644 --- a/system/src/Grav/Common/Getters.php +++ b/system/src/Grav/Common/Getters.php @@ -69,6 +69,7 @@ public function __unset($offset) * @param int|string $offset * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { if ($this->gettersVariable) { @@ -84,6 +85,7 @@ public function offsetExists($offset) * @param int|string $offset * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if ($this->gettersVariable) { @@ -99,6 +101,7 @@ public function offsetGet($offset) * @param int|string $offset * @param mixed $value */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($this->gettersVariable) { @@ -112,6 +115,7 @@ public function offsetSet($offset, $value) /** * @param int|string $offset */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { if ($this->gettersVariable) { diff --git a/system/src/Grav/Common/HTTP/Client.php b/system/src/Grav/Common/HTTP/Client.php new file mode 100644 index 0000000000..cd4f8dbb2a --- /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 0000000000..4a0513667c --- /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/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php index f0a35322dc..e637bddaf6 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' => 'தமிழ் (இலங்கை)' ], diff --git a/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php b/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php index 06056ebc61..f6e22224ec 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 @@ protected function image() $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 @@ protected function saveImage() $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/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php index 32b917cd28..930aeda2bf 100644 --- a/system/src/Grav/Common/Page/Collection.php +++ b/system/src/Grav/Common/Page/Collection.php @@ -187,6 +187,7 @@ public function key() * @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/Media.php b/system/src/Grav/Common/Page/Media.php index 94fd2c4f8a..e18201420c 100644 --- a/system/src/Grav/Common/Page/Media.php +++ b/system/src/Grav/Common/Page/Media.php @@ -63,6 +63,7 @@ public function __wakeup() * @param string $offset * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return parent::offsetExists($offset) ?: isset(static::$global[$offset]); @@ -72,6 +73,7 @@ public function offsetExists($offset) * @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 4097c8f587..50d69c747a 100644 --- a/system/src/Grav/Common/Page/Medium/GlobalMedia.php +++ b/system/src/Grav/Common/Page/Medium/GlobalMedia.php @@ -46,6 +46,7 @@ public function getPath(): ?string * @param string $offset * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return parent::offsetExists($offset) ?: !empty($this->resolveStream($offset)); @@ -55,6 +56,7 @@ public function offsetExists($offset) * @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 70d13f8fdb..46c832ee95 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\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 @@ public function retinaScale($scale = 1) 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 ![](image.jpg?watermark) 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/Pages.php b/system/src/Grav/Common/Page/Pages.php index 941c9a7b3b..9e5d47db09 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -649,7 +649,7 @@ protected function evaluate($value, PageInterface $self = null) $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 { diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php index b32e39e6bb..3b0d3fb14e 100644 --- a/system/src/Grav/Common/Plugin.php +++ b/system/src/Grav/Common/Plugin.php @@ -227,6 +227,7 @@ protected function disable(array $events) * @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') { @@ -244,6 +245,7 @@ public function offsetExists($offset) * @param string $offset The offset to retrieve. * @return mixed Can return all value types. */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if ($offset === 'title') { @@ -262,6 +264,7 @@ public function offsetGet($offset) * @param mixed $value The value to set. * @throws LogicException */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { throw new LogicException(__CLASS__ . ' blueprints cannot be modified.'); @@ -273,6 +276,7 @@ public function offsetSet($offset, $value) * @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/Processors/InitializeProcessor.php b/system/src/Grav/Common/Processors/InitializeProcessor.php index 61144648c9..55cba033cc 100644 --- a/system/src/Grav/Common/Processors/InitializeProcessor.php +++ b/system/src/Grav/Common/Processors/InitializeProcessor.php @@ -105,12 +105,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface // 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/Security.php b/system/src/Grav/Common/Security.php index 833e648088..fe259d8ba9 100644 --- a/system/src/Grav/Common/Security.php +++ b/system/src/Grav/Common/Security.php @@ -138,7 +138,7 @@ public static function detectXssFromArray(array $array, string $prefix = '', arr $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 @@ public static function detectXssFromArray(array $array, string $prefix = '', arr } } - if (!empty($list)) { - return array_merge(...$list); - } - - return $list; + return array_merge(...$list); } /** @@ -207,7 +203,7 @@ public static function detectXss($string, array $options = null): ?string $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); diff --git a/system/src/Grav/Common/User/DataUser/User.php b/system/src/Grav/Common/User/DataUser/User.php index 47353425de..8fcdbbbd6d 100644 --- a/system/src/Grav/Common/User/DataUser/User.php +++ b/system/src/Grav/Common/User/DataUser/User.php @@ -57,6 +57,7 @@ public function __construct(array $items = [], $blueprints = null) * @param string $offset * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { $value = parent::offsetExists($offset); @@ -73,6 +74,7 @@ public function offsetExists($offset) * @param string $offset * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { $value = parent::offsetGet($offset); diff --git a/system/src/Grav/Framework/Acl/PermissionsReader.php b/system/src/Grav/Framework/Acl/PermissionsReader.php index 2c38afbea0..4f4a6a3174 100644 --- a/system/src/Grav/Framework/Acl/PermissionsReader.php +++ b/system/src/Grav/Framework/Acl/PermissionsReader.php @@ -131,7 +131,7 @@ protected static function initTypes(array $types) */ 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/AbstractIndexCollection.php b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php index 6a8861e9ba..38d3ab51ef 100644 --- a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php +++ b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php @@ -144,6 +144,7 @@ public function removeElement($element) * * {@inheritDoc} */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return $this->containsKey($offset); @@ -154,6 +155,7 @@ public function offsetExists($offset) * * {@inheritDoc} */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); @@ -164,6 +166,7 @@ public function offsetGet($offset) * * {@inheritDoc} */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if (null === $offset) { @@ -178,6 +181,7 @@ public function offsetSet($offset, $value) * * {@inheritDoc} */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { return $this->remove($offset); diff --git a/system/src/Grav/Framework/Flex/FlexDirectoryForm.php b/system/src/Grav/Framework/Flex/FlexDirectoryForm.php index a2259678bc..66b6f3e148 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\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 @@ public function __construct(string $name, FlexDirectory $directory, array $optio $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->items['disabled'] ?? $this->form['disabled'] ?? false)) { + $this->disable(); + } + $this->initialize(); } diff --git a/system/src/Grav/Framework/Flex/FlexForm.php b/system/src/Grav/Framework/Flex/FlexForm.php index 4fb8daf272..d25cefc408 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\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 @@ public function __construct(string $name, FlexObjectInterface $object, array $op $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(); } diff --git a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php index 5b4f9f3861..ff2c94d7a6 100644 --- a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php +++ b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php @@ -634,7 +634,7 @@ protected function buildPrefixedIndexFromFilesystem($path) $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 +644,7 @@ protected function buildPrefixedIndexFromFilesystem($path) $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 8dfed19835..4362cf5e47 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 @@ public function setId(string $id): void $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 */ diff --git a/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php index de5cc7aeb4..affff03b0d 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 @@ public function offsetExists($offset) * @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 @@ public function offsetGet($offset) * @param mixed $value The value to set. * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->setProperty($offset, $value); @@ -55,6 +58,7 @@ public function offsetSet($offset, $value) * @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 e0485655c0..83658753d0 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 @@ public function offsetExists($offset) * @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 @@ public function offsetGet($offset) * @param mixed $value The value to set. * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->setNestedProperty($offset, $value); @@ -55,6 +58,7 @@ public function offsetSet($offset, $value) * @param mixed $offset The offset to unset. * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { $this->unsetNestedProperty($offset);