From f5cb936c97f9e1ae2e634bd6f0e1c1e087d19be4 Mon Sep 17 00:00:00 2001 From: ouidade Date: Mon, 6 Dec 2021 13:54:48 +0100 Subject: [PATCH] maj --- CHANGELOG.md | 63 ++ composer.json | 23 +- composer.lock | 663 ++++++++++-------- system/blueprints/config/system.yaml | 90 ++- system/config/system.yaml | 20 +- system/defines.php | 2 +- system/images/watermark.png | Bin 0 -> 95789 bytes system/languages/ar.yaml | 11 + system/languages/ca.yaml | 16 + system/languages/fr.yaml | 14 +- system/languages/gl.yaml | 3 + system/languages/id.yaml | 112 ++- system/languages/mn.yaml | 147 ++++ system/languages/pt.yaml | 3 + system/languages/si.yaml | 9 + system/languages/tr.yaml | 2 + system/languages/zh-tw.yaml | 17 +- system/router.php | 21 +- system/src/Grav/Common/Assets/BaseAsset.php | 6 +- system/src/Grav/Common/Assets/Pipeline.php | 2 +- .../Common/Assets/Traits/AssetUtilsTrait.php | 6 +- system/src/Grav/Common/Backup/Backups.php | 5 +- system/src/Grav/Common/Cache.php | 8 +- system/src/Grav/Common/Data/Blueprint.php | 4 +- .../src/Grav/Common/Data/BlueprintSchema.php | 13 +- system/src/Grav/Common/Data/Data.php | 2 +- system/src/Grav/Common/Data/Validation.php | 16 +- .../Grav/Common/Data/ValidationException.php | 23 +- system/src/Grav/Common/Debugger.php | 2 +- system/src/Grav/Common/Filesystem/Folder.php | 7 +- .../Grav/Common/Filesystem/ZipArchiver.php | 7 +- system/src/Grav/Common/Flex/FlexObject.php | 2 +- .../Flex/Types/Pages/PageCollection.php | 7 +- .../Common/Flex/Types/Pages/PageIndex.php | 27 +- .../Common/Flex/Types/Pages/PageObject.php | 14 +- .../Types/Pages/Traits/PageLegacyTrait.php | 5 +- .../Flex/Types/Users/UserCollection.php | 4 +- .../Common/Flex/Types/Users/UserIndex.php | 4 +- .../Common/Flex/Types/Users/UserObject.php | 22 +- system/src/Grav/Common/GPM/Response.php | 144 +--- system/src/Grav/Common/Getters.php | 4 + system/src/Grav/Common/Grav.php | 32 +- system/src/Grav/Common/HTTP/Client.php | 130 ++++ system/src/Grav/Common/HTTP/Response.php | 96 +++ system/src/Grav/Common/Helpers/Excerpts.php | 7 +- system/src/Grav/Common/Helpers/LogViewer.php | 22 +- system/src/Grav/Common/Iterator.php | 4 +- .../Grav/Common/Language/LanguageCodes.php | 17 +- .../Common/Media/Traits/ImageMediaTrait.php | 8 + .../Common/Media/Traits/MediaUploadTrait.php | 2 +- system/src/Grav/Common/Page/Collection.php | 3 +- .../Page/Interfaces/PageContentInterface.php | 2 +- system/src/Grav/Common/Page/Media.php | 2 + .../Grav/Common/Page/Medium/GlobalMedia.php | 2 + .../Grav/Common/Page/Medium/ImageMedium.php | 62 ++ system/src/Grav/Common/Page/Page.php | 25 +- system/src/Grav/Common/Page/Pages.php | 86 ++- system/src/Grav/Common/Plugin.php | 25 + system/src/Grav/Common/Plugins.php | 2 +- .../Common/Processors/InitializeProcessor.php | 8 +- .../Grav/Common/Processors/PagesProcessor.php | 20 +- system/src/Grav/Common/Scheduler/Job.php | 2 +- system/src/Grav/Common/Security.php | 22 +- .../Common/Service/ConfigServiceProvider.php | 2 +- system/src/Grav/Common/Session.php | 4 +- system/src/Grav/Common/Taxonomy.php | 2 +- .../Common/Twig/Extension/GravExtension.php | 39 +- system/src/Grav/Common/Twig/Twig.php | 35 +- system/src/Grav/Common/Uri.php | 48 +- system/src/Grav/Common/User/DataUser/User.php | 2 + system/src/Grav/Common/User/Group.php | 2 +- system/src/Grav/Common/Utils.php | 46 +- system/src/Grav/Common/Yaml.php | 6 +- system/src/Grav/Console/Cli/CleanCommand.php | 9 +- system/src/Grav/Console/Gpm/IndexCommand.php | 2 - .../Grav/Framework/Acl/PermissionsReader.php | 4 +- .../Collection/AbstractFileCollection.php | 6 +- .../Collection/AbstractIndexCollection.php | 16 +- .../Collection/AbstractLazyCollection.php | 2 +- .../Framework/Collection/ArrayCollection.php | 2 +- .../Collection/CollectionInterface.php | 10 +- .../Framework/Collection/FileCollection.php | 6 +- .../Collection/FileCollectionInterface.php | 2 +- .../Traits/ControllerResponseTrait.php | 13 +- system/src/Grav/Framework/Flex/Flex.php | 2 +- .../Grav/Framework/Flex/FlexDirectoryForm.php | 17 + system/src/Grav/Framework/Flex/FlexForm.php | 17 + system/src/Grav/Framework/Flex/FlexIndex.php | 8 +- system/src/Grav/Framework/Flex/FlexObject.php | 62 +- .../Flex/Interfaces/FlexIndexInterface.php | 1 + .../Framework/Flex/Pages/FlexPageObject.php | 8 +- .../Framework/Flex/Storage/FolderStorage.php | 10 +- .../Grav/Framework/Form/Traits/FormTrait.php | 28 +- .../Logger/Processors/UserProcessor.php | 34 + .../Object/Access/ArrayAccessTrait.php | 4 + .../Object/Access/NestedArrayAccessTrait.php | 4 + .../Object/Base/ObjectCollectionTrait.php | 2 - .../NestedObjectCollectionInterface.php | 2 +- .../Interfaces/ObjectCollectionInterface.php | 3 +- .../Framework/Object/ObjectCollection.php | 2 +- .../src/Grav/Framework/Object/ObjectIndex.php | 8 +- .../RequestHandler/Middlewares/Exceptions.php | 31 +- .../Extensions/Deferred/DeferredExtension.php | 70 -- .../DeferredExtension/DeferredBlockNode.php | 43 ++ .../DeferredExtension/DeferredExtension.php | 72 ++ .../DeferredExtensionNode.php | 27 + .../Twig/DeferredExtension/DeferredNode.php | 27 + .../DeferredExtension/DeferredNodeVisitor.php | 49 ++ .../DeferredNodeVisitorCompat.php | 49 ++ .../DeferredExtension/DeferredTokenParser.php | 77 ++ user/config/versions.yaml | 3 +- 111 files changed, 2189 insertions(+), 858 deletions(-) create mode 100644 system/images/watermark.png create mode 100644 system/languages/mn.yaml create mode 100644 system/languages/si.yaml create mode 100644 system/src/Grav/Common/HTTP/Client.php create mode 100644 system/src/Grav/Common/HTTP/Response.php create mode 100644 system/src/Grav/Framework/Logger/Processors/UserProcessor.php delete mode 100644 system/src/Phive/Twig/Extensions/Deferred/DeferredExtension.php create mode 100644 system/src/Twig/DeferredExtension/DeferredBlockNode.php create mode 100644 system/src/Twig/DeferredExtension/DeferredExtension.php create mode 100644 system/src/Twig/DeferredExtension/DeferredExtensionNode.php create mode 100644 system/src/Twig/DeferredExtension/DeferredNode.php create mode 100644 system/src/Twig/DeferredExtension/DeferredNodeVisitor.php create mode 100644 system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php create mode 100644 system/src/Twig/DeferredExtension/DeferredTokenParser.php 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 0000000000000000000000000000000000000000..0f38a75c40d48ba2fd062a82eb50090f4cc61133 GIT binary patch literal 95789 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%0D2)uoNCMzd4 z4#kTL#oXu@n|bCUT5!dX{{bK=muucT!s8qNM%;`z^jbhDM!bJ^86LrH?o%%R6+eKa zT*VKXBBH&y|NN8X#hm3vF-YYZN*%axg_A?&jbNBY9whVQs|4n*a1rf>oYobl?Zc`a zvTE=D;!u?;M17i~Y)R|7JnVD#H5bvY-&l*J^!X;-2eDzot+=>Vn5RHiuoooI@7`Za z?MpvT{5WHV6v@!FyhS(s{;KfOp{nEDY5^OshFiUj1mNX+gA4{Rd&=`2T4PkkPGxS+ zMbU6}1PonH1t#*^?JQ|9hxN96P^JZq=r4-XIil`PVr5mJWTF4gUKVHe>e@+13Y<`4 zNAQq>Q^62XL4EUYw%F$YTZLpN{YkxzF`|pV8m2gA45qW=H07gat$xR0lxLNQ`4;YG z*LxUdO=$~w7zuO|=V2!1hk4TT`7CSf^_nsrRk58yYw&v7>mv8gr~giNj-b5Gm!%uB zS-FgcrNeIYdt!2vL6JoyDxx&nacQ#5XOi5xpr>&W{!cdD04TfgD|E38#c7|<@sU41 z_T*1!*&a{zC9CS~ml*p;PF}AZo}KGv*L<}AT2X)m(DX&BPJeZ`ondl9mRnUK+&&p?#h#8<@)BktPi1`AU zG}>IKg-A*qyZ8lU+mQ$);(-$uWl8c;>Ky}Hw7%O3X<#4|Kc@k>%l5as(mnkt1}AyN zuznp3S-zY@sp=QYRDYaQumX;`g#!IWyQFd>Lj$E;a}>TSm-}W2twH&#ua5Kzv4A)N zlIj4UeHi=;&~Ci0IdOC7p*`hVnP`H!K`M3!F@tDaXI1P|+Ij{#ty!1?mJaNj(>NS8 z%UL&SxPbORE}&hO188UB-{k>-_AU+bgds)$%N~ee2k-%#UgB%Fl~)i$mqSfpnGVKm z%xIF4^rg67G~j@S7+~>9eW3&XxEB=?-j$BdOUy>XdB4;jHkN`q-2p+Y5<5<+C0o4n zXwTM`&v3C|HU&f#^SLEW)xcLsB!S1oY~?H9dfe(c!xG~wQW;?DB< zE>b?Yxfg!(G{CrBiI!isYjJ0X%&gD%0RLIm)_QuZ@e9RyZk2^Qwq=tH7yGHl;_l}v zs(9&WbW9h%=nvbL>8|(pMf}m(o%4AZN$;>v#^IVqY94RGYA;<9w>`;Igu*aXFoi_A+%ElxP*3{jfHq%iyQ$6 z?JpRlTYb&~S+H@VP8M<2lU+g%mP1h+{4Jt5HWWBE5+uK7zaRGxJBBVP)q5KR5ZdWB z-QyfWdoq{M?vW#57O~bfdJovwpk4Pr{35gqW;2G)zVlxnM2#@8){1e!@+6P>lCL?Lu*0Mne227@aEqxv_8xGvUAP_#GB}hy#;P0$}np)|JO6(JzVCdnU4CGv{^1 zRN|etgC9Hq9$$Y!S{Yh^By56}wm(7^Q0S$ok>Y=R_Z zeHd=ud^E?8M%ZG6`7vYjjJyz@Spd9ni&6pHp`TO~H?&{Jo#Gj7$d6 zGA=oYwkK+q1lZer`Z}eyXV+@HFqmocC}9^Tod|L@9mS3fTZcTSh4i-BoeXbWMW3h? z^5hz7*i2{%6V6_cxwnMG#}xoVTuqI9za+zlr;h1#$f6uFGC}94o889->ygCJ!d_*A zO6k@_b=U0)|HC$Js|~oBng`Lml?1M_I+eQ%PNa8#SRZhxi37TgUB~^@y40rBvM)Uw z53(+(rrXWdwB}3~yLtHN8zb@h+}q(_g=CZBQTkfmMRJZmH`H!{`>Xp~~kj ze=;}4TX5|Yx-6Y^iUkFW8VLsOpK%w3C8)90opgEP`Mlb&N7J^0^E5dT%NiM#0ABC( zx0s9#?LjtdYhZKP++>{1gqo|;6|5P2G%$18&vdnIV%kGN;etp3!uxksa8pb)f3wYx T&xفشل التحقق من صحة:' 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' }