mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
304 Commits
1.4.5
...
1.6.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08f07e663e | ||
|
|
cc61fb5d0f | ||
|
|
776d1419c1 | ||
|
|
d848dcde5d | ||
|
|
4ff0d34aa2 | ||
|
|
c6f6677d6e | ||
|
|
b96e264cc7 | ||
|
|
1d6cdd45eb | ||
|
|
0204906454 | ||
|
|
a7b184abdb | ||
|
|
fff9c657d4 | ||
|
|
6070bfc46e | ||
|
|
6b46c288a6 | ||
|
|
8dd352c5c4 | ||
|
|
8fe3f0c35f | ||
|
|
16edb93efb | ||
|
|
b6b5e329aa | ||
|
|
6e5b173861 | ||
|
|
6010b8e1b4 | ||
|
|
8d0d26ed9b | ||
|
|
65bb683b4a | ||
|
|
75e8a19363 | ||
|
|
1a47b837f5 | ||
|
|
50c211fc10 | ||
|
|
4fb035c512 | ||
|
|
78992df894 | ||
|
|
70c423563f | ||
|
|
2503180e73 | ||
|
|
ed0cb0c8f2 | ||
|
|
a6449cb8ba | ||
|
|
9e776df296 | ||
|
|
15e371564a | ||
|
|
0e973dab07 | ||
|
|
f0e33dc242 | ||
|
|
e67c3c1091 | ||
|
|
d5ce0bd93c | ||
|
|
70b6fb32c4 | ||
|
|
7148e9e136 | ||
|
|
44dbcdf2b1 | ||
|
|
ecaeb32675 | ||
|
|
3216442946 | ||
|
|
9d4471b196 | ||
|
|
e4f6f8bcf2 | ||
|
|
91d8a16db2 | ||
|
|
920b0fcb2e | ||
|
|
760c3e869f | ||
|
|
c48107acd9 | ||
|
|
4671518409 | ||
|
|
41bf943f49 | ||
|
|
6488a0f2fb | ||
|
|
efe45f64bc | ||
|
|
d893dd55ff | ||
|
|
f40c6a8617 | ||
|
|
fb98ca7b19 | ||
|
|
451ec49d9c | ||
|
|
1709eb038c | ||
|
|
e69d6cefee | ||
|
|
7abe01ed8c | ||
|
|
17a371d86a | ||
|
|
5b787d56e6 | ||
|
|
33d98114ba | ||
|
|
ce5f0b7785 | ||
|
|
9204b87f3a | ||
|
|
4a83314095 | ||
|
|
99ea119483 | ||
|
|
c361f16fcf | ||
|
|
b4d4fb900e | ||
|
|
0c3b34e89a | ||
|
|
a549615257 | ||
|
|
c796474bce | ||
|
|
26aea439c6 | ||
|
|
32cfb749af | ||
|
|
b79cbf8975 | ||
|
|
3ef154949d | ||
|
|
007b17a7ba | ||
|
|
8c64434069 | ||
|
|
ff1d5bc965 | ||
|
|
346b8683ac | ||
|
|
7a61b09a85 | ||
|
|
53f5a6fa57 | ||
|
|
95172ce4c1 | ||
|
|
84ecdfee71 | ||
|
|
537c5f4ee8 | ||
|
|
68ee0f7580 | ||
|
|
178193ab1a | ||
|
|
e7ddae713f | ||
|
|
bd2883d63f | ||
|
|
12c3c6c472 | ||
|
|
1c08fa2b1a | ||
|
|
24991dc17d | ||
|
|
9261f105e3 | ||
|
|
51f29e112a | ||
|
|
ca8805683d | ||
|
|
8295bd8243 | ||
|
|
7a4b234c6d | ||
|
|
da95d1bb1e | ||
|
|
a1680ddeaa | ||
|
|
871333d3a0 | ||
|
|
5c81d7863c | ||
|
|
354831338c | ||
|
|
593abccedc | ||
|
|
e5c6788243 | ||
|
|
a358f2953a | ||
|
|
9444b63f8b | ||
|
|
79df293fc4 | ||
|
|
be0573f6ea | ||
|
|
4dc6264c64 | ||
|
|
bbc4fd6c79 | ||
|
|
732ff8ecab | ||
|
|
3a0c7168c9 | ||
|
|
41b7aadbda | ||
|
|
a0e475b277 | ||
|
|
834d6938db | ||
|
|
a7ad34bd99 | ||
|
|
dfabceb3d2 | ||
|
|
5499f2edb6 | ||
|
|
935fb98013 | ||
|
|
665fa6cc9c | ||
|
|
772f07e521 | ||
|
|
2bbf219dc0 | ||
|
|
7cc98b8265 | ||
|
|
1808fd3d6e | ||
|
|
0b5c1dcfa7 | ||
|
|
1369f941f2 | ||
|
|
2101c6d0dc | ||
|
|
1993fc6a2c | ||
|
|
b9b43d1f05 | ||
|
|
8d53cf3c77 | ||
|
|
756ddaa97d | ||
|
|
89f64e423d | ||
|
|
ec5596b1a3 | ||
|
|
2de89e31c0 | ||
|
|
9ca5598b6f | ||
|
|
05863276ef | ||
|
|
5ac518f311 | ||
|
|
41f488f8da | ||
|
|
6cc6e51878 | ||
|
|
78bcf84127 | ||
|
|
6b224823f1 | ||
|
|
2734b2f605 | ||
|
|
1ee88d5836 | ||
|
|
2dfd6b76d8 | ||
|
|
330b2e6a6b | ||
|
|
896c25dc9e | ||
|
|
3b04315a38 | ||
|
|
33fffa6a50 | ||
|
|
dbd825f0b6 | ||
|
|
8ab0078d5a | ||
|
|
c381bc8304 | ||
|
|
fb20b58369 | ||
|
|
906017e0c1 | ||
|
|
266369ee04 | ||
|
|
308ac14dbe | ||
|
|
2a9da76512 | ||
|
|
8e43550841 | ||
|
|
75ac0201d8 | ||
|
|
8d9efe4ff7 | ||
|
|
593400743a | ||
|
|
42ff8eaeb0 | ||
|
|
5c2f9946f8 | ||
|
|
63161e62a2 | ||
|
|
c84983ad5b | ||
|
|
3cee53508e | ||
|
|
fde75e1ed5 | ||
|
|
16d2f607c8 | ||
|
|
816a3ebd93 | ||
|
|
d59fe2fa3c | ||
|
|
ef55e7d219 | ||
|
|
424da520cf | ||
|
|
08cb311e5e | ||
|
|
e1b5875c5b | ||
|
|
7d27206fec | ||
|
|
18d405d798 | ||
|
|
34fa50fcf0 | ||
|
|
ca3cf2ea3c | ||
|
|
76fb11366b | ||
|
|
e4f2808870 | ||
|
|
f7496b5341 | ||
|
|
2f0d600e86 | ||
|
|
fa7e6be95a | ||
|
|
cea43a2d21 | ||
|
|
b7387c8741 | ||
|
|
c83852f4e1 | ||
|
|
ce271cf389 | ||
|
|
ead125d599 | ||
|
|
8ee367e52e | ||
|
|
db03091cff | ||
|
|
6b5849b207 | ||
|
|
ba0a8c4092 | ||
|
|
c8ab5d34f7 | ||
|
|
c9367ba4f3 | ||
|
|
a754f697d7 | ||
|
|
dd75ce515f | ||
|
|
ea83b46bfb | ||
|
|
e7f628233d | ||
|
|
24edf15e16 | ||
|
|
70e65129d7 | ||
|
|
a5e97ef846 | ||
|
|
8a1f0d4932 | ||
|
|
f29997a5cf | ||
|
|
4daec6908c | ||
|
|
79bff58021 | ||
|
|
05028d0d9b | ||
|
|
b4148804e1 | ||
|
|
07f8dfb1c5 | ||
|
|
f3c559f1c7 | ||
|
|
48a3228efd | ||
|
|
be661e8685 | ||
|
|
a0918dfc4f | ||
|
|
d214080974 | ||
|
|
a09c6b1088 | ||
|
|
dfed333e1b | ||
|
|
578e12940b | ||
|
|
7d215f95cf | ||
|
|
5435ee60d8 | ||
|
|
761d79272c | ||
|
|
68a9552877 | ||
|
|
d72eca7fb5 | ||
|
|
ca9dba1372 | ||
|
|
7aa688ecbb | ||
|
|
794db2e3e5 | ||
|
|
ba457f7bf3 | ||
|
|
64715573a1 | ||
|
|
8288551531 | ||
|
|
34cc3781d6 | ||
|
|
4eb986643c | ||
|
|
290e5be534 | ||
|
|
aea26f4db9 | ||
|
|
bf5e742a7f | ||
|
|
9816b538f9 | ||
|
|
bd21b7f966 | ||
|
|
021fbb8ecd | ||
|
|
6d7e9ba107 | ||
|
|
bbfbdec483 | ||
|
|
62a32ab5c5 | ||
|
|
2e3a64fcef | ||
|
|
ecdbff68d8 | ||
|
|
9ca427e369 | ||
|
|
228757a5ba | ||
|
|
027a760ce2 | ||
|
|
0a3cadc6b2 | ||
|
|
04ea069280 | ||
|
|
280d54057c | ||
|
|
036fc2d2af | ||
|
|
ab58cca3f7 | ||
|
|
f883820c6a | ||
|
|
9053f9ab44 | ||
|
|
bd7706a38e | ||
|
|
da7a93527d | ||
|
|
9f7534153e | ||
|
|
3b4296c7a4 | ||
|
|
8e065e1109 | ||
|
|
93f3fa9685 | ||
|
|
27a9390ec7 | ||
|
|
62a8d8b203 | ||
|
|
d7bd0bf1df | ||
|
|
9eded2ef39 | ||
|
|
636bc97d29 | ||
|
|
3ccadded97 | ||
|
|
b82c17ea56 | ||
|
|
a0946c67b9 | ||
|
|
00376d3118 | ||
|
|
e8fd5405a7 | ||
|
|
eae017a30a | ||
|
|
718dfa9b5d | ||
|
|
1976471982 | ||
|
|
11266ce8f8 | ||
|
|
dab595f571 | ||
|
|
5ab956a8ec | ||
|
|
2c82e15fa1 | ||
|
|
2c7d866724 | ||
|
|
a977023e45 | ||
|
|
9c7008e225 | ||
|
|
342aa0ff04 | ||
|
|
d434d51d42 | ||
|
|
f03eb693e6 | ||
|
|
18928d6962 | ||
|
|
f7832e78dc | ||
|
|
68428a714a | ||
|
|
58db31a7d8 | ||
|
|
2917345b56 | ||
|
|
1cfd3482bb | ||
|
|
4d690938a8 | ||
|
|
0b1c18d63e | ||
|
|
f681f1c60b | ||
|
|
27df27d1df | ||
|
|
91e98cd32e | ||
|
|
1cef2a182a | ||
|
|
ad8764897a | ||
|
|
d2e700eea2 | ||
|
|
895e145d82 | ||
|
|
78ab2aa476 | ||
|
|
b9a7341e5e | ||
|
|
261ea62472 | ||
|
|
fae2aa4582 | ||
|
|
fb7230ec9a | ||
|
|
830c9524eb | ||
|
|
ce1e635451 | ||
|
|
8b0c1b7937 | ||
|
|
87b0d80de0 | ||
|
|
b8c61e34c9 | ||
|
|
02555ba3f5 | ||
|
|
2b17bf70de | ||
|
|
f31f7f0962 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -38,7 +38,9 @@ Thumbs.db
|
||||
# phpstorm
|
||||
.idea/*
|
||||
|
||||
# testing stuff
|
||||
tests/_output/*
|
||||
tests/_support/_generated/*
|
||||
tests/cache/*
|
||||
tests/error.log
|
||||
system/templates/testing/*
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
language: php
|
||||
php:
|
||||
- '5.5'
|
||||
- '5.6'
|
||||
- '7.0.21'
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
branches:
|
||||
@@ -54,7 +51,7 @@ before_install:
|
||||
- if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then
|
||||
composer install --dev --prefer-dist;
|
||||
fi
|
||||
- if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "5.6" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
- if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "7.1" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
export TRAVIS_TAG=$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4);
|
||||
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.8 bash)";
|
||||
go get github.com/aktau/github-release;
|
||||
@@ -71,7 +68,7 @@ script:
|
||||
vendor/bin/codecept run;
|
||||
fi
|
||||
- echo "Latest Release Tag - ${TRAVIS_TAG}"
|
||||
- if [ ! -z "$TRAVIS_TAG" ] && [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "5.6" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
- if [ ! -z "$TRAVIS_TAG" ] && [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "7.1" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
FILES="$RT_DEVTOOLS/grav-dist/*.zip";
|
||||
for file in ${FILES[@]}; do
|
||||
NAME=${file##*/};
|
||||
|
||||
161
CHANGELOG.md
161
CHANGELOG.md
@@ -1,3 +1,164 @@
|
||||
# v1.6.0-beta.3
|
||||
## 10/15/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Call `onFatalException` event also on internal PHP errors
|
||||
* Built-in PHP Webserver: log requests before handling them
|
||||
1. [](#bugfix)
|
||||
* Grav 1.6: Scheduler Fallback for never runs and Windows support [#2202](https://github.com/getgrav/grav/pull/2202)
|
||||
|
||||
# v1.6.0-beta.2
|
||||
## 10/09/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added Flex support for custom media tasks
|
||||
1. [](#improved)
|
||||
* Added support for syslog and syslog facility logging (default: 'file')
|
||||
* Improved usability of `System` configuration blueprint with side-tabs
|
||||
1. [](#bugfix)
|
||||
* Fixed asset manager to not add empty assets when they don't exist in the filesystem
|
||||
* Regression: Fixed asset manager methods with default legacy attributes
|
||||
* Update `script` and `style` Twig tags to use the new `Assets` classes
|
||||
* Fixed asset pipeline to rewrite remote URLs as well as local [#2216](https://github.com/getgrav/grav/issues/2216)
|
||||
|
||||
# v1.6.0-beta.1
|
||||
## 10/01/2018
|
||||
|
||||
1. [](#new)
|
||||
* Set minimum requirements to [PHP 7.1.3](https://getgrav.org/blog/raising-php-requirements-2018)
|
||||
* New `Scheduler` functionality for periodic jobs
|
||||
* New `Backup` functionality with multiple backup profiles and scheduler integration
|
||||
* Refactored `Assets Manager` to be more powerful and flexible
|
||||
* Updated Doctrine Collections to 1.5
|
||||
* Updated Doctrine Cache to 1.8
|
||||
* Updated Symfony Components to 4.1
|
||||
* Added a new Deferred Twig extension to allow adding content to Twig blocks after render
|
||||
* Added new Cache purge functionality old cache manually via CLI/Admin as well as scheduler integration
|
||||
* Added new `{% throw 404 'Not Found' %}` twig tag (with custom code/message)
|
||||
* Added `Grav\Framework\File` classes for handling YAML, Markdown, JSON, INI and PHP serialized files
|
||||
* Added `Grav\Framework\Collection\AbstractIndexCollection` class
|
||||
* Added `Grav\Framework\Object\ObjectIndex` class
|
||||
* Added `Grav\Framework\Flex` classes
|
||||
* Added support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar`, `scope@: object` or `scope-ignore@: object` to any field
|
||||
1. [](#improved)
|
||||
* Doctrine filecache is now namespaced with prefix to support purging
|
||||
* Register all page types into `blueprint://pages` stream
|
||||
|
||||
# v1.5.3
|
||||
## 10/08/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added `Utils::getMimeByFilename()`, `Utils::getMimeByLocalFile()` and `Utils::checkFilename()` methods
|
||||
* Added configurable dangerous upload extensions in `security.yaml`
|
||||
1. [](#improved)
|
||||
* Updated vendor libraries to latest
|
||||
|
||||
# v1.5.2
|
||||
## 10/01/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added new `Security` class for Grav security functionality including XSS checks
|
||||
* Added new `bin/grav security` command to scan for security issues
|
||||
* Added new `xss()` Twig function to allow for XSS checks on strings and arrays
|
||||
* Added `onHttpPostFilter` event to allow plugins to globally clean up XSS in the forms and tasks
|
||||
* Added `Deprecated` tab to DebugBar to catch future incompatibilities with later Grav versions
|
||||
* Added deprecation notices for features which will be removed in Grav 2.0
|
||||
1. [](#improved)
|
||||
* Updated vendor libraries to latest
|
||||
1. [](#bugfix)
|
||||
* Allow `$page->slug()` to be called before `$page->init()` without breaking the page
|
||||
* Fix for `Page::translatedLanguages()` to use routes always [#2163](https://github.com/getgrav/grav/issues/2163)
|
||||
* Fixed `nicetime()` twig function
|
||||
* Allow twig tags `{% script %}`, `{% style %}` and `{% switch %}` to be placed outside of blocks
|
||||
* Session expires in 30 mins independent from config settings [login#178](https://github.com/getgrav/grav-plugin-login/issues/178)
|
||||
|
||||
# v1.5.1
|
||||
## 08/23/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added static `Grav\Common\Yaml` class which should be used instead of `Symfony\Component\Yaml\Yaml`
|
||||
1. [](#improved)
|
||||
* Updated deprecated Twig code so it works in both in Twig 1.34+ and Twig 2.4+
|
||||
* Switched to new Grav Yaml class to support Native + Fallback YAML libraries
|
||||
1. [](#bugfix)
|
||||
* Broken handling of user folder in Grav URI object [#2151](https://github.com/getgrav/grav/issues/2151)
|
||||
|
||||
# v1.5.0
|
||||
## 08/17/2018
|
||||
|
||||
1. [](#new)
|
||||
* Set minimum requirements to [PHP 5.6.4](https://getgrav.org/blog/raising-php-requirements-2018)
|
||||
* Updated Doctrine Collections to 1.4
|
||||
* Updated Symfony Components to 3.4 (with compatibility mode to fall back to Symfony YAML 2.8)
|
||||
* Added `Uri::method()` to get current HTTP method (GET/POST etc)
|
||||
* `FormatterInterface`: Added `getSupportedFileExtensions()` and `getDefaultFileExtension()` methods
|
||||
* Added option to disable `SimpleCache` key validation
|
||||
* Added support for multiple repo locations for `bin/grav install` command
|
||||
* Added twig filters for casting values: `|string`, `|int`, `|bool`, `|float`, `|array`
|
||||
* Made `ObjectCollection::matching()` criteria expressions to behave more like in Twig
|
||||
* Criteria: Added support for `LENGTH()`, `LOWER()`, `UPPER()`, `LTRIM()`, `RTRIM()` and `TRIM()`
|
||||
* Added `Grav\Framework\File\Formatter` classes for encoding/decoding YAML, Markdown, JSON, INI and PHP serialized strings
|
||||
* Added `Grav\Framework\Session` class to replace `RocketTheme\Toolbox\Session\Session`
|
||||
* Added `Grav\Common\Media` interfaces and trait; use those in `Page` and `Media` classes
|
||||
* Added `Grav\Common\Page` interface to allow custom page types in the future
|
||||
* Added setting to disable sessions from the site [#2013](https://github.com/getgrav/grav/issues/2013)
|
||||
* Added new `strict_mode` settings in `system.yaml` for compatibility
|
||||
1. [](#improved)
|
||||
* Improved `Utils::url()` to support query strings
|
||||
* Display better exception message if Grav fails to initialize
|
||||
* Added `muted` and `playsinline` support to videos [#2124](https://github.com/getgrav/grav/pull/2124)
|
||||
* Added `MediaTrait::clearMediaCache()` to allow cache to be cleared
|
||||
* Added `MediaTrait::getMediaCache()` to allow custom caching
|
||||
* Improved session handling, allow all session configuration options in `system.session.options`
|
||||
1. [](#bugfix)
|
||||
* Fix broken form nonce logic [#2121](https://github.com/getgrav/grav/pull/2121)
|
||||
* Fixed issue with uppercase extensions and fallback media URLs [#2133](https://github.com/getgrav/grav/issues/2133)
|
||||
* Fixed theme inheritance issue with `camel-case` that includes numbers [#2134](https://github.com/getgrav/grav/issues/2134)
|
||||
* Typo in demo typography page [#2136](https://github.com/getgrav/grav/pull/2136)
|
||||
* Fix for incorrect plugin order in debugger panel
|
||||
* Made `|markdown` filter HTML safe
|
||||
* Fixed bug in `ContentBlock` serialization
|
||||
* Fixed `Route::withQueryParam()` to accept array values
|
||||
* Fixed typo in truncate function [#1943](https://github.com/getgrav/grav/issues/1943)
|
||||
* Fixed blueprint field validation: Allow numeric inputs in text fields
|
||||
|
||||
# v1.4.8
|
||||
## 07/31/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Add Grav version to debug bar messages tab [#2106](https://github.com/getgrav/grav/pull/2106)
|
||||
* Add Nginx config for ddev project to `webserver-configs` [#2117](https://github.com/getgrav/grav/pull/2117)
|
||||
* Vendor library updates
|
||||
1. [](#bugfix)
|
||||
* Don't allow `null` to be set as Page content
|
||||
|
||||
# v1.4.7
|
||||
## 07/13/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Use `getFilename` instead of `getBasename` [#2087](https://github.com/getgrav/grav/issues/2087)
|
||||
1. [](#bugfix)
|
||||
* Fix for modular page preview [#2066](https://github.com/getgrav/grav/issues/2066)
|
||||
* `Page::routeCanonical()` should be string not array [#2069](https://github.com/getgrav/grav/issues/2069)
|
||||
|
||||
# v1.4.6
|
||||
## 06/20/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Manually re-added the improved SSL off-loading that was lost with Grav v1.4.0 merge [#1888](https://github.com/getgrav/grav/pull/1888)
|
||||
* Handle multibyte strings in `truncateLetters()` [#2007](https://github.com/getgrav/grav/pull/2007)
|
||||
* Updated robots.txt to include `/user/images/` folder [#2043](https://github.com/getgrav/grav/pull/2043)
|
||||
* Add getter methods for original and action to the Page object [#2005](https://github.com/getgrav/grav/pull/2005)
|
||||
* Modular template extension follows the master page extension [#2044](https://github.com/getgrav/grav/pull/2044)
|
||||
* Vendor library updates
|
||||
1. [](#bugfix)
|
||||
* Handle `errors.display` system property better in admin plugin [admin#1452](https://github.com/getgrav/grav-plugin-admin/issues/1452)
|
||||
* Fix classes on non-http based protocol links [#2034](https://github.com/getgrav/grav/issues/2034)
|
||||
* Fixed crash on IIS (Windows) with open_basedir in effect [#2053](https://github.com/getgrav/grav/issues/2053)
|
||||
* Fixed incorrect routing with setup.php based base [#1892](https://github.com/getgrav/grav/issues/1892)
|
||||
* Fixed image resource memory deallocation [#2045](https://github.com/getgrav/grav/pull/2045)
|
||||
* Fixed issue with Errors `display:` option not handling integers properly [admin#1452](https://github.com/getgrav/grav-plugin-admin/issues/1452)
|
||||
|
||||
# v1.4.5
|
||||
## 05/15/2018
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ The underlying architecture of Grav is designed to use well-established and _bes
|
||||
|
||||
# Requirements
|
||||
|
||||
- PHP 5.5.9 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
|
||||
- PHP 7.1.3 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
|
||||
- Check the [Apache](https://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](https://learn.getgrav.org/basics/requirements#iis-requirements) requirements
|
||||
|
||||
# QuickStart
|
||||
|
||||
Binary file not shown.
11
bin/grav
11
bin/grav
@@ -6,7 +6,9 @@ if (!file_exists(__DIR__ . '/../vendor')){
|
||||
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
|
||||
}
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Composer;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
@@ -16,9 +18,8 @@ if (!file_exists(__DIR__ . '/../vendor')){
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
$autoload = require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
Grav::instance(array('loader' => $autoload));
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
@@ -38,8 +39,10 @@ $app->addCommands(array(
|
||||
new \Grav\Console\Cli\ComposerCommand(),
|
||||
new \Grav\Console\Cli\SandboxCommand(),
|
||||
new \Grav\Console\Cli\CleanCommand(),
|
||||
new \Grav\Console\Cli\ClearCacheCommand(),
|
||||
new \Grav\Console\Cli\CacheCommand(),
|
||||
new \Grav\Console\Cli\BackupCommand(),
|
||||
new \Grav\Console\Cli\NewProjectCommand(),
|
||||
new \Grav\Console\Cli\SchedulerCommand(),
|
||||
new \Grav\Console\Cli\SecurityCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
@@ -6,46 +6,55 @@
|
||||
"homepage": "http://getgrav.org",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"twig/twig": "~1.24",
|
||||
"erusev/parsedown": "1.6.4",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~2.8",
|
||||
"symfony/console": "~2.8",
|
||||
"symfony/event-dispatcher": "~2.8",
|
||||
"symfony/var-dumper": "~2.8",
|
||||
"symfony/polyfill-iconv": "~1.0",
|
||||
"doctrine/cache": "^1.6",
|
||||
"doctrine/collections": "1.3",
|
||||
"psr/simple-cache": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"filp/whoops": "~2.0",
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "2.*",
|
||||
"donatj/phpuseragentparser": "~0.3",
|
||||
"pimple/pimple": "~3.2",
|
||||
"rockettheme/toolbox": "~1.3",
|
||||
"maximebf/debugbar": "~1.10",
|
||||
"php": ">=7.1.3",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-zip": "*",
|
||||
"league/climate": "^3.2",
|
||||
"symfony/polyfill-iconv": "^1.9",
|
||||
"symfony/polyfill-php72": "^1.9",
|
||||
"symfony/polyfill-php73": "^1.9",
|
||||
|
||||
"psr/simple-cache": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
|
||||
"twig/twig": "~1.35",
|
||||
"erusev/parsedown": "1.6.4",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~4.1",
|
||||
"symfony/console": "~4.1",
|
||||
"symfony/event-dispatcher": "~4.1",
|
||||
"symfony/var-dumper": "~4.1",
|
||||
"symfony/process": "~4.1",
|
||||
"doctrine/cache": "^1.8",
|
||||
"doctrine/collections": "^1.5",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"filp/whoops": "~2.2",
|
||||
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "2.*",
|
||||
"donatj/phpuseragentparser": "~0.10",
|
||||
"pimple/pimple": "~3.2",
|
||||
"rockettheme/toolbox": "~1.4",
|
||||
"maximebf/debugbar": "~1.15",
|
||||
"league/climate": "^3.4",
|
||||
"antoligy/dom-string-iterators": "^1.0",
|
||||
"miljar/php-exif": "^0.6.3",
|
||||
"composer/ca-bundle": "^1.0"
|
||||
"miljar/php-exif": "^0.6.4",
|
||||
"composer/ca-bundle": "^1.0",
|
||||
"dragonmantank/cron-expression": "^1.2",
|
||||
"phive/twig-extensions-deferred": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.1",
|
||||
"phpunit/php-code-coverage": "~2.0",
|
||||
"fzaninotto/faker": "^1.5",
|
||||
"codeception/codeception": "^2.4",
|
||||
"phpunit/php-code-coverage": "~6.0",
|
||||
"fzaninotto/faker": "^1.8",
|
||||
"victorjonsson/markdowndocs": "dev-master"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "5.5.9"
|
||||
"php": "7.1.3"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
|
||||
1563
composer.lock
generated
1563
composer.lock
generated
File diff suppressed because it is too large
Load Diff
37
index.php
37
index.php
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav.Core
|
||||
*
|
||||
@@ -7,35 +8,34 @@
|
||||
*/
|
||||
|
||||
namespace Grav;
|
||||
define('GRAV_PHP_MIN', '5.5.9');
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
die("Please run: <i>bin/grav install</i>");
|
||||
}
|
||||
|
||||
if (PHP_SAPI == 'cli-server') {
|
||||
if (!isset($_SERVER['PHP_CLI_ROUTER'])) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
}
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
\define('GRAV_PHP_MIN', '7.1.3');
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
die(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli-server' && !isset($_SERVER['PHP_CLI_ROUTER'])) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
die('Please run: <i>bin/grav install</i>');
|
||||
}
|
||||
|
||||
// Register the auto-loader.
|
||||
$loader = require_once $autoload;
|
||||
$loader = require $autoload;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
// Set internal encoding if mbstring loaded
|
||||
if (!extension_loaded('mbstring')) {
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
mb_internal_encoding('UTF-8');
|
||||
@@ -50,6 +50,9 @@ $grav = Grav::instance(
|
||||
// Process the page
|
||||
try {
|
||||
$grav->process();
|
||||
} catch (\Error $e) {
|
||||
$grav->fireEvent('onFatalException', new Event(array('exception' => $e)));
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
$grav->fireEvent('onFatalException', new Event(array('exception' => $e)));
|
||||
throw $e;
|
||||
|
||||
@@ -9,3 +9,4 @@ Disallow: /vendor/
|
||||
Disallow: /user/
|
||||
Allow: /user/pages/
|
||||
Allow: /user/themes/
|
||||
Allow: /user/images/
|
||||
|
||||
125
system/blueprints/config/backups.yaml
Normal file
125
system/blueprints/config/backups.yaml
Normal file
@@ -0,0 +1,125 @@
|
||||
title: PLUGIN_ADMIN.BACKUPS
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
|
||||
fields:
|
||||
history_title:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.BACKUPS_HISTORY
|
||||
underline: true
|
||||
|
||||
history:
|
||||
type: backupshistory
|
||||
|
||||
config_title:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.BACKUPS_PURGE_CONFIG
|
||||
underline: true
|
||||
|
||||
purge.trigger:
|
||||
type: select
|
||||
label: Backup Storage Purge Trigger
|
||||
size: medium
|
||||
default: space
|
||||
options:
|
||||
space: Maximum Backup Space
|
||||
number: Maximum Number of Backups
|
||||
time: maximum Rention Time
|
||||
validate:
|
||||
required: true
|
||||
|
||||
purge.max_backups_count:
|
||||
type: number
|
||||
label: Maximum Number of Backups
|
||||
default: 25
|
||||
size: x-small
|
||||
help: "0 is unlimited"
|
||||
validate:
|
||||
min: 0
|
||||
type: number
|
||||
required: true
|
||||
message: Must be a number 0 or greater
|
||||
|
||||
purge.max_backups_space:
|
||||
type: number
|
||||
label: Maximum Backups Space
|
||||
append: in GB
|
||||
size: x-small
|
||||
default: 5
|
||||
validate:
|
||||
min: 1
|
||||
type: number
|
||||
required: true
|
||||
message: Space must be 1GB or greater
|
||||
|
||||
purge.max_backups_time:
|
||||
type: number
|
||||
label: Maximum Rention Time
|
||||
append: in Days
|
||||
size: x-small
|
||||
default: 365
|
||||
validate:
|
||||
min: 7
|
||||
type: number
|
||||
required: true
|
||||
message: Rentenion days must be 7 or greater
|
||||
|
||||
profiles_title:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.BACKUPS_PROFILES
|
||||
underline: true
|
||||
|
||||
profiles:
|
||||
type: list
|
||||
style: vertical
|
||||
label:
|
||||
classes: backups-list compact
|
||||
sort: false
|
||||
|
||||
fields:
|
||||
.name:
|
||||
type: text
|
||||
label: Name
|
||||
placeholder: 'Clear Backup Name'
|
||||
validate:
|
||||
max: 20
|
||||
message: 'Name must be less than 20 characters'
|
||||
required: true
|
||||
.root:
|
||||
type: text
|
||||
label: Root Folder
|
||||
help: Can be an absolute path or a stream
|
||||
placeholder: '/'
|
||||
default: '/'
|
||||
validate:
|
||||
required: true
|
||||
.exclude_paths:
|
||||
type: textarea
|
||||
label: Exclude Paths
|
||||
rows: 5
|
||||
placeholder: "/backup\r/cache\r/images\r/logs\r/tmp"
|
||||
help: Absolute paths to exclude, one per line
|
||||
.exclude_files:
|
||||
type: textarea
|
||||
label: Exclude Files
|
||||
rows: 5
|
||||
placeholder: ".DS_Store\r.git\r.svn\r.hg\r.idea\r.vscode\rnode_modules"
|
||||
help: Specfic Files or Folders to exclude, one per line
|
||||
.schedule:
|
||||
type: toggle
|
||||
label: Enable Scheduled Job
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
.schedule_at:
|
||||
type: cron
|
||||
label: Run Scheduled Job
|
||||
default: '* 3 * * *'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
76
system/blueprints/config/scheduler.yaml
Normal file
76
system/blueprints/config/scheduler.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
title: PLUGIN_ADMIN.SCHEDULER
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
|
||||
fields:
|
||||
|
||||
status_title:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.SCHEDULER_STATUS
|
||||
underline: true
|
||||
|
||||
status:
|
||||
type: cronstatus
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
jobs_title:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.SCHEDULER_JOBS
|
||||
underline: true
|
||||
|
||||
custom_jobs:
|
||||
type: list
|
||||
style: vertical
|
||||
label:
|
||||
classes: cron-job-list compact
|
||||
key: id
|
||||
fields:
|
||||
.id:
|
||||
type: key
|
||||
label: ID
|
||||
placeholder: 'process-name'
|
||||
validate:
|
||||
required: true
|
||||
pattern: '[a-zа-я0-9_\-]+'
|
||||
max: 20
|
||||
message: 'ID must be lowercase with dashes/underscores only and less than 20 characters'
|
||||
.command:
|
||||
type: text
|
||||
label: Command
|
||||
placeholder: 'cd ~;ls -lah;'
|
||||
validate:
|
||||
required: true
|
||||
.args:
|
||||
type: text
|
||||
label: Extra Arguments
|
||||
.at:
|
||||
type: cron
|
||||
label: Run At
|
||||
help: 'Cron formatted "at" syntax'
|
||||
placeholder: '* * * * *'
|
||||
validate:
|
||||
required: true
|
||||
.output:
|
||||
type: text
|
||||
label: Output File
|
||||
help: 'The path/filename of the output file (from the root of the Grav installation)'
|
||||
placeholder: 'logs/ls-cron.out'
|
||||
.output_mode:
|
||||
type: select
|
||||
label: Output Type
|
||||
help: 'Either append to the same file each run, or overwrite the file with each run'
|
||||
default: append
|
||||
options:
|
||||
append: Append
|
||||
overwrite: Overwrite
|
||||
.email:
|
||||
type: text
|
||||
label: Email
|
||||
help: 'Email to send output to. NOTE: requires output file to be set'
|
||||
placeholder: 'notifications@yoursite.com'
|
||||
|
||||
|
||||
|
||||
|
||||
99
system/blueprints/config/security.yaml
Normal file
99
system/blueprints/config/security.yaml
Normal file
@@ -0,0 +1,99 @@
|
||||
title: PLUGIN_ADMIN.SECURITY
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
xss_section:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.XSS_SECURITY
|
||||
underline: true
|
||||
|
||||
xss_whitelist:
|
||||
type: selectize
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.XSS_WHITELIST_PERMISSIONS
|
||||
help: PLUGIN_ADMIN.XSS_WHITELIST_PERMISSIONS_HELP
|
||||
placeholder: 'admin.super'
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
xss_enabled.on_events:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.XSS_ON_EVENTS
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
xss_enabled.invalid_protocols:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.XSS_INVALID_PROTOCOLS
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
xss_enabled.moz_binding:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.XSS_MOZ_BINDINGS
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
xss_enabled.html_inline_styles:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.XSS_HTML_INLINE_STYLES
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
xss_enabled.dangerous_tags:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.XSS_DANGEROUS_TAGS
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
xss_dangerous_tags:
|
||||
type: selectize
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.XSS_DANGEROUS_TAGS_LIST
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
uploads_section:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.UPLOADS_SECURITY
|
||||
underline: true
|
||||
|
||||
|
||||
uploads_dangerous_extensions:
|
||||
type: selectize
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.UPLOADS_DANGEROUS_EXTENSIONS
|
||||
help: PLUGIN_ADMIN.UPLOADS_DANGEROUS_EXTENSIONS_HELP
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,9 @@ form:
|
||||
title: PLUGIN_ADMIN.CONTENT
|
||||
|
||||
fields:
|
||||
xss_check:
|
||||
type: xss
|
||||
|
||||
header.title:
|
||||
type: text
|
||||
autofocus: true
|
||||
|
||||
@@ -94,6 +94,7 @@ form:
|
||||
twofa_secret:
|
||||
type: 2fa_secret
|
||||
outerclasses: 'twofa-secret'
|
||||
markdown: true
|
||||
label: PLUGIN_ADMIN.2FA_SECRET
|
||||
sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
|
||||
|
||||
|
||||
15
system/config/backups.yaml
Normal file
15
system/config/backups.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
purge:
|
||||
trigger: space
|
||||
max_backups_count: 25
|
||||
max_backups_space: 5
|
||||
max_backups_time: 365
|
||||
|
||||
profiles:
|
||||
-
|
||||
name: 'Default Site Backup'
|
||||
root: '/'
|
||||
schedule: false
|
||||
schedule_at: '0 3 * * *'
|
||||
exclude_paths: "/backup\r\n/cache\r\n/images\r\n/logs\r\n/tmp"
|
||||
exclude_files: ".DS_Store\r\n.git\r\n.svn\r\n.hg\r\n.idea\r\n.vscode\r\nnode_modules"
|
||||
|
||||
31
system/config/security.yaml
Normal file
31
system/config/security.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
xss_whitelist: [admin.super] # Whitelist of user access that should 'skip' XSS checking
|
||||
xss_enabled:
|
||||
on_events: true
|
||||
invalid_protocols: true
|
||||
moz_binding: true
|
||||
html_inline_styles: true
|
||||
dangerous_tags: true
|
||||
xss_dangerous_tags:
|
||||
- applet
|
||||
- meta
|
||||
- xml
|
||||
- blink
|
||||
- link
|
||||
- style
|
||||
- script
|
||||
- embed
|
||||
- object
|
||||
- iframe
|
||||
- frame
|
||||
- frameset
|
||||
- ilayer
|
||||
- layer
|
||||
- bgsound
|
||||
- title
|
||||
- base
|
||||
uploads_dangerous_extensions:
|
||||
- php
|
||||
- html
|
||||
- htm
|
||||
- js
|
||||
- exe
|
||||
@@ -3,7 +3,7 @@ default_lang: en # Default language for site (potenti
|
||||
|
||||
author:
|
||||
name: John Appleseed # Default author name
|
||||
email: 'john@email.com' # Default author email
|
||||
email: 'john@example.com' # Default author email
|
||||
|
||||
taxonomies: [category,tag] # Arbitrary list of taxonomy types
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
schemes:
|
||||
image:
|
||||
type: ReadOnlyStream
|
||||
type: Stream
|
||||
paths:
|
||||
- user://images
|
||||
- system://images
|
||||
|
||||
@@ -76,6 +76,7 @@ cache:
|
||||
method: file # Method to check for updates in pages: file|folder|hash|none
|
||||
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
|
||||
prefix: 'g' # Cache prefix string (prevents cache conflicts)
|
||||
purge_at: '0 4 * * *' # How often to purge old cache (using new scheduler)
|
||||
clear_images_by_default: true # By default grav will include processed images in cache clear, this can be disabled
|
||||
cli_compatibility: false # Ensures only non-volatile drivers are used (file, redis, memcache, etc.)
|
||||
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
|
||||
@@ -88,7 +89,7 @@ twig:
|
||||
cache: true # Set to true to enable Twig caching
|
||||
debug: true # Enable Twig debug
|
||||
auto_reload: true # Refresh cache on changes
|
||||
autoescape: false # Autoescape Twig vars
|
||||
autoescape: false # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
|
||||
undefined_functions: true # Allow undefined functions
|
||||
undefined_filters: true # Allow undefined filters
|
||||
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
|
||||
@@ -112,6 +113,11 @@ errors:
|
||||
display: 0 # Display either (1) Full backtrace | (0) Simple Error | (-1) System Error
|
||||
log: true # Log errors to /logs folder
|
||||
|
||||
log:
|
||||
handler: file # Log handler. Currently supported: file | syslog
|
||||
syslog:
|
||||
facility: local6 # Syslog facilities output
|
||||
|
||||
debugger:
|
||||
enabled: false # Enable Grav debugger and following settings
|
||||
shutdown:
|
||||
@@ -141,8 +147,12 @@ session:
|
||||
path:
|
||||
|
||||
gpm:
|
||||
releases: stable # Set to either 'stable' or 'testing'
|
||||
releases: testing # 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
|
||||
|
||||
strict_mode:
|
||||
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
|
||||
twig_compat: true # Grav 1.5+: Enables deprecated Twig autoescape setting (autoescape: false)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.4.5');
|
||||
define('GRAV_TESTING', false);
|
||||
define('GRAV_VERSION', '1.6.0-beta.3');
|
||||
define('GRAV_TESTING', true);
|
||||
define('DS', '/');
|
||||
|
||||
if (!defined('GRAV_PHP_MIN')) {
|
||||
define('GRAV_PHP_MIN', '5.5.9');
|
||||
define('GRAV_PHP_MIN', '7.1.3');
|
||||
}
|
||||
|
||||
// Directories and Paths
|
||||
|
||||
@@ -72,7 +72,6 @@ NICETIME:
|
||||
SEC: sec
|
||||
MIN: min
|
||||
HR: hr
|
||||
DAY: day
|
||||
WK: wk
|
||||
MO: mo
|
||||
YR: yr
|
||||
@@ -88,11 +87,27 @@ NICETIME:
|
||||
SEC_PLURAL: secs
|
||||
MIN_PLURAL: mins
|
||||
HR_PLURAL: hrs
|
||||
DAY_PLURAL: days
|
||||
WK_PLURAL: wks
|
||||
MO_PLURAL: mos
|
||||
YR_PLURAL: yrs
|
||||
DEC_PLURAL: decs
|
||||
CRON:
|
||||
EVERY: every
|
||||
EVERY_HOUR: every hour
|
||||
EVERY_MINUTE: every minute
|
||||
EVERY_DAY_OF_WEEK: every day of the week
|
||||
EVERY_DAY_OF_MONTH: every day of the month
|
||||
EVERY_MONTH: every month
|
||||
TEXT_PERIOD: Every <b />
|
||||
TEXT_MINS: ' at <b /> minute(s) past the hour'
|
||||
TEXT_TIME: ' at <b />:<b />'
|
||||
TEXT_DOW: ' on <b />'
|
||||
TEXT_MONTH: ' of <b />'
|
||||
TEXT_DOM: ' on <b />'
|
||||
ERROR1: The tag %s is not supported!
|
||||
ERROR2: Bad number of elements
|
||||
ERROR3: The jquery_element should be set into jqCron settings
|
||||
ERROR4: Unrecognized expression
|
||||
FORM:
|
||||
VALIDATION_FAIL: <b>Validation failed:</b>
|
||||
INVALID_INPUT: Invalid input in
|
||||
|
||||
@@ -30,7 +30,6 @@ NICETIME:
|
||||
SEC: 秒
|
||||
MIN: 分
|
||||
HR: 時
|
||||
DAY: 日
|
||||
WK: 週
|
||||
MO: 月
|
||||
YR: 年
|
||||
@@ -46,7 +45,6 @@ NICETIME:
|
||||
SEC_PLURAL: 秒
|
||||
MIN_PLURAL: 分
|
||||
HR_PLURAL: 時
|
||||
DAY_PLURAL: 日
|
||||
WK_PLURAL: 週
|
||||
MO_PLURAL: 月
|
||||
YR_PLURAL: 年
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav.Core
|
||||
*
|
||||
@@ -7,7 +8,7 @@
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli-server') {
|
||||
exit('This script cannot be run from browser. Run it from a CLI.');
|
||||
die('This script cannot be run from browser. Run it from a CLI.');
|
||||
}
|
||||
|
||||
$_SERVER['PHP_CLI_ROUTER'] = true;
|
||||
@@ -21,6 +22,6 @@ $_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR .
|
||||
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
|
||||
require 'index.php';
|
||||
|
||||
error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4);
|
||||
|
||||
require 'index.php';
|
||||
|
||||
1438
system/src/Grav/Common/Assets.old.php
Normal file
1438
system/src/Grav/Common/Assets.old.php
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
178
system/src/Grav/Common/Assets/BaseAsset.php
Normal file
178
system/src/Grav/Common/Assets/BaseAsset.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Assets\Traits\AssetUtilsTrait;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
abstract class BaseAsset extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
|
||||
const CSS_ASSET = true;
|
||||
const JS_ASSET = false;
|
||||
|
||||
/** @const Regex to match CSS import content */
|
||||
const CSS_IMPORT_REGEX = '{@import(.*?);}';
|
||||
|
||||
protected $asset;
|
||||
|
||||
protected $asset_type;
|
||||
protected $order;
|
||||
protected $group;
|
||||
protected $position;
|
||||
protected $priority;
|
||||
protected $attributes = [];
|
||||
|
||||
|
||||
protected $timestamp;
|
||||
protected $modified;
|
||||
protected $remote;
|
||||
protected $query = '';
|
||||
|
||||
// Private Bits
|
||||
private $base_url;
|
||||
private $fetch_command;
|
||||
private $css_rewrite = false;
|
||||
private $css_minify = false;
|
||||
|
||||
abstract function render();
|
||||
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_config = [
|
||||
'group' => 'head',
|
||||
'position' => 'pipeline',
|
||||
'priority' => 10,
|
||||
'modified' => null,
|
||||
'asset' => null
|
||||
];
|
||||
|
||||
// Merge base defaults
|
||||
$elements = array_merge($base_config, $elements);
|
||||
|
||||
parent::__construct($elements, $key);
|
||||
}
|
||||
|
||||
public function init($asset, $options)
|
||||
{
|
||||
$config = Grav::instance()['config'];
|
||||
$uri = Grav::instance()['uri'];
|
||||
|
||||
// set attributes
|
||||
foreach ($options as $key => $value) {
|
||||
if ($this->hasProperty($key)) {
|
||||
$this->setProperty($key, $value);
|
||||
} else {
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Do some special stuff for CSS/JS (not inline)
|
||||
if (!Utils::startsWith($this->getType(), 'inline')) {
|
||||
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
|
||||
$this->remote = $this->isRemoteLink($asset);
|
||||
|
||||
// Move this to render?
|
||||
if (!$this->remote) {
|
||||
|
||||
$asset_parts = parse_url($asset);
|
||||
if (isset($asset_parts['query'])) {
|
||||
$this->query = $asset_parts['query'];
|
||||
unset($asset_parts['query']);
|
||||
$asset = Uri::buildUrl($asset_parts);
|
||||
}
|
||||
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($locator->isStream($asset)) {
|
||||
$path = $locator->findResource($asset, true);
|
||||
} else {
|
||||
$path = GRAV_ROOT . $asset;
|
||||
}
|
||||
|
||||
// If local file is missing return
|
||||
if ($path === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = new \SplFileInfo($path);
|
||||
|
||||
$asset = $this->buildLocalLink($file->getPathname());
|
||||
|
||||
$this->modified = $file->isFile() ? $file->getMTime() : false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->asset = $asset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAsset()
|
||||
{
|
||||
return $this->asset;
|
||||
}
|
||||
|
||||
public function getRemote()
|
||||
{
|
||||
return $this->remote;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the last modification time of asset
|
||||
*
|
||||
* @param string $asset the asset string reference
|
||||
*
|
||||
* @return string the last modifcation time or false on error
|
||||
*/
|
||||
// protected function getLastModificationTime($asset)
|
||||
// {
|
||||
// $file = GRAV_ROOT . $asset;
|
||||
// if (Grav::instance()['locator']->isStream($asset)) {
|
||||
// $file = $this->buildLocalLink($asset, true);
|
||||
// }
|
||||
//
|
||||
// return file_exists($file) ? filemtime($file) : false;
|
||||
// }
|
||||
|
||||
/**
|
||||
*
|
||||
* Build local links including grav asset shortcodes
|
||||
*
|
||||
* @param string $asset the asset string reference
|
||||
* @param bool $absolute build absolute asset link
|
||||
*
|
||||
* @return string the final link url to the asset
|
||||
*/
|
||||
protected function buildLocalLink($asset)
|
||||
{
|
||||
if ($asset) {
|
||||
return $this->base_url . ltrim(Utils::replaceFirstOccurrence(GRAV_ROOT, '', $asset), '/');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return ['type' => $this->getType(), 'elements' => $this->getElements()];
|
||||
}
|
||||
}
|
||||
40
system/src/Grav/Common/Assets/Css.php
Normal file
40
system/src/Grav/Common/Assets/Css.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
class Css extends BaseAsset
|
||||
{
|
||||
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'css',
|
||||
'attributes' => [
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet'
|
||||
]
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
|
||||
$buffer = $this->gatherLinks( [$this], self::CSS_ASSET);
|
||||
return "<style>\n" . trim($buffer) . "\n</style>\n";
|
||||
}
|
||||
|
||||
return '<link href="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . ">\n";
|
||||
}
|
||||
}
|
||||
31
system/src/Grav/Common/Assets/InlineCss.php
Normal file
31
system/src/Grav/Common/Assets/InlineCss.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
class InlineCss extends BaseAsset
|
||||
{
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'css',
|
||||
'position' => 'after'
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return '<style' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</style>\n";
|
||||
}
|
||||
}
|
||||
31
system/src/Grav/Common/Assets/InlineJs.php
Normal file
31
system/src/Grav/Common/Assets/InlineJs.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
class InlineJs extends BaseAsset
|
||||
{
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js',
|
||||
'position' => 'after'
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return '<script' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</script>\n";
|
||||
}
|
||||
}
|
||||
35
system/src/Grav/Common/Assets/Js.php
Normal file
35
system/src/Grav/Common/Assets/Js.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
class Js extends BaseAsset
|
||||
{
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js',
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
|
||||
$buffer = $this->gatherLinks( [$this], self::JS_ASSET);
|
||||
return '<script' . $this->renderAttributes() . ">\n" . trim($buffer) . "\n</script>\n";
|
||||
}
|
||||
|
||||
return '<script src="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . "></script>\n";
|
||||
}
|
||||
}
|
||||
283
system/src/Grav/Common/Assets/Pipeline.php
Normal file
283
system/src/Grav/Common/Assets/Pipeline.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Assets\Traits\AssetUtilsTrait;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Pipeline extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
|
||||
const CSS_ASSET = true;
|
||||
const JS_ASSET = false;
|
||||
|
||||
/** @const Regex to match CSS urls */
|
||||
const CSS_URL_REGEX = '{url\(([\'\"]?)(.*?)\1\)}';
|
||||
|
||||
/** @const Regex to match CSS sourcemap comments */
|
||||
const CSS_SOURCEMAP_REGEX = '{\/\*# (.*?) \*\/}';
|
||||
|
||||
/** @const Regex to match CSS import content */
|
||||
const CSS_IMPORT_REGEX = '{@import(.*?);}';
|
||||
|
||||
protected $css_minify;
|
||||
protected $css_minify_windows;
|
||||
protected $css_rewrite;
|
||||
|
||||
protected $js_minify;
|
||||
protected $js_minify_windows;
|
||||
|
||||
protected $base_url;
|
||||
protected $assets_dir;
|
||||
protected $assets_url;
|
||||
protected $timestamp;
|
||||
protected $attributes;
|
||||
protected $query;
|
||||
protected $asset;
|
||||
|
||||
protected $css_pipeline_include_externals;
|
||||
protected $js_pipeline_include_externals;
|
||||
|
||||
/**
|
||||
* Closure used by the pipeline to fetch assets.
|
||||
*
|
||||
* Useful when file_get_contents() function is not available in your PHP
|
||||
* installation or when you want to apply any kind of preprocessing to
|
||||
* your assets before they get pipelined.
|
||||
*
|
||||
* The closure will receive as the only parameter a string with the path/URL of the asset and
|
||||
* it should return the content of the asset file as a string.
|
||||
*
|
||||
* @var \Closure
|
||||
*/
|
||||
protected $fetch_command;
|
||||
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
parent::__construct($elements, $key);
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = Grav::instance()['uri'];
|
||||
|
||||
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
|
||||
$this->assets_dir = $locator->findResource('asset://') . DS;
|
||||
$this->assets_url = $locator->findResource('asset://', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify and concatenate CSS
|
||||
*
|
||||
* @param array $assets
|
||||
* @param string $group
|
||||
* @param array $attributes
|
||||
* @param array $no_pipeline
|
||||
*
|
||||
* @return bool|string URL or generated content if available, else false
|
||||
*/
|
||||
public function renderCss($assets, $group, $attributes = [], &$no_pipeline = [])
|
||||
{
|
||||
// temporary list of assets to pipeline
|
||||
$inline_group = false;
|
||||
|
||||
if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
|
||||
$inline_group = true;
|
||||
unset($attributes['loading']);
|
||||
}
|
||||
|
||||
// Store Attributes
|
||||
$this->attributes = array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes);
|
||||
|
||||
// Compute uid based on assets and timestamp
|
||||
$json_assets = json_encode($assets);
|
||||
$uid = md5($json_assets . $this->css_minify . $this->css_rewrite . $group);
|
||||
$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";
|
||||
} else {
|
||||
|
||||
foreach ($assets as $id => $asset) {
|
||||
if ($this->css_pipeline_include_externals === false && $asset->getRemote()) {
|
||||
$no_pipeline[$id] = $asset;
|
||||
unset($assets[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
//if nothing found get out of here!
|
||||
if (empty($assets) && empty($no_pipeline)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Concatenate files
|
||||
$buffer = $this->gatherLinks($assets, self::CSS_ASSET, $no_pipeline);
|
||||
|
||||
// Minify if required
|
||||
if ($this->shouldMinify('css')) {
|
||||
$minifier = new \MatthiasMullie\Minify\CSS();
|
||||
$minifier->add($buffer);
|
||||
$buffer = $minifier->minify();
|
||||
}
|
||||
|
||||
// Write file
|
||||
if (\strlen(trim($buffer)) > 0) {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if ($inline_group) {
|
||||
$output = "<style>\n" . $buffer . "\n</style>\n";
|
||||
} else {
|
||||
$this->asset = $relative_path;
|
||||
$output = '<link href="' . $relative_path . $this->renderQueryString() . '"' . $this->renderAttributes() . ">\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify and concatenate JS files.
|
||||
*
|
||||
* @param array $assets
|
||||
* @param string $group
|
||||
* @param array $attributes
|
||||
* @param array $no_pipeline
|
||||
*
|
||||
* @return bool|string URL or generated content if available, else false
|
||||
*/
|
||||
public function renderJs($assets, $group, $attributes = [], &$no_pipeline = [])
|
||||
{
|
||||
// temporary list of assets to pipeline
|
||||
$inline_group = false;
|
||||
|
||||
if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
|
||||
$inline_group = true;
|
||||
unset($attributes['loading']);
|
||||
}
|
||||
|
||||
// Store Attributes
|
||||
$this->attributes = $attributes;
|
||||
|
||||
// Compute uid based on assets and timestamp
|
||||
$json_assets = json_encode($assets);
|
||||
$uid = md5($json_assets . $this->js_minify . $group);
|
||||
$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";
|
||||
} else {
|
||||
|
||||
foreach ($assets as $id => $asset) {
|
||||
if ($this->js_pipeline_include_externals === false && $asset->getRemote()) {
|
||||
$no_pipeline[$id] = $asset;
|
||||
unset($assets[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
//if nothing found get out of here!
|
||||
if (empty($assets) && empty($no_pipeline)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Concatenate files
|
||||
$buffer = $this->gatherLinks($assets, self::JS_ASSET, $no_pipeline);
|
||||
|
||||
// Minify if required
|
||||
if ($this->shouldMinify('js')) {
|
||||
$minifier = new \MatthiasMullie\Minify\JS();
|
||||
$minifier->add($buffer);
|
||||
$buffer = $minifier->minify();
|
||||
}
|
||||
|
||||
// Write file
|
||||
if (\strlen(trim($buffer)) > 0) {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if ($inline_group) {
|
||||
$output = "<script" . $this->renderAttributes(). ">\n" . $buffer . "\n</script>\n";
|
||||
} else {
|
||||
$this->asset = $relative_path;
|
||||
$output = "<script src=\"" . $relative_path . $this->renderQueryString() . "\"" . $this->renderAttributes() . "></script>\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds relative CSS urls() and rewrites the URL with an absolute one
|
||||
*
|
||||
* @param string $file the css source file
|
||||
* @param string $dir , $local relative path to the css file
|
||||
* @param boolean $local is this a local or remote asset
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function cssRewrite($file, $dir, $local)
|
||||
{
|
||||
// Strip any sourcemap comments
|
||||
$file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file);
|
||||
|
||||
// Find any css url() elements, grab the URLs and calculate an absolute path
|
||||
// Then replace the old url with the new one
|
||||
$file = (string)preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($dir, $local) {
|
||||
|
||||
$old_url = $matches[2];
|
||||
|
||||
// Ensure link is not rooted to webserver, a data URL, or to a remote host
|
||||
if (Utils::startsWith($old_url, '/') || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$new_url = ($local ? $this->base_url: '') . ltrim(Utils::normalizePath($dir . '/' . $old_url), '/');
|
||||
|
||||
return str_replace($old_url, $new_url, $matches[0]);
|
||||
}, $file);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function shouldMinify($type = 'css')
|
||||
{
|
||||
$check = $type . '_minify';
|
||||
$win_check = $type . '_minify_windows';
|
||||
|
||||
$minify = (bool) $this->$check;
|
||||
|
||||
// If this is a Windows server, and minify_windows is false (default value) skip the
|
||||
// minification process because it will cause Apache to die/crash due to insufficient
|
||||
// ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689
|
||||
if (stripos(php_uname('s'), 'WIN') === 0 && !$this->{$win_check}) {
|
||||
$minify = false;
|
||||
}
|
||||
|
||||
return $minify;
|
||||
}
|
||||
}
|
||||
186
system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
Normal file
186
system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets.Traits
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
trait AssetUtilsTrait
|
||||
{
|
||||
/**
|
||||
* Determine whether a link is local or remote.
|
||||
* Understands both "http://" and "https://" as well as protocol agnostic links "//"
|
||||
*
|
||||
* @param string $link
|
||||
* @return bool
|
||||
*/
|
||||
public static function isRemoteLink($link)
|
||||
{
|
||||
$base = Grav::instance()['uri']->rootUrl(true);
|
||||
|
||||
// sanity check for local URLs with absolute URL's enabled
|
||||
if (Utils::startsWith($link, $base)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (0 === strpos($link, 'http://') || 0 === strpos($link, 'https://') || 0 === strpos($link, '//'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and concatenate the content of several links.
|
||||
*
|
||||
* @param array $assets
|
||||
* @param bool $css
|
||||
* @param array $no_pipeline
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function gatherLinks(array $assets, $css = true, &$no_pipeline = [])
|
||||
{
|
||||
$buffer = '';
|
||||
|
||||
|
||||
foreach ($assets as $id => $asset) {
|
||||
$local = true;
|
||||
|
||||
$link = $asset->getAsset();
|
||||
$relative_path = $link;
|
||||
|
||||
if ($this->isRemoteLink($link)) {
|
||||
$local = false;
|
||||
if (0 === strpos($link, '//')) {
|
||||
$link = 'http:' . $link;
|
||||
}
|
||||
$relative_dir = dirname($relative_path);
|
||||
} else {
|
||||
// Fix to remove relative dir if grav is in one
|
||||
if (($this->base_url !== '/') && Utils::startsWith($relative_path, $this->base_url)) {
|
||||
$base_url = '#' . preg_quote($this->base_url, '#') . '#';
|
||||
$relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
|
||||
}
|
||||
|
||||
$relative_dir = dirname($relative_path);
|
||||
$link = ROOT_DIR . $relative_path;
|
||||
}
|
||||
|
||||
$file = ($this->fetch_command instanceof \Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
|
||||
|
||||
// No file found, skip it...
|
||||
if ($file === false) {
|
||||
if (!$local) { // Assume we coudln't download this file for some reason assume it's not pipeline compatible
|
||||
$no_pipeline[$id] = $asset;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Double check last character being
|
||||
if (!$css) {
|
||||
$file = rtrim($file, ' ;') . ';';
|
||||
}
|
||||
|
||||
// If this is CSS + the file is local + rewrite enabled
|
||||
if ($css && $this->css_rewrite) {
|
||||
$file = $this->cssRewrite($file, $relative_dir, $local);
|
||||
}
|
||||
|
||||
$file = rtrim($file) . PHP_EOL;
|
||||
$buffer .= $file;
|
||||
}
|
||||
|
||||
// Pull out @imports and move to top
|
||||
if ($css) {
|
||||
$buffer = $this->moveImports($buffer);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves @import statements to the top of the file per the CSS specification
|
||||
*
|
||||
* @param string $file the file containing the combined CSS files
|
||||
*
|
||||
* @return string the modified file with any @imports at the top of the file
|
||||
*/
|
||||
protected function moveImports($file)
|
||||
{
|
||||
$imports = [];
|
||||
|
||||
$file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) {
|
||||
$imports[] = $matches[0];
|
||||
|
||||
return '';
|
||||
}, $file);
|
||||
|
||||
return implode("\n", $imports) . "\n\n" . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Build an HTML attribute string from an array.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderAttributes()
|
||||
{
|
||||
$html = '';
|
||||
$no_key = ['loading'];
|
||||
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
if (is_numeric($key)) {
|
||||
$key = $value;
|
||||
}
|
||||
if (\is_array($value)) {
|
||||
$value = implode(' ', $value);
|
||||
}
|
||||
|
||||
if (\in_array($key, $no_key, true)) {
|
||||
$element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
|
||||
} else {
|
||||
$element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
|
||||
}
|
||||
|
||||
$html .= ' ' . $element;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Querystring
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderQueryString($asset = null)
|
||||
{
|
||||
$querystring = '';
|
||||
|
||||
$asset = $asset ?? $this->asset;
|
||||
|
||||
if (!empty($this->query)) {
|
||||
if (Utils::contains($asset, '?')) {
|
||||
$querystring .= '&' . $this->query;
|
||||
} else {
|
||||
$querystring .= '?' . $this->query;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->timestamp) {
|
||||
if (Utils::contains($asset, '?') || $querystring) {
|
||||
$querystring .= '&' . $this->timestamp;
|
||||
} else {
|
||||
$querystring .= '?' . $this->timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return $querystring;
|
||||
}
|
||||
}
|
||||
110
system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
Normal file
110
system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets.Traits
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Grav\Common\Assets;
|
||||
|
||||
trait LegacyAssetsTrait
|
||||
{
|
||||
|
||||
protected function unifyLegacyArguments($args, $type = Assets::CSS_TYPE)
|
||||
{
|
||||
// First argument is always the asset
|
||||
array_shift($args);
|
||||
|
||||
if (\count($args) === 0) {
|
||||
return [];
|
||||
}
|
||||
if (\count($args) === 1 && \is_array($args[0])) {
|
||||
return $args[0];
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case(Assets::INLINE_CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
case(Assets::JS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'loading' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
case(Assets::INLINE_JS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null, 'attributes' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
|
||||
// special case to handle old attributes being passed in
|
||||
if (isset($arguments['attributes'])) {
|
||||
$old_attributes = $arguments['attributes'];
|
||||
$arguments = array_merge($arguments, $old_attributes);
|
||||
}
|
||||
unset($arguments['attributes']);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
case(Assets::CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
protected function createArgumentsFromLegacy(array $args, array $defaults)
|
||||
{
|
||||
// Remove arguments with old default values.
|
||||
$arguments = [];
|
||||
foreach ($args as $arg) {
|
||||
$default = current($defaults);
|
||||
if ($arg !== $default) {
|
||||
$arguments[key($defaults)] = $arg;
|
||||
}
|
||||
next($defaults);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper for async loading of JavaScript
|
||||
*
|
||||
* @param $asset
|
||||
* @param int $priority
|
||||
* @param bool $pipeline
|
||||
* @param string $group name of the group
|
||||
*
|
||||
* @deprecated Please use dynamic method with ['loading' => 'async']
|
||||
*
|
||||
* @return \Grav\Common\Assets
|
||||
*/
|
||||
public function addAsyncJs($asset, $priority = 10, $pipeline = true, $group = 'head')
|
||||
{
|
||||
return $this->addJs($asset, $priority, $pipeline, 'async', $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper for deferred loading of JavaScript
|
||||
*
|
||||
* @param $asset
|
||||
* @param int $priority
|
||||
* @param bool $pipeline
|
||||
* @param string $group name of the group
|
||||
*
|
||||
* @deprecated Please use dynamic method with ['loading' => 'defer']
|
||||
*
|
||||
* @return \Grav\Common\Assets
|
||||
*/
|
||||
public function addDeferJs($asset, $priority = 10, $pipeline = true, $group = 'head')
|
||||
{
|
||||
return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
|
||||
}
|
||||
|
||||
}
|
||||
341
system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
Normal file
341
system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Assets.Traits
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
trait TestingAssetsTrait
|
||||
{
|
||||
/**
|
||||
* Determines if an asset exists as a collection, CSS or JS reference
|
||||
*
|
||||
* @param $asset
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($asset)
|
||||
{
|
||||
return isset($this->collections[$asset]) || isset($this->assets_css[$asset]) || isset($this->assets_js[$asset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the array of all the registered collections
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCollections()
|
||||
{
|
||||
return $this->collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the array of collections explicitly
|
||||
*
|
||||
* @param $collections
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCollection($collections)
|
||||
{
|
||||
$this->collections = $collections;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the array of all the registered CSS assets
|
||||
* If a $key is provided, it will try to return only that asset
|
||||
* else it will return null
|
||||
*
|
||||
* @param null|string $key the asset key
|
||||
* @return array
|
||||
*/
|
||||
public function getCss($key = null)
|
||||
{
|
||||
if (null !== $key) {
|
||||
$asset_key = md5($key);
|
||||
|
||||
return $this->assets_css[$asset_key] ?? null;
|
||||
}
|
||||
|
||||
return $this->assets_css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the array of all the registered JS assets
|
||||
* If a $key is provided, it will try to return only that asset
|
||||
* else it will return null
|
||||
*
|
||||
* @param null|string $key the asset key
|
||||
* @return array
|
||||
*/
|
||||
public function getJs($key = null)
|
||||
{
|
||||
if (null !== $key) {
|
||||
$asset_key = md5($key);
|
||||
|
||||
return $this->assets_js[$asset_key] ?? null;
|
||||
}
|
||||
|
||||
return $this->assets_js;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whole array of CSS assets
|
||||
*
|
||||
* @param $css
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCss($css)
|
||||
{
|
||||
$this->assets_css = $css;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whole array of JS assets
|
||||
*
|
||||
* @param $js
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setJs($js)
|
||||
{
|
||||
$this->assets_js = $js;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the CSS array if set
|
||||
*
|
||||
* @param string $key The asset key
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeCss($key)
|
||||
{
|
||||
$asset_key = md5($key);
|
||||
if (isset($this->assets_css[$asset_key])) {
|
||||
unset($this->assets_css[$asset_key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the JS array if set
|
||||
*
|
||||
* @param string $key The asset key
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeJs($key)
|
||||
{
|
||||
$asset_key = md5($key);
|
||||
if (isset($this->assets_js[$asset_key])) {
|
||||
unset($this->assets_js[$asset_key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of CSS Pipeline
|
||||
*
|
||||
* @param boolean $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCssPipeline($value)
|
||||
{
|
||||
$this->css_pipeline = (bool)$value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of JS Pipeline
|
||||
*
|
||||
* @param boolean $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setJsPipeline($value)
|
||||
{
|
||||
$this->js_pipeline = (bool)$value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->resetCss();
|
||||
$this->resetJs();
|
||||
$this->setCssPipeline(false);
|
||||
$this->setJsPipeline(false);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset JavaScript assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetJs()
|
||||
{
|
||||
$this->assets_js = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset CSS assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCss()
|
||||
{
|
||||
$this->assets_css = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set's a timestamp for assets
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
public function setTimestamp($value)
|
||||
{
|
||||
$this->timestamp = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for assets
|
||||
*
|
||||
* @param bool $include_join
|
||||
* @return string
|
||||
*/
|
||||
public function getTimestamp($include_join = true)
|
||||
{
|
||||
if ($this->timestamp) {
|
||||
return $include_join ? '?' . $this->timestamp : $this->timestamp;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all assets matching $pattern within $directory.
|
||||
*
|
||||
* @param string $directory Relative to the Grav root path, or a stream identifier
|
||||
* @param string $pattern (regex)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
|
||||
{
|
||||
$root_dir = rtrim(ROOT_DIR, '/');
|
||||
|
||||
// Check if $directory is a stream.
|
||||
if (strpos($directory, '://')) {
|
||||
$directory = Grav::instance()['locator']->findResource($directory, null);
|
||||
}
|
||||
|
||||
// Get files
|
||||
$files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/');
|
||||
|
||||
// No luck? Nothing to do
|
||||
if (!$files) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Add CSS files
|
||||
if ($pattern === self::CSS_REGEX) {
|
||||
foreach ($files as $file) {
|
||||
$this->addCss($file);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Add JavaScript files
|
||||
if ($pattern === self::JS_REGEX) {
|
||||
foreach ($files as $file) {
|
||||
$this->addJs($file);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Unknown pattern.
|
||||
foreach ($files as $asset) {
|
||||
$this->add($asset);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all JavaScript assets within $directory
|
||||
*
|
||||
* @param string $directory Relative to the Grav root path, or a stream identifier
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirJs($directory)
|
||||
{
|
||||
return $this->addDir($directory, self::JS_REGEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all CSS assets within $directory
|
||||
*
|
||||
* @param string $directory Relative to the Grav root path, or a stream identifier
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirCss($directory)
|
||||
{
|
||||
return $this->addDir($directory, self::CSS_REGEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively get files matching $pattern within $directory.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $pattern (regex)
|
||||
* @param string $ltrim Will be trimmed from the left of the file path
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function rglob($directory, $pattern, $ltrim = null)
|
||||
{
|
||||
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory,
|
||||
\FilesystemIterator::SKIP_DOTS)), $pattern);
|
||||
$offset = \strlen($ltrim);
|
||||
$files = [];
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = substr($file->getPathname(), $offset);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
250
system/src/Grav/Common/Backup/Backups.php
Normal file
250
system/src/Grav/Common/Backup/Backups.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Backup
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Backup;
|
||||
|
||||
use Grav\Common\Filesystem\Archiver;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Scheduler\Job;
|
||||
use Grav\Common\Scheduler\Scheduler;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Backups
|
||||
{
|
||||
const BACKUP_FILENAME_REGEXZ = "#(.*)--(\d*).zip#";
|
||||
|
||||
const BACKUP_DATE_FORMAT = 'YmdHis';
|
||||
|
||||
protected static $backup_dir;
|
||||
|
||||
protected static $backups = null;
|
||||
|
||||
public function init()
|
||||
{
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = Grav::instance()['events'];
|
||||
$dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
|
||||
Grav::instance()->fireEvent('onBackupsInitialized', new Event(['backups' => $this]));
|
||||
}
|
||||
|
||||
public function setup()
|
||||
{
|
||||
if (is_null(static::$backup_dir)) {
|
||||
static::$backup_dir = Grav::instance()['locator']->findResource('backup://', true, true);
|
||||
Folder::create(static::$backup_dir);
|
||||
}
|
||||
}
|
||||
|
||||
public function onSchedulerInitialized(Event $event)
|
||||
{
|
||||
/** @var Scheduler $scheduler */
|
||||
$scheduler = $event['scheduler'];
|
||||
|
||||
/** @var Inflector $inflector */
|
||||
$inflector = Grav::instance()['inflector'];
|
||||
|
||||
foreach ($this->getBackupProfiles() as $id => $profile) {
|
||||
$at = $profile['schedule_at'];
|
||||
$name = $inflector->hyphenize($profile['name']);
|
||||
$logs = 'logs/backup-' . $name . '.out';
|
||||
/** @var Job $job */
|
||||
$job = $scheduler->addFunction('Grav\Common\Backup\Backups::backup', [$id], $name );
|
||||
$job->at($at);
|
||||
$job->output($logs);
|
||||
}
|
||||
}
|
||||
|
||||
public function getBackupDownloadUrl($backup, $base_url)
|
||||
{
|
||||
$param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
|
||||
$download = urlencode(base64_encode($backup));
|
||||
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim($base_url,
|
||||
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function getBackupProfiles()
|
||||
{
|
||||
return Grav::instance()['config']->get('backups.profiles');
|
||||
}
|
||||
|
||||
public static function getPurgeConfig()
|
||||
{
|
||||
return Grav::instance()['config']->get('backups.purge');
|
||||
}
|
||||
|
||||
public function getBackupNames()
|
||||
{
|
||||
return array_column($this->getBackupProfiles(), 'name');
|
||||
}
|
||||
|
||||
public static function getTotalBackupsSize()
|
||||
{
|
||||
$backups = static::getAvailableBackups();
|
||||
$size = array_sum(array_column($backups, 'size'));
|
||||
|
||||
return $size ?? 0;
|
||||
}
|
||||
|
||||
public static function getAvailableBackups($force = false)
|
||||
{
|
||||
if ($force || is_null(static::$backups)) {
|
||||
static::$backups = [];
|
||||
$backups_itr = new \GlobIterator(static::$backup_dir . '/*.zip', \FilesystemIterator::KEY_AS_FILENAME);
|
||||
$inflector = Grav::instance()['inflector'];
|
||||
$long_date_format = DATE_RFC2822;
|
||||
|
||||
/**
|
||||
* @var string $name
|
||||
* @var \SplFileInfo $file
|
||||
*/
|
||||
foreach ($backups_itr as $name => $file) {
|
||||
|
||||
if (preg_match(static::BACKUP_FILENAME_REGEXZ, $name, $matches)) {
|
||||
$date = \DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]);
|
||||
$timestamp = $date->getTimestamp();
|
||||
$backup = new \stdClass();
|
||||
$backup->title = $inflector->titleize($matches[1]);
|
||||
$backup->time = $date;
|
||||
$backup->date = $date->format($long_date_format);
|
||||
$backup->filename = $name;
|
||||
$backup->path = $file->getPathname();
|
||||
$backup->size = $file->getSize();
|
||||
static::$backups[$timestamp] = $backup;
|
||||
}
|
||||
}
|
||||
// Reverse Key Sort to get in reverse date order
|
||||
krsort(static::$backups);
|
||||
}
|
||||
|
||||
return static::$backups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup
|
||||
*
|
||||
* @param int $id
|
||||
* @param callable|null $status
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public static function backup($id = 0, callable $status = null)
|
||||
{
|
||||
$profiles = static::getBackupProfiles();
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if (isset($profiles[$id])) {
|
||||
$backup = (object) $profiles[$id];
|
||||
} else {
|
||||
throw new \RuntimeException('No backups defined...');
|
||||
}
|
||||
|
||||
$name = Grav::instance()['inflector']->underscorize($backup->name);
|
||||
$date = date(static::BACKUP_DATE_FORMAT, time());
|
||||
$filename = trim($name, '_') . '--' . $date . '.zip';
|
||||
$destination = static::$backup_dir . DS . $filename;
|
||||
$max_execution_time = ini_set('max_execution_time', 600);
|
||||
$backup_root = $backup->root;
|
||||
|
||||
if ($locator->isStream($backup_root)) {
|
||||
$backup_root = $locator->findResource($backup_root);
|
||||
} else {
|
||||
$backup_root = rtrim(GRAV_ROOT . $backup_root, '/');
|
||||
}
|
||||
|
||||
if (!file_exists($backup_root)) {
|
||||
throw new \RuntimeException("Backup location: " . $backup_root . ' does not exist...');
|
||||
}
|
||||
|
||||
$options = [
|
||||
'exclude_files' => static::convertExclude($backup->exclude_files ?? ''),
|
||||
'exclude_paths' => static::convertExclude($backup->exclude_paths ?? ''),
|
||||
];
|
||||
|
||||
/** @var Archiver $archiver */
|
||||
$archiver = Archiver::create('zip');
|
||||
$archiver->setArchive($destination)->setOptions($options)->compress($backup_root, $status)->addEmptyFolders($options['exclude_paths'], $status);
|
||||
|
||||
$status && $status([
|
||||
'type' => 'message',
|
||||
'message' => 'Done...',
|
||||
]);
|
||||
|
||||
$status && $status([
|
||||
'type' => 'progress',
|
||||
'complete' => true
|
||||
]);
|
||||
|
||||
if ($max_execution_time !== false) {
|
||||
ini_set('max_execution_time', $max_execution_time);
|
||||
}
|
||||
|
||||
// Log the backup
|
||||
Grav::instance()['log']->error('Backup Created: ' . $destination);
|
||||
|
||||
// Fire Finished event
|
||||
Grav::instance()->fireEvent('onBackupFinished', new Event(['backup' => $destination]));
|
||||
|
||||
// Purge anything required
|
||||
static::purge();
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
public static function purge()
|
||||
{
|
||||
$purge_config = static::getPurgeConfig();
|
||||
$trigger = $purge_config['trigger'];
|
||||
$backups = static::getAvailableBackups(true);
|
||||
|
||||
switch ($trigger)
|
||||
{
|
||||
case 'number':
|
||||
$backups_count = count($backups);
|
||||
if ($backups_count > $purge_config['max_backups_count']) {
|
||||
$last = end($backups);
|
||||
unlink ($last->path);
|
||||
static::purge();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'time':
|
||||
$last = end($backups);
|
||||
$now = new \DateTime();
|
||||
$interval = $now->diff($last->time);
|
||||
if ($interval->days > $purge_config['max_backups_time']) {
|
||||
unlink($last->path);
|
||||
static::purge();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$used_space = static::getTotalBackupsSize();
|
||||
$max_space = $purge_config['max_backups_space'] * 1024 * 1024 * 1024;
|
||||
if ($used_space > $max_space) {
|
||||
$last = end($backups);
|
||||
unlink($last->path);
|
||||
static::purge();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected static function convertExclude($exclude)
|
||||
{
|
||||
$lines = preg_split("/[\s,]+/", $exclude);
|
||||
return array_map('trim', $lines, array_fill(0,count($lines),'/'));
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Backup
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Backup;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Inflector;
|
||||
|
||||
class ZipBackup
|
||||
{
|
||||
protected static $ignorePaths = [
|
||||
'backup',
|
||||
'cache',
|
||||
'images',
|
||||
'logs',
|
||||
'tmp'
|
||||
];
|
||||
|
||||
protected static $ignoreFolders = [
|
||||
'.git',
|
||||
'.svn',
|
||||
'.hg',
|
||||
'.idea',
|
||||
'node_modules'
|
||||
];
|
||||
|
||||
/**
|
||||
* Backup
|
||||
*
|
||||
* @param string|null $destination
|
||||
* @param callable|null $messager
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public static function backup($destination = null, callable $messager = null)
|
||||
{
|
||||
if (!$destination) {
|
||||
$destination = Grav::instance()['locator']->findResource('backup://', true);
|
||||
|
||||
if (!$destination) {
|
||||
throw new \RuntimeException('The backup folder is missing.');
|
||||
}
|
||||
}
|
||||
|
||||
$name = substr(strip_tags(Grav::instance()['config']->get('site.title', basename(GRAV_ROOT))), 0, 20);
|
||||
|
||||
$inflector = new Inflector();
|
||||
|
||||
if (is_dir($destination)) {
|
||||
$date = date('YmdHis', time());
|
||||
$filename = trim($inflector->hyphenize($name), '-') . '-' . $date . '.zip';
|
||||
$destination = rtrim($destination, DS) . DS . $filename;
|
||||
}
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => 'Creating new Backup "' . $destination . '"'
|
||||
]);
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => ''
|
||||
]);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip->open($destination, \ZipArchive::CREATE);
|
||||
|
||||
$max_execution_time = ini_set('max_execution_time', 600);
|
||||
|
||||
static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'progress',
|
||||
'percentage' => false,
|
||||
'complete' => true
|
||||
]);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => ''
|
||||
]);
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => 'Saving and compressing archive...'
|
||||
]);
|
||||
|
||||
$zip->close();
|
||||
|
||||
if ($max_execution_time !== false) {
|
||||
ini_set('max_execution_time', $max_execution_time);
|
||||
}
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $folder
|
||||
* @param $zipFile
|
||||
* @param $exclusiveLength
|
||||
* @param $messager
|
||||
*/
|
||||
private static function folderToZip($folder, \ZipArchive $zipFile, $exclusiveLength, callable $messager = null)
|
||||
{
|
||||
$handle = opendir($folder);
|
||||
while (false !== $f = readdir($handle)) {
|
||||
if ($f !== '.' && $f !== '..') {
|
||||
$filePath = "$folder/$f";
|
||||
// Remove prefix from file path before add to zip.
|
||||
$localPath = substr($filePath, $exclusiveLength);
|
||||
|
||||
if (in_array($f, static::$ignoreFolders)) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($localPath, static::$ignorePaths)) {
|
||||
$zipFile->addEmptyDir($f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_file($filePath)) {
|
||||
$zipFile->addFile($filePath, $localPath);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'progress',
|
||||
'percentage' => false,
|
||||
'complete' => false
|
||||
]);
|
||||
} elseif (is_dir($filePath)) {
|
||||
// Add sub-directory.
|
||||
$zipFile->addEmptyDir($localPath);
|
||||
static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
}
|
||||
@@ -117,27 +117,45 @@ class Cache extends Getters
|
||||
$this->config = $grav['config'];
|
||||
$this->now = time();
|
||||
|
||||
$this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
|
||||
if (is_null($this->enabled)) {
|
||||
$this->enabled = (bool)$this->config->get('system.cache.enabled');
|
||||
}
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $grav['uri'];
|
||||
|
||||
$prefix = $this->config->get('system.cache.prefix');
|
||||
|
||||
if (is_null($this->enabled)) {
|
||||
$this->enabled = (bool)$this->config->get('system.cache.enabled');
|
||||
}
|
||||
$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') . '-' . substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION),
|
||||
2, 8);
|
||||
|
||||
$this->key = ($prefix ? $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();
|
||||
|
||||
// Set the cache namespace to our unique key
|
||||
$this->driver->setNamespace($this->key);
|
||||
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = Grav::instance()['events'];
|
||||
$dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
|
||||
}
|
||||
|
||||
public function purgeOldCache()
|
||||
{
|
||||
$cache_dir = dirname($this->cache_dir);
|
||||
$current = basename($this->cache_dir);
|
||||
$count = 0;
|
||||
|
||||
foreach (new \DirectoryIterator($cache_dir) as $file) {
|
||||
$dir = $file->getBasename();
|
||||
if ($file->isDot() || $file->isFile() || $dir === $current) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Folder::delete($file->getPathname());
|
||||
$count++;
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,6 +331,19 @@ class Cache extends Getters
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all cache
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteAll()
|
||||
{
|
||||
if ($this->enabled) {
|
||||
return $this->driver->deleteAll();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean state of whether or not the item exists in the cache based on id key
|
||||
*
|
||||
@@ -382,6 +413,12 @@ class Cache extends Getters
|
||||
|
||||
}
|
||||
|
||||
// Delete entries in the doctrine cache if required
|
||||
if (in_array($remove, ['all', 'standard'])) {
|
||||
$cache = Grav::instance()['cache'];
|
||||
$cache->driver->deleteAll();
|
||||
}
|
||||
|
||||
// Clearing cache event to add paths to clear
|
||||
Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths]));
|
||||
|
||||
@@ -507,4 +544,30 @@ class Cache extends Getters
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function purgeJob()
|
||||
{
|
||||
$cache = Grav::instance()['cache'];
|
||||
$deleted_folders = $cache->purgeOldCache();
|
||||
$msg = 'Purged ' . $deleted_folders . ' old cache folders...';
|
||||
return $msg;
|
||||
}
|
||||
|
||||
public function onSchedulerInitialized(Event $event)
|
||||
{
|
||||
/** @var Scheduler $scheduler */
|
||||
$scheduler = $event['scheduler'];
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$at = $config->get('system.cache.purge_at');
|
||||
$name = 'cache-purge';
|
||||
$logs = 'logs/' . $name . '.out';
|
||||
|
||||
$job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', null, $name );
|
||||
$job->at($at);
|
||||
$job->output($logs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ abstract class CompiledBase
|
||||
*/
|
||||
public function checksum()
|
||||
{
|
||||
if (!isset($this->checksum)) {
|
||||
if (null === $this->checksum) {
|
||||
$this->checksum = md5(json_encode($this->files) . $this->version);
|
||||
}
|
||||
|
||||
@@ -197,11 +197,11 @@ abstract class CompiledBase
|
||||
|
||||
$cache = include $filename;
|
||||
if (
|
||||
!is_array($cache)
|
||||
!\is_array($cache)
|
||||
|| !isset($cache['checksum'])
|
||||
|| !isset($cache['data'])
|
||||
|| !isset($cache['@class'])
|
||||
|| $cache['@class'] != get_class($this)
|
||||
|| $cache['@class'] !== \get_class($this)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -212,7 +212,7 @@ abstract class CompiledBase
|
||||
}
|
||||
|
||||
$this->createObject($cache['data']);
|
||||
$this->timestamp = isset($cache['timestamp']) ? $cache['timestamp'] : 0;
|
||||
$this->timestamp = $cache['timestamp'] ?? 0;
|
||||
|
||||
$this->finalizeObject();
|
||||
|
||||
@@ -243,7 +243,7 @@ abstract class CompiledBase
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'@class' => get_class($this),
|
||||
'@class' => \get_class($this),
|
||||
'timestamp' => time(),
|
||||
'checksum' => $this->checksum(),
|
||||
'files' => $this->files,
|
||||
|
||||
@@ -63,7 +63,7 @@ class CompiledConfig extends CompiledBase
|
||||
*/
|
||||
protected function createObject(array $data = [])
|
||||
{
|
||||
if ($this->withDefaults && empty($data) && is_callable($this->callable)) {
|
||||
if ($this->withDefaults && empty($data) && \is_callable($this->callable)) {
|
||||
$blueprints = $this->callable;
|
||||
$data = $blueprints()->getDefaults();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ use Grav\Common\Utils;
|
||||
|
||||
class Config extends Data
|
||||
{
|
||||
public $environment;
|
||||
|
||||
/** @var string */
|
||||
protected $checksum;
|
||||
protected $modified = false;
|
||||
@@ -90,7 +92,7 @@ class Config extends Data
|
||||
{
|
||||
$setup = Grav::instance()['setup']->toArray();
|
||||
foreach ($setup as $key => $value) {
|
||||
if ($key === 'streams' || !is_array($value)) {
|
||||
if ($key === 'streams' || !\is_array($value)) {
|
||||
// Optimized as streams and simple values are fully defined in setup.
|
||||
$this->items[$key] = $value;
|
||||
} else {
|
||||
@@ -109,6 +111,8 @@ class Config extends Data
|
||||
*/
|
||||
public function getLanguages()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use Grav::instance()[\'languages\'] instead', E_USER_DEPRECATED);
|
||||
|
||||
return Grav::instance()['languages'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ class ConfigFileFinder
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $directory->getBasename();
|
||||
$name = $directory->getFilename();
|
||||
$find = ($lookup ?: $name) . '.yaml';
|
||||
$filename = "{$path}/{$name}/{$find}";
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class Setup extends Data
|
||||
// If not defined, environment will be set up in the constructor.
|
||||
],
|
||||
'asset' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
'' => ['assets'],
|
||||
]
|
||||
@@ -109,7 +109,7 @@ class Setup extends Data
|
||||
]
|
||||
],
|
||||
'image' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
'' => ['user://images', 'system://images']
|
||||
]
|
||||
@@ -133,12 +133,12 @@ class Setup extends Data
|
||||
*/
|
||||
public function __construct($container)
|
||||
{
|
||||
$environment = null !== static::$environment ? static::$environment : ($container['uri']->environment() ?: 'localhost');
|
||||
$environment = static::$environment ?? $container['uri']->environment() ?: 'localhost';
|
||||
|
||||
// Pre-load setup.php which contains our initial configuration.
|
||||
// Configuration may contain dynamic parts, which is why we need to always load it.
|
||||
// If "GRAVE_SETUP_PATH" has been defined, use it, otherwise use defaults.
|
||||
$file = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
|
||||
$file = \defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
|
||||
$setup = is_file($file) ? (array) include $file : [];
|
||||
|
||||
// Add default streams defined in beginning of the class.
|
||||
@@ -152,7 +152,7 @@ class Setup extends Data
|
||||
|
||||
// Set up environment.
|
||||
$this->def('environment', $environment ?: 'cli');
|
||||
$this->def('streams.schemes.environment.prefixes', ['' => $environment ? ["user://{$this->environment}"] : []]);
|
||||
$this->def('streams.schemes.environment.prefixes', ['' => $environment ? ["user://{$environment}"] : []]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,8 +212,8 @@ class Setup extends Data
|
||||
$locator->addPath($scheme, '', $config['paths']);
|
||||
}
|
||||
|
||||
$override = isset($config['override']) ? $config['override'] : false;
|
||||
$force = isset($config['force']) ? $config['force'] : false;
|
||||
$override = $config['override'] ?? false;
|
||||
$force = $config['force'] ?? false;
|
||||
|
||||
if (isset($config['prefixes'])) {
|
||||
foreach ((array)$config['prefixes'] as $prefix => $paths) {
|
||||
@@ -232,7 +232,7 @@ class Setup extends Data
|
||||
{
|
||||
$schemes = [];
|
||||
foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
|
||||
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
|
||||
$type = $config['type'] ?? 'ReadOnlyStream';
|
||||
if ($type[0] !== '\\') {
|
||||
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
|
||||
}
|
||||
@@ -251,8 +251,8 @@ class Setup extends Data
|
||||
*/
|
||||
protected function check(UniformResourceLocator $locator)
|
||||
{
|
||||
$streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
|
||||
if (!is_array($streams)) {
|
||||
$streams = $this->items['streams']['schemes'] ?? null;
|
||||
if (!\is_array($streams)) {
|
||||
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
|
||||
}
|
||||
$diff = array_keys(array_diff_key($this->streams, $streams));
|
||||
@@ -262,18 +262,22 @@ 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' => []]);
|
||||
$this->initializeLocator($locator);
|
||||
}
|
||||
try {
|
||||
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' => []]);
|
||||
$this->initializeLocator($locator);
|
||||
}
|
||||
|
||||
// Create security.yaml if it doesn't exist.
|
||||
$filename = $locator->findResource('config://security.yaml', true, true);
|
||||
$file = YamlFile::instance($filename);
|
||||
if (!$file->exists()) {
|
||||
$file->save(['salt' => Utils::generateRandomString(14)]);
|
||||
$file->free();
|
||||
// Create security.yaml if it doesn't exist.
|
||||
$filename = $locator->findResource('config://security.yaml', true, true);
|
||||
$file = YamlFile::instance($filename);
|
||||
if (!$file->exists()) {
|
||||
$file->save(['salt' => Utils::generateRandomString(14)]);
|
||||
$file->free();
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,21 +10,25 @@ namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\User;
|
||||
use RocketTheme\Toolbox\Blueprints\BlueprintForm;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Blueprint extends BlueprintForm
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $context = 'blueprints://';
|
||||
|
||||
/**
|
||||
* @var BlueprintSchema
|
||||
*/
|
||||
protected $scope;
|
||||
|
||||
/** @var BlueprintSchema */
|
||||
protected $blueprintSchema;
|
||||
|
||||
public function setScope($scope)
|
||||
{
|
||||
$this->scope = $scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values for field types.
|
||||
*
|
||||
@@ -127,13 +131,15 @@ class Blueprint extends BlueprintForm
|
||||
*/
|
||||
protected function initInternals()
|
||||
{
|
||||
if (!isset($this->blueprintSchema)) {
|
||||
if (null === $this->blueprintSchema) {
|
||||
$types = Grav::instance()['plugins']->formFieldTypes;
|
||||
|
||||
$this->blueprintSchema = new BlueprintSchema;
|
||||
|
||||
if ($types) {
|
||||
$this->blueprintSchema->setTypes($types);
|
||||
}
|
||||
|
||||
$this->blueprintSchema->embed('', $this->items);
|
||||
$this->blueprintSchema->init();
|
||||
}
|
||||
@@ -162,17 +168,19 @@ class Blueprint extends BlueprintForm
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if (is_string($path) && !$locator->isStream($path)) {
|
||||
if (\is_string($path) && !$locator->isStream($path)) {
|
||||
// Find path overrides.
|
||||
$paths = isset($this->overrides[$path]) ? (array) $this->overrides[$path] : [];
|
||||
$paths = (array) ($this->overrides[$path] ?? null);
|
||||
|
||||
// Add path pointing to default context.
|
||||
if ($context === null) {
|
||||
$context = $this->context;
|
||||
}
|
||||
if ($context && $context[strlen($context)-1] !== '/') {
|
||||
|
||||
if ($context && $context[\strlen($context)-1] !== '/') {
|
||||
$context .= '/';
|
||||
}
|
||||
|
||||
$path = $context . $path;
|
||||
|
||||
if (!preg_match('/\.yaml$/', $path)) {
|
||||
@@ -186,7 +194,7 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
$files = [];
|
||||
foreach ($paths as $lookup) {
|
||||
if (is_string($lookup) && strpos($lookup, '://')) {
|
||||
if (\is_string($lookup) && strpos($lookup, '://')) {
|
||||
$files = array_merge($files, $locator->findResources($lookup));
|
||||
} else {
|
||||
$files[] = $lookup;
|
||||
@@ -205,27 +213,29 @@ class Blueprint extends BlueprintForm
|
||||
{
|
||||
$params = $call['params'];
|
||||
|
||||
if (is_array($params)) {
|
||||
if (\is_array($params)) {
|
||||
$function = array_shift($params);
|
||||
} else {
|
||||
$function = $params;
|
||||
$params = [];
|
||||
}
|
||||
|
||||
list($o, $f) = preg_split('/::/', $function, 2);
|
||||
[$o, $f] = explode('::', $function, 2);
|
||||
|
||||
$data = null;
|
||||
if (!$f) {
|
||||
if (function_exists($o)) {
|
||||
$data = call_user_func_array($o, $params);
|
||||
if (\function_exists($o)) {
|
||||
$data = \call_user_func_array($o, $params);
|
||||
}
|
||||
} else {
|
||||
if (method_exists($o, $f)) {
|
||||
$data = call_user_func_array(array($o, $f), $params);
|
||||
$data = \call_user_func_array([$o, $f], $params);
|
||||
}
|
||||
}
|
||||
|
||||
// If function returns a value,
|
||||
if (isset($data)) {
|
||||
if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
|
||||
if (null !== $data) {
|
||||
if (\is_array($data) && isset($field[$property]) && \is_array($field[$property])) {
|
||||
// Combine field and @data-field together.
|
||||
$field[$property] += $data;
|
||||
} else {
|
||||
@@ -243,12 +253,75 @@ class Blueprint extends BlueprintForm
|
||||
protected function dynamicConfig(array &$field, $property, array &$call)
|
||||
{
|
||||
$value = $call['params'];
|
||||
|
||||
$default = isset($field[$property]) ? $field[$property] : null;
|
||||
$default = $field[$property] ?? null;
|
||||
$config = Grav::instance()['config']->get($value, $default);
|
||||
|
||||
if (!is_null($config)) {
|
||||
if (null !== $config) {
|
||||
$field[$property] = $config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
*/
|
||||
protected function dynamicSecurity(array &$field, $property, array &$call)
|
||||
{
|
||||
if ($property) {
|
||||
return;
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
$actions = (array)$call['params'];
|
||||
|
||||
/** @var User $user */
|
||||
if (isset($grav['user'])) {
|
||||
$user = Grav::instance()['user'] ?? null;
|
||||
foreach ($actions as $action) {
|
||||
if (!$user->authorize($action)) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
*/
|
||||
protected function dynamicScope(array &$field, $property, array &$call)
|
||||
{
|
||||
if ($property && $property !== 'ignore') {
|
||||
return;
|
||||
}
|
||||
|
||||
$scopes = (array)$call['params'];
|
||||
$matches = \in_array($this->scope, $scopes, true);
|
||||
if ($this->scope && $property !== 'ignore') {
|
||||
$matches = !$matches;
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function addPropertyRecursive(array &$field, $property, $value)
|
||||
{
|
||||
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
|
||||
$field[$property] = array_merge_recursive($field[$property], $value);
|
||||
} else {
|
||||
$field[$property] = $value;
|
||||
}
|
||||
|
||||
if (!empty($field['fields'])) {
|
||||
foreach ($field['fields'] as $key => &$child) {
|
||||
$this->addPropertyRecursive($child, $property, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,23 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
'fields' => true
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTypes()
|
||||
{
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function getType($name)
|
||||
{
|
||||
return $this->types[$name] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate data against blueprints.
|
||||
*
|
||||
@@ -49,33 +66,38 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
/**
|
||||
* Filter data by using blueprints.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $data Incoming data, for example from a form.
|
||||
* @param bool $missingValuesAsNull Include missing values as nulls.
|
||||
* @return array
|
||||
*/
|
||||
public function filter(array $data)
|
||||
public function filter(array $data, $missingValuesAsNull = false)
|
||||
{
|
||||
return $this->filterArray($data, $this->nested);
|
||||
return $this->filterArray($data, $this->nested, $missingValuesAsNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $rules
|
||||
* @returns array
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function validateArray(array $data, array $rules)
|
||||
{
|
||||
$messages = $this->checkRequired($data, $rules);
|
||||
|
||||
foreach ($data as $key => $field) {
|
||||
$val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
|
||||
$rule = is_string($val) ? $this->items[$val] : null;
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
|
||||
if ($rule) {
|
||||
// Item has been defined in blueprints.
|
||||
if (!empty($rule['validate']['ignore'])) {
|
||||
// Skip validation in the ignored field.
|
||||
continue;
|
||||
}
|
||||
|
||||
$messages += Validation::validate($field, $rule);
|
||||
} elseif (is_array($field) && is_array($val)) {
|
||||
} elseif (\is_array($field) && \is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$messages += $this->validateArray($field, $val);
|
||||
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
|
||||
@@ -90,27 +112,47 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $rules
|
||||
* @param bool $missingValuesAsNull
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function filterArray(array $data, array $rules)
|
||||
protected function filterArray(array $data, array $rules, $missingValuesAsNull)
|
||||
{
|
||||
$results = array();
|
||||
$results = [];
|
||||
|
||||
if ($missingValuesAsNull) {
|
||||
// First pass is to fill up all the fields with null. This is done to lock the ordering of the fields.
|
||||
foreach ($rules as $key => $rule) {
|
||||
if ($key && !isset($results[$key])) {
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
|
||||
if (empty($rule['validate']['ignore'])) {
|
||||
$results[$key] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $key => $field) {
|
||||
$val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
|
||||
$rule = is_string($val) ? $this->items[$val] : null;
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
|
||||
if ($rule) {
|
||||
// Item has been defined in blueprints.
|
||||
if (!empty($rule['validate']['ignore'])) {
|
||||
// Skip any data in the ignored field.
|
||||
continue;
|
||||
}
|
||||
|
||||
$field = Validation::filter($field, $rule);
|
||||
} elseif (is_array($field) && is_array($val)) {
|
||||
} elseif (\is_array($field) && \is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$field = $this->filterArray($field, $val);
|
||||
$field = $this->filterArray($field, $val, $missingValuesAsNull);
|
||||
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
|
||||
$field = null;
|
||||
}
|
||||
|
||||
if (isset($field) && (!is_array($field) || !empty($field))) {
|
||||
if (null !== $field && (!\is_array($field) || !empty($field))) {
|
||||
$results[$key] = $field;
|
||||
}
|
||||
}
|
||||
@@ -128,10 +170,18 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
$messages = [];
|
||||
|
||||
foreach ($fields as $name => $field) {
|
||||
if (!is_string($field)) {
|
||||
if (!\is_string($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field = $this->items[$field];
|
||||
|
||||
// Skip ignored field, it will not be required.
|
||||
if (!empty($field['validate']['ignore'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if required.
|
||||
if (isset($field['validate']['required'])
|
||||
&& $field['validate']['required'] === true) {
|
||||
|
||||
@@ -142,7 +192,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = isset($field['label']) ? $field['label'] : $field['name'];
|
||||
$value = $field['label'] ?? $field['name'];
|
||||
$language = Grav::instance()['language'];
|
||||
$message = sprintf($language->translate('FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value));
|
||||
$messages[$field['name']][] = $message;
|
||||
@@ -161,7 +211,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
{
|
||||
$value = $call['params'];
|
||||
|
||||
$default = isset($field[$property]) ? $field[$property] : null;
|
||||
$default = $field[$property] ?? null;
|
||||
$config = Grav::instance()['config']->get($value, $default);
|
||||
|
||||
if (null !== $config) {
|
||||
|
||||
@@ -13,8 +13,11 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Blueprints
|
||||
{
|
||||
/** @var array|string */
|
||||
protected $search;
|
||||
/** @var array */
|
||||
protected $types;
|
||||
/** @var array */
|
||||
protected $instances = [];
|
||||
|
||||
/**
|
||||
@@ -49,7 +52,7 @@ class Blueprints
|
||||
public function types()
|
||||
{
|
||||
if ($this->types === null) {
|
||||
$this->types = array();
|
||||
$this->types = [];
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
@@ -87,7 +90,7 @@ class Blueprints
|
||||
{
|
||||
$blueprint = new Blueprint($name);
|
||||
|
||||
if (is_array($this->search) || is_object($this->search)) {
|
||||
if (\is_array($this->search) || \is_object($this->search)) {
|
||||
// Page types.
|
||||
$blueprint->setOverrides($this->search);
|
||||
$blueprint->setContext('blueprints://pages');
|
||||
|
||||
@@ -19,24 +19,23 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
|
||||
{
|
||||
use NestedArrayAccessWithGetters, Countable, Export;
|
||||
|
||||
/** @var string */
|
||||
protected $gettersVariable = 'items';
|
||||
|
||||
/** @var array */
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var Blueprints
|
||||
*/
|
||||
/** @var Blueprints */
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @var File
|
||||
*/
|
||||
/** @var File */
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
* @param Blueprint|callable $blueprints
|
||||
*/
|
||||
public function __construct(array $items = array(), $blueprints = null)
|
||||
public function __construct(array $items = [], $blueprints = null)
|
||||
{
|
||||
$this->items = $items;
|
||||
$this->blueprints = $blueprints;
|
||||
@@ -70,14 +69,16 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
|
||||
{
|
||||
$old = $this->get($name, null, $separator);
|
||||
if ($old !== null) {
|
||||
if (!is_array($old)) {
|
||||
if (!\is_array($old)) {
|
||||
throw new \RuntimeException('Value ' . $old);
|
||||
}
|
||||
if (is_object($value)) {
|
||||
|
||||
if (\is_object($value)) {
|
||||
$value = (array) $value;
|
||||
} elseif (!is_array($value)) {
|
||||
} elseif (!\is_array($value)) {
|
||||
throw new \RuntimeException('Value ' . $value);
|
||||
}
|
||||
|
||||
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
|
||||
}
|
||||
|
||||
@@ -108,9 +109,10 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
|
||||
*/
|
||||
public function joinDefaults($name, $value, $separator = '.')
|
||||
{
|
||||
if (is_object($value)) {
|
||||
if (\is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
$old = $this->get($name, null, $separator);
|
||||
if ($old !== null) {
|
||||
$value = $this->blueprints()->mergeData($value, $old, $name, $separator);
|
||||
@@ -125,16 +127,16 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
|
||||
* Get value from the configuration and join it with given data.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param array $value Value to be joined.
|
||||
* @param array|object $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getJoined($name, $value, $separator = '.')
|
||||
{
|
||||
if (is_object($value)) {
|
||||
if (\is_object($value)) {
|
||||
$value = (array) $value;
|
||||
} elseif (!is_array($value)) {
|
||||
} elseif (!\is_array($value)) {
|
||||
throw new \RuntimeException('Value ' . $value);
|
||||
}
|
||||
|
||||
@@ -145,7 +147,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!is_array($old)) {
|
||||
if (!\is_array($old)) {
|
||||
throw new \RuntimeException('Value ' . $old);
|
||||
}
|
||||
|
||||
@@ -223,7 +225,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
|
||||
{
|
||||
if (!$this->blueprints){
|
||||
$this->blueprints = new Blueprint;
|
||||
} elseif (is_callable($this->blueprints)) {
|
||||
} elseif (\is_callable($this->blueprints)) {
|
||||
// Lazy load blueprints.
|
||||
$blueprints = $this->blueprints;
|
||||
$this->blueprints = $blueprints();
|
||||
@@ -282,6 +284,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
|
||||
if ($storage) {
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
return $this->storage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Grav\Common\Yaml;
|
||||
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYaml;
|
||||
|
||||
class Validation
|
||||
{
|
||||
@@ -30,7 +29,7 @@ class Validation
|
||||
$validate = isset($field['validate']) ? (array) $field['validate'] : [];
|
||||
// Validate type with fallback type text.
|
||||
$type = (string) isset($validate['type']) ? $validate['type'] : $field['type'];
|
||||
$method = 'type'.strtr($type, '-', '_');
|
||||
$method = 'type' . str_replace('-', '_', $type);
|
||||
|
||||
// If value isn't required, we will stop validation if empty value is given.
|
||||
if ((empty($validate['required']) || (isset($validate['required']) && $validate['required'] !== true)) && ($value === null || $value === '' || (($field['type'] === 'checkbox' || $field['type'] === 'switch') && $value == false))) {
|
||||
@@ -44,14 +43,14 @@ class Validation
|
||||
// Get language class.
|
||||
$language = Grav::instance()['language'];
|
||||
|
||||
$name = ucfirst(isset($field['label']) ? $field['label'] : $field['name']);
|
||||
$name = ucfirst($field['label'] ?? $field['name']);
|
||||
$message = (string) isset($field['validate']['message'])
|
||||
? $language->translate($field['validate']['message'])
|
||||
: $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
|
||||
|
||||
|
||||
// If this is a YAML field validate/filter as such
|
||||
if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
|
||||
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
|
||||
$method = 'typeYaml';
|
||||
}
|
||||
|
||||
@@ -67,7 +66,7 @@ class Validation
|
||||
|
||||
// Check individual rules.
|
||||
foreach ($validate as $rule => $params) {
|
||||
$method = 'validate' . ucfirst(strtr($rule, '-', '_'));
|
||||
$method = 'validate' . ucfirst(str_replace('-', '_', $rule));
|
||||
|
||||
if (method_exists(__CLASS__, $method)) {
|
||||
$success = self::$method($value, $params);
|
||||
@@ -93,7 +92,7 @@ class Validation
|
||||
$validate = isset($field['validate']) ? (array) $field['validate'] : [];
|
||||
|
||||
// If value isn't required, we will return null if empty value is given.
|
||||
if (empty($validate['required']) && ($value === null || $value === '')) {
|
||||
if (($value === null || $value === '') && empty($validate['required'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -104,10 +103,10 @@ class Validation
|
||||
|
||||
// Validate type with fallback type text.
|
||||
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
|
||||
$method = 'filter' . ucfirst(strtr($type, '-', '_'));
|
||||
$method = 'filter' . ucfirst(str_replace('-', '_', $type));
|
||||
|
||||
// If this is a YAML field validate/filter as such
|
||||
if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
|
||||
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
|
||||
$method = 'filterYaml';
|
||||
}
|
||||
|
||||
@@ -128,20 +127,22 @@ class Validation
|
||||
*/
|
||||
public static function typeText($value, array $params, array $field)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
if (!\is_string($value) && !is_numeric($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['min']) && strlen($value) < $params['min']) {
|
||||
$value = (string)$value;
|
||||
|
||||
if (isset($params['min']) && \strlen($value) < $params['min']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['max']) && strlen($value) > $params['max']) {
|
||||
if (isset($params['max']) && \strlen($value) > $params['max']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$min = isset($params['min']) ? $params['min'] : 0;
|
||||
if (isset($params['step']) && (strlen($value) - $min) % $params['step'] == 0) {
|
||||
$min = $params['min'] ?? 0;
|
||||
if (isset($params['step']) && (\strlen($value) - $min) % $params['step'] === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -159,12 +160,12 @@ class Validation
|
||||
|
||||
protected static function filterCommaList($value, array $params, array $field)
|
||||
{
|
||||
return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
public static function typeCommaList($value, array $params, array $field)
|
||||
{
|
||||
return is_array($value) ? true : self::typeText($value, $params, $field);
|
||||
return \is_array($value) ? true : self::typeText($value, $params, $field);
|
||||
}
|
||||
|
||||
protected static function filterLower($value, array $params)
|
||||
@@ -233,6 +234,7 @@ class Validation
|
||||
{
|
||||
// Set multiple: true so checkboxes can easily use min/max counts to control number of options required
|
||||
$field['multiple'] = true;
|
||||
|
||||
return self::typeArray((array) $value, $params, $field);
|
||||
}
|
||||
|
||||
@@ -252,15 +254,9 @@ class Validation
|
||||
public static function typeCheckbox($value, array $params, array $field)
|
||||
{
|
||||
$value = (string) $value;
|
||||
$field_value = (string) ($field['value'] ?? '1');
|
||||
|
||||
if (!isset($field['value'])) {
|
||||
$field['value'] = 1;
|
||||
}
|
||||
if (isset($value) && $value != $field['value']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return $value === $field_value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,12 +338,9 @@ class Validation
|
||||
return false;
|
||||
}
|
||||
|
||||
$min = isset($params['min']) ? $params['min'] : 0;
|
||||
if (isset($params['step']) && fmod($value - $min, $params['step']) == 0) {
|
||||
return false;
|
||||
}
|
||||
$min = $params['min'] ?? 0;
|
||||
|
||||
return true;
|
||||
return !(isset($params['step']) && fmod($value - $min, $params['step']) === 0);
|
||||
}
|
||||
|
||||
protected static function filterNumber($value, array $params, array $field)
|
||||
@@ -407,10 +400,10 @@ class Validation
|
||||
*/
|
||||
public static function typeEmail($value, array $params, array $field)
|
||||
{
|
||||
$values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
|
||||
$values = !\is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (!(self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_EMAIL))) {
|
||||
foreach ($values as $val) {
|
||||
if (!(self::typeText($val, $params, $field) && filter_var($val, FILTER_VALIDATE_EMAIL))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -444,9 +437,11 @@ class Validation
|
||||
{
|
||||
if ($value instanceof \DateTime) {
|
||||
return true;
|
||||
} elseif (!is_string($value)) {
|
||||
}
|
||||
if (!\is_string($value)) {
|
||||
return false;
|
||||
} elseif (!isset($params['format'])) {
|
||||
}
|
||||
if (!isset($params['format'])) {
|
||||
return false !== strtotime($value);
|
||||
}
|
||||
|
||||
@@ -478,10 +473,10 @@ class Validation
|
||||
*/
|
||||
public static function typeDate($value, array $params, array $field)
|
||||
{
|
||||
$params = array($params);
|
||||
if (!isset($params['format'])) {
|
||||
$params['format'] = 'Y-m-d';
|
||||
}
|
||||
|
||||
return self::typeDatetime($value, $params, $field);
|
||||
}
|
||||
|
||||
@@ -495,10 +490,10 @@ class Validation
|
||||
*/
|
||||
public static function typeTime($value, array $params, array $field)
|
||||
{
|
||||
$params = array($params);
|
||||
if (!isset($params['format'])) {
|
||||
$params['format'] = 'H:i';
|
||||
}
|
||||
|
||||
return self::typeDatetime($value, $params, $field);
|
||||
}
|
||||
|
||||
@@ -512,10 +507,10 @@ class Validation
|
||||
*/
|
||||
public static function typeMonth($value, array $params, array $field)
|
||||
{
|
||||
$params = array($params);
|
||||
if (!isset($params['format'])) {
|
||||
$params['format'] = 'Y-m';
|
||||
}
|
||||
|
||||
return self::typeDatetime($value, $params, $field);
|
||||
}
|
||||
|
||||
@@ -532,6 +527,7 @@ class Validation
|
||||
if (!isset($params['format']) && !preg_match('/^\d{4}-W\d{2}$/u', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::typeDatetime($value, $params, $field);
|
||||
}
|
||||
|
||||
@@ -545,72 +541,69 @@ class Validation
|
||||
*/
|
||||
public static function typeArray($value, array $params, array $field)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
if (!\is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($field['multiple'])) {
|
||||
if (isset($params['min']) && count($value) < $params['min']) {
|
||||
if (isset($params['min']) && \count($value) < $params['min']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['max']) && count($value) > $params['max']) {
|
||||
if (isset($params['max']) && \count($value) > $params['max']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$min = isset($params['min']) ? $params['min'] : 0;
|
||||
if (isset($params['step']) && (count($value) - $min) % $params['step'] == 0) {
|
||||
$min = $params['min'] ?? 0;
|
||||
if (isset($params['step']) && (\count($value) - $min) % $params['step'] === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$options = isset($field['options']) ? array_keys($field['options']) : array();
|
||||
$values = isset($field['use']) && $field['use'] == 'keys' ? array_keys($value) : $value;
|
||||
if ($options && array_diff($values, $options)) {
|
||||
return false;
|
||||
}
|
||||
$options = isset($field['options']) ? array_keys($field['options']) : [];
|
||||
$values = isset($field['use']) && $field['use'] === 'keys' ? array_keys($value) : $value;
|
||||
|
||||
return true;
|
||||
return !($options && array_diff($values, $options));
|
||||
}
|
||||
|
||||
protected static function filterArray($value, $params, $field)
|
||||
{
|
||||
$values = (array) $value;
|
||||
$options = isset($field['options']) ? array_keys($field['options']) : array();
|
||||
$multi = isset($field['multiple']) ? $field['multiple'] : false;
|
||||
$options = isset($field['options']) ? array_keys($field['options']) : [];
|
||||
$multi = $field['multiple'] ?? false;
|
||||
|
||||
if (count($values) == 1 && isset($values[0]) && $values[0] == '') {
|
||||
if (\count($values) === 1 && isset($values[0]) && $values[0] === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if ($options) {
|
||||
$useKey = isset($field['use']) && $field['use'] == 'keys';
|
||||
foreach ($values as $key => $value) {
|
||||
$values[$key] = $useKey ? (bool) $value : $value;
|
||||
$useKey = isset($field['use']) && $field['use'] === 'keys';
|
||||
foreach ($values as $key => $val) {
|
||||
$values[$key] = $useKey ? (bool) $val : $val;
|
||||
}
|
||||
}
|
||||
|
||||
if ($multi) {
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$value = implode(',', $value);
|
||||
$values[$key] = array_map('trim', explode(',', $value));
|
||||
foreach ($values as $key => $val) {
|
||||
if (\is_array($val)) {
|
||||
$val = implode(',', $val);
|
||||
$values[$key] = array_map('trim', explode(',', $val));
|
||||
} else {
|
||||
$values[$key] = trim($value);
|
||||
$values[$key] = trim($val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
|
||||
foreach ($values as $key => $value) {
|
||||
foreach ($value as $inner_key => $inner_value) {
|
||||
foreach ($values as $key => $val) {
|
||||
foreach ($val as $inner_key => $inner_value) {
|
||||
if ($inner_value == '') {
|
||||
unset($value[$inner_key]);
|
||||
unset($val[$inner_key]);
|
||||
}
|
||||
}
|
||||
|
||||
$values[$key] = $value;
|
||||
$values[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,7 +612,7 @@ class Validation
|
||||
|
||||
public static function typeList($value, array $params, array $field)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
if (!\is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -627,7 +620,7 @@ class Validation
|
||||
foreach ($value as $key => $item) {
|
||||
foreach ($field['fields'] as $subKey => $subField) {
|
||||
$subKey = trim($subKey, '.');
|
||||
$subValue = isset($item[$subKey]) ? $item[$subKey] : null;
|
||||
$subValue = $item[$subKey] ?? null;
|
||||
self::validate($subValue, $subField);
|
||||
}
|
||||
}
|
||||
@@ -643,15 +636,12 @@ class Validation
|
||||
|
||||
public static function filterYaml($value, $params)
|
||||
{
|
||||
try {
|
||||
if (is_string($value)) {
|
||||
return (array) Yaml::parse($value);
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
} catch (ParseException $e) {
|
||||
if (!\is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return (array) Yaml::parse($value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -679,9 +669,9 @@ class Validation
|
||||
{
|
||||
if (is_scalar($value)) {
|
||||
return (bool) $params !== true || $value !== '';
|
||||
} else {
|
||||
return (bool) $params !== true || !empty($value);
|
||||
}
|
||||
|
||||
return (bool) $params !== true || !empty($value);
|
||||
}
|
||||
|
||||
public static function validatePattern($value, $params)
|
||||
@@ -704,12 +694,12 @@ class Validation
|
||||
|
||||
public static function typeBool($value, $params)
|
||||
{
|
||||
return is_bool($value) || $value == 1 || $value == 0;
|
||||
return \is_bool($value) || $value == 1 || $value == 0;
|
||||
}
|
||||
|
||||
public static function validateBool($value, $params)
|
||||
{
|
||||
return is_bool($value) || $value == 1 || $value == 0;
|
||||
return \is_bool($value) || $value == 1 || $value == 0;
|
||||
}
|
||||
|
||||
protected static function filterBool($value, $params)
|
||||
@@ -724,7 +714,7 @@ class Validation
|
||||
|
||||
public static function validateFloat($value, $params)
|
||||
{
|
||||
return is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
|
||||
return \is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
|
||||
}
|
||||
|
||||
protected static function filterFloat($value, $params)
|
||||
@@ -749,7 +739,7 @@ class Validation
|
||||
|
||||
public static function validateArray($value, $params)
|
||||
{
|
||||
return is_array($value)
|
||||
return \is_array($value)
|
||||
|| ($value instanceof \ArrayAccess
|
||||
&& $value instanceof \Traversable
|
||||
&& $value instanceof \Countable);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace Grav\Common;
|
||||
|
||||
use DebugBar\DataCollector\ConfigCollector;
|
||||
use DebugBar\DataCollector\MessagesCollector;
|
||||
use DebugBar\JavascriptRenderer;
|
||||
use DebugBar\StandardDebugBar;
|
||||
use Grav\Common\Config\Config;
|
||||
@@ -31,6 +32,11 @@ class Debugger
|
||||
|
||||
protected $timers = [];
|
||||
|
||||
/** @var string[] $deprecations */
|
||||
protected $deprecations = [];
|
||||
|
||||
protected $errorHandler;
|
||||
|
||||
/**
|
||||
* Debugger constructor.
|
||||
*/
|
||||
@@ -41,6 +47,9 @@ class Debugger
|
||||
|
||||
$this->debugbar = new StandardDebugBar();
|
||||
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
|
||||
|
||||
// Set deprecation collector.
|
||||
$this->setErrorHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,8 +67,15 @@ class Debugger
|
||||
$this->enabled = $this->config->get('system.debugger.enabled');
|
||||
|
||||
if ($this->enabled()) {
|
||||
|
||||
$plugins_config = (array)$this->config->get('plugins');
|
||||
|
||||
ksort($plugins_config);
|
||||
|
||||
|
||||
$this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
|
||||
$this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('plugins'), 'Plugins'));
|
||||
$this->debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
|
||||
$this->addMessage('Grav v' . GRAV_VERSION);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -121,9 +137,9 @@ class Debugger
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCaller($ignore = 2)
|
||||
public function getCaller($limit = 2)
|
||||
{
|
||||
$trace = debug_backtrace(false, $ignore);
|
||||
$trace = debug_backtrace(false, $limit);
|
||||
|
||||
return array_pop($trace);
|
||||
}
|
||||
@@ -170,6 +186,8 @@ class Debugger
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->addDeprecations();
|
||||
|
||||
echo $this->renderer->render();
|
||||
}
|
||||
|
||||
@@ -184,6 +202,7 @@ class Debugger
|
||||
public function sendDataInHeaders()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
$this->addDeprecations();
|
||||
$this->debugbar->sendDataInHeaders();
|
||||
}
|
||||
|
||||
@@ -201,6 +220,7 @@ class Debugger
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->addDeprecations();
|
||||
$this->timers = [];
|
||||
|
||||
return $this->debugbar->getData();
|
||||
@@ -272,4 +292,152 @@ class Debugger
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setErrorHandler()
|
||||
{
|
||||
$this->errorHandler = set_error_handler(
|
||||
[$this, 'deprecatedErrorHandler']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param int $errline
|
||||
* @return bool
|
||||
*/
|
||||
public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline)
|
||||
{
|
||||
if ($errno !== E_USER_DEPRECATED) {
|
||||
if ($this->errorHandler) {
|
||||
return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->enabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$backtrace = debug_backtrace(false);
|
||||
|
||||
// Skip current call.
|
||||
array_shift($backtrace);
|
||||
|
||||
// Skip vendor libraries and the method where error was triggered.
|
||||
while ($current = array_shift($backtrace)) {
|
||||
if (isset($current['file']) && strpos($current['file'], 'vendor') !== false) {
|
||||
continue;
|
||||
}
|
||||
if (isset($current['function']) && ($current['function'] === 'user_error' || $current['function'] === 'trigger_error')) {
|
||||
$current = array_shift($backtrace);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Add back last call.
|
||||
array_unshift($backtrace, $current);
|
||||
|
||||
// Filter arguments.
|
||||
foreach ($backtrace as &$current) {
|
||||
if (isset($current['args'])) {
|
||||
$args = [];
|
||||
foreach ($current['args'] as $arg) {
|
||||
if (\is_string($arg)) {
|
||||
$args[] = "'" . $arg . "'";
|
||||
} elseif (\is_bool($arg)) {
|
||||
$args[] = $arg ? 'true' : 'false';
|
||||
} elseif (\is_scalar($arg)) {
|
||||
$args[] = $arg;
|
||||
} elseif (\is_object($arg)) {
|
||||
$args[] = get_class($arg) . ' $object';
|
||||
} elseif (\is_array($arg)) {
|
||||
$args[] = '$array';
|
||||
} else {
|
||||
$args[] = '$object';
|
||||
}
|
||||
}
|
||||
$current['args'] = $args;
|
||||
}
|
||||
}
|
||||
unset($current);
|
||||
|
||||
$this->deprecations[] = [
|
||||
'message' => $errstr,
|
||||
'file' => $errfile,
|
||||
'line' => $errline,
|
||||
'trace' => $backtrace,
|
||||
];
|
||||
|
||||
// Do not pass forward.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function addDeprecations()
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
return;
|
||||
}
|
||||
|
||||
$collector = new MessagesCollector('deprecated');
|
||||
$this->addCollector($collector);
|
||||
$collector->addMessage('Your site is using following deprecated features:');
|
||||
|
||||
/** @var array $deprecated */
|
||||
foreach ($this->deprecations as $deprecated) {
|
||||
list($message, $scope) = $this->getDepracatedMessage($deprecated);
|
||||
|
||||
$collector->addMessage($message, $scope);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDepracatedMessage($deprecated)
|
||||
{
|
||||
$scope = 'unknown';
|
||||
if (stripos($deprecated['message'], 'grav') !== false) {
|
||||
$scope = 'grav';
|
||||
} elseif (!isset($deprecated['file'])) {
|
||||
$scope = 'unknown';
|
||||
} elseif (stripos($deprecated['file'], 'twig') !== false) {
|
||||
$scope = 'twig';
|
||||
} elseif (stripos($deprecated['file'], 'yaml') !== false) {
|
||||
$scope = 'yaml';
|
||||
} elseif (stripos($deprecated['file'], 'vendor') !== false) {
|
||||
$scope = 'vendor';
|
||||
}
|
||||
|
||||
$trace = [];
|
||||
foreach ($deprecated['trace'] as $current) {
|
||||
$class = isset($current['class']) ? $current['class'] : '';
|
||||
$type = isset($current['type']) ? $current['type'] : '';
|
||||
$function = $this->getFunction($current);
|
||||
if (isset($current['file'])) {
|
||||
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
|
||||
}
|
||||
|
||||
unset($current['class'], $current['type'], $current['function'], $current['args']);
|
||||
|
||||
$trace[] = ['call' => $class . $type . $function] + $current;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'message' => $deprecated['message'],
|
||||
'trace' => $trace
|
||||
],
|
||||
$scope
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFunction($trace)
|
||||
{
|
||||
if (!isset($trace['function'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $trace['function'] . '(' . implode(', ', $trace['args']) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,5 +74,8 @@ class Errors
|
||||
}
|
||||
|
||||
$whoops->register();
|
||||
|
||||
// Re-register deprecation handler.
|
||||
$grav['debugger']->setErrorHandler();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ trait CompiledFile
|
||||
*/
|
||||
public function content($var = null)
|
||||
{
|
||||
// Set some options
|
||||
$this->settings(['native' => true, 'compat' => true]);
|
||||
|
||||
try {
|
||||
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
||||
if ($var === null && $this->raw === null && $this->content === null) {
|
||||
|
||||
58
system/src/Grav/Common/Filesystem/Archiver.php
Normal file
58
system/src/Grav/Common/Filesystem/Archiver.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.FileSystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
abstract class Archiver
|
||||
{
|
||||
protected $options = [
|
||||
'exclude_files' => ['.DS_Store'],
|
||||
'exclude_paths' => []
|
||||
];
|
||||
|
||||
protected $archive_file;
|
||||
|
||||
public static function create($compression)
|
||||
{
|
||||
if ($compression == 'zip') {
|
||||
return new ZipArchiver();
|
||||
} else {
|
||||
return new ZipArchiver();
|
||||
}
|
||||
}
|
||||
|
||||
public function setArchive($archive_file)
|
||||
{
|
||||
$this->archive_file = $archive_file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOptions($options)
|
||||
{
|
||||
$this->options = $options + $this->options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public abstract function compress($folder, callable $status = null);
|
||||
|
||||
public abstract function extract($destination, callable $status = null);
|
||||
|
||||
public abstract function addEmptyFolders($folders, callable $status = null);
|
||||
|
||||
protected function getArchiveFiles($rootPath)
|
||||
{
|
||||
$exclude_paths = $this->options['exclude_paths'];
|
||||
$exclude_files = $this->options['exclude_files'];
|
||||
$dirItr = new \RecursiveDirectoryIterator($rootPath, \RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::UNIX_PATHS);
|
||||
$filterItr = new RecursiveDirectoryFilterIterator($dirItr, $rootPath, $exclude_paths, $exclude_files);
|
||||
$files = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.FileSystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
protected static $root;
|
||||
protected static $ignore_folders;
|
||||
protected static $ignore_files;
|
||||
|
||||
/**
|
||||
* Create a RecursiveFilterIterator from a RecursiveIterator
|
||||
*
|
||||
* @param \RecursiveIterator $iterator
|
||||
* @param string $root
|
||||
* @param array $ignore_folders
|
||||
* @param array $ignore_files
|
||||
*/
|
||||
public function __construct(\RecursiveIterator $iterator, $root, $ignore_folders, $ignore_files)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
|
||||
$this::$root = $root;
|
||||
$this::$ignore_folders = $ignore_folders;
|
||||
$this::$ignore_files = $ignore_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
*
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/** @var $file \SplFileInfo */
|
||||
$file = $this->current();
|
||||
$filename = $file->getFilename();
|
||||
$relative_filename = str_replace($this::$root . '/', '', $file->getPathname());
|
||||
|
||||
if ($file->isDir()) {
|
||||
if (in_array($relative_filename, $this::$ignore_folders, true)) {
|
||||
return false;
|
||||
}
|
||||
if (!in_array($filename, $this::$ignore_files, true)) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($file->isFile() && !in_array($filename, $this::$ignore_files, true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getChildren() {
|
||||
return new self($this->getInnerIterator()->getChildren(), $this::$root, $this::$ignore_folders, $this::$ignore_files);
|
||||
}
|
||||
}
|
||||
@@ -12,19 +12,23 @@ use Grav\Common\Grav;
|
||||
|
||||
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
protected static $folder_ignores;
|
||||
protected static $ignore_folders;
|
||||
|
||||
/**
|
||||
* Create a RecursiveFilterIterator from a RecursiveIterator
|
||||
*
|
||||
* @param \RecursiveIterator $iterator
|
||||
* @param array $ignore_folders
|
||||
*/
|
||||
public function __construct(\RecursiveIterator $iterator)
|
||||
public function __construct(\RecursiveIterator $iterator, $ignore_folders = [])
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
if (empty($this::$folder_ignores)) {
|
||||
$this::$folder_ignores = Grav::instance()['config']->get('system.pages.ignore_folders');
|
||||
|
||||
if (empty($ignore_folders)) {
|
||||
$ignore_folders = Grav::instance()['config']->get('system.pages.ignore_folders');
|
||||
}
|
||||
|
||||
$this::$ignore_folders = $ignore_folders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +41,7 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
|
||||
/** @var $current \SplFileInfo */
|
||||
$current = $this->current();
|
||||
|
||||
if ($current->isDir() && !in_array($current->getFilename(), $this::$folder_ignores, true)) {
|
||||
if ($current->isDir() && !in_array($current->getFilename(), $this::$ignore_folders, true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
110
system/src/Grav/Common/Filesystem/ZipArchiver.php
Normal file
110
system/src/Grav/Common/Filesystem/ZipArchiver.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.FileSystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
class ZipArchiver extends Archiver
|
||||
{
|
||||
|
||||
public function extract($destination, callable $status = null)
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($this->archive_file);
|
||||
|
||||
if ($archive === true) {
|
||||
Folder::mkdir($destination);
|
||||
|
||||
if (!$zip->extractTo($destination)) {
|
||||
throw new \RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
|
||||
}
|
||||
|
||||
public function compress($source, callable $status = null)
|
||||
{
|
||||
if (!extension_loaded('zip')) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
|
||||
}
|
||||
|
||||
if (!file_exists($source)) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
if (!$zip->open($this->archive_file, \ZipArchive::CREATE)) {
|
||||
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([
|
||||
'type' => 'count',
|
||||
'steps' => iterator_count($files),
|
||||
]);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filePath = $file->getPathname();
|
||||
$relativePath = ltrim(substr($filePath, strlen($rootPath)), '/');
|
||||
|
||||
if ($file->isDir()) {
|
||||
$zip->addEmptyDir($relativePath);
|
||||
} else {
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
|
||||
$status && $status([
|
||||
'type' => 'progress',
|
||||
]);
|
||||
}
|
||||
|
||||
$status && $status([
|
||||
'type' => 'message',
|
||||
'message' => 'Compressing...'
|
||||
]);
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addEmptyFolders($folders, callable $status = null)
|
||||
{
|
||||
if (!extension_loaded('zip')) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
if (!$zip->open($this->archive_file)) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened...');
|
||||
}
|
||||
|
||||
$status && $status([
|
||||
'type' => 'message',
|
||||
'message' => 'Adding empty folders...'
|
||||
]);
|
||||
|
||||
foreach($folders as $folder) {
|
||||
$zip->addEmptyDir($folder);
|
||||
$status && $status([
|
||||
'type' => 'progress',
|
||||
]);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Utils;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
class GPM extends Iterator
|
||||
{
|
||||
@@ -624,7 +624,10 @@ class GPM extends Iterator
|
||||
return false;
|
||||
}
|
||||
|
||||
$blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
|
||||
$file = YamlFile::instance($blueprint_file);
|
||||
$blueprint = (array)$file->content();
|
||||
$file->free();
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
@@ -873,7 +876,9 @@ class GPM extends Iterator
|
||||
// get currently installed version
|
||||
$locator = Grav::instance()['locator'];
|
||||
$blueprints_path = $locator->findResource('plugins://' . $dependency_slug . DS . 'blueprints.yaml');
|
||||
$package_yaml = Yaml::parse(file_get_contents($blueprints_path));
|
||||
$file = YamlFile::instance($blueprints_path);
|
||||
$package_yaml = $file->content();
|
||||
$file->free();
|
||||
$currentlyInstalledVersion = $package_yaml['version'];
|
||||
|
||||
// if requirement is next significant release, check is compatible with currently installed version, might not be
|
||||
|
||||
@@ -296,17 +296,17 @@ class Installer
|
||||
{
|
||||
foreach (new \DirectoryIterator($source_path) as $file) {
|
||||
|
||||
if ($file->isLink() || $file->isDot() || in_array($file->getBasename(),$ignores)) {
|
||||
if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $install_path . DS . $file->getBasename();
|
||||
$path = $install_path . DS . $file->getFilename();
|
||||
|
||||
if ($file->isDir()) {
|
||||
Folder::delete($path);
|
||||
Folder::move($file->getPathname(), $path);
|
||||
|
||||
if ($file->getBasename() == 'bin') {
|
||||
if ($file->getFilename() === 'bin') {
|
||||
foreach (glob($path . DS . '*') as $bin_file) {
|
||||
@chmod($bin_file, 0755);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ class Licenses
|
||||
|
||||
{
|
||||
if (!isset(self::$file)) {
|
||||
$path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';;
|
||||
$path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';
|
||||
if (!file_exists($path)) {
|
||||
touch($path);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Page;
|
||||
@@ -41,6 +40,7 @@ class Grav extends Container
|
||||
'cache' => 'Grav\Common\Cache',
|
||||
'Grav\Common\Service\SessionServiceProvider',
|
||||
'plugins' => 'Grav\Common\Plugins',
|
||||
'scheduler' => 'Grav\Common\Scheduler\Scheduler',
|
||||
'themes' => 'Grav\Common\Themes',
|
||||
'twig' => 'Grav\Common\Twig\Twig',
|
||||
'taxonomy' => 'Grav\Common\Taxonomy',
|
||||
@@ -54,14 +54,19 @@ class Grav extends Container
|
||||
'exif' => 'Grav\Common\Helpers\Exif',
|
||||
'Grav\Common\Service\StreamsServiceProvider',
|
||||
'Grav\Common\Service\ConfigServiceProvider',
|
||||
'Grav\Common\Service\InflectorServiceProvider',
|
||||
'Grav\Common\Service\BackupsServiceProvider',
|
||||
'inflector' => 'Grav\Common\Inflector',
|
||||
'siteSetupProcessor' => 'Grav\Common\Processors\SiteSetupProcessor',
|
||||
'configurationProcessor' => 'Grav\Common\Processors\ConfigurationProcessor',
|
||||
'loggerProcessor' => 'Grav\Common\Processors\LoggerProcessor',
|
||||
'errorsProcessor' => 'Grav\Common\Processors\ErrorsProcessor',
|
||||
'debuggerInitProcessor' => 'Grav\Common\Processors\DebuggerInitProcessor',
|
||||
'initializeProcessor' => 'Grav\Common\Processors\InitializeProcessor',
|
||||
'backupsProcessor' => 'Grav\Common\Processors\BackupsProcessor',
|
||||
'pluginsProcessor' => 'Grav\Common\Processors\PluginsProcessor',
|
||||
'themesProcessor' => 'Grav\Common\Processors\ThemesProcessor',
|
||||
'schedulerProcessor' => 'Grav\Common\Processors\SchedulerProcessor',
|
||||
'tasksProcessor' => 'Grav\Common\Processors\TasksProcessor',
|
||||
'assetsProcessor' => 'Grav\Common\Processors\AssetsProcessor',
|
||||
'twigProcessor' => 'Grav\Common\Processors\TwigProcessor',
|
||||
@@ -76,11 +81,14 @@ class Grav extends Container
|
||||
protected $processors = [
|
||||
'siteSetupProcessor',
|
||||
'configurationProcessor',
|
||||
'loggerProcessor',
|
||||
'errorsProcessor',
|
||||
'debuggerInitProcessor',
|
||||
'initializeProcessor',
|
||||
'pluginsProcessor',
|
||||
'themesProcessor',
|
||||
'backupsProcessor',
|
||||
'schedulerProcessor',
|
||||
'tasksProcessor',
|
||||
'assetsProcessor',
|
||||
'twigProcessor',
|
||||
@@ -148,7 +156,7 @@ class Grav extends Container
|
||||
// Initialize Locale if set and configured.
|
||||
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
|
||||
$language = $this['language']->getLanguage();
|
||||
setlocale(LC_ALL, strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
|
||||
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
|
||||
} elseif ($this['config']->get('system.default_locale')) {
|
||||
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
|
||||
}
|
||||
@@ -205,11 +213,8 @@ class Grav extends Container
|
||||
*/
|
||||
public function redirectLangSafe($route, $code = null)
|
||||
{
|
||||
/** @var Language $language */
|
||||
$language = $this['language'];
|
||||
|
||||
if (!$this['uri']->isExternal($route) && $language->enabled() && $language->isIncludeDefaultLanguage()) {
|
||||
$this->redirect($language->getLanguage() . $route, $code);
|
||||
if (!$this['uri']->isExternal($route)) {
|
||||
$this->redirect($this['pages']->route($route), $code);
|
||||
} else {
|
||||
$this->redirect($route, $code);
|
||||
}
|
||||
@@ -290,7 +295,7 @@ class Grav extends Container
|
||||
public function shutdown()
|
||||
{
|
||||
// Prevent user abort allowing onShutdown event to run without interruptions.
|
||||
if (function_exists('ignore_user_abort')) {
|
||||
if (\function_exists('ignore_user_abort')) {
|
||||
@ignore_user_abort(true);
|
||||
}
|
||||
|
||||
@@ -304,7 +309,7 @@ class Grav extends Container
|
||||
// the connection to the client open. This will make page loads to feel much faster.
|
||||
|
||||
// FastCGI allows us to flush all response data to the client and finish the request.
|
||||
$success = function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
|
||||
$success = \function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
|
||||
|
||||
if (!$success) {
|
||||
// Unfortunately without FastCGI there is no way to force close the connection.
|
||||
@@ -327,7 +332,7 @@ class Grav extends Container
|
||||
|
||||
// Get length and close the connection.
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
header("Connection: close");
|
||||
header('Connection: close');
|
||||
|
||||
ob_end_flush();
|
||||
@ob_flush();
|
||||
@@ -346,8 +351,8 @@ class Grav extends Container
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$closure = $this->$method;
|
||||
call_user_func_array($closure, $args);
|
||||
$closure = $this->{$method};
|
||||
\call_user_func_array($closure, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,7 +398,7 @@ class Grav extends Container
|
||||
protected function registerServices()
|
||||
{
|
||||
foreach (self::$diMap as $serviceKey => $serviceClass) {
|
||||
if (is_int($serviceKey)) {
|
||||
if (\is_int($serviceKey)) {
|
||||
$this->registerServiceProvider($serviceClass);
|
||||
} else {
|
||||
$this->registerService($serviceKey, $serviceClass);
|
||||
@@ -443,7 +448,7 @@ class Grav extends Container
|
||||
/** @var Config $config */
|
||||
$config = $this['config'];
|
||||
|
||||
$uri_extension = $uri->extension();
|
||||
$uri_extension = strtolower($uri->extension());
|
||||
$fallback_types = $config->get('system.media.allowed_fallback_types', null);
|
||||
$supported_types = $config->get('media.types');
|
||||
|
||||
@@ -470,8 +475,8 @@ class Grav extends Container
|
||||
/** @var Medium $medium */
|
||||
$medium = $media[$media_file];
|
||||
foreach ($uri->query(null, true) as $action => $params) {
|
||||
if (in_array($action, ImageMedium::$magic_actions)) {
|
||||
call_user_func_array([&$medium, $action], explode(',', $params));
|
||||
if (\in_array($action, ImageMedium::$magic_actions, true)) {
|
||||
\call_user_func_array([&$medium, $action], explode(',', $params));
|
||||
}
|
||||
}
|
||||
Utils::download($medium->path(), false);
|
||||
@@ -490,7 +495,7 @@ class Grav extends Container
|
||||
|
||||
if ($extension) {
|
||||
$download = true;
|
||||
if (in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []))) {
|
||||
if (\in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []), true)) {
|
||||
$download = false;
|
||||
}
|
||||
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* @deprecated 2.0
|
||||
* @deprecated 1.4 Use Grav::instance() instead
|
||||
*/
|
||||
trait GravTrait
|
||||
{
|
||||
@@ -24,8 +24,7 @@ trait GravTrait
|
||||
self::$grav = Grav::instance();
|
||||
}
|
||||
|
||||
$caller = self::$grav['debugger']->getCaller();
|
||||
self::$grav['debugger']->addMessage("Deprecated GravTrait used in {$caller['file']}", 'deprecated');
|
||||
user_error(__TRAIT__ . ' is deprecated since Grav 1.4, use Grav::instance() instead', E_USER_DEPRECATED);
|
||||
|
||||
return self::$grav;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
class Base32 {
|
||||
protected static $base32Chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
protected static $base32Lookup = array(
|
||||
class Base32
|
||||
{
|
||||
protected static $base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
protected static $base32Lookup = [
|
||||
0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
|
||||
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
|
||||
@@ -22,7 +22,7 @@ class Base32 {
|
||||
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
|
||||
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
|
||||
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* Encode in Base32
|
||||
@@ -30,19 +30,23 @@ class Base32 {
|
||||
* @param $bytes
|
||||
* @return string
|
||||
*/
|
||||
public static function encode( $bytes ) {
|
||||
public static function encode($bytes)
|
||||
{
|
||||
$i = 0; $index = 0; $digit = 0;
|
||||
$base32 = '';
|
||||
$bytes_len = strlen($bytes);
|
||||
while( $i < $bytes_len ) {
|
||||
$currByte = ord($bytes{$i});
|
||||
$bytesLen = \strlen($bytes);
|
||||
|
||||
while ($i < $bytesLen) {
|
||||
$currByte = \ord($bytes[$i]);
|
||||
|
||||
/* Is the current digit going to span a byte boundary? */
|
||||
if( $index > 3 ) {
|
||||
if( ($i + 1) < $bytes_len ) {
|
||||
$nextByte = ord($bytes{$i+1});
|
||||
if ($index > 3) {
|
||||
if (($i + 1) < $bytesLen) {
|
||||
$nextByte = \ord($bytes[$i+1]);
|
||||
} else {
|
||||
$nextByte = 0;
|
||||
}
|
||||
|
||||
$digit = $currByte & (0xFF >> $index);
|
||||
$index = ($index + 5) % 8;
|
||||
$digit <<= $index;
|
||||
@@ -51,9 +55,12 @@ class Base32 {
|
||||
} else {
|
||||
$digit = ($currByte >> (8 - ($index + 5))) & 0x1F;
|
||||
$index = ($index + 5) % 8;
|
||||
if( $index === 0 ) $i++;
|
||||
if ($index === 0) {
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$base32 .= self::$base32Chars{$digit};
|
||||
|
||||
$base32 .= self::$base32Chars[$digit];
|
||||
}
|
||||
return $base32;
|
||||
}
|
||||
@@ -64,27 +71,39 @@ class Base32 {
|
||||
* @param $base32
|
||||
* @return string
|
||||
*/
|
||||
public static function decode( $base32 ) {
|
||||
$bytes = array();
|
||||
$base32_len = strlen($base32);
|
||||
for( $i=$base32_len*5/8-1; $i>=0; --$i ) {
|
||||
public static function decode($base32)
|
||||
{
|
||||
$bytes = [];
|
||||
$base32Len = \strlen($base32);
|
||||
$base32LookupLen = \count(self::$base32Lookup);
|
||||
|
||||
for ($i = $base32Len * 5 / 8 - 1; $i >= 0; --$i) {
|
||||
$bytes[] = 0;
|
||||
}
|
||||
for( $i = 0, $index = 0, $offset = 0; $i < $base32_len; $i++ ) {
|
||||
$lookup = ord($base32{$i}) - ord('0');
|
||||
|
||||
for ($i = 0, $index = 0, $offset = 0; $i < $base32Len; $i++) {
|
||||
$lookup = \ord($base32[$i]) - \ord('0');
|
||||
|
||||
/* Skip chars outside the lookup table */
|
||||
if( $lookup < 0 || $lookup >= count(self::$base32Lookup) ) {
|
||||
if ($lookup < 0 || $lookup >= $base32LookupLen) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$digit = self::$base32Lookup[$lookup];
|
||||
|
||||
/* If this digit is not in the table, ignore it */
|
||||
if( $digit == 0xFF ) continue;
|
||||
if( $index <= 3 ) {
|
||||
if ($digit === 0xFF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($index <= 3) {
|
||||
$index = ($index + 5) % 8;
|
||||
if( $index == 0) {
|
||||
if ($index === 0) {
|
||||
$bytes[$offset] |= $digit;
|
||||
$offset++;
|
||||
if( $offset >= count($bytes) ) break;
|
||||
if ($offset >= \count($bytes)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$bytes[$offset] |= $digit << (8 - $index);
|
||||
}
|
||||
@@ -92,12 +111,18 @@ class Base32 {
|
||||
$index = ($index + 5) % 8;
|
||||
$bytes[$offset] |= ($digit >> $index);
|
||||
$offset++;
|
||||
if ($offset >= count($bytes) ) break;
|
||||
if ($offset >= \count($bytes)) {
|
||||
break;
|
||||
}
|
||||
$bytes[$offset] |= $digit << (8 - $index);
|
||||
}
|
||||
}
|
||||
|
||||
$bites = '';
|
||||
foreach( $bytes as $byte ) $bites .= chr($byte);
|
||||
foreach ($bytes as $byte) {
|
||||
$bites .= \chr($byte);
|
||||
}
|
||||
|
||||
return $bites;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +172,9 @@ class Excerpts
|
||||
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
|
||||
$url_parts['path'] = Grav::instance()['base_url_relative'] . '/' . static::resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
|
||||
unset($url_parts['stream'], $url_parts['scheme']);
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ class Truncator {
|
||||
if ($letters->key() >= $limit) {
|
||||
|
||||
$currentText = $letters->currentTextPosition();
|
||||
$currentText[0]->nodeValue = substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
|
||||
$currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
|
||||
self::removeProceedingNodes($currentText[0], $body);
|
||||
|
||||
if (!empty($ellipsis)) {
|
||||
|
||||
@@ -190,10 +190,11 @@ class Inflector
|
||||
public function hyphenize($word)
|
||||
{
|
||||
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
|
||||
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1);
|
||||
$regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex2);
|
||||
$regex2 = preg_replace('/([a-z])([A-Z])/', '\1-\2', $regex1);
|
||||
$regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
|
||||
$regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
|
||||
|
||||
return strtolower($regex3);
|
||||
return strtolower($regex4);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -181,7 +181,7 @@ class Language
|
||||
$uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
|
||||
|
||||
// Store in session if language is different.
|
||||
if (isset($this->grav['session']) && $this->grav['session']->started()
|
||||
if (isset($this->grav['session']) && $this->grav['session']->isStarted()
|
||||
&& $this->config->get('system.languages.session_store_active', true)
|
||||
&& $this->grav['session']->active_language != $this->active
|
||||
) {
|
||||
@@ -189,7 +189,7 @@ class Language
|
||||
}
|
||||
} else {
|
||||
// Try getting language from the session, else no active.
|
||||
if (isset($this->grav['session']) && $this->grav['session']->started()
|
||||
if (isset($this->grav['session']) && $this->grav['session']->isStarted()
|
||||
&& $this->config->get('system.languages.session_store_active', true)) {
|
||||
$this->active = $this->grav['session']->active_language ?: null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\Media\Interfaces;
|
||||
|
||||
/**
|
||||
* Class implements media collection interface.
|
||||
*/
|
||||
interface MediaCollectionInterface extends \Grav\Framework\Media\Interfaces\MediaCollectionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\Media\Interfaces;
|
||||
|
||||
/**
|
||||
* Class implements media interface.
|
||||
*/
|
||||
interface MediaInterface extends \Grav\Framework\Media\Interfaces\MediaInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\Media\Interfaces;
|
||||
|
||||
/**
|
||||
* Class implements media object interface.
|
||||
*/
|
||||
interface MediaObjectInterface extends \Grav\Framework\Media\Interfaces\MediaObjectInterface
|
||||
{
|
||||
}
|
||||
112
system/src/Grav/Common/Media/Traits/MediaTrait.php
Normal file
112
system/src/Grav/Common/Media/Traits/MediaTrait.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace Grav\Common\Media\Traits;
|
||||
|
||||
use Grav\Common\Cache;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Page\Media;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
trait MediaTrait
|
||||
{
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Get filesystem path to the associated media.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
abstract public function getMediaFolder();
|
||||
|
||||
/**
|
||||
* Get display order for the associated media.
|
||||
*
|
||||
* @return array Empty array means default ordering.
|
||||
*/
|
||||
abstract public function getMediaOrder();
|
||||
|
||||
/**
|
||||
* Get URI ot the associated media. Method will return null if path isn't URI.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getMediaUri()
|
||||
{
|
||||
$folder = $this->getMediaFolder();
|
||||
|
||||
if (strpos($folder, '://')) {
|
||||
return $folder;
|
||||
}
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$user = $locator->findResource('user://');
|
||||
if (strpos($folder, $user) === 0) {
|
||||
return 'user://' . substr($folder, \strlen($user)+1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated media collection.
|
||||
*
|
||||
* @return MediaCollectionInterface Representation of associated media.
|
||||
*/
|
||||
public function getMedia()
|
||||
{
|
||||
$cache = $this->getMediaCache();
|
||||
|
||||
if ($this->media === null) {
|
||||
// Use cached media if possible.
|
||||
$cacheKey = md5('media' . $this->getCacheKey());
|
||||
if (!$media = $cache->fetch($cacheKey)) {
|
||||
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
|
||||
$cache->save($cacheKey, $media);
|
||||
}
|
||||
$this->media = $media;
|
||||
}
|
||||
|
||||
return $this->media;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the associated media collection.
|
||||
*
|
||||
* @param MediaCollectionInterface $media Representation of associated media.
|
||||
* @return $this
|
||||
*/
|
||||
protected function setMedia(MediaCollectionInterface $media)
|
||||
{
|
||||
$cache = $this->getMediaCache();
|
||||
$cacheKey = md5('media' . $this->getCacheKey());
|
||||
$cache->save($cacheKey, $media);
|
||||
|
||||
$this->media = $media;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear media cache.
|
||||
*/
|
||||
protected function clearMediaCache()
|
||||
{
|
||||
$cache = $this->getMediaCache();
|
||||
$cacheKey = md5('media' . $this->getCacheKey());
|
||||
$cache->delete($cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Cache
|
||||
*/
|
||||
protected function getMediaCache()
|
||||
{
|
||||
return Grav::instance()['cache'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getCacheKey();
|
||||
}
|
||||
9
system/src/Grav/Common/Page/Interfaces/PageInterface.php
Normal file
9
system/src/Grav/Common/Page/Interfaces/PageInterface.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
/**
|
||||
* Class implements page interface.
|
||||
*/
|
||||
interface PageInterface
|
||||
{
|
||||
}
|
||||
@@ -9,11 +9,11 @@
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Common\Page\Medium\AbstractMedia;
|
||||
use Grav\Common\Page\Medium\GlobalMedia;
|
||||
use Grav\Common\Page\Medium\MediumFactory;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Media extends AbstractMedia
|
||||
{
|
||||
@@ -24,11 +24,13 @@ class Media extends AbstractMedia
|
||||
protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @param string $path
|
||||
* @param array $media_order
|
||||
*/
|
||||
public function __construct($path)
|
||||
public function __construct($path, array $media_order = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->media_order = $media_order;
|
||||
|
||||
$this->__wakeup();
|
||||
$this->init();
|
||||
@@ -86,7 +88,7 @@ class Media extends AbstractMedia
|
||||
/** @var \DirectoryIterator $info */
|
||||
foreach ($iterator as $path => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || $info->getBasename()[0] === '.') {
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || $info->getFilename()[0] === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@ namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Getters;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
abstract class AbstractMedia extends Getters
|
||||
abstract class AbstractMedia extends Getters implements MediaCollectionInterface
|
||||
{
|
||||
protected $gettersVariable = 'instances';
|
||||
|
||||
@@ -21,6 +23,7 @@ abstract class AbstractMedia extends Getters
|
||||
protected $videos = [];
|
||||
protected $audios = [];
|
||||
protected $files = [];
|
||||
protected $media_order;
|
||||
|
||||
/**
|
||||
* Get medium by filename.
|
||||
@@ -44,10 +47,25 @@ abstract class AbstractMedia extends Getters
|
||||
return $this->offsetGet($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
$object = parent::offsetGet($offset);
|
||||
|
||||
// It would be nice if previous image modification would not affect the later ones.
|
||||
//$object = $object ? clone($object) : null;
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
* @return array|MediaObjectInterface[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
@@ -59,7 +77,7 @@ abstract class AbstractMedia extends Getters
|
||||
/**
|
||||
* Get a list of all image media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
* @return array|MediaObjectInterface[]
|
||||
*/
|
||||
public function images()
|
||||
{
|
||||
@@ -70,7 +88,7 @@ abstract class AbstractMedia extends Getters
|
||||
/**
|
||||
* Get a list of all video media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
* @return array|MediaObjectInterface[]
|
||||
*/
|
||||
public function videos()
|
||||
{
|
||||
@@ -81,7 +99,7 @@ abstract class AbstractMedia extends Getters
|
||||
/**
|
||||
* Get a list of all audio media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
* @return array|MediaObjectInterface[]
|
||||
*/
|
||||
public function audios()
|
||||
{
|
||||
@@ -92,7 +110,7 @@ abstract class AbstractMedia extends Getters
|
||||
/**
|
||||
* Get a list of all file media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
* @return array|MediaObjectInterface[]
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
@@ -102,7 +120,7 @@ abstract class AbstractMedia extends Getters
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Medium $file
|
||||
* @param MediaObjectInterface $file
|
||||
*/
|
||||
protected function add($name, $file)
|
||||
{
|
||||
@@ -130,14 +148,20 @@ abstract class AbstractMedia extends Getters
|
||||
*/
|
||||
protected function orderMedia($media)
|
||||
{
|
||||
$page = Grav::instance()['pages']->get($this->path);
|
||||
if (null === $this->media_order) {
|
||||
$page = Grav::instance()['pages']->get($this->path);
|
||||
|
||||
if ($page && isset($page->header()->media_order)) {
|
||||
$media_order = array_map('trim', explode(',', $page->header()->media_order));
|
||||
$media = Utils::sortArrayByArray($media, $media_order);
|
||||
if ($page && isset($page->header()->media_order)) {
|
||||
$this->media_order = array_map('trim', explode(',', $page->header()->media_order));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->media_order) && is_array($this->media_order)) {
|
||||
$media = Utils::sortArrayByArray($media, $this->media_order);
|
||||
} else {
|
||||
ksort($media, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
@@ -168,8 +192,8 @@ abstract class AbstractMedia extends Getters
|
||||
$type = 'base';
|
||||
|
||||
while (($part = array_shift($fileParts)) !== null) {
|
||||
if ($part != 'meta' && $part != 'thumb') {
|
||||
if (isset($extension)) {
|
||||
if ($part !== 'meta' && $part !== 'thumb') {
|
||||
if (null !== $extension) {
|
||||
$name .= '.' . $extension;
|
||||
}
|
||||
$extension = $part;
|
||||
|
||||
@@ -11,10 +11,16 @@ namespace Grav\Common\Page\Medium;
|
||||
use Grav\Common\Grav;
|
||||
use Gregwar\Image\Exceptions\GenerationError;
|
||||
use Gregwar\Image\Image;
|
||||
use Gregwar\Image\Source;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class ImageFile extends Image
|
||||
{
|
||||
public function __destruct()
|
||||
{
|
||||
$this->getAdapter()->deinit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear previously applied operations
|
||||
*/
|
||||
@@ -30,15 +36,15 @@ class ImageFile extends Image
|
||||
* @param int $quality the quality (for JPEG)
|
||||
* @param bool $actual
|
||||
*
|
||||
* @return mixed|string
|
||||
* @return string
|
||||
*/
|
||||
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
|
||||
{
|
||||
if ($type == 'guess') {
|
||||
if ($type === 'guess') {
|
||||
$type = $this->guessType();
|
||||
}
|
||||
|
||||
if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) {
|
||||
if (!$this->forceCache && !count($this->operations) && $type === $this->guessType()) {
|
||||
return $this->getFilename($this->getFilePath());
|
||||
}
|
||||
|
||||
@@ -60,8 +66,7 @@ class ImageFile extends Image
|
||||
$cacheFile .= $this->prettyName;
|
||||
}
|
||||
|
||||
|
||||
$cacheFile .= '.'.$type;
|
||||
$cacheFile .= '.' . $type;
|
||||
|
||||
// If the files does not exists, save it
|
||||
$image = $this;
|
||||
@@ -76,7 +81,7 @@ class ImageFile extends Image
|
||||
$generate = function ($target) use ($image, $type, $quality) {
|
||||
$result = $image->save($target, $type, $quality);
|
||||
|
||||
if ($result != $target) {
|
||||
if ($result !== $target) {
|
||||
throw new GenerationError($result);
|
||||
}
|
||||
|
||||
@@ -87,15 +92,19 @@ class ImageFile extends Image
|
||||
try {
|
||||
$perms = Grav::instance()['config']->get('system.images.cache_perms', '0755');
|
||||
$perms = octdec($perms);
|
||||
$file = $this->cache->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
|
||||
$file = $this->getCacheSystem()->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
|
||||
} catch (GenerationError $e) {
|
||||
$file = $e->getNewFile();
|
||||
}
|
||||
|
||||
// Nulling the resource
|
||||
$this->getAdapter()->setSource(new Source\File($file));
|
||||
$this->getAdapter()->deinit();
|
||||
|
||||
if ($actual) {
|
||||
return $file;
|
||||
} else {
|
||||
return $this->getFilename($file);
|
||||
}
|
||||
|
||||
return $this->getFilename($file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Grav\Common\Page\Medium;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class ImageMedium extends Medium
|
||||
{
|
||||
@@ -103,6 +104,18 @@ class ImageMedium extends Medium
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
unset($this->image);
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->image = $this->image ? clone $this->image : null;
|
||||
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the medium.
|
||||
*
|
||||
@@ -152,14 +165,20 @@ class ImageMedium extends Medium
|
||||
*/
|
||||
public function url($reset = true)
|
||||
{
|
||||
$image_path = Grav::instance()['locator']->findResource('cache://images', true);
|
||||
$image_dir = Grav::instance()['locator']->findResource('cache://images', false);
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$image_path = $locator->findResource('cache://images', true);
|
||||
$image_dir = $locator->findResource('cache://images', false);
|
||||
$saved_image_path = $this->saveImage();
|
||||
|
||||
$output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $saved_image_path);
|
||||
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
|
||||
|
||||
if ($locator->isStream($output)) {
|
||||
$output = $locator->findResource($output, false);
|
||||
}
|
||||
|
||||
if (Utils::startsWith($output, $image_path)) {
|
||||
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path) . '|', '', $output);
|
||||
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
|
||||
}
|
||||
|
||||
if ($reset) {
|
||||
@@ -226,13 +245,13 @@ class ImageMedium extends Medium
|
||||
{
|
||||
if ($this->get('prettyname')) {
|
||||
return $this->get('prettyname');
|
||||
} else {
|
||||
$basename = $this->get('basename');
|
||||
if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) {
|
||||
$basename = $matches[1];
|
||||
}
|
||||
return $basename;
|
||||
}
|
||||
|
||||
$basename = $this->get('basename');
|
||||
if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) {
|
||||
$basename = $matches[1];
|
||||
}
|
||||
return $basename;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,7 +299,7 @@ class ImageMedium extends Medium
|
||||
// It's possible that MediumFactory::fromFile returns null if the
|
||||
// original image file no longer exists and this class instance was
|
||||
// retrieved from the page cache
|
||||
if (isset($derivative)) {
|
||||
if (null !== $derivative) {
|
||||
$index = 2;
|
||||
$alt_widths = array_keys($this->alternatives);
|
||||
sort($alt_widths);
|
||||
@@ -339,7 +358,6 @@ class ImageMedium extends Medium
|
||||
|
||||
if ($this->image) {
|
||||
$this->image();
|
||||
$this->image->clearOperations(); // Clear previously applied operations
|
||||
$this->querystring('');
|
||||
$this->filter();
|
||||
$this->clearAlternatives();
|
||||
@@ -432,7 +450,7 @@ class ImageMedium extends Medium
|
||||
* Set or get sizes parameter for srcset media action
|
||||
*
|
||||
* @param string $sizes
|
||||
* @return $this
|
||||
* @return string
|
||||
*/
|
||||
public function sizes($sizes = null)
|
||||
{
|
||||
@@ -459,7 +477,7 @@ class ImageMedium extends Medium
|
||||
*/
|
||||
public function width($value = 'auto')
|
||||
{
|
||||
if (!$value || $value == 'auto')
|
||||
if (!$value || $value === 'auto')
|
||||
$this->attributes['width'] = $this->get('width');
|
||||
else
|
||||
$this->attributes['width'] = $value;
|
||||
@@ -480,7 +498,7 @@ class ImageMedium extends Medium
|
||||
*/
|
||||
public function height($value = 'auto')
|
||||
{
|
||||
if (!$value || $value == 'auto')
|
||||
if (!$value || $value === 'auto')
|
||||
$this->attributes['height'] = $this->get('height');
|
||||
else
|
||||
$this->attributes['height'] = $value;
|
||||
@@ -496,11 +514,11 @@ class ImageMedium extends Medium
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($method == 'cropZoom') {
|
||||
if ($method === 'cropZoom') {
|
||||
$method = 'zoomCrop';
|
||||
}
|
||||
|
||||
if (!in_array($method, self::$magic_actions)) {
|
||||
if (!\in_array($method, self::$magic_actions, true)) {
|
||||
return parent::__call($method, $args);
|
||||
}
|
||||
|
||||
@@ -551,6 +569,9 @@ class ImageMedium extends Medium
|
||||
// Use existing cache folder or if it doesn't exist, create it.
|
||||
$cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
|
||||
|
||||
// Make sure we free previous image.
|
||||
unset($this->image);
|
||||
|
||||
$this->image = ImageFile::open($file)
|
||||
->setCacheDir($cacheDir)
|
||||
->setActualCacheDir($cacheDir)
|
||||
@@ -562,7 +583,7 @@ class ImageMedium extends Medium
|
||||
/**
|
||||
* Save the image with cache.
|
||||
*
|
||||
* @return mixed|string
|
||||
* @return string
|
||||
*/
|
||||
protected function saveImage()
|
||||
{
|
||||
@@ -576,7 +597,7 @@ class ImageMedium extends Medium
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
if ($this->get('debug') && !$this->debug_watermarked) {
|
||||
if (!$this->debug_watermarked && $this->get('debug')) {
|
||||
$ratio = $this->get('ratio');
|
||||
if (!$ratio) {
|
||||
$ratio = 1;
|
||||
@@ -623,9 +644,9 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
return $max;
|
||||
} else {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
|
||||
class Medium extends Data implements RenderableInterface
|
||||
class Medium extends Data implements RenderableInterface, MediaObjectInterface
|
||||
{
|
||||
use ParsedownHtmlTrait;
|
||||
|
||||
@@ -73,6 +73,11 @@ class Medium extends Data implements RenderableInterface
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
// Allows future compatibility as parent::__clone() works.
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of this media object
|
||||
*
|
||||
@@ -80,7 +85,7 @@ class Medium extends Data implements RenderableInterface
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return clone($this);
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +199,12 @@ class Medium extends Data implements RenderableInterface
|
||||
*/
|
||||
public function url($reset = true)
|
||||
{
|
||||
$output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->get('filepath'));
|
||||
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $this->get('filepath'));
|
||||
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($output)) {
|
||||
$output = $locator->findResource($output, false);
|
||||
}
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
|
||||
@@ -94,6 +94,40 @@ class VideoMedium extends Medium
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to set the playsinline attribute
|
||||
*
|
||||
* @param bool $status
|
||||
* @return $this
|
||||
*/
|
||||
public function playsinline($status = false)
|
||||
{
|
||||
if($status) {
|
||||
$this->attributes['playsinline'] = true;
|
||||
} else {
|
||||
unset($this->attributes['playsinline']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to set the muted attribute
|
||||
*
|
||||
* @param bool $status
|
||||
* @return $this
|
||||
*/
|
||||
public function muted($status = false)
|
||||
{
|
||||
if($status) {
|
||||
$this->attributes['muted'] = true;
|
||||
} else {
|
||||
unset($this->attributes['muted']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset medium.
|
||||
*
|
||||
|
||||
@@ -12,23 +12,26 @@ use Exception;
|
||||
use Grav\Common\Cache;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Media\Traits\MediaTrait;
|
||||
use Grav\Common\Taxonomy;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
define('PAGE_ORDER_PREFIX_REGEX', '/^[0-9]+\./u');
|
||||
|
||||
class Page
|
||||
class Page implements PageInterface
|
||||
{
|
||||
use MediaTrait;
|
||||
|
||||
/**
|
||||
* @var string Filename. Leave as null if page is folder.
|
||||
*/
|
||||
@@ -65,7 +68,6 @@ class Page
|
||||
protected $summary;
|
||||
protected $raw_content;
|
||||
protected $pagination;
|
||||
protected $media;
|
||||
protected $metadata;
|
||||
protected $title;
|
||||
protected $max_count;
|
||||
@@ -135,7 +137,7 @@ class Page
|
||||
$this->metadata();
|
||||
$this->url();
|
||||
$this->visible();
|
||||
$this->modularTwig($this->slug[0] === '_');
|
||||
$this->modularTwig(strpos($this->slug(), '_') === 0);
|
||||
$this->setPublishState();
|
||||
$this->published();
|
||||
$this->urlExtension();
|
||||
@@ -193,7 +195,7 @@ class Page
|
||||
|
||||
$route = isset($aPage->header()->routes['default']) ? $aPage->header()->routes['default'] : $aPage->rawRoute();
|
||||
if (!$route) {
|
||||
$route = $aPage->slug();
|
||||
$route = $aPage->route();
|
||||
}
|
||||
|
||||
if ($onlyPublished && !$aPage->published()) {
|
||||
@@ -318,8 +320,6 @@ class Page
|
||||
if (!$this->header) {
|
||||
$file = $this->file();
|
||||
if ($file) {
|
||||
// Set some options
|
||||
$file->settings(['native' => true, 'compat' => true]);
|
||||
try {
|
||||
$this->raw_content = $file->markdown();
|
||||
$this->frontmatter = $file->frontmatter();
|
||||
@@ -328,11 +328,12 @@ class Page
|
||||
if (!Utils::isAdminPlugin()) {
|
||||
// If there's a `frontmatter.yaml` file merge that in with the page header
|
||||
// note page's own frontmatter has precedence and will overwrite any defaults
|
||||
$frontmatter_file = $this->path . '/' . $this->folder . '/frontmatter.yaml';
|
||||
if (file_exists($frontmatter_file)) {
|
||||
$frontmatter_data = (array)Yaml::parse(file_get_contents($frontmatter_file));
|
||||
$frontmatterFile = CompiledYamlFile::instance($this->path . '/' . $this->folder . '/frontmatter.yaml');
|
||||
if ($frontmatterFile->exists()) {
|
||||
$frontmatter_data = (array)$frontmatterFile->content();
|
||||
$this->header = (object)array_replace_recursive($frontmatter_data,
|
||||
(array)$this->header);
|
||||
$frontmatterFile->free();
|
||||
}
|
||||
// Process frontmatter with Twig if enabled
|
||||
if (Grav::instance()['config']->get('system.pages.frontmatter.process_twig') === true) {
|
||||
@@ -763,6 +764,8 @@ class Page
|
||||
|
||||
// pages.markdown_extra is deprecated, but still check it...
|
||||
if (!isset($defaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) {
|
||||
user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED);
|
||||
|
||||
$defaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
|
||||
}
|
||||
|
||||
@@ -813,6 +816,8 @@ class Page
|
||||
*/
|
||||
public function setRawContent($content)
|
||||
{
|
||||
$content = $content === null ? '': $content;
|
||||
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
@@ -1122,6 +1127,14 @@ class Page
|
||||
return json_encode($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheKey()
|
||||
{
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the associated media as found in the page folder.
|
||||
*
|
||||
@@ -1131,23 +1144,33 @@ class Page
|
||||
*/
|
||||
public function media($var = null)
|
||||
{
|
||||
/** @var Cache $cache */
|
||||
$cache = Grav::instance()['cache'];
|
||||
|
||||
if ($var) {
|
||||
$this->media = $var;
|
||||
}
|
||||
if ($this->media === null) {
|
||||
// Use cached media if possible.
|
||||
$media_cache_id = md5('media' . $this->id());
|
||||
if (!$media = $cache->fetch($media_cache_id)) {
|
||||
$media = new Media($this->path());
|
||||
$cache->save($media_cache_id, $media);
|
||||
}
|
||||
$this->media = $media;
|
||||
$this->setMedia($var);
|
||||
}
|
||||
|
||||
return $this->media;
|
||||
return $this->getMedia();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filesystem path to the associated media.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMediaFolder()
|
||||
{
|
||||
return $this->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display order for the associated media.
|
||||
*
|
||||
* @return array Empty array means default ordering.
|
||||
*/
|
||||
public function getMediaOrder()
|
||||
{
|
||||
$header = $this->header();
|
||||
|
||||
return isset($header->media_order) ? array_map('trim', explode(',', (string)$header->media_order)) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1561,7 +1584,7 @@ class Page
|
||||
}
|
||||
|
||||
if (empty($this->slug)) {
|
||||
$this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
|
||||
$this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder)) ?: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1626,14 +1649,19 @@ class Page
|
||||
* Gets the url for the Page.
|
||||
*
|
||||
* @param bool $include_host Defaults false, but true would include http://yourhost.com
|
||||
* @param bool $canonical true to return the canonical URL
|
||||
* @param bool $include_lang
|
||||
* @param bool $canonical True to return the canonical URL
|
||||
* @param bool $include_base Include base url on multisite as well as language code
|
||||
* @param bool $raw_route
|
||||
*
|
||||
* @return string The url.
|
||||
*/
|
||||
public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false)
|
||||
public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false)
|
||||
{
|
||||
// Override any URL when external_url is set
|
||||
if (isset($this->external_url)) {
|
||||
return $this->external_url;
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Pages $pages */
|
||||
@@ -1642,41 +1670,25 @@ class Page
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $grav['uri'];
|
||||
|
||||
// Override any URL when external_url is set
|
||||
if (isset($this->external_url)) {
|
||||
return $this->external_url;
|
||||
}
|
||||
|
||||
// get pre-route
|
||||
if ($include_lang && $language->enabled()) {
|
||||
$pre_route = $language->getLanguageURLPrefix();
|
||||
} else {
|
||||
$pre_route = '';
|
||||
}
|
||||
// get base route (multisite base and language)
|
||||
$route = $include_base ? $pages->baseRoute() : '';
|
||||
|
||||
// add full route if configured to do so
|
||||
if ($config->get('system.absolute_urls', false)) {
|
||||
if (!$include_host && $config->get('system.absolute_urls', false)) {
|
||||
$include_host = true;
|
||||
}
|
||||
|
||||
// get canonical route if requested
|
||||
if ($canonical) {
|
||||
$route = $pre_route . $this->routeCanonical();
|
||||
$route .= $this->routeCanonical();
|
||||
} elseif ($raw_route) {
|
||||
$route = $pre_route . $this->rawRoute();
|
||||
$route .= $this->rawRoute();
|
||||
} else {
|
||||
$route = $pre_route . $this->route();
|
||||
$route .= $this->route();
|
||||
}
|
||||
|
||||
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
|
||||
|
||||
$url = $rootUrl . '/' . trim($route, '/') . $this->urlExtension();
|
||||
/** @var Uri $uri */
|
||||
$uri = $grav['uri'];
|
||||
$url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension();
|
||||
|
||||
// trim trailing / if not root
|
||||
if ($url !== '/') {
|
||||
@@ -1790,7 +1802,7 @@ class Page
|
||||
public function routeCanonical($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->routes['canonical'] = (array)$var;
|
||||
$this->routes['canonical'] = $var;
|
||||
}
|
||||
|
||||
if (!empty($this->routes) && isset($this->routes['canonical'])) {
|
||||
@@ -2955,4 +2967,26 @@ class Page
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Page Unmodified (original) version of the page.
|
||||
*
|
||||
* @return Page
|
||||
* The original version of the page.
|
||||
*/
|
||||
public function getOriginal()
|
||||
{
|
||||
return $this->_original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the action.
|
||||
*
|
||||
* @return string
|
||||
* The Action string.
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
return $this->_action;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class Pages
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected $baseUrl = [];
|
||||
protected $baseRoute = [];
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
@@ -120,47 +120,69 @@ class Pages
|
||||
if ($path !== null) {
|
||||
$path = trim($path, '/');
|
||||
$this->base = $path ? '/' . $path : null;
|
||||
$this->baseUrl = [];
|
||||
$this->baseRoute = [];
|
||||
}
|
||||
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get base route for Grav pages.
|
||||
*
|
||||
* @param string $lang Optional language code for multilingual routes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function baseRoute($lang = null)
|
||||
{
|
||||
$key = $lang ?: 'default';
|
||||
|
||||
if (!isset($this->baseRoute[$key])) {
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
$path_base = rtrim($this->base(), '/');
|
||||
$path_lang = $language->enabled() ? $language->getLanguageURLPrefix($lang) : '';
|
||||
|
||||
$this->baseRoute[$key] = $path_base . $path_lang;
|
||||
}
|
||||
|
||||
return $this->baseRoute[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get route for Grav site.
|
||||
*
|
||||
* @param string $route Optional route to the page.
|
||||
* @param string $lang Optional language code for multilingual links.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function route($route = '/', $lang = null)
|
||||
{
|
||||
if (!$route || $route === '/') {
|
||||
return $this->baseRoute($lang) ?: '/';
|
||||
}
|
||||
|
||||
return $this->baseRoute($lang) . $route;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get base URL for Grav pages.
|
||||
*
|
||||
* @param string $lang Optional language code for multilingual links.
|
||||
* @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
|
||||
* @param string $lang Optional language code for multilingual links.
|
||||
* @param bool|null $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function baseUrl($lang = null, $absolute = null)
|
||||
{
|
||||
$lang = (string) $lang;
|
||||
$type = $absolute === null ? 'base_url' : ($absolute ? 'base_url_absolute' : 'base_url_relative');
|
||||
$key = "{$lang} {$type}";
|
||||
|
||||
if (!isset($this->baseUrl[$key])) {
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
if (!$lang) {
|
||||
$lang = $language->getActive();
|
||||
}
|
||||
|
||||
$path_append = rtrim($this->grav['pages']->base(), '/');
|
||||
if ($language->getDefault() !== $lang || $config->get('system.languages.include_default_lang') === true) {
|
||||
$path_append .= $lang ? '/' . $lang : '';
|
||||
}
|
||||
|
||||
$this->baseUrl[$key] = $this->grav[$type] . $path_append;
|
||||
}
|
||||
|
||||
return $this->baseUrl[$key];
|
||||
return $this->grav[$type] . $this->baseRoute($lang);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +201,7 @@ class Pages
|
||||
|
||||
/**
|
||||
*
|
||||
* Get home URL for Grav site.
|
||||
* Get URL for Grav site.
|
||||
*
|
||||
* @param string $route Optional route to the page.
|
||||
* @param string $lang Optional language code for multilingual links.
|
||||
@@ -189,7 +211,7 @@ class Pages
|
||||
*/
|
||||
public function url($route = '/', $lang = null, $absolute = null)
|
||||
{
|
||||
if ($route === '/') {
|
||||
if (!$route || $route === '/') {
|
||||
return $this->homeUrl($lang, $absolute);
|
||||
}
|
||||
|
||||
@@ -688,7 +710,6 @@ class Pages
|
||||
public static function getTypes()
|
||||
{
|
||||
if (!self::$types) {
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
$scanBlueprintsAndTemplates = function () use ($grav) {
|
||||
@@ -726,6 +747,18 @@ class Pages
|
||||
$scanBlueprintsAndTemplates();
|
||||
}
|
||||
|
||||
// Register custom paths to the locator.
|
||||
$locator = $grav['locator'];
|
||||
foreach (self::$types as $type => $paths) {
|
||||
foreach ($paths as $k => $path) {
|
||||
if (strpos($path, 'blueprints://') === 0) {
|
||||
unset($paths[$k]);
|
||||
}
|
||||
}
|
||||
if ($paths) {
|
||||
$locator->addPath('blueprints', "pages/$type.yaml", $paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$types;
|
||||
@@ -1007,32 +1040,63 @@ class Pages
|
||||
throw new \RuntimeException('Fatal error when creating page instances.');
|
||||
}
|
||||
|
||||
$content_exists = false;
|
||||
$pages_found = new \GlobIterator($directory . '/*' . CONTENT_EXT);
|
||||
// Build regular expression for all the allowed page extensions.
|
||||
$page_extensions = $language->getFallbackPageExtensions();
|
||||
$regex = '/^[^\.]*(' . implode('|', array_map(
|
||||
function ($str) {
|
||||
return preg_quote($str, '/');
|
||||
},
|
||||
$page_extensions
|
||||
)) . ')$/';
|
||||
|
||||
$folders = [];
|
||||
$page_found = null;
|
||||
|
||||
$page_extension = '';
|
||||
$last_modified = 0;
|
||||
|
||||
if ($pages_found && count($pages_found) > 0) {
|
||||
$iterator = new \FilesystemIterator($directory);
|
||||
/** @var \FilesystemIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$filename = $file->getFilename();
|
||||
|
||||
$page_extensions = $language->getFallbackPageExtensions();
|
||||
// Ignore all hidden files if set.
|
||||
if ($this->ignore_hidden && $filename && $filename[0] === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($page_extensions as $extension) {
|
||||
foreach ($pages_found as $found) {
|
||||
if ($found->isDir()) {
|
||||
continue;
|
||||
}
|
||||
$regex = '/^[^\.]*' . preg_quote($extension, '/') . '$/';
|
||||
if (preg_match($regex, $found->getFilename())) {
|
||||
$page_found = $found;
|
||||
$page_extension = $extension;
|
||||
break 2;
|
||||
}
|
||||
// Handle folders later.
|
||||
if ($file->isDir()) {
|
||||
// But ignore all folders in ignore list.
|
||||
if (!\in_array($filename, $this->ignore_folders, true)) {
|
||||
$folders[] = $file;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore all files in ignore list.
|
||||
if (\in_array($filename, $this->ignore_files, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update last modified date to match the last updated file in the folder.
|
||||
$modified = $file->getMTime();
|
||||
if ($modified > $last_modified) {
|
||||
$last_modified = $modified;
|
||||
}
|
||||
|
||||
// Page is the one that matches to $page_extensions list with the lowest index number.
|
||||
if (preg_match($regex, $filename, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
$ext = $matches[1][0];
|
||||
|
||||
if ($page_found === null || array_search($ext, $page_extensions, true) < array_search($page_extension, $page_extensions, true)) {
|
||||
$page_found = $file;
|
||||
$page_extension = $ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent && !empty($page_found)) {
|
||||
$content_exists = false;
|
||||
if ($parent && $page_found) {
|
||||
$page->init($page_found, $page_extension);
|
||||
|
||||
$content_exists = true;
|
||||
@@ -1042,48 +1106,31 @@ class Pages
|
||||
}
|
||||
}
|
||||
|
||||
// set current modified of page
|
||||
$last_modified = $page->modified();
|
||||
// Now handle all the folders under the page.
|
||||
/** @var \FilesystemIterator $file */
|
||||
foreach ($folders as $file) {
|
||||
$filename = $file->getFilename();
|
||||
|
||||
$iterator = new \FilesystemIterator($directory);
|
||||
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$name = $file->getFilename();
|
||||
|
||||
// Ignore all hidden files if set.
|
||||
if ($this->ignore_hidden && $name && $name[0] === '.') {
|
||||
// if folder contains separator, continue
|
||||
if (Utils::contains($file->getFilename(), $config->get('system.param_sep', ':'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($file->isFile()) {
|
||||
// Update the last modified if it's newer than already found
|
||||
if (!in_array($file->getBasename(), $this->ignore_files) && ($modified = $file->getMTime()) > $last_modified) {
|
||||
$last_modified = $modified;
|
||||
}
|
||||
} elseif ($file->isDir() && !in_array($file->getFilename(), $this->ignore_folders)) {
|
||||
if (!$page->path()) {
|
||||
$page->path($file->getPath());
|
||||
}
|
||||
|
||||
// if folder contains separator, continue
|
||||
if (Utils::contains($file->getFilename(), $config->get('system.param_sep', ':'))) {
|
||||
continue;
|
||||
}
|
||||
$path = $directory . DS . $filename;
|
||||
$child = $this->recurse($path, $page);
|
||||
|
||||
if (!$page->path()) {
|
||||
$page->path($file->getPath());
|
||||
}
|
||||
if (Utils::startsWith($filename, '_')) {
|
||||
$child->routable(false);
|
||||
}
|
||||
|
||||
$path = $directory . DS . $name;
|
||||
$child = $this->recurse($path, $page);
|
||||
$this->children[$page->path()][$child->path()] = ['slug' => $child->slug()];
|
||||
|
||||
if (Utils::startsWith($name, '_')) {
|
||||
$child->routable(false);
|
||||
}
|
||||
|
||||
$this->children[$page->path()][$child->path()] = ['slug' => $child->slug()];
|
||||
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
}
|
||||
|
||||
if (!$blueprint && $this->systemBlueprints) {
|
||||
$blueprint = isset($this->systemBlueprints[$type]) ? $this->systemBlueprints[$type] : $this->systemBlueprints['default'];
|
||||
$blueprint = $this->systemBlueprints[$type] ?? $this->systemBlueprints['default'];
|
||||
}
|
||||
|
||||
if ($blueprint) {
|
||||
@@ -43,7 +43,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
|
||||
public function scanBlueprints($uri)
|
||||
{
|
||||
if (!is_string($uri)) {
|
||||
if (!\is_string($uri)) {
|
||||
throw new \InvalidArgumentException('First parameter must be URI');
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
|
||||
public function scanTemplates($uri)
|
||||
{
|
||||
if (!is_string($uri)) {
|
||||
if (!\is_string($uri)) {
|
||||
throw new \InvalidArgumentException('First parameter must be URI');
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
if (strpos($name, '/')) {
|
||||
continue;
|
||||
}
|
||||
$list[$name] = ucfirst(strtr($name, '_', ' '));
|
||||
$list[$name] = ucfirst(str_replace('_', ' ', $name));
|
||||
}
|
||||
ksort($list);
|
||||
return $list;
|
||||
@@ -109,7 +109,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
if (strpos($name, 'modular/') !== 0) {
|
||||
continue;
|
||||
}
|
||||
$list[$name] = trim(ucfirst(strtr(basename($name), '_', ' ')));
|
||||
$list[$name] = ucfirst(trim(str_replace('_', ' ', basename($name))));
|
||||
}
|
||||
ksort($list);
|
||||
return $list;
|
||||
|
||||
@@ -33,7 +33,7 @@ class Plugins extends Iterator
|
||||
if (!$directory->isDir()) {
|
||||
continue;
|
||||
}
|
||||
$plugins[] = $directory->getBasename();
|
||||
$plugins[] = $directory->getFilename();
|
||||
}
|
||||
|
||||
natsort($plugins);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Grav\Common\Processors;
|
||||
|
||||
class AssetsProcessor extends ProcessorBase implements ProcessorInterface
|
||||
{
|
||||
public $id = 'assets';
|
||||
public $id = '_assets';
|
||||
public $title = 'Assets';
|
||||
|
||||
public function process()
|
||||
|
||||
23
system/src/Grav/Common/Processors/BackupsProcessor.php
Normal file
23
system/src/Grav/Common/Processors/BackupsProcessor.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class BackupsProcessor extends ProcessorBase implements ProcessorInterface
|
||||
{
|
||||
public $id = '_backups';
|
||||
public $title = 'Backups';
|
||||
|
||||
public function process()
|
||||
{
|
||||
$backups = $this->container['backups'];
|
||||
$backups->init();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
class InitializeProcessor extends ProcessorBase implements ProcessorInterface
|
||||
{
|
||||
public $id = 'init';
|
||||
@@ -15,29 +19,36 @@ class InitializeProcessor extends ProcessorBase implements ProcessorInterface
|
||||
|
||||
public function process()
|
||||
{
|
||||
$this->container['config']->debug();
|
||||
/** @var Config $config */
|
||||
$config = $this->container['config'];
|
||||
$config->debug();
|
||||
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
if ($this->container['config']->get('system.cache.gzip')) {
|
||||
if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) {
|
||||
// Enable zip/deflate with a fallback in case of if browser does not support compressing.
|
||||
if (!@ob_start("ob_gzhandler")) {
|
||||
ob_start();
|
||||
}
|
||||
ob_start();
|
||||
}
|
||||
|
||||
// Initialize the timezone.
|
||||
if ($this->container['config']->get('system.timezone')) {
|
||||
if ($config->get('system.timezone')) {
|
||||
date_default_timezone_set($this->container['config']->get('system.timezone'));
|
||||
}
|
||||
|
||||
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
|
||||
if ($this->container['config']->get('system.session.initialize', 1) && isset($this->container['session'])) {
|
||||
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
|
||||
$this->container['session']->init();
|
||||
}
|
||||
|
||||
// Initialize uri.
|
||||
$this->container['uri']->init();
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->container['uri'];
|
||||
$uri->init();
|
||||
|
||||
// Redirect pages with trailing slash if configured to do so.
|
||||
$path = $uri->path() ?: '/';
|
||||
if ($path !== '/' && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($path, '/')) {
|
||||
$this->container->redirect(rtrim($path, '/'), 302);
|
||||
}
|
||||
|
||||
$this->container->setLocale();
|
||||
}
|
||||
|
||||
44
system/src/Grav/Common/Processors/LoggerProcessor.php
Normal file
44
system/src/Grav/Common/Processors/LoggerProcessor.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\SyslogHandler;
|
||||
|
||||
class LoggerProcessor extends ProcessorBase implements ProcessorInterface
|
||||
{
|
||||
public $id = '_logger';
|
||||
public $title = 'Logger';
|
||||
|
||||
public function process()
|
||||
{
|
||||
$grav = $this->container;
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
$log = $grav['log'];
|
||||
$handler = $config->get('system.log.handler', 'file');
|
||||
|
||||
|
||||
switch ($handler) {
|
||||
case 'syslog':
|
||||
$log->popHandler();
|
||||
|
||||
$facility = $config->get('system.log.syslog.facility', 'local6');
|
||||
$logHandler = new SyslogHandler('grav', $facility);
|
||||
$formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
|
||||
$logHandler->setFormatter($formatter);
|
||||
|
||||
$log->pushHandler($logHandler);
|
||||
break;
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
}
|
||||
23
system/src/Grav/Common/Processors/SchedulerProcessor.php
Normal file
23
system/src/Grav/Common/Processors/SchedulerProcessor.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class SchedulerProcessor extends ProcessorBase implements ProcessorInterface
|
||||
{
|
||||
public $id = '_scheduler';
|
||||
public $title = 'Scheduler';
|
||||
|
||||
public function process()
|
||||
{
|
||||
$scheduler = $this->container['scheduler'];
|
||||
$this->container->fireEvent('onSchedulerInitialized', new Event(['scheduler' => $scheduler]));
|
||||
}
|
||||
}
|
||||
512
system/src/Grav/Common/Scheduler/Cron.php
Normal file
512
system/src/Grav/Common/Scheduler/Cron.php
Normal file
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Scheduler
|
||||
* @author Originally based on jqCron by Arnaud Buathier <arnaud@arnapou.net> modified for Grav integration
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Scheduler;
|
||||
|
||||
/*
|
||||
* Usage examples :
|
||||
* ----------------
|
||||
*
|
||||
* $cron = new Cron('10-30/5 12 * * *');
|
||||
*
|
||||
* var_dump($cron->getMinutes());
|
||||
* // array(5) {
|
||||
* // [0]=> int(10)
|
||||
* // [1]=> int(15)
|
||||
* // [2]=> int(20)
|
||||
* // [3]=> int(25)
|
||||
* // [4]=> int(30)
|
||||
* // }
|
||||
*
|
||||
* var_dump($cron->getText('fr'));
|
||||
* // string(32) "Chaque jour à 12:10,15,20,25,30"
|
||||
*
|
||||
* var_dump($cron->getText('en'));
|
||||
* // string(30) "Every day at 12:10,15,20,25,30"
|
||||
*
|
||||
* var_dump($cron->getType());
|
||||
* // string(3) "day"
|
||||
*
|
||||
* var_dump($cron->getCronHours());
|
||||
* // string(2) "12"
|
||||
*
|
||||
* var_dump($cron->matchExact(new \DateTime('2012-07-01 13:25:10')));
|
||||
* // bool(false)
|
||||
*
|
||||
* var_dump($cron->matchExact(new \DateTime('2012-07-01 12:15:20')));
|
||||
* // bool(true)
|
||||
*
|
||||
* var_dump($cron->matchWithMargin(new \DateTime('2012-07-01 12:32:50'), -3, 5));
|
||||
* // bool(true)
|
||||
*/
|
||||
class Cron {
|
||||
const TYPE_UNDEFINED = '';
|
||||
const TYPE_MINUTE = 'minute';
|
||||
const TYPE_HOUR = 'hour';
|
||||
const TYPE_DAY = 'day';
|
||||
const TYPE_WEEK = 'week';
|
||||
const TYPE_MONTH = 'month';
|
||||
const TYPE_YEAR = 'year';
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $texts = array(
|
||||
'fr' => array(
|
||||
'empty' => '-tout-',
|
||||
'name_minute' => 'minute',
|
||||
'name_hour' => 'heure',
|
||||
'name_day' => 'jour',
|
||||
'name_week' => 'semaine',
|
||||
'name_month' => 'mois',
|
||||
'name_year' => 'année',
|
||||
'text_period' => 'Chaque %s',
|
||||
'text_mins' => 'à %s minutes',
|
||||
'text_time' => 'à %s:%s',
|
||||
'text_dow' => 'le %s',
|
||||
'text_month' => 'de %s',
|
||||
'text_dom' => 'le %s',
|
||||
'weekdays' => array('lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'),
|
||||
'months' => array('janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'),
|
||||
),
|
||||
'en' => array(
|
||||
'empty' => '-all-',
|
||||
'name_minute' => 'minute',
|
||||
'name_hour' => 'hour',
|
||||
'name_day' => 'day',
|
||||
'name_week' => 'week',
|
||||
'name_month' => 'month',
|
||||
'name_year' => 'year',
|
||||
'text_period' => 'Every %s',
|
||||
'text_mins' => 'at %s minutes past the hour',
|
||||
'text_time' => 'at %s:%s',
|
||||
'text_dow' => 'on %s',
|
||||
'text_month' => 'of %s',
|
||||
'text_dom' => 'on the %s',
|
||||
'weekdays' => array('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'),
|
||||
'months' => array('january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'),
|
||||
),
|
||||
);
|
||||
/**
|
||||
* min hour dom month dow
|
||||
* @var string
|
||||
*/
|
||||
protected $cron = '';
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $minutes = array();
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hours = array();
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $months = array();
|
||||
/**
|
||||
* 0-7 : sunday, monday, ... saturday, sunday
|
||||
* @var array
|
||||
*/
|
||||
protected $dow = array();
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dom = array();
|
||||
/**
|
||||
*
|
||||
* @param string $cron
|
||||
*/
|
||||
public function __construct($cron = null) {
|
||||
if (!empty($cron)) {
|
||||
$this->setCron($cron);
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCron() {
|
||||
return implode(' ', array(
|
||||
$this->getCronMinutes(),
|
||||
$this->getCronHours(),
|
||||
$this->getCronDaysOfMonth(),
|
||||
$this->getCronMonths(),
|
||||
$this->getCronDaysOfWeek(),
|
||||
));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string $lang 'fr' or 'en'
|
||||
* @return string
|
||||
*/
|
||||
public function getText($lang) {
|
||||
// check lang
|
||||
if (!isset($this->texts[$lang])) {
|
||||
return $this->getCron();
|
||||
}
|
||||
$texts = $this->texts[$lang];
|
||||
// check type
|
||||
$type = $this->getType();
|
||||
if ($type == self::TYPE_UNDEFINED) {
|
||||
return $this->getCron();
|
||||
}
|
||||
// init
|
||||
$elements = array();
|
||||
$elements[] = sprintf($texts['text_period'], $texts['name_' . $type]);
|
||||
// hour
|
||||
if (in_array($type, array(self::TYPE_HOUR))) {
|
||||
$elements[] = sprintf($texts['text_mins'], $this->getCronMinutes());
|
||||
}
|
||||
// week
|
||||
if (in_array($type, array(self::TYPE_WEEK))) {
|
||||
$dow = $this->getCronDaysOfWeek();
|
||||
foreach ($texts['weekdays'] as $i => $wd) {
|
||||
$dow = str_replace((string) ($i + 1), $wd, $dow);
|
||||
}
|
||||
$elements[] = sprintf($texts['text_dow'], $dow);
|
||||
}
|
||||
// month + year
|
||||
if (in_array($type, array(self::TYPE_MONTH, self::TYPE_YEAR))) {
|
||||
$elements[] = sprintf($texts['text_dom'], $this->getCronDaysOfMonth());
|
||||
}
|
||||
// year
|
||||
if (in_array($type, array(self::TYPE_YEAR))) {
|
||||
$months = $this->getCronMonths();
|
||||
for ($i = count($texts['months']) - 1; $i >= 0; $i--) {
|
||||
$months = str_replace((string) ($i + 1), $texts['months'][$i], $months);
|
||||
}
|
||||
$elements[] = sprintf($texts['text_month'], $months);
|
||||
}
|
||||
// day + week + month + year
|
||||
if (in_array($type, array(self::TYPE_DAY, self::TYPE_WEEK, self::TYPE_MONTH, self::TYPE_YEAR))) {
|
||||
$elements[] = sprintf($texts['text_time'], $this->getCronHours(), $this->getCronMinutes());
|
||||
}
|
||||
return str_replace('*', $texts['empty'], implode(' ', $elements));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType() {
|
||||
$mask = preg_replace('/[^\* ]/si', '-', $this->getCron());
|
||||
$mask = preg_replace('/-+/si', '-', $mask);
|
||||
$mask = preg_replace('/[^-\*]/si', '', $mask);
|
||||
if ($mask == '*****') {
|
||||
return self::TYPE_MINUTE;
|
||||
}
|
||||
elseif ($mask == '-****') {
|
||||
return self::TYPE_HOUR;
|
||||
}
|
||||
elseif (substr($mask, -3) == '***') {
|
||||
return self::TYPE_DAY;
|
||||
}
|
||||
elseif (substr($mask, -3) == '-**') {
|
||||
return self::TYPE_MONTH;
|
||||
}
|
||||
elseif (substr($mask, -3) == '**-') {
|
||||
return self::TYPE_WEEK;
|
||||
}
|
||||
elseif (substr($mask, -2) == '-*') {
|
||||
return self::TYPE_YEAR;
|
||||
}
|
||||
return self::TYPE_UNDEFINED;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string $cron
|
||||
* @return Cron
|
||||
*/
|
||||
public function setCron($cron) {
|
||||
// sanitize
|
||||
$cron = trim($cron);
|
||||
$cron = preg_replace('/\s+/', ' ', $cron);
|
||||
// explode
|
||||
$elements = explode(' ', $cron);
|
||||
if (count($elements) != 5) {
|
||||
throw new Exception('Bad number of elements');
|
||||
}
|
||||
$this->cron = $cron;
|
||||
$this->setMinutes($elements[0]);
|
||||
$this->setHours($elements[1]);
|
||||
$this->setDaysOfMonth($elements[2]);
|
||||
$this->setMonths($elements[3]);
|
||||
$this->setDaysOfWeek($elements[4]);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCronMinutes() {
|
||||
return $this->arrayToCron($this->minutes);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCronHours() {
|
||||
return $this->arrayToCron($this->hours);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCronDaysOfMonth() {
|
||||
return $this->arrayToCron($this->dom);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCronMonths() {
|
||||
return $this->arrayToCron($this->months);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCronDaysOfWeek() {
|
||||
return $this->arrayToCron($this->dow);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMinutes() {
|
||||
return $this->minutes;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHours() {
|
||||
return $this->hours;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDaysOfMonth() {
|
||||
return $this->dom;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMonths() {
|
||||
return $this->months;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDaysOfWeek() {
|
||||
return $this->dow;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string|array $minutes
|
||||
* @return Cron
|
||||
*/
|
||||
public function setMinutes($minutes) {
|
||||
$this->minutes = $this->cronToArray($minutes, 0, 59);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string|array $hours
|
||||
* @return Cron
|
||||
*/
|
||||
public function setHours($hours) {
|
||||
$this->hours = $this->cronToArray($hours, 0, 23);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string|array $months
|
||||
* @return Cron
|
||||
*/
|
||||
public function setMonths($months) {
|
||||
$this->months = $this->cronToArray($months, 1, 12);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string|array $dow
|
||||
* @return Cron
|
||||
*/
|
||||
public function setDaysOfWeek($dow) {
|
||||
$this->dow = $this->cronToArray($dow, 0, 7);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string|array $dom
|
||||
* @return Cron
|
||||
*/
|
||||
public function setDaysOfMonth($dom) {
|
||||
$this->dom = $this->cronToArray($dom, 1, 31);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param mixed $date
|
||||
* @param int $min
|
||||
* @param int $hour
|
||||
* @param int $day
|
||||
* @param int $month
|
||||
* @param int $weekday
|
||||
* @return DateTime
|
||||
*/
|
||||
protected function parseDate($date, &$min, &$hour, &$day, &$month, &$weekday) {
|
||||
if (is_numeric($date) && intval($date) == $date) {
|
||||
$date = new \DateTime('@' . $date);
|
||||
}
|
||||
elseif (is_string($date)) {
|
||||
$date = new \DateTime('@' . strtotime($date));
|
||||
}
|
||||
if ($date instanceof \DateTime) {
|
||||
$min = intval($date->format('i'));
|
||||
$hour = intval($date->format('H'));
|
||||
$day = intval($date->format('d'));
|
||||
$month = intval($date->format('m'));
|
||||
$weekday = intval($date->format('w')); // 0-6
|
||||
}
|
||||
else {
|
||||
throw new Exception('Date format not supported');
|
||||
}
|
||||
return new \DateTime($date->format('Y-m-d H:i:sP'));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param int|string|\Datetime $date
|
||||
*/
|
||||
public function matchExact($date) {
|
||||
$date = $this->parseDate($date, $min, $hour, $day, $month, $weekday);
|
||||
return
|
||||
(empty($this->minutes) || in_array($min, $this->minutes)) &&
|
||||
(empty($this->hours) || in_array($hour, $this->hours)) &&
|
||||
(empty($this->dom) || in_array($day, $this->dom)) &&
|
||||
(empty($this->months) || in_array($month, $this->months)) &&
|
||||
(empty($this->dow) || in_array($weekday, $this->dow) || ($weekday == 0 && in_array(7, $this->dow)) || ($weekday == 7 && in_array(0, $this->dow))
|
||||
);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param int|string|\Datetime $date
|
||||
* @param int $minuteBefore
|
||||
* @param int $minuteAfter
|
||||
*/
|
||||
public function matchWithMargin($date, $minuteBefore = 0, $minuteAfter = 0) {
|
||||
if ($minuteBefore > 0) {
|
||||
throw new Exception('MinuteBefore parameter cannot be positive !');
|
||||
}
|
||||
if ($minuteAfter < 0) {
|
||||
throw new Exception('MinuteAfter parameter cannot be negative !');
|
||||
}
|
||||
$date = $this->parseDate($date, $min, $hour, $day, $month, $weekday);
|
||||
$interval = new \DateInterval('PT1M'); // 1 min
|
||||
if ($minuteBefore != 0) {
|
||||
$date->sub(new \DateInterval('PT' . abs($minuteBefore) . 'M'));
|
||||
}
|
||||
$n = $minuteAfter - $minuteBefore + 1;
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
if ($this->matchExact($date)) {
|
||||
return true;
|
||||
}
|
||||
$date->add($interval);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param array $array
|
||||
* @return string
|
||||
*/
|
||||
protected function arrayToCron($array) {
|
||||
$n = count($array);
|
||||
if (!is_array($array) || $n == 0) {
|
||||
return '*';
|
||||
}
|
||||
$cron = array($array[0]);
|
||||
$s = $c = $array[0];
|
||||
for ($i = 1; $i < $n; $i++) {
|
||||
if ($array[$i] == $c + 1) {
|
||||
$c = $array[$i];
|
||||
$cron[count($cron) - 1] = $s . '-' . $c;
|
||||
}
|
||||
else {
|
||||
$s = $c = $array[$i];
|
||||
$cron[] = $c;
|
||||
}
|
||||
}
|
||||
return implode(',', $cron);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $min
|
||||
* @param int $max
|
||||
* @return array
|
||||
*/
|
||||
protected function cronToArray($string, $min, $max) {
|
||||
$array = array();
|
||||
if (is_array($string)) {
|
||||
foreach ($string as $val) {
|
||||
if (is_numeric($val) && intval($val) == $val && $val >= $min && $val <= $max) {
|
||||
$array[] = intval($val);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($string !== '*') {
|
||||
while ($string != '') {
|
||||
// test "*/n" expression
|
||||
if (preg_match('/^\*\/([0-9]+),?/', $string, $m)) {
|
||||
for ($i = max(0, $min); $i <= min(59, $max); $i+=$m[1]) {
|
||||
$array[] = intval($i);
|
||||
}
|
||||
$string = substr($string, strlen($m[0]));
|
||||
continue;
|
||||
}
|
||||
// test "a-b/n" expression
|
||||
if (preg_match('/^([0-9]+)-([0-9]+)\/([0-9]+),?/', $string, $m)) {
|
||||
for ($i = max($m[1], $min); $i <= min($m[2], $max); $i+=$m[3]) {
|
||||
$array[] = intval($i);
|
||||
}
|
||||
$string = substr($string, strlen($m[0]));
|
||||
continue;
|
||||
}
|
||||
// test "a-b" expression
|
||||
if (preg_match('/^([0-9]+)-([0-9]+),?/', $string, $m)) {
|
||||
for ($i = max($m[1], $min); $i <= min($m[2], $max); $i++) {
|
||||
$array[] = intval($i);
|
||||
}
|
||||
$string = substr($string, strlen($m[0]));
|
||||
continue;
|
||||
}
|
||||
// test "c" expression
|
||||
if (preg_match('/^([0-9]+),?/', $string, $m)) {
|
||||
if ($m[1] >= $min && $m[1] <= $max) {
|
||||
$array[] = intval($m[1]);
|
||||
}
|
||||
$string = substr($string, strlen($m[0]));
|
||||
continue;
|
||||
}
|
||||
// something goes wrong in the expression
|
||||
return array();
|
||||
}
|
||||
}
|
||||
sort($array);
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
365
system/src/Grav/Common/Scheduler/IntervalTrait.php
Normal file
365
system/src/Grav/Common/Scheduler/IntervalTrait.php
Normal file
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Scheduler
|
||||
* @author Originally based on peppeocchi/php-cron-scheduler modified for Grav integration
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Scheduler;
|
||||
|
||||
use Cron\CronExpression;
|
||||
|
||||
trait IntervalTrait
|
||||
{
|
||||
/**
|
||||
* Set the Job execution time.
|
||||
*compo
|
||||
* @param string $expression
|
||||
* @return self
|
||||
*/
|
||||
public function at($expression)
|
||||
{
|
||||
$this->at = $expression;
|
||||
$this->executionTime = CronExpression::factory($expression);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every minute.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function everyMinute()
|
||||
{
|
||||
return $this->at('* * * * *');
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every hour.
|
||||
*
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function hourly($minute = 0)
|
||||
{
|
||||
$c = $this->validateCronSequence($minute);
|
||||
return $this->at("{$c['minute']} * * * *");
|
||||
}
|
||||
/**
|
||||
* Set the execution time to once a day.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function daily($hour = 0, $minute = 0)
|
||||
{
|
||||
if (is_string($hour)) {
|
||||
$parts = explode(':', $hour);
|
||||
$hour = $parts[0];
|
||||
$minute = isset($parts[1]) ? $parts[1] : '0';
|
||||
}
|
||||
$c = $this->validateCronSequence($minute, $hour);
|
||||
return $this->at("{$c['minute']} {$c['hour']} * * *");
|
||||
}
|
||||
/**
|
||||
* Set the execution time to once a week.
|
||||
*
|
||||
* @param int|string $weekday
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function weekly($weekday = 0, $hour = 0, $minute = 0)
|
||||
{
|
||||
if (is_string($hour)) {
|
||||
$parts = explode(':', $hour);
|
||||
$hour = $parts[0];
|
||||
$minute = isset($parts[1]) ? $parts[1] : '0';
|
||||
}
|
||||
$c = $this->validateCronSequence($minute, $hour, null, null, $weekday);
|
||||
return $this->at("{$c['minute']} {$c['hour']} * * {$c['weekday']}");
|
||||
}
|
||||
/**
|
||||
* Set the execution time to once a month.
|
||||
*
|
||||
* @param int|string $month
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function monthly($month = '*', $day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
if (is_string($hour)) {
|
||||
$parts = explode(':', $hour);
|
||||
$hour = $parts[0];
|
||||
$minute = isset($parts[1]) ? $parts[1] : '0';
|
||||
}
|
||||
$c = $this->validateCronSequence($minute, $hour, $day, $month);
|
||||
return $this->at("{$c['minute']} {$c['hour']} {$c['day']} {$c['month']} *");
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every Sunday.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function sunday($hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->weekly(0, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every Monday.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function monday($hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->weekly(1, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every Tuesday.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function tuesday($hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->weekly(2, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every Wednesday.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function wednesday($hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->weekly(3, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every Thursday.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function thursday($hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->weekly(4, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every Friday.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function friday($hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->weekly(5, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every Saturday.
|
||||
*
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function saturday($hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->weekly(6, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every January.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function january($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(1, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every February.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function february($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(2, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every March.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function march($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(3, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every April.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function april($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(4, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every May.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function may($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(5, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every June.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function june($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(6, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every July.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function july($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(7, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every August.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function august($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(8, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every September.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function september($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(9, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every October.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function october($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(10, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every November.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function november($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(11, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Set the execution time to every December.
|
||||
*
|
||||
* @param int|string $day
|
||||
* @param int|string $hour
|
||||
* @param int|string $minute
|
||||
* @return self
|
||||
*/
|
||||
public function december($day = 1, $hour = 0, $minute = 0)
|
||||
{
|
||||
return $this->monthly(12, $day, $hour, $minute);
|
||||
}
|
||||
/**
|
||||
* Validate sequence of cron expression.
|
||||
*
|
||||
* @param int|string $minute
|
||||
* @param int|string $hour
|
||||
* @param int|string $day
|
||||
* @param int|string $month
|
||||
* @param int|string $weekday
|
||||
* @return array
|
||||
*/
|
||||
private function validateCronSequence($minute = null, $hour = null, $day = null, $month = null, $weekday = null)
|
||||
{
|
||||
return [
|
||||
'minute' => $this->validateCronRange($minute, 0, 59),
|
||||
'hour' => $this->validateCronRange($hour, 0, 23),
|
||||
'day' => $this->validateCronRange($day, 1, 31),
|
||||
'month' => $this->validateCronRange($month, 1, 12),
|
||||
'weekday' => $this->validateCronRange($weekday, 0, 6),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Validate sequence of cron expression.
|
||||
*
|
||||
* @param int|string $value
|
||||
* @param int $min
|
||||
* @param int $max
|
||||
* @return mixed
|
||||
*/
|
||||
private function validateCronRange($value, $min, $max)
|
||||
{
|
||||
if ($value === null || $value === '*') {
|
||||
return '*';
|
||||
}
|
||||
if (! is_numeric($value) ||
|
||||
! ($value >= $min && $value <= $max)
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
"Invalid value: it should be '*' or between {$min} and {$max}."
|
||||
);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
491
system/src/Grav/Common/Scheduler/Job.php
Normal file
491
system/src/Grav/Common/Scheduler/Job.php
Normal file
@@ -0,0 +1,491 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Scheduler
|
||||
* @author Originally based on peppeocchi/php-cron-scheduler modified for Grav integration
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Scheduler;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Grav\Common\Grav;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class Job
|
||||
{
|
||||
use IntervalTrait;
|
||||
|
||||
private $id;
|
||||
private $enabled = true;
|
||||
private $command;
|
||||
private $at;
|
||||
private $args = [];
|
||||
private $runInBackground = true;
|
||||
private $creationTime;
|
||||
private $executionTime;
|
||||
private $tempDir;
|
||||
private $lockFile;
|
||||
private $truthTest = true;
|
||||
private $output;
|
||||
private $returnCode = 0;
|
||||
private $outputTo = [];
|
||||
private $emailTo = [];
|
||||
private $emailConfig = [];
|
||||
private $before;
|
||||
private $after;
|
||||
private $whenOverlapping;
|
||||
private $outputMode;
|
||||
private $process;
|
||||
private $successful = false;
|
||||
|
||||
/**
|
||||
* Create a new Job instance.
|
||||
*
|
||||
* @param string|callable $command
|
||||
* @param array $args
|
||||
* @param string $id
|
||||
*/
|
||||
public function __construct($command, $args = [], $id = null)
|
||||
{
|
||||
if (is_string($id)) {
|
||||
$this->id = Grav::instance()['inflector']->hyphenize($id);
|
||||
} else {
|
||||
if (is_string($command)) {
|
||||
$this->id = md5($command);
|
||||
} else {
|
||||
/* @var object $command */
|
||||
$this->id = spl_object_hash($command);
|
||||
}
|
||||
}
|
||||
$this->creationTime = new \DateTime('now');
|
||||
// initialize the directory path for lock files
|
||||
$this->tempDir = sys_get_temp_dir();
|
||||
$this->command = $command;
|
||||
$this->args = $args;
|
||||
// Set enabled state
|
||||
$status = Grav::instance()['config']->get('scheduler.status');
|
||||
$this->enabled = isset($status[$id]) && $status[$id] === 'disabled' ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cron 'at' syntax for this job
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAt()
|
||||
{
|
||||
return $this->at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of this job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get optional arguments
|
||||
*
|
||||
* @return array|string|void
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
if (is_string($this->args)) {
|
||||
return $this->args;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public function getCronExpression()
|
||||
{
|
||||
return CronExpression::factory($this->at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of the last run for this job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSuccessful()
|
||||
{
|
||||
return $this->successful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Job id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Job is due to run.
|
||||
* It accepts as input a DateTime used to check if
|
||||
* the job is due. Defaults to job creation time.
|
||||
* It also default the execution time if not previously defined.
|
||||
*
|
||||
* @param DateTime $date
|
||||
* @return bool
|
||||
*/
|
||||
public function isDue(\DateTime $date = null)
|
||||
{
|
||||
// The execution time is being defaulted if not defined
|
||||
if (!$this->executionTime) {
|
||||
$this->at('* * * * *');
|
||||
}
|
||||
$date = $date !== null ? $date : $this->creationTime;
|
||||
return $this->executionTime->isDue($date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Job is overlapping.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOverlapping()
|
||||
{
|
||||
return $this->lockFile &&
|
||||
file_exists($this->lockFile) &&
|
||||
call_user_func($this->whenOverlapping, filemtime($this->lockFile)) === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the Job to run in foreground.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function inForeground()
|
||||
{
|
||||
$this->runInBackground = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Job can run in background.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function runInBackground()
|
||||
{
|
||||
if (is_callable($this->command) || $this->runInBackground === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will prevent the Job from overlapping.
|
||||
* It prevents another instance of the same Job of
|
||||
* being executed if the previous is still running.
|
||||
* The job id is used as a filename for the lock file.
|
||||
*
|
||||
* @param string $tempDir The directory path for the lock files
|
||||
* @param callable $whenOverlapping A callback to ignore job overlapping
|
||||
* @return self
|
||||
*/
|
||||
public function onlyOne($tempDir = null, callable $whenOverlapping = null)
|
||||
{
|
||||
if ($tempDir === null || !is_dir($tempDir)) {
|
||||
$tempDir = $this->tempDir;
|
||||
}
|
||||
$this->lockFile = implode('/', [
|
||||
trim($tempDir),
|
||||
trim($this->id) . '.lock',
|
||||
]);
|
||||
if ($whenOverlapping) {
|
||||
$this->whenOverlapping = $whenOverlapping;
|
||||
} else {
|
||||
$this->whenOverlapping = function () {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the job.
|
||||
*
|
||||
* @param array $config
|
||||
* @return self
|
||||
*/
|
||||
public function configure(array $config = [])
|
||||
{
|
||||
// Check if config has defined a tempDir
|
||||
if (isset($config['tempDir']) && is_dir($config['tempDir'])) {
|
||||
$this->tempDir = $config['tempDir'];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truth test to define if the job should run if due.
|
||||
*
|
||||
* @param callable $fn
|
||||
* @return self
|
||||
*/
|
||||
public function when(callable $fn)
|
||||
{
|
||||
$this->truthTest = $fn();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the job.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// If the truthTest failed, don't run
|
||||
if ($this->truthTest !== true) {
|
||||
return false;
|
||||
}
|
||||
// If overlapping, don't run
|
||||
if ($this->isOverlapping()) {
|
||||
return false;
|
||||
}
|
||||
// Write lock file if necessary
|
||||
$this->createLockFile();
|
||||
|
||||
// Call before if required
|
||||
if (is_callable($this->before)) {
|
||||
call_user_func($this->before);
|
||||
}
|
||||
// If command is callable...
|
||||
if (is_callable($this->command)) {
|
||||
$this->output = $this->exec();
|
||||
} else {
|
||||
/** @var Process process */
|
||||
$args = is_string($this->args) ? $this->args : implode(' ', $this->args);
|
||||
$command = $this->command . ' ' . $args;
|
||||
$process = new Process($command);
|
||||
|
||||
$this->process = $process;
|
||||
|
||||
if ($this->runInBackground()) {
|
||||
$process->start();
|
||||
} else {
|
||||
$process->run();
|
||||
$this->finalize();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish up processing the job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function finalize()
|
||||
{
|
||||
/** @var Process $process */
|
||||
$process = $this->process;
|
||||
|
||||
if ($process) {
|
||||
$process->wait();
|
||||
|
||||
if ($process->isSuccessful()) {
|
||||
$this->successful = true;
|
||||
$this->output = $process->getOutput();
|
||||
} else {
|
||||
$this->successful = false;
|
||||
$this->output = $process->getErrorOutput();
|
||||
}
|
||||
|
||||
$this->postRun();
|
||||
|
||||
unset($this->process);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Things to run after job has run
|
||||
*/
|
||||
private function postRun()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Send output to email
|
||||
$this->emailOutput();
|
||||
|
||||
// Call any callback defined
|
||||
if (is_callable($this->after)) {
|
||||
call_user_func($this->after, $this->output, $this->returnCode);
|
||||
}
|
||||
|
||||
$this->removeLockFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the job lock file.
|
||||
*
|
||||
* @param mixed $content
|
||||
* @return void
|
||||
*/
|
||||
private function createLockFile($content = null)
|
||||
{
|
||||
if ($this->lockFile) {
|
||||
if ($content === null || !is_string($content)) {
|
||||
$content = $this->getId();
|
||||
}
|
||||
file_put_contents($this->lockFile, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the job lock file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function removeLockFile()
|
||||
{
|
||||
if ($this->lockFile && file_exists($this->lockFile)) {
|
||||
unlink($this->lockFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callable job.
|
||||
*
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
private function exec()
|
||||
{
|
||||
$return_data = '';
|
||||
ob_start();
|
||||
try {
|
||||
$return_data = call_user_func_array($this->command, $this->args);
|
||||
$this->successful = true;
|
||||
} catch (Exception $e) {
|
||||
$this->successful = false;
|
||||
}
|
||||
$this->output = ob_get_clean() . (is_string($return_data) ? $return_data : '');
|
||||
|
||||
$this->postRun();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file/s where to write the output of the job.
|
||||
*
|
||||
* @param string|array $filename
|
||||
* @param bool $append
|
||||
* @return self
|
||||
*/
|
||||
public function output($filename, $append = false)
|
||||
{
|
||||
$this->outputTo = is_array($filename) ? $filename : [$filename];
|
||||
$this->outputMode = $append === false ? 'overwrite' : 'append';
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job output.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the emails where the output should be sent to.
|
||||
* The Job should be set to write output to a file
|
||||
* for this to work.
|
||||
*
|
||||
* @param string|array $email
|
||||
* @return self
|
||||
*/
|
||||
public function email($email)
|
||||
{
|
||||
if (!is_string($email) && !is_array($email)) {
|
||||
throw new InvalidArgumentException('The email can be only string or array');
|
||||
}
|
||||
$this->emailTo = is_array($email) ? $email : [$email];
|
||||
// Force the job to run in foreground
|
||||
$this->inForeground();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email the output of the job, if any.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function emailOutput()
|
||||
{
|
||||
if (!count($this->outputTo) || !count($this->emailTo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_callable('Grav\Plugin\Email\Utils::sendEmail')) {
|
||||
$subject ='Grav Scheduled Job [' . $this->getId() . ']';
|
||||
$content = "<h1>Output from Job ID: {$this->getId()}</h1>\n<h4>Command: {$this->getCommand()}</h4><br /><pre style=\"font-size: 12px; font-family: Monaco, Consolas, monospace\">\n".$this->getOutput()."\n</pre>";
|
||||
$to = $this->emailTo;
|
||||
|
||||
\Grav\Plugin\Email\Utils::sendEmail($subject, $content, $to);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set function to be called before job execution
|
||||
* Job object is injected as a parameter to callable function.
|
||||
*
|
||||
* @param callable $fn
|
||||
* @return self
|
||||
*/
|
||||
public function before(callable $fn)
|
||||
{
|
||||
$this->before = $fn;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a function to be called after job execution.
|
||||
* By default this will force the job to run in foreground
|
||||
* because the output is injected as a parameter of this
|
||||
* function, but it could be avoided by passing true as a
|
||||
* second parameter. The job will run in background if it
|
||||
* meets all the other criteria.
|
||||
*
|
||||
* @param callable $fn
|
||||
* @param bool $runInBackground
|
||||
* @return self
|
||||
*/
|
||||
public function then(callable $fn, $runInBackground = false)
|
||||
{
|
||||
$this->after = $fn;
|
||||
// Force the job to run in foreground
|
||||
if ($runInBackground === false) {
|
||||
$this->inForeground();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
364
system/src/Grav/Common/Scheduler/Scheduler.php
Normal file
364
system/src/Grav/Common/Scheduler/Scheduler.php
Normal file
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Scheduler
|
||||
* @author Originally based on peppeocchi/php-cron-scheduler modified for Grav integration
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Scheduler;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
class Scheduler
|
||||
{
|
||||
/**
|
||||
* The queued jobs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $jobs = [];
|
||||
private $saved_jobs = [];
|
||||
private $executed_jobs = [];
|
||||
private $failed_jobs = [];
|
||||
private $jobs_run = [];
|
||||
private $output_schedule = [];
|
||||
private $config;
|
||||
private $status_path;
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = Grav::instance()['config']->get('scheduler.defaults', []);
|
||||
$this->config = $config;
|
||||
|
||||
$this->status_path = Grav::instance()['locator']->findResource('user://data/scheduler', true, true);
|
||||
if (!file_exists($this->status_path)) {
|
||||
Folder::create($this->status_path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load saved jobs from config/scheduler.yaml file
|
||||
*/
|
||||
public function loadSavedJobs()
|
||||
{
|
||||
$this->saved_jobs = [];
|
||||
$saved_jobs = (array) Grav::instance()['config']->get('scheduler.custom_jobs', []);
|
||||
|
||||
foreach ($saved_jobs as $id => $j) {
|
||||
$args = isset($j['args']) ? $j['args'] : [];
|
||||
$id = Grav::instance()['inflector']->hyphenize($id);
|
||||
$job = $this->addCommand($j['command'], $args, $id);
|
||||
|
||||
if (isset($j['at'])) {
|
||||
$job->at($j['at']);
|
||||
}
|
||||
|
||||
if (isset($j['output'])) {
|
||||
$mode = isset($j['output_mode']) && $j['output_mode'] === 'append' ? true : false;
|
||||
$job->output($j['output'], $mode);
|
||||
}
|
||||
|
||||
if (isset($j['email'])) {
|
||||
$job->email($j['email']);
|
||||
}
|
||||
|
||||
// store in saved_jobs
|
||||
$this->saved_jobs[] = $job;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queued jobs as background/foreground
|
||||
*
|
||||
* @param bool $all
|
||||
* @return array
|
||||
*/
|
||||
public function getQueuedJobs($all = false)
|
||||
{
|
||||
$background = [];
|
||||
$foreground = [];
|
||||
foreach ($this->jobs as $job) {
|
||||
if ($all || $job->getEnabled()) {
|
||||
if ($job->runInBackground()) {
|
||||
$background[] = $job;
|
||||
} else {
|
||||
$foreground[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return [$background, $foreground];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all jobs if they are disabled or not as one array
|
||||
*
|
||||
* @param bool $all
|
||||
* @return array
|
||||
*/
|
||||
public function getAllJobs()
|
||||
{
|
||||
list($background, $foreground) = $this->loadSavedJobs()->getQueuedJobs(true);
|
||||
return array_merge($background, $foreground);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a PHP function execution.
|
||||
*
|
||||
* @param callable $fn The function to execute
|
||||
* @param array $args Optional arguments to pass to the php script
|
||||
* @param string $id Optional custom identifier
|
||||
* @return Job
|
||||
*/
|
||||
public function addFunction(callable $fn, $args = [], $id = null)
|
||||
{
|
||||
$job = new Job($fn, $args, $id);
|
||||
$this->queueJob($job->configure($this->config));
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a raw shell command.
|
||||
*
|
||||
* @param string $command The command to execute
|
||||
* @param array $args Optional arguments to pass to the command
|
||||
* @param string $id Optional custom identifier
|
||||
* @return Job
|
||||
*/
|
||||
public function addCommand($command, $args = [], $id = null)
|
||||
{
|
||||
$job = new Job($command, $args, $id);
|
||||
$this->queueJob($job->configure($this->config));
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the scheduler.
|
||||
*
|
||||
* @param \DateTime $runTime Optional, run at specific moment
|
||||
* @return array Executed jobs
|
||||
*/
|
||||
public function run(\Datetime $runTime = null)
|
||||
{
|
||||
$this->loadSavedJobs();
|
||||
|
||||
list($background, $foreground) = $this->getQueuedJobs(false);
|
||||
$alljobs = array_merge($background, $foreground);
|
||||
|
||||
if (is_null($runTime)) {
|
||||
$runTime = new \DateTime('now');
|
||||
}
|
||||
|
||||
// Star processing jobs
|
||||
foreach ($alljobs as $job) {
|
||||
if ($job->isDue($runTime)) {
|
||||
$job->run();
|
||||
$this->jobs_run[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish handling any background jobs
|
||||
foreach($background as $job) {
|
||||
$job->finalize();
|
||||
}
|
||||
|
||||
// Store states
|
||||
$this->saveJobStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all collected data of last run.
|
||||
*
|
||||
* Call before run() if you call run() multiple times.
|
||||
*/
|
||||
public function resetRun()
|
||||
{
|
||||
// Reset collected data of last run
|
||||
$this->executed_jobs = [];
|
||||
$this->failed_jobs = [];
|
||||
$this->output_schedule = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheduler verbose output.
|
||||
*
|
||||
* @param string $type Allowed: text, html, array
|
||||
* @return mixed The return depends on the requested $type
|
||||
*/
|
||||
public function getVerboseOutput($type = 'text')
|
||||
{
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
return implode("\n", $this->output_schedule);
|
||||
case 'html':
|
||||
return implode('<br>', $this->output_schedule);
|
||||
case 'array':
|
||||
return $this->output_schedule;
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid output type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all queued Jobs.
|
||||
*/
|
||||
public function clearJobs()
|
||||
{
|
||||
$this->jobs = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the full Cron command
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCronCommand()
|
||||
{
|
||||
$phpBinaryFinder = new PhpExecutableFinder();
|
||||
$php = $phpBinaryFinder->find();
|
||||
$command = 'cd ' . GRAV_ROOT . ';' . $php . ' bin/grav scheduler';
|
||||
|
||||
return "(crontab -l; echo \"* * * * * {$command} 1>> /dev/null 2>&1\") | crontab -";
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine if cron job is setup
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function isCrontabSetup()
|
||||
{
|
||||
$process = new Process('crontab -l');
|
||||
$process->run();
|
||||
|
||||
if ($process->isSuccessful()) {
|
||||
$output = $process->getOutput();
|
||||
|
||||
if (preg_match('$bin\/grav schedule$', $output)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$error = $process->getErrorOutput();
|
||||
if (Utils::startsWith($error, 'crontab: no crontab')) {
|
||||
return 0;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Job states file
|
||||
*
|
||||
* @return \RocketTheme\Toolbox\File\FileInterface|YamlFile
|
||||
*/
|
||||
public function getJobStates()
|
||||
{
|
||||
$file = YamlFile::instance($this->status_path . '/status.yaml');
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save job states to statys file
|
||||
*/
|
||||
private function saveJobStates()
|
||||
{
|
||||
$now = time();
|
||||
$new_states = [];
|
||||
|
||||
foreach ($this->jobs_run as $job) {
|
||||
if ($job->isSuccessful()) {
|
||||
$new_states[$job->getId()] = ['state' => 'success', 'last-run' => $now];
|
||||
$this->pushExecutedJob($job);
|
||||
} else {
|
||||
$new_states[$job->getId()] = ['state' => 'failure', 'last-run' => $now, 'error' => $job->getOutput()];
|
||||
$this->pushFailedJob($job);
|
||||
}
|
||||
}
|
||||
$saved_states = $this->getJobStates();
|
||||
$saved_states->save(array_merge($saved_states->content(), $new_states));
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a job for execution in the correct queue.
|
||||
*
|
||||
* @param Job $job
|
||||
* @return void
|
||||
*/
|
||||
private function queueJob(Job $job)
|
||||
{
|
||||
$this->jobs[] = $job;
|
||||
|
||||
// Store jobs
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to the scheduler verbose output array.
|
||||
*
|
||||
* @param string $string
|
||||
* @return void
|
||||
*/
|
||||
private function addSchedulerVerboseOutput($string)
|
||||
{
|
||||
$now = '[' . (new \DateTime('now'))->format('c') . '] ';
|
||||
$this->output_schedule[] = $now . $string;
|
||||
// Print to stdoutput in light gray
|
||||
// echo "\033[37m{$string}\033[0m\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a succesfully executed job.
|
||||
*
|
||||
* @param Job $job
|
||||
* @return Job
|
||||
*/
|
||||
private function pushExecutedJob(Job $job)
|
||||
{
|
||||
$this->executed_jobs[] = $job;
|
||||
$command = $job->getCommand();
|
||||
$args = $job->getArguments();
|
||||
// If callable, log the string Closure
|
||||
if (is_callable($command)) {
|
||||
$command = is_string($command) ? $command : 'Closure';
|
||||
}
|
||||
$this->addSchedulerVerboseOutput("<green>Success</green>: <white>{$command} {$args}</white>");
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a failed job.
|
||||
*
|
||||
* @param Job $job
|
||||
* @return Job
|
||||
*/
|
||||
private function pushFailedJob(Job $job)
|
||||
{
|
||||
$this->failed_jobs[] = $job;
|
||||
$command = $job->getCommand();
|
||||
// If callable, log the string Closure
|
||||
if (is_callable($command)) {
|
||||
$command = is_string($command) ? $command : 'Closure';
|
||||
}
|
||||
$output = trim($job->getOutput());
|
||||
$this->addSchedulerVerboseOutput("<red>Error</red>: <white>{$command}</white> → <normal>{$output}</normal>");
|
||||
return $job;
|
||||
}
|
||||
}
|
||||
163
system/src/Grav/Common/Security.php
Normal file
163
system/src/Grav/Common/Security.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
class Security
|
||||
{
|
||||
|
||||
public static function detectXssFromPages($pages, $route = true, callable $status = null)
|
||||
{
|
||||
$routes = $pages->routes();
|
||||
|
||||
// Remove duplicate for homepage
|
||||
unset($routes['/']);
|
||||
|
||||
$list = [];
|
||||
|
||||
// This needs Symfony 4.1 to work
|
||||
$status && $status([
|
||||
'type' => 'count',
|
||||
'steps' => count($routes),
|
||||
]);
|
||||
|
||||
foreach ($routes as $path) {
|
||||
|
||||
$status && $status([
|
||||
'type' => 'progress',
|
||||
]);
|
||||
|
||||
try {
|
||||
$page = $pages->get($path);
|
||||
|
||||
// call the content to load/cache it
|
||||
$header = (array) $page->header();
|
||||
$content = $page->value('content');
|
||||
|
||||
$data = ['header' => $header, 'content' => $content];
|
||||
$results = Security::detectXssFromArray($data);
|
||||
|
||||
if (!empty($results)) {
|
||||
if ($route) {
|
||||
$list[$page->route()] = $results;
|
||||
} else {
|
||||
$list[$page->filePathClean()] = $results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array Array such as $_POST or $_GET
|
||||
* @param string $prefix Prefix for returned values.
|
||||
* @return array Returns flatten list of potentially dangerous input values, such as 'data.content'.
|
||||
*/
|
||||
public static function detectXssFromArray(array $array, $prefix = '')
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
$list[] = static::detectXssFromArray($value, $prefix . $key . '.');
|
||||
}
|
||||
if ($result = static::detectXss($value)) {
|
||||
$list[] = [$prefix . $key => $result];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($list)) {
|
||||
return array_merge(...$list);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if string potentially has a XSS attack. This simple function does not catch all XSS and it is likely to
|
||||
* return false positives because of it tags all potentially dangerous HTML tags and attributes without looking into
|
||||
* their content.
|
||||
*
|
||||
* @param string $string The string to run XSS detection logic on
|
||||
* @return boolean|string Type of XSS vector if the given `$string` may contain XSS, false otherwise.
|
||||
*
|
||||
* Copies the code from: https://github.com/symphonycms/xssfilter/blob/master/extension.driver.php#L138
|
||||
*/
|
||||
public static function detectXss($string)
|
||||
{
|
||||
// Skip any null or non string values
|
||||
if (null === $string || !\is_string($string) || empty($string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep a copy of the original string before cleaning up
|
||||
$orig = $string;
|
||||
|
||||
// URL decode
|
||||
$string = urldecode($string);
|
||||
|
||||
// Convert Hexadecimals
|
||||
$string = (string)preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!u', function($m) {
|
||||
return \chr(hexdec($m[2]));
|
||||
}, $string);
|
||||
|
||||
// Clean up entities
|
||||
$string = preg_replace('!(�+[0-9]+)!u','$1;', $string);
|
||||
|
||||
// Decode entities
|
||||
$string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
|
||||
|
||||
// Strip whitespace characters
|
||||
$string = preg_replace('!\s!u','', $string);
|
||||
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$dangerous_tags = $config->get('security.xss_dangerous_tags');
|
||||
$dangerous_tags = array_map('preg_quote', array_map("trim", $dangerous_tags));
|
||||
|
||||
$enabled_rules = $config->get('security.xss_enabled');
|
||||
|
||||
// Set the patterns we'll test against
|
||||
$patterns = [
|
||||
// Match any attribute starting with "on" or xmlns
|
||||
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])(\son|\sxmlns)[a-z].*=>?#iUu',
|
||||
|
||||
// Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
|
||||
'invalid_protocols' => '#((java|live|vb)script|mocha|feed|data):.*?#iUu',
|
||||
|
||||
// Match -moz-bindings
|
||||
'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u',
|
||||
|
||||
// Match style attributes
|
||||
'html_inline_styles' => '#(<[^>]+[a-z\x00-\x20\"\'\/])(style=[^>]*(url\:|x\:expression).*)>?#iUu',
|
||||
|
||||
// Match potentially dangerous tags
|
||||
'dangerous_tags' => '#</*(' . implode('|', $dangerous_tags ) . ')[^>]*>?#ui'
|
||||
];
|
||||
|
||||
|
||||
// Iterate over rules and return label if fail
|
||||
foreach ((array) $patterns as $name => $regex) {
|
||||
if ($enabled_rules[$name] === true) {
|
||||
|
||||
if (preg_match($regex, $string) || preg_match($regex, $orig)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
26
system/src/Grav/Common/Service/BackupsServiceProvider.php
Normal file
26
system/src/Grav/Common/Service/BackupsServiceProvider.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Service
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Backup\Backups;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
|
||||
class BackupsServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['backups'] = function () {
|
||||
$backups = new Backups();
|
||||
$backups->setup();
|
||||
|
||||
return $backups;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use Grav\Common\Config\ConfigFileFinder;
|
||||
use Grav\Common\Config\Setup;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class ConfigServiceProvider implements ServiceProviderInterface
|
||||
@@ -31,7 +32,14 @@ class ConfigServiceProvider implements ServiceProviderInterface
|
||||
};
|
||||
|
||||
$container['config'] = function ($c) {
|
||||
return static::load($c);
|
||||
$config = static::load($c);
|
||||
|
||||
// After configuration has been loaded, we can disable YAML compatibility if strict mode has been enabled.
|
||||
if (!$config->get('system.strict_mode.yaml_compat', true)) {
|
||||
YamlFile::globalSettings(['compat' => false, 'native' => true]);
|
||||
}
|
||||
|
||||
return $config;
|
||||
};
|
||||
|
||||
$container['languages'] = function ($c) {
|
||||
@@ -65,6 +73,10 @@ class ConfigServiceProvider implements ServiceProviderInterface
|
||||
return $blueprints->name("master-{$setup->environment}")->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Container $container
|
||||
* @return Config
|
||||
*/
|
||||
public static function load(Container $container)
|
||||
{
|
||||
/** Setup $setup */
|
||||
|
||||
25
system/src/Grav/Common/Service/InflectorServiceProvider.php
Normal file
25
system/src/Grav/Common/Service/InflectorServiceProvider.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Service
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Inflector;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
|
||||
class InflectorServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['inflector'] = function () {
|
||||
$inflector = new Inflector();
|
||||
|
||||
return $inflector;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use \Monolog\Logger;
|
||||
use \Monolog\Handler\StreamHandler;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class LoggerServiceProvider implements ServiceProviderInterface
|
||||
@@ -25,7 +25,6 @@ class LoggerServiceProvider implements ServiceProviderInterface
|
||||
$locator = $c['locator'];
|
||||
|
||||
$log_file = $locator->findResource('log://grav.log', true, true);
|
||||
|
||||
$log->pushHandler(new StreamHandler($log_file, Logger::DEBUG));
|
||||
|
||||
return $log;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Page;
|
||||
@@ -26,35 +27,33 @@ class PageServiceProvider implements ServiceProviderInterface
|
||||
/** @var Pages $pages */
|
||||
$pages = $c['pages'];
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $c['config'];
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $c['uri'];
|
||||
|
||||
$path = $uri->path(); // Don't trim to support trailing slash default routes
|
||||
$path = $path ?: '/';
|
||||
|
||||
$path = $uri->path() ?: '/'; // Don't trim to support trailing slash default routes
|
||||
$page = $pages->dispatch($path);
|
||||
|
||||
// Redirection tests
|
||||
if ($page) {
|
||||
/** @var Language $language */
|
||||
$language = $c['language'];
|
||||
|
||||
// some debugger override logic
|
||||
if ($page->debugger() === false) {
|
||||
$c['debugger']->enabled(false);
|
||||
}
|
||||
|
||||
if ($c['config']->get('system.force_ssl')) {
|
||||
if (!isset($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on") {
|
||||
$url = "https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
|
||||
if ($config->get('system.force_ssl')) {
|
||||
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
|
||||
$url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
$c->redirect($url);
|
||||
}
|
||||
}
|
||||
|
||||
$url = $page->route();
|
||||
$url = $pages->route($page->route());
|
||||
|
||||
if ($uri->params()) {
|
||||
if ($url == '/') { //Avoid double slash
|
||||
if ($url === '/') { //Avoid double slash
|
||||
$url = $uri->params();
|
||||
} else {
|
||||
$url .= $uri->params();
|
||||
@@ -67,18 +66,16 @@ class PageServiceProvider implements ServiceProviderInterface
|
||||
$url .= '#' . $uri->fragment();
|
||||
}
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $c['language'];
|
||||
|
||||
// Language-specific redirection scenarios
|
||||
if ($language->enabled()) {
|
||||
if ($language->isLanguageInUrl() && !$language->isIncludeDefaultLanguage()) {
|
||||
$c->redirect($url);
|
||||
}
|
||||
if (!$language->isLanguageInUrl() && $language->isIncludeDefaultLanguage()) {
|
||||
$c->redirectLangSafe($url);
|
||||
}
|
||||
if ($language->enabled() && ($language->isLanguageInUrl() xor $language->isIncludeDefaultLanguage())) {
|
||||
$c->redirect($url);
|
||||
}
|
||||
// Default route test and redirect
|
||||
if ($c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) {
|
||||
$c->redirectLangSafe($url);
|
||||
if ($config->get('system.pages.redirect_default_route') && $page->route() !== $path) {
|
||||
$c->redirect($url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
system/src/Grav/Common/Service/SchedulerServiceProvider.php
Normal file
21
system/src/Grav/Common/Service/SchedulerServiceProvider.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Service
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Scheduler\Scheduler;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
|
||||
class SchedulerServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['scheduler'] = new Scheduler();
|
||||
}
|
||||
}
|
||||
@@ -29,21 +29,22 @@ class SessionServiceProvider implements ServiceProviderInterface
|
||||
/** @var Uri $uri */
|
||||
$uri = $c['uri'];
|
||||
|
||||
// Get session parameters.
|
||||
$session_timeout = (int)$config->get('system.session.timeout', 1800);
|
||||
$session_path = $config->get('system.session.path');
|
||||
if (null === $session_path) {
|
||||
$session_path = '/' . ltrim(Uri::filterPath($uri->rootUrl(false)), '/');
|
||||
}
|
||||
$domain = $uri->host();
|
||||
if ($domain === 'localhost') {
|
||||
$domain = '';
|
||||
}
|
||||
|
||||
// Get session options.
|
||||
$secure = (bool)$config->get('system.session.secure', false);
|
||||
$httponly = (bool)$config->get('system.session.httponly', true);
|
||||
$enabled = (bool)$config->get('system.session.enabled', false);
|
||||
$cookie_secure = (bool)$config->get('system.session.secure', false);
|
||||
$cookie_httponly = (bool)$config->get('system.session.httponly', true);
|
||||
$cookie_lifetime = (int)$config->get('system.session.timeout', 1800);
|
||||
$cookie_path = $config->get('system.session.path');
|
||||
if (null === $cookie_path) {
|
||||
$cookie_path = '/' . trim(Uri::filterPath($uri->rootUrl(false)), '/');
|
||||
}
|
||||
// Session cookie path requires trailing slash.
|
||||
$cookie_path = rtrim($cookie_path, '/') . '/';
|
||||
|
||||
$cookie_domain = $uri->host();
|
||||
if ($cookie_domain === 'localhost') {
|
||||
$cookie_domain = '';
|
||||
}
|
||||
|
||||
// Activate admin if we're inside the admin path.
|
||||
$is_admin = false;
|
||||
@@ -56,14 +57,14 @@ class SessionServiceProvider implements ServiceProviderInterface
|
||||
// Check no language, simple language prefix (en) and region specific language prefix (en-US).
|
||||
$pos = strpos($current_route, $base);
|
||||
if ($pos === 0 || $pos === 3 || $pos === 6) {
|
||||
$session_timeout = $config->get('plugins.admin.session.timeout', 1800);
|
||||
$cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
|
||||
$enabled = $is_admin = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for HUGE session timeouts.
|
||||
if ($session_timeout > 99999999999) {
|
||||
$session_timeout = 9999999999;
|
||||
if ($cookie_lifetime > 99999999999) {
|
||||
$cookie_lifetime = 9999999999;
|
||||
}
|
||||
|
||||
$inflector = new Inflector();
|
||||
@@ -73,10 +74,16 @@ class SessionServiceProvider implements ServiceProviderInterface
|
||||
}
|
||||
|
||||
// Define session service.
|
||||
$session = new Session($session_timeout, $session_path, $domain);
|
||||
$session->setName($session_name);
|
||||
$session->setSecure($secure);
|
||||
$session->setHttpOnly($httponly);
|
||||
$options = [
|
||||
'name' => $session_name,
|
||||
'cookie_lifetime' => $cookie_lifetime,
|
||||
'cookie_path' => $cookie_path,
|
||||
'cookie_domain' => $cookie_domain,
|
||||
'cookie_secure' => $cookie_secure,
|
||||
'cookie_httponly' => $cookie_httponly
|
||||
] + (array) $config->get('system.session.options');
|
||||
|
||||
$session = new Session($options);
|
||||
$session->setAutoStart($enabled);
|
||||
|
||||
return $session;
|
||||
@@ -84,7 +91,7 @@ class SessionServiceProvider implements ServiceProviderInterface
|
||||
|
||||
// Define session message service.
|
||||
$container['messages'] = function ($c) {
|
||||
if (!isset($c['session']) || !$c['session']->started()) {
|
||||
if (!isset($c['session']) || !$c['session']->isStarted()) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $c['debugger'];
|
||||
$debugger->addMessage('Inactive session: session messages may disappear', 'warming');
|
||||
|
||||
@@ -8,34 +8,20 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use RocketTheme\Toolbox\Session\Session as BaseSession;
|
||||
|
||||
class Session extends BaseSession
|
||||
class Session extends \Grav\Framework\Session\Session
|
||||
{
|
||||
/** @var bool */
|
||||
protected $autoStart = false;
|
||||
|
||||
protected $lifetime;
|
||||
protected $path;
|
||||
protected $domain;
|
||||
protected $secure;
|
||||
protected $httpOnly;
|
||||
|
||||
/**
|
||||
* @param int $lifetime Defaults to 1800 seconds.
|
||||
* @param string $path Cookie path.
|
||||
* @param string $domain Optional, domain for the session
|
||||
* @throws \RuntimeException
|
||||
* @return \Grav\Framework\Session\Session
|
||||
* @deprecated 1.5 Use getInstance() method instead
|
||||
*/
|
||||
public function __construct($lifetime, $path, $domain = null)
|
||||
public static function instance()
|
||||
{
|
||||
$this->lifetime = $lifetime;
|
||||
$this->path = $path;
|
||||
$this->domain = $domain;
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getInstance() method instead', E_USER_DEPRECATED);
|
||||
|
||||
if (php_sapi_name() !== 'cli') {
|
||||
parent::__construct($lifetime, $path, $domain);
|
||||
}
|
||||
return static::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,9 +34,6 @@ class Session extends BaseSession
|
||||
if ($this->autoStart) {
|
||||
$this->start();
|
||||
|
||||
// TODO: This setcookie shouldn't be here, session should by itself be able to update its cookie.
|
||||
setcookie(session_name(), session_id(), $this->lifetime ? time() + $this->lifetime : 0, $this->path, $this->domain, $this->secure, $this->httpOnly);
|
||||
|
||||
$this->autoStart = false;
|
||||
}
|
||||
}
|
||||
@@ -67,27 +50,29 @@ class Session extends BaseSession
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $secure
|
||||
* @return $this
|
||||
* Returns attributes.
|
||||
*
|
||||
* @return array Attributes
|
||||
* @deprecated 1.5 Use getAll() method instead
|
||||
*/
|
||||
public function setSecure($secure)
|
||||
public function all()
|
||||
{
|
||||
$this->secure = $secure;
|
||||
ini_set('session.cookie_secure', (bool)$secure);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getAll() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this;
|
||||
return $this->getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $httpOnly
|
||||
* @return $this
|
||||
* Checks if the session was started.
|
||||
*
|
||||
* @return Boolean
|
||||
* @deprecated 1.5 Use isStarted() method instead
|
||||
*/
|
||||
public function setHttpOnly($httpOnly)
|
||||
public function started()
|
||||
{
|
||||
$this->httpOnly = $httpOnly;
|
||||
ini_set('session.cookie_httponly', (bool)$httpOnly);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use isStarted() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this;
|
||||
return $this->isStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -98,7 +98,7 @@ class Themes extends Iterator
|
||||
continue;
|
||||
}
|
||||
|
||||
$theme = $directory->getBasename();
|
||||
$theme = $directory->getFilename();
|
||||
$result = self::get($theme);
|
||||
|
||||
if ($result) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user