mirror of
https://github.com/getgrav/grav.git
synced 2025-02-20 19:56:53 +01:00
Merge branch 'develop' into feature/watermark
This commit is contained in:
commit
883c935e05
|
|
@ -13,5 +13,5 @@ indent_size = 4
|
|||
trim_trailing_whitespace = true
|
||||
|
||||
# 2 space indentation
|
||||
[*.{yaml,yml}]
|
||||
[*.{yaml,yml,vue,js,css}]
|
||||
indent_size = 2
|
||||
|
|
|
|||
17
.github/workflows/build.yaml
vendored
17
.github/workflows/build.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
1
.github/workflows/tests.yaml
vendored
1
.github/workflows/tests.yaml
vendored
|
|
@ -25,6 +25,7 @@ jobs:
|
|||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: opcache, gd
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
env:
|
||||
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -45,3 +45,4 @@ tests/cache/*
|
|||
tests/error.log
|
||||
system/templates/testing/*
|
||||
/user/config/versions.yaml
|
||||
/user/cli/config/security.yaml
|
||||
|
|
|
|||
191
CHANGELOG.md
191
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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
1147
composer.lock
generated
1147
composer.lock
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -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: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: '* * * * *'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -184,9 +184,9 @@ config:
|
|||
# Fields to be searched
|
||||
fields:
|
||||
- key
|
||||
- slug
|
||||
- menu
|
||||
- title
|
||||
- name
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ config:
|
|||
configure:
|
||||
path: '/accounts/configure'
|
||||
redirects:
|
||||
'/groups': '/accounts/groups'
|
||||
'/accounts': '/accounts/groups'
|
||||
|
||||
# Permissions
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ form:
|
|||
underline: true
|
||||
|
||||
folder:
|
||||
type: text
|
||||
type: folder-slug
|
||||
label: PLUGIN_ADMIN.FOLDER_NAME
|
||||
validate:
|
||||
rule: slug
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1986
system/config/mime.yaml
Normal file
1986
system/config/mime.yaml
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Core
|
||||
*
|
||||
|
|
@ -8,7 +9,7 @@
|
|||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.7.10');
|
||||
define('GRAV_VERSION', '1.7.23');
|
||||
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
|
||||
define('GRAV_TESTING', false);
|
||||
|
||||
|
|
@ -22,59 +23,68 @@ if (!defined('DS')) {
|
|||
define('DS', '/');
|
||||
}
|
||||
|
||||
// Directories and Paths
|
||||
// Absolute path to Grav root. This is where Grav is installed into.
|
||||
if (!defined('GRAV_ROOT')) {
|
||||
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, getenv('GRAV_ROOT') ?: getcwd()), DS);
|
||||
define('GRAV_ROOT', $path);
|
||||
}
|
||||
// Absolute path to Grav webroot. This is the path where your site is located in.
|
||||
if (!defined('GRAV_WEBROOT')) {
|
||||
$path = rtrim(getenv('GRAV_WEBROOT') ?: GRAV_ROOT, DS);
|
||||
define('GRAV_WEBROOT', $path);
|
||||
}
|
||||
// Relative path to user folder. This path needs to be located under GRAV_WEBROOT.
|
||||
if (!defined('GRAV_USER_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_USER_PATH') ?: 'user', DS);
|
||||
define('GRAV_USER_PATH', $path);
|
||||
}
|
||||
// Absolute or relative path to system folder. Defaults to GRAV_ROOT/system
|
||||
// If system folder is outside of webroot, see https://github.com/getgrav/grav/issues/3297#issuecomment-810294972
|
||||
if (!defined('GRAV_SYSTEM_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_SYSTEM_PATH') ?: 'system', DS);
|
||||
define('GRAV_SYSTEM_PATH', $path);
|
||||
}
|
||||
// Absolute or relative path to cache folder. Defaults to GRAV_ROOT/cache
|
||||
if (!defined('GRAV_CACHE_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_CACHE_PATH') ?: 'cache', DS);
|
||||
define('GRAV_CACHE_PATH', $path);
|
||||
}
|
||||
// Absolute or relative path to logs folder. Defaults to GRAV_ROOT/logs
|
||||
if (!defined('GRAV_LOG_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_LOG_PATH') ?: 'logs', DS);
|
||||
define('GRAV_LOG_PATH', $path);
|
||||
}
|
||||
// Absolute or relative path to tmp folder. Defaults to GRAV_ROOT/tmp
|
||||
if (!defined('GRAV_TMP_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_TMP_PATH') ?: 'tmp', DS);
|
||||
define('GRAV_TMP_PATH', $path);
|
||||
}
|
||||
// Absolute or relative path to backup folder. Defaults to GRAV_ROOT/backup
|
||||
if (!defined('GRAV_BACKUP_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_BACKUP_PATH') ?: 'backup', DS);
|
||||
define('GRAV_BACKUP_PATH', $path);
|
||||
}
|
||||
unset($path);
|
||||
|
||||
define('USER_PATH', GRAV_USER_PATH . DS);
|
||||
define('CACHE_PATH', GRAV_CACHE_PATH . DS);
|
||||
define('ROOT_DIR', GRAV_ROOT . DS);
|
||||
define('USER_DIR', GRAV_WEBROOT . '/' . USER_PATH);
|
||||
define('CACHE_DIR', (!str_starts_with(CACHE_PATH, '/') ? ROOT_DIR : '') . CACHE_PATH);
|
||||
// INTERNAL: Do not use!
|
||||
define('USER_DIR', GRAV_WEBROOT . '/' . GRAV_USER_PATH . '/');
|
||||
define('CACHE_DIR', (!preg_match('`^(/|[a-z]:[\\\/])`ui', GRAV_CACHE_PATH) ? GRAV_ROOT . '/' : '') . GRAV_CACHE_PATH . '/');
|
||||
|
||||
// DEPRECATED: Do not use!
|
||||
define('CACHE_PATH', GRAV_CACHE_PATH . DS);
|
||||
define('USER_PATH', GRAV_USER_PATH . DS);
|
||||
define('ROOT_DIR', GRAV_ROOT . DS);
|
||||
define('ASSETS_DIR', GRAV_WEBROOT . '/assets/');
|
||||
define('IMAGES_DIR', GRAV_WEBROOT . '/images/');
|
||||
define('ACCOUNTS_DIR', USER_DIR .'accounts/');
|
||||
define('PAGES_DIR', USER_DIR .'pages/');
|
||||
define('DATA_DIR', USER_DIR .'data/');
|
||||
define('PLUGINS_DIR', USER_DIR .'plugins/');
|
||||
define('THEMES_DIR', USER_DIR .'themes/');
|
||||
define('SYSTEM_DIR', (!str_starts_with(GRAV_SYSTEM_PATH, '/') ? ROOT_DIR : '') . GRAV_SYSTEM_PATH);
|
||||
define('LIB_DIR', SYSTEM_DIR .'src/');
|
||||
define('VENDOR_DIR', ROOT_DIR .'vendor/');
|
||||
define('LOG_DIR', (!str_starts_with(GRAV_LOG_PATH, '/') ? ROOT_DIR : '') . GRAV_LOG_PATH . DS);
|
||||
define('ACCOUNTS_DIR', USER_DIR . 'accounts/');
|
||||
define('PAGES_DIR', USER_DIR . 'pages/');
|
||||
define('DATA_DIR', USER_DIR . 'data/');
|
||||
define('PLUGINS_DIR', USER_DIR . 'plugins/');
|
||||
define('THEMES_DIR', USER_DIR . 'themes/');
|
||||
define('SYSTEM_DIR', (!preg_match('`^(/|[a-z]:[\\\/])`ui', GRAV_SYSTEM_PATH) ? GRAV_ROOT . '/' : '') . GRAV_SYSTEM_PATH . '/');
|
||||
define('LIB_DIR', SYSTEM_DIR . 'src/');
|
||||
define('VENDOR_DIR', GRAV_ROOT . '/vendor/');
|
||||
define('LOG_DIR', (!preg_match('`^(/|[a-z]:[\\\/])`ui', GRAV_LOG_PATH) ? GRAV_ROOT . '/' : '') . GRAV_LOG_PATH . '/');
|
||||
// END DEPRECATED
|
||||
|
||||
// Some extensions
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ GRAV:
|
|||
VALIDATION_FAIL: '<b>فشل التحقق من صحة:</b>'
|
||||
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: كل <b />
|
||||
TEXT_MINS: ' في <b /> دقيقة(دقائق) بعد الساعة'
|
||||
TEXT_TIME: ' في <b />:<b />'
|
||||
TEXT_DOW: ' في <b />'
|
||||
TEXT_MONTH: ' من <b />'
|
||||
TEXT_DOM: ' في <b />'
|
||||
ERROR1: الوسم %s غير مدعوم!
|
||||
ERROR2: عدد عناصر غير صالح.
|
||||
ERROR4: تعبير غير معروف
|
||||
|
|
|
|||
|
|
@ -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: '<b>Ha fallat la validació:</b>'
|
||||
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 <b />
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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: '<b>La validation a échoué :</b>'
|
||||
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 <b/>'
|
||||
TEXT_MONTH: ' de <b />'
|
||||
TEXT_DOM: ' sur <b/>'
|
||||
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
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ GRAV:
|
|||
VALIDATION_FAIL: '<b>Fallou a validación:</b>'
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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: '<b>Validasi gagal:</b>'
|
||||
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 <b />
|
||||
TEXT_MINS: 'dalam <b /> menit setelah jam yang lalu'
|
||||
TEXT_TIME: ' pada <b />:<b />'
|
||||
TEXT_DOW: ' pada <b />'
|
||||
TEXT_MONTH: ' pada <b />'
|
||||
TEXT_DOM: ' pada <b />'
|
||||
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
|
||||
|
|
|
|||
147
system/languages/mn.yaml
Normal file
147
system/languages/mn.yaml
Normal file
|
|
@ -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: '<b>Баталгаажуулалт амжилтгүй боллоо:</b>'
|
||||
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: Бүрийн <b />
|
||||
TEXT_MINS: ' <b /> энэ сүүлийн цагийн минутад'
|
||||
TEXT_TIME: ' <b />:<b /> -д'
|
||||
TEXT_DOW: ' <b /> -д'
|
||||
TEXT_MONTH: ' <b /> -ын'
|
||||
TEXT_DOM: ' <b /> -т'
|
||||
ERROR1: '%s -н утга нь дэмжигддэггүй!'
|
||||
ERROR2: Элементүүдийн тоо хэмжээ буруу
|
||||
ERROR3: jquery_element нь jqCron тохиргоонд хийгдсэн байх ёстой
|
||||
ERROR4: Танигдаагүй илэрхийлэл
|
||||
|
|
@ -104,6 +104,7 @@ GRAV:
|
|||
VALIDATION_FAIL: '<b>Falha na validação:</b>'
|
||||
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
|
||||
|
|
|
|||
9
system/languages/si.yaml
Normal file
9
system/languages/si.yaml
Normal file
|
|
@ -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'
|
||||
|
|
@ -82,6 +82,8 @@ GRAV:
|
|||
- 'Cuma'
|
||||
- 'Cumartesi'
|
||||
- 'Pazar'
|
||||
YES: "Evet"
|
||||
NO: "Hayır"
|
||||
CRON:
|
||||
EVERY: her
|
||||
EVERY_HOUR: saatte bir
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ GRAV:
|
|||
YR_PLURAL: 年
|
||||
DEC_PLURAL: 十年
|
||||
FORM:
|
||||
MISSING_REQUIRED_FIELD: 遺漏必填欄位:
|
||||
VALIDATION_FAIL: '<b>確驗證失敗:</b>'
|
||||
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: 每 <b />
|
||||
TEXT_MINS: ' 的 <b /> 分'
|
||||
TEXT_TIME: ' <b />:<b />'
|
||||
TEXT_DOW: ' 的 <b />'
|
||||
TEXT_MONTH: ' 的 <b />'
|
||||
TEXT_DOM: ' 的 <b />'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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, '://')) {
|
||||
|
|
|
|||
|
|
@ -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...");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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]));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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')) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,143 +1,3 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\GPM
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Grav;
|
||||
use Symfony\Component\HttpClient\CurlHttpClient;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\HttpClient\HttpOptions;
|
||||
use Symfony\Component\HttpClient\NativeHttpClient;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use function call_user_func;
|
||||
use function defined;
|
||||
use function function_exists;
|
||||
|
||||
/**
|
||||
* Class Response
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/** @var callable The callback for the progress, either a function or callback in array notation */
|
||||
public static $callback = null;
|
||||
/** @var string[] */
|
||||
private static $headers = [
|
||||
'User-Agent' => '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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
130
system/src/Grav/Common/HTTP/Client.php
Normal file
130
system/src/Grav/Common/HTTP/Client.php
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\HTTP
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\HTTP;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Symfony\Component\HttpClient\CurlHttpClient;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\HttpClient\HttpOptions;
|
||||
use Symfony\Component\HttpClient\NativeHttpClient;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class Client
|
||||
{
|
||||
/** @var callable The callback for the progress, either a function or callback in array notation */
|
||||
public static $callback = null;
|
||||
/** @var string[] */
|
||||
private static $headers = [
|
||||
'User-Agent' => '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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
system/src/Grav/Common/HTTP/Response.php
Normal file
96
system/src/Grav/Common/HTTP/Response.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\HTTP
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\HTTP;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Grav;
|
||||
use Symfony\Component\HttpClient\CurlHttpClient;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\HttpClient\HttpOptions;
|
||||
use Symfony\Component\HttpClient\NativeHttpClient;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use function call_user_func;
|
||||
use function defined;
|
||||
|
||||
/**
|
||||
* Class Response
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Backwards compatible helper method
|
||||
*
|
||||
* @param string $uri
|
||||
* @param array $overrides
|
||||
* @param callable|null $callback
|
||||
* @return string
|
||||
* @throws TransportExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface|ClientExceptionInterface
|
||||
*/
|
||||
public static function get(string $uri = '', array $overrides = [], callable $callback = null): string
|
||||
{
|
||||
$response = static::request('GET', $uri, $overrides, $callback);
|
||||
return $response->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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
19
system/src/Grav/Common/Twig/Exception/TwigException.php
Normal file
19
system/src/Grav/Common/Twig/Exception/TwigException.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig\Exception
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Exception;
|
||||
|
||||
/**
|
||||
* TwigException gets thrown when you use {% throw code message %} in twig.
|
||||
*
|
||||
* This allows Grav to catch 401, 403 and 404 exceptions and display proper error page.
|
||||
*/
|
||||
class TwigException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
391
system/src/Grav/Common/Twig/Extension/FilesystemExtension.php
Normal file
391
system/src/Grav/Common/Twig/Extension/FilesystemExtension.php
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Extension;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
/**
|
||||
* Class FilesystemExtension
|
||||
* @package Grav\Common\Twig\Extension
|
||||
*/
|
||||
class FilesystemExtension extends AbstractExtension
|
||||
{
|
||||
/** @var UniformResourceLocator */
|
||||
private $locator;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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));
|
||||
}
|
||||
}
|
||||
1635
system/src/Grav/Common/Twig/Extension/GravExtension.php
Normal file
1635
system/src/Grav/Common/Twig/Extension/GravExtension.php
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -47,6 +47,6 @@ class TwigNodeMarkdown extends Node implements NodeOutputInterface
|
|||
->write('$lines = explode("\n", $content);' . PHP_EOL)
|
||||
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
|
||||
->write('$content = join("\n", $content);' . PHP_EOL)
|
||||
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($context, $content);' . PHP_EOL);
|
||||
->write('echo $this->env->getExtension(\'Grav\Common\Twig\Extension\GravExtension\')->markdownFunction($context, $content);' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class TwigNodeThrow extends Node
|
|||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler
|
||||
->write('throw new \RuntimeException(')
|
||||
->write('throw new \Grav\Common\Twig\Exception\TwigException(')
|
||||
->subcompile($this->getNode('message'))
|
||||
->write(', ')
|
||||
->write($this->getAttribute('code') ?: 500)
|
||||
|
|
|
|||
|
|
@ -49,16 +49,15 @@ class TwigNodeTryCatch extends Node
|
|||
|
||||
$compiler
|
||||
->indent()
|
||||
->subcompile($this->getNode('try'));
|
||||
->subcompile($this->getNode('try'))
|
||||
->outdent()
|
||||
->write('} catch (\Exception $e) {' . "\n")
|
||||
->indent()
|
||||
->write('if (isset($context[\'grav\'][\'debugger\'])) $context[\'grav\'][\'debugger\']->addException($e);' . "\n")
|
||||
->write('$context[\'e\'] = $e;' . "\n");
|
||||
|
||||
if ($this->hasNode('catch')) {
|
||||
$compiler
|
||||
->outdent()
|
||||
->write('} catch (\Exception $e) {' . "\n")
|
||||
->indent()
|
||||
->write('if (isset($context[\'grav\'][\'debugger\'])) $context[\'grav\'][\'debugger\']->addException($e);' . "\n")
|
||||
->write('$context[\'e\'] = $e;' . "\n")
|
||||
->subcompile($this->getNode('catch'));
|
||||
$compiler->subcompile($this->getNode('catch'));
|
||||
}
|
||||
|
||||
$compiler
|
||||
|
|
|
|||
|
|
@ -16,24 +16,30 @@ use Grav\Common\Language\Language;
|
|||
use Grav\Common\Language\LanguageCodes;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Twig\Exception\TwigException;
|
||||
use Grav\Common\Twig\Extension\FilesystemExtension;
|
||||
use Grav\Common\Twig\Extension\GravExtension;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Phive\Twig\Extensions\Deferred\DeferredExtension;
|
||||
use RuntimeException;
|
||||
use Twig\Cache\FilesystemCache;
|
||||
use Twig\DeferredExtension\DeferredExtension;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\CoreExtension;
|
||||
use Twig\Extension\DebugExtension;
|
||||
use Twig\Extension\StringLoaderExtension;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\ExistsLoaderInterface;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use Twig\Profiler\Profile;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class Twig
|
||||
|
|
@ -154,27 +160,53 @@ class Twig
|
|||
|
||||
$this->twig = new TwigEnvironment($loader_chain, $params);
|
||||
|
||||
if ($config->get('system.twig.undefined_functions')) {
|
||||
$this->twig->registerUndefinedFunctionCallback(function ($name) {
|
||||
$this->twig->registerUndefinedFunctionCallback(function ($name) use ($config) {
|
||||
$allowed = $config->get('system.twig.safe_functions');
|
||||
if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) {
|
||||
return new TwigFunction($name, $name);
|
||||
}
|
||||
if ($config->get('system.twig.undefined_functions')) {
|
||||
if (function_exists($name)) {
|
||||
return new TwigFunction($name, $name);
|
||||
if (!Utils::isDangerousFunction($name)) {
|
||||
user_error("PHP function {$name}() was used as Twig function. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_functions`", E_USER_DEPRECATED);
|
||||
|
||||
return new TwigFunction($name, $name);
|
||||
}
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig function. If you really want to use it, please add it to system configuration: `system.twig.safe_functions`"));
|
||||
}
|
||||
|
||||
return new TwigFunction($name, static function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
return new TwigFunction($name, static function () {});
|
||||
}
|
||||
|
||||
if ($config->get('system.twig.undefined_filters')) {
|
||||
$this->twig->registerUndefinedFilterCallback(function ($name) {
|
||||
return false;
|
||||
});
|
||||
|
||||
$this->twig->registerUndefinedFilterCallback(function ($name) use ($config) {
|
||||
$allowed = $config->get('system.twig.safe_filters');
|
||||
if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) {
|
||||
return new TwigFilter($name, $name);
|
||||
}
|
||||
if ($config->get('system.twig.undefined_filters')) {
|
||||
if (function_exists($name)) {
|
||||
return new TwigFilter($name, $name);
|
||||
if (!Utils::isDangerousFunction($name)) {
|
||||
user_error("PHP function {$name}() used as Twig filter. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_filters`", E_USER_DEPRECATED);
|
||||
|
||||
return new TwigFilter($name, $name);
|
||||
}
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig filter. If you really want to use it, please add it to system configuration: `system.twig.safe_filters`"));
|
||||
}
|
||||
|
||||
return new TwigFilter($name, static function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
return new TwigFilter($name, static function () {});
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$this->grav->fireEvent('onTwigInitialized');
|
||||
|
||||
|
|
@ -188,7 +220,8 @@ class Twig
|
|||
if ($config->get('system.twig.debug')) {
|
||||
$this->twig->addExtension(new DebugExtension());
|
||||
}
|
||||
$this->twig->addExtension(new TwigExtension());
|
||||
$this->twig->addExtension(new GravExtension());
|
||||
$this->twig->addExtension(new FilesystemExtension());
|
||||
$this->twig->addExtension(new DeferredExtension());
|
||||
$this->twig->addExtension(new StringLoaderExtension());
|
||||
|
||||
|
|
@ -211,7 +244,7 @@ class Twig
|
|||
'assets' => $this->grav['assets'],
|
||||
'taxonomy' => $this->grav['taxonomy'],
|
||||
'browser' => $this->grav['browser'],
|
||||
'base_dir' => rtrim(ROOT_DIR, '/'),
|
||||
'base_dir' => GRAV_ROOT,
|
||||
'home_url' => $pages->homeUrl($active_language),
|
||||
'base_url' => $pages->baseUrl($active_language),
|
||||
'base_url_absolute' => $pages->baseUrl($active_language, true),
|
||||
|
|
@ -372,38 +405,63 @@ class Twig
|
|||
*/
|
||||
public function processSite($format = null, array $vars = [])
|
||||
{
|
||||
// set the page now its been processed
|
||||
$this->grav->fireEvent('onTwigSiteVariables');
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
/** @var PageInterface $page */
|
||||
$page = $this->grav['page'];
|
||||
$content = $page->content();
|
||||
|
||||
$twig_vars = $this->twig_vars;
|
||||
|
||||
$twig_vars['theme'] = $this->grav['config']->get('theme');
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
$twig_vars['media'] = $page->media();
|
||||
$twig_vars['content'] = $content;
|
||||
|
||||
// determine if params are set, if so disable twig cache
|
||||
$params = $this->grav['uri']->params(null, true);
|
||||
if (!empty($params)) {
|
||||
$this->twig->setCache(false);
|
||||
}
|
||||
|
||||
// Get Twig template layout
|
||||
$template = $this->getPageTwigTemplate($page, $format);
|
||||
$page->templateFormat($format);
|
||||
|
||||
try {
|
||||
$grav = $this->grav;
|
||||
|
||||
// set the page now its been processed
|
||||
$grav->fireEvent('onTwigSiteVariables');
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $grav['pages'];
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $grav['page'];
|
||||
|
||||
$twig_vars = $this->twig_vars;
|
||||
$twig_vars['theme'] = $grav['config']->get('theme');
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
$twig_vars['media'] = $page->media();
|
||||
$twig_vars['content'] = $page->content();
|
||||
|
||||
// determine if params are set, if so disable twig cache
|
||||
$params = $grav['uri']->params(null, true);
|
||||
if (!empty($params)) {
|
||||
$this->twig->setCache(false);
|
||||
}
|
||||
|
||||
// Get Twig template layout
|
||||
$template = $this->getPageTwigTemplate($page, $format);
|
||||
$page->templateFormat($format);
|
||||
|
||||
$output = $this->twig->render($template, $vars + $twig_vars);
|
||||
} catch (LoaderError $e) {
|
||||
$error_msg = $e->getMessage();
|
||||
throw new RuntimeException($error_msg, 400, $e);
|
||||
throw new RuntimeException($e->getMessage(), 400, $e);
|
||||
} catch (RuntimeError $e) {
|
||||
$prev = $e->getPrevious();
|
||||
if ($prev instanceof TwigException) {
|
||||
$code = $prev->getCode() ?: 500;
|
||||
// Fire onPageNotFound event.
|
||||
$event = new Event([
|
||||
'page' => $page,
|
||||
'code' => $code,
|
||||
'message' => $prev->getMessage(),
|
||||
'exception' => $prev,
|
||||
'route' => $grav['route'],
|
||||
'request' => $grav['request']
|
||||
]);
|
||||
$event = $grav->fireEvent("onDisplayErrorPage.{$code}", $event);
|
||||
$newPage = $event['page'];
|
||||
if ($newPage && $newPage !== $page) {
|
||||
unset($grav['page']);
|
||||
$grav['page'] = $newPage;
|
||||
|
||||
return $this->processSite($newPage->templateFormat(), $vars);
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
|
@ -456,25 +514,21 @@ class Twig
|
|||
$twig_extension = $extension ? '.'. $extension .TWIG_EXT : TEMPLATE_EXT;
|
||||
$template_file = $this->template($page->template() . $twig_extension);
|
||||
|
||||
$page_template = null;
|
||||
|
||||
$loader = $this->twig->getLoader();
|
||||
if ($loader instanceof ExistsLoaderInterface) {
|
||||
if ($loader->exists($template_file)) {
|
||||
// template.xxx.twig
|
||||
$page_template = $template_file;
|
||||
} elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) {
|
||||
// template.html.twig
|
||||
$page_template = $template . TEMPLATE_EXT;
|
||||
$format = 'html';
|
||||
} elseif ($loader->exists($default . $twig_extension)) {
|
||||
// default.xxx.twig
|
||||
$page_template = $default . $twig_extension;
|
||||
} else {
|
||||
// default.html.twig
|
||||
$page_template = $default . TEMPLATE_EXT;
|
||||
$format = 'html';
|
||||
}
|
||||
if ($loader->exists($template_file)) {
|
||||
// template.xxx.twig
|
||||
$page_template = $template_file;
|
||||
} elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) {
|
||||
// template.html.twig
|
||||
$page_template = $template . TEMPLATE_EXT;
|
||||
$format = 'html';
|
||||
} elseif ($loader->exists($default . $twig_extension)) {
|
||||
// default.xxx.twig
|
||||
$page_template = $default . $twig_extension;
|
||||
} else {
|
||||
// default.html.twig
|
||||
$page_template = $default . TEMPLATE_EXT;
|
||||
$format = 'html';
|
||||
}
|
||||
|
||||
return $page_template;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -160,8 +160,8 @@ class Uri
|
|||
$language = $grav['language'];
|
||||
|
||||
// add the port to the base for non-standard ports
|
||||
if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
|
||||
$this->base .= ':' . (string)$this->port;
|
||||
if ($this->port && $config->get('system.reverse_proxy_setup') === false) {
|
||||
$this->base .= ':' . $this->port;
|
||||
}
|
||||
|
||||
// Handle custom base
|
||||
|
|
@ -176,8 +176,8 @@ class Uri
|
|||
if (isset($custom_parts['scheme'])) {
|
||||
$this->base = $custom_parts['scheme'] . '://' . $custom_parts['host'];
|
||||
$this->port = $custom_parts['port'] ?? null;
|
||||
if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
|
||||
$this->base .= ':' . (string)$this->port;
|
||||
if ($this->port && $config->get('system.reverse_proxy_setup') === false) {
|
||||
$this->base .= ':' . $this->port;
|
||||
}
|
||||
$this->root = $custom_base;
|
||||
} else {
|
||||
|
|
@ -462,8 +462,8 @@ class Uri
|
|||
public function port($raw = false)
|
||||
{
|
||||
$port = $this->port;
|
||||
// If not in raw mode and port is not set, figure it out from scheme.
|
||||
if (!$raw && $port === null) {
|
||||
// If not in raw mode and port is not set or is 0, figure it out from scheme.
|
||||
if (!$raw && !$port) {
|
||||
if ($this->scheme === 'http') {
|
||||
$this->port = 80;
|
||||
} elseif ($this->scheme === 'https') {
|
||||
|
|
@ -471,7 +471,7 @@ class Uri
|
|||
}
|
||||
}
|
||||
|
||||
return $this->port;
|
||||
return $this->port ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -586,33 +586,38 @@ class Uri
|
|||
/**
|
||||
* Return relative path to the referrer defaulting to current or given page.
|
||||
*
|
||||
* You should set the third parameter to `true` for redirects as long as you came from the same sub-site and language.
|
||||
*
|
||||
* @param string|null $default
|
||||
* @param string|null $attributes
|
||||
* @param bool $withoutBaseRoute
|
||||
* @return string
|
||||
*/
|
||||
public function referrer($default = null, $attributes = null)
|
||||
public function referrer($default = null, $attributes = null, bool $withoutBaseRoute = false)
|
||||
{
|
||||
$referrer = $_SERVER['HTTP_REFERER'] ?? null;
|
||||
|
||||
// Check that referrer came from our site.
|
||||
$root = $this->rootUrl(true);
|
||||
if ($referrer) {
|
||||
// Referrer should always have host set and it should come from the same base address.
|
||||
if (stripos($referrer, $root) !== 0) {
|
||||
$referrer = null;
|
||||
}
|
||||
if ($withoutBaseRoute) {
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
$base = $pages->baseUrl(null, true);
|
||||
} else {
|
||||
$base = $this->rootUrl(true);
|
||||
}
|
||||
|
||||
if (!$referrer) {
|
||||
// Referrer should always have host set and it should come from the same base address.
|
||||
if (!is_string($referrer) || !str_starts_with($referrer, $base)) {
|
||||
$referrer = $default ?: $this->route(true, true);
|
||||
}
|
||||
|
||||
// Relative path from grav root.
|
||||
$referrer = substr($referrer, strlen($base));
|
||||
if ($attributes) {
|
||||
$referrer .= $attributes;
|
||||
}
|
||||
|
||||
// Return relative path.
|
||||
return substr($referrer, strlen($root));
|
||||
return $referrer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -648,7 +653,7 @@ class Uri
|
|||
return [
|
||||
'scheme' => $this->scheme,
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
'port' => $this->port ?: null,
|
||||
'user' => $this->user,
|
||||
'pass' => $this->password,
|
||||
'path' => $path,
|
||||
|
|
@ -665,7 +670,7 @@ class Uri
|
|||
*/
|
||||
public static function paramsRegex()
|
||||
{
|
||||
return '/\/([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
|
||||
return '/\/{1,}([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -675,10 +680,15 @@ class Uri
|
|||
*/
|
||||
public static function ip()
|
||||
{
|
||||
$ip = 'UNKNOWN';
|
||||
|
||||
if (getenv('HTTP_CLIENT_IP')) {
|
||||
$ip = getenv('HTTP_CLIENT_IP');
|
||||
} elseif (getenv('HTTP_CF_CONNECTING_IP')) {
|
||||
$ip = getenv('HTTP_CF_CONNECTING_IP');
|
||||
} elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
|
||||
$ip = getenv('HTTP_X_FORWARDED_FOR');
|
||||
$ips = array_map('trim', explode(',', getenv('HTTP_X_FORWARDED_FOR')));
|
||||
$ip = array_shift($ips);
|
||||
} elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
|
||||
$ip = getenv('HTTP_X_FORWARDED');
|
||||
} elseif (getenv('HTTP_FORWARDED_FOR')) {
|
||||
|
|
@ -687,8 +697,6 @@ class Uri
|
|||
$ip = getenv('HTTP_FORWARDED');
|
||||
} elseif (getenv('REMOTE_ADDR')) {
|
||||
$ip = getenv('REMOTE_ADDR');
|
||||
} else {
|
||||
$ip = 'UNKNOWN';
|
||||
}
|
||||
|
||||
return $ip;
|
||||
|
|
@ -1143,11 +1151,8 @@ class Uri
|
|||
public static function isValidUrl($url)
|
||||
{
|
||||
$regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/';
|
||||
if (preg_match($regex, $url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return (bool)preg_match($regex, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1258,7 +1263,7 @@ class Uri
|
|||
$this->port = null;
|
||||
}
|
||||
|
||||
if ($this->hasStandardPort()) {
|
||||
if ($this->port === 0 || $this->hasStandardPort()) {
|
||||
$this->port = null;
|
||||
}
|
||||
|
||||
|
|
@ -1298,7 +1303,7 @@ class Uri
|
|||
*/
|
||||
protected function hasStandardPort()
|
||||
{
|
||||
return ($this->port === 80 || $this->port === 443);
|
||||
return (!$this->port || $this->port === 80 || $this->port === 443);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1311,11 +1316,13 @@ class Uri
|
|||
if ($parts === false) {
|
||||
throw new RuntimeException('Malformed URL: ' . $url);
|
||||
}
|
||||
$port = (int)($parts['port'] ?? 0);
|
||||
|
||||
$this->scheme = $parts['scheme'] ?? null;
|
||||
$this->user = $parts['user'] ?? null;
|
||||
$this->password = $parts['pass'] ?? null;
|
||||
$this->host = $parts['host'] ?? null;
|
||||
$this->port = isset($parts['port']) ? (int)$parts['port'] : null;
|
||||
$this->port = $port ?: null;
|
||||
$this->path = $parts['path'] ?? '';
|
||||
$this->query = $parts['query'] ?? '';
|
||||
$this->fragment = $parts['fragment'] ?? null;
|
||||
|
|
@ -1498,7 +1505,7 @@ class Uri
|
|||
* @param string $delimiter
|
||||
* @return string
|
||||
*/
|
||||
private function processParams($uri, $delimiter = ':')
|
||||
private function processParams(string $uri, string $delimiter = ':'): string
|
||||
{
|
||||
if (strpos($uri, $delimiter) !== false) {
|
||||
preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ class User extends Data implements UserInterface
|
|||
* @param string $offset
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
$value = parent::offsetExists($offset);
|
||||
|
|
@ -73,6 +74,7 @@ class User extends Data implements UserInterface
|
|||
* @param string $offset
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
$value = parent::offsetGet($offset);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ use function strlen;
|
|||
*/
|
||||
abstract class Utils
|
||||
{
|
||||
/** @var array */
|
||||
/** @var array */
|
||||
protected static $nonces = [];
|
||||
|
||||
protected const ROOTURL_REGEX = '{^((?:http[s]?:\/\/[^\/]+)|(?:\/\/[^\/]+))(.*)}';
|
||||
|
|
@ -166,9 +166,9 @@ abstract class Utils
|
|||
|
||||
if ($locator->isStream($path)) {
|
||||
$path = $locator->findResource($path, true);
|
||||
} elseif (!Utils::startsWith($path, GRAV_ROOT)) {
|
||||
} elseif (!static::startsWith($path, GRAV_ROOT)) {
|
||||
$base_url = Grav::instance()['base_url'];
|
||||
$path = GRAV_ROOT . '/' . ltrim(Utils::replaceFirstOccurrence($base_url, '', $path), '/');
|
||||
$path = GRAV_ROOT . '/' . ltrim(static::replaceFirstOccurrence($base_url, '', $path), '/');
|
||||
}
|
||||
|
||||
return $path;
|
||||
|
|
@ -178,8 +178,8 @@ abstract class Utils
|
|||
/**
|
||||
* Check if the $haystack string starts with the substring $needle
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @return bool
|
||||
*/
|
||||
|
|
@ -202,8 +202,8 @@ abstract class Utils
|
|||
/**
|
||||
* Check if the $haystack string ends with the substring $needle
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @return bool
|
||||
*/
|
||||
|
|
@ -227,9 +227,9 @@ abstract class Utils
|
|||
/**
|
||||
* Check if the $haystack string contains the substring $needle
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @return bool
|
||||
*/
|
||||
public static function contains($haystack, $needle, $case_sensitive = true)
|
||||
|
|
@ -266,19 +266,19 @@ abstract class Utils
|
|||
{
|
||||
$regex = str_replace(
|
||||
array("\*", "\?"), // wildcard chars
|
||||
array('.*','.'), // regexp chars
|
||||
array('.*', '.'), // regexp chars
|
||||
preg_quote($wildcard_pattern, '/')
|
||||
);
|
||||
|
||||
return preg_match('/^'.$regex.'$/is', $haystack);
|
||||
return preg_match('/^' . $regex . '$/is', $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render simple template filling up the variables in it. If value is not defined, leave it as it was.
|
||||
*
|
||||
* @param string $template Template string
|
||||
* @param array $variables Variables with values
|
||||
* @param array $brackets Optional array of opening and closing brackets or symbols
|
||||
* @param string $template Template string
|
||||
* @param array $variables Variables with values
|
||||
* @param array $brackets Optional array of opening and closing brackets or symbols
|
||||
* @return string Final string filled with values
|
||||
*/
|
||||
public static function simpleTemplate(string $template, array $variables, array $brackets = ['{', '}']): string
|
||||
|
|
@ -376,8 +376,8 @@ abstract class Utils
|
|||
/**
|
||||
* Merge two objects into one.
|
||||
*
|
||||
* @param object $obj1
|
||||
* @param object $obj2
|
||||
* @param object $obj1
|
||||
* @param object $obj2
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
|
|
@ -415,7 +415,7 @@ abstract class Utils
|
|||
*/
|
||||
public static function arrayRemoveValue(array $search, $value)
|
||||
{
|
||||
foreach ((array) $value as $val) {
|
||||
foreach ((array)$value as $val) {
|
||||
$key = array_search($val, $search);
|
||||
if ($key !== false) {
|
||||
unset($search[$key]);
|
||||
|
|
@ -481,8 +481,8 @@ abstract class Utils
|
|||
/**
|
||||
* Array combine but supports different array lengths
|
||||
*
|
||||
* @param array $arr1
|
||||
* @param array $arr2
|
||||
* @param array $arr1
|
||||
* @param array $arr2
|
||||
* @return array|false
|
||||
*/
|
||||
public static function arrayCombine($arr1, $arr2)
|
||||
|
|
@ -495,7 +495,7 @@ abstract class Utils
|
|||
/**
|
||||
* Array is associative or not
|
||||
*
|
||||
* @param array $arr
|
||||
* @param array $arr
|
||||
* @return bool
|
||||
*/
|
||||
public static function arrayIsAssociative($arr)
|
||||
|
|
@ -517,15 +517,15 @@ abstract class Utils
|
|||
$now = new DateTime();
|
||||
|
||||
$date_formats = [
|
||||
'd-m-Y H:i' => 'd-m-Y H:i (e.g. '.$now->format('d-m-Y H:i').')',
|
||||
'Y-m-d H:i' => 'Y-m-d H:i (e.g. '.$now->format('Y-m-d H:i').')',
|
||||
'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. '.$now->format('m/d/Y h:i a').')',
|
||||
'H:i d-m-Y' => 'H:i d-m-Y (e.g. '.$now->format('H:i d-m-Y').')',
|
||||
'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. '.$now->format('h:i a m/d/Y').')',
|
||||
];
|
||||
'd-m-Y H:i' => 'd-m-Y H:i (e.g. ' . $now->format('d-m-Y H:i') . ')',
|
||||
'Y-m-d H:i' => 'Y-m-d H:i (e.g. ' . $now->format('Y-m-d H:i') . ')',
|
||||
'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. ' . $now->format('m/d/Y h:i a') . ')',
|
||||
'H:i d-m-Y' => 'H:i d-m-Y (e.g. ' . $now->format('H:i d-m-Y') . ')',
|
||||
'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. ' . $now->format('h:i a m/d/Y') . ')',
|
||||
];
|
||||
$default_format = Grav::instance()['config']->get('system.pages.dateformat.default');
|
||||
if ($default_format) {
|
||||
$date_formats = array_merge([$default_format => $default_format.' (e.g. '.$now->format($default_format).')'], $date_formats);
|
||||
$date_formats = array_merge([$default_format => $default_format . ' (e.g. ' . $now->format($default_format) . ')'], $date_formats);
|
||||
}
|
||||
|
||||
return $date_formats;
|
||||
|
|
@ -552,11 +552,11 @@ abstract class Utils
|
|||
/**
|
||||
* Truncate text by number of characters but can cut off words.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $limit Max number of characters.
|
||||
* @param bool $up_to_break truncate up to breakpoint after char count
|
||||
* @param string $break Break point.
|
||||
* @param string $pad Appended padding to the end of the string.
|
||||
* @param string $string
|
||||
* @param int $limit Max number of characters.
|
||||
* @param bool $up_to_break truncate up to breakpoint after char count
|
||||
* @param string $break Break point.
|
||||
* @param string $pad Appended padding to the end of the string.
|
||||
* @return string
|
||||
*/
|
||||
public static function truncate($string, $limit = 150, $up_to_break = false, $break = ' ', $pad = '…')
|
||||
|
|
@ -582,7 +582,7 @@ abstract class Utils
|
|||
* Truncate text by number of characters in a "word-safe" manor.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $limit
|
||||
* @param int $limit
|
||||
* @return string
|
||||
*/
|
||||
public static function safeTruncate($string, $limit = 150)
|
||||
|
|
@ -594,9 +594,9 @@ abstract class Utils
|
|||
/**
|
||||
* Truncate HTML by number of characters. not "word-safe"!
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $length in characters
|
||||
* @param string $ellipsis
|
||||
* @param string $text
|
||||
* @param int $length in characters
|
||||
* @param string $ellipsis
|
||||
* @return string
|
||||
*/
|
||||
public static function truncateHtml($text, $length = 100, $ellipsis = '...')
|
||||
|
|
@ -607,9 +607,9 @@ abstract class Utils
|
|||
/**
|
||||
* Truncate HTML by number of characters in a "word-safe" manor.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $length in words
|
||||
* @param string $ellipsis
|
||||
* @param string $text
|
||||
* @param int $length in words
|
||||
* @param string $ellipsis
|
||||
* @return string
|
||||
*/
|
||||
public static function safeTruncateHtml($text, $length = 25, $ellipsis = '...')
|
||||
|
|
@ -628,13 +628,30 @@ abstract class Utils
|
|||
return substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random string with configurable length, prefix and suffix.
|
||||
* Unlike the built-in `uniqid()`, this string is non-conflicting and safe
|
||||
*
|
||||
* @param int $length
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function uniqueId(int $length = 13, array $options = []): string
|
||||
{
|
||||
$options = array_merge(['prefix' => '', 'suffix' => ''], $options);
|
||||
$bytes = random_bytes(ceil($length / 2));
|
||||
|
||||
return $options['prefix'] . substr(bin2hex($bytes), 0, $length) . $options['suffix'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ability to download a file to the browser
|
||||
*
|
||||
* @param string $file the full path to the file to be downloaded
|
||||
* @param bool $force_download as opposed to letting browser choose if to download or render
|
||||
* @param int $sec Throttling, try 0.1 for some speed throttling of downloads
|
||||
* @param int $bytes Size of chunks to send in bytes. Default is 1024
|
||||
* @param int $sec Throttling, try 0.1 for some speed throttling of downloads
|
||||
* @param int $bytes Size of chunks to send in bytes. Default is 1024
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function download($file, $force_download = true, $sec = 0, $bytes = 1024)
|
||||
|
|
@ -645,7 +662,7 @@ abstract class Utils
|
|||
|
||||
$file_parts = pathinfo($file);
|
||||
$mimetype = static::getMimeByExtension($file_parts['extension']);
|
||||
$size = filesize($file); // File size
|
||||
$size = filesize($file); // File size
|
||||
|
||||
// clean all buffers
|
||||
while (ob_get_level()) {
|
||||
|
|
@ -742,7 +759,7 @@ abstract class Utils
|
|||
// Set from uri extension
|
||||
$uri_extension = $uri->extension();
|
||||
if (is_string($uri_extension) && $uri->isValidExtension($uri_extension)) {
|
||||
return($uri_extension);
|
||||
return ($uri_extension);
|
||||
}
|
||||
|
||||
// Use content negotiation via the `accept:` header
|
||||
|
|
@ -750,13 +767,13 @@ abstract class Utils
|
|||
if (is_string($http_accept)) {
|
||||
$negotiator = new Negotiator();
|
||||
|
||||
$supported_types = Utils::getSupportPageTypes(['html', 'json']);
|
||||
$priorities = Utils::getMimeTypes($supported_types);
|
||||
$supported_types = static::getSupportPageTypes(['html', 'json']);
|
||||
$priorities = static::getMimeTypes($supported_types);
|
||||
|
||||
$media_type = $negotiator->getBest($http_accept, $priorities);
|
||||
$mimetype = $media_type instanceof Accept ? $media_type->getValue() : '';
|
||||
|
||||
return Utils::getExtensionByMime($mimetype);
|
||||
return static::getExtensionByMime($mimetype);
|
||||
}
|
||||
|
||||
return 'html';
|
||||
|
|
@ -791,13 +808,7 @@ abstract class Utils
|
|||
|
||||
$media_types = Grav::instance()['config']->get('media.types');
|
||||
|
||||
if (isset($media_types[$extension])) {
|
||||
if (isset($media_types[$extension]['mime'])) {
|
||||
return $media_types[$extension]['mime'];
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
return $media_types[$extension]['mime'] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1060,7 +1071,7 @@ abstract class Utils
|
|||
|
||||
$pretty_offset = "UTC${offset_prefix}${offset_formatted}";
|
||||
|
||||
$timezone_list[$timezone] = "(${pretty_offset}) ".str_replace('_', ' ', $timezone);
|
||||
$timezone_list[$timezone] = "(${pretty_offset}) " . str_replace('_', ' ', $timezone);
|
||||
}
|
||||
|
||||
return $timezone_list;
|
||||
|
|
@ -1069,11 +1080,11 @@ abstract class Utils
|
|||
/**
|
||||
* Recursively filter an array, filtering values by processing them through the $fn function argument
|
||||
*
|
||||
* @param array $source the Array to filter
|
||||
* @param callable $fn the function to pass through each array item
|
||||
* @param array $source the Array to filter
|
||||
* @param callable $fn the function to pass through each array item
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayFilterRecursive(Array $source, $fn)
|
||||
public static function arrayFilterRecursive(array $source, $fn)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($source as $key => $value) {
|
||||
|
|
@ -1093,15 +1104,15 @@ abstract class Utils
|
|||
/**
|
||||
* Flatten a multi-dimensional associative array into query params.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayToQueryParams($array, $prepend = '')
|
||||
{
|
||||
$results = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$name = $prepend ? $prepend . '[' . $key . ']' : $key;
|
||||
$name = $prepend ? $prepend . '[' . $key . ']' : $key;
|
||||
|
||||
if (is_array($value)) {
|
||||
$results = array_merge($results, static::arrayToQueryParams($value, $name));
|
||||
|
|
@ -1138,8 +1149,8 @@ abstract class Utils
|
|||
/**
|
||||
* Flatten a multi-dimensional associative array into dot notation
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayFlattenDotNotation($array, $prepend = '')
|
||||
|
|
@ -1147,9 +1158,9 @@ abstract class Utils
|
|||
$results = array();
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend.$key.'.'));
|
||||
$results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend . $key . '.'));
|
||||
} else {
|
||||
$results[$prepend.$key] = $value;
|
||||
$results[$prepend . $key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1297,7 +1308,7 @@ abstract class Utils
|
|||
* with reverse proxy setups.
|
||||
*
|
||||
* @param string $action
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @return string the nonce string
|
||||
*/
|
||||
private static function generateNonceString($action, $previousTick = false)
|
||||
|
|
@ -1334,8 +1345,8 @@ abstract class Utils
|
|||
* Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given
|
||||
* action is the same for 12 hours.
|
||||
*
|
||||
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @return string the nonce
|
||||
*/
|
||||
public static function getNonce($action, $previousTick = false)
|
||||
|
|
@ -1353,7 +1364,7 @@ abstract class Utils
|
|||
/**
|
||||
* Verify the passed nonce for the give action
|
||||
*
|
||||
* @param string|string[] $nonce the nonce to verify
|
||||
* @param string|string[] $nonce the nonce to verify
|
||||
* @param string $action the action to verify the nonce to
|
||||
* @return boolean verified or not
|
||||
*/
|
||||
|
|
@ -1434,7 +1445,7 @@ abstract class Utils
|
|||
while (count($keys) > 1) {
|
||||
$key = array_shift($keys);
|
||||
|
||||
if (! isset($array[$key]) || ! is_array($array[$key])) {
|
||||
if (!isset($array[$key]) || !is_array($array[$key])) {
|
||||
$array[$key] = array();
|
||||
}
|
||||
|
||||
|
|
@ -1725,7 +1736,7 @@ abstract class Utils
|
|||
$size *= 1024 ** stripos('bkmgtpezy', $unit[0]);
|
||||
}
|
||||
|
||||
return (int) abs(round($size));
|
||||
return (int)abs(round($size));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1739,7 +1750,7 @@ abstract class Utils
|
|||
{
|
||||
$enc_url = preg_replace_callback(
|
||||
'%[^:/@?&=#]+%usD',
|
||||
function ($matches) {
|
||||
static function ($matches) {
|
||||
return urlencode($matches[0]);
|
||||
},
|
||||
$url
|
||||
|
|
@ -1770,7 +1781,7 @@ abstract class Utils
|
|||
public static function processMarkdown($string, $block = true, $page = null)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$page = $page ?? $grav['page'] ?? null;
|
||||
$page = $page ?? $grav['page'] ?? null;
|
||||
$defaults = [
|
||||
'markdown' => $grav['config']->get('system.pages.markdown', []),
|
||||
'images' => $grav['config']->get('system.images', [])
|
||||
|
|
@ -1812,12 +1823,12 @@ abstract class Utils
|
|||
$ip = (string)inet_pton($ip);
|
||||
|
||||
// Maximum netmask length = same as packed address
|
||||
$len = 8*strlen($ip);
|
||||
$len = 8 * strlen($ip);
|
||||
if ($prefix > $len) {
|
||||
$prefix = $len;
|
||||
}
|
||||
|
||||
$mask = str_repeat('f', $prefix>>2);
|
||||
$mask = str_repeat('f', $prefix >> 2);
|
||||
|
||||
switch ($prefix & 3) {
|
||||
case 3:
|
||||
|
|
@ -1830,7 +1841,7 @@ abstract class Utils
|
|||
$mask .= '8';
|
||||
break;
|
||||
}
|
||||
$mask = str_pad($mask, $len>>2, '0');
|
||||
$mask = str_pad($mask, $len >> 2, '0');
|
||||
|
||||
// Packed representation of netmask
|
||||
$mask = pack('H*', $mask);
|
||||
|
|
@ -1861,4 +1872,244 @@ abstract class Utils
|
|||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDangerousFunction(string $name): bool
|
||||
{
|
||||
static $commandExecutionFunctions = [
|
||||
'exec',
|
||||
'passthru',
|
||||
'system',
|
||||
'shell_exec',
|
||||
'popen',
|
||||
'proc_open',
|
||||
'pcntl_exec',
|
||||
];
|
||||
|
||||
static $codeExecutionFunctions = [
|
||||
'assert',
|
||||
'preg_replace',
|
||||
'create_function',
|
||||
'include',
|
||||
'include_once',
|
||||
'require',
|
||||
'require_once'
|
||||
];
|
||||
|
||||
static $callbackFunctions = [
|
||||
'ob_start' => 0,
|
||||
'array_diff_uassoc' => -1,
|
||||
'array_diff_ukey' => -1,
|
||||
'array_filter' => 1,
|
||||
'array_intersect_uassoc' => -1,
|
||||
'array_intersect_ukey' => -1,
|
||||
'array_map' => 0,
|
||||
'array_reduce' => 1,
|
||||
'array_udiff_assoc' => -1,
|
||||
'array_udiff_uassoc' => [-1, -2],
|
||||
'array_udiff' => -1,
|
||||
'array_uintersect_assoc' => -1,
|
||||
'array_uintersect_uassoc' => [-1, -2],
|
||||
'array_uintersect' => -1,
|
||||
'array_walk_recursive' => 1,
|
||||
'array_walk' => 1,
|
||||
'assert_options' => 1,
|
||||
'uasort' => 1,
|
||||
'uksort' => 1,
|
||||
'usort' => 1,
|
||||
'preg_replace_callback' => 1,
|
||||
'spl_autoload_register' => 0,
|
||||
'iterator_apply' => 1,
|
||||
'call_user_func' => 0,
|
||||
'call_user_func_array' => 0,
|
||||
'register_shutdown_function' => 0,
|
||||
'register_tick_function' => 0,
|
||||
'set_error_handler' => 0,
|
||||
'set_exception_handler' => 0,
|
||||
'session_set_save_handler' => [0, 1, 2, 3, 4, 5],
|
||||
'sqlite_create_aggregate' => [2, 3],
|
||||
'sqlite_create_function' => 2,
|
||||
];
|
||||
|
||||
static $informationDiscosureFunctions = [
|
||||
'phpinfo',
|
||||
'posix_mkfifo',
|
||||
'posix_getlogin',
|
||||
'posix_ttyname',
|
||||
'getenv',
|
||||
'get_current_user',
|
||||
'proc_get_status',
|
||||
'get_cfg_var',
|
||||
'disk_free_space',
|
||||
'disk_total_space',
|
||||
'diskfreespace',
|
||||
'getcwd',
|
||||
'getlastmo',
|
||||
'getmygid',
|
||||
'getmyinode',
|
||||
'getmypid',
|
||||
'getmyuid'
|
||||
];
|
||||
|
||||
static $otherFunctions = [
|
||||
'extract',
|
||||
'parse_str',
|
||||
'putenv',
|
||||
'ini_set',
|
||||
'mail',
|
||||
'header',
|
||||
'proc_nice',
|
||||
'proc_terminate',
|
||||
'proc_close',
|
||||
'pfsockopen',
|
||||
'fsockopen',
|
||||
'apache_child_terminate',
|
||||
'posix_kill',
|
||||
'posix_mkfifo',
|
||||
'posix_setpgid',
|
||||
'posix_setsid',
|
||||
'posix_setuid',
|
||||
];
|
||||
|
||||
if (in_array($name, $commandExecutionFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $codeExecutionFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isset($callbackFunctions[$name])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $informationDiscosureFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $otherFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return static::isFilesystemFunction($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public static function isFilesystemFunction(string $name): bool
|
||||
{
|
||||
static $fileWriteFunctions = [
|
||||
'fopen',
|
||||
'tmpfile',
|
||||
'bzopen',
|
||||
'gzopen',
|
||||
// write to filesystem (partially in combination with reading)
|
||||
'chgrp',
|
||||
'chmod',
|
||||
'chown',
|
||||
'copy',
|
||||
'file_put_contents',
|
||||
'lchgrp',
|
||||
'lchown',
|
||||
'link',
|
||||
'mkdir',
|
||||
'move_uploaded_file',
|
||||
'rename',
|
||||
'rmdir',
|
||||
'symlink',
|
||||
'tempnam',
|
||||
'touch',
|
||||
'unlink',
|
||||
'imagepng',
|
||||
'imagewbmp',
|
||||
'image2wbmp',
|
||||
'imagejpeg',
|
||||
'imagexbm',
|
||||
'imagegif',
|
||||
'imagegd',
|
||||
'imagegd2',
|
||||
'iptcembed',
|
||||
'ftp_get',
|
||||
'ftp_nb_get',
|
||||
];
|
||||
|
||||
static $fileContentFunctions = [
|
||||
'file_get_contents',
|
||||
'file',
|
||||
'filegroup',
|
||||
'fileinode',
|
||||
'fileowner',
|
||||
'fileperms',
|
||||
'glob',
|
||||
'is_executable',
|
||||
'is_uploaded_file',
|
||||
'parse_ini_file',
|
||||
'readfile',
|
||||
'readlink',
|
||||
'realpath',
|
||||
'gzfile',
|
||||
'readgzfile',
|
||||
'stat',
|
||||
'imagecreatefromgif',
|
||||
'imagecreatefromjpeg',
|
||||
'imagecreatefrompng',
|
||||
'imagecreatefromwbmp',
|
||||
'imagecreatefromxbm',
|
||||
'imagecreatefromxpm',
|
||||
'ftp_put',
|
||||
'ftp_nb_put',
|
||||
'hash_update_file',
|
||||
'highlight_file',
|
||||
'show_source',
|
||||
'php_strip_whitespace',
|
||||
];
|
||||
|
||||
static $filesystemFunctions = [
|
||||
// read from filesystem
|
||||
'file_exists',
|
||||
'fileatime',
|
||||
'filectime',
|
||||
'filemtime',
|
||||
'filesize',
|
||||
'filetype',
|
||||
'is_dir',
|
||||
'is_file',
|
||||
'is_link',
|
||||
'is_readable',
|
||||
'is_writable',
|
||||
'is_writeable',
|
||||
'linkinfo',
|
||||
'lstat',
|
||||
//'pathinfo',
|
||||
'getimagesize',
|
||||
'exif_read_data',
|
||||
'read_exif_data',
|
||||
'exif_thumbnail',
|
||||
'exif_imagetype',
|
||||
'hash_file',
|
||||
'hash_hmac_file',
|
||||
'md5_file',
|
||||
'sha1_file',
|
||||
'get_meta_tags',
|
||||
];
|
||||
|
||||
if (in_array($name, $fileWriteFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $fileContentFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $filesystemFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,11 +102,10 @@ class CleanCommand extends Command
|
|||
'vendor/dragonmantank/cron-expression/composer.json',
|
||||
'vendor/dragonmantank/cron-expression/tests',
|
||||
'vendor/dragonmantank/cron-expression/CHANGELOG.md',
|
||||
'vendor/enshrined/svg-sanitize/tests',
|
||||
'vendor/enshrined/svg-sanitize/.gitignore',
|
||||
'vendor/enshrined/svg-sanitize/.travis.yml',
|
||||
'vendor/enshrined/svg-sanitize/composer.json',
|
||||
'vendor/enshrined/svg-sanitize/phpunit.xml',
|
||||
'vendor/rhukster/dom-sanitizer/tests',
|
||||
'vendor/rhukster/dom-sanitizer/.gitignore',
|
||||
'vendor/rhukster/dom-sanitizer/composer.json',
|
||||
'vendor/rhukster/dom-sanitizer/composer.lock',
|
||||
'vendor/erusev/parsedown/composer.json',
|
||||
'vendor/erusev/parsedown/phpunit.xml.dist',
|
||||
'vendor/erusev/parsedown/.travis.yml',
|
||||
|
|
|
|||
|
|
@ -73,11 +73,11 @@ class InstallCommand extends GravCommand
|
|||
$io = $this->getIO();
|
||||
|
||||
$dependencies_file = '.dependencies';
|
||||
$this->destination = $input->getArgument('destination') ?: GRAV_ROOT;
|
||||
$this->destination = $input->getArgument('destination') ?: GRAV_WEBROOT;
|
||||
|
||||
// fix trailing slash
|
||||
$this->destination = rtrim($this->destination, DS) . DS;
|
||||
$this->user_path = $this->destination . USER_PATH;
|
||||
$this->user_path = $this->destination . GRAV_USER_PATH . DS;
|
||||
if ($local_config_file = $this->loadLocalConfig()) {
|
||||
$io->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user