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 ![](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/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' }