diff --git a/.editorconfig b/.editorconfig index 8d06361b3..bb3487439 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,5 +13,5 @@ indent_size = 4 trim_trailing_whitespace = true # 2 space indentation -[*.{yaml,yml}] +[*.{yaml,yml,vue,js,css}] indent_size = 2 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6f9d9b21b..53f95be15 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,11 +12,15 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Extract Tag + run: echo "PACKAGE_VERSION=${{ github.ref }}" >> $GITHUB_ENV + - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 7.3 extensions: opcache, gd + tools: composer:v2 coverage: none env: COMPOSER_TOKEN: ${{ secrets.GLOBAL_TOKEN }} @@ -38,13 +42,14 @@ jobs: run: | bash ./build-grav.sh - - name: Upload Grav Release Assets - id: upload-release-asset - uses: alexellis/upload-assets@0.2.3 - env: - GITHUB_TOKEN: ${{ secrets.GLOBAL_TOKEN }} + - name: Upload packages to release + uses: svenstaro/upload-release-action@v2 with: - asset_paths: '["./grav-dist/*.zip"]' + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ env.PACKAGE_VERSION }} + file: ./grav-dist/*.zip + overwrite: true + file_glob: true slack: name: Slack diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e83e0c488..ea22ad291 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -25,6 +25,7 @@ jobs: with: php-version: ${{ matrix.php }} extensions: opcache, gd + tools: composer:v2 coverage: none env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 26ca72dff..5f80bdaa1 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ tests/cache/* tests/error.log system/templates/testing/* /user/config/versions.yaml +/user/cli/config/security.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index da12f46ff..0ce4cba37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,198 @@ -# v1.7.11 +# v1.7.24 ## mm/dd/2021 +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 + +# 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 + +1. [](#new) + * Added `|yaml` filter to convert input to YAML + * Added `route` and `request` to `onPageNotFound` event + * Added file upload/remove support for `Flex Forms` + * Added support for `flex-required@: not exists` and `flex-required@: '!exists'` in blueprints + * Added `$object->getOriginalData()` to get flex objects data before it was modified with `update()` + * Throwing exceptions from Twig templates fires `onDisplayErrorPage.[code]` event allowing better error pages +2. [](#improved) + * Use a simplified text-based `cron` field for scheduler + * Add timestamp to logging output of scheduler jobs to see when they ran +3. [](#bugfix) + * Fixed escaping in PageIndex::getLevelListing() + * Fixed validation of `number` type [#3433](https://github.com/getgrav/grav/issues/3433) + * Fixed excessive `security.yaml` file creation [#3432](https://github.com/getgrav/grav/issues/3432) + * Fixed incorrect port :0 with nginx unix socket setup [#3439](https://github.com/getgrav/grav/issues/3439) + * Fixed `Session::setFlashCookieObject()` to use the same options as the main session cookie + +# v1.7.20 +## 09/01/2021 + +2. [](#improved) + * Added support for `task` and `action` inside JSON request body + +# v1.7.19 +## 08/31/2021 + +1. [](#new) + * Include active form and request in `onPageTask` and `onPageAction` events (defaults to `null`) + * Added `UserObject::$authorizeCallable` to allow `$user->authorize()` customization +2. [](#improved) + * Added meta support for `UploadedFile` class + * Added support for multiple mime-types per file extension [#3422](https://github.com/getgrav/grav/issues/3422) + * Added `setCurrent()` method to Page Collection [#3398](https://github.com/getgrav/grav/pull/3398) + * Initialize `$grav['uri']` before session +3. [](#bugfix) + * Fixed `Warning: Undefined array key "SERVER_SOFTWARE" in index.php` [#3408](https://github.com/getgrav/grav/issues/3408) + * Fixed error in `loadDirectoryConfig()` if configuration hasn't been saved [#3409](https://github.com/getgrav/grav/issues/3409) + * Fixed GPM not using non-standard cache path [#3410](https://github.com/getgrav/grav/issues/3410) + * Fixed broken `environment://` stream when it doesn't have configuration + * Fixed `Flex Object` missing key field value when using `FolderStorage` + * Fixed broken Twig try tag when catch has not been defined or is empty + * Fixed `FlexForm` serialization + * Fixed form validation for numeric values in PHP 8 + * Fixed `flex-options@` in blueprints duplicating items in array + * Fixed wrong form issue with flex objects after cache clear + * Fixed Flex object types not implementing `MediaInterface` + * Fixed issue with `svgImageFunction()` that was causing broken output + +# v1.7.18 +## 07/19/2021 + +1. [](#improved) + * Added support for loading Flex Directory configuration from main configuration + * Move SVGs that cannot be sanitized to quarantine folder under `log://quarantine` + * Added support for CloudFlare-forwarded client IP in the `URI::ip()` method +1. [](#bugfix) + * Fixed error when using Flex `SimpleStorage` with no entries + * Fixed page search to include slug field [#3316](https://github.com/getgrav/grav/issues/3316) + * Fixed Admin becoming unusable when GPM cannot be reached [#3383](https://github.com/getgrav/grav/issues/3383) + * Fixed `Failed to save entry: Forbidden` when moving a page to a visible page [#3389](https://github.com/getgrav/grav/issues/3389) + * Better support for Symfony local server on linux [#3400](https://github.com/getgrav/grav/pull/3400) + * Fixed `open_basedir()` error with some forms + +# v1.7.17 +## 06/15/2021 + +1. [](#new) + * Interface `FlexDirectoryInterface` now extends `FlexAuthorizeInterface` +1. [](#improved) + * Allow to unset an asset attribute by specifying null (ie, `'defer': null`) + * Support specifying custom attributes to assets in a collection [Read more](https://learn.getgrav.org/17/themes/asset-manager#collections-with-attributes?target=_blank) [#3358](https://github.com/getgrav/grav/issues/3358) + * File `frontmatter.yaml` isn't part of media, ignore it + * Switched default `JQuery` collection to use 3.x rather than 2.x +1. [](#bugfix) + * Fixed missing styles when CSS/JS Pipeline is used and `asset://` folder is missing + * Fixed permission check when moving a page [#3382](https://github.com/getgrav/grav/issues/3382) + +# v1.7.16 +## 06/02/2021 + +1. [](#new) + * Added 'addFrame()' method to ImageMedium [#3323](https://github.com/getgrav/grav/pull/3323) +1. [](#improved) + * Set `cache.clear_images_by_default` to `false` by default + * Improve error on bad nested form data [#3364](https://github.com/getgrav/grav/issues/3364) +1. [](#bugfix) + * Improve Plugin and Theme initialization to fix PHP8 bug [#3368](https://github.com/getgrav/grav/issues/3368) + * Fixed `pathinfo()` twig filter in PHP7 + * Fixed the first visible child page getting ordering number `999999.` [#3365](https://github.com/getgrav/grav/issues/3365) + * Fixed flex pages search using only folder name [#3316](https://github.com/getgrav/grav/issues/3316) + * Fixed flex pages using wrong type in `onBlueprintCreated` event [#3157](https://github.com/getgrav/grav/issues/3157) + * Fixed wrong SRI paths invoked when Grav instance as a sub folder [#3358](https://github.com/getgrav/grav/issues/3358) + * Fixed SRI trying to calculate remote assets, only ever set integrity for local files. Use the SRI provided by the remote source and manually add it in the `addJs/addCss` call for remote support. [#3358](https://github.com/getgrav/grav/issues/3358) + * Fix for weird regex issue with latest PHP versions on Intel Macs causing params to not parse properly in URI object + +# v1.7.15 +## 05/19/2021 + +1. [](#improved) + * Allow optional start date in page collections [#3350](https://github.com/getgrav/grav/pull/3350) + * Added `page` and `output` properties to `onOutputGenerated` and `onOutputRendered` events +1. [](#bugfix) + * Fixed twig deprecated TwigFilter messages [#3348](https://github.com/getgrav/grav/issues/3348) + * Fixed fatal error with some markdown links [getgrav/grav-premium-issues#95](https://github.com/getgrav/grav-premium-issues/issues/95) + * Fixed markdown media operations not working when using `image://` stream [#3333](https://github.com/getgrav/grav/issues/3333) [#3349](https://github.com/getgrav/grav/issues/3349) + * Fixed copying page without changing the slug [getgrav/grav-plugin-admin#2135](https://github.com/getgrav/grav-plugin-admin/issues/2139) + * Fixed missing and commonly used methods when using `system.twig.undefined_functions = false` [getgrav/grav-plugin-admin#2138](https://github.com/getgrav/grav-plugin-admin/issues/2138) + * Fixed uploading images into Flex Object if field destination is not set + +# v1.7.14 +## 04/29/2021 + +1. [](#new) + * Added `MediaUploadTrait::checkFileMetadata()` method +1. [](#improved) + * Updating a theme should always keep the custom files [getgrav/grav-plugin-admin#2135](https://github.com/getgrav/grav-plugin-admin/issues/2135) +1. [](#bugfix) + * Fixed broken numeric language codes in Flex Pages [#3332](https://github.com/getgrav/grav/issues/3332) + * Fixed broken `exif_imagetype()` twig function + +# v1.7.13 +## 04/23/2021 + +1. [](#new) + * Added support for getting translated collection of Flex Pages using `$collection->withTranslated('de')` +1. [](#improved) + * Moved `gregwar/Image` and `gregwar/Cache` in-house to official `getgrav/Image` and `getgrav/Cache` packagist packages. This will help environments with very strict proxy setups that don't allow VCS setup. [#3289](https://github.com/getgrav/grav/issues/3289) + * Improved XSS Invalid Protocol detection regex [#3298](https://github.com/getgrav/grav/issues/3298) + * Added support for user provided folder in Flex `$page->copy()` +1. [](#bugfix) + * Fixed `The "Grav/Common/Twig/TwigExtension" extension is not enabled` when using markdown twig tag [#3317](https://github.com/getgrav/grav/issues/3317) + * Fixed text field maxlength validation newline issue [#3324](https://github.com/getgrav/grav/issues/3324) + * Fixed a bug in Flex Object `refresh()` method + +# v1.7.12 +## 04/15/2021 + +1. [](#improved) + * Improve JSON support for the request +1. [](#bugfix) + * Fixed absolute path support for Windows [#3297](https://github.com/getgrav/grav/issues/3297) + * Fixed adding tags in admin after upgrading Grav [#3315](https://github.com/getgrav/grav/issues/3315) + +# v1.7.11 +## 04/13/2021 + +1. [](#new) + * Added configuration options to allow PHP methods to be used in Twig functions (`system.twig.safe_functions`) and filters (`system.twig.safe_filters`) + * Deprecated using PHP methods in Twig without them being in the safe lists + * Prevent dangerous PHP methods from being used as Twig functions and filters + * Restrict filesystem Twig functions to accept only local filesystem and grav streams 1. [](#improved) * Better GPM detection of unauthorized installations 1. [](#bugfix) + * **IMPORTANT** Fixed security vulnerability with Twig allowing dangerous PHP functions by default [GHSA-g8r4-p96j-xfxc](https://github.com/getgrav/grav/security/advisories/GHSA-g8r4-p96j-xfxc) * Fixed nxinx appending repeating `?_url=` in some redirects * Fixed deleting page with language code not removing the folder if it was the last language [#3305](https://github.com/getgrav/grav/issues/3305) * Fixed fatal error when using markdown links with `image://` stream [#3285](https://github.com/getgrav/grav/issues/3285) diff --git a/README.md b/README.md index 562349f91..31a4a7e4b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ #  Grav [](https://github.com/phpstan/phpstan) -[](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [](https://chat.getgrav.org) [](https://github.com/getgrav/grav/actions?query=workflow%3A%22PHP+Tests%22) [](#backers) [](#sponsors) diff --git a/composer.json b/composer.json index 34d1e7e09..e86c6aa06 100644 --- a/composer.json +++ b/composer.json @@ -44,8 +44,8 @@ "filp/whoops": "~2.9", "matthiasmullie/minify": "^1.3", "monolog/monolog": "~1.25", - "gregwar/image": "dev-php8", - "gregwar/cache": "dev-php8", + "getgrav/image": "^3.0", + "getgrav/cache": "^2.0", "donatj/phpuseragentparser": "~1.1", "pimple/pimple": "~3.3.0", "rockettheme/toolbox": "~1.5", @@ -55,19 +55,18 @@ "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", "phpunit/php-code-coverage": "~9.2", - "victorjonsson/markdowndocs": "dev-master", + "getgrav/markdowndocs": "^2.0", "codeception/module-asserts": "^1.3", "codeception/module-phpbrowser": "^1.0", "symfony/service-contracts": "*" @@ -91,23 +90,10 @@ "php": "7.3.6" } }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator" - }, - { - "type": "vcs", - "url": "https://github.com/getgrav/Cache" - }, - { - "type": "vcs", - "url": "https://github.com/getgrav/Image" - } - ], "autoload": { "psr-4": { - "Grav\\": "system/src/Grav" + "Grav\\": "system/src/Grav", + "Twig\\": "system/src/Twig" }, "files": [ "system/defines.php" @@ -121,8 +107,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 350b627b3..df02b8d5a 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": "4ae6fc7274c018b1bb34bb1b80bd62c5", + "content-hash": "23dd68cea2a3f2d963e57638131f1122", "packages": [ { "name": "antoligy/dom-string-iterators", @@ -56,16 +56,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.9", + "version": "1.2.11", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "78a0e288fdcebf92aa2318a8d3656168da6ac1a5" + "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/78a0e288fdcebf92aa2318a8d3656168da6ac1a5", - "reference": "78a0e288fdcebf92aa2318a8d3656168da6ac1a5", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0b072d51c5a9c6f3412f7ea3ab043d6603cb2582", + "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582", "shasum": "" }, "require": { @@ -77,7 +77,7 @@ "phpstan/phpstan": "^0.12.55", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" }, "type": "library", "extra": { @@ -112,7 +112,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.2.9" + "source": "https://github.com/composer/ca-bundle/tree/1.2.11" }, "funding": [ { @@ -128,7 +128,7 @@ "type": "tidelift" } ], - "time": "2021-01-12T12:10:35+00:00" + "time": "2021-09-25T20:32:43+00:00" }, { "name": "composer/semver", @@ -212,16 +212,16 @@ }, { "name": "doctrine/cache", - "version": "1.10.2", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "13e3381b25847283a91948d04640543941309727" + "reference": "4cf401d14df219fa6f38b671f5493449151c9ad8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", - "reference": "13e3381b25847283a91948d04640543941309727", + "url": "https://api.github.com/repos/doctrine/cache/zipball/4cf401d14df219fa6f38b671f5493449151c9ad8", + "reference": "4cf401d14df219fa6f38b671f5493449151c9ad8", "shasum": "" }, "require": { @@ -232,20 +232,19 @@ }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^6.0", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0", - "predis/predis": "~1.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" }, "suggest": { "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" @@ -292,7 +291,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.10.x" + "source": "https://github.com/doctrine/cache/tree/1.12.1" }, "funding": [ { @@ -308,30 +307,30 @@ "type": "tidelift" } ], - "time": "2020-07-07T18:54:01+00:00" + "time": "2021-07-17T14:39:21+00:00" }, { "name": "doctrine/collections", - "version": "1.6.7", + "version": "1.6.8", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a" + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/55f8b799269a1a472457bd1a41b4f379d4cfba4a", - "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", + "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", "shasum": "" }, "require": { "php": "^7.1.3 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan-shim": "^0.9.2", - "phpunit/phpunit": "^7.0", - "vimeo/psalm": "^3.8.1" + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.2.1" }, "type": "library", "autoload": { @@ -375,26 +374,26 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.6.7" + "source": "https://github.com/doctrine/collections/tree/1.6.8" }, - "time": "2020-07-27T17:53:49+00:00" + "time": "2021-08-10T18:51:53+00:00" }, { "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", @@ -433,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": [ { @@ -445,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", @@ -494,51 +493,6 @@ }, "time": "2017-01-23T04:29:33+00:00" }, - { - "name": "enshrined/svg-sanitize", - "version": "0.14.0", - "source": { - "type": "git", - "url": "https://github.com/darylldoyle/svg-sanitizer.git", - "reference": "beff89576a72540ee99476aeb9cfe98222e76fb8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/beff89576a72540ee99476aeb9cfe98222e76fb8", - "reference": "beff89576a72540ee99476aeb9cfe98222e76fb8", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*" - }, - "require-dev": { - "codeclimate/php-test-reporter": "^0.1.2", - "phpunit/phpunit": "^6" - }, - "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.0" - }, - "time": "2021-01-21T10:13:20+00:00" - }, { "name": "erusev/parsedown", "version": "1.7.4", @@ -642,21 +596,21 @@ }, { "name": "filp/whoops", - "version": "2.12.0", + "version": "2.14.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "d501fd2658d55491a2295ff600ae5978eaad7403" + "reference": "f056f1fe935d9ed86e698905a957334029899895" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/d501fd2658d55491a2295ff600ae5978eaad7403", - "reference": "d501fd2658d55491a2295ff600ae5978eaad7403", + "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.12.0" + "source": "https://github.com/filp/whoops/tree/2.14.4" }, "funding": [ { @@ -709,21 +663,21 @@ "type": "github" } ], - "time": "2021-03-30T12:00:00+00:00" + "time": "2021-10-03T12:00:00+00:00" }, { - "name": "gregwar/cache", - "version": "dev-php8", + "name": "getgrav/cache", + "version": "v2.0.0", "target-dir": "Gregwar/Cache", "source": { "type": "git", "url": "https://github.com/getgrav/Cache.git", - "reference": "49ccdf9ae760b009a192bc3c7b417980c8a8cc2e" + "reference": "56fd63f752779928fcd1074ab7d12f406dde8861" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getgrav/Cache/zipball/49ccdf9ae760b009a192bc3c7b417980c8a8cc2e", - "reference": "49ccdf9ae760b009a192bc3c7b417980c8a8cc2e", + "url": "https://api.github.com/repos/getgrav/Cache/zipball/56fd63f752779928fcd1074ab7d12f406dde8861", + "reference": "56fd63f752779928fcd1074ab7d12f406dde8861", "shasum": "" }, "require": { @@ -735,6 +689,7 @@ "Gregwar\\Cache": "" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -742,6 +697,11 @@ { "name": "Gregwar", "email": "g.passault@gmail.com" + }, + { + "name": "Grav CMS", + "email": "hello@getgrav.org", + "homepage": "https://getgrav.org" } ], "description": "A lightweight file-system cache system", @@ -752,28 +712,28 @@ "system" ], "support": { - "source": "https://github.com/getgrav/Cache/tree/php8" + "source": "https://github.com/getgrav/Cache/tree/v2.0.0" }, - "time": "2020-12-02T10:54:35+00:00" + "time": "2021-04-20T05:48:00+00:00" }, { - "name": "gregwar/image", - "version": "dev-php8", + "name": "getgrav/image", + "version": "v3.0.0", "target-dir": "Gregwar/Image", "source": { "type": "git", "url": "https://github.com/getgrav/Image.git", - "reference": "ea23859700f32447a85e79d96f331e3d6c8897a8" + "reference": "02c1bb2c179dd894c4f6610c9c49da364ee7d264" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getgrav/Image/zipball/ea23859700f32447a85e79d96f331e3d6c8897a8", - "reference": "ea23859700f32447a85e79d96f331e3d6c8897a8", + "url": "https://api.github.com/repos/getgrav/Image/zipball/02c1bb2c179dd894c4f6610c9c49da364ee7d264", + "reference": "02c1bb2c179dd894c4f6610c9c49da364ee7d264", "shasum": "" }, "require": { "ext-gd": "*", - "gregwar/cache": "dev-php8", + "getgrav/cache": "^2.0", "php": "^5.6 || ^7.0 || ^8.0" }, "require-dev": { @@ -789,6 +749,7 @@ "Gregwar\\Image": "" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -797,6 +758,11 @@ "name": "Grégoire Passault", "email": "g.passault@gmail.com", "homepage": "http://www.gregwar.com/" + }, + { + "name": "Grav CMS", + "email": "hello@getgrav.org", + "homepage": "https://getgrav.org" } ], "description": "Image handling", @@ -806,22 +772,22 @@ "image" ], "support": { - "source": "https://github.com/getgrav/Image/tree/php8" + "source": "https://github.com/getgrav/Image/tree/v3.0.0" }, - "time": "2021-03-15T17:03:52+00:00" + "time": "2021-04-20T05:50:18+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.8.1", + "version": "1.8.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1" + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/35ea11d335fd638b5882ff1725228b3d35496ab1", - "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", "shasum": "" }, "require": { @@ -858,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" } ], @@ -881,22 +868,36 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.1" + "source": "https://github.com/guzzle/psr7/tree/1.8.3" }, - "time": "2021-03-21T16:25:00+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.0.7", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "e41ee368ff4dcc30d3f4563fe8bd80ed72b293b4" + "reference": "b963dee47429a49c9669981cfa9a8362ce209278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/e41ee368ff4dcc30d3f4563fe8bd80ed72b293b4", - "reference": "e41ee368ff4dcc30d3f4563fe8bd80ed72b293b4", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/b963dee47429a49c9669981cfa9a8362ce209278", + "reference": "b963dee47429a49c9669981cfa9a8362ce209278", "shasum": "" }, "require": { @@ -944,7 +945,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.0.7" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.0" }, "funding": [ { @@ -952,7 +953,7 @@ "type": "github" } ], - "time": "2021-03-14T16:29:40+00:00" + "time": "2021-08-07T23:04:17+00:00" }, { "name": "league/climate", @@ -1152,21 +1153,21 @@ }, { "name": "maximebf/debugbar", - "version": "v1.16.5", + "version": "v1.17.2", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62" + "reference": "3541f09f09c003c4a9ff7ddb0eb3361a7f14d418" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6d51ee9e94cff14412783785e79a4e7ef97b9d62", - "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/3541f09f09c003c4a9ff7ddb0eb3361a7f14d418", + "reference": "3541f09f09c003c4a9ff7ddb0eb3361a7f14d418", "shasum": "" }, "require": { "php": "^7.1|^8", - "psr/log": "^1.0", + "psr/log": "^1|^2|^3", "symfony/var-dumper": "^2.6|^3|^4|^5" }, "require-dev": { @@ -1180,7 +1181,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.16-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -1211,9 +1212,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.16.5" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.17.2" }, - "time": "2020-12-07T11:07:24+00:00" + "time": "2021-10-18T09:39:00+00:00" }, { "name": "miljar/php-exif", @@ -1277,16 +1278,16 @@ }, { "name": "monolog/monolog", - "version": "1.26.0", + "version": "1.26.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "2209ddd84e7ef1256b7af205d0717fb62cfc9c33" + "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/2209ddd84e7ef1256b7af205d0717fb62cfc9c33", - "reference": "2209ddd84e7ef1256b7af205d0717fb62cfc9c33", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c6b00f05152ae2c9b04a448f99c7590beb6042f5", + "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5", "shasum": "" }, "require": { @@ -1347,7 +1348,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/1.26.0" + "source": "https://github.com/Seldaek/monolog/tree/1.26.1" }, "funding": [ { @@ -1359,20 +1360,20 @@ "type": "tidelift" } ], - "time": "2020-12-14T12:56:38+00:00" + "time": "2021-05-28T08:32:12+00:00" }, { "name": "nyholm/psr7", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "23ae1f00fbc6a886cbe3062ca682391b9cc7c37b" + "reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/23ae1f00fbc6a886cbe3062ca682391b9cc7c37b", - "reference": "23ae1f00fbc6a886cbe3062ca682391b9cc7c37b", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/2212385b47153ea71b1c1b1374f8cb5e4f7892ec", + "reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec", "shasum": "" }, "require": { @@ -1386,7 +1387,7 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "http-interop/http-factory-tests": "^0.8", + "http-interop/http-factory-tests": "^0.9", "php-http/psr7-integration-tests": "^1.0", "phpunit/phpunit": "^7.5 || 8.5 || 9.4", "symfony/error-handler": "^4.4" @@ -1424,7 +1425,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.4.0" + "source": "https://github.com/Nyholm/psr7/tree/1.4.1" }, "funding": [ { @@ -1436,20 +1437,20 @@ "type": "github" } ], - "time": "2021-02-18T15:41:32+00:00" + "time": "2021-07-02T08:32:20+00:00" }, { "name": "nyholm/psr7-server", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7-server.git", - "reference": "5c134aeb5dd6521c7978798663470dabf0528c96" + "reference": "b846a689844cef114e8079d8c80f0afd96745ae3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/5c134aeb5dd6521c7978798663470dabf0528c96", - "reference": "5c134aeb5dd6521c7978798663470dabf0528c96", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/b846a689844cef114e8079d8c80f0afd96745ae3", + "reference": "b846a689844cef114e8079d8c80f0afd96745ae3", "shasum": "" }, "require": { @@ -1490,7 +1491,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7-server/issues", - "source": "https://github.com/Nyholm/psr7-server/tree/1.0.1" + "source": "https://github.com/Nyholm/psr7-server/tree/1.0.2" }, "funding": [ { @@ -1502,60 +1503,7 @@ "type": "github" } ], - "time": "2020-11-15T15:26:20+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" + "time": "2021-05-12T11:11:27+00:00" }, { "name": "php-http/message-factory", @@ -1990,16 +1938,16 @@ }, { "name": "psr/log", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -2023,7 +1971,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -2034,9 +1982,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "psr/simple-cache", @@ -2134,17 +2082,62 @@ "time": "2019-03-08T08:55:37+00:00" }, { - "name": "rockettheme/toolbox", - "version": "1.5.7", + "name": "rhukster/dom-sanitizer", + "version": "1.0.6", "source": { "type": "git", - "url": "https://github.com/rockettheme/toolbox.git", - "reference": "8d3ebc4d982595d6eac90e851f2b4d5c0cec0399" + "url": "https://github.com/rhukster/dom-sanitizer.git", + "reference": "4db3ef1ac3d5505d044c5eb12aa106ba745bf129" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/8d3ebc4d982595d6eac90e851f2b4d5c0cec0399", - "reference": "8d3ebc4d982595d6eac90e851f2b4d5c0cec0399", + "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": { @@ -2185,9 +2178,9 @@ ], "support": { "issues": "https://github.com/rockettheme/toolbox/issues", - "source": "https://github.com/rockettheme/toolbox/tree/1.5.7" + "source": "https://github.com/rockettheme/toolbox/tree/1.5.10" }, - "time": "2021-02-17T17:58:36+00:00" + "time": "2021-09-29T16:50:13+00:00" }, { "name": "seld/cli-prompt", @@ -2246,36 +2239,37 @@ }, { "name": "symfony/console", - "version": "v4.4.21", + "version": "v4.4.30", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1ba4560dbbb9fcf5ae28b61f71f49c678086cf23" + "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1ba4560dbbb9fcf5ae28b61f71f49c678086cf23", - "reference": "1ba4560dbbb9fcf5ae28b61f71f49c678086cf23", + "url": "https://api.github.com/repos/symfony/console/zipball/a3f7189a0665ee33b50e9e228c46f50f5acbed22", + "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22", "shasum": "" }, "require": { "php": ">=7.1.3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", + "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2" }, "conflict": { + "psr/log": ">=3", "symfony/dependency-injection": "<3.4", "symfony/event-dispatcher": "<4.3|>=5", "symfony/lock": "<4.4", "symfony/process": "<3.3" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2", "symfony/config": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/event-dispatcher": "^4.3", @@ -2315,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.21" + "source": "https://github.com/symfony/console/tree/v4.4.30" }, "funding": [ { @@ -2331,7 +2325,7 @@ "type": "tidelift" } ], - "time": "2021-03-26T09:23:24+00:00" + "time": "2021-08-25T19:27:26+00:00" }, { "name": "symfony/contracts", @@ -2429,21 +2423,22 @@ }, { "name": "symfony/event-dispatcher", - "version": "v4.4.20", + "version": "v4.4.30", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "c352647244bd376bf7d31efbd5401f13f50dad0c" + "reference": "2fe81680070043c4c80e7cedceb797e34f377bac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c352647244bd376bf7d31efbd5401f13f50dad0c", - "reference": "c352647244bd376bf7d31efbd5401f13f50dad0c", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2fe81680070043c4c80e7cedceb797e34f377bac", + "reference": "2fe81680070043c4c80e7cedceb797e34f377bac", "shasum": "" }, "require": { "php": ">=7.1.3", - "symfony/event-dispatcher-contracts": "^1.1" + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" }, "conflict": { "symfony/dependency-injection": "<3.4" @@ -2453,7 +2448,7 @@ "symfony/event-dispatcher-implementation": "1.1" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2|^3", "symfony/config": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/error-handler": "~3.4|~4.4", @@ -2492,7 +2487,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.20" + "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.30" }, "funding": [ { @@ -2508,27 +2503,28 @@ "type": "tidelift" } ], - "time": "2021-01-27T09:09:26+00:00" + "time": "2021-08-04T20:31:23+00:00" }, { "name": "symfony/http-client", - "version": "v4.4.21", + "version": "v4.4.31", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "911177e186b82e5b9a9f41c13af53699b6745657" + "reference": "6b900ffa399e25203f30f79f6f4a56b89eee14c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/911177e186b82e5b9a9f41c13af53699b6745657", - "reference": "911177e186b82e5b9a9f41c13af53699b6745657", + "url": "https://api.github.com/repos/symfony/http-client/zipball/6b900ffa399e25203f30f79f6f4a56b89eee14c2", + "reference": "6b900ffa399e25203f30f79f6f4a56b89eee14c2", "shasum": "" }, "require": { "php": ">=7.1.3", - "psr/log": "^1.0", + "psr/log": "^1|^2|^3", "symfony/http-client-contracts": "^1.1.10|^2", "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.0|^2" }, "provide": { @@ -2572,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.21" + "source": "https://github.com/symfony/http-client/tree/v4.4.31" }, "funding": [ { @@ -2588,20 +2584,20 @@ "type": "tidelift" } ], - "time": "2021-03-25T17:52:07+00:00" + "time": "2021-09-06T10:00:00+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -2613,7 +2609,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2651,7 +2647,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" }, "funding": [ { @@ -2667,20 +2663,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342" + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/06fb361659649bcfd6a208a0f1fcaf4e827ad342", - "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", "shasum": "" }, "require": { @@ -2692,7 +2688,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2731,7 +2727,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0" }, "funding": [ { @@ -2747,20 +2743,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.1", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", "shasum": "" }, "require": { @@ -2772,7 +2768,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2811,7 +2807,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" }, "funding": [ { @@ -2827,20 +2823,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T12:26:48+00:00" }, { "name": "symfony/polyfill-php74", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php74.git", - "reference": "577e147350331efeb816897e004d85e6e765daaf" + "reference": "a5d80cdf049bd3b0af6da91184a2cd37533c0fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/577e147350331efeb816897e004d85e6e765daaf", - "reference": "577e147350331efeb816897e004d85e6e765daaf", + "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/a5d80cdf049bd3b0af6da91184a2cd37533c0fd8", + "reference": "a5d80cdf049bd3b0af6da91184a2cd37533c0fd8", "shasum": "" }, "require": { @@ -2849,7 +2845,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2891,7 +2887,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php74/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php74/tree/v1.23.0" }, "funding": [ { @@ -2907,20 +2903,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.22.1", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", "shasum": "" }, "require": { @@ -2929,7 +2925,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2974,7 +2970,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" }, "funding": [ { @@ -2990,24 +2986,25 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-07-28T13:41:28+00:00" }, { "name": "symfony/process", - "version": "v4.4.20", + "version": "v4.4.30", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7e950b6366d4da90292c2e7fa820b3c1842b965a" + "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7e950b6366d4da90292c2e7fa820b3c1842b965a", - "reference": "7e950b6366d4da90292c2e7fa820b3c1842b965a", + "url": "https://api.github.com/repos/symfony/process/zipball/13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d", + "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d", "shasum": "" }, "require": { - "php": ">=7.1.3" + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -3035,7 +3032,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v4.4.20" + "source": "https://github.com/symfony/process/tree/v4.4.30" }, "funding": [ { @@ -3051,27 +3048,27 @@ "type": "tidelift" } ], - "time": "2021-01-27T09:09:26+00:00" + "time": "2021-08-04T20:31:23+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.4.21", + "version": "v4.4.31", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0da0e174f728996f5d5072d6a9f0a42259dbc806" + "reference": "1f12cc0c2e880a5f39575c19af81438464717839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0da0e174f728996f5d5072d6a9f0a42259dbc806", - "reference": "0da0e174f728996f5d5072d6a9f0a42259dbc806", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1f12cc0c2e880a5f39575c19af81438464717839", + "reference": "1f12cc0c2e880a5f39575c19af81438464717839", "shasum": "" }, "require": { "php": ">=7.1.3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php72": "~1.5", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", @@ -3124,7 +3121,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v4.4.21" + "source": "https://github.com/symfony/var-dumper/tree/v4.4.31" }, "funding": [ { @@ -3140,20 +3137,20 @@ "type": "tidelift" } ], - "time": "2021-03-27T19:49:03+00:00" + "time": "2021-09-24T15:30:11+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.21", + "version": "v4.4.29", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3871c720871029f008928244e56cf43497da7e9d" + "reference": "3abcc4db06d4e776825eaa3ed8ad924d5bc7432a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3871c720871029f008928244e56cf43497da7e9d", - "reference": "3871c720871029f008928244e56cf43497da7e9d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3abcc4db06d4e776825eaa3ed8ad924d5bc7432a", + "reference": "3abcc4db06d4e776825eaa3ed8ad924d5bc7432a", "shasum": "" }, "require": { @@ -3195,7 +3192,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.21" + "source": "https://github.com/symfony/yaml/tree/v4.4.29" }, "funding": [ { @@ -3211,20 +3208,20 @@ "type": "tidelift" } ], - "time": "2021-03-05T17:58:50+00:00" + "time": "2021-07-27T16:19:30+00:00" }, { "name": "twig/twig", - "version": "v1.44.2", + "version": "v1.44.5", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "138c493c5b8ee7cff3821f80b8896d371366b5fe" + "reference": "dd4353357c5a116322e92a00d16043a31881a81e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/138c493c5b8ee7cff3821f80b8896d371366b5fe", - "reference": "138c493c5b8ee7cff3821f80b8896d371366b5fe", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/dd4353357c5a116322e92a00d16043a31881a81e", + "reference": "dd4353357c5a116322e92a00d16043a31881a81e", "shasum": "" }, "require": { @@ -3277,7 +3274,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v1.44.2" + "source": "https://github.com/twigphp/Twig/tree/v1.44.5" }, "funding": [ { @@ -3289,7 +3286,7 @@ "type": "tidelift" } ], - "time": "2021-01-05T10:10:05+00:00" + "time": "2021-09-17T08:35:19+00:00" }, { "name": "willdurand/negotiation", @@ -3351,25 +3348,24 @@ "packages-dev": [ { "name": "behat/gherkin", - "version": "v4.8.0", + "version": "v4.9.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd" + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2391482cd003dfdc36b679b27e9f5326bd656acd", - "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", "shasum": "" }, "require": { "php": "~7.2|~8.0" }, "require-dev": { - "cucumber/cucumber": "dev-gherkin-16.0.0", + "cucumber/cucumber": "dev-gherkin-22.0.0", "phpunit/phpunit": "~8|~9", - "symfony/phpunit-bridge": "~3|~4|~5", "symfony/yaml": "~3|~4|~5" }, "suggest": { @@ -3378,7 +3374,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "4.x-dev" } }, "autoload": { @@ -3409,22 +3405,22 @@ ], "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", - "version": "4.1.19", + "version": "4.1.22", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "138dc9345a81ec994dcd6b9680c501a752a37b00" + "reference": "9777ec3690ceedc4bce2ed13af7af4ca4ee3088f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/138dc9345a81ec994dcd6b9680c501a752a37b00", - "reference": "138dc9345a81ec994dcd6b9680c501a752a37b00", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/9777ec3690ceedc4bce2ed13af7af4ca4ee3088f", + "reference": "9777ec3690ceedc4bce2ed13af7af4ca4ee3088f", "shasum": "" }, "require": { @@ -3435,7 +3431,7 @@ "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "guzzlehttp/psr7": "~1.4", + "guzzlehttp/psr7": "^1.4 | ^2.0", "php": ">=5.6.0 <9.0", "symfony/console": ">=2.7 <6.0", "symfony/css-selector": ">=2.7 <6.0", @@ -3444,11 +3440,11 @@ "symfony/yaml": ">=2.7 <6.0" }, "require-dev": { - "codeception/module-asserts": "*@dev", - "codeception/module-cli": "*@dev", - "codeception/module-db": "*@dev", - "codeception/module-filesystem": "*@dev", - "codeception/module-phpbrowser": "*@dev", + "codeception/module-asserts": "1.*@dev", + "codeception/module-cli": "1.*@dev", + "codeception/module-db": "1.*@dev", + "codeception/module-filesystem": "1.*@dev", + "codeception/module-phpbrowser": "1.*@dev", "codeception/specify": "~0.3", "codeception/util-universalframework": "*@dev", "monolog/monolog": "~1.8", @@ -3498,7 +3494,7 @@ ], "support": { "issues": "https://github.com/Codeception/Codeception/issues", - "source": "https://github.com/Codeception/Codeception/tree/4.1.19" + "source": "https://github.com/Codeception/Codeception/tree/4.1.22" }, "funding": [ { @@ -3506,7 +3502,7 @@ "type": "open_collective" } ], - "time": "2021-03-28T13:26:08+00:00" + "time": "2021-08-06T17:15:34+00:00" }, { "name": "codeception/lib-asserts", @@ -3564,20 +3560,20 @@ }, { "name": "codeception/lib-innerbrowser", - "version": "1.4.1", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/Codeception/lib-innerbrowser.git", - "reference": "693e116f81ef98eae98c43ef785a726faf87394e" + "reference": "31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/693e116f81ef98eae98c43ef785a726faf87394e", - "reference": "693e116f81ef98eae98c43ef785a726faf87394e", + "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2", + "reference": "31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2", "shasum": "" }, "require": { - "codeception/codeception": "*@dev", + "codeception/codeception": "4.*@dev", "ext-dom": "*", "ext-json": "*", "ext-mbstring": "*", @@ -3618,9 +3614,9 @@ ], "support": { "issues": "https://github.com/Codeception/lib-innerbrowser/issues", - "source": "https://github.com/Codeception/lib-innerbrowser/tree/1.4.1" + "source": "https://github.com/Codeception/lib-innerbrowser/tree/1.5.1" }, - "time": "2021-03-02T08:01:54+00:00" + "time": "2021-08-30T15:21:42+00:00" }, { "name": "codeception/module-asserts", @@ -3892,25 +3888,78 @@ "time": "2020-11-10T18:47:58+00:00" }, { - "name": "guzzlehttp/guzzle", - "version": "7.3.0", + "name": "getgrav/markdowndocs", + "version": "2.0.1", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "7008573787b430c1c1f650e3722d9bba59967628" + "url": "https://github.com/getgrav/PHP-Markdown-Documentation-Generator.git", + "reference": "4a24d1b64a88da17e8f1696dc64969f5ca769064" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", - "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "url": "https://api.github.com/repos/getgrav/PHP-Markdown-Documentation-Generator/zipball/4a24d1b64a88da17e8f1696dc64969f5ca769064", + "reference": "4a24d1b64a88da17e8f1696dc64969f5ca769064", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": ">=2.6" + }, + "require-dev": { + "phpunit/phpunit": "3.7.23" + }, + "bin": [ + "bin/phpdoc-md" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPDocsMD": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Victor Jonsson", + "email": "kontakt@victorjonsson.se" + }, + { + "name": "Grav CMS", + "email": "hello@getgrav.org", + "homepage": "https://getgrav.org" + } + ], + "description": "Command line tool for generating markdown-formatted class documentation", + "homepage": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator", + "support": { + "source": "https://github.com/getgrav/PHP-Markdown-Documentation-Generator/tree/2.0.1" + }, + "time": "2021-04-20T06:04:42+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94" + }, + "dist": { + "type": "zip", + "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" @@ -3920,7 +3969,7 @@ "ext-curl": "*", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^8.5.5 || ^9.3.5", - "psr/log": "^1.1" + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { "ext-curl": "Required for CURL handler support", @@ -3930,7 +3979,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -3946,19 +3995,43 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, { "name": "Márk Sági-Kazár", "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", "keywords": [ "client", "curl", @@ -3972,7 +4045,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + "source": "https://github.com/guzzle/guzzle/tree/7.4.0" }, "funding": [ { @@ -3984,28 +4057,24 @@ "type": "github" }, { - "url": "https://github.com/alexeyshockov", - "type": "github" - }, - { - "url": "https://github.com/gmponos", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" } ], - "time": "2021-03-23T11:33:13+00:00" + "time": "2021-10-18T09:52:00+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + "reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/136a635e2b4a49b9d79e9c8fee267ffb257fdba0", + "reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0", "shasum": "" }, "require": { @@ -4017,7 +4086,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -4033,10 +4102,25 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle promises library", @@ -4045,9 +4129,23 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.1" + "source": "https://github.com/guzzle/promises/tree/1.5.0" }, - "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-07T13:05:22+00:00" }, { "name": "myclabs/deep-copy", @@ -4109,16 +4207,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.10.4", + "version": "v4.13.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { @@ -4159,22 +4257,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" }, - "time": "2020-12-20T10:01:03+00:00" + "time": "2021-09-20T12:20:58+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { @@ -4219,9 +4317,9 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2020-06-27T14:33:11+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", @@ -4329,16 +4427,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { @@ -4349,7 +4447,8 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -4379,22 +4478,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.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": { @@ -4402,7 +4501,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -4428,39 +4528,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": { @@ -4495,22 +4595,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.82", + "version": "0.12.99", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11" + "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3920f0fb0aff39263d3a4cb0bca120a67a1a6a11", - "reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4d40f1d759942f523be267a1bab6884f46ca3f7", + "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7", "shasum": "" }, "require": { @@ -4541,13 +4641,17 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.82" + "source": "https://github.com/phpstan/phpstan/tree/0.12.99" }, "funding": [ { "url": "https://github.com/ondrejmirtes", "type": "github" }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, { "url": "https://www.patreon.com/phpstan", "type": "patreon" @@ -4557,7 +4661,7 @@ "type": "tidelift" } ], - "time": "2021-03-19T06:08:17+00:00" + "time": "2021-09-12T20:09:55+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -4612,23 +4716,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "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/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.12.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -4677,7 +4781,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.7" }, "funding": [ { @@ -4685,7 +4789,7 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4930,16 +5034,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.4", + "version": "9.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c73c6737305e779771147af66c96ca6a7ed8a741" + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741", - "reference": "c73c6737305e779771147af66c96ca6a7ed8a741", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", "shasum": "" }, "require": { @@ -4951,11 +5055,11 @@ "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", + "phar-io/manifest": "^2.0.3", "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", @@ -4969,7 +5073,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3", + "sebastian/type": "^2.3.4", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -5017,7 +5121,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" }, "funding": [ { @@ -5029,7 +5133,7 @@ "type": "github" } ], - "time": "2021-03-23T07:16:29+00:00" + "time": "2021-09-25T07:38:51+00:00" }, { "name": "psr/http-client", @@ -5589,16 +5693,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.2", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", "shasum": "" }, "require": { @@ -5641,7 +5745,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" }, "funding": [ { @@ -5649,7 +5753,7 @@ "type": "github" } ], - "time": "2020-10-26T15:55:19+00:00" + "time": "2021-06-11T13:31:12+00:00" }, { "name": "sebastian/lines-of-code", @@ -5936,20 +6040,21 @@ "type": "github" } ], + "abandoned": true, "time": "2020-09-28T06:45:17+00:00" }, { "name": "sebastian/type", - "version": "2.3.1", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", "shasum": "" }, "require": { @@ -5984,7 +6089,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" }, "funding": [ { @@ -5992,7 +6097,7 @@ "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2021-06-15T12:49:02+00:00" }, { "name": "sebastian/version", @@ -6049,21 +6154,22 @@ }, { "name": "symfony/browser-kit", - "version": "v5.2.4", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "3ca3a57ce9860318b20a924fec5daf5c6db44d93" + "reference": "c1e3f64fcc631c96e2c5843b666db66679ced11c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/3ca3a57ce9860318b20a924fec5daf5c6db44d93", - "reference": "3ca3a57ce9860318b20a924fec5daf5c6db44d93", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c1e3f64fcc631c96e2c5843b666db66679ced11c", + "reference": "c1e3f64fcc631c96e2c5843b666db66679ced11c", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/dom-crawler": "^4.4|^5.0" + "symfony/dom-crawler": "^4.4|^5.0", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { "symfony/css-selector": "^4.4|^5.0", @@ -6100,7 +6206,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.2.4" + "source": "https://github.com/symfony/browser-kit/tree/v5.3.4" }, "funding": [ { @@ -6116,24 +6222,25 @@ "type": "tidelift" } ], - "time": "2021-02-22T06:48:33+00:00" + "time": "2021-07-21T12:40:44+00:00" }, { "name": "symfony/css-selector", - "version": "v5.2.4", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f" + "reference": "7fb120adc7f600a59027775b224c13a33530dd90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/f65f217b3314504a1ec99c2d6ef69016bb13490f", - "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90", + "reference": "7fb120adc7f600a59027775b224c13a33530dd90", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -6165,7 +6272,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.2.4" + "source": "https://github.com/symfony/css-selector/tree/v5.3.4" }, "funding": [ { @@ -6181,27 +6288,95 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2021-07-21T12:38:00+00:00" }, { - "name": "symfony/dom-crawler", - "version": "v5.2.4", + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "400e265163f65aceee7e904ef532e15228de674b" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/400e265163f65aceee7e904ef532e15228de674b", - "reference": "400e265163f65aceee7e904ef532e15228de674b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.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-03-23T23:28:01+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7eef3a60ccfdd8eafe07f81652e769ac9c7146c", + "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "masterminds/html5": "<2.6" @@ -6239,7 +6414,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.2.4" + "source": "https://github.com/symfony/dom-crawler/tree/v5.3.7" }, "funding": [ { @@ -6255,24 +6430,25 @@ "type": "tidelift" } ], - "time": "2021-02-15T18:55:04+00:00" + "time": "2021-08-29T19:32:13+00:00" }, { "name": "symfony/finder", - "version": "v5.2.4", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0d639a0943822626290d169965804f79400e6a04" + "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04", - "reference": "0d639a0943822626290d169965804f79400e6a04", + "url": "https://api.github.com/repos/symfony/finder/zipball/a10000ada1e600d109a6c7632e9ac42e8bf2fb93", + "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -6300,7 +6476,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.2.4" + "source": "https://github.com/symfony/finder/tree/v5.3.7" }, "funding": [ { @@ -6316,20 +6492,20 @@ "type": "tidelift" } ], - "time": "2021-02-15T18:55:04+00:00" + "time": "2021-08-04T21:20:46+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -6358,7 +6534,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { @@ -6366,54 +6542,7 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" - }, - { - "name": "victorjonsson/markdowndocs", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator.git", - "reference": "c9fa153b28a79f5da89ec32aa501be92db212aed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/trilbymedia/PHP-Markdown-Documentation-Generator/zipball/c9fa153b28a79f5da89ec32aa501be92db212aed", - "reference": "c9fa153b28a79f5da89ec32aa501be92db212aed", - "shasum": "" - }, - "require": { - "php": ">=5.5.0", - "symfony/console": ">=2.6" - }, - "require-dev": { - "phpunit/phpunit": "3.7.23" - }, - "default-branch": true, - "bin": [ - "bin/phpdoc-md" - ], - "type": "library", - "autoload": { - "psr-0": { - "PHPDocsMD": "src/" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Victor Jonsson", - "email": "kontakt@victorjonsson.se" - } - ], - "description": "Command line tool for generating markdown-formatted class documentation", - "homepage": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator", - "support": { - "source": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator/tree/master" - }, - "time": "2017-09-20T13:29:22+00:00" + "time": "2021-07-28T10:34:58+00:00" }, { "name": "webmozart/assert", @@ -6476,11 +6605,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "gregwar/image": 20, - "gregwar/cache": 20, - "victorjonsson/markdowndocs": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/index.php b/index.php index 9842952d5..091e9a824 100644 --- a/index.php +++ b/index.php @@ -17,8 +17,8 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) { } if (PHP_SAPI === 'cli-server') { - $symfony_server = stripos(getenv('_'), 'symfony') !== false || stripos($_SERVER['SERVER_SOFTWARE'], 'symfony -') !== false; + $symfony_server = stripos(getenv('_'), 'symfony') !== false || stripos($_SERVER['SERVER_SOFTWARE'] ?? '', 'symfony') !== false || stripos($_ENV['SERVER_SOFTWARE'] ?? '', 'symfony') !== false; + if (!isset($_SERVER['PHP_CLI_ROUTER']) && !$symfony_server) { die("PHP webserver requires a router to run Grav, please use:
php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php");
}
diff --git a/system/blueprints/config/scheduler.yaml b/system/blueprints/config/scheduler.yaml
index caa771167..a8dce314b 100644
--- a/system/blueprints/config/scheduler.yaml
+++ b/system/blueprints/config/scheduler.yaml
@@ -47,7 +47,8 @@ form:
label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
placeholder: '-lah'
.at:
- type: cron
+ type: text
+ wrapper_classes: cron-selector
label: PLUGIN_ADMIN.SCHEDULER_RUNAT
help: PLUGIN_ADMIN.SCHEDULER_RUNAT_HELP
placeholder: '* * * * *'
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index 84c1dcc05..1aeaf2bf2 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -646,7 +646,7 @@ form:
type: toggle
label: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT
help: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT_HELP
- highlight: 1
+ highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
@@ -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/blueprints/flex/pages.yaml b/system/blueprints/flex/pages.yaml
index ee2e7e5fa..5c6ed8eb5 100644
--- a/system/blueprints/flex/pages.yaml
+++ b/system/blueprints/flex/pages.yaml
@@ -184,9 +184,9 @@ config:
# Fields to be searched
fields:
- key
+ - slug
- menu
- title
- - name
blueprints:
configure:
diff --git a/system/blueprints/flex/user-groups.yaml b/system/blueprints/flex/user-groups.yaml
index a09a821b5..05ba87b73 100644
--- a/system/blueprints/flex/user-groups.yaml
+++ b/system/blueprints/flex/user-groups.yaml
@@ -18,6 +18,7 @@ config:
configure:
path: '/accounts/configure'
redirects:
+ '/groups': '/accounts/groups'
'/accounts': '/accounts/groups'
# Permissions
diff --git a/system/blueprints/pages/default.yaml b/system/blueprints/pages/default.yaml
index 0e7b8c374..f3ef29f5e 100644
--- a/system/blueprints/pages/default.yaml
+++ b/system/blueprints/pages/default.yaml
@@ -121,7 +121,7 @@ form:
underline: true
folder:
- type: text
+ type: folder-slug
label: PLUGIN_ADMIN.FOLDER_NAME
validate:
rule: slug
diff --git a/system/config/media.yaml b/system/config/media.yaml
index e5439d6d8..b118a210e 100644
--- a/system/config/media.yaml
+++ b/system/config/media.yaml
@@ -28,6 +28,10 @@ types:
type: image
thumb: media/thumb-webp.png
mime: image/webp
+ avif:
+ type: image
+ thumb: media/thumb.png
+ mime: image/avif
gif:
type: animated
thumb: media/thumb-gif.png
@@ -91,7 +95,7 @@ types:
aif:
type: audio
thumb: media/thumb-aif.png
- mime: audio/aif
+ mime: audio/aiff
txt:
type: file
thumb: media/thumb-txt.png
@@ -207,7 +211,7 @@ types:
js:
type: file
thumb: media/thumb-js.png
- mime: application/javascript
+ mime: text/javascript
json:
type: file
thumb: media/thumb-json.png
diff --git a/system/config/mime.yaml b/system/config/mime.yaml
new file mode 100644
index 000000000..3143c6733
--- /dev/null
+++ b/system/config/mime.yaml
@@ -0,0 +1,1986 @@
+types:
+ '123':
+ - application/vnd.lotus-1-2-3
+ wof:
+ - application/font-woff
+ php:
+ - application/php
+ - application/x-httpd-php
+ - application/x-httpd-php-source
+ - application/x-php
+ - text/php
+ - text/x-php
+ otf:
+ - application/x-font-otf
+ - font/otf
+ ttf:
+ - application/x-font-ttf
+ - font/ttf
+ ttc:
+ - application/x-font-ttf
+ - font/collection
+ zip:
+ - application/x-gzip
+ - application/zip
+ - application/x-zip-compressed
+ amr:
+ - audio/amr
+ mp3:
+ - audio/mpeg
+ mpga:
+ - audio/mpeg
+ mp2:
+ - audio/mpeg
+ mp2a:
+ - audio/mpeg
+ m2a:
+ - audio/mpeg
+ m3a:
+ - audio/mpeg
+ jpg:
+ - image/jpeg
+ jpeg:
+ - image/jpeg
+ jpe:
+ - image/jpeg
+ bmp:
+ - image/x-ms-bmp
+ - image/bmp
+ ez:
+ - application/andrew-inset
+ aw:
+ - application/applixware
+ atom:
+ - application/atom+xml
+ atomcat:
+ - application/atomcat+xml
+ atomsvc:
+ - application/atomsvc+xml
+ ccxml:
+ - application/ccxml+xml
+ cdmia:
+ - application/cdmi-capability
+ cdmic:
+ - application/cdmi-container
+ cdmid:
+ - application/cdmi-domain
+ cdmio:
+ - application/cdmi-object
+ cdmiq:
+ - application/cdmi-queue
+ cu:
+ - application/cu-seeme
+ davmount:
+ - application/davmount+xml
+ dbk:
+ - application/docbook+xml
+ dssc:
+ - application/dssc+der
+ xdssc:
+ - application/dssc+xml
+ ecma:
+ - application/ecmascript
+ emma:
+ - application/emma+xml
+ epub:
+ - application/epub+zip
+ exi:
+ - application/exi
+ pfr:
+ - application/font-tdpfr
+ gml:
+ - application/gml+xml
+ gpx:
+ - application/gpx+xml
+ gxf:
+ - application/gxf
+ stk:
+ - application/hyperstudio
+ ink:
+ - application/inkml+xml
+ inkml:
+ - application/inkml+xml
+ ipfix:
+ - application/ipfix
+ jar:
+ - application/java-archive
+ ser:
+ - application/java-serialized-object
+ class:
+ - application/java-vm
+ js:
+ - application/javascript
+ json:
+ - application/json
+ jsonml:
+ - application/jsonml+json
+ lostxml:
+ - application/lost+xml
+ hqx:
+ - application/mac-binhex40
+ cpt:
+ - application/mac-compactpro
+ mads:
+ - application/mads+xml
+ mrc:
+ - application/marc
+ mrcx:
+ - application/marcxml+xml
+ ma:
+ - application/mathematica
+ nb:
+ - application/mathematica
+ mb:
+ - application/mathematica
+ mathml:
+ - application/mathml+xml
+ mbox:
+ - application/mbox
+ mscml:
+ - application/mediaservercontrol+xml
+ metalink:
+ - application/metalink+xml
+ meta4:
+ - application/metalink4+xml
+ mets:
+ - application/mets+xml
+ mods:
+ - application/mods+xml
+ m21:
+ - application/mp21
+ mp21:
+ - application/mp21
+ mp4s:
+ - application/mp4
+ doc:
+ - application/msword
+ dot:
+ - application/msword
+ mxf:
+ - application/mxf
+ bin:
+ - application/octet-stream
+ dms:
+ - application/octet-stream
+ lrf:
+ - application/octet-stream
+ mar:
+ - application/octet-stream
+ so:
+ - application/octet-stream
+ dist:
+ - application/octet-stream
+ distz:
+ - application/octet-stream
+ pkg:
+ - application/octet-stream
+ bpk:
+ - application/octet-stream
+ dump:
+ - application/octet-stream
+ elc:
+ - application/octet-stream
+ deploy:
+ - application/octet-stream
+ oda:
+ - application/oda
+ opf:
+ - application/oebps-package+xml
+ ogx:
+ - application/ogg
+ omdoc:
+ - application/omdoc+xml
+ onetoc:
+ - application/onenote
+ onetoc2:
+ - application/onenote
+ onetmp:
+ - application/onenote
+ onepkg:
+ - application/onenote
+ oxps:
+ - application/oxps
+ xer:
+ - application/patch-ops-error+xml
+ pdf:
+ - application/pdf
+ pgp:
+ - application/pgp-encrypted
+ asc:
+ - application/pgp-signature
+ sig:
+ - application/pgp-signature
+ prf:
+ - application/pics-rules
+ p10:
+ - application/pkcs10
+ p7m:
+ - application/pkcs7-mime
+ p7c:
+ - application/pkcs7-mime
+ p7s:
+ - application/pkcs7-signature
+ p8:
+ - application/pkcs8
+ ac:
+ - application/pkix-attr-cert
+ cer:
+ - application/pkix-cert
+ crl:
+ - application/pkix-crl
+ pkipath:
+ - application/pkix-pkipath
+ pki:
+ - application/pkixcmp
+ pls:
+ - application/pls+xml
+ ai:
+ - application/postscript
+ eps:
+ - application/postscript
+ ps:
+ - application/postscript
+ cww:
+ - application/prs.cww
+ pskcxml:
+ - application/pskc+xml
+ rdf:
+ - application/rdf+xml
+ rif:
+ - application/reginfo+xml
+ rnc:
+ - application/relax-ng-compact-syntax
+ rl:
+ - application/resource-lists+xml
+ rld:
+ - application/resource-lists-diff+xml
+ rs:
+ - application/rls-services+xml
+ gbr:
+ - application/rpki-ghostbusters
+ mft:
+ - application/rpki-manifest
+ roa:
+ - application/rpki-roa
+ rsd:
+ - application/rsd+xml
+ rss:
+ - application/rss+xml
+ rtf:
+ - application/rtf
+ sbml:
+ - application/sbml+xml
+ scq:
+ - application/scvp-cv-request
+ scs:
+ - application/scvp-cv-response
+ spq:
+ - application/scvp-vp-request
+ spp:
+ - application/scvp-vp-response
+ sdp:
+ - application/sdp
+ setpay:
+ - application/set-payment-initiation
+ setreg:
+ - application/set-registration-initiation
+ shf:
+ - application/shf+xml
+ smi:
+ - application/smil+xml
+ smil:
+ - application/smil+xml
+ rq:
+ - application/sparql-query
+ srx:
+ - application/sparql-results+xml
+ gram:
+ - application/srgs
+ grxml:
+ - application/srgs+xml
+ sru:
+ - application/sru+xml
+ ssdl:
+ - application/ssdl+xml
+ ssml:
+ - application/ssml+xml
+ tei:
+ - application/tei+xml
+ teicorpus:
+ - application/tei+xml
+ tfi:
+ - application/thraud+xml
+ tsd:
+ - application/timestamped-data
+ plb:
+ - application/vnd.3gpp.pic-bw-large
+ psb:
+ - application/vnd.3gpp.pic-bw-small
+ pvb:
+ - application/vnd.3gpp.pic-bw-var
+ tcap:
+ - application/vnd.3gpp2.tcap
+ pwn:
+ - application/vnd.3m.post-it-notes
+ aso:
+ - application/vnd.accpac.simply.aso
+ imp:
+ - application/vnd.accpac.simply.imp
+ acu:
+ - application/vnd.acucobol
+ atc:
+ - application/vnd.acucorp
+ acutc:
+ - application/vnd.acucorp
+ air:
+ - application/vnd.adobe.air-application-installer-package+zip
+ fcdt:
+ - application/vnd.adobe.formscentral.fcdt
+ fxp:
+ - application/vnd.adobe.fxp
+ fxpl:
+ - application/vnd.adobe.fxp
+ xdp:
+ - application/vnd.adobe.xdp+xml
+ xfdf:
+ - application/vnd.adobe.xfdf
+ ahead:
+ - application/vnd.ahead.space
+ azf:
+ - application/vnd.airzip.filesecure.azf
+ azs:
+ - application/vnd.airzip.filesecure.azs
+ azw:
+ - application/vnd.amazon.ebook
+ acc:
+ - application/vnd.americandynamics.acc
+ ami:
+ - application/vnd.amiga.ami
+ apk:
+ - application/vnd.android.package-archive
+ cii:
+ - application/vnd.anser-web-certificate-issue-initiation
+ fti:
+ - application/vnd.anser-web-funds-transfer-initiation
+ atx:
+ - application/vnd.antix.game-component
+ mpkg:
+ - application/vnd.apple.installer+xml
+ m3u8:
+ - application/vnd.apple.mpegurl
+ swi:
+ - application/vnd.aristanetworks.swi
+ iota:
+ - application/vnd.astraea-software.iota
+ aep:
+ - application/vnd.audiograph
+ mpm:
+ - application/vnd.blueice.multipass
+ bmi:
+ - application/vnd.bmi
+ rep:
+ - application/vnd.businessobjects
+ cdxml:
+ - application/vnd.chemdraw+xml
+ mmd:
+ - application/vnd.chipnuts.karaoke-mmd
+ cdy:
+ - application/vnd.cinderella
+ cla:
+ - application/vnd.claymore
+ rp9:
+ - application/vnd.cloanto.rp9
+ c4g:
+ - application/vnd.clonk.c4group
+ c4d:
+ - application/vnd.clonk.c4group
+ c4f:
+ - application/vnd.clonk.c4group
+ c4p:
+ - application/vnd.clonk.c4group
+ c4u:
+ - application/vnd.clonk.c4group
+ c11amc:
+ - application/vnd.cluetrust.cartomobile-config
+ c11amz:
+ - application/vnd.cluetrust.cartomobile-config-pkg
+ csp:
+ - application/vnd.commonspace
+ cdbcmsg:
+ - application/vnd.contact.cmsg
+ cmc:
+ - application/vnd.cosmocaller
+ clkx:
+ - application/vnd.crick.clicker
+ clkk:
+ - application/vnd.crick.clicker.keyboard
+ clkp:
+ - application/vnd.crick.clicker.palette
+ clkt:
+ - application/vnd.crick.clicker.template
+ clkw:
+ - application/vnd.crick.clicker.wordbank
+ wbs:
+ - application/vnd.criticaltools.wbs+xml
+ pml:
+ - application/vnd.ctc-posml
+ ppd:
+ - application/vnd.cups-ppd
+ car:
+ - application/vnd.curl.car
+ pcurl:
+ - application/vnd.curl.pcurl
+ dart:
+ - application/vnd.dart
+ rdz:
+ - application/vnd.data-vision.rdz
+ uvf:
+ - application/vnd.dece.data
+ uvvf:
+ - application/vnd.dece.data
+ uvd:
+ - application/vnd.dece.data
+ uvvd:
+ - application/vnd.dece.data
+ uvt:
+ - application/vnd.dece.ttml+xml
+ uvvt:
+ - application/vnd.dece.ttml+xml
+ uvx:
+ - application/vnd.dece.unspecified
+ uvvx:
+ - application/vnd.dece.unspecified
+ uvz:
+ - application/vnd.dece.zip
+ uvvz:
+ - application/vnd.dece.zip
+ fe_launch:
+ - application/vnd.denovo.fcselayout-link
+ dna:
+ - application/vnd.dna
+ mlp:
+ - application/vnd.dolby.mlp
+ dpg:
+ - application/vnd.dpgraph
+ dfac:
+ - application/vnd.dreamfactory
+ kpxx:
+ - application/vnd.ds-keypoint
+ ait:
+ - application/vnd.dvb.ait
+ svc:
+ - application/vnd.dvb.service
+ geo:
+ - application/vnd.dynageo
+ mag:
+ - application/vnd.ecowin.chart
+ nml:
+ - application/vnd.enliven
+ esf:
+ - application/vnd.epson.esf
+ msf:
+ - application/vnd.epson.msf
+ qam:
+ - application/vnd.epson.quickanime
+ slt:
+ - application/vnd.epson.salt
+ ssf:
+ - application/vnd.epson.ssf
+ es3:
+ - application/vnd.eszigno3+xml
+ et3:
+ - application/vnd.eszigno3+xml
+ ez2:
+ - application/vnd.ezpix-album
+ ez3:
+ - application/vnd.ezpix-package
+ fdf:
+ - application/vnd.fdf
+ mseed:
+ - application/vnd.fdsn.mseed
+ seed:
+ - application/vnd.fdsn.seed
+ dataless:
+ - application/vnd.fdsn.seed
+ gph:
+ - application/vnd.flographit
+ ftc:
+ - application/vnd.fluxtime.clip
+ fm:
+ - application/vnd.framemaker
+ frame:
+ - application/vnd.framemaker
+ maker:
+ - application/vnd.framemaker
+ book:
+ - application/vnd.framemaker
+ fnc:
+ - application/vnd.frogans.fnc
+ ltf:
+ - application/vnd.frogans.ltf
+ fsc:
+ - application/vnd.fsc.weblaunch
+ oas:
+ - application/vnd.fujitsu.oasys
+ oa2:
+ - application/vnd.fujitsu.oasys2
+ oa3:
+ - application/vnd.fujitsu.oasys3
+ fg5:
+ - application/vnd.fujitsu.oasysgp
+ bh2:
+ - application/vnd.fujitsu.oasysprs
+ ddd:
+ - application/vnd.fujixerox.ddd
+ xdw:
+ - application/vnd.fujixerox.docuworks
+ xbd:
+ - application/vnd.fujixerox.docuworks.binder
+ fzs:
+ - application/vnd.fuzzysheet
+ txd:
+ - application/vnd.genomatix.tuxedo
+ ggb:
+ - application/vnd.geogebra.file
+ ggt:
+ - application/vnd.geogebra.tool
+ gex:
+ - application/vnd.geometry-explorer
+ gre:
+ - application/vnd.geometry-explorer
+ gxt:
+ - application/vnd.geonext
+ g2w:
+ - application/vnd.geoplan
+ g3w:
+ - application/vnd.geospace
+ gmx:
+ - application/vnd.gmx
+ kml:
+ - application/vnd.google-earth.kml+xml
+ kmz:
+ - application/vnd.google-earth.kmz
+ gqf:
+ - application/vnd.grafeq
+ gqs:
+ - application/vnd.grafeq
+ gac:
+ - application/vnd.groove-account
+ ghf:
+ - application/vnd.groove-help
+ gim:
+ - application/vnd.groove-identity-message
+ grv:
+ - application/vnd.groove-injector
+ gtm:
+ - application/vnd.groove-tool-message
+ tpl:
+ - application/vnd.groove-tool-template
+ vcg:
+ - application/vnd.groove-vcard
+ hal:
+ - application/vnd.hal+xml
+ zmm:
+ - application/vnd.handheld-entertainment+xml
+ hbci:
+ - application/vnd.hbci
+ les:
+ - application/vnd.hhe.lesson-player
+ hpgl:
+ - application/vnd.hp-hpgl
+ hpid:
+ - application/vnd.hp-hpid
+ hps:
+ - application/vnd.hp-hps
+ jlt:
+ - application/vnd.hp-jlyt
+ pcl:
+ - application/vnd.hp-pcl
+ pclxl:
+ - application/vnd.hp-pclxl
+ sfd-hdstx:
+ - application/vnd.hydrostatix.sof-data
+ mpy:
+ - application/vnd.ibm.minipay
+ afp:
+ - application/vnd.ibm.modcap
+ listafp:
+ - application/vnd.ibm.modcap
+ list3820:
+ - application/vnd.ibm.modcap
+ irm:
+ - application/vnd.ibm.rights-management
+ sc:
+ - application/vnd.ibm.secure-container
+ icc:
+ - application/vnd.iccprofile
+ icm:
+ - application/vnd.iccprofile
+ igl:
+ - application/vnd.igloader
+ ivp:
+ - application/vnd.immervision-ivp
+ ivu:
+ - application/vnd.immervision-ivu
+ igm:
+ - application/vnd.insors.igm
+ xpw:
+ - application/vnd.intercon.formnet
+ xpx:
+ - application/vnd.intercon.formnet
+ i2g:
+ - application/vnd.intergeo
+ qbo:
+ - application/vnd.intu.qbo
+ qfx:
+ - application/vnd.intu.qfx
+ rcprofile:
+ - application/vnd.ipunplugged.rcprofile
+ irp:
+ - application/vnd.irepository.package+xml
+ xpr:
+ - application/vnd.is-xpr
+ fcs:
+ - application/vnd.isac.fcs
+ jam:
+ - application/vnd.jam
+ rms:
+ - application/vnd.jcp.javame.midlet-rms
+ jisp:
+ - application/vnd.jisp
+ joda:
+ - application/vnd.joost.joda-archive
+ ktz:
+ - application/vnd.kahootz
+ ktr:
+ - application/vnd.kahootz
+ karbon:
+ - application/vnd.kde.karbon
+ chrt:
+ - application/vnd.kde.kchart
+ kfo:
+ - application/vnd.kde.kformula
+ flw:
+ - application/vnd.kde.kivio
+ kon:
+ - application/vnd.kde.kontour
+ kpr:
+ - application/vnd.kde.kpresenter
+ kpt:
+ - application/vnd.kde.kpresenter
+ ksp:
+ - application/vnd.kde.kspread
+ kwd:
+ - application/vnd.kde.kword
+ kwt:
+ - application/vnd.kde.kword
+ htke:
+ - application/vnd.kenameaapp
+ kia:
+ - application/vnd.kidspiration
+ kne:
+ - application/vnd.kinar
+ knp:
+ - application/vnd.kinar
+ skp:
+ - application/vnd.koan
+ skd:
+ - application/vnd.koan
+ skt:
+ - application/vnd.koan
+ skm:
+ - application/vnd.koan
+ sse:
+ - application/vnd.kodak-descriptor
+ lasxml:
+ - application/vnd.las.las+xml
+ lbd:
+ - application/vnd.llamagraphics.life-balance.desktop
+ lbe:
+ - application/vnd.llamagraphics.life-balance.exchange+xml
+ apr:
+ - application/vnd.lotus-approach
+ pre:
+ - application/vnd.lotus-freelance
+ nsf:
+ - application/vnd.lotus-notes
+ org:
+ - application/vnd.lotus-organizer
+ scm:
+ - application/vnd.lotus-screencam
+ lwp:
+ - application/vnd.lotus-wordpro
+ portpkg:
+ - application/vnd.macports.portpkg
+ mcd:
+ - application/vnd.mcd
+ mc1:
+ - application/vnd.medcalcdata
+ cdkey:
+ - application/vnd.mediastation.cdkey
+ mwf:
+ - application/vnd.mfer
+ mfm:
+ - application/vnd.mfmp
+ flo:
+ - application/vnd.micrografx.flo
+ igx:
+ - application/vnd.micrografx.igx
+ mif:
+ - application/vnd.mif
+ daf:
+ - application/vnd.mobius.daf
+ dis:
+ - application/vnd.mobius.dis
+ mbk:
+ - application/vnd.mobius.mbk
+ mqy:
+ - application/vnd.mobius.mqy
+ msl:
+ - application/vnd.mobius.msl
+ plc:
+ - application/vnd.mobius.plc
+ txf:
+ - application/vnd.mobius.txf
+ mpn:
+ - application/vnd.mophun.application
+ mpc:
+ - application/vnd.mophun.certificate
+ xul:
+ - application/vnd.mozilla.xul+xml
+ cil:
+ - application/vnd.ms-artgalry
+ cab:
+ - application/vnd.ms-cab-compressed
+ xls:
+ - application/vnd.ms-excel
+ xlm:
+ - application/vnd.ms-excel
+ xla:
+ - application/vnd.ms-excel
+ xlc:
+ - application/vnd.ms-excel
+ xlt:
+ - application/vnd.ms-excel
+ xlw:
+ - application/vnd.ms-excel
+ xlam:
+ - application/vnd.ms-excel.addin.macroenabled.12
+ xlsb:
+ - application/vnd.ms-excel.sheet.binary.macroenabled.12
+ xlsm:
+ - application/vnd.ms-excel.sheet.macroenabled.12
+ xltm:
+ - application/vnd.ms-excel.template.macroenabled.12
+ eot:
+ - application/vnd.ms-fontobject
+ chm:
+ - application/vnd.ms-htmlhelp
+ ims:
+ - application/vnd.ms-ims
+ lrm:
+ - application/vnd.ms-lrm
+ thmx:
+ - application/vnd.ms-officetheme
+ cat:
+ - application/vnd.ms-pki.seccat
+ stl:
+ - application/vnd.ms-pki.stl
+ ppt:
+ - application/vnd.ms-powerpoint
+ pps:
+ - application/vnd.ms-powerpoint
+ pot:
+ - application/vnd.ms-powerpoint
+ ppam:
+ - application/vnd.ms-powerpoint.addin.macroenabled.12
+ pptm:
+ - application/vnd.ms-powerpoint.presentation.macroenabled.12
+ sldm:
+ - application/vnd.ms-powerpoint.slide.macroenabled.12
+ ppsm:
+ - application/vnd.ms-powerpoint.slideshow.macroenabled.12
+ potm:
+ - application/vnd.ms-powerpoint.template.macroenabled.12
+ mpp:
+ - application/vnd.ms-project
+ mpt:
+ - application/vnd.ms-project
+ docm:
+ - application/vnd.ms-word.document.macroenabled.12
+ dotm:
+ - application/vnd.ms-word.template.macroenabled.12
+ wps:
+ - application/vnd.ms-works
+ wks:
+ - application/vnd.ms-works
+ wcm:
+ - application/vnd.ms-works
+ wdb:
+ - application/vnd.ms-works
+ wpl:
+ - application/vnd.ms-wpl
+ xps:
+ - application/vnd.ms-xpsdocument
+ mseq:
+ - application/vnd.mseq
+ mus:
+ - application/vnd.musician
+ msty:
+ - application/vnd.muvee.style
+ taglet:
+ - application/vnd.mynfc
+ nlu:
+ - application/vnd.neurolanguage.nlu
+ ntf:
+ - application/vnd.nitf
+ nitf:
+ - application/vnd.nitf
+ nnd:
+ - application/vnd.noblenet-directory
+ nns:
+ - application/vnd.noblenet-sealer
+ nnw:
+ - application/vnd.noblenet-web
+ ngdat:
+ - application/vnd.nokia.n-gage.data
+ n-gage:
+ - application/vnd.nokia.n-gage.symbian.install
+ rpst:
+ - application/vnd.nokia.radio-preset
+ rpss:
+ - application/vnd.nokia.radio-presets
+ edm:
+ - application/vnd.novadigm.edm
+ edx:
+ - application/vnd.novadigm.edx
+ ext:
+ - application/vnd.novadigm.ext
+ odc:
+ - application/vnd.oasis.opendocument.chart
+ otc:
+ - application/vnd.oasis.opendocument.chart-template
+ odb:
+ - application/vnd.oasis.opendocument.database
+ odf:
+ - application/vnd.oasis.opendocument.formula
+ odft:
+ - application/vnd.oasis.opendocument.formula-template
+ odg:
+ - application/vnd.oasis.opendocument.graphics
+ otg:
+ - application/vnd.oasis.opendocument.graphics-template
+ odi:
+ - application/vnd.oasis.opendocument.image
+ oti:
+ - application/vnd.oasis.opendocument.image-template
+ odp:
+ - application/vnd.oasis.opendocument.presentation
+ otp:
+ - application/vnd.oasis.opendocument.presentation-template
+ ods:
+ - application/vnd.oasis.opendocument.spreadsheet
+ ots:
+ - application/vnd.oasis.opendocument.spreadsheet-template
+ odt:
+ - application/vnd.oasis.opendocument.text
+ odm:
+ - application/vnd.oasis.opendocument.text-master
+ ott:
+ - application/vnd.oasis.opendocument.text-template
+ oth:
+ - application/vnd.oasis.opendocument.text-web
+ xo:
+ - application/vnd.olpc-sugar
+ dd2:
+ - application/vnd.oma.dd2+xml
+ oxt:
+ - application/vnd.openofficeorg.extension
+ pptx:
+ - application/vnd.openxmlformats-officedocument.presentationml.presentation
+ sldx:
+ - application/vnd.openxmlformats-officedocument.presentationml.slide
+ ppsx:
+ - application/vnd.openxmlformats-officedocument.presentationml.slideshow
+ potx:
+ - application/vnd.openxmlformats-officedocument.presentationml.template
+ xlsx:
+ - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+ xltx:
+ - application/vnd.openxmlformats-officedocument.spreadsheetml.template
+ docx:
+ - application/vnd.openxmlformats-officedocument.wordprocessingml.document
+ dotx:
+ - application/vnd.openxmlformats-officedocument.wordprocessingml.template
+ mgp:
+ - application/vnd.osgeo.mapguide.package
+ dp:
+ - application/vnd.osgi.dp
+ esa:
+ - application/vnd.osgi.subsystem
+ pdb:
+ - application/vnd.palm
+ pqa:
+ - application/vnd.palm
+ oprc:
+ - application/vnd.palm
+ paw:
+ - application/vnd.pawaafile
+ str:
+ - application/vnd.pg.format
+ ei6:
+ - application/vnd.pg.osasli
+ efif:
+ - application/vnd.picsel
+ wg:
+ - application/vnd.pmi.widget
+ plf:
+ - application/vnd.pocketlearn
+ pbd:
+ - application/vnd.powerbuilder6
+ box:
+ - application/vnd.previewsystems.box
+ mgz:
+ - application/vnd.proteus.magazine
+ qps:
+ - application/vnd.publishare-delta-tree
+ ptid:
+ - application/vnd.pvi.ptid1
+ qxd:
+ - application/vnd.quark.quarkxpress
+ qxt:
+ - application/vnd.quark.quarkxpress
+ qwd:
+ - application/vnd.quark.quarkxpress
+ qwt:
+ - application/vnd.quark.quarkxpress
+ qxl:
+ - application/vnd.quark.quarkxpress
+ qxb:
+ - application/vnd.quark.quarkxpress
+ bed:
+ - application/vnd.realvnc.bed
+ mxl:
+ - application/vnd.recordare.musicxml
+ musicxml:
+ - application/vnd.recordare.musicxml+xml
+ cryptonote:
+ - application/vnd.rig.cryptonote
+ cod:
+ - application/vnd.rim.cod
+ rm:
+ - application/vnd.rn-realmedia
+ rmvb:
+ - application/vnd.rn-realmedia-vbr
+ link66:
+ - application/vnd.route66.link66+xml
+ st:
+ - application/vnd.sailingtracker.track
+ see:
+ - application/vnd.seemail
+ sema:
+ - application/vnd.sema
+ semd:
+ - application/vnd.semd
+ semf:
+ - application/vnd.semf
+ ifm:
+ - application/vnd.shana.informed.formdata
+ itp:
+ - application/vnd.shana.informed.formtemplate
+ iif:
+ - application/vnd.shana.informed.interchange
+ ipk:
+ - application/vnd.shana.informed.package
+ twd:
+ - application/vnd.simtech-mindmapper
+ twds:
+ - application/vnd.simtech-mindmapper
+ mmf:
+ - application/vnd.smaf
+ teacher:
+ - application/vnd.smart.teacher
+ sdkm:
+ - application/vnd.solent.sdkm+xml
+ sdkd:
+ - application/vnd.solent.sdkm+xml
+ dxp:
+ - application/vnd.spotfire.dxp
+ sfs:
+ - application/vnd.spotfire.sfs
+ sdc:
+ - application/vnd.stardivision.calc
+ sda:
+ - application/vnd.stardivision.draw
+ sdd:
+ - application/vnd.stardivision.impress
+ smf:
+ - application/vnd.stardivision.math
+ sdw:
+ - application/vnd.stardivision.writer
+ vor:
+ - application/vnd.stardivision.writer
+ sgl:
+ - application/vnd.stardivision.writer-global
+ smzip:
+ - application/vnd.stepmania.package
+ sm:
+ - application/vnd.stepmania.stepchart
+ sxc:
+ - application/vnd.sun.xml.calc
+ stc:
+ - application/vnd.sun.xml.calc.template
+ sxd:
+ - application/vnd.sun.xml.draw
+ std:
+ - application/vnd.sun.xml.draw.template
+ sxi:
+ - application/vnd.sun.xml.impress
+ sti:
+ - application/vnd.sun.xml.impress.template
+ sxm:
+ - application/vnd.sun.xml.math
+ sxw:
+ - application/vnd.sun.xml.writer
+ sxg:
+ - application/vnd.sun.xml.writer.global
+ stw:
+ - application/vnd.sun.xml.writer.template
+ sus:
+ - application/vnd.sus-calendar
+ susp:
+ - application/vnd.sus-calendar
+ svd:
+ - application/vnd.svd
+ sis:
+ - application/vnd.symbian.install
+ sisx:
+ - application/vnd.symbian.install
+ xsm:
+ - application/vnd.syncml+xml
+ bdm:
+ - application/vnd.syncml.dm+wbxml
+ xdm:
+ - application/vnd.syncml.dm+xml
+ tao:
+ - application/vnd.tao.intent-module-archive
+ pcap:
+ - application/vnd.tcpdump.pcap
+ cap:
+ - application/vnd.tcpdump.pcap
+ dmp:
+ - application/vnd.tcpdump.pcap
+ tmo:
+ - application/vnd.tmobile-livetv
+ tpt:
+ - application/vnd.trid.tpt
+ mxs:
+ - application/vnd.triscape.mxs
+ tra:
+ - application/vnd.trueapp
+ ufd:
+ - application/vnd.ufdl
+ ufdl:
+ - application/vnd.ufdl
+ utz:
+ - application/vnd.uiq.theme
+ umj:
+ - application/vnd.umajin
+ unityweb:
+ - application/vnd.unity
+ uoml:
+ - application/vnd.uoml+xml
+ vcx:
+ - application/vnd.vcx
+ vsd:
+ - application/vnd.visio
+ vst:
+ - application/vnd.visio
+ vss:
+ - application/vnd.visio
+ vsw:
+ - application/vnd.visio
+ vis:
+ - application/vnd.visionary
+ vsf:
+ - application/vnd.vsf
+ wbxml:
+ - application/vnd.wap.wbxml
+ wmlc:
+ - application/vnd.wap.wmlc
+ wmlsc:
+ - application/vnd.wap.wmlscriptc
+ wtb:
+ - application/vnd.webturbo
+ nbp:
+ - application/vnd.wolfram.player
+ wpd:
+ - application/vnd.wordperfect
+ wqd:
+ - application/vnd.wqd
+ stf:
+ - application/vnd.wt.stf
+ xar:
+ - application/vnd.xara
+ xfdl:
+ - application/vnd.xfdl
+ hvd:
+ - application/vnd.yamaha.hv-dic
+ hvs:
+ - application/vnd.yamaha.hv-script
+ hvp:
+ - application/vnd.yamaha.hv-voice
+ osf:
+ - application/vnd.yamaha.openscoreformat
+ osfpvg:
+ - application/vnd.yamaha.openscoreformat.osfpvg+xml
+ saf:
+ - application/vnd.yamaha.smaf-audio
+ spf:
+ - application/vnd.yamaha.smaf-phrase
+ cmp:
+ - application/vnd.yellowriver-custom-menu
+ zir:
+ - application/vnd.zul
+ zirz:
+ - application/vnd.zul
+ zaz:
+ - application/vnd.zzazz.deck+xml
+ vxml:
+ - application/voicexml+xml
+ wgt:
+ - application/widget
+ hlp:
+ - application/winhlp
+ wsdl:
+ - application/wsdl+xml
+ wspolicy:
+ - application/wspolicy+xml
+ 7z:
+ - application/x-7z-compressed
+ abw:
+ - application/x-abiword
+ ace:
+ - application/x-ace-compressed
+ dmg:
+ - application/x-apple-diskimage
+ aab:
+ - application/x-authorware-bin
+ x32:
+ - application/x-authorware-bin
+ u32:
+ - application/x-authorware-bin
+ vox:
+ - application/x-authorware-bin
+ aam:
+ - application/x-authorware-map
+ aas:
+ - application/x-authorware-seg
+ bcpio:
+ - application/x-bcpio
+ torrent:
+ - application/x-bittorrent
+ blb:
+ - application/x-blorb
+ blorb:
+ - application/x-blorb
+ bz:
+ - application/x-bzip
+ bz2:
+ - application/x-bzip2
+ boz:
+ - application/x-bzip2
+ cbr:
+ - application/x-cbr
+ cba:
+ - application/x-cbr
+ cbt:
+ - application/x-cbr
+ cbz:
+ - application/x-cbr
+ cb7:
+ - application/x-cbr
+ vcd:
+ - application/x-cdlink
+ cfs:
+ - application/x-cfs-compressed
+ chat:
+ - application/x-chat
+ pgn:
+ - application/x-chess-pgn
+ nsc:
+ - application/x-conference
+ cpio:
+ - application/x-cpio
+ csh:
+ - application/x-csh
+ deb:
+ - application/x-debian-package
+ udeb:
+ - application/x-debian-package
+ dgc:
+ - application/x-dgc-compressed
+ dir:
+ - application/x-director
+ dcr:
+ - application/x-director
+ dxr:
+ - application/x-director
+ cst:
+ - application/x-director
+ cct:
+ - application/x-director
+ cxt:
+ - application/x-director
+ w3d:
+ - application/x-director
+ fgd:
+ - application/x-director
+ swa:
+ - application/x-director
+ wad:
+ - application/x-doom
+ ncx:
+ - application/x-dtbncx+xml
+ dtb:
+ - application/x-dtbook+xml
+ res:
+ - application/x-dtbresource+xml
+ dvi:
+ - application/x-dvi
+ evy:
+ - application/x-envoy
+ eva:
+ - application/x-eva
+ bdf:
+ - application/x-font-bdf
+ gsf:
+ - application/x-font-ghostscript
+ psf:
+ - application/x-font-linux-psf
+ pcf:
+ - application/x-font-pcf
+ snf:
+ - application/x-font-snf
+ pfa:
+ - application/x-font-type1
+ pfb:
+ - application/x-font-type1
+ pfm:
+ - application/x-font-type1
+ afm:
+ - application/x-font-type1
+ arc:
+ - application/x-freearc
+ spl:
+ - application/x-futuresplash
+ gca:
+ - application/x-gca-compressed
+ ulx:
+ - application/x-glulx
+ gnumeric:
+ - application/x-gnumeric
+ gramps:
+ - application/x-gramps-xml
+ gtar:
+ - application/x-gtar
+ hdf:
+ - application/x-hdf
+ install:
+ - application/x-install-instructions
+ iso:
+ - application/x-iso9660-image
+ jnlp:
+ - application/x-java-jnlp-file
+ latex:
+ - application/x-latex
+ lzh:
+ - application/x-lzh-compressed
+ lha:
+ - application/x-lzh-compressed
+ mie:
+ - application/x-mie
+ prc:
+ - application/x-mobipocket-ebook
+ mobi:
+ - application/x-mobipocket-ebook
+ application:
+ - application/x-ms-application
+ lnk:
+ - application/x-ms-shortcut
+ wmd:
+ - application/x-ms-wmd
+ wmz:
+ - application/x-ms-wmz
+ - application/x-msmetafile
+ xbap:
+ - application/x-ms-xbap
+ mdb:
+ - application/x-msaccess
+ obd:
+ - application/x-msbinder
+ crd:
+ - application/x-mscardfile
+ clp:
+ - application/x-msclip
+ exe:
+ - application/x-msdownload
+ dll:
+ - application/x-msdownload
+ com:
+ - application/x-msdownload
+ bat:
+ - application/x-msdownload
+ msi:
+ - application/x-msdownload
+ mvb:
+ - application/x-msmediaview
+ m13:
+ - application/x-msmediaview
+ m14:
+ - application/x-msmediaview
+ wmf:
+ - application/x-msmetafile
+ emf:
+ - application/x-msmetafile
+ emz:
+ - application/x-msmetafile
+ mny:
+ - application/x-msmoney
+ pub:
+ - application/x-mspublisher
+ scd:
+ - application/x-msschedule
+ trm:
+ - application/x-msterminal
+ wri:
+ - application/x-mswrite
+ nc:
+ - application/x-netcdf
+ cdf:
+ - application/x-netcdf
+ nzb:
+ - application/x-nzb
+ p12:
+ - application/x-pkcs12
+ pfx:
+ - application/x-pkcs12
+ p7b:
+ - application/x-pkcs7-certificates
+ spc:
+ - application/x-pkcs7-certificates
+ p7r:
+ - application/x-pkcs7-certreqresp
+ rar:
+ - application/x-rar-compressed
+ ris:
+ - application/x-research-info-systems
+ sh:
+ - application/x-sh
+ shar:
+ - application/x-shar
+ swf:
+ - application/x-shockwave-flash
+ xap:
+ - application/x-silverlight-app
+ sql:
+ - application/x-sql
+ sit:
+ - application/x-stuffit
+ sitx:
+ - application/x-stuffitx
+ srt:
+ - application/x-subrip
+ sv4cpio:
+ - application/x-sv4cpio
+ sv4crc:
+ - application/x-sv4crc
+ t3:
+ - application/x-t3vm-image
+ gam:
+ - application/x-tads
+ tar:
+ - application/x-tar
+ tcl:
+ - application/x-tcl
+ tex:
+ - application/x-tex
+ tfm:
+ - application/x-tex-tfm
+ texinfo:
+ - application/x-texinfo
+ texi:
+ - application/x-texinfo
+ obj:
+ - application/x-tgif
+ ustar:
+ - application/x-ustar
+ src:
+ - application/x-wais-source
+ der:
+ - application/x-x509-ca-cert
+ crt:
+ - application/x-x509-ca-cert
+ fig:
+ - application/x-xfig
+ xlf:
+ - application/x-xliff+xml
+ xpi:
+ - application/x-xpinstall
+ xz:
+ - application/x-xz
+ z1:
+ - application/x-zmachine
+ z2:
+ - application/x-zmachine
+ z3:
+ - application/x-zmachine
+ z4:
+ - application/x-zmachine
+ z5:
+ - application/x-zmachine
+ z6:
+ - application/x-zmachine
+ z7:
+ - application/x-zmachine
+ z8:
+ - application/x-zmachine
+ xaml:
+ - application/xaml+xml
+ xdf:
+ - application/xcap-diff+xml
+ xenc:
+ - application/xenc+xml
+ xhtml:
+ - application/xhtml+xml
+ xht:
+ - application/xhtml+xml
+ xml:
+ - application/xml
+ xsl:
+ - application/xml
+ dtd:
+ - application/xml-dtd
+ xop:
+ - application/xop+xml
+ xpl:
+ - application/xproc+xml
+ xslt:
+ - application/xslt+xml
+ xspf:
+ - application/xspf+xml
+ mxml:
+ - application/xv+xml
+ xhvml:
+ - application/xv+xml
+ xvml:
+ - application/xv+xml
+ xvm:
+ - application/xv+xml
+ yang:
+ - application/yang
+ yin:
+ - application/yin+xml
+ adp:
+ - audio/adpcm
+ au:
+ - audio/basic
+ snd:
+ - audio/basic
+ mid:
+ - audio/midi
+ midi:
+ - audio/midi
+ kar:
+ - audio/midi
+ rmi:
+ - audio/midi
+ m4a:
+ - audio/mp4
+ mp4a:
+ - audio/mp4
+ oga:
+ - audio/ogg
+ ogg:
+ - audio/ogg
+ spx:
+ - audio/ogg
+ s3m:
+ - audio/s3m
+ sil:
+ - audio/silk
+ uva:
+ - audio/vnd.dece.audio
+ uvva:
+ - audio/vnd.dece.audio
+ eol:
+ - audio/vnd.digital-winds
+ dra:
+ - audio/vnd.dra
+ dts:
+ - audio/vnd.dts
+ dtshd:
+ - audio/vnd.dts.hd
+ lvp:
+ - audio/vnd.lucent.voice
+ pya:
+ - audio/vnd.ms-playready.media.pya
+ ecelp4800:
+ - audio/vnd.nuera.ecelp4800
+ ecelp7470:
+ - audio/vnd.nuera.ecelp7470
+ ecelp9600:
+ - audio/vnd.nuera.ecelp9600
+ rip:
+ - audio/vnd.rip
+ weba:
+ - audio/webm
+ aac:
+ - audio/x-aac
+ aif:
+ - audio/x-aiff
+ aiff:
+ - audio/x-aiff
+ aifc:
+ - audio/x-aiff
+ caf:
+ - audio/x-caf
+ flac:
+ - audio/x-flac
+ mka:
+ - audio/x-matroska
+ m3u:
+ - audio/x-mpegurl
+ wax:
+ - audio/x-ms-wax
+ wma:
+ - audio/x-ms-wma
+ ram:
+ - audio/x-pn-realaudio
+ ra:
+ - audio/x-pn-realaudio
+ rmp:
+ - audio/x-pn-realaudio-plugin
+ wav:
+ - audio/x-wav
+ xm:
+ - audio/xm
+ cdx:
+ - chemical/x-cdx
+ cif:
+ - chemical/x-cif
+ cmdf:
+ - chemical/x-cmdf
+ cml:
+ - chemical/x-cml
+ csml:
+ - chemical/x-csml
+ xyz:
+ - chemical/x-xyz
+ woff:
+ - font/woff
+ woff2:
+ - font/woff2
+ cgm:
+ - image/cgm
+ g3:
+ - image/g3fax
+ gif:
+ - image/gif
+ ief:
+ - image/ief
+ ktx:
+ - image/ktx
+ png:
+ - image/png
+ btif:
+ - image/prs.btif
+ sgi:
+ - image/sgi
+ svg:
+ - image/svg+xml
+ svgz:
+ - image/svg+xml
+ tiff:
+ - image/tiff
+ tif:
+ - image/tiff
+ psd:
+ - image/vnd.adobe.photoshop
+ uvi:
+ - image/vnd.dece.graphic
+ uvvi:
+ - image/vnd.dece.graphic
+ uvg:
+ - image/vnd.dece.graphic
+ uvvg:
+ - image/vnd.dece.graphic
+ djvu:
+ - image/vnd.djvu
+ djv:
+ - image/vnd.djvu
+ sub:
+ - image/vnd.dvb.subtitle
+ - text/vnd.dvb.subtitle
+ dwg:
+ - image/vnd.dwg
+ dxf:
+ - image/vnd.dxf
+ fbs:
+ - image/vnd.fastbidsheet
+ fpx:
+ - image/vnd.fpx
+ fst:
+ - image/vnd.fst
+ mmr:
+ - image/vnd.fujixerox.edmics-mmr
+ rlc:
+ - image/vnd.fujixerox.edmics-rlc
+ mdi:
+ - image/vnd.ms-modi
+ wdp:
+ - image/vnd.ms-photo
+ npx:
+ - image/vnd.net-fpx
+ wbmp:
+ - image/vnd.wap.wbmp
+ xif:
+ - image/vnd.xiff
+ webp:
+ - image/webp
+ 3ds:
+ - image/x-3ds
+ ras:
+ - image/x-cmu-raster
+ cmx:
+ - image/x-cmx
+ fh:
+ - image/x-freehand
+ fhc:
+ - image/x-freehand
+ fh4:
+ - image/x-freehand
+ fh5:
+ - image/x-freehand
+ fh7:
+ - image/x-freehand
+ ico:
+ - image/x-icon
+ sid:
+ - image/x-mrsid-image
+ pcx:
+ - image/x-pcx
+ pic:
+ - image/x-pict
+ pct:
+ - image/x-pict
+ pnm:
+ - image/x-portable-anymap
+ pbm:
+ - image/x-portable-bitmap
+ pgm:
+ - image/x-portable-graymap
+ ppm:
+ - image/x-portable-pixmap
+ rgb:
+ - image/x-rgb
+ tga:
+ - image/x-tga
+ xbm:
+ - image/x-xbitmap
+ xpm:
+ - image/x-xpixmap
+ xwd:
+ - image/x-xwindowdump
+ eml:
+ - message/rfc822
+ mime:
+ - message/rfc822
+ igs:
+ - model/iges
+ iges:
+ - model/iges
+ msh:
+ - model/mesh
+ mesh:
+ - model/mesh
+ silo:
+ - model/mesh
+ dae:
+ - model/vnd.collada+xml
+ dwf:
+ - model/vnd.dwf
+ gdl:
+ - model/vnd.gdl
+ gtw:
+ - model/vnd.gtw
+ mts:
+ - model/vnd.mts
+ vtu:
+ - model/vnd.vtu
+ wrl:
+ - model/vrml
+ vrml:
+ - model/vrml
+ x3db:
+ - model/x3d+binary
+ x3dbz:
+ - model/x3d+binary
+ x3dv:
+ - model/x3d+vrml
+ x3dvz:
+ - model/x3d+vrml
+ x3d:
+ - model/x3d+xml
+ x3dz:
+ - model/x3d+xml
+ appcache:
+ - text/cache-manifest
+ ics:
+ - text/calendar
+ ifb:
+ - text/calendar
+ css:
+ - text/css
+ csv:
+ - text/csv
+ html:
+ - text/html
+ htm:
+ - text/html
+ n3:
+ - text/n3
+ txt:
+ - text/plain
+ text:
+ - text/plain
+ conf:
+ - text/plain
+ def:
+ - text/plain
+ list:
+ - text/plain
+ log:
+ - text/plain
+ in:
+ - text/plain
+ dsc:
+ - text/prs.lines.tag
+ rtx:
+ - text/richtext
+ sgml:
+ - text/sgml
+ sgm:
+ - text/sgml
+ tsv:
+ - text/tab-separated-values
+ t:
+ - text/troff
+ tr:
+ - text/troff
+ roff:
+ - text/troff
+ man:
+ - text/troff
+ me:
+ - text/troff
+ ms:
+ - text/troff
+ ttl:
+ - text/turtle
+ uri:
+ - text/uri-list
+ uris:
+ - text/uri-list
+ urls:
+ - text/uri-list
+ vcard:
+ - text/vcard
+ curl:
+ - text/vnd.curl
+ dcurl:
+ - text/vnd.curl.dcurl
+ mcurl:
+ - text/vnd.curl.mcurl
+ scurl:
+ - text/vnd.curl.scurl
+ fly:
+ - text/vnd.fly
+ flx:
+ - text/vnd.fmi.flexstor
+ gv:
+ - text/vnd.graphviz
+ 3dml:
+ - text/vnd.in3d.3dml
+ spot:
+ - text/vnd.in3d.spot
+ jad:
+ - text/vnd.sun.j2me.app-descriptor
+ wml:
+ - text/vnd.wap.wml
+ wmls:
+ - text/vnd.wap.wmlscript
+ s:
+ - text/x-asm
+ asm:
+ - text/x-asm
+ c:
+ - text/x-c
+ cc:
+ - text/x-c
+ cxx:
+ - text/x-c
+ cpp:
+ - text/x-c
+ h:
+ - text/x-c
+ hh:
+ - text/x-c
+ dic:
+ - text/x-c
+ f:
+ - text/x-fortran
+ for:
+ - text/x-fortran
+ f77:
+ - text/x-fortran
+ f90:
+ - text/x-fortran
+ java:
+ - text/x-java-source
+ nfo:
+ - text/x-nfo
+ opml:
+ - text/x-opml
+ p:
+ - text/x-pascal
+ pas:
+ - text/x-pascal
+ etx:
+ - text/x-setext
+ sfv:
+ - text/x-sfv
+ uu:
+ - text/x-uuencode
+ vcs:
+ - text/x-vcalendar
+ vcf:
+ - text/x-vcard
+ 3gp:
+ - video/3gpp
+ 3g2:
+ - video/3gpp2
+ h261:
+ - video/h261
+ h263:
+ - video/h263
+ h264:
+ - video/h264
+ jpgv:
+ - video/jpeg
+ jpm:
+ - video/jpm
+ jpgm:
+ - video/jpm
+ mj2:
+ - video/mj2
+ mjp2:
+ - video/mj2
+ mp4:
+ - video/mp4
+ mp4v:
+ - video/mp4
+ mpg4:
+ - video/mp4
+ mpeg:
+ - video/mpeg
+ mpg:
+ - video/mpeg
+ mpe:
+ - video/mpeg
+ m1v:
+ - video/mpeg
+ m2v:
+ - video/mpeg
+ ogv:
+ - video/ogg
+ qt:
+ - video/quicktime
+ mov:
+ - video/quicktime
+ uvh:
+ - video/vnd.dece.hd
+ uvvh:
+ - video/vnd.dece.hd
+ uvm:
+ - video/vnd.dece.mobile
+ uvvm:
+ - video/vnd.dece.mobile
+ uvp:
+ - video/vnd.dece.pd
+ uvvp:
+ - video/vnd.dece.pd
+ uvs:
+ - video/vnd.dece.sd
+ uvvs:
+ - video/vnd.dece.sd
+ uvv:
+ - video/vnd.dece.video
+ uvvv:
+ - video/vnd.dece.video
+ dvb:
+ - video/vnd.dvb.file
+ fvt:
+ - video/vnd.fvt
+ mxu:
+ - video/vnd.mpegurl
+ m4u:
+ - video/vnd.mpegurl
+ pyv:
+ - video/vnd.ms-playready.media.pyv
+ uvu:
+ - video/vnd.uvvu.mp4
+ uvvu:
+ - video/vnd.uvvu.mp4
+ viv:
+ - video/vnd.vivo
+ webm:
+ - video/webm
+ f4v:
+ - video/x-f4v
+ fli:
+ - video/x-fli
+ flv:
+ - video/x-flv
+ m4v:
+ - video/x-m4v
+ mkv:
+ - video/x-matroska
+ mk3d:
+ - video/x-matroska
+ mks:
+ - video/x-matroska
+ mng:
+ - video/x-mng
+ asf:
+ - video/x-ms-asf
+ asx:
+ - video/x-ms-asf
+ vob:
+ - video/x-ms-vob
+ wm:
+ - video/x-ms-wm
+ wmv:
+ - video/x-ms-wmv
+ wmx:
+ - video/x-ms-wmx
+ wvx:
+ - video/x-ms-wvx
+ avi:
+ - video/x-msvideo
+ movie:
+ - video/x-sgi-movie
+ smv:
+ - video/x-smv
+ ice:
+ - x-conference/x-cooltalk
diff --git a/system/config/system.yaml b/system/config/system.yaml
index 3d02b2ea3..3d0983c31 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: true # By default grav will include processed images in cache clear, this can be disabled
+ 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
@@ -113,6 +113,8 @@ twig:
autoescape: true # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
+ safe_functions: [] # List of PHP functions which are allowed to be used as Twig functions
+ safe_filters: [] # List of PHP functions which are allowed to be used as Twig filters
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
assets: # Configuration for Assets Manager (JS, CSS)
@@ -129,7 +131,7 @@ assets: # Configuration for Assets Mana
enable_asset_timestamp: false # Enable asset timestamps
enable_asset_sri: false # Enable asset SRI
collections:
- jquery: system://assets/jquery/jquery-2.x.min.js
+ jquery: system://assets/jquery/jquery-3.x.min.js
errors:
display: 0 # Display either (1) Full backtrace | (0) Simple Error | (-1) System Error
@@ -188,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 51e2bc1f2..e3cbb369f 100644
--- a/system/defines.php
+++ b/system/defines.php
@@ -1,4 +1,5 @@
فشل التحقق من صحة:'
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 7795aef8b..f5a194017 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 d9fa177ea..6c17e6aa5 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 b016c59c3..b9e581f91 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 690959d85..810723595 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 000000000..73cda0ee3
--- /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 2da6944d1..daaa616d0 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 000000000..18850a4e0
--- /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 783674d58..47f32dbe8 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 6cb39c9ad..fefbc3317 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/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php
index 51a2531e4..a9b930b13 100644
--- a/system/src/Grav/Common/Assets.php
+++ b/system/src/Grav/Common/Assets.php
@@ -110,7 +110,7 @@ class Assets extends PropertyObject
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
- $this->assets_dir = $locator->findResource('asset://') . DS;
+ $this->assets_dir = $locator->findResource('asset://');
$this->assets_url = $locator->findResource('asset://', false);
$this->config($asset_config);
@@ -164,10 +164,19 @@ class Assets extends PropertyObject
// More than one asset
if (is_array($asset)) {
- foreach ($asset as $a) {
- array_shift($args);
- $args = array_merge([$a], $args);
- call_user_func_array([$this, 'add'], $args);
+ foreach ($asset as $index => $location) {
+ $params = array_slice($args, 1);
+ if (is_array($location)) {
+ $params = array_shift($params);
+ if (is_numeric($params)) {
+ $params = [ 'priority' => $params ];
+ }
+ $params = [array_replace_recursive([], $location, $params)];
+ $location = $index;
+ }
+
+ $params = array_merge([$location], $params);
+ call_user_func_array([$this, 'add'], $params);
}
} elseif (isset($this->collections[$asset])) {
array_shift($args);
@@ -201,8 +210,13 @@ class Assets extends PropertyObject
protected function addType($collection, $type, $asset, $options)
{
if (is_array($asset)) {
- foreach ($asset as $a) {
- $this->addType($collection, $type, $a, $options);
+ foreach ($asset as $index => $location) {
+ $assetOptions = $options;
+ if (is_array($location)) {
+ $assetOptions = array_replace_recursive([], $options, $location);
+ $location = $index;
+ }
+ $this->addType($collection, $type, $location, $assetOptions);
}
return $this;
diff --git a/system/src/Grav/Common/Assets/BaseAsset.php b/system/src/Grav/Common/Assets/BaseAsset.php
index 3e079746b..a115ce9ec 100644
--- a/system/src/Grav/Common/Assets/BaseAsset.php
+++ b/system/src/Grav/Common/Assets/BaseAsset.php
@@ -15,6 +15,7 @@ use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Framework\Object\PropertyObject;
+use RocketTheme\Toolbox\File\File;
use SplFileInfo;
/**
@@ -91,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'];
@@ -182,16 +187,21 @@ abstract class BaseAsset extends PropertyObject
public static function integrityHash($input)
{
$grav = Grav::instance();
+ $uri = $grav['uri'];
$assetsConfig = $grav['config']->get('system.assets');
- if ( !empty($assetsConfig['enable_asset_sri']) && $assetsConfig['enable_asset_sri'] )
- {
- $dataToHash = file_get_contents( GRAV_WEBROOT . $input);
+ if (!self::isRemoteLink($input) && !empty($assetsConfig['enable_asset_sri']) && $assetsConfig['enable_asset_sri']) {
+ $input = preg_replace('#^' . $uri->rootUrl() . '#', '', $input);
+ $asset = File::instance(GRAV_WEBROOT . $input);
- $hash = hash('sha256', $dataToHash, true);
- $hash_base64 = base64_encode($hash);
- return ' integrity="sha256-' . $hash_base64 . '"';
+ if ($asset->exists()) {
+ $dataToHash = $asset->content();
+ $hash = hash('sha256', $dataToHash, true);
+ $hash_base64 = base64_encode($hash);
+
+ return ' integrity="sha256-' . $hash_base64 . '"';
+ }
}
return '';
@@ -253,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 7aef0e145..948104ab9 100644
--- a/system/src/Grav/Common/Assets/Pipeline.php
+++ b/system/src/Grav/Common/Assets/Pipeline.php
@@ -9,9 +9,9 @@
namespace Grav\Common\Assets;
-use Grav\Common\Assets\BaseAsset;
use Grav\Common\Assets\Traits\AssetUtilsTrait;
use Grav\Common\Config\Config;
+use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Utils;
@@ -88,7 +88,14 @@ class Pipeline extends PropertyObject
$uri = Grav::instance()['uri'];
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
- $this->assets_dir = $locator->findResource('asset://') . DS;
+ $this->assets_dir = $locator->findResource('asset://');
+ if (!$this->assets_dir) {
+ // Attempt to create assets folder if it doesn't exist yet.
+ $this->assets_dir = $locator->findResource('asset://', true, true);
+ Folder::mkdir($this->assets_dir);
+ $locator->clearCache();
+ }
+
$this->assets_url = $locator->findResource('asset://', false);
}
@@ -119,10 +126,9 @@ class Pipeline extends PropertyObject
$file = $uid . '.css';
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
- $buffer = null;
-
- if (file_exists($this->assets_dir . $file)) {
- $buffer = file_get_contents($this->assets_dir . $file) . "\n";
+ $filepath = "{$this->assets_dir}/{$file}";
+ if (file_exists($filepath)) {
+ $buffer = file_get_contents($filepath) . "\n";
} else {
//if nothing found get out of here!
if (empty($assets)) {
@@ -141,7 +147,7 @@ class Pipeline extends PropertyObject
// Write file
if (trim($buffer) !== '') {
- file_put_contents($this->assets_dir . $file, $buffer);
+ file_put_contents($filepath, $buffer);
}
}
@@ -182,10 +188,9 @@ class Pipeline extends PropertyObject
$file = $uid . '.js';
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
- $buffer = null;
-
- if (file_exists($this->assets_dir . $file)) {
- $buffer = file_get_contents($this->assets_dir . $file) . "\n";
+ $filepath = "{$this->assets_dir}/{$file}";
+ if (file_exists($filepath)) {
+ $buffer = file_get_contents($filepath) . "\n";
} else {
//if nothing found get out of here!
if (empty($assets)) {
@@ -204,7 +209,7 @@ class Pipeline extends PropertyObject
// Write file
if (trim($buffer) !== '') {
- file_put_contents($this->assets_dir . $file, $buffer);
+ file_put_contents($filepath, $buffer);
}
}
@@ -249,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 a2dad4faa..3f5a690fb 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;
@@ -90,7 +88,7 @@ trait AssetUtilsTrait
}
$relative_dir = dirname($relative_path);
- $link = ROOT_DIR . $relative_path;
+ $link = GRAV_ROOT . '/' . $relative_path;
}
// TODO: looks like this is not being used.
@@ -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 '';
@@ -156,6 +154,10 @@ trait AssetUtilsTrait
$no_key = ['loading'];
foreach ($this->attributes as $key => $value) {
+ if ($value === null) {
+ continue;
+ }
+
if (is_numeric($key)) {
$key = $value;
}
@@ -196,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/Assets/Traits/TestingAssetsTrait.php b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
index cb8de18ae..15dc00e3d 100644
--- a/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
+++ b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
@@ -252,7 +252,7 @@ trait TestingAssetsTrait
*/
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
{
- $root_dir = rtrim(ROOT_DIR, '/');
+ $root_dir = GRAV_ROOT;
// Check if $directory is a stream.
if (strpos($directory, '://')) {
diff --git a/system/src/Grav/Common/Backup/Backups.php b/system/src/Grav/Common/Backup/Backups.php
index 9483b15f7..b70def96f 100644
--- a/system/src/Grav/Common/Backup/Backups.php
+++ b/system/src/Grav/Common/Backup/Backups.php
@@ -222,7 +222,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 a961904de..a50351836 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/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php
index 5693a921f..6ff7372d0 100644
--- a/system/src/Grav/Common/Config/Setup.php
+++ b/system/src/Grav/Common/Config/Setup.php
@@ -41,6 +41,9 @@ class Setup extends Data
*/
public static $environment;
+ /** @var string */
+ public static $securityFile = 'config://security.yaml';
+
/** @var array */
protected $streams = [
'user' => [
@@ -390,12 +393,19 @@ class Setup extends Data
if (!$locator->findResource('environment://config', true)) {
// If environment does not have its own directory, remove it from the lookup.
- $this->set('streams.schemes.environment.prefixes', ['config' => []]);
+ $prefixes = $this->get('streams.schemes.environment.prefixes');
+ $prefixes['config'] = [];
+
+ $this->set('streams.schemes.environment.prefixes', $prefixes);
$this->initializeLocator($locator);
}
- // Create security.yaml if it doesn't exist.
- $filename = $locator->findResource('config://security.yaml', true, true);
+ // Create security.yaml salt if it doesn't exist into existing configuration environment if possible.
+ $securityFile = basename(static::$securityFile);
+ $securityFolder = substr(static::$securityFile, 0, -\strlen($securityFile));
+ $securityFolder = $locator->findResource($securityFolder, true) ?: $locator->findResource($securityFolder, true, true);
+ $filename = "{$securityFolder}/{$securityFile}";
+
$security_file = CompiledYamlFile::instance($filename);
$security_content = (array)$security_file->content();
diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php
index 29a26369c..287819939 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 a50bf2310..7990bec29 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.
*
@@ -317,6 +326,10 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
$toggle = [];
}
// Recursively fetch the items.
+ $childData = $data[$key] ?? null;
+ if (null !== $childData && !is_array($childData)) {
+ throw new \RuntimeException(sprintf("Bad form data for field collection '%s': %s used instead of an array", $key, gettype($childData)));
+ }
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $value);
} else {
$field = $this->get($value);
diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php
index 67fb0a831..4de437dcb 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 4cba620af..bfb40578d 100644
--- a/system/src/Grav/Common/Data/Validation.php
+++ b/system/src/Grav/Common/Data/Validation.php
@@ -238,6 +238,7 @@ class Validation
$value = trim($value);
}
+ $value = preg_replace("/\r\n|\r/um", "\n", $value);
$len = mb_strlen($value);
$min = (int)($params['min'] ?? 0);
@@ -280,7 +281,7 @@ class Validation
$value = trim($value);
}
- return $value;
+ return preg_replace("/\r\n|\r/um", "\n", $value);
}
/**
@@ -518,17 +519,32 @@ class Validation
return false;
}
- if (isset($params['min']) && $value < $params['min']) {
- return false;
+ $value = (float)$value;
+
+ $min = 0;
+ if (isset($params['min'])) {
+ $min = (float)$params['min'];
+ if ($value < $min) {
+ return false;
+ }
}
- if (isset($params['max']) && $value > $params['max']) {
- return false;
+ if (isset($params['max'])) {
+ $max = (float)$params['max'];
+ if ($value > $max) {
+ return false;
+ }
}
- $min = $params['min'] ?? 0;
+ if (isset($params['step'])) {
+ $step = (float)$params['step'];
+ // Count of how many steps we are above/below the minimum value.
+ $pos = ($value - $min) / $step;
- return !(isset($params['step']) && fmod($value - $min, $params['step']) === 0);
+ return is_int(static::filterNumber($pos, $params, $field));
+ }
+
+ return true;
}
/**
@@ -592,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);
}
/**
@@ -765,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') {
@@ -1173,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/Debugger.php b/system/src/Grav/Common/Debugger.php
index 6a1e256b9..b8143f68b 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 6a3783b91..ecbc6ceac 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 450a58126..6e53e70a3 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 2a43eaa44..b64aea1df 100644
--- a/system/src/Grav/Common/Flex/FlexObject.php
+++ b/system/src/Grav/Common/Flex/FlexObject.php
@@ -13,6 +13,7 @@ namespace Grav\Common\Flex;
use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\Flex\Traits\FlexObjectTrait;
+use Grav\Common\Media\Interfaces\MediaInterface;
use Grav\Framework\Flex\Traits\FlexMediaTrait;
use function is_array;
@@ -21,7 +22,7 @@ use function is_array;
*
* @package Grav\Common\Flex
*/
-abstract class FlexObject extends \Grav\Framework\Flex\FlexObject
+abstract class FlexObject extends \Grav\Framework\Flex\FlexObject implements MediaInterface
{
use FlexGravTrait;
use FlexObjectTrait;
@@ -42,7 +43,7 @@ abstract class FlexObject extends \Grav\Framework\Flex\FlexObject
// 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 c75c4ce27..7503653b8 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.');
}
@@ -192,6 +191,14 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
}
+ /**
+ * Set current page.
+ */
+ public function setCurrent(string $path): void
+ {
+ throw new RuntimeException(__METHOD__ . '(): Not Implemented');
+ }
+
/**
* Return previous item.
*
@@ -392,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++;
}
@@ -426,20 +433,20 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
/**
* Returns the items between a set of date ranges of either the page date field (default) or
- * an arbitrary datetime page field where end date is optional
- * Dates can be passed in as text that strtotime() can process
+ * an arbitrary datetime page field where start date and end date are optional
+ * Dates must be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
- * @param string $startDate
- * @param string|false $endDate
+ * @param string|null $startDate
+ * @param string|null $endDate
* @param string|null $field
* @return static
* @throws Exception
*/
- public function dateRange($startDate, $endDate = false, $field = null)
+ public function dateRange($startDate = null, $endDate = null, $field = null)
{
- $start = Utils::date2timestamp($startDate);
- $end = $endDate ? Utils::date2timestamp($endDate) : false;
+ $start = $startDate ? Utils::date2timestamp($startDate) : null;
+ $end = $endDate ? Utils::date2timestamp($endDate) : null;
$entries = [];
foreach ($this as $key => $object) {
@@ -449,7 +456,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
$date = $field ? strtotime($object->getNestedProperty($field)) : $object->date();
- if ($date >= $start && (!$end || $date <= $end)) {
+ if ((!$start || $date >= $start) && (!$end || $date <= $end)) {
$entries[$key] = $object;
}
}
@@ -746,6 +753,16 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
return $bool ? $this->select($list) : $this->unselect($list);
}
+ /**
+ * @param string|null $languageCode
+ * @param bool|null $fallback
+ * @return PageIndex
+ */
+ public function withTranslated(string $languageCode = null, bool $fallback = null)
+ {
+ return $this->getIndex()->withTranslated($languageCode, $fallback);
+ }
+
/**
* Filter pages by given filters.
*
diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
index b43b88602..f02a64866 100644
--- a/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
+++ b/system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
@@ -18,6 +18,7 @@ use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\Flex\Traits\FlexIndexTrait;
use Grav\Common\Grav;
+use Grav\Common\Language\Language;
use Grav\Common\Page\Header;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
@@ -108,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, '.'));
}
@@ -164,6 +169,31 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $root;
}
+ /**
+ * @param string|null $languageCode
+ * @param bool|null $fallback
+ * @return PageIndex
+ */
+ public function withTranslated(string $languageCode = null, bool $fallback = null)
+ {
+ if (null === $languageCode) {
+ return $this;
+ }
+
+ $entries = $this->translateEntries($this->getEntries(), $languageCode, $fallback);
+ $params = ['language' => $languageCode, 'language_fallback' => $fallback] + $this->getParams();
+
+ return $this->createFrom($entries)->setParams($params);
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLanguage(): ?string
+ {
+ return $this->_params['language'] ?? null;
+ }
+
/**
* Get the collection params
*
@@ -174,6 +204,17 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $this->_params ?? [];
}
+ /**
+ * Get the collection param
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getParam(string $name)
+ {
+ return $this->_params[$name] ?? null;
+ }
+
/**
* Set parameters to the Collection
*
@@ -187,6 +228,20 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $this;
}
+ /**
+ * Set a parameter to the Collection
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return $this
+ */
+ public function setParam(string $name, $value)
+ {
+ $this->_params[$name] = $value;
+
+ return $this;
+ }
+
/**
* Get the collection params
*
@@ -197,6 +252,15 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $this->getParams();
}
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getCacheKey()
+ */
+ public function getCacheKey(): string
+ {
+ return $this->getTypePrefix() . $this->getFlexType() . '.' . sha1(json_encode($this->getKeys()) . $this->getKeyField() . $this->getLanguage());
+ }
+
/**
* Filter pages by given filters.
*
@@ -271,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;
}
/**
@@ -345,6 +412,96 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $index;
}
+ /**
+ * @param array $entries
+ * @param string $lang
+ * @param bool|null $fallback
+ * @return array
+ */
+ protected function translateEntries(array $entries, string $lang, bool $fallback = null): array
+ {
+ $languages = $this->getFallbackLanguages($lang, $fallback);
+ foreach ($entries as $key => &$entry) {
+ // Find out which version of the page we should load.
+ $translations = $this->getLanguageTemplates((string)$key);
+ if (!$translations) {
+ // No translations found, is this a folder?
+ continue;
+ }
+
+ // Find a translation.
+ $template = null;
+ foreach ($languages as $code) {
+ if (isset($translations[$code])) {
+ $template = $translations[$code];
+ break;
+ }
+ }
+
+ // We couldn't find a translation, remove entry from the list.
+ if (!isset($code, $template)) {
+ unset($entries['key']);
+ continue;
+ }
+
+ // Get the main key without template and langauge.
+ [$main_key,] = explode('|', $entry['storage_key'] . '|', 2);
+
+ // Update storage key and language.
+ $entry['storage_key'] = $main_key . '|' . $template . '.' . $code;
+ $entry['lang'] = $code;
+ }
+ unset($entry);
+
+ return $entries;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getLanguageTemplates(string $key): array
+ {
+ $meta = $this->getMetaData($key);
+ $template = $meta['template'] ?? 'folder';
+ $translations = $meta['markdown'] ?? [];
+ $list = [];
+ foreach ($translations as $code => $search) {
+ if (isset($search[$template])) {
+ // Use main template if possible.
+ $list[$code] = $template;
+ } elseif (!empty($search)) {
+ // Fall back to first matching template.
+ $list[$code] = key($search);
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param string|null $languageCode
+ * @param bool|null $fallback
+ * @return array
+ */
+ protected function getFallbackLanguages(string $languageCode = null, bool $fallback = null): array
+ {
+ $fallback = $fallback ?? true;
+ if (!$fallback && null !== $languageCode) {
+ return [$languageCode];
+ }
+
+ $grav = Grav::instance();
+
+ /** @var Language $language */
+ $language = $grav['language'];
+ $languageCode = $languageCode ?? '';
+ if ($languageCode === '' && $fallback) {
+ return $language->getFallbackLanguages(null, true);
+ }
+
+ return $fallback ? $language->getFallbackLanguages($languageCode, true) : [$languageCode];
+ }
+
/**
* @param array $options
* @return array
@@ -523,13 +680,14 @@ 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' => basename($child->rawRoute() ?? $child->getKey()),
+ 'item-key' => htmlspecialchars(basename($child->rawRoute() ?? $child->getKey())),
'icon' => $icon,
'title' => htmlspecialchars($child->menu()),
'route' => [
- 'display' => ($route ? ($route->toString(false) ?: '/') : null) ?? '',
- 'raw' => $child->rawRoute(),
+ 'display' => htmlspecialchars($route) ?: null,
+ 'raw' => htmlspecialchars($child->rawRoute()),
],
'modified' => $this->jsDate($child->modified()),
'child_count' => $child_count ?: null,
@@ -684,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);
}
@@ -799,17 +956,17 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
/**
* Returns the items between a set of date ranges of either the page date field (default) or
- * an arbitrary datetime page field where end date is optional
- * Dates can be passed in as text that strtotime() can process
+ * an arbitrary datetime page field where start date and end date are optional
+ * Dates must be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
- * @param string $startDate
- * @param bool $endDate
+ * @param string|null $startDate
+ * @param string|null $endDate
* @param string|null $field
* @return static
* @throws Exception
*/
- public function dateRange($startDate, $endDate = false, $field = null)
+ public function dateRange($startDate = null, $endDate = null, $field = null)
{
$collection = $this->__call('dateRange', [$startDate, $endDate, $field]);
diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageObject.php b/system/src/Grav/Common/Flex/Types/Pages/PageObject.php
index 933a00a32..4c83f959b 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')) {
@@ -262,6 +262,24 @@ class PageObject extends FlexPageObject
$this->getFlexDirectory()->reloadIndex();
}
+ /**
+ * @param UserInterface|null $user
+ */
+ public function check(UserInterface $user = null): void
+ {
+ parent::check($user);
+
+ if ($user && $this->isMoved()) {
+ $parentKey = $this->getProperty('parent_key');
+
+ /** @var PageObject|null $parent */
+ $parent = $this->getFlexDirectory()->getObject($parentKey, 'storage_key');
+ if (!$parent || !$parent->isAuthorized('create', null, $user)) {
+ throw new \RuntimeException('Forbidden', 403);
+ }
+ }
+ }
+
/**
* @param array|bool $reorder
* @return FlexObject|FlexObjectInterface
@@ -293,7 +311,7 @@ class PageObject extends FlexPageObject
}
// Reset original after save events have all been called.
- $this->_original = null;
+ $this->_originalObject = null;
return $instance;
}
@@ -357,6 +375,19 @@ class PageObject extends FlexPageObject
return parent::isAuthorizedOverride($user, $action, $scope, $isMe);
}
+ /**
+ * @return bool
+ */
+ protected function isMoved(): bool
+ {
+ $storageKey = $this->getMasterKey();
+ $filesystem = Filesystem::getInstance(false);
+ $oldParentKey = ltrim($filesystem->dirname("/{$storageKey}"), '/');
+ $newParentKey = $this->getProperty('parent_key');
+
+ return $this->exists() && $oldParentKey !== $newParentKey;
+ }
+
/**
* @param array $ordering
* @return PageCollection|null
@@ -364,10 +395,7 @@ class PageObject extends FlexPageObject
protected function reorderSiblings(array $ordering)
{
$storageKey = $this->getMasterKey();
- $filesystem = Filesystem::getInstance(false);
- $oldParentKey = ltrim($filesystem->dirname("/{$storageKey}"), '/');
- $newParentKey = $this->getProperty('parent_key');
- $isMoved = $oldParentKey !== $newParentKey;
+ $isMoved = $this->isMoved();
$order = !$isMoved ? $this->order() : false;
if ($order !== false) {
$order = (int)$order;
@@ -385,10 +413,12 @@ class PageObject extends FlexPageObject
// Handle special case where ordering isn't given.
if ($ordering === []) {
if ($order >= 999999) {
- // Set ordering to point to be the last item.
+ // Set ordering to point to be the last item, ignoring the object itself.
$order = 0;
foreach ($siblings as $sibling) {
- $order = max($order, (int)$sibling->order());
+ if ($sibling->getKey() !== $this->getKey()) {
+ $order = max($order, (int)$sibling->order());
+ }
}
$this->order($order + 1);
}
@@ -411,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;
}
@@ -421,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);
@@ -500,6 +532,8 @@ class PageObject extends FlexPageObject
if ($isNew === true && $name === '') {
// Support onBlueprintCreated event just like in Pages::blueprints($template)
$blueprint->set('initialized', true);
+ $blueprint->setFilename($template);
+
Grav::instance()->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $template]));
}
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 d8193df7c..589ea881a 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/UserGroups/UserGroupObject.php b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php
index fb69eab68..ea68fa1bf 100644
--- a/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php
+++ b/system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php
@@ -41,6 +41,14 @@ class UserGroupObject extends FlexObject implements UserGroupInterface
] + parent::getCachedMethods();
}
+ /**
+ * @return string
+ */
+ public function getTitle(): string
+ {
+ return $this->getProperty('readableName');
+ }
+
/**
* Checks user authorization to the action.
*
diff --git a/system/src/Grav/Common/Flex/Types/Users/UserCollection.php b/system/src/Grav/Common/Flex/Types/Users/UserCollection.php
index e17525a9c..9b6490a8a 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 4a3f58773..6e0bc65fe 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 4109eec34..02caee4aa 100644
--- a/system/src/Grav/Common/Flex/Types/Users/UserObject.php
+++ b/system/src/Grav/Common/Flex/Types/Users/UserObject.php
@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Grav\Common\Flex\Types\Users;
+use Closure;
use Countable;
use Grav\Common\Config\Config;
use Grav\Common\Data\Blueprint;
@@ -22,7 +23,6 @@ use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaUploadInterface;
use Grav\Common\Page\Media;
-use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Medium\MediumFactory;
use Grav\Common\User\Access;
use Grav\Common\User\Authentication;
@@ -32,6 +32,7 @@ use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\User\Traits\UserTrait;
use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\Formatter\YamlFormatter;
+use Grav\Framework\Filesystem\Filesystem;
use Grav\Framework\Flex\Flex;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Flex\Storage\FileStorage;
@@ -76,18 +77,17 @@ class UserObject extends FlexObject implements UserInterface, Countable
use UserTrait;
use UserObjectLegacyTrait;
+ /** @var Closure|null */
+ static public $authorizeCallable;
+
/** @var array|null */
protected $_uploads_original;
-
/** @var FileInterface|null */
protected $_storage;
-
/** @var UserGroupIndex */
protected $_groups;
-
/** @var Access */
protected $_access;
-
/** @var array|null */
protected $access;
@@ -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,15 @@ class UserObject extends FlexObject implements UserInterface, Countable
}
}
+ $authorizeCallable = static::$authorizeCallable;
+ if ($authorizeCallable instanceof Closure) {
+ $authorizeCallable->bindTo($this);
+ $authorized = $authorizeCallable($action, $scope);
+ if (is_bool($authorized)) {
+ return $authorized;
+ }
+ }
+
// Check user access.
$access = $this->getAccess();
$authorized = $access->authorize($action, $scope);
@@ -297,6 +316,14 @@ class UserObject extends FlexObject implements UserInterface, Countable
return $value;
}
+ /**
+ * @return UserGroupIndex
+ */
+ public function getRoles(): UserGroupIndex
+ {
+ return $this->getGroups();
+ }
+
/**
* Convert object into an array.
*
@@ -694,6 +721,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
/**
* @param array $files
+ * @return void
*/
protected function setUpdatedMedia(array $files): void
{
@@ -701,10 +729,16 @@ class UserObject extends FlexObject implements UserInterface, Countable
$locator = Grav::instance()['locator'];
$media = $this->getMedia();
+ if (!$media instanceof MediaUploadInterface) {
+ return;
+ }
+
+ $filesystem = Filesystem::getInstance(false);
$list = [];
$list_original = [];
foreach ($files as $field => $group) {
+ // Ignore files without a field.
if ($field === '') {
continue;
}
@@ -712,7 +746,6 @@ class UserObject extends FlexObject implements UserInterface, Countable
// Load settings for the field.
$settings = $this->getMediaFieldSettings($field);
-
foreach ($group as $filename => $file) {
if ($file) {
// File upload.
@@ -727,8 +760,8 @@ class UserObject extends FlexObject implements UserInterface, Countable
}
if ($file) {
- // Check file upload against media limits.
- $filename = $media->checkUploadedFile($file, $filename, $settings);
+ // Check file upload against media limits (except for max size).
+ $filename = $media->checkUploadedFile($file, $filename, ['filesize' => 0] + $settings);
}
$self = $settings['self'];
@@ -751,19 +784,25 @@ class UserObject extends FlexObject implements UserInterface, Countable
continue;
}
+ // Calculate path without the retina scaling factor.
+ $realpath = $filesystem->pathname($filepath) . str_replace(['@3x', '@2x'], '', basename($filepath));
+
$list[$filename] = [$file, $settings];
+ $path = str_replace('.', "\n", $field);
if (null !== $data) {
$data['name'] = $filename;
$data['path'] = $filepath;
- $this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
+ $this->setNestedProperty("{$path}\n{$realpath}", $data, "\n");
} else {
- $this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
+ $this->unsetNestedProperty("{$path}\n{$realpath}", "\n");
}
}
}
+ $this->clearMediaCache();
+
$this->_uploads = $list;
$this->_uploads_original = $list_original;
}
diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php
index 1b15e5de8..450569632 100644
--- a/system/src/Grav/Common/GPM/GPM.php
+++ b/system/src/Grav/Common/GPM/GPM.php
@@ -35,7 +35,11 @@ class GPM extends Iterator
/** @var Remote\Packages|null Remote available Packages */
private $repository;
/** @var Remote\GravCore|null Remove Grav Packages */
- public $grav;
+ private $grav;
+ /** @var bool */
+ private $refresh;
+ /** @var callable|null */
+ private $callback;
/** @var array Internal cache */
protected $cache;
@@ -55,13 +59,45 @@ class GPM extends Iterator
public function __construct($refresh = false, $callback = null)
{
parent::__construct();
+
+ Folder::create(CACHE_DIR . '/gpm');
+
$this->cache = [];
$this->installed = new Local\Packages();
- try {
- $this->repository = new Remote\Packages($refresh, $callback);
- $this->grav = new Remote\GravCore($refresh, $callback);
- } catch (Exception $e) {
+ $this->refresh = $refresh;
+ $this->callback = $callback;
+ }
+
+ /**
+ * Magic getter method
+ *
+ * @param string $offset Asset name value
+ * @return mixed Asset value
+ */
+ public function __get($offset)
+ {
+ switch ($offset) {
+ case 'grav':
+ return $this->getGrav();
}
+
+ return parent::__get($offset);
+ }
+
+ /**
+ * Magic method to determine if the attribute is set
+ *
+ * @param string $offset Asset name value
+ * @return bool True if the value is set
+ */
+ public function __isset($offset)
+ {
+ switch ($offset) {
+ case 'grav':
+ return $this->getGrav() !== null;
+ }
+
+ return parent::__isset($offset);
}
/**
@@ -266,11 +302,12 @@ class GPM extends Iterator
{
$items = [];
- if (null === $this->repository) {
+ $repository = $this->getRepository();
+ if (null === $repository) {
return $items;
}
- $repository = $this->repository['plugins'];
+ $plugins = $repository['plugins'];
// local cache to speed things up
if (isset($this->cache[__METHOD__])) {
@@ -278,18 +315,18 @@ class GPM extends Iterator
}
foreach ($this->installed['plugins'] as $slug => $plugin) {
- if (!isset($repository[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
+ if (!isset($plugins[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
continue;
}
$local_version = $plugin->version ?? 'Unknown';
- $remote_version = $repository[$slug]->version;
+ $remote_version = $plugins[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
- $repository[$slug]->available = $remote_version;
- $repository[$slug]->version = $local_version;
- $repository[$slug]->type = $repository[$slug]->release_type;
- $items[$slug] = $repository[$slug];
+ $plugins[$slug]->available = $remote_version;
+ $plugins[$slug]->version = $local_version;
+ $plugins[$slug]->type = $plugins[$slug]->release_type;
+ $items[$slug] = $plugins[$slug];
}
}
@@ -306,19 +343,20 @@ class GPM extends Iterator
*/
public function getLatestVersionOfPackage($package_name)
{
- if (null === $this->repository) {
+ $repository = $this->getRepository();
+ if (null === $repository) {
return null;
}
- $repository = $this->repository['plugins'];
- if (isset($repository[$package_name])) {
- return $repository[$package_name]->available ?: $repository[$package_name]->version;
+ $plugins = $repository['plugins'];
+ if (isset($plugins[$package_name])) {
+ return $plugins[$package_name]->available ?: $plugins[$package_name]->version;
}
//Not a plugin, it's a theme?
- $repository = $this->repository['themes'];
- if (isset($repository[$package_name])) {
- return $repository[$package_name]->available ?: $repository[$package_name]->version;
+ $themes = $repository['themes'];
+ if (isset($themes[$package_name])) {
+ return $themes[$package_name]->available ?: $themes[$package_name]->version;
}
return null;
@@ -356,11 +394,12 @@ class GPM extends Iterator
{
$items = [];
- if (null === $this->repository) {
+ $repository = $this->getRepository();
+ if (null === $repository) {
return $items;
}
- $repository = $this->repository['themes'];
+ $themes = $repository['themes'];
// local cache to speed things up
if (isset($this->cache[__METHOD__])) {
@@ -368,18 +407,18 @@ class GPM extends Iterator
}
foreach ($this->installed['themes'] as $slug => $plugin) {
- if (!isset($repository[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
+ if (!isset($themes[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
continue;
}
$local_version = $plugin->version ?? 'Unknown';
- $remote_version = $repository[$slug]->version;
+ $remote_version = $themes[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
- $repository[$slug]->available = $remote_version;
- $repository[$slug]->version = $local_version;
- $repository[$slug]->type = $repository[$slug]->release_type;
- $items[$slug] = $repository[$slug];
+ $themes[$slug]->available = $remote_version;
+ $themes[$slug]->version = $local_version;
+ $themes[$slug]->type = $themes[$slug]->release_type;
+ $items[$slug] = $themes[$slug];
}
}
@@ -407,19 +446,20 @@ class GPM extends Iterator
*/
public function getReleaseType($package_name)
{
- if (null === $this->repository) {
+ $repository = $this->getRepository();
+ if (null === $repository) {
return null;
}
- $repository = $this->repository['plugins'];
- if (isset($repository[$package_name])) {
- return $repository[$package_name]->release_type;
+ $plugins = $repository['plugins'];
+ if (isset($plugins[$package_name])) {
+ return $plugins[$package_name]->release_type;
}
//Not a plugin, it's a theme?
- $repository = $this->repository['themes'];
- if (isset($repository[$package_name])) {
- return $repository[$package_name]->release_type;
+ $themes = $repository['themes'];
+ if (isset($themes[$package_name])) {
+ return $themes[$package_name]->release_type;
}
return null;
@@ -470,7 +510,7 @@ class GPM extends Iterator
*/
public function getRepositoryPlugins()
{
- return $this->repository['plugins'] ?? null;
+ return $this->getRepository()['plugins'] ?? null;
}
/**
@@ -493,7 +533,7 @@ class GPM extends Iterator
*/
public function getRepositoryThemes()
{
- return $this->repository['themes'] ?? null;
+ return $this->getRepository()['themes'] ?? null;
}
/**
@@ -504,9 +544,31 @@ class GPM extends Iterator
*/
public function getRepository()
{
+ if (null === $this->repository) {
+ try {
+ $this->repository = new Remote\Packages($this->refresh, $this->callback);
+ } catch (Exception $e) {}
+ }
+
return $this->repository;
}
+ /**
+ * Returns Grav version available in the repository
+ *
+ * @return Remote\GravCore|null
+ */
+ public function getGrav()
+ {
+ if (null === $this->grav) {
+ try {
+ $this->grav = new Remote\GravCore($this->refresh, $this->callback);
+ } catch (Exception $e) {}
+ }
+
+ return $this->grav;
+ }
+
/**
* Searches for a Package in the repository
*
@@ -527,7 +589,7 @@ class GPM extends Iterator
$plugins = $this->getRepositoryPlugins();
if (null === $themes || null === $plugins) {
- if (!is_writable(ROOT_DIR . '/cache/gpm')) {
+ if (!is_writable(GRAV_ROOT . '/cache/gpm')) {
throw new RuntimeException('The cache/gpm folder is not writable. Please check the folder permissions.');
}
diff --git a/system/src/Grav/Common/GPM/Installer.php b/system/src/Grav/Common/GPM/Installer.php
index 9cdea52aa..639240be6 100644
--- a/system/src/Grav/Common/GPM/Installer.php
+++ b/system/src/Grav/Common/GPM/Installer.php
@@ -135,7 +135,10 @@ class Installer
}
if (!$options['sophisticated']) {
- if ($options['theme']) {
+ $isTheme = $options['theme'] ?? false;
+ // Make sure that themes are always being copied, even if option was not set!
+ $isTheme = $isTheme || preg_match('|/themes/[^/]+|ui', $install_path);
+ if ($isTheme) {
self::copyInstall($extracted, $install_path);
} else {
self::moveInstall($extracted, $install_path);
diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php
index 47cef7c09..98654b6a1 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 8f3a73a2b..916d5249a 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 c9097eea8..f5919613b 100644
--- a/system/src/Grav/Common/Grav.php
+++ b/system/src/Grav/Common/Grav.php
@@ -9,6 +9,7 @@
namespace Grav\Common;
+use Composer\Autoload\ClassLoader;
use Grav\Common\Config\Config;
use Grav\Common\Config\Setup;
use Grav\Common\Helpers\Exif;
@@ -134,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
@@ -152,6 +153,13 @@ class Grav extends Container
{
if (null === self::$instance) {
self::$instance = static::load($values);
+
+ /** @var ClassLoader|null $loader */
+ $loader = self::$instance['loader'] ?? null;
+ if ($loader) {
+ // Load fix for Deferred Twig Extension
+ $loader->addPsr4('Phive\\Twig\\Extensions\\Deferred\\', LIB_DIR . 'Phive/Twig/Extensions/Deferred/', true);
+ }
} elseif ($values) {
$instance = self::$instance;
foreach ($values as $key => $value) {
@@ -234,7 +242,7 @@ class Grav extends Container
*
* @return void
*/
- public function process()
+ public function process(): void
{
if (isset($this->initialized['process'])) {
return;
@@ -466,7 +474,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);
@@ -481,7 +489,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 */
@@ -506,7 +514,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')) {
@@ -567,7 +575,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')) {
@@ -686,7 +694,7 @@ class Grav extends Container
*
* @return void
*/
- protected function registerServices()
+ protected function registerServices(): void
{
foreach (self::$diMap as $serviceKey => $serviceClass) {
if (is_int($serviceKey)) {
@@ -753,12 +761,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) {
@@ -773,6 +779,6 @@ class Grav extends Container
return false;
}
- return $page;
+ return $page ?? false;
}
}
diff --git a/system/src/Grav/Common/HTTP/Client.php b/system/src/Grav/Common/HTTP/Client.php
new file mode 100644
index 000000000..cd4f8dbb2
--- /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 000000000..4a0513667
--- /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 cd285cf87..173850a53 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 a03fde810..187828ae3 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 cc7cf9286..6e80b947e 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 9282070d6..e637bddaf 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 0f5c5ed26..f6e22224e 100644
--- a/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php
+++ b/system/src/Grav/Common/Media/Traits/ImageMediaTrait.php
@@ -58,7 +58,7 @@ trait ImageMediaTrait
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
'negate', 'brightness', 'contrast', 'grayscale', 'emboss',
'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive',
- 'rotate', 'flip', 'fixOrientation', 'gaussianBlur', 'format', 'merge'
+ 'rotate', 'flip', 'fixOrientation', 'gaussianBlur', 'format', 'create', 'fill', 'merge'
];
/** @var array */
diff --git a/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php b/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php
index 3a0181a32..7a1f55d6e 100644
--- a/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php
+++ b/system/src/Grav/Common/Media/Traits/MediaUploadTrait.php
@@ -20,11 +20,13 @@ use Grav\Common\Security;
use Grav\Common\Utils;
use Grav\Framework\Filesystem\Filesystem;
use Grav\Framework\Form\FormFlashFile;
+use Grav\Framework\Mime\MimeTypes;
use Psr\Http\Message\UploadedFileInterface;
use RocketTheme\Toolbox\File\YamlFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function dirname;
+use function in_array;
/**
* Implements media upload and delete functionality.
@@ -71,15 +73,6 @@ trait MediaUploadTrait
*/
public function checkUploadedFile(UploadedFileInterface $uploadedFile, string $filename = null, array $settings = null): string
{
- // Add the defaults to the settings.
- $settings = $this->getUploadSettings($settings);
-
- // Destination is always needed (but it can be set in defaults).
- $self = $settings['self'] ?? false;
- if (!isset($settings['destination']) && $self === false) {
- throw new RuntimeException($this->translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED'), 400);
- }
-
// Check if there is an upload error.
switch ($uploadedFile->getError()) {
case UPLOAD_ERR_OK:
@@ -101,10 +94,38 @@ trait MediaUploadTrait
throw new RuntimeException($this->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 400);
}
+ $metadata = [
+ 'filename' => $uploadedFile->getClientFilename(),
+ 'mime' => $uploadedFile->getClientMediaType(),
+ 'size' => $uploadedFile->getSize(),
+ ];
+
+ return $this->checkFileMetadata($metadata, $filename, $settings);
+ }
+
+ /**
+ * Checks that file metadata meets the requirements. Returns new filename.
+ *
+ * @param array $metadata
+ * @param array|null $settings
+ * @return string
+ * @throws RuntimeException
+ */
+ public function checkFileMetadata(array $metadata, string $filename = null, array $settings = null): string
+ {
+ // Add the defaults to the settings.
+ $settings = $this->getUploadSettings($settings);
+
+ // Destination is always needed (but it can be set in defaults).
+ $self = $settings['self'] ?? false;
+ if (!isset($settings['destination']) && $self === false) {
+ throw new RuntimeException($this->translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED'), 400);
+ }
+
if (null === $filename) {
// If no filename is given, use the filename from the uploaded file (path is not allowed).
$folder = '';
- $filename = $uploadedFile->getClientFilename() ?? '';
+ $filename = $metadata['filename'] ?? '';
} else {
// If caller sets the filename, we will accept any custom path.
$folder = dirname($filename);
@@ -128,7 +149,7 @@ trait MediaUploadTrait
$filename = date('YmdHis') . '-' . $filename;
}
}
- $filepath = $folder !== '' ? $folder . $filename : $filename;
+ $filepath = $folder . $filename;
// Check if the filename is allowed.
if (!Utils::checkFilename($filename)) {
@@ -148,23 +169,32 @@ trait MediaUploadTrait
$filesize = $settings['filesize'];
if ($filesize) {
$max_filesize = $filesize * 1048576;
- if ($uploadedFile->getSize() > $max_filesize) {
+ if ($metadata['size'] > $max_filesize) {
// TODO: use own language string
throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400);
}
} elseif (null === $filesize) {
// Check size against the Grav upload limit.
$grav_limit = Utils::getUploadLimit();
- if ($grav_limit > 0 && $uploadedFile->getSize() > $grav_limit) {
+ if ($grav_limit > 0 && $metadata['size'] > $grav_limit) {
throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400);
}
}
+ $grav = Grav::instance();
+ /** @var MimeTypes $mimeChecker */
+ $mimeChecker = $grav['mime'];
+
// Handle Accepted file types. Accept can only be mime types (image/png | image/*) or file extensions (.pdf | .jpg)
+ // Do not trust mime type sent by the browser.
+ $mime = $metadata['mime'] ?? $mimeChecker->getMimeType($extension);
+ $validExtensions = $mimeChecker->getExtensions($mime);
+ if (!in_array($extension, $validExtensions, true)) {
+ throw new RuntimeException('The mime type does not match to file extension', 400);
+ }
+
$accepted = false;
$errors = [];
- // Do not trust mime type sent by the browser.
- $mime = Utils::getMimeByFilename($filename);
foreach ((array)$settings['accept'] as $type) {
// Force acceptance of any file when star notation
if ($type === '*') {
@@ -394,6 +424,17 @@ trait MediaUploadTrait
$uploadedFile->moveTo($filepath);
}
+ /**
+ * Get upload settings.
+ *
+ * @param array|null $settings Form field specific settings (override).
+ * @return array
+ */
+ public function getUploadSettings(?array $settings = null): array
+ {
+ return null !== $settings ? $settings + $this->_upload_defaults : $this->_upload_defaults;
+ }
+
/**
* Internal logic to copy file.
*
@@ -580,17 +621,6 @@ trait MediaUploadTrait
}
}
- /**
- * Get upload settings.
- *
- * @param array|null $settings Form field specific settings (override).
- * @return array
- */
- protected function getUploadSettings(?array $settings = null): array
- {
- return null !== $settings ? $settings + $this->_upload_defaults : $this->_upload_defaults;
- }
-
/**
* @param string $filename
* @param string $path
diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php
index e67ff047b..930aeda2b 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');
}
/**
@@ -145,6 +145,18 @@ class Collection extends Iterator implements PageCollectionInterface
return $this;
}
+ /**
+ * Set current page.
+ */
+ public function setCurrent(string $path): void
+ {
+ reset($this->items);
+
+ while (($key = key($this->items)) !== null && $key !== $path) {
+ next($this->items);
+ }
+ }
+
/**
* Returns current page.
*
@@ -175,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;
@@ -319,30 +332,32 @@ class Collection extends Iterator implements PageCollectionInterface
/**
* Returns the items between a set of date ranges of either the page date field (default) or
- * an arbitrary datetime page field where end date is optional
- * Dates can be passed in as text that strtotime() can process
+ * an arbitrary datetime page field where start date and end date are optional
+ * Dates must be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
- * @param string $startDate
- * @param bool $endDate
+ * @param string|null $startDate
+ * @param string|null $endDate
* @param string|null $field
* @return $this
* @throws Exception
*/
- public function dateRange($startDate, $endDate = false, $field = null)
+ public function dateRange($startDate = null, $endDate = null, $field = null)
{
- $start = Utils::date2timestamp($startDate);
- $end = $endDate ? Utils::date2timestamp($endDate) : false;
+ $start = $startDate ? Utils::date2timestamp($startDate) : null;
+ $end = $endDate ? Utils::date2timestamp($endDate) : null;
$date_range = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
- if ($page !== null) {
- $date = $field ? strtotime($page->value($field)) : $page->date();
+ if (!$page) {
+ continue;
+ }
- if ($date >= $start && (!$end || $date <= $end)) {
- $date_range[$path] = $slug;
- }
+ $date = $field ? strtotime($page->value($field)) : $page->date();
+
+ if ((!$start || $date >= $start) && (!$end || $date <= $end)) {
+ $date_range[$path] = $slug;
}
}
diff --git a/system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php b/system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php
index 67c5b1e96..50029118f 100644
--- a/system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php
+++ b/system/src/Grav/Common/Page/Interfaces/PageCollectionInterface.php
@@ -158,17 +158,17 @@ interface PageCollectionInterface extends Traversable, ArrayAccess, Countable, S
/**
* Returns the items between a set of date ranges of either the page date field (default) or
- * an arbitrary datetime page field where end date is optional
- * Dates can be passed in as text that strtotime() can process
+ * an arbitrary datetime page field where start date and end date are optional
+ * Dates must be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
- * @param string $startDate
- * @param bool $endDate
+ * @param string|null $startDate
+ * @param string|null $endDate
* @param string|null $field
* @return PageCollectionInterface
* @throws Exception
*/
- public function dateRange($startDate, $endDate = false, $field = null);
+ public function dateRange($startDate = null, $endDate = null, $field = null);
/**
* Creates new collection with only visible pages
diff --git a/system/src/Grav/Common/Page/Markdown/Excerpts.php b/system/src/Grav/Common/Page/Markdown/Excerpts.php
index 0c687e7f1..9b0b9963c 100644
--- a/system/src/Grav/Common/Page/Markdown/Excerpts.php
+++ b/system/src/Grav/Common/Page/Markdown/Excerpts.php
@@ -114,7 +114,7 @@ class Excerpts
);
// Valid attributes supported.
- $valid_attributes = $grav['config']->get('system.pages.markdown.valid_link_attributes');
+ $valid_attributes = $grav['config']->get('system.pages.markdown.valid_link_attributes') ?? [];
$skip = [];
// Unless told to not process, go through actions.
@@ -157,7 +157,7 @@ class Excerpts
// Handle custom streams.
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
- if ($locator->isStream($url)) {
+ if ($type === 'link' && $locator->isStream($url)) {
$path = $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
$url_parts['path'] = $grav['base_url_relative'] . '/' . $path;
unset($url_parts['stream'], $url_parts['scheme']);
diff --git a/system/src/Grav/Common/Page/Media.php b/system/src/Grav/Common/Page/Media.php
index 9201e66f8..e18201420 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];
@@ -102,12 +104,13 @@ class Media extends AbstractMedia
foreach ($iterator as $file => $info) {
// Ignore folders and Markdown files.
- if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
+ $filename = $info->getFilename();
+ if (!$info->isFile() || $info->getExtension() === 'md' || $filename === 'frontmatter.yaml' || strpos($filename, '.') === 0) {
continue;
}
// Find out what type we're dealing with
- [$basename, $ext, $type, $extra] = $this->getFileParts($info->getFilename());
+ [$basename, $ext, $type, $extra] = $this->getFileParts($filename);
if (!in_array(strtolower($ext), $media_types, true)) {
continue;
diff --git a/system/src/Grav/Common/Page/Medium/GlobalMedia.php b/system/src/Grav/Common/Page/Medium/GlobalMedia.php
index 4097c8f58..50d69c747 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 d35c0a241..46c832ee9 100644
--- a/system/src/Grav/Common/Page/Medium/ImageMedium.php
+++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php
@@ -399,6 +399,37 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
return $this;
}
+ /**
+ * Add a frame to image
+ *
+ * @return $this
+ */
+ public function addFrame(int $border = 10, string $color = '0x000000')
+ {
+ if(is_int(intval($border)) && $border>0 && preg_match('/^0x[a-f0-9]{6}$/i', $color)) { // $border must be an integer and bigger than 0; $color must be formatted as an HEX value (0x??????).
+ $image = ImageFile::open($this->path());
+ }
+ else {
+ return $this;
+ }
+
+ $dst_width = $image->width()+2*$border;
+ $dst_height = $image->height()+2*$border;
+
+ $frame = ImageFile::create($dst_width, $dst_height);
+
+ $frame->__call('fill', [$color]);
+
+ $this->image = $frame;
+
+ $this->__call('merge', [$image, $border, $border]);
+
+ $this->saveImage();
+
+ return $this;
+
+ }
+
/**
* Forward the call to the image processing method.
*
@@ -406,6 +437,7 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
* @param mixed $args
* @return $this|mixed
*/
+
public function __call($method, $args)
{
if (!in_array($method, static::$magic_actions, true)) {
diff --git a/system/src/Grav/Common/Page/Medium/Link.php b/system/src/Grav/Common/Page/Medium/Link.php
index 2bb857ba1..d57687573 100644
--- a/system/src/Grav/Common/Page/Medium/Link.php
+++ b/system/src/Grav/Common/Page/Medium/Link.php
@@ -41,9 +41,7 @@ class Link implements RenderableInterface, MediaLinkInterface
$this->attributes = $attributes;
$source = $medium->reset()->thumbnail('auto')->display('thumbnail');
-
- // FIXME: Thumbnail can be null, maybe we should not allow that?
- if (null === $source) {
+ if (!$source instanceof MediaObjectInterface) {
throw new RuntimeException('Media has no thumbnail set');
}
@@ -89,10 +87,15 @@ class Link implements RenderableInterface, MediaLinkInterface
throw new BadMethodCallException(get_class($object) . '::' . $method . '() not found.');
}
- $this->source = call_user_func_array($callable, $args);
+ $object = call_user_func_array($callable, $args);
+ if (!$object instanceof MediaLinkInterface) {
+ // Don't start nesting links, if user has multiple link calls in his
+ // actions, we will drop the previous links.
+ return $this;
+ }
- // Don't start nesting links, if user has multiple link calls in his
- // actions, we will drop the previous links.
- return $this->source instanceof MediaLinkInterface ? $this->source : $this;
+ $this->source = $object;
+
+ return $object;
}
}
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index 5c92cba9a..2099be212 100644
--- a/system/src/Grav/Common/Page/Page.php
+++ b/system/src/Grav/Common/Page/Page.php
@@ -2095,7 +2095,7 @@ class Page implements PageInterface
*/
public function filePathClean()
{
- return str_replace(ROOT_DIR, '', $this->filePath());
+ return str_replace(GRAV_ROOT . DS, '', $this->filePath());
}
/**
@@ -2274,11 +2274,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 3b3ce8ef3..941c9a7b3 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;
@@ -540,9 +592,9 @@ class Pages
}
if (isset($params['dateRange'])) {
- $start = $params['dateRange']['start'] ?? 0;
- $end = $params['dateRange']['end'] ?? false;
- $field = $params['dateRange']['field'] ?? false;
+ $start = $params['dateRange']['start'] ?? null;
+ $end = $params['dateRange']['end'] ?? null;
+ $field = $params['dateRange']['field'] ?? null;
$collection = $collection->dateRange($start, $end, $field);
}
@@ -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
}
@@ -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 65b11dba1..3b0d3fb14 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 be8f43bc2..88b5e524e 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);
@@ -287,33 +287,42 @@ class Plugins extends Iterator
{
// NOTE: ALL THE LOCAL VARIABLES ARE USED INSIDE INCLUDED FILE, DO NOT REMOVE THEM!
$grav = Grav::instance();
+ /** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
- $file = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT);
+ $class = null;
+ // Start by attempting to load the plugin_name.php file.
+ $file = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT);
if (is_file($file)) {
// Local variables available in the file: $grav, $name, $file
$class = include_once $file;
+ if (!is_object($class) || !is_subclass_of($class, Plugin::class, true)) {
+ $class = null;
+ }
+ }
- if (!$class || !is_subclass_of($class, Plugin::class, true)) {
- $className = Inflector::camelize($name);
- $pluginClassFormat = [
- 'Grav\\Plugin\\' . ucfirst($name). 'Plugin',
- 'Grav\\Plugin\\' . $className . 'Plugin',
- 'Grav\\Plugin\\' . $className
- ];
+ // If the class hasn't been initialized yet, guess the class name and create a new instance.
+ if (null === $class) {
+ $className = Inflector::camelize($name);
+ $pluginClassFormat = [
+ 'Grav\\Plugin\\' . ucfirst($name). 'Plugin',
+ 'Grav\\Plugin\\' . $className . 'Plugin',
+ 'Grav\\Plugin\\' . $className
+ ];
- foreach ($pluginClassFormat as $pluginClass) {
- if (is_subclass_of($pluginClass, Plugin::class, true)) {
- $class = new $pluginClass($name, $grav);
- break;
- }
+ foreach ($pluginClassFormat as $pluginClass) {
+ if (is_subclass_of($pluginClass, Plugin::class, true)) {
+ $class = new $pluginClass($name, $grav);
+ break;
}
}
- } else {
+ }
+
+ // Log a warning if plugin cannot be found.
+ if (null === $class) {
$grav['log']->addWarning(
- sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $name)
+ sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clearcache`", $name)
);
- return null;
}
return $class;
diff --git a/system/src/Grav/Common/Processors/InitializeProcessor.php b/system/src/Grav/Common/Processors/InitializeProcessor.php
index cc8e4f260..55cba033c 100644
--- a/system/src/Grav/Common/Processors/InitializeProcessor.php
+++ b/system/src/Grav/Common/Processors/InitializeProcessor.php
@@ -105,7 +105,7 @@ class InitializeProcessor extends ProcessorBase
// TODO: remove in 2.0.
$this->container['accounts'];
- // Initialize session.
+ // Initialize session (used by URI, see issue #3269).
$this->initializeSession($config);
// Initialize URI (uses session, see issue #3269).
diff --git a/system/src/Grav/Common/Processors/PagesProcessor.php b/system/src/Grav/Common/Processors/PagesProcessor.php
index 33b483fbd..470ca907b 100644
--- a/system/src/Grav/Common/Processors/PagesProcessor.php
+++ b/system/src/Grav/Common/Processors/PagesProcessor.php
@@ -10,6 +10,8 @@
namespace Grav\Common\Processors;
use Grav\Common\Page\Interfaces\PageInterface;
+use Grav\Framework\RequestHandler\Exception\RequestException;
+use Grav\Plugin\Form\Forms;
use RocketTheme\Toolbox\Event\Event;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
@@ -47,8 +49,17 @@ class PagesProcessor extends ProcessorBase
$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]);
+ $event = new Event([
+ 'page' => $page,
+ 'code' => $exception->getCode(),
+ 'message' => $exception->getMessage(),
+ 'exception' => $exception,
+ 'route' => $route,
+ 'request' => $request
+ ]);
$event->page = null;
$event = $this->container->fireEvent('onPageNotFound', $event);
@@ -65,12 +76,18 @@ class PagesProcessor extends ProcessorBase
$task = $this->container['task'];
$action = $this->container['action'];
+
+ /** @var Forms $forms */
+ $forms = $this->container['forms'] ?? null;
+ $form = $forms ? $forms->getActiveForm() : null;
+
+ $options = ['page' => $page, 'form' => $form, 'request' => $request];
if ($task) {
- $event = new Event(['task' => $task, 'page' => $page]);
+ $event = new Event(['task' => $task] + $options);
$this->container->fireEvent('onPageTask', $event);
$this->container->fireEvent('onPageTask.' . $task, $event);
} elseif ($action) {
- $event = new Event(['action' => $action, 'page' => $page]);
+ $event = new Event(['action' => $action] + $options);
$this->container->fireEvent('onPageAction', $event);
$this->container->fireEvent('onPageAction.' . $action, $event);
}
diff --git a/system/src/Grav/Common/Processors/RenderProcessor.php b/system/src/Grav/Common/Processors/RenderProcessor.php
index 32f48e7ce..329da22a3 100644
--- a/system/src/Grav/Common/Processors/RenderProcessor.php
+++ b/system/src/Grav/Common/Processors/RenderProcessor.php
@@ -14,6 +14,7 @@ use Grav\Framework\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
+use RocketTheme\Toolbox\Event\Event;
/**
* Class RenderProcessor
@@ -42,23 +43,27 @@ class RenderProcessor extends ProcessorBase
return $output;
}
- ob_start();
+ /** @var PageInterface $page */
+ $page = $this->container['page'];
// Use internal Grav output.
$container->output = $output;
- $container->fireEvent('onOutputGenerated');
+
+ ob_start();
+
+ $event = new Event(['page' => $page, 'output' => &$container->output]);
+ $container->fireEvent('onOutputGenerated', $event);
echo $container->output;
+ $html = ob_get_clean();
+
// remove any output
$container->output = '';
- $this->container->fireEvent('onOutputRendered');
+ $event = new Event(['page' => $page, 'output' => $html]);
+ $this->container->fireEvent('onOutputRendered', $event);
- $html = ob_get_clean();
-
- /** @var PageInterface $page */
- $page = $this->container['page'];
$this->stopTimer();
return new Response($page->httpResponseCode(), $page->httpHeaders(), $html);
diff --git a/system/src/Grav/Common/Scheduler/Job.php b/system/src/Grav/Common/Scheduler/Job.php
index f21c26def..13059a572 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;
};
}
@@ -390,7 +390,9 @@ class Job
if (count($this->outputTo) > 0) {
foreach ($this->outputTo as $file) {
$output_mode = $this->outputMode === 'append' ? FILE_APPEND | LOCK_EX : LOCK_EX;
- file_put_contents($file, $this->output, $output_mode);
+ $timestamp = (new DateTime('now'))->format('c');
+ $output = $timestamp . "\n" . str_pad('', strlen($timestamp), '>') . "\n" . $this->output;
+ file_put_contents($file, $output, $output_mode);
}
}
diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php
index 21d059e23..55aa54593 100644
--- a/system/src/Grav/Common/Security.php
+++ b/system/src/Grav/Common/Security.php
@@ -9,10 +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;
@@ -33,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;
@@ -52,13 +53,20 @@ 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);
- // TODO: what to do with bad SVG files which return false?
- if ($clean_svg !== false && $clean_svg !== $original_svg) {
+ // Quarantine bad SVG files and throw exception
+ if ($clean_svg !== false ) {
file_put_contents($file, $clean_svg);
+ } else {
+ $quarantine_file = basename($file);
+ $quarantine_dir = 'log://quarantine';
+ Folder::mkdir($quarantine_dir);
+ file_put_contents("$quarantine_dir/$quarantine_file", $original_svg);
+ unlink($file);
+ throw new Exception('SVG could not be sanitized, it has been moved to the logs/quarantine folder');
}
}
}
@@ -99,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) {
@@ -130,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);
@@ -140,11 +148,7 @@ class Security
}
}
- if (!empty($list)) {
- return array_merge(...$list);
- }
-
- return $list;
+ return array_merge(...$list);
}
/**
@@ -191,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);
@@ -210,7 +214,7 @@ class Security
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])([\s\/]on|\sxmlns)[a-z].*=>?#iUu',
// Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
- 'invalid_protocols' => '#(' . implode('|', array_map('preg_quote', $invalid_protocols, ['#'])) . '):.*?#iUu',
+ 'invalid_protocols' => '#(' . implode('|', array_map('preg_quote', $invalid_protocols, ['#'])) . '):\S.*?#iUu',
// Match -moz-bindings
'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u',
@@ -231,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 a423f6e1d..b56f62fde 100644
--- a/system/src/Grav/Common/Service/ConfigServiceProvider.php
+++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php
@@ -17,6 +17,7 @@ use Grav\Common\Config\Config;
use Grav\Common\Config\ConfigFileFinder;
use Grav\Common\Config\Setup;
use Grav\Common\Language\Language;
+use Grav\Framework\Mime\MimeTypes;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\File\YamlFile;
@@ -56,6 +57,19 @@ class ConfigServiceProvider implements ServiceProviderInterface
return $config;
};
+ $container['mime'] = function ($c) {
+ /** @var Config $config */
+ $config = $c['config'];
+ $mimes = $config->get('mime.types', []);
+ foreach ($config->get('media.types', []) as $ext => $media) {
+ if (!empty($media['mime'])) {
+ $mimes[$ext] = array_unique(array_merge([$media['mime']], $mimes[$ext] ?? []));
+ }
+ }
+
+ return MimeTypes::createFromMimes($mimes);
+ };
+
$container['languages'] = function ($c) {
return static::languages($c);
};
diff --git a/system/src/Grav/Common/Service/RequestServiceProvider.php b/system/src/Grav/Common/Service/RequestServiceProvider.php
index 271571351..17b3149e5 100644
--- a/system/src/Grav/Common/Service/RequestServiceProvider.php
+++ b/system/src/Grav/Common/Service/RequestServiceProvider.php
@@ -10,6 +10,7 @@
namespace Grav\Common\Service;
use Grav\Common\Uri;
+use JsonException;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
use Pimple\Container;
@@ -18,6 +19,7 @@ use function explode;
use function fopen;
use function function_exists;
use function in_array;
+use function is_array;
use function strtolower;
use function trim;
@@ -51,18 +53,30 @@ class RequestServiceProvider implements ServiceProviderInterface
$headers = function_exists('getallheaders') ? getallheaders() : $creator::getHeadersFromServer($_SERVER);
$post = null;
- if ('POST' === $method) {
+ if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
foreach ($headers as $headerName => $headerValue) {
if ('content-type' !== strtolower($headerName)) {
continue;
}
- if (in_array(
- strtolower(trim(explode(';', $headerValue, 2)[0])),
- ['application/x-www-form-urlencoded', 'multipart/form-data']
- )) {
- $post = $_POST;
- break;
+ $contentType = strtolower(trim(explode(';', $headerValue, 2)[0]));
+ switch ($contentType) {
+ case 'application/x-www-form-urlencoded':
+ case 'multipart/form-data':
+ $post = $_POST;
+ break 2;
+ case 'application/json':
+ case 'application/vnd.api+json':
+ try {
+ $json = file_get_contents('php://input');
+ $post = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
+ if (!is_array($post)) {
+ $post = null;
+ }
+ } catch (JsonException $e) {
+ $post = null;
+ }
+ break 2;
}
}
}
diff --git a/system/src/Grav/Common/Service/TaskServiceProvider.php b/system/src/Grav/Common/Service/TaskServiceProvider.php
index 9afab59a4..49ce147e9 100644
--- a/system/src/Grav/Common/Service/TaskServiceProvider.php
+++ b/system/src/Grav/Common/Service/TaskServiceProvider.php
@@ -12,6 +12,7 @@ namespace Grav\Common\Service;
use Grav\Common\Grav;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
+use Psr\Http\Message\ServerRequestInterface;
/**
* Class TaskServiceProvider
@@ -26,7 +27,11 @@ class TaskServiceProvider implements ServiceProviderInterface
public function register(Container $container)
{
$container['task'] = function (Grav $c) {
- $task = $_POST['task'] ?? $c['uri']->param('task');
+ /** @var ServerRequestInterface $request */
+ $request = $c['request'];
+ $body = $request->getParsedBody();
+
+ $task = $body['task'] ?? $c['uri']->param('task');
if (null !== $task) {
$task = filter_var($task, FILTER_SANITIZE_STRING);
}
@@ -35,7 +40,11 @@ class TaskServiceProvider implements ServiceProviderInterface
};
$container['action'] = function (Grav $c) {
- $action = $_POST['action'] ?? $c['uri']->param('action');
+ /** @var ServerRequestInterface $request */
+ $request = $c['request'];
+ $body = $request->getParsedBody();
+
+ $action = $body['action'] ?? $c['uri']->param('action');
if (null !== $action) {
$action = filter_var($action, FILTER_SANITIZE_STRING);
}
diff --git a/system/src/Grav/Common/Session.php b/system/src/Grav/Common/Session.php
index 8856e7a0a..322162705 100644
--- a/system/src/Grav/Common/Session.php
+++ b/system/src/Grav/Common/Session.php
@@ -12,6 +12,7 @@ namespace Grav\Common;
use Grav\Common\Form\FormFlash;
use Grav\Events\SessionStartEvent;
use Grav\Plugin\Form\Forms;
+use JsonException;
use function is_string;
/**
@@ -128,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;
}
}
@@ -148,10 +149,11 @@ class Session extends \Grav\Framework\Session\Session
* @param mixed $object
* @param int $time
* @return $this
+ * @throws JsonException
*/
public function setFlashCookieObject($name, $object, $time = 60)
{
- setcookie($name, json_encode($object), time() + $time, '/');
+ setcookie($name, json_encode($object, JSON_THROW_ON_ERROR), $this->getCookieOptions($time));
return $this;
}
@@ -161,13 +163,15 @@ class Session extends \Grav\Framework\Session\Session
*
* @param string $name
* @return mixed|null
+ * @throws JsonException
*/
public function getFlashCookieObject($name)
{
if (isset($_COOKIE[$name])) {
- $object = json_decode($_COOKIE[$name], false);
- setcookie($name, '', time() - 3600, '/');
- return $object;
+ $cookie = $_COOKIE[$name];
+ setcookie($name, '', $this->getCookieOptions(-42000));
+
+ return json_decode($cookie, false, 512, JSON_THROW_ON_ERROR);
}
return null;
diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php
index de3c4526c..0d0a450b0 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/Themes.php b/system/src/Grav/Common/Themes.php
index bf155ede5..6126841cf 100644
--- a/system/src/Grav/Common/Themes.php
+++ b/system/src/Grav/Common/Themes.php
@@ -224,28 +224,18 @@ class Themes extends Iterator
$grav = $this->grav;
$config = $this->config;
$name = $this->current();
+ $class = null;
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
- $file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php");
+ // Start by attempting to load the theme.php file.
+ $file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php");
if ($file) {
// Local variables available in the file: $grav, $config, $name, $file
$class = include $file;
-
- if (!$class || !is_subclass_of($class, Plugin::class, true)) {
- $className = Inflector::camelize($name);
- $themeClassFormat = [
- 'Grav\\Theme\\' . $className,
- 'Grav\\Theme\\' . ucfirst($name)
- ];
-
- foreach ($themeClassFormat as $themeClass) {
- if (is_subclass_of($themeClass, Theme::class, true)) {
- $class = new $themeClass($grav, $config, $name);
- break;
- }
- }
+ if (!\is_object($class) || !is_subclass_of($class, Theme::class, true)) {
+ $class = null;
}
} elseif (!$locator('theme://') && !defined('GRAV_CLI')) {
$response = new Response(500, [], "Theme '$name' does not exist, unable to display page.");
@@ -253,12 +243,28 @@ class Themes extends Iterator
$grav->close($response);
}
- $this->config->set('theme', $config->get('themes.' . $name));
+ // If the class hasn't been initialized yet, guess the class name and create a new instance.
+ if (null === $class) {
+ $themeClassFormat = [
+ 'Grav\\Theme\\' . Inflector::camelize($name),
+ 'Grav\\Theme\\' . ucfirst($name)
+ ];
- if (empty($class)) {
+ foreach ($themeClassFormat as $themeClass) {
+ if (is_subclass_of($themeClass, Theme::class, true)) {
+ $class = new $themeClass($grav, $config, $name);
+ break;
+ }
+ }
+ }
+
+ // Finally if everything else fails, just create a new instance from the default Theme class.
+ if (null === $class) {
$class = new Theme($grav, $config, $name);
}
+ $this->config->set('theme', $config->get('themes.' . $name));
+
return $class;
}
diff --git a/system/src/Grav/Common/Twig/Exception/TwigException.php b/system/src/Grav/Common/Twig/Exception/TwigException.php
new file mode 100644
index 000000000..8f543dcc1
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Exception/TwigException.php
@@ -0,0 +1,19 @@
+locator = Grav::instance()['locator'];
+ }
+
+ /**
+ * @return TwigFilter[]
+ */
+ public function getFilters()
+ {
+ return [
+ new TwigFilter('file_exists', [$this, 'file_exists']),
+ new TwigFilter('fileatime', [$this, 'fileatime']),
+ new TwigFilter('filectime', [$this, 'filectime']),
+ new TwigFilter('filemtime', [$this, 'filemtime']),
+ new TwigFilter('filesize', [$this, 'filesize']),
+ new TwigFilter('filetype', [$this, 'filetype']),
+ new TwigFilter('is_dir', [$this, 'is_dir']),
+ new TwigFilter('is_file', [$this, 'is_file']),
+ new TwigFilter('is_link', [$this, 'is_link']),
+ new TwigFilter('is_readable', [$this, 'is_readable']),
+ new TwigFilter('is_writable', [$this, 'is_writable']),
+ new TwigFilter('is_writeable', [$this, 'is_writable']),
+ new TwigFilter('lstat', [$this, 'lstat']),
+ new TwigFilter('getimagesize', [$this, 'getimagesize']),
+ new TwigFilter('exif_read_data', [$this, 'exif_read_data']),
+ new TwigFilter('read_exif_data', [$this, 'exif_read_data']),
+ new TwigFilter('exif_imagetype', [$this, 'exif_imagetype']),
+ new TwigFilter('hash_file', [$this, 'hash_file']),
+ new TwigFilter('hash_hmac_file', [$this, 'hash_hmac_file']),
+ new TwigFilter('md5_file', [$this, 'md5_file']),
+ new TwigFilter('sha1_file', [$this, 'sha1_file']),
+ new TwigFilter('get_meta_tags', [$this, 'get_meta_tags']),
+ new TwigFilter('pathinfo', [$this, 'pathinfo']),
+ ];
+ }
+
+ /**
+ * Return a list of all functions.
+ *
+ * @return TwigFunction[]
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('file_exists', [$this, 'file_exists']),
+ new TwigFunction('fileatime', [$this, 'fileatime']),
+ new TwigFunction('filectime', [$this, 'filectime']),
+ new TwigFunction('filemtime', [$this, 'filemtime']),
+ new TwigFunction('filesize', [$this, 'filesize']),
+ new TwigFunction('filetype', [$this, 'filetype']),
+ new TwigFunction('is_dir', [$this, 'is_dir']),
+ new TwigFunction('is_file', [$this, 'is_file']),
+ new TwigFunction('is_link', [$this, 'is_link']),
+ new TwigFunction('is_readable', [$this, 'is_readable']),
+ new TwigFunction('is_writable', [$this, 'is_writable']),
+ new TwigFunction('is_writeable', [$this, 'is_writable']),
+ new TwigFunction('lstat', [$this, 'lstat']),
+ new TwigFunction('getimagesize', [$this, 'getimagesize']),
+ new TwigFunction('exif_read_data', [$this, 'exif_read_data']),
+ new TwigFunction('read_exif_data', [$this, 'exif_read_data']),
+ new TwigFunction('exif_imagetype', [$this, 'exif_imagetype']),
+ new TwigFunction('hash_file', [$this, 'hash_file']),
+ new TwigFunction('hash_hmac_file', [$this, 'hash_hmac_file']),
+ new TwigFunction('md5_file', [$this, 'md5_file']),
+ new TwigFunction('sha1_file', [$this, 'sha1_file']),
+ new TwigFunction('get_meta_tags', [$this, 'get_meta_tags']),
+ new TwigFunction('pathinfo', [$this, 'pathinfo']),
+ ];
+ }
+
+ /**
+ * @param string $filename
+ * @return bool
+ */
+ public function file_exists($filename): bool
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return file_exists($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return int|false
+ */
+ public function fileatime($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return fileatime($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return int|false
+ */
+ public function filectime($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return filectime($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return int|false
+ */
+ public function filemtime($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return filemtime($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return int|false
+ */
+ public function filesize($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return filesize($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return string|false
+ */
+ public function filetype($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return filetype($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return bool
+ */
+ public function is_dir($filename): bool
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return is_dir($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return bool
+ */
+ public function is_file($filename): bool
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return is_file($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return bool
+ */
+ public function is_link($filename): bool
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return is_link($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return bool
+ */
+ public function is_readable($filename): bool
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return is_readable($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return bool
+ */
+ public function is_writable($filename): bool
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return is_writable($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return array|false
+ */
+ public function lstat($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return lstat($filename);
+ }
+
+ /**
+ * @param string $filename
+ * @return array|false
+ */
+ public function getimagesize($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return getimagesize($filename);
+ }
+
+ /**
+ * @param string $file
+ * @param string|null $required_sections
+ * @param bool $as_arrays
+ * @param bool $read_thumbnail
+ * @return array|false
+ */
+ public function exif_read_data($file, ?string $required_sections, bool $as_arrays = false, bool $read_thumbnail = false)
+ {
+ if (!Utils::functionExists('exif_read_data') || !$this->checkFilename($file)) {
+ return false;
+ }
+
+ return exif_read_data($file, $required_sections, $as_arrays, $read_thumbnail);
+ }
+
+ /**
+ * @param string $filename
+ * @return string|false
+ */
+ public function exif_imagetype($filename)
+ {
+ if (!Utils::functionExists('exif_imagetype') || !$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return @exif_imagetype($filename);
+ }
+
+ /**
+ * @param string $algo
+ * @param string $filename
+ * @param bool $binary
+ * @return string|false
+ */
+ public function hash_file(string $algo, string $filename, bool $binary = false)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return hash_file($algo, $filename, $binary);
+ }
+
+ /**
+ * @param string $algo
+ * @param string $data
+ * @param string $key
+ * @param bool $binary
+ * @return string|false
+ */
+ public function hash_hmac_file(string $algo, string $data, string $key, bool $binary = false)
+ {
+ if (!$this->checkFilename($data)) {
+ return false;
+ }
+
+ return hash_hmac_file($algo, $data, $key, $binary);
+ }
+
+ /**
+ * @param string $filename
+ * @param bool $binary
+ * @return string|false
+ */
+ public function md5_file($filename, bool $binary = false)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return md5_file($filename, $binary);
+ }
+
+ /**
+ * @param string $filename
+ * @param bool $binary
+ * @return string|false
+ */
+ public function sha1_file($filename, bool $binary = false)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return sha1_file($filename, $binary);
+ }
+
+ /**
+ * @param string $filename
+ * @return array|false
+ */
+ public function get_meta_tags($filename)
+ {
+ if (!$this->checkFilename($filename)) {
+ return false;
+ }
+
+ return get_meta_tags($filename);
+ }
+
+ /**
+ * @param string $path
+ * @param int|null $flags
+ * @return string|string[]
+ */
+ public function pathinfo($path, $flags = null)
+ {
+ if (null !== $flags) {
+ return pathinfo($path, (int)$flags);
+ }
+
+ return pathinfo($path);
+ }
+
+ /**
+ * @param string $filename
+ * @return bool
+ */
+ private function checkFilename($filename): bool
+ {
+ return is_string($filename) && (!str_contains($filename, '://') || $this->locator->isStream($filename));
+ }
+}
diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php
new file mode 100644
index 000000000..638617c4d
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php
@@ -0,0 +1,1635 @@
+grav = Grav::instance();
+ $this->debugger = $this->grav['debugger'] ?? null;
+ $this->config = $this->grav['config'];
+ }
+
+ /**
+ * Register some standard globals
+ *
+ * @return array
+ */
+ public function getGlobals(): array
+ {
+ return [
+ 'grav' => $this->grav,
+ ];
+ }
+
+ /**
+ * Return a list of all filters.
+ *
+ * @return array
+ */
+ public function getFilters(): array
+ {
+ return [
+ new TwigFilter('*ize', [$this, 'inflectorFilter']),
+ new TwigFilter('absolute_url', [$this, 'absoluteUrlFilter']),
+ new TwigFilter('contains', [$this, 'containsFilter']),
+ new TwigFilter('chunk_split', [$this, 'chunkSplitFilter']),
+ new TwigFilter('nicenumber', [$this, 'niceNumberFunc']),
+ new TwigFilter('nicefilesize', [$this, 'niceFilesizeFunc']),
+ new TwigFilter('nicetime', [$this, 'nicetimeFunc']),
+ new TwigFilter('defined', [$this, 'definedDefaultFilter']),
+ new TwigFilter('ends_with', [$this, 'endsWithFilter']),
+ new TwigFilter('fieldName', [$this, 'fieldNameFilter']),
+ new TwigFilter('ksort', [$this, 'ksortFilter']),
+ new TwigFilter('ltrim', [$this, 'ltrimFilter']),
+ new TwigFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]),
+ new TwigFilter('md5', [$this, 'md5Filter']),
+ new TwigFilter('base32_encode', [$this, 'base32EncodeFilter']),
+ new TwigFilter('base32_decode', [$this, 'base32DecodeFilter']),
+ new TwigFilter('base64_encode', [$this, 'base64EncodeFilter']),
+ new TwigFilter('base64_decode', [$this, 'base64DecodeFilter']),
+ new TwigFilter('randomize', [$this, 'randomizeFilter']),
+ new TwigFilter('modulus', [$this, 'modulusFilter']),
+ new TwigFilter('rtrim', [$this, 'rtrimFilter']),
+ new TwigFilter('pad', [$this, 'padFilter']),
+ new TwigFilter('regex_replace', [$this, 'regexReplace']),
+ new TwigFilter('safe_email', [$this, 'safeEmailFilter'], ['is_safe' => ['html']]),
+ new TwigFilter('safe_truncate', [Utils::class, 'safeTruncate']),
+ new TwigFilter('safe_truncate_html', [Utils::class, 'safeTruncateHTML']),
+ new TwigFilter('sort_by_key', [$this, 'sortByKeyFilter']),
+ new TwigFilter('starts_with', [$this, 'startsWithFilter']),
+ new TwigFilter('truncate', [Utils::class, 'truncate']),
+ new TwigFilter('truncate_html', [Utils::class, 'truncateHTML']),
+ new TwigFilter('json_decode', [$this, 'jsonDecodeFilter']),
+ new TwigFilter('array_unique', 'array_unique'),
+ new TwigFilter('basename', 'basename'),
+ new TwigFilter('dirname', 'dirname'),
+ new TwigFilter('print_r', [$this, 'print_r']),
+ new TwigFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
+ new TwigFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
+ new TwigFilter('nicecron', [$this, 'niceCronFilter']),
+
+ // Translations
+ new TwigFilter('t', [$this, 'translate'], ['needs_environment' => true]),
+ new TwigFilter('tl', [$this, 'translateLanguage']),
+ new TwigFilter('ta', [$this, 'translateArray']),
+
+ // Casting values
+ new TwigFilter('string', [$this, 'stringFilter']),
+ new TwigFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]),
+ new TwigFilter('bool', [$this, 'boolFilter']),
+ new TwigFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]),
+ new TwigFilter('array', [$this, 'arrayFilter']),
+ new TwigFilter('yaml', [$this, 'yamlFilter']),
+
+ // Object Types
+ new TwigFilter('get_type', [$this, 'getTypeFunc']),
+ new TwigFilter('of_type', [$this, 'ofTypeFunc']),
+
+ // PHP methods
+ new TwigFilter('count', 'count'),
+ new TwigFilter('array_diff', 'array_diff'),
+ ];
+ }
+
+ /**
+ * Return a list of all functions.
+ *
+ * @return array
+ */
+ public function getFunctions(): array
+ {
+ return [
+ new TwigFunction('array', [$this, 'arrayFilter']),
+ new TwigFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
+ new TwigFunction('array_key_exists', 'array_key_exists'),
+ new TwigFunction('array_unique', 'array_unique'),
+ new TwigFunction('array_intersect', [$this, 'arrayIntersectFunc']),
+ new TwigFunction('array_diff', 'array_diff'),
+ new TwigFunction('authorize', [$this, 'authorize']),
+ new TwigFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
+ new TwigFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
+ new TwigFunction('vardump', [$this, 'vardumpFunc']),
+ new TwigFunction('print_r', [$this, 'print_r']),
+ new TwigFunction('http_response_code', 'http_response_code'),
+ new TwigFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]),
+ new TwigFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]),
+ new TwigFunction('gist', [$this, 'gistFunc']),
+ new TwigFunction('nonce_field', [$this, 'nonceFieldFunc']),
+ new TwigFunction('pathinfo', 'pathinfo'),
+ new TwigFunction('random_string', [$this, 'randomStringFunc']),
+ new TwigFunction('repeat', [$this, 'repeatFunc']),
+ new TwigFunction('regex_replace', [$this, 'regexReplace']),
+ new TwigFunction('regex_filter', [$this, 'regexFilter']),
+ new TwigFunction('regex_match', [$this, 'regexMatch']),
+ new TwigFunction('regex_split', [$this, 'regexSplit']),
+ new TwigFunction('string', [$this, 'stringFilter']),
+ new TwigFunction('url', [$this, 'urlFunc']),
+ new TwigFunction('json_decode', [$this, 'jsonDecodeFilter']),
+ new TwigFunction('get_cookie', [$this, 'getCookie']),
+ new TwigFunction('redirect_me', [$this, 'redirectFunc']),
+ new TwigFunction('range', [$this, 'rangeFunc']),
+ new TwigFunction('isajaxrequest', [$this, 'isAjaxFunc']),
+ new TwigFunction('exif', [$this, 'exifFunc']),
+ new TwigFunction('media_directory', [$this, 'mediaDirFunc']),
+ new TwigFunction('body_class', [$this, 'bodyClassFunc'], ['needs_context' => true]),
+ new TwigFunction('theme_var', [$this, 'themeVarFunc'], ['needs_context' => true]),
+ new TwigFunction('header_var', [$this, 'pageHeaderVarFunc'], ['needs_context' => true]),
+ new TwigFunction('read_file', [$this, 'readFileFunc']),
+ new TwigFunction('nicenumber', [$this, 'niceNumberFunc']),
+ new TwigFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
+ new TwigFunction('nicetime', [$this, 'nicetimeFunc']),
+ 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]),
+ new TwigFunction('tl', [$this, 'translateLanguage']),
+ new TwigFunction('ta', [$this, 'translateArray']),
+
+ // Object Types
+ new TwigFunction('get_type', [$this, 'getTypeFunc']),
+ new TwigFunction('of_type', [$this, 'ofTypeFunc']),
+
+ // PHP methods
+ new TwigFunction('is_numeric', 'is_numeric'),
+ new TwigFunction('is_iterable', 'is_iterable'),
+ new TwigFunction('is_countable', 'is_countable'),
+ new TwigFunction('is_null', 'is_null'),
+ new TwigFunction('is_string', 'is_string'),
+ new TwigFunction('is_array', 'is_array'),
+ new TwigFunction('is_object', 'is_object'),
+ new TwigFunction('count', 'count'),
+ new TwigFunction('array_diff', 'array_diff'),
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function getTokenParsers(): array
+ {
+ return [
+ new TwigTokenParserRender(),
+ new TwigTokenParserThrow(),
+ new TwigTokenParserTryCatch(),
+ new TwigTokenParserScript(),
+ new TwigTokenParserStyle(),
+ new TwigTokenParserMarkdown(),
+ new TwigTokenParserSwitch(),
+ new TwigTokenParserCache(),
+ ];
+ }
+
+ public function print_r($var)
+ {
+ return print_r($var, true);
+ }
+
+ /**
+ * Filters field name by changing dot notation into array notation.
+ *
+ * @param string $str
+ * @return string
+ */
+ public function fieldNameFilter($str)
+ {
+ $path = explode('.', rtrim($str, '.'));
+
+ return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : '');
+ }
+
+ /**
+ * Protects email address.
+ *
+ * @param string $str
+ * @return string
+ */
+ public function safeEmailFilter($str)
+ {
+ static $list = [
+ '"' => '"',
+ "'" => ''',
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ '@' => '@'
+ ];
+
+ $characters = mb_str_split($str, 1, 'UTF-8');
+
+ $encoded = '';
+ foreach ($characters as $chr) {
+ $encoded .= $list[$chr] ?? (random_int(0, 1) ? '' . mb_ord($chr) . ';' : $chr);
+ }
+
+ return $encoded;
+ }
+
+ /**
+ * Returns array in a random order.
+ *
+ * @param array|Traversable $original
+ * @param int $offset Can be used to return only slice of the array.
+ * @return array
+ */
+ public function randomizeFilter($original, $offset = 0)
+ {
+ if ($original instanceof Traversable) {
+ $original = iterator_to_array($original, false);
+ }
+
+ if (!is_array($original)) {
+ return $original;
+ }
+
+ $sorted = [];
+ $random = array_slice($original, $offset);
+ shuffle($random);
+
+ $sizeOf = count($original);
+ for ($x = 0; $x < $sizeOf; $x++) {
+ if ($x < $offset) {
+ $sorted[] = $original[$x];
+ } else {
+ $sorted[] = array_shift($random);
+ }
+ }
+
+ return $sorted;
+ }
+
+ /**
+ * Returns the modulus of an integer
+ *
+ * @param string|int $number
+ * @param int $divider
+ * @param array|null $items array of items to select from to return
+ * @return int
+ */
+ public function modulusFilter($number, $divider, $items = null)
+ {
+ if (is_string($number)) {
+ $number = strlen($number);
+ }
+
+ $remainder = $number % $divider;
+
+ if (is_array($items)) {
+ return $items[$remainder] ?? $items[0];
+ }
+
+ return $remainder;
+ }
+
+ /**
+ * Inflector supports following notations:
+ *
+ * `{{ 'person'|pluralize }} => people`
+ * `{{ 'shoes'|singularize }} => shoe`
+ * `{{ 'welcome page'|titleize }} => "Welcome Page"`
+ * `{{ 'send_email'|camelize }} => SendEmail`
+ * `{{ 'CamelCased'|underscorize }} => camel_cased`
+ * `{{ 'Something Text'|hyphenize }} => something-text`
+ * `{{ 'something_text_to_read'|humanize }} => "Something text to read"`
+ * `{{ '181'|monthize }} => 5`
+ * `{{ '10'|ordinalize }} => 10th`
+ *
+ * @param string $action
+ * @param string $data
+ * @param int|null $count
+ * @return string
+ */
+ public function inflectorFilter($action, $data, $count = null)
+ {
+ $action .= 'ize';
+
+ /** @var Inflector $inflector */
+ $inflector = $this->grav['inflector'];
+
+ if (in_array(
+ $action,
+ ['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'],
+ true
+ )) {
+ return $inflector->{$action}($data);
+ }
+
+ if (in_array($action, ['pluralize', 'singularize'], true)) {
+ return $count ? $inflector->{$action}($data, $count) : $inflector->{$action}($data);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Return MD5 hash from the input.
+ *
+ * @param string $str
+ * @return string
+ */
+ public function md5Filter($str)
+ {
+ return md5($str);
+ }
+
+ /**
+ * Return Base32 encoded string
+ *
+ * @param string $str
+ * @return string
+ */
+ public function base32EncodeFilter($str)
+ {
+ return Base32::encode($str);
+ }
+
+ /**
+ * Return Base32 decoded string
+ *
+ * @param string $str
+ * @return string
+ */
+ public function base32DecodeFilter($str)
+ {
+ return Base32::decode($str);
+ }
+
+ /**
+ * Return Base64 encoded string
+ *
+ * @param string $str
+ * @return string
+ */
+ public function base64EncodeFilter($str)
+ {
+ return base64_encode($str);
+ }
+
+ /**
+ * Return Base64 decoded string
+ *
+ * @param string $str
+ * @return string|false
+ */
+ public function base64DecodeFilter($str)
+ {
+ return base64_decode($str);
+ }
+
+ /**
+ * Sorts a collection by key
+ *
+ * @param array $input
+ * @param string $filter
+ * @param int $direction
+ * @param int $sort_flags
+ * @return array
+ */
+ public function sortByKeyFilter($input, $filter, $direction = SORT_ASC, $sort_flags = SORT_REGULAR)
+ {
+ return Utils::sortArrayByKey($input, $filter, $direction, $sort_flags);
+ }
+
+ /**
+ * Return ksorted collection.
+ *
+ * @param array|null $array
+ * @return array
+ */
+ public function ksortFilter($array)
+ {
+ if (null === $array) {
+ $array = [];
+ }
+ ksort($array);
+
+ return $array;
+ }
+
+ /**
+ * Wrapper for chunk_split() function
+ *
+ * @param string $value
+ * @param int $chars
+ * @param string $split
+ * @return string
+ */
+ public function chunkSplitFilter($value, $chars, $split = '-')
+ {
+ return chunk_split($value, $chars, $split);
+ }
+
+ /**
+ * determine if a string contains another
+ *
+ * @param string $haystack
+ * @param string $needle
+ * @return string|bool
+ * @todo returning $haystack here doesn't make much sense
+ */
+ public function containsFilter($haystack, $needle)
+ {
+ if (empty($needle)) {
+ return $haystack;
+ }
+
+ return (strpos($haystack, (string) $needle) !== false);
+ }
+
+ /**
+ * Gets a human readable output for cron syntax
+ *
+ * @param string $at
+ * @return string
+ */
+ public function niceCronFilter($at)
+ {
+ $cron = new Cron($at);
+ return $cron->getText('en');
+ }
+
+ /**
+ * Get Cron object for a crontab 'at' format
+ *
+ * @param string $at
+ * @return CronExpression
+ */
+ public function cronFunc($at)
+ {
+ return CronExpression::factory($at);
+ }
+
+ /**
+ * displays a facebook style 'time ago' formatted date/time
+ *
+ * @param string $date
+ * @param bool $long_strings
+ * @param bool $show_tense
+ * @return string
+ */
+ public function nicetimeFunc($date, $long_strings = true, $show_tense = true)
+ {
+ if (empty($date)) {
+ return $this->grav['language']->translate('GRAV.NICETIME.NO_DATE_PROVIDED');
+ }
+
+ if ($long_strings) {
+ $periods = [
+ 'NICETIME.SECOND',
+ 'NICETIME.MINUTE',
+ 'NICETIME.HOUR',
+ 'NICETIME.DAY',
+ 'NICETIME.WEEK',
+ 'NICETIME.MONTH',
+ 'NICETIME.YEAR',
+ 'NICETIME.DECADE'
+ ];
+ } else {
+ $periods = [
+ 'NICETIME.SEC',
+ 'NICETIME.MIN',
+ 'NICETIME.HR',
+ 'NICETIME.DAY',
+ 'NICETIME.WK',
+ 'NICETIME.MO',
+ 'NICETIME.YR',
+ 'NICETIME.DEC'
+ ];
+ }
+
+ $lengths = ['60', '60', '24', '7', '4.35', '12', '10'];
+
+ $now = time();
+
+ // check if unix timestamp
+ if ((string)(int)$date === (string)$date) {
+ $unix_date = $date;
+ } else {
+ $unix_date = strtotime($date);
+ }
+
+ // check validity of date
+ if (empty($unix_date)) {
+ return $this->grav['language']->translate('GRAV.NICETIME.BAD_DATE');
+ }
+
+ // is it future date or past date
+ if ($now > $unix_date) {
+ $difference = $now - $unix_date;
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.AGO');
+ } elseif ($now == $unix_date) {
+ $difference = $now - $unix_date;
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.JUST_NOW');
+ } else {
+ $difference = $unix_date - $now;
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.FROM_NOW');
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ if ($difference != 1) {
+ $periods[$j] .= '_PLURAL';
+ }
+
+ if ($this->grav['language']->getTranslation(
+ $this->grav['language']->getLanguage(),
+ $periods[$j] . '_MORE_THAN_TWO'
+ )
+ ) {
+ if ($difference > 2) {
+ $periods[$j] .= '_MORE_THAN_TWO';
+ }
+ }
+
+ $periods[$j] = $this->grav['language']->translate('GRAV.'.$periods[$j]);
+
+ if ($now == $unix_date) {
+ return $tense;
+ }
+
+ $time = "{$difference} {$periods[$j]}";
+ $time .= $show_tense ? " {$tense}" : '';
+
+ return $time;
+ }
+
+ /**
+ * Allow quick check of a string for XSS Vulnerabilities
+ *
+ * @param string|array $data
+ * @return bool|string|array
+ */
+ public function xssFunc($data)
+ {
+ if (!is_array($data)) {
+ return Security::detectXss($data);
+ }
+
+ $results = Security::detectXssFromArray($data);
+ $results_parts = array_map(static function ($value, $key) {
+ return $key.': \''.$value . '\'';
+ }, array_values($results), array_keys($results));
+
+ 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
+ */
+ public function absoluteUrlFilter($string)
+ {
+ $url = $this->grav['uri']->base();
+ $string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string);
+
+ return $string;
+ }
+
+ /**
+ * @param array $context
+ * @param string $string
+ * @param bool $block Block or Line processing
+ * @return string
+ */
+ public function markdownFunction($context, $string, $block = true)
+ {
+ $page = $context['page'] ?? null;
+ return Utils::processMarkdown($string, $block, $page);
+ }
+
+ /**
+ * @param string $haystack
+ * @param string $needle
+ * @return bool
+ */
+ public function startsWithFilter($haystack, $needle)
+ {
+ return Utils::startsWith($haystack, $needle);
+ }
+
+ /**
+ * @param string $haystack
+ * @param string $needle
+ * @return bool
+ */
+ public function endsWithFilter($haystack, $needle)
+ {
+ return Utils::endsWith($haystack, $needle);
+ }
+
+ /**
+ * @param mixed $value
+ * @param null $default
+ * @return mixed|null
+ */
+ public function definedDefaultFilter($value, $default = null)
+ {
+ return $value ?? $default;
+ }
+
+ /**
+ * @param string $value
+ * @param string|null $chars
+ * @return string
+ */
+ public function rtrimFilter($value, $chars = null)
+ {
+ return null !== $chars ? rtrim($value, $chars) : rtrim($value);
+ }
+
+ /**
+ * @param string $value
+ * @param string|null $chars
+ * @return string
+ */
+ public function ltrimFilter($value, $chars = null)
+ {
+ return null !== $chars ? ltrim($value, $chars) : ltrim($value);
+ }
+
+ /**
+ * Returns a string from a value. If the value is array, return it json encoded
+ *
+ * @param mixed $value
+ * @return string
+ */
+ public function stringFilter($value)
+ {
+ // Format the array as a string
+ if (is_array($value)) {
+ return json_encode($value);
+ }
+
+ // Boolean becomes '1' or '0'
+ if (is_bool($value)) {
+ $value = (int)$value;
+ }
+
+ // Cast the other values to string.
+ return (string)$value;
+ }
+
+ /**
+ * Casts input to int.
+ *
+ * @param mixed $input
+ * @return int
+ */
+ public function intFilter($input)
+ {
+ return (int) $input;
+ }
+
+ /**
+ * Casts input to bool.
+ *
+ * @param mixed $input
+ * @return bool
+ */
+ public function boolFilter($input)
+ {
+ return (bool) $input;
+ }
+
+ /**
+ * Casts input to float.
+ *
+ * @param mixed $input
+ * @return float
+ */
+ public function floatFilter($input)
+ {
+ return (float) $input;
+ }
+
+ /**
+ * Casts input to array.
+ *
+ * @param mixed $input
+ * @return array
+ */
+ public function arrayFilter($input)
+ {
+ if (is_array($input)) {
+ return $input;
+ }
+
+ if (is_object($input)) {
+ if (method_exists($input, 'toArray')) {
+ return $input->toArray();
+ }
+
+ if ($input instanceof Iterator) {
+ return iterator_to_array($input);
+ }
+ }
+
+ return (array)$input;
+ }
+
+ /**
+ * @param array|object $value
+ * @param int|null $inline
+ * @param int|null $indent
+ * @return string
+ */
+ public function yamlFilter($value, $inline = null, $indent = null): string
+ {
+ return Yaml::dump($value, $inline, $indent);
+ }
+
+ /**
+ * @param Environment $twig
+ * @return string
+ */
+ public function translate(Environment $twig, ...$args)
+ {
+ // If admin and tu filter provided, use it
+ if (isset($this->grav['admin'])) {
+ $numargs = count($args);
+ $lang = null;
+
+ if (($numargs === 3 && is_array($args[1])) || ($numargs === 2 && !is_array($args[1]))) {
+ $lang = array_pop($args);
+ } elseif ($numargs === 2 && is_array($args[1])) {
+ $subs = array_pop($args);
+ $args = array_merge($args, $subs);
+ }
+
+ return $this->grav['admin']->translate($args, $lang);
+ }
+
+ // else use the default grav translate functionality
+ return $this->grav['language']->translate($args);
+ }
+
+ /**
+ * Translate Strings
+ *
+ * @param string|array $args
+ * @param array|null $languages
+ * @param bool $array_support
+ * @param bool $html_out
+ * @return string
+ */
+ public function translateLanguage($args, array $languages = null, $array_support = false, $html_out = false)
+ {
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ return $language->translate($args, $languages, $array_support, $html_out);
+ }
+
+ /**
+ * @param string $key
+ * @param string $index
+ * @param array|null $lang
+ * @return string
+ */
+ public function translateArray($key, $index, $lang = null)
+ {
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ return $language->translateArray($key, $index, $lang);
+ }
+
+ /**
+ * Repeat given string x times.
+ *
+ * @param string $input
+ * @param int $multiplier
+ *
+ * @return string
+ */
+ public function repeatFunc($input, $multiplier)
+ {
+ return str_repeat($input, $multiplier);
+ }
+
+ /**
+ * Return URL to the resource.
+ *
+ * @example {{ url('theme://images/logo.png')|default('http://www.placehold.it/150x100/f4f4f4') }}
+ *
+ * @param string $input Resource to be located.
+ * @param bool $domain True to include domain name.
+ * @param bool $failGracefully If true, return URL even if the file does not exist.
+ * @return string|false Returns url to the resource or null if resource was not found.
+ */
+ public function urlFunc($input, $domain = false, $failGracefully = false)
+ {
+ return Utils::url($input, $domain, $failGracefully);
+ }
+
+ /**
+ * This function will evaluate Twig $twig through the $environment, and return its results.
+ *
+ * @param array $context
+ * @param string $twig
+ * @return mixed
+ */
+ public function evaluateTwigFunc($context, $twig)
+ {
+
+ $loader = new FilesystemLoader('.');
+ $env = new Environment($loader);
+ $env->addExtension($this);
+
+ $template = $env->createTemplate($twig);
+
+ return $template->render($context);
+ }
+
+ /**
+ * This function will evaluate a $string through the $environment, and return its results.
+ *
+ * @param array $context
+ * @param string $string
+ * @return mixed
+ */
+ public function evaluateStringFunc($context, $string)
+ {
+ return $this->evaluateTwigFunc($context, "{{ $string }}");
+ }
+
+ /**
+ * Based on Twig\Extension\Debug / twig_var_dump
+ * (c) 2011 Fabien Potencier
+ *
+ * @param Environment $env
+ * @param array $context
+ */
+ public function dump(Environment $env, $context)
+ {
+ if (!$env->isDebug() || !$this->debugger) {
+ return;
+ }
+
+ $count = func_num_args();
+ if (2 === $count) {
+ $data = [];
+ foreach ($context as $key => $value) {
+ if (is_object($value)) {
+ if (method_exists($value, 'toArray')) {
+ $data[$key] = $value->toArray();
+ } else {
+ $data[$key] = 'Object (' . get_class($value) . ')';
+ }
+ } else {
+ $data[$key] = $value;
+ }
+ }
+ $this->debugger->addMessage($data, 'debug');
+ } else {
+ for ($i = 2; $i < $count; $i++) {
+ $var = func_get_arg($i);
+ $this->debugger->addMessage($var, 'debug');
+ }
+ }
+ }
+
+ /**
+ * Output a Gist
+ *
+ * @param string $id
+ * @param string|false $file
+ * @return string
+ */
+ public function gistFunc($id, $file = false)
+ {
+ $url = 'https://gist.github.com/' . $id . '.js';
+ if ($file) {
+ $url .= '?file=' . $file;
+ }
+ return '';
+ }
+
+ /**
+ * Generate a random string
+ *
+ * @param int $count
+ * @return string
+ */
+ public function randomStringFunc($count = 5)
+ {
+ return Utils::generateRandomString($count);
+ }
+
+ /**
+ * Pad a string to a certain length with another string
+ *
+ * @param string $input
+ * @param int $pad_length
+ * @param string $pad_string
+ * @param int $pad_type
+ * @return string
+ */
+ public static function padFilter($input, $pad_length, $pad_string = ' ', $pad_type = STR_PAD_RIGHT)
+ {
+ return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
+ }
+
+ /**
+ * Workaround for twig associative array initialization
+ * Returns a key => val array
+ *
+ * @param string $key key of item
+ * @param string $val value of item
+ * @param array|null $current_array optional array to add to
+ * @return array
+ */
+ public function arrayKeyValueFunc($key, $val, $current_array = null)
+ {
+ if (empty($current_array)) {
+ return array($key => $val);
+ }
+
+ $current_array[$key] = $val;
+
+ return $current_array;
+ }
+
+ /**
+ * Wrapper for array_intersect() method
+ *
+ * @param array|Collection $array1
+ * @param array|Collection $array2
+ * @return array|Collection
+ */
+ public function arrayIntersectFunc($array1, $array2)
+ {
+ if ($array1 instanceof Collection && $array2 instanceof Collection) {
+ return $array1->intersect($array2)->toArray();
+ }
+
+ return array_intersect($array1, $array2);
+ }
+
+ /**
+ * Translate a string
+ *
+ * @return string
+ */
+ public function translateFunc()
+ {
+ return $this->grav['language']->translate(func_get_args());
+ }
+
+ /**
+ * Authorize an action. Returns true if the user is logged in and
+ * has the right to execute $action.
+ *
+ * @param string|array $action An action or a list of actions. Each
+ * entry can be a string like 'group.action'
+ * or without dot notation an associative
+ * array.
+ * @return bool Returns TRUE if the user is authorized to
+ * perform the action, FALSE otherwise.
+ */
+ public function authorize($action)
+ {
+ // Admin can use Flex users even if the site does not; make sure we use the right version of the user.
+ $admin = $this->grav['admin'] ?? null;
+ if ($admin) {
+ $user = $admin->user;
+ } else {
+ /** @var UserInterface|null $user */
+ $user = $this->grav['user'] ?? null;
+ }
+
+ if (!$user) {
+ return false;
+ }
+
+ if (is_array($action)) {
+ if (Utils::isAssoc($action)) {
+ // Handle nested access structure.
+ $actions = Utils::arrayFlattenDotNotation($action);
+ } else {
+ // Handle simple access list.
+ $actions = array_combine($action, array_fill(0, count($action), true));
+ }
+ } else {
+ // Handle single action.
+ $actions = [(string)$action => true];
+ }
+
+ $count = count($actions);
+ foreach ($actions as $act => $authenticated) {
+ // Ignore 'admin.super' if it's not the only value to be checked.
+ if ($act === 'admin.super' && $count > 1 && $user instanceof FlexObjectInterface) {
+ continue;
+ }
+
+ $auth = $user->authorize($act) ?? false;
+ if (is_bool($auth) && $auth === Utils::isPositive($authenticated)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action.
+ *
+ * For maximum protection, ensure that the string representing the action is as specific as possible
+ *
+ * @param string $action the action
+ * @param string $nonceParamName a custom nonce param name
+ * @return string the nonce input field
+ */
+ public function nonceFieldFunc($action, $nonceParamName = 'nonce')
+ {
+ $string = '';
+
+ return $string;
+ }
+
+ /**
+ * Decodes string from JSON.
+ *
+ * @param string $str
+ * @param bool $assoc
+ * @param int $depth
+ * @param int $options
+ * @return array
+ */
+ public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0)
+ {
+ return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options);
+ }
+
+ /**
+ * Used to retrieve a cookie value
+ *
+ * @param string $key The cookie name to retrieve
+ * @return string
+ */
+ public function getCookie($key)
+ {
+ return filter_input(INPUT_COOKIE, $key, FILTER_SANITIZE_STRING);
+ }
+
+ /**
+ * Twig wrapper for PHP's preg_replace method
+ *
+ * @param string|string[] $subject the content to perform the replacement on
+ * @param string|string[] $pattern the regex pattern to use for matches
+ * @param string|string[] $replace the replacement value either as a string or an array of replacements
+ * @param int $limit the maximum possible replacements for each pattern in each subject
+ * @return string|string[]|null the resulting content
+ */
+ public function regexReplace($subject, $pattern, $replace, $limit = -1)
+ {
+ return preg_replace($pattern, $replace, $subject, $limit);
+ }
+
+ /**
+ * Twig wrapper for PHP's preg_grep method
+ *
+ * @param array $array
+ * @param string $regex
+ * @param int $flags
+ * @return array
+ */
+ public function regexFilter($array, $regex, $flags = 0)
+ {
+ return preg_grep($regex, $array, $flags);
+ }
+
+ /**
+ * Twig wrapper for PHP's preg_match method
+ *
+ * @param string $subject the content to perform the match on
+ * @param string $pattern the regex pattern to use for match
+ * @param int $flags
+ * @param int $offset
+ * @return array|false returns the matches if there is at least one match in the subject for a given pattern or null if not.
+ */
+ public function regexMatch($subject, $pattern, $flags = 0, $offset = 0)
+ {
+ if (preg_match($pattern, $subject, $matches, $flags, $offset) === false) {
+ return false;
+ }
+
+ return $matches;
+ }
+
+ /**
+ * Twig wrapper for PHP's preg_split method
+ *
+ * @param string $subject the content to perform the split on
+ * @param string $pattern the regex pattern to use for split
+ * @param int $limit the maximum possible splits for the given pattern
+ * @param int $flags
+ * @return array|false the resulting array after performing the split operation
+ */
+ public function regexSplit($subject, $pattern, $limit = -1, $flags = 0)
+ {
+ return preg_split($pattern, $subject, $limit, $flags);
+ }
+
+ /**
+ * redirect browser from twig
+ *
+ * @param string $url the url to redirect to
+ * @param int $statusCode statusCode, default 303
+ * @return void
+ */
+ public function redirectFunc($url, $statusCode = 303)
+ {
+ $response = new Response($statusCode, ['location' => $url]);
+
+ $this->grav->close($response);
+ }
+
+ /**
+ * Generates an array containing a range of elements, optionally stepped
+ *
+ * @param int $start Minimum number, default 0
+ * @param int $end Maximum number, default `getrandmax()`
+ * @param int $step Increment between elements in the sequence, default 1
+ * @return array
+ */
+ public function rangeFunc($start = 0, $end = 100, $step = 1)
+ {
+ return range($start, $end, $step);
+ }
+
+ /**
+ * Check if HTTP_X_REQUESTED_WITH has been set to xmlhttprequest,
+ * in which case we may unsafely assume ajax. Non critical use only.
+ *
+ * @return bool True if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest
+ */
+ public function isAjaxFunc()
+ {
+ return (
+ !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
+ && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
+ }
+
+ /**
+ * Get the Exif data for a file
+ *
+ * @param string $image
+ * @param bool $raw
+ * @return mixed
+ */
+ public function exifFunc($image, $raw = false)
+ {
+ if (isset($this->grav['exif'])) {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ if ($locator->isStream($image)) {
+ $image = $locator->findResource($image);
+ }
+
+ $exif_reader = $this->grav['exif']->getReader();
+
+ if ($image && file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
+ $exif_data = $exif_reader->read($image);
+
+ if ($exif_data) {
+ if ($raw) {
+ return $exif_data->getRawData();
+ }
+
+ return $exif_data->getData();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Simple function to read a file based on a filepath and output it
+ *
+ * @param string $filepath
+ * @return bool|string
+ */
+ public function readFileFunc($filepath)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ if ($locator->isStream($filepath)) {
+ $filepath = $locator->findResource($filepath);
+ }
+
+ if ($filepath && file_exists($filepath)) {
+ return file_get_contents($filepath);
+ }
+
+ return false;
+ }
+
+ /**
+ * Process a folder as Media and return a media object
+ *
+ * @param string $media_dir
+ * @return Media|null
+ */
+ public function mediaDirFunc($media_dir)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ if ($locator->isStream($media_dir)) {
+ $media_dir = $locator->findResource($media_dir);
+ }
+
+ if ($media_dir && file_exists($media_dir)) {
+ return new Media($media_dir);
+ }
+
+ return null;
+ }
+
+ /**
+ * Dump a variable to the browser
+ *
+ * @param mixed $var
+ * @return void
+ */
+ public function vardumpFunc($var)
+ {
+ var_dump($var);
+ }
+
+ /**
+ * Returns a nicer more readable filesize based on bytes
+ *
+ * @param int $bytes
+ * @return string
+ */
+ public function niceFilesizeFunc($bytes)
+ {
+ return Utils::prettySize($bytes);
+ }
+
+ /**
+ * Returns a nicer more readable number
+ *
+ * @param int|float|string $n
+ * @return string|bool
+ */
+ public function niceNumberFunc($n)
+ {
+ if (!is_float($n) && !is_int($n)) {
+ if (!is_string($n) || $n === '') {
+ return false;
+ }
+
+ // Strip any thousand formatting and find the first number.
+ $list = array_filter(preg_split("/\D+/", str_replace(',', '', $n)));
+ $n = reset($list);
+
+ if (!is_numeric($n)) {
+ return false;
+ }
+
+ $n = (float)$n;
+ }
+
+ // now filter it;
+ if ($n > 1000000000000) {
+ return round($n/1000000000000, 2).' t';
+ }
+ if ($n > 1000000000) {
+ return round($n/1000000000, 2).' b';
+ }
+ if ($n > 1000000) {
+ return round($n/1000000, 2).' m';
+ }
+ if ($n > 1000) {
+ return round($n/1000, 2).' k';
+ }
+
+ return number_format($n);
+ }
+
+ /**
+ * Get a theme variable
+ * Will try to get the variable for the current page, if not found, it tries it's parent page on up to root.
+ * If still not found, will use the theme's configuration value,
+ * If still not found, will use the $default value passed in
+ *
+ * @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 bool $exists toggle to simply return the page where the variable is set, else null
+ * @return mixed
+ */
+ public function themeVarFunc($context, $var, $default = null, $page = null, $exists = false)
+ {
+ $page = $page ?? $context['page'] ?? Grav::instance()['page'] ?? null;
+
+ // Try to find var in the page headers
+ if ($page instanceof PageInterface && $page->exists()) {
+ // Loop over pages and look for header vars
+ while ($page && !$page->root()) {
+ $header = new Data((array)$page->header());
+ $value = $header->get($var);
+ if (isset($value)) {
+ if ($exists) {
+ return $page;
+ }
+
+ return $value;
+ }
+ $page = $page->parent();
+ }
+ }
+
+ if ($exists) {
+ return false;
+ }
+
+ return Grav::instance()['config']->get('theme.' . $var, $default);
+ }
+
+ /**
+ * Look for a page header variable in an array of pages working its way through until a value is found
+ *
+ * @param array $context
+ * @param string $var the variable to look for in the page header
+ * @param string|string[]|null $pages array of pages to check (current page upwards if not null)
+ * @return mixed
+ * @deprecated 1.7 Use themeVarFunc() instead
+ */
+ public function pageHeaderVarFunc($context, $var, $pages = null)
+ {
+ if (is_array($pages)) {
+ $page = array_shift($pages);
+ } else {
+ $page = null;
+ }
+ return $this->themeVarFunc($context, $var, null, $page);
+ }
+
+ /**
+ * takes an array of classes, and if they are not set on body_classes
+ * look to see if they are set in theme config
+ *
+ * @param array $context
+ * @param string|string[] $classes
+ * @return string
+ */
+ public function bodyClassFunc($context, $classes)
+ {
+
+ $header = $context['page']->header();
+ $body_classes = $header->body_classes ?? '';
+
+ foreach ((array)$classes as $class) {
+ if (!empty($body_classes) && Utils::contains($body_classes, $class)) {
+ continue;
+ }
+
+ $val = $this->config->get('theme.' . $class, false) ? $class : false;
+ $body_classes .= $val ? ' ' . $val : '';
+ }
+
+ return $body_classes;
+ }
+
+ /**
+ * Returns the content of an SVG image and adds extra classes as needed
+ *
+ * @param string $path
+ * @param string|null $classes
+ * @return string|string[]|null
+ */
+ public static function svgImageFunction($path, $classes = null, $strip_style = false)
+ {
+ $path = Utils::fullPath($path);
+
+ $classes = $classes ?: '';
+
+ if (file_exists($path) && !is_dir($path)) {
+ $svg = file_get_contents($path);
+ $classes = " inline-block $classes";
+ $matched = false;
+
+ //Remove xml tag if it exists
+ $svg = preg_replace('/^<\?xml.*\?>/','', $svg);
+
+ //Strip style if needed
+ if ($strip_style) {
+ $svg = preg_replace('/