mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
358 Commits
1.4.0-rc.1
...
1.5.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9c28c5a7c | ||
|
|
c56d7ac793 | ||
|
|
000bac8cfc | ||
|
|
e7d660149e | ||
|
|
829638c143 | ||
|
|
d8a627898e | ||
|
|
a3caa13c23 | ||
|
|
9944486c17 | ||
|
|
da5c9e415f | ||
|
|
7b5a1b2c14 | ||
|
|
235a5cc765 | ||
|
|
073d601b67 | ||
|
|
ad1bbba0b3 | ||
|
|
b6b5e329aa | ||
|
|
0e973dab07 | ||
|
|
15e371564a | ||
|
|
f0e33dc242 | ||
|
|
e67c3c1091 | ||
|
|
d5ce0bd93c | ||
|
|
44dbcdf2b1 | ||
|
|
3216442946 | ||
|
|
9d4471b196 | ||
|
|
c48107acd9 | ||
|
|
4671518409 | ||
|
|
41bf943f49 | ||
|
|
f40c6a8617 | ||
|
|
fb98ca7b19 | ||
|
|
451ec49d9c | ||
|
|
1709eb038c | ||
|
|
e69d6cefee | ||
|
|
7abe01ed8c | ||
|
|
17a371d86a | ||
|
|
5b787d56e6 | ||
|
|
33d98114ba | ||
|
|
51f29e112a | ||
|
|
ca8805683d | ||
|
|
8295bd8243 | ||
|
|
da95d1bb1e | ||
|
|
bbc4fd6c79 | ||
|
|
732ff8ecab | ||
|
|
41b7aadbda | ||
|
|
834d6938db | ||
|
|
dfabceb3d2 | ||
|
|
1808fd3d6e | ||
|
|
0b5c1dcfa7 | ||
|
|
1369f941f2 | ||
|
|
2101c6d0dc | ||
|
|
1993fc6a2c | ||
|
|
b9b43d1f05 | ||
|
|
8d53cf3c77 | ||
|
|
756ddaa97d | ||
|
|
89f64e423d | ||
|
|
ec5596b1a3 | ||
|
|
2de89e31c0 | ||
|
|
9ca5598b6f | ||
|
|
05863276ef | ||
|
|
5ac518f311 | ||
|
|
41f488f8da | ||
|
|
6cc6e51878 | ||
|
|
78bcf84127 | ||
|
|
6b224823f1 | ||
|
|
2734b2f605 | ||
|
|
1ee88d5836 | ||
|
|
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 | ||
|
|
03a0c42795 | ||
|
|
1976471982 | ||
|
|
1426a7ec95 | ||
|
|
2a759eed74 | ||
|
|
8980b78220 | ||
|
|
12b0a839e7 | ||
|
|
b4d570fd21 | ||
|
|
e60fd82400 | ||
|
|
a1abcfd067 | ||
|
|
7f90ad8474 | ||
|
|
e1d52181a3 | ||
|
|
d4494cb502 | ||
|
|
11266ce8f8 | ||
|
|
2f17b3fa7d | ||
|
|
dab595f571 | ||
|
|
afe72d0783 | ||
|
|
8e0e3e8718 | ||
|
|
5ab956a8ec | ||
|
|
a17554c951 | ||
|
|
30ff986603 | ||
|
|
18f46d902d | ||
|
|
3948ed5618 | ||
|
|
e6dd91f698 | ||
|
|
2c82e15fa1 | ||
|
|
2c7d866724 | ||
|
|
a977023e45 | ||
|
|
9c7008e225 | ||
|
|
342aa0ff04 | ||
|
|
d434d51d42 | ||
|
|
f03eb693e6 | ||
|
|
18928d6962 | ||
|
|
f7832e78dc | ||
|
|
68428a714a | ||
|
|
58db31a7d8 | ||
|
|
2917345b56 | ||
|
|
1cfd3482bb | ||
|
|
4d690938a8 | ||
|
|
40b475ecb9 | ||
|
|
4ab0a601ad | ||
|
|
40ba5e9106 | ||
|
|
d4ec2a25d4 | ||
|
|
0b1c18d63e | ||
|
|
f681f1c60b | ||
|
|
27df27d1df | ||
|
|
027dbcf6fa | ||
|
|
91e98cd32e | ||
|
|
1cef2a182a | ||
|
|
ad8764897a | ||
|
|
d2e700eea2 | ||
|
|
895e145d82 | ||
|
|
78ab2aa476 | ||
|
|
b9a7341e5e | ||
|
|
261ea62472 | ||
|
|
fae2aa4582 | ||
|
|
fb7230ec9a | ||
|
|
692aff3c89 | ||
|
|
3091a14223 | ||
|
|
a49f3d460e | ||
|
|
b6e0f8b75a | ||
|
|
ac8a1191f8 | ||
|
|
830c9524eb | ||
|
|
ce1e635451 | ||
|
|
8b0c1b7937 | ||
|
|
33cfa17899 | ||
|
|
87b0d80de0 | ||
|
|
b8c61e34c9 | ||
|
|
02555ba3f5 | ||
|
|
4db2b75699 | ||
|
|
c64590af16 | ||
|
|
3607124e55 | ||
|
|
e7405a13fc | ||
|
|
46f16ce4db | ||
|
|
d3a4466d9b | ||
|
|
46d682f889 | ||
|
|
2b17bf70de | ||
|
|
8e019b7958 | ||
|
|
47037e3f5e | ||
|
|
1ec653268d | ||
|
|
3ee140e77f | ||
|
|
cb490a1762 | ||
|
|
6d8ba5ed4d | ||
|
|
7020130511 | ||
|
|
ff2df04a58 | ||
|
|
3248b97997 | ||
|
|
3c26d831fd | ||
|
|
f4e584cda1 | ||
|
|
df7e9b9c1f | ||
|
|
1108d063ef | ||
|
|
a6306aae05 | ||
|
|
fdf79caf71 | ||
|
|
f31f7f0962 | ||
|
|
df185621ad | ||
|
|
301429d992 | ||
|
|
07db8a2f9d | ||
|
|
3a207843c7 | ||
|
|
8f1639c10b | ||
|
|
b515a5add0 | ||
|
|
33cfa5e104 | ||
|
|
83b85e2cac | ||
|
|
861eb43efa | ||
|
|
a20d6d7230 | ||
|
|
40e7ee79b4 | ||
|
|
dce97221c5 | ||
|
|
fea02736c5 | ||
|
|
d58ad3749d | ||
|
|
727f759b41 | ||
|
|
f42d59409b | ||
|
|
4adaead4ec | ||
|
|
8dbe248df1 | ||
|
|
0e26422613 | ||
|
|
234555b208 | ||
|
|
3cfc3f1cbe | ||
|
|
9b149307ef | ||
|
|
4c1d1dd5d0 | ||
|
|
cad10b6095 | ||
|
|
7aa930ca55 | ||
|
|
7e1c6b6137 | ||
|
|
d008376169 | ||
|
|
eaac77881f | ||
|
|
43ca0a2f58 | ||
|
|
54cd7f85b8 | ||
|
|
5ffe32ef58 | ||
|
|
b0ad83ebf7 | ||
|
|
d5060a2012 | ||
|
|
bf16e2e854 | ||
|
|
ec78319993 | ||
|
|
67b5649ee4 | ||
|
|
eca23a918a | ||
|
|
5b291f8f54 | ||
|
|
274c701907 | ||
|
|
e533024b2e | ||
|
|
8750602f76 | ||
|
|
51a9b9b7c7 | ||
|
|
42d8836569 | ||
|
|
7cfc6fbdb8 | ||
|
|
01886b6df9 | ||
|
|
b7d4697db8 | ||
|
|
742c6f9baa | ||
|
|
c97da77153 | ||
|
|
871848dc28 | ||
|
|
84a0b5d1e1 | ||
|
|
8e68317e7c | ||
|
|
35f7a2a9de | ||
|
|
62dfa0e060 | ||
|
|
2e28461435 | ||
|
|
4259d01638 | ||
|
|
39783df92d | ||
|
|
d39253e36f | ||
|
|
82868037ec | ||
|
|
17ba58a5c2 | ||
|
|
0a79788582 | ||
|
|
ccf0f9c74e | ||
|
|
79e580a79d | ||
|
|
5b9a40de5f | ||
|
|
550468aefb | ||
|
|
84a2a08d22 | ||
|
|
ee8ea5af57 | ||
|
|
2f763809ac | ||
|
|
f91678df4a | ||
|
|
4c654ef19c | ||
|
|
637308d294 | ||
|
|
e2ce65dc61 | ||
|
|
f6061e96e8 | ||
|
|
8c0134eb64 | ||
|
|
18c6bd6e23 | ||
|
|
08be06c9bc | ||
|
|
19ae66d0e9 | ||
|
|
c3f84d8682 | ||
|
|
c721be8787 | ||
|
|
47746d3313 | ||
|
|
103ac4b137 | ||
|
|
f261173de2 | ||
|
|
9d2dea07ad | ||
|
|
0695c29cf5 | ||
|
|
084a5c7fbe |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,9 +37,9 @@ Thumbs.db
|
||||
|
||||
# phpstorm
|
||||
.idea/*
|
||||
user/config/security.yaml
|
||||
|
||||
tests/_output/*
|
||||
tests/_support/_generated/*
|
||||
tests/cache/*
|
||||
tests/error.log
|
||||
/system/templates/testing
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
language: php
|
||||
php:
|
||||
- '5.5'
|
||||
- '5.6'
|
||||
- '7.0.21'
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
|
||||
269
CHANGELOG.md
269
CHANGELOG.md
@@ -1,46 +1,204 @@
|
||||
# v1.4.0-rc.1
|
||||
## 01/22/2018
|
||||
# v1.5.4
|
||||
## 11/05/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Updated default page `index.md` with some consistency fixes [#2245](https://github.com/getgrav/grav/pull/2245)
|
||||
1. [](#bugfix)
|
||||
* Fixed fatal error if calling `$session->invalidate()` when there's no active session
|
||||
* Fixed typo in media.yaml for `webm` extension [#2220](https://github.com/getgrav/grav/pull/2220)
|
||||
* Fixed markdown processing for telephone links [#2235](https://github.com/getgrav/grav/pull/2235)
|
||||
|
||||
# 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
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed an issue with some users getting **2FA** prompt after upgrade [admin#1442](https://github.com/getgrav/grav-plugin-admin/issues/1442)
|
||||
* Do not crash when generating URLs with arrays as parameters [#2018](https://github.com/getgrav/grav/pull/2018)
|
||||
* Utils::truncateHTML removes whitespace when generating summaries [#2004](https://github.com/getgrav/grav/pull/2004)
|
||||
|
||||
# v1.4.4
|
||||
## 05/11/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added support for `Uri::post()` and `Uri::getConentType()`
|
||||
* Added a new `Medium:thumbnailExists()` function [#1966](https://github.com/getgrav/grav/issues/1966)
|
||||
* Added `authorized` support for 2FA
|
||||
1. [](#improved)
|
||||
* Added default configuration for images [#1979](https://github.com/getgrav/grav/pull/1979)
|
||||
* Added dedicated PHPUnit assertions [#1990](https://github.com/getgrav/grav/pull/1990)
|
||||
1. [](#bugfix)
|
||||
* Use `array_key_exists` instead of `in_array + array_keys` [#1991](https://github.com/getgrav/grav/pull/1991)
|
||||
* Fixed an issue with `custom_base_url` always causing 404 errors
|
||||
* Improve support for regex redirects with query and params [#1983](https://github.com/getgrav/grav/issues/1983)
|
||||
* Changed collection-based date sorting to `SORT_REGULAR` for better server compatibility [#1910](https://github.com/getgrav/grav/issues/1910)
|
||||
* Fix hardcoded string in modular blueprint [#1933](https://github.com/getgrav/grav/pull/1993)
|
||||
|
||||
# v1.4.3
|
||||
## 04/12/2018
|
||||
|
||||
1. [](#new)
|
||||
* moved Twig `sortArrayByKey` logic into `Utils::` class
|
||||
1. [](#improved)
|
||||
* Rolled back Parsedown library to stable `1.6.4` until a better solution for `1.8.0` compatibility can fe found
|
||||
* Updated vendor libraries to latest versions
|
||||
1. [](#bugfix)
|
||||
* Fix for bad reference to `ZipArchive` in `GPM::Installer`
|
||||
|
||||
# v1.4.2
|
||||
## 03/21/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added new `|nicefilesize` Twig filter for pretty file (auto converts to bytes, kB, MB, GB, etc)
|
||||
* Added new `regex_filter()` Twig function to values in arrays
|
||||
1. [](#improved)
|
||||
* Added bosnian to lang codes [#1917](https://github.com/getgrav/grav/issues/1917)
|
||||
* Improved Zip extraction error codes [#1922](https://github.com/getgrav/grav/issues/1922)
|
||||
1. [](#bugfix)
|
||||
* Fixed an issue with Markdown Video and Audio that broke after Parsedown 1.7.0 Security updates [#1924](https://github.com/getgrav/grav/issues/1924)
|
||||
* Fix for case-sensitive page metadata [admin#1370](https://github.com/getgrav/grav-plugin-admin/issues/1370)
|
||||
* Fixed missing composer requirements for the new `Grav\Framework\Uri` classes
|
||||
* Added missing PSR-7 vendor library required for URI additions in Grav 1.4.0
|
||||
|
||||
# v1.4.1
|
||||
## 03/11/2018
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed session timing out because of session cookie was not being sent
|
||||
|
||||
# v1.4.0
|
||||
## 03/09/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added `Grav\Framework\Uri` classes extending PSR-7 `HTTP message UriInterface` implementation
|
||||
* Added `Grav\Framework\Route` classes to allow route/link manipulation
|
||||
* Added `$grav['uri]->getCurrentUri()` method to get `Grav\Framework\Uri\Uri` instance for the current URL
|
||||
* Added `$grav['uri]->getCurrentRoute()` method to get `Grav\Framework\Route\Route` instance for the current URL
|
||||
* Added ability to have `php` version dependencies in GPM assets
|
||||
* Added new `{% switch %}` twig tag for more elegant if statements
|
||||
* Added new `{% markdown %}` twig tag
|
||||
* Added **Route Overrides** to the default page blueprint
|
||||
* Added new `Collection::toExtendedArray()` method that's particularly useful for Json output of data
|
||||
* Added new `|yaml_encode` and `|yaml_decode` Twig filter to convert to and from YAML
|
||||
* Added new `read_file()` Twig function to allow you to load and display a file in Twig (Supports streams and regular paths)
|
||||
* Added a new `Medium::exists()` method to check for file existence
|
||||
* Moved Twig `urlFunc()` to `Utils::url()` as its so darn handy
|
||||
1. [](#improved)
|
||||
* Made `modular` blueprint more flexible
|
||||
* Code optimizations to `Utils` class [#1830](https://github.com/getgrav/grav/pull/1830)
|
||||
* Objects: Add protected function `getElement()` to get serialized value for a single property
|
||||
* `ObjectPropertyTrait`: Added protected functions `isPropertyLoaded()`, `offsetLoad()`, `offsetPrepare()` and `offsetSerialize()`
|
||||
* `Grav\Framework\Cache`: Allow unlimited TTL
|
||||
* Optimizations & refactoring to the test suite [#1779](https://github.com/getgrav/grav/pull/1779)
|
||||
* Slight modification of Whoops error colors
|
||||
* Updated vendor libs to latest
|
||||
1. [](#bugfix)
|
||||
* Date ordering should always be numeric [#1810](https://github.com/getgrav/grav/issues/1810)
|
||||
|
||||
# v1.4.0-beta.3
|
||||
## 12/29/2017
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for base paths containing special characters [#1799](https://github.com/getgrav/grav/issues/1799)
|
||||
* Fix for session cookies in paths containing special characters
|
||||
* Fix for `vundefined` error for version numbers in GPM [form#222](https://github.com/getgrav/grav-plugin-form/issues/222)
|
||||
1. [](#improved)
|
||||
* Added new configuration option `system.session.initialize` to delay session initialization if needed by a plugin
|
||||
|
||||
# v1.4.0-beta.2
|
||||
## 12/18/2017
|
||||
|
||||
1. [](#new)
|
||||
* Transferred overall copyright from RocketTheme, LLC, to Trilby Media LLC
|
||||
* Added `theme_var`, `header_var` and `body_class` Twig functions for themes
|
||||
1. [](#improved)
|
||||
* Updated vendor libraries to latest versions
|
||||
* Removed constructor from `ObjectInterface`
|
||||
1. [](#bugfix)
|
||||
* Fixed `BadMethodCallException` thrown in GPM updates [#1784](https://github.com/getgrav/grav/issues/1784)
|
||||
|
||||
# v1.4.0-beta.1
|
||||
## 12/11/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added `Grav\Framework\Cache` classes providing PSR-16 `Simple Cache` implementation
|
||||
* Added `Grav\Framework\ContentBlock` classes for nested HTML blocks with CSS/JS assets
|
||||
* Added `Grav\Framework\Object` classes for creating collections of objects
|
||||
@@ -49,15 +207,38 @@
|
||||
* Added `{% script %}` and `{% style %}` tags for Twig templates
|
||||
* Deprecated GravTrait
|
||||
1. [](#improved)
|
||||
* Improved `Session` initialization
|
||||
* Added ability to set a `theme_var()` option in page frontmatter
|
||||
* Force clearing PHP `clearstatcache` and `opcache-reset` on `Cache::clear()`
|
||||
* Better `Page.collection()` filtering support including ability to have non-published pages in collections
|
||||
* Stopped Chrome from auto-completing admin user profile form [#1847](https://github.com/getgrav/grav/issues/1847)
|
||||
* Support for empty `switch` field like a `checkbox`
|
||||
* Made `modular` blueprint more flexible
|
||||
* Code optimizations to `Utils` class [#1830](https://github.com/getgrav/grav/pull/1830)
|
||||
* Objects: Add protected function `getElement()` to get serialized value for a single property
|
||||
* `ObjectPropertyTrait`: Added protected functions `isPropertyLoaded()`, `offsetLoad()`, `offsetPrepare()` and `offsetSerialize()`
|
||||
* `Grav\Framework\Cache`: Allow unlimited TTL
|
||||
* Optimizations & refactoring to the test suite [#1779](https://github.com/getgrav/grav/pull/1779)
|
||||
* Slight modification of Whoops error colors
|
||||
* Added new configuration option `system.session.initialize` to delay session initialization if needed by a plugin
|
||||
* Updated vendor libraries to latest versions
|
||||
* Removed constructor from `ObjectInterface`
|
||||
* Make it possible to include debug bar also into non-HTML responses
|
||||
|
||||
# v1.3.11
|
||||
## mm/dd/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Updated vendor libraries to latest
|
||||
* Updated built-in JQuery to latest 3.3.1
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with image alt tag always getting empted out unless set in markdown
|
||||
* Fixed issue with remote PHP version determination for Grav updates [#1883](https://github.com/getgrav/grav/issues/1883)
|
||||
* Fixed issue with _illegal scheme offset_ in `Uri::convertUrl()` [page-inject#8](https://github.com/getgrav/grav-plugin-page-inject/issues/8)
|
||||
* Properly validate YAML blueprint fields so admin can save as proper YAML now [addresses many issues]
|
||||
* Fixed OpenGraph metatags so only Twitter uses `name=`, and all others use `property=` [#1849](https://github.com/getgrav/grav/issues/1849)
|
||||
* Fixed an issue with `evaluate()` and `evaluate_twig()` Twig functions that throws invalid template error
|
||||
* Fixed issue with `|sort_by_key` twig filter if the input was null or not an array
|
||||
* Date ordering should always be numeric [#1810](https://github.com/getgrav/grav/issues/1810)
|
||||
* Fix for base paths containing special characters [#1799](https://github.com/getgrav/grav/issues/1799)
|
||||
* Fix for session cookies in paths containing special characters
|
||||
* Fix for `vundefined` error for version numbers in GPM [form#222](https://github.com/getgrav/grav-plugin-form/issues/222)
|
||||
* Fixed `BadMethodCallException` thrown in GPM updates [#1784](https://github.com/getgrav/grav/issues/1784)
|
||||
* NOTE: Parsedown security release now escapes `&` to `&` in Markdown links
|
||||
|
||||
# v1.3.10
|
||||
## 12/06/2017
|
||||
|
||||
@@ -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 5.6.4 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
|
||||
@@ -183,7 +183,7 @@ See [LICENSE](LICENSE.txt)
|
||||
|
||||
# Running Tests
|
||||
|
||||
First install the dev dependencies by running `composer update` from the Grav root.
|
||||
Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.
|
||||
|
||||
First install the dev dependencies by running `composer update` from the Grav root.
|
||||
Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.
|
||||
Windows users should use the `composer test-windows` command.
|
||||
You can also run a single unit test file, e.g. `composer test tests/unit/Grav/Common/AssetsTest.php`
|
||||
|
||||
0
backup/.gitkeep
Normal file
0
backup/.gitkeep
Normal file
Binary file not shown.
1
bin/grav
1
bin/grav
@@ -41,5 +41,6 @@ $app->addCommands(array(
|
||||
new \Grav\Console\Cli\ClearCacheCommand(),
|
||||
new \Grav\Console\Cli\BackupCommand(),
|
||||
new \Grav\Console\Cli\NewProjectCommand(),
|
||||
new \Grav\Console\Cli\SecurityCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
@@ -6,31 +6,33 @@
|
||||
"homepage": "http://getgrav.org",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
"php": ">=5.5.9",
|
||||
"php": ">=5.6.4",
|
||||
"twig/twig": "~1.24",
|
||||
"erusev/parsedown": "~1.6",
|
||||
"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/yaml": "~3.4",
|
||||
"symfony/console": "~3.4",
|
||||
"symfony/event-dispatcher": "~3.4",
|
||||
"symfony/var-dumper": "~3.4",
|
||||
"symfony/polyfill-iconv": "~1.0",
|
||||
"doctrine/cache": "^1.6",
|
||||
"doctrine/collections": "1.3",
|
||||
"doctrine/collections": "^1.4",
|
||||
"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.0",
|
||||
"rockettheme/toolbox": "~1.3",
|
||||
"pimple/pimple": "~3.2",
|
||||
"rockettheme/toolbox": "~1.4",
|
||||
"maximebf/debugbar": "~1.10",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-json": "*",
|
||||
"league/climate": "^3.2",
|
||||
"antoligy/dom-string-iterators": "^1.0",
|
||||
"miljar/php-exif": "^0.6.3",
|
||||
@@ -44,7 +46,7 @@
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "5.5.9"
|
||||
"php": "5.6.4"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
|
||||
1035
composer.lock
generated
1035
composer.lock
generated
File diff suppressed because it is too large
Load Diff
10
index.php
10
index.php
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav.Core
|
||||
*
|
||||
@@ -7,7 +8,8 @@
|
||||
*/
|
||||
|
||||
namespace Grav;
|
||||
define('GRAV_PHP_MIN', '5.5.9');
|
||||
|
||||
define('GRAV_PHP_MIN', '5.6.4');
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
@@ -15,9 +17,9 @@ if (!is_file($autoload)) {
|
||||
die("Please run: <i>bin/grav install</i>");
|
||||
}
|
||||
|
||||
if (PHP_SAPI == 'cli-server') {
|
||||
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>");
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +31,7 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
}
|
||||
|
||||
// Register the auto-loader.
|
||||
$loader = require_once $autoload;
|
||||
$loader = require $autoload;
|
||||
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
@@ -9,3 +9,4 @@ Disallow: /vendor/
|
||||
Disallow: /user/
|
||||
Allow: /user/pages/
|
||||
Allow: /user/themes/
|
||||
Allow: /user/images/
|
||||
|
||||
6
system/assets/jquery/jquery-3.x.min.js
vendored
6
system/assets/jquery/jquery-3.x.min.js
vendored
File diff suppressed because one or more lines are too long
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
|
||||
|
||||
@@ -835,6 +835,8 @@ form:
|
||||
-1: PLUGIN_ADMIN.ERROR_SYSTEM
|
||||
0: PLUGIN_ADMIN.ERROR_SIMPLE
|
||||
1: PLUGIN_ADMIN.ERROR_FULL_BACKTRACE
|
||||
validate:
|
||||
type: int
|
||||
|
||||
|
||||
errors.log:
|
||||
@@ -994,6 +996,18 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
session.initialize:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.SESSION_INITIALIZE
|
||||
help: PLUGIN_ADMIN.SESSION_INITIALIZE_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
session.timeout:
|
||||
type: text
|
||||
size: small
|
||||
@@ -1204,3 +1218,27 @@ form:
|
||||
placeholder: "e.g. http://yoursite.com/yourpath"
|
||||
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
|
||||
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
|
||||
|
||||
strict_mode.yaml_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
|
||||
highlight: 1
|
||||
default: 1
|
||||
help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
strict_mode.twig_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
|
||||
highlight: 1
|
||||
default: 1
|
||||
help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
@@ -21,6 +21,9 @@ form:
|
||||
title: PLUGIN_ADMIN.CONTENT
|
||||
|
||||
fields:
|
||||
xss_check:
|
||||
type: xss
|
||||
|
||||
header.title:
|
||||
type: text
|
||||
autofocus: true
|
||||
@@ -310,6 +313,31 @@ form:
|
||||
toggleable: true
|
||||
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
|
||||
|
||||
routes_only:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.ROUTE_OVERRIDES
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
|
||||
header.routes.default:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.ROUTE_DEFAULT
|
||||
|
||||
header.routes.canonical:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.ROUTE_CANONICAL
|
||||
|
||||
header.routes.aliases:
|
||||
type: array
|
||||
toggleable: true
|
||||
value_only: true
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.ROUTE_ALIASES
|
||||
|
||||
|
||||
admin_only:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.ADMIN_SPECIFIC_OVERRIDES
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
title: PLUGIN_ADMIN:EXTERNAL
|
||||
@extends:
|
||||
extends@:
|
||||
type: default
|
||||
context: blueprints://pages
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
title: PLUGIN_ADMIN.MODULAR
|
||||
'@extends': default
|
||||
extends@: default
|
||||
|
||||
form:
|
||||
fields:
|
||||
@@ -13,7 +13,7 @@ form:
|
||||
|
||||
modular_title:
|
||||
type: spacer
|
||||
title: Modular Setup
|
||||
title: PLUGIN_ADMIN.MODULAR_SETUP
|
||||
|
||||
header.content.items:
|
||||
type: text
|
||||
|
||||
@@ -40,6 +40,7 @@ form:
|
||||
type: password
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.PASSWORD
|
||||
autocomplete: new-password
|
||||
validate:
|
||||
required: false
|
||||
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
|
||||
@@ -93,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
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ types:
|
||||
thumb: media/thumb-flv.png
|
||||
mime: video/x-flv
|
||||
webm:
|
||||
type: file
|
||||
type: video
|
||||
thumb: media/thumb-webm.png
|
||||
mime: video/webm
|
||||
ogv:
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ home:
|
||||
hide_in_urls: false # Hide the home route in URLs
|
||||
|
||||
pages:
|
||||
theme: antimatter # Default theme (defaults to "antimatter" theme)
|
||||
theme: quark # Default theme (defaults to "quark" theme)
|
||||
order:
|
||||
by: default # Order pages by "default", "alpha" or "date"
|
||||
dir: asc # Default ordering direction, "asc" or "desc"
|
||||
@@ -88,7 +88,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
|
||||
@@ -146,3 +146,7 @@ gpm:
|
||||
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.0-rc.1');
|
||||
define('GRAV_TESTING', true);
|
||||
define('GRAV_VERSION', '1.5.4');
|
||||
define('GRAV_TESTING', false);
|
||||
define('DS', '/');
|
||||
|
||||
if (!defined('GRAV_PHP_MIN')) {
|
||||
define('GRAV_PHP_MIN', '5.5.9');
|
||||
define('GRAV_PHP_MIN', '5.6.4');
|
||||
}
|
||||
|
||||
// Directories and Paths
|
||||
|
||||
@@ -72,7 +72,6 @@ NICETIME:
|
||||
SEC: sec
|
||||
MIN: min
|
||||
HR: hr
|
||||
DAY: day
|
||||
WK: wk
|
||||
MO: mo
|
||||
YR: yr
|
||||
@@ -88,7 +87,6 @@ NICETIME:
|
||||
SEC_PLURAL: secs
|
||||
MIN_PLURAL: mins
|
||||
HR_PLURAL: hrs
|
||||
DAY_PLURAL: days
|
||||
WK_PLURAL: wks
|
||||
MO_PLURAL: mos
|
||||
YR_PLURAL: yrs
|
||||
|
||||
@@ -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: 年
|
||||
|
||||
@@ -617,7 +617,7 @@ class Assets
|
||||
|
||||
$inlineGroup = array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline';
|
||||
|
||||
$attributes = $this->attributes(array_merge(['type' => 'text/javascript'], $attributes));
|
||||
$attributes = $this->attributes($attributes);
|
||||
|
||||
$output = '';
|
||||
$inline_js = '';
|
||||
|
||||
@@ -429,6 +429,14 @@ class Cache extends Getters
|
||||
$output[] = '';
|
||||
}
|
||||
|
||||
// Clear stat cache
|
||||
@clearstatcache();
|
||||
|
||||
// Clear opcache
|
||||
if (function_exists('opcache_reset')) {
|
||||
@opcache_reset();
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,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}";
|
||||
|
||||
|
||||
@@ -137,7 +137,8 @@ class Setup extends Data
|
||||
|
||||
// Pre-load setup.php which contains our initial configuration.
|
||||
// Configuration may contain dynamic parts, which is why we need to always load it.
|
||||
$file = GRAV_ROOT . '/setup.php';
|
||||
// If "GRAVE_SETUP_PATH" has been defined, use it, otherwise use defaults.
|
||||
$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.
|
||||
@@ -261,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,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
|
||||
{
|
||||
@@ -33,7 +32,7 @@ class Validation
|
||||
$method = 'type'.strtr($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' && $value == false))) {
|
||||
if ((empty($validate['required']) || (isset($validate['required']) && $validate['required'] !== true)) && ($value === null || $value === '' || (($field['type'] === 'checkbox' || $field['type'] === 'switch') && $value == false))) {
|
||||
return $messages;
|
||||
}
|
||||
|
||||
@@ -41,11 +40,6 @@ class Validation
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
|
||||
// If this is a YAML field, stop validation
|
||||
if (isset($field['yaml']) && $field['yaml'] === true) {
|
||||
return $messages;
|
||||
}
|
||||
|
||||
// Get language class.
|
||||
$language = Grav::instance()['language'];
|
||||
|
||||
@@ -54,6 +48,12 @@ class Validation
|
||||
? $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) {
|
||||
$method = 'typeYaml';
|
||||
}
|
||||
|
||||
if (method_exists(__CLASS__, $method)) {
|
||||
$success = self::$method($value, $validate, $field);
|
||||
} else {
|
||||
@@ -100,15 +100,16 @@ class Validation
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
|
||||
// If this is a YAML field, simply parse it and return the value.
|
||||
if (isset($field['yaml']) && $field['yaml'] === true) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Validate type with fallback type text.
|
||||
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
|
||||
$method = 'filter' . ucfirst(strtr($type, '-', '_'));
|
||||
|
||||
// If this is a YAML field validate/filter as such
|
||||
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
|
||||
$method = 'filterYaml';
|
||||
}
|
||||
|
||||
if (!method_exists(__CLASS__, $method)) {
|
||||
$method = 'filterText';
|
||||
}
|
||||
@@ -126,10 +127,12 @@ class Validation
|
||||
*/
|
||||
public static function typeText($value, array $params, array $field)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
if (!is_string($value) && !is_numeric($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = (string)$value;
|
||||
|
||||
if (isset($params['min']) && strlen($value) < $params['min']) {
|
||||
return false;
|
||||
}
|
||||
@@ -639,23 +642,14 @@ class Validation
|
||||
return (array) $value;
|
||||
}
|
||||
|
||||
public static function typeYaml($value, $params)
|
||||
{
|
||||
try {
|
||||
Yaml::parse($value);
|
||||
return true;
|
||||
} catch (ParseException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function filterYaml($value, $params)
|
||||
{
|
||||
try {
|
||||
return (array) Yaml::parse($value);
|
||||
} catch (ParseException $e) {
|
||||
return null;
|
||||
if (!is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return (array) Yaml::parse($value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -719,8 +722,8 @@ class GPM extends Iterator
|
||||
foreach ($packages as $package_name => $package) {
|
||||
if (isset($package['dependencies'])) {
|
||||
foreach ($package['dependencies'] as $dependency) {
|
||||
if (is_array($dependency)) {
|
||||
$dependency = array_keys($dependency)[0];
|
||||
if (is_array($dependency) && isset($dependency['name'])) {
|
||||
$dependency = $dependency['name'];
|
||||
}
|
||||
|
||||
if ($dependency == $slug) {
|
||||
@@ -835,6 +838,20 @@ class GPM extends Iterator
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check PHP version
|
||||
if ($dependency_slug == 'php') {
|
||||
$current_php_version = phpversion();
|
||||
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
|
||||
$current_php_version) === 1
|
||||
) {
|
||||
//Needs a Grav update first
|
||||
throw new \Exception("<red>One of the packages require PHP " . $dependencies['php'] . ". Please update PHP to resolve this");
|
||||
} else {
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
|
||||
if ($dependency_slug == 'grav') {
|
||||
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
|
||||
@@ -859,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
|
||||
@@ -1062,9 +1081,9 @@ class GPM extends Iterator
|
||||
} elseif ($version == '') {
|
||||
return null;
|
||||
} elseif ($this->versionFormatIsNextSignificantRelease($version)) {
|
||||
return substr($version, 1);
|
||||
return trim(substr($version, 1));
|
||||
} elseif ($this->versionFormatIsEqualOrHigher($version)) {
|
||||
return substr($version, 2);
|
||||
return trim(substr($version, 2));
|
||||
} else {
|
||||
return $version;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@ class Installer
|
||||
*/
|
||||
protected static $error = 0;
|
||||
|
||||
/**
|
||||
* @var integer Zip Error Code
|
||||
*/
|
||||
protected static $error_zip = 0;
|
||||
|
||||
/**
|
||||
* @var string Post install message
|
||||
*/
|
||||
@@ -190,10 +195,10 @@ class Installer
|
||||
}
|
||||
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
self::$error_zip = $archive;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates and returns the package installer class
|
||||
*
|
||||
@@ -291,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);
|
||||
}
|
||||
@@ -460,7 +465,42 @@ class Installer
|
||||
break;
|
||||
|
||||
case self::ZIP_EXTRACT_ERROR:
|
||||
$msg = 'An error occurred while extracting the package';
|
||||
$msg = 'Unable to extract the package. ';
|
||||
if (self::$error_zip) {
|
||||
switch(self::$error_zip) {
|
||||
case \ZipArchive::ER_EXISTS:
|
||||
$msg .= "File already exists.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_INCONS:
|
||||
$msg .= "Zip archive inconsistent.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_MEMORY:
|
||||
$msg .= "Malloc failure.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_NOENT:
|
||||
$msg .= "No such file.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_NOZIP:
|
||||
$msg .= "Not a zip archive.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_OPEN:
|
||||
$msg .= "Can't open file.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_READ:
|
||||
$msg .= "Read error.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_SEEK:
|
||||
$msg .= "Seek error.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class GravCore extends AbstractPackageCollection
|
||||
|
||||
private $version;
|
||||
private $date;
|
||||
private $min_php;
|
||||
|
||||
/**
|
||||
* @param bool $refresh
|
||||
@@ -37,6 +38,7 @@ class GravCore extends AbstractPackageCollection
|
||||
$this->data = json_decode($this->raw, true);
|
||||
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
|
||||
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
|
||||
$this->min_php = isset($this->data['min_php']) ? $this->data['min_php'] : null;
|
||||
|
||||
if (isset($this->data['assets'])) {
|
||||
foreach ((array)$this->data['assets'] as $slug => $data) {
|
||||
@@ -92,6 +94,11 @@ class GravCore extends AbstractPackageCollection
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this version of Grav is eligible to be updated
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function isUpdatable()
|
||||
{
|
||||
return version_compare(GRAV_VERSION, $this->getVersion(), '<');
|
||||
@@ -107,6 +114,25 @@ class GravCore extends AbstractPackageCollection
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum PHP version
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getMinPHPVersion()
|
||||
{
|
||||
// If non min set, assume current PHP version
|
||||
if (is_null($this->min_php)) {
|
||||
$this->min_php = phpversion();
|
||||
}
|
||||
return $this->min_php;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this installation symlinked?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSymlink()
|
||||
{
|
||||
return is_link(GRAV_ROOT . DS . 'index.php');
|
||||
|
||||
@@ -24,6 +24,8 @@ class Upgrader
|
||||
*/
|
||||
private $remote;
|
||||
|
||||
private $min_php;
|
||||
|
||||
/**
|
||||
* Creates a new GPM instance with Local and Remote packages available
|
||||
*
|
||||
@@ -89,17 +91,33 @@ class Upgrader
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure this meets minimum PHP requirements
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function meetsRequirements()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, GRAV_PHP_MIN, '<')) {
|
||||
$current_php_version = phpversion();
|
||||
if (version_compare($current_php_version, $this->minPHPVersion(), '<')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minimum PHP version from remote
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function minPHPVersion()
|
||||
{
|
||||
if (is_null($this->min_php)) {
|
||||
$this->min_php = $this->remote->getMinPHPVersion();
|
||||
}
|
||||
return $this->min_php;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the currently installed Grav is upgradable to a newer version
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
@@ -39,8 +38,7 @@ class Grav extends Container
|
||||
'uri' => 'Grav\Common\Uri',
|
||||
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
|
||||
'cache' => 'Grav\Common\Cache',
|
||||
'session' => 'Grav\Common\Session',
|
||||
'Grav\Common\Service\MessagesServiceProvider',
|
||||
'Grav\Common\Service\SessionServiceProvider',
|
||||
'plugins' => 'Grav\Common\Plugins',
|
||||
'themes' => 'Grav\Common\Themes',
|
||||
'twig' => 'Grav\Common\Twig\Twig',
|
||||
@@ -206,11 +204,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);
|
||||
}
|
||||
@@ -444,15 +439,16 @@ 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');
|
||||
|
||||
// Check whitelist first, then ensure extension is a valid media type
|
||||
if (!empty($fallback_types) && !in_array($uri_extension, $fallback_types)) {
|
||||
return;
|
||||
} elseif (!array_key_exists($uri_extension, $supported_types)) {
|
||||
return;
|
||||
if (!empty($fallback_types) && !\in_array($uri_extension, $fallback_types, true)) {
|
||||
return false;
|
||||
}
|
||||
if (!array_key_exists($uri_extension, $supported_types)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class Excerpts
|
||||
*/
|
||||
public static function processLinkExcerpt($excerpt, Page $page, $type = 'link')
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['href']));
|
||||
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
|
||||
|
||||
$url_parts = static::parseUrl($url);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -246,11 +245,12 @@ class Excerpts
|
||||
|
||||
// Process operations
|
||||
$medium = static::processMediaActions($medium, $url_parts);
|
||||
$element_excerpt = $excerpt['element']['attributes'];
|
||||
|
||||
$alt = isset($excerpt['element']['attributes']['alt']) ? $excerpt['element']['attributes']['alt'] : '';
|
||||
$title = isset($excerpt['element']['attributes']['title']) ? $excerpt['element']['attributes']['title'] : '';
|
||||
$class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : '';
|
||||
$id = isset($excerpt['element']['attributes']['id']) ? $excerpt['element']['attributes']['id'] : '';
|
||||
$alt = isset($element_excerpt['alt']) ? $element_excerpt['alt'] : '';
|
||||
$title = isset($element_excerpt['title']) ? $element_excerpt['title'] : '';
|
||||
$class = isset($element_excerpt['class']) ? $element_excerpt['class'] : '';
|
||||
$id = isset($element_excerpt['id']) ? $element_excerpt['id'] : '';
|
||||
|
||||
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
|
||||
|
||||
@@ -293,6 +293,15 @@ class Excerpts
|
||||
if (Grav::instance()['config']->get('system.images.auto_fix_orientation')) {
|
||||
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
|
||||
}
|
||||
$defaults = Grav::instance()['config']->get('system.images.defaults');
|
||||
if (is_array($defaults) && count($defaults)) {
|
||||
foreach ($defaults as $method => $params) {
|
||||
$actions[] = [
|
||||
'method' => $method,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
|
||||
@@ -47,6 +47,7 @@ class Truncator {
|
||||
|
||||
// Iterate over words.
|
||||
$words = new DOMWordsIterator($body);
|
||||
$truncated = false;
|
||||
foreach ($words as $word) {
|
||||
|
||||
// If we have exceeded the limit, we delete the remainder of the content.
|
||||
@@ -70,12 +71,19 @@ class Truncator {
|
||||
self::insertEllipsis($curNode, $ellipsis);
|
||||
}
|
||||
|
||||
$truncated = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return self::innerHTML($body);
|
||||
// Return original HTML if not truncated.
|
||||
if ($truncated) {
|
||||
return self::innerHTML($body);
|
||||
} else {
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,24 +106,32 @@ class Truncator {
|
||||
|
||||
// Iterate over letters.
|
||||
$letters = new DOMLettersIterator($body);
|
||||
$truncated = false;
|
||||
foreach ($letters as $letter) {
|
||||
|
||||
// If we have exceeded the limit, we want to delete the remainder of this document.
|
||||
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)) {
|
||||
self::insertEllipsis($currentText[0], $ellipsis);
|
||||
}
|
||||
|
||||
$truncated = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return self::innerHTML($body);
|
||||
// Return original HTML if not truncated.
|
||||
if ($truncated) {
|
||||
return self::innerHTML($body);
|
||||
} else {
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -174,23 +174,23 @@ class Language
|
||||
|
||||
// if languages set
|
||||
if ($this->enabled()) {
|
||||
// try setting from prefix of URL (/en/blah/blah)
|
||||
// Try setting language from prefix of URL (/en/blah/blah).
|
||||
if (preg_match($regex, $uri, $matches)) {
|
||||
$this->lang_in_url = true;
|
||||
$this->active = $matches[2];
|
||||
$uri = preg_replace("/\\" . $matches[1] . "/", '', $uri, 1);
|
||||
$uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
|
||||
|
||||
// store in session if different
|
||||
if ($this->config->get('system.session.enabled', false)
|
||||
// Store in session if language is different.
|
||||
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
|
||||
) {
|
||||
$this->grav['session']->active_language = $this->active;
|
||||
}
|
||||
} else {
|
||||
// try getting from session, else no active
|
||||
if ($this->config->get('system.session.enabled', false) &&
|
||||
$this->config->get('system.languages.session_store_active', true)) {
|
||||
// Try getting language from the session, else no active.
|
||||
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;
|
||||
}
|
||||
// if still null, try from http_accept_language header
|
||||
@@ -203,8 +203,8 @@ class Language
|
||||
}
|
||||
}
|
||||
|
||||
// repeat if not found, try base language only - fixes Safari sending the language code always
|
||||
// with a locale (e.g. it-it or fr-fr)
|
||||
// Repeat if not found, try base language only - fixes Safari sending the language code always
|
||||
// with a locale (e.g. it-it or fr-fr).
|
||||
foreach ($preferred as $lang) {
|
||||
$lang = substr($lang, 0, 2);
|
||||
if ($this->validate($lang)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ class LanguageCodes
|
||||
'bn-BD' => [ 'name' => 'Bengali (Bangladesh)', 'nativeName' => 'বাংলা (বাংলাদেশ)' ],
|
||||
'bn-IN' => [ 'name' => 'Bengali (India)', 'nativeName' => 'বাংলা (ভারত)' ],
|
||||
'br' => [ 'name' => 'Breton', 'nativeName' => 'Brezhoneg' ],
|
||||
'bs' => [ 'name' => 'Bosnian', 'nativeName' => 'Bosanski' ],
|
||||
'ca' => [ 'name' => 'Catalan', 'nativeName' => 'Català' ],
|
||||
'ca-valencia'=> [ 'name' => 'Catalan (Valencian)', 'nativeName' => 'Català (valencià)' ], // not iso-639-1. a=l10n-drivers
|
||||
'cs' => [ 'name' => 'Czech', 'nativeName' => 'Čeština' ],
|
||||
|
||||
@@ -35,7 +35,7 @@ trait ParsedownGravTrait
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->page = $page;
|
||||
$this->BlockTypes['{'] [] = "TwigTag";
|
||||
$this->BlockTypes['{'] [] = 'TwigTag';
|
||||
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
|
||||
|
||||
if ($defaults === null) {
|
||||
@@ -56,6 +56,9 @@ trait ParsedownGravTrait
|
||||
*
|
||||
* @param $type
|
||||
* @param $tag
|
||||
* @param bool $continuable
|
||||
* @param bool $completable
|
||||
* @param $index
|
||||
*/
|
||||
public function addBlockType($type, $tag, $continuable = false, $completable = false, $index = null)
|
||||
{
|
||||
@@ -67,7 +70,7 @@ trait ParsedownGravTrait
|
||||
$block = &$this->BlockTypes[$type];
|
||||
}
|
||||
|
||||
if (!isset($index)) {
|
||||
if (null === $index) {
|
||||
$block[] = $tag;
|
||||
} else {
|
||||
array_splice($block, $index, 0, [$tag]);
|
||||
@@ -86,10 +89,11 @@ trait ParsedownGravTrait
|
||||
*
|
||||
* @param $type
|
||||
* @param $tag
|
||||
* @param $index
|
||||
*/
|
||||
public function addInlineType($type, $tag, $index = null)
|
||||
{
|
||||
if (!isset($index) || !isset($this->InlineTypes[$type])) {
|
||||
if (null === $index || !isset($this->InlineTypes[$type])) {
|
||||
$this->InlineTypes[$type] [] = $tag;
|
||||
} else {
|
||||
array_splice($this->InlineTypes[$type], $index, 0, [$tag]);
|
||||
@@ -109,7 +113,7 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
protected function isBlockContinuable($Type)
|
||||
{
|
||||
$continuable = in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue');
|
||||
$continuable = \in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue');
|
||||
|
||||
return $continuable;
|
||||
}
|
||||
@@ -123,7 +127,7 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
protected function isBlockCompletable($Type)
|
||||
{
|
||||
$completable = in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete');
|
||||
$completable = \in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete');
|
||||
|
||||
return $completable;
|
||||
}
|
||||
@@ -157,32 +161,31 @@ trait ParsedownGravTrait
|
||||
|
||||
/**
|
||||
* Ensure Twig tags are treated as block level items with no <p></p> tags
|
||||
*
|
||||
* @param array $line
|
||||
* @return array|null
|
||||
*/
|
||||
protected function blockTwigTag($Line)
|
||||
protected function blockTwigTag($line)
|
||||
{
|
||||
if (preg_match('/(?:{{|{%|{#)(.*)(?:}}|%}|#})/', $Line['body'], $matches)) {
|
||||
$Block = [
|
||||
'markup' => $Line['body'],
|
||||
];
|
||||
|
||||
return $Block;
|
||||
if (preg_match('/(?:{{|{%|{#)(.*)(?:}}|%}|#})/', $line['body'], $matches)) {
|
||||
return ['markup' => $line['body']];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function inlineSpecialCharacter($Excerpt)
|
||||
protected function inlineSpecialCharacter($excerpt)
|
||||
{
|
||||
if ($Excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $Excerpt['text'])) {
|
||||
if ($excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $excerpt['text'])) {
|
||||
return [
|
||||
'markup' => '&',
|
||||
'extent' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($this->special_chars[$Excerpt['text'][0]])) {
|
||||
if (isset($this->special_chars[$excerpt['text'][0]])) {
|
||||
return [
|
||||
'markup' => '&' . $this->special_chars[$Excerpt['text'][0]] . ';',
|
||||
'markup' => '&' . $this->special_chars[$excerpt['text'][0]] . ';',
|
||||
'extent' => 1,
|
||||
];
|
||||
}
|
||||
@@ -199,11 +202,11 @@ trait ParsedownGravTrait
|
||||
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
|
||||
|
||||
return $excerpt;
|
||||
} else {
|
||||
$excerpt['type'] = 'image';
|
||||
$excerpt = parent::inlineImage($excerpt);
|
||||
}
|
||||
|
||||
$excerpt['type'] = 'image';
|
||||
$excerpt = parent::inlineImage($excerpt);
|
||||
|
||||
// if this is an image process it
|
||||
if (isset($excerpt['element']['attributes']['src'])) {
|
||||
$excerpt = Excerpts::processImageExcerpt($excerpt, $this->page);
|
||||
@@ -228,10 +231,10 @@ trait ParsedownGravTrait
|
||||
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
|
||||
|
||||
return $excerpt;
|
||||
} else {
|
||||
$excerpt = parent::inlineLink($excerpt);
|
||||
}
|
||||
|
||||
$excerpt = parent::inlineLink($excerpt);
|
||||
|
||||
// if this is a link
|
||||
if (isset($excerpt['element']['attributes']['href'])) {
|
||||
$excerpt = Excerpts::processLinkExcerpt($excerpt, $this->page, $type);
|
||||
@@ -243,10 +246,10 @@ trait ParsedownGravTrait
|
||||
// For extending this class via plugins
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (isset($this->$method) === true) {
|
||||
$func = $this->$method;
|
||||
if (isset($this->{$method}) === true) {
|
||||
$func = $this->{$method};
|
||||
|
||||
return call_user_func_array($func, $args);
|
||||
return \call_user_func_array($func, $args);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\Media\Interfaces;
|
||||
|
||||
/**
|
||||
* Class implements media collection interface.
|
||||
*/
|
||||
interface MediaCollectionInterface
|
||||
{
|
||||
}
|
||||
29
system/src/Grav/Common/Media/Interfaces/MediaInterface.php
Normal file
29
system/src/Grav/Common/Media/Interfaces/MediaInterface.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Grav\Common\Media\Interfaces;
|
||||
|
||||
/**
|
||||
* Class implements media interface.
|
||||
*/
|
||||
interface MediaInterface
|
||||
{
|
||||
/**
|
||||
* Gets the associated media collection.
|
||||
*
|
||||
* @return MediaCollectionInterface Collection of associated media.
|
||||
*/
|
||||
public function getMedia();
|
||||
|
||||
/**
|
||||
* Get filesystem path to the associated media.
|
||||
*
|
||||
* @return string|null Media path or null if the object doesn't have media folder.
|
||||
*/
|
||||
public function getMediaFolder();
|
||||
|
||||
/**
|
||||
* Get display order for the associated media.
|
||||
*
|
||||
* @return array Empty array means default ordering.
|
||||
*/
|
||||
public function getMediaOrder();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\Media\Interfaces;
|
||||
|
||||
/**
|
||||
* Class implements media object interface.
|
||||
*/
|
||||
interface 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();
|
||||
}
|
||||
@@ -610,4 +610,23 @@ class Collection extends Iterator
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function toExtendedArray()
|
||||
{
|
||||
$items = [];
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
|
||||
if ($page !== null) {
|
||||
$items[$page->route()] = $page->toArray();
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
||||
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,8 +12,9 @@ use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
|
||||
class Medium extends Data implements RenderableInterface
|
||||
class Medium extends Data implements RenderableInterface, MediaObjectInterface
|
||||
{
|
||||
use ParsedownHtmlTrait;
|
||||
|
||||
@@ -72,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
|
||||
*
|
||||
@@ -79,7 +85,7 @@ class Medium extends Data implements RenderableInterface
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return clone($this);
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,6 +98,20 @@ class Medium extends Data implements RenderableInterface
|
||||
return new Data($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this medium exists or not
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
$path = $this->get('filepath');
|
||||
if (file_exists($path)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing just the metadata
|
||||
*
|
||||
@@ -179,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();
|
||||
@@ -270,10 +295,14 @@ class Medium extends Data implements RenderableInterface
|
||||
}
|
||||
|
||||
if (empty($attributes['alt'])) {
|
||||
if (!empty($alt) || $alt === '') {
|
||||
if (!empty($alt)) {
|
||||
$attributes['alt'] = $alt;
|
||||
} elseif (!empty($this->items['alt'])) {
|
||||
$attributes['alt'] = $this->items['alt'];
|
||||
} elseif (!empty($this->items['alt_text'])) {
|
||||
$attributes['alt'] = $this->items['alt_text'];
|
||||
} else {
|
||||
$attributes['alt'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,6 +409,22 @@ class Medium extends Data implements RenderableInterface
|
||||
return $mode === 'thumbnail' ? ($this->getThumbnail() ? $this->getThumbnail()->reset() : null) : $this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if this media item has a thumbnail or not
|
||||
*
|
||||
* @param string $type;
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function thumbnailExists($type = 'page')
|
||||
{
|
||||
$thumbs = $this->get('thumbnails');
|
||||
if (isset($thumbs[$type])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch thumbnail.
|
||||
*
|
||||
@@ -402,6 +447,7 @@ class Medium extends Data implements RenderableInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Turn the current Medium into a Link
|
||||
*
|
||||
@@ -500,7 +546,12 @@ class Medium extends Data implements RenderableInterface
|
||||
{
|
||||
$qs = $method;
|
||||
if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) {
|
||||
$qs .= '=' . implode(',', array_map(function ($a) { return rawurlencode($a); }, $args));
|
||||
$qs .= '=' . implode(',', array_map(function ($a) {
|
||||
if (is_array($a)) {
|
||||
$a = '[' . implode(',', $a) . ']';
|
||||
}
|
||||
return rawurlencode($a);
|
||||
}, $args));
|
||||
}
|
||||
|
||||
if (!empty($qs)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1109,7 +1114,7 @@ class Page
|
||||
*/
|
||||
public function toYaml()
|
||||
{
|
||||
return Yaml::dump($this->toArray(), 10);
|
||||
return Yaml::dump($this->toArray(), 20);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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)) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1499,6 +1522,8 @@ class Page
|
||||
|
||||
// Build an array of meta objects..
|
||||
foreach ((array)$metadata as $key => $value) {
|
||||
// Lowercase the key
|
||||
$key = strtolower($key);
|
||||
// If this is a property type metadata: "og", "twitter", "facebook" etc
|
||||
// Backward compatibility for nested arrays in metas
|
||||
if (is_array($value)) {
|
||||
@@ -1525,12 +1550,13 @@ class Page
|
||||
$separator = strpos($key, ':');
|
||||
$hasSeparator = $separator && $separator < strlen($key) - 1;
|
||||
$entry = [
|
||||
'name' => $key,
|
||||
'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
|
||||
];
|
||||
|
||||
if ($hasSeparator) {
|
||||
if ($hasSeparator && !Utils::startsWith($key, 'twitter')) {
|
||||
$entry['property'] = $key;
|
||||
} else {
|
||||
$entry['name'] = $key;
|
||||
}
|
||||
|
||||
$this->metadata[$key] = $entry;
|
||||
@@ -1558,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1623,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 */
|
||||
@@ -1639,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 !== '/') {
|
||||
@@ -1787,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'])) {
|
||||
@@ -2471,7 +2486,15 @@ class Page
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
$collection = $this->evaluate($params['items']);
|
||||
// See if require published filter is set and use that, if assume published=true
|
||||
$only_published = true;
|
||||
if (isset($params['filter']['published']) && $params['filter']['published']) {
|
||||
$only_published = false;
|
||||
} elseif (isset($params['filter']['non-published']) && $params['filter']['non-published']) {
|
||||
$only_published = false;
|
||||
}
|
||||
|
||||
$collection = $this->evaluate($params['items'], $only_published);
|
||||
if (!$collection instanceof Collection) {
|
||||
$collection = new Collection();
|
||||
}
|
||||
@@ -2486,7 +2509,7 @@ class Page
|
||||
|
||||
if ($process_taxonomy) {
|
||||
foreach ((array)$config->get('site.taxonomies') as $taxonomy) {
|
||||
if ($uri->param($taxonomy)) {
|
||||
if ($uri->param(rawurlencode($taxonomy))) {
|
||||
$items = explode(',', $uri->param($taxonomy));
|
||||
$collection->setParams(['taxonomies' => [$taxonomy => $items]]);
|
||||
|
||||
@@ -2510,25 +2533,60 @@ class Page
|
||||
|
||||
// If a filter or filters are set, filter the collection...
|
||||
if (isset($params['filter'])) {
|
||||
|
||||
// remove any inclusive sets from filer:
|
||||
$sets = ['published', 'visible', 'modular', 'routable'];
|
||||
foreach ($sets as $type) {
|
||||
if (isset($params['filter'][$type]) && isset($params['filter']['non-'.$type])) {
|
||||
if ($params['filter'][$type] && $params['filter']['non-'.$type]) {
|
||||
unset ($params['filter'][$type]);
|
||||
unset ($params['filter']['non-'.$type]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((array)$params['filter'] as $type => $filter) {
|
||||
switch ($type) {
|
||||
case 'published':
|
||||
if ((bool) $filter) {
|
||||
$collection->published();
|
||||
}
|
||||
break;
|
||||
case 'non-published':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonPublished();
|
||||
}
|
||||
break;
|
||||
case 'visible':
|
||||
$collection->visible($filter);
|
||||
if ((bool) $filter) {
|
||||
$collection->visible();
|
||||
}
|
||||
break;
|
||||
case 'non-visible':
|
||||
$collection->nonVisible($filter);
|
||||
if ((bool) $filter) {
|
||||
$collection->nonVisible();
|
||||
}
|
||||
break;
|
||||
case 'modular':
|
||||
$collection->modular($filter);
|
||||
if ((bool) $filter) {
|
||||
$collection->modular();
|
||||
}
|
||||
break;
|
||||
case 'non-modular':
|
||||
$collection->nonModular($filter);
|
||||
if ((bool) $filter) {
|
||||
$collection->nonModular();
|
||||
}
|
||||
break;
|
||||
case 'routable':
|
||||
$collection->routable($filter);
|
||||
if ((bool) $filter) {
|
||||
$collection->routable();
|
||||
}
|
||||
break;
|
||||
case 'non-routable':
|
||||
$collection->nonRoutable($filter);
|
||||
if ((bool) $filter) {
|
||||
$collection->nonRoutable();
|
||||
}
|
||||
break;
|
||||
case 'type':
|
||||
$collection->ofType($filter);
|
||||
@@ -2589,11 +2647,11 @@ class Page
|
||||
|
||||
/**
|
||||
* @param string|array $value
|
||||
*
|
||||
* @param bool $only_published
|
||||
* @return mixed
|
||||
* @internal
|
||||
*/
|
||||
public function evaluate($value)
|
||||
public function evaluate($value, $only_published = true)
|
||||
{
|
||||
// Parse command.
|
||||
if (is_string($value)) {
|
||||
@@ -2662,7 +2720,7 @@ class Page
|
||||
}
|
||||
}
|
||||
|
||||
$results = $results->published();
|
||||
|
||||
break;
|
||||
|
||||
case 'page@':
|
||||
@@ -2706,16 +2764,14 @@ class Page
|
||||
$results = $page->children()->nonModular();
|
||||
}
|
||||
|
||||
$results = $results->published();
|
||||
|
||||
break;
|
||||
|
||||
case 'root@':
|
||||
case '@root':
|
||||
if (!empty($parts) && $parts[0] === 'descendants') {
|
||||
$results = $pages->all($pages->root())->nonModular()->published();
|
||||
$results = $pages->all($pages->root())->nonModular();
|
||||
} else {
|
||||
$results = $pages->root()->children()->nonModular()->published();
|
||||
$results = $pages->root()->children()->nonModular();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2732,10 +2788,14 @@ class Page
|
||||
if (!empty($parts)) {
|
||||
$params = [implode('.', $parts) => $params];
|
||||
}
|
||||
$results = $taxonomy_map->findTaxonomy($params)->published();
|
||||
$results = $taxonomy_map->findTaxonomy($params);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($only_published) {
|
||||
$results = $results->published();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
@@ -2907,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);
|
||||
}
|
||||
|
||||
@@ -469,14 +491,13 @@ class Pages
|
||||
if ($site_route) {
|
||||
$page = $this->dispatch($site_route, $all);
|
||||
} else {
|
||||
// Try Regex style redirects
|
||||
$uri = $this->grav['uri'];
|
||||
$source_url = $route;
|
||||
$extension = $uri->extension();
|
||||
if (isset($extension) && !Utils::endsWith($uri->url(), $extension)) {
|
||||
$source_url.= '.' . $extension;
|
||||
}
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
/** @var \Grav\Framework\Uri\Uri $source_url */
|
||||
$source_url = $uri->uri(false);
|
||||
|
||||
// Try Regex style redirects
|
||||
$site_redirects = $config->get("site.redirects");
|
||||
if (is_array($site_redirects)) {
|
||||
foreach ((array)$site_redirects as $pattern => $replace) {
|
||||
@@ -1008,32 +1029,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;
|
||||
@@ -1043,48 +1095,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]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1203,19 +1238,19 @@ class Pages
|
||||
break;
|
||||
case 'date':
|
||||
$list[$key] = $child->date();
|
||||
$sort_flags = SORT_NUMERIC;
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'modified':
|
||||
$list[$key] = $child->modified();
|
||||
$sort_flags = SORT_NUMERIC;
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'publish_date':
|
||||
$list[$key] = $child->publishDate();
|
||||
$sort_flags = SORT_NUMERIC;
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'unpublish_date':
|
||||
$list[$key] = $child->unpublishDate();
|
||||
$sort_flags = SORT_NUMERIC;
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'slug':
|
||||
$list[$key] = $child->slug();
|
||||
|
||||
@@ -33,7 +33,7 @@ class Plugins extends Iterator
|
||||
if (!$directory->isDir()) {
|
||||
continue;
|
||||
}
|
||||
$plugins[] = $directory->getBasename();
|
||||
$plugins[] = $directory->getFilename();
|
||||
}
|
||||
|
||||
natsort($plugins);
|
||||
|
||||
@@ -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)) {
|
||||
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();
|
||||
}
|
||||
|
||||
158
system/src/Grav/Common/Security.php
Normal file
158
system/src/Grav/Common/Security.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?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, 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)) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?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 Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\Session\Message;
|
||||
|
||||
class MessagesServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
// Define session message service.
|
||||
$container['messages'] = function ($c) {
|
||||
$session = $c['session'];
|
||||
|
||||
if (!isset($session->messages)) {
|
||||
$session->messages = new Message;
|
||||
}
|
||||
|
||||
return $session->messages;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
112
system/src/Grav/Common/Service/SessionServiceProvider.php
Normal file
112
system/src/Grav/Common/Service/SessionServiceProvider.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?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\Config\Config;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Session;
|
||||
use Grav\Common\Uri;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\Session\Message;
|
||||
|
||||
class SessionServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
// Define session service.
|
||||
$container['session'] = function ($c) {
|
||||
/** @var Config $config */
|
||||
$config = $c['config'];
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $c['uri'];
|
||||
|
||||
// Get session options.
|
||||
$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;
|
||||
if ($config->get('plugins.admin.enabled')) {
|
||||
$base = '/' . trim($config->get('plugins.admin.route'), '/');
|
||||
|
||||
// Uri::route() is not processed yet, let's quickly get what we need.
|
||||
$current_route = str_replace(Uri::filterPath($uri->rootUrl(false)), '', parse_url($uri->url(true), PHP_URL_PATH));
|
||||
|
||||
// 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) {
|
||||
$cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
|
||||
$enabled = $is_admin = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for HUGE session timeouts.
|
||||
if ($cookie_lifetime > 99999999999) {
|
||||
$cookie_lifetime = 9999999999;
|
||||
}
|
||||
|
||||
$inflector = new Inflector();
|
||||
$session_name = $inflector->hyphenize($config->get('system.session.name', 'grav_site')) . '-' . substr(md5(GRAV_ROOT), 0, 7);
|
||||
if ($is_admin && $config->get('system.session.split', true)) {
|
||||
$session_name .= '-admin';
|
||||
}
|
||||
|
||||
// Define session service.
|
||||
$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;
|
||||
};
|
||||
|
||||
// Define session message service.
|
||||
$container['messages'] = function ($c) {
|
||||
if (!isset($c['session']) || !$c['session']->isStarted()) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $c['debugger'];
|
||||
$debugger->addMessage('Inactive session: session messages may disappear', 'warming');
|
||||
|
||||
return new Message;
|
||||
}
|
||||
|
||||
/** @var Session $session */
|
||||
$session = $c['session'];
|
||||
|
||||
if (!isset($session->messages)) {
|
||||
$session->messages = new Message;
|
||||
}
|
||||
|
||||
return $session->messages;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,111 +8,123 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use RocketTheme\Toolbox\Session\Session as BaseSession;
|
||||
|
||||
class Session extends BaseSession
|
||||
class Session extends \Grav\Framework\Session\Session
|
||||
{
|
||||
protected $grav;
|
||||
protected $session;
|
||||
/** @var bool */
|
||||
protected $autoStart = false;
|
||||
|
||||
/**
|
||||
* Session constructor.
|
||||
*
|
||||
* @param Grav $grav
|
||||
* @return \Grav\Framework\Session\Session
|
||||
* @deprecated 1.5 Use getInstance() method instead
|
||||
*/
|
||||
public function __construct(Grav $grav)
|
||||
public static function instance()
|
||||
{
|
||||
$this->grav = $grav;
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getInstance() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return static::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Session init
|
||||
* Initialize session.
|
||||
*
|
||||
* Code in this function has been moved into SessionServiceProvider class.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
$config = $this->grav['config'];
|
||||
|
||||
$is_admin = false;
|
||||
$base_url = $uri->rootUrl(false);
|
||||
|
||||
$session_timeout = $config->get('system.session.timeout', 1800);
|
||||
$session_path = $config->get('system.session.path');
|
||||
if (!$session_path) {
|
||||
$session_path = '/' . ltrim(Uri::filterPath($base_url), '/');
|
||||
}
|
||||
|
||||
// Activate admin if we're inside the admin path.
|
||||
if ($config->get('plugins.admin.enabled')) {
|
||||
$route = $config->get('plugins.admin.route');
|
||||
// Uri::route() is not processed yet, let's quickly get what we need
|
||||
$current_route = str_replace(Uri::filterPath($base_url), '', parse_url($uri->url(true), PHP_URL_PATH));
|
||||
$base = '/' . trim($route, '/');
|
||||
|
||||
if (substr($current_route, 0, strlen($base)) == $base || //handle no language specified
|
||||
substr($current_route, 3, strlen($base)) == $base || //handle language (en)
|
||||
substr($current_route, 6, strlen($base)) == $base) { //handle region specific language prefix (en-US)
|
||||
$session_timeout = $config->get('plugins.admin.session.timeout', 1800);
|
||||
$is_admin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->get('system.session.enabled') || $is_admin) {
|
||||
$domain = $uri->host();
|
||||
if ($domain === 'localhost') {
|
||||
$domain = '';
|
||||
}
|
||||
|
||||
// Fix for HUGE session timeouts
|
||||
if ($session_timeout > 99999999999) {
|
||||
$session_timeout = 9999999999;
|
||||
}
|
||||
|
||||
// Define session service.
|
||||
parent::__construct($session_timeout, $session_path, $domain);
|
||||
|
||||
$secure = $config->get('system.session.secure', false);
|
||||
$httponly = $config->get('system.session.httponly', true);
|
||||
|
||||
$unique_identifier = GRAV_ROOT;
|
||||
$inflector = new Inflector();
|
||||
$session_name = $inflector->hyphenize($config->get('system.session.name', 'grav_site')) . '-' . substr(md5($unique_identifier), 0, 7);
|
||||
$split_session = $config->get('system.session.split', true);
|
||||
if ($is_admin && $split_session) {
|
||||
$session_name .= '-admin';
|
||||
}
|
||||
$this->setName($session_name);
|
||||
ini_set('session.cookie_secure', $secure);
|
||||
ini_set('session.cookie_httponly', $httponly);
|
||||
if ($this->autoStart) {
|
||||
$this->start();
|
||||
setcookie(session_name(), session_id(), $session_timeout ? time() + $session_timeout : 0, $session_path, $domain, $secure, $httponly);
|
||||
|
||||
$this->autoStart = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Store something in session temporarily
|
||||
/**
|
||||
* @param bool $auto
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoStart($auto)
|
||||
{
|
||||
$this->autoStart = (bool)$auto;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns attributes.
|
||||
*
|
||||
* @return array Attributes
|
||||
* @deprecated 1.5 Use getAll() method instead
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getAll() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session was started.
|
||||
*
|
||||
* @return Boolean
|
||||
* @deprecated 1.5 Use isStarted() method instead
|
||||
*/
|
||||
public function started()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use isStarted() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->isStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store something in session temporarily.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $object
|
||||
* @return $this
|
||||
*/
|
||||
public function setFlashObject($name, $object)
|
||||
{
|
||||
$this->$name = serialize($object);
|
||||
$this->{$name} = serialize($object);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Return object and remove it from session
|
||||
/**
|
||||
* Return object and remove it from session.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFlashObject($name)
|
||||
{
|
||||
$object = unserialize($this->$name);
|
||||
$object = unserialize($this->{$name});
|
||||
|
||||
$this->$name = null;
|
||||
$this->{$name} = null;
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
// Store something in cookie temporarily
|
||||
/**
|
||||
* Store something in cookie temporarily.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $object
|
||||
* @param int $time
|
||||
* @return $this
|
||||
*/
|
||||
public function setFlashCookieObject($name, $object, $time = 60)
|
||||
{
|
||||
setcookie($name, json_encode($object), time() + $time, '/');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Return object and remove it from the cookie
|
||||
/**
|
||||
* Return object and remove it from the cookie.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getFlashCookieObject($name)
|
||||
{
|
||||
if (isset($_COOKIE[$name])) {
|
||||
@@ -120,5 +132,7 @@ class Session extends BaseSession
|
||||
setcookie($name, '', time() - 3600, '/');
|
||||
return $object;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ class Themes extends Iterator
|
||||
continue;
|
||||
}
|
||||
|
||||
$theme = $directory->getBasename();
|
||||
$theme = $directory->getFilename();
|
||||
$result = self::get($theme);
|
||||
|
||||
if ($result) {
|
||||
|
||||
35
system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
Normal file
35
system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
class TwigNodeMarkdown extends \Twig_Node implements \Twig_NodeOutputInterface
|
||||
{
|
||||
public function __construct(\Twig_Node $body, $lineno, $tag = 'markdown')
|
||||
{
|
||||
parent::__construct(['body' => $body], [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param \Twig_Compiler A Twig_Compiler instance
|
||||
*/
|
||||
public function compile(\Twig_Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('ob_start();' . PHP_EOL)
|
||||
->subcompile($this->getNode('body'))
|
||||
->write('$content = ob_get_clean();' . PHP_EOL)
|
||||
->write('preg_match("/^\s*/", $content, $matches);' . PHP_EOL)
|
||||
->write('$lines = explode("\n", $content);' . PHP_EOL)
|
||||
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
|
||||
->write('$content = join("\n", $content);' . PHP_EOL)
|
||||
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($content);' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
class TwigNodeScript extends \Twig_Node implements \Twig_NodeOutputInterface
|
||||
class TwigNodeScript extends \Twig_Node implements \Twig_NodeCaptureInterface
|
||||
{
|
||||
protected $tagName = 'script';
|
||||
|
||||
/**
|
||||
* TwigNodeScript constructor.
|
||||
* @param \Twig_NodeInterface|null $body
|
||||
* @param \Twig_Node|null $body
|
||||
* @param \Twig_Node_Expression|null $file
|
||||
* @param \Twig_Node_Expression|null $group
|
||||
* @param \Twig_Node_Expression|null $priority
|
||||
@@ -23,12 +23,12 @@ class TwigNodeScript extends \Twig_Node implements \Twig_NodeOutputInterface
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
\Twig_NodeInterface $body = null,
|
||||
\Twig_Node $body = null,
|
||||
\Twig_Node_Expression $file = null,
|
||||
\Twig_Node_Expression $group = null,
|
||||
\Twig_Node_Expression $priority = null,
|
||||
\Twig_Node_Expression $attributes = null,
|
||||
$lineno,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
|
||||
@@ -8,24 +8,24 @@
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
class TwigNodeStyle extends \Twig_Node implements \Twig_NodeOutputInterface
|
||||
class TwigNodeStyle extends \Twig_Node implements \Twig_NodeCaptureInterface
|
||||
{
|
||||
protected $tagName = 'style';
|
||||
|
||||
/**
|
||||
* TwigNodeAssets constructor.
|
||||
* @param \Twig_NodeInterface|null $body
|
||||
* @param \Twig_Node|null $body
|
||||
* @param \Twig_Node_Expression|null $attributes
|
||||
* @param int $lineno
|
||||
* @param null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
\Twig_NodeInterface $body = null,
|
||||
\Twig_Node $body = null,
|
||||
\Twig_Node_Expression $file = null,
|
||||
\Twig_Node_Expression $group = null,
|
||||
\Twig_Node_Expression $priority = null,
|
||||
\Twig_Node_Expression $attributes = null,
|
||||
$lineno,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
|
||||
73
system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php
Normal file
73
system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
class TwigNodeSwitch extends \Twig_Node
|
||||
{
|
||||
public function __construct(
|
||||
\Twig_Node $value,
|
||||
\Twig_Node $cases,
|
||||
\Twig_Node $default = null,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(array('value' => $value, 'cases' => $cases, 'default' => $default), array(), $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param \Twig_Compiler A Twig_Compiler instance
|
||||
*/
|
||||
public function compile(\Twig_Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('switch (')
|
||||
->subcompile($this->getNode('value'))
|
||||
->raw(") {\n")
|
||||
->indent();
|
||||
|
||||
foreach ($this->getNode('cases') as $case) {
|
||||
if (!$case->hasNode('body')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($case->getNode('values') as $value) {
|
||||
$compiler
|
||||
->write('case ')
|
||||
->subcompile($value)
|
||||
->raw(":\n");
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->subcompile($case->getNode('body'))
|
||||
->write("break;\n")
|
||||
->outdent()
|
||||
->write("}\n");
|
||||
}
|
||||
|
||||
if ($this->hasNode('default') && $this->getNode('default') !== null) {
|
||||
$compiler
|
||||
->write("default:\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('default'))
|
||||
->outdent()
|
||||
->write("}\n");
|
||||
}
|
||||
|
||||
$compiler
|
||||
->outdent()
|
||||
->write("}\n");
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,12 @@ namespace Grav\Common\Twig\Node;
|
||||
|
||||
class TwigNodeTryCatch extends \Twig_Node
|
||||
{
|
||||
public function __construct(\Twig_NodeInterface $try, \Twig_NodeInterface $catch = null, $lineno, $tag = null)
|
||||
public function __construct(
|
||||
\Twig_Node $try,
|
||||
\Twig_Node $catch = null,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(array('try' => $try, 'catch' => $catch), array(), $lineno, $tag);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeMarkdown;
|
||||
|
||||
/**
|
||||
* Adds ability to inline markdown between tags.
|
||||
*
|
||||
* {% markdown %}
|
||||
* This is **bold** and this _underlined_
|
||||
*
|
||||
* 1. This is a bullet list
|
||||
* 2. This is another item in that same list
|
||||
* {% endmarkdown %}
|
||||
*/
|
||||
class TwigTokenParserMarkdown extends \Twig_TokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideMarkdownEnd'), true);
|
||||
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
return new TwigNodeMarkdown($body, $lineno, $this->getTag());
|
||||
}
|
||||
/**
|
||||
* Decide if current token marks end of Markdown block.
|
||||
*
|
||||
* @param \Twig_Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideMarkdownEnd(\Twig_Token $token)
|
||||
{
|
||||
return $token->test('endmarkdown');
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'markdown';
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class TwigTokenParserScript extends \Twig_TokenParser
|
||||
*
|
||||
* @param \Twig_Token $token A Twig_Token instance
|
||||
*
|
||||
* @return \Twig_NodeInterface A Twig_NodeInterface instance
|
||||
* @return \Twig_Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ class TwigTokenParserStyle extends \Twig_TokenParser
|
||||
*
|
||||
* @param \Twig_Token $token A Twig_Token instance
|
||||
*
|
||||
* @return \Twig_NodeInterface A Twig_NodeInterface instance
|
||||
* @return \Twig_Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
* @origin https://gist.github.com/maxgalbu/9409182
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeSwitch;
|
||||
|
||||
/**
|
||||
* Adds ability use elegant switch instead of ungainly if statements
|
||||
*
|
||||
* {% switch type %}
|
||||
* {% case 'foo' %}
|
||||
* {{ my_data.foo }}
|
||||
* {% case 'bar' %}
|
||||
* {{ my_data.bar }}
|
||||
* {% default %}
|
||||
* {{ my_data.default }}
|
||||
* {% endswitch %}
|
||||
*/
|
||||
class TwigTokenParserSwitch extends \Twig_TokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$name = $this->parser->getExpressionParser()->parseExpression();
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
// There can be some whitespace between the {% switch %} and first {% case %} tag.
|
||||
while ($stream->getCurrent()->getType() === \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
|
||||
$stream->next();
|
||||
}
|
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_START_TYPE);
|
||||
|
||||
$expressionParser = $this->parser->getExpressionParser();
|
||||
|
||||
$default = null;
|
||||
$cases = [];
|
||||
$end = false;
|
||||
|
||||
while (!$end) {
|
||||
$next = $stream->next();
|
||||
|
||||
switch ($next->getValue()) {
|
||||
case 'case':
|
||||
$values = [];
|
||||
|
||||
while (true) {
|
||||
$values[] = $expressionParser->parsePrimaryExpression();
|
||||
// Multiple allowed values?
|
||||
if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or')) {
|
||||
$stream->next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideIfFork'));
|
||||
$cases[] = new \Twig_Node([
|
||||
'values' => new \Twig_Node($values),
|
||||
'body' => $body
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'default':
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
|
||||
break;
|
||||
|
||||
case 'endswitch':
|
||||
$end = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
|
||||
}
|
||||
}
|
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeSwitch($name, new \Twig_Node($cases), $default, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide if current token marks switch logic.
|
||||
*
|
||||
* @param \Twig_Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideIfFork(\Twig_Token $token)
|
||||
{
|
||||
return $token->test(array('case', 'default', 'endswitch'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide if current token marks end of swtich block.
|
||||
*
|
||||
* @param \Twig_Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideIfEnd(\Twig_Token $token)
|
||||
{
|
||||
return $token->test(array('endswitch'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'switch';
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class TwigTokenParserTryCatch extends \Twig_TokenParser
|
||||
*
|
||||
* @param \Twig_Token $token A Twig_Token instance
|
||||
*
|
||||
* @return \Twig_NodeInterface A Twig_NodeInterface instance
|
||||
* @return \Twig_Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
|
||||
@@ -113,8 +113,15 @@ class Twig
|
||||
$params['cache'] = new \Twig_Cache_Filesystem($cachePath, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION);
|
||||
}
|
||||
|
||||
if (!empty($this->autoescape)) {
|
||||
$params['autoescape'] = $this->autoescape;
|
||||
if (!$config->get('system.strict_mode.twig_compat', true)) {
|
||||
// Force autoescape on for all files if in strict mode.
|
||||
$params['autoescape'] = 'html';
|
||||
} elseif (!empty($this->autoescape)) {
|
||||
$params['autoescape'] = $this->autoescape ? 'html' : false;
|
||||
}
|
||||
|
||||
if (empty($params['autoescape'])) {
|
||||
user_error('Grav 2.0 will have Twig auto-escaping forced on (can be emulated by turning off \'system.strict_mode.twig_compat\' setting in your configuration)', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->twig = new TwigEnvironment($loader_chain, $params);
|
||||
@@ -122,10 +129,10 @@ class Twig
|
||||
if ($config->get('system.twig.undefined_functions')) {
|
||||
$this->twig->registerUndefinedFunctionCallback(function ($name) {
|
||||
if (function_exists($name)) {
|
||||
return new \Twig_Function_Function($name);
|
||||
return new \Twig_SimpleFunction($name, $name);
|
||||
}
|
||||
|
||||
return new \Twig_Function_Function(function () {
|
||||
return new \Twig_SimpleFunction($name, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -133,10 +140,10 @@ class Twig
|
||||
if ($config->get('system.twig.undefined_filters')) {
|
||||
$this->twig->registerUndefinedFilterCallback(function ($name) {
|
||||
if (function_exists($name)) {
|
||||
return new \Twig_Filter_Function($name);
|
||||
return new \Twig_SimpleFilter($name, $name);
|
||||
}
|
||||
|
||||
return new \Twig_Filter_Function(function () {
|
||||
return new \Twig_SimpleFilter($name, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -145,7 +152,7 @@ class Twig
|
||||
|
||||
// set default date format if set in config
|
||||
if ($config->get('system.pages.dateformat.long')) {
|
||||
$this->twig->getExtension('core')->setDateFormat($config->get('system.pages.dateformat.long'));
|
||||
$this->twig->getExtension('Twig_Extension_Core')->setDateFormat($config->get('system.pages.dateformat.long'));
|
||||
}
|
||||
// enable the debug extension if required
|
||||
if ($config->get('system.twig.debug')) {
|
||||
@@ -159,7 +166,7 @@ class Twig
|
||||
$pages = $this->grav['pages'];
|
||||
|
||||
// Set some standard variables for twig
|
||||
$this->twig_vars = $this->twig_vars + [
|
||||
$this->twig_vars += [
|
||||
'config' => $config,
|
||||
'system' => $config->get('system'),
|
||||
'theme' => $config->get('theme'),
|
||||
@@ -238,7 +245,9 @@ class Twig
|
||||
// Process Modular Twig
|
||||
if ($item->modularTwig()) {
|
||||
$twig_vars['content'] = $content;
|
||||
$template = $item->template() . TEMPLATE_EXT;
|
||||
$extension = $item->templateFormat();
|
||||
$extension = $extension ? ".{$extension}.twig" : TEMPLATE_EXT;
|
||||
$template = $item->template() . $extension;
|
||||
$output = $content = $local_twig->render($template, $twig_vars);
|
||||
}
|
||||
|
||||
@@ -406,8 +415,14 @@ class Twig
|
||||
* Overrides the autoescape setting
|
||||
*
|
||||
* @param boolean $state
|
||||
* @deprecated 1.5
|
||||
*/
|
||||
public function setAutoescape($state) {
|
||||
public function setAutoescape($state)
|
||||
{
|
||||
if (!$state) {
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '(false) is deprecated since Grav 1.5', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->autoescape = (bool) $state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,17 @@ namespace Grav\Common\Twig;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Collection;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Security;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserScript;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserStyle;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserSwitch;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserTryCatch;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserMarkdown;
|
||||
use Grav\Common\User\User;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Helpers\Base32;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
@@ -61,20 +65,20 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
|
||||
new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
|
||||
new \Twig_SimpleFilter('chunk_split', [$this, 'chunkSplitFilter']),
|
||||
|
||||
new \Twig_SimpleFilter('nicenumber', [$this, 'niceNumberFunc']),
|
||||
new \Twig_SimpleFilter('nicefilesize', [$this, 'niceFilesizeFunc']),
|
||||
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFunc']),
|
||||
new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']),
|
||||
new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
|
||||
new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
|
||||
new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
|
||||
new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
|
||||
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter']),
|
||||
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]),
|
||||
new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
|
||||
new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
|
||||
new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
|
||||
new \Twig_SimpleFilter('base64_encode', [$this, 'base64EncodeFilter']),
|
||||
new \Twig_SimpleFilter('base64_decode', [$this, 'base64DecodeFilter']),
|
||||
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFilter']),
|
||||
new \Twig_SimpleFilter('randomize', [$this, 'randomizeFilter']),
|
||||
new \Twig_SimpleFilter('modulus', [$this, 'modulusFilter']),
|
||||
new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']),
|
||||
@@ -85,9 +89,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
|
||||
new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']),
|
||||
new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
|
||||
new \Twig_SimpleFilter('t', [$this, 'translate']),
|
||||
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
|
||||
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
|
||||
new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
|
||||
new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
|
||||
new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
@@ -95,6 +96,20 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new \Twig_SimpleFilter('basename', 'basename'),
|
||||
new \Twig_SimpleFilter('dirname', 'dirname'),
|
||||
new \Twig_SimpleFilter('print_r', 'print_r'),
|
||||
new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
|
||||
new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
|
||||
|
||||
// Translations
|
||||
new \Twig_SimpleFilter('t', [$this, 'translate']),
|
||||
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
|
||||
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
|
||||
|
||||
// Casting values
|
||||
new \Twig_SimpleFilter('string', [$this, 'stringFilter']),
|
||||
new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]),
|
||||
new \Twig_SimpleFilter('bool', [$this, 'boolFilter']),
|
||||
new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]),
|
||||
new \Twig_SimpleFilter('array', [$this, 'arrayFilter']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -106,7 +121,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('array', [$this, 'arrayFunc']),
|
||||
new \Twig_SimpleFunction('array', [$this, 'arrayFilter']),
|
||||
new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
|
||||
new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'),
|
||||
new \Twig_SimpleFunction('array_unique', 'array_unique'),
|
||||
@@ -117,18 +132,16 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new \Twig_SimpleFunction('vardump', [$this, 'vardumpFunc']),
|
||||
new \Twig_SimpleFunction('print_r', 'print_r'),
|
||||
new \Twig_SimpleFunction('http_response_code', 'http_response_code'),
|
||||
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true, 'needs_environment' => true]),
|
||||
new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true, 'needs_environment' => true]),
|
||||
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]),
|
||||
new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]),
|
||||
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
|
||||
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
|
||||
new \Twig_SimpleFunction('pathinfo', 'pathinfo'),
|
||||
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
|
||||
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
|
||||
new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
|
||||
new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
|
||||
new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
|
||||
new \Twig_simpleFunction('t', [$this, 'translate']),
|
||||
new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
|
||||
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
|
||||
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
|
||||
new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
|
||||
@@ -140,7 +153,16 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']),
|
||||
new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc']),
|
||||
new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc']),
|
||||
new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
|
||||
new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
|
||||
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
|
||||
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']),
|
||||
new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
|
||||
|
||||
// Translations
|
||||
new \Twig_simpleFunction('t', [$this, 'translate']),
|
||||
new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
|
||||
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -153,6 +175,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new TwigTokenParserTryCatch(),
|
||||
new TwigTokenParserScript(),
|
||||
new TwigTokenParserStyle(),
|
||||
new TwigTokenParserMarkdown(),
|
||||
new TwigTokenParserSwitch(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -181,10 +205,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
{
|
||||
$email = '';
|
||||
for ( $i = 0, $len = strlen( $str ); $i < $len; $i++ ) {
|
||||
$j = rand( 0, 1);
|
||||
if ( $j == 0 ) {
|
||||
$j = mt_rand( 0, 1);
|
||||
if ( $j === 0 ) {
|
||||
$email .= '&#' . ord( $str[$i] ) . ';';
|
||||
} elseif ( $j == 1 ) {
|
||||
} elseif ( $j === 1 ) {
|
||||
$email .= $str[$i];
|
||||
}
|
||||
}
|
||||
@@ -214,7 +238,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
$random = array_slice($original, $offset);
|
||||
shuffle($random);
|
||||
|
||||
$sizeOf = sizeof($original);
|
||||
$sizeOf = count($original);
|
||||
for ($x = 0; $x < $sizeOf; $x++) {
|
||||
if ($x < $offset) {
|
||||
$sorted[] = $original[$x];
|
||||
@@ -229,9 +253,9 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
/**
|
||||
* Returns the modulus of an integer
|
||||
*
|
||||
* @param int $number
|
||||
* @param int $divider
|
||||
* @param array $items array of items to select from to return
|
||||
* @param string|int $number
|
||||
* @param int $divider
|
||||
* @param array $items array of items to select from to return
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
@@ -246,10 +270,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
if (is_array($items)) {
|
||||
if (isset($items[$remainder])) {
|
||||
return $items[$remainder];
|
||||
} else {
|
||||
}
|
||||
|
||||
return $items[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $remainder;
|
||||
}
|
||||
@@ -279,20 +303,23 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
|
||||
$inflector = $this->grav['inflector'];
|
||||
|
||||
if (in_array(
|
||||
if (\in_array(
|
||||
$action,
|
||||
['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize']
|
||||
['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'],
|
||||
true
|
||||
)) {
|
||||
return $inflector->$action($data);
|
||||
} elseif (in_array($action, ['pluralize', 'singularize'])) {
|
||||
}
|
||||
|
||||
if (\in_array($action, ['pluralize', 'singularize'], true)) {
|
||||
if ($count) {
|
||||
return $inflector->$action($data, $count);
|
||||
} else {
|
||||
}
|
||||
|
||||
return $inflector->$action($data);
|
||||
}
|
||||
} else {
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,25 +384,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*
|
||||
* @param array $input
|
||||
* @param string $filter
|
||||
* @param array|int $direction
|
||||
* @param int $direction
|
||||
* @param int $sort_flags
|
||||
*
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
public function sortByKeyFilter(array $input, $filter, $direction = SORT_ASC)
|
||||
public function sortByKeyFilter($input, $filter, $direction = SORT_ASC, $sort_flags = SORT_REGULAR)
|
||||
{
|
||||
$output = [];
|
||||
|
||||
if (!$input) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
foreach ($input as $key => $row) {
|
||||
$output[$key] = $row[$filter];
|
||||
}
|
||||
|
||||
array_multisort($output, $direction, $input);
|
||||
|
||||
return $input;
|
||||
return Utils::sortArrayByKey($input, $filter, $direction, $sort_flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,7 +403,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function ksortFilter($array)
|
||||
{
|
||||
if (is_null($array)) {
|
||||
if (null === $array) {
|
||||
$array = [];
|
||||
}
|
||||
ksort($array);
|
||||
@@ -429,7 +445,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function nicetimeFilter($date, $long_strings = true)
|
||||
public function nicetimeFunc($date, $long_strings = true)
|
||||
{
|
||||
if (empty($date)) {
|
||||
return $this->grav['language']->translate('NICETIME.NO_DATE_PROVIDED', null, true);
|
||||
@@ -512,9 +528,29 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
if ($now == $unix_date) {
|
||||
return "{$tense}";
|
||||
}
|
||||
else {
|
||||
|
||||
return "$difference $periods[$j] {$tense}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow quick check of a string for XSS Vulnerabilities
|
||||
*
|
||||
* @param $string
|
||||
* @return bool|string|array
|
||||
*/
|
||||
public function xssFunc($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$results = Security::detectXssFromArray($data);
|
||||
} else {
|
||||
return Security::detectXss($data);
|
||||
}
|
||||
|
||||
$results_parts = array_map(function($value, $key) {
|
||||
return $key.': \''.$value . '\'';
|
||||
}, array_values($results), array_keys($results));
|
||||
|
||||
return implode(', ', $results_parts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,7 +573,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
* @param bool $block Block or Line processing
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function markdownFilter($string, $block = true)
|
||||
public function markdownFunction($string, $block = true)
|
||||
{
|
||||
$page = $this->grav['page'];
|
||||
$defaults = $this->config->get('system.pages.markdown');
|
||||
@@ -589,12 +625,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function definedDefaultFilter($value, $default = null)
|
||||
{
|
||||
if (isset($value)) {
|
||||
return $value;
|
||||
} else {
|
||||
return $default;
|
||||
return null !== $value ? $value : $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
@@ -618,6 +650,62 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
return ltrim($value, $chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts input to string.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return string
|
||||
*/
|
||||
public function stringFilter($input)
|
||||
{
|
||||
return (string) $input;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Casts input to int.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return int
|
||||
*/
|
||||
public function intFilter($input)
|
||||
{
|
||||
return (int) $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts input to bool.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return bool
|
||||
*/
|
||||
public function boolFilter($input)
|
||||
{
|
||||
return (bool) $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts input to float.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return float
|
||||
*/
|
||||
public function floatFilter($input)
|
||||
{
|
||||
return (float) $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts input to array.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return array
|
||||
*/
|
||||
public function arrayFilter($input)
|
||||
{
|
||||
return (array) $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -678,53 +766,36 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
public function urlFunc($input, $domain = false)
|
||||
{
|
||||
return Utils::url($input, $domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will evaluate Twig $twig through the $environment, and return its results.
|
||||
*
|
||||
* @param \Twig_Environment $environment
|
||||
* @param array $context
|
||||
* @param string $twig
|
||||
* @return mixed
|
||||
*/
|
||||
public function evaluateTwigFunc( \Twig_Environment $environment, $context, $twig ) {
|
||||
$loader = $environment->getLoader( );
|
||||
public function evaluateTwigFunc($context, $twig ) {
|
||||
|
||||
$parsed = $this->parseString( $environment, $context, $twig );
|
||||
$loader = new \Twig_Loader_Filesystem('.');
|
||||
$env = new \Twig_Environment($loader);
|
||||
|
||||
$environment->setLoader( $loader );
|
||||
return $parsed;
|
||||
$template = $env->createTemplate($twig);
|
||||
return $template->render($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will evaluate a $string through the $environment, and return its results.
|
||||
*
|
||||
* @param \Twig_Environment $environment
|
||||
* @param $context
|
||||
* @param $string
|
||||
* @return mixed
|
||||
*/
|
||||
public function evaluateStringFunc(\Twig_Environment $environment, $context, $string )
|
||||
public function evaluateStringFunc($context, $string )
|
||||
{
|
||||
$parsed = $this->evaluateTwigFunc($environment, $context, "{{ $string }}");
|
||||
return $parsed;
|
||||
return $this->evaluateTwigFunc($context, "{{ $string }}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parser for the environment to Twig_Loader_String, and parsed the string $string.
|
||||
*
|
||||
* @param \Twig_Environment $environment
|
||||
* @param array $context
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function parseString( \Twig_Environment $environment, $context, $string ) {
|
||||
$environment->setLoader( new \Twig_Loader_String( ) );
|
||||
return $environment->render( $string, $context );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Based on Twig_Extension_Debug / twig_var_dump
|
||||
@@ -765,7 +836,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
* Output a Gist
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $file
|
||||
* @param string|bool $file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -805,26 +876,13 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cast a value to array
|
||||
*
|
||||
* @param $value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function arrayFunc($value)
|
||||
{
|
||||
return (array)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for twig associative array initialization
|
||||
* Returns a key => val array
|
||||
*
|
||||
* @param string $key key of item
|
||||
* @param string $val value of item
|
||||
* @param string $current_array optional array to add to
|
||||
* @param array $current_array optional array to add to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -832,11 +890,11 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
{
|
||||
if (empty($current_array)) {
|
||||
return array($key => $val);
|
||||
} else {
|
||||
}
|
||||
|
||||
$current_array[$key] = $val;
|
||||
return $current_array;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for array_intersect() method
|
||||
@@ -849,10 +907,9 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
{
|
||||
if ($array1 instanceof Collection && $array2 instanceof Collection) {
|
||||
return $array1->intersect($array2);
|
||||
} else {
|
||||
return array_intersect($array1, $array2);
|
||||
}
|
||||
|
||||
return array_intersect($array1, $array2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -866,10 +923,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
{
|
||||
if (is_array($value)) { //format the array as a string
|
||||
return json_encode($value);
|
||||
} else {
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a string
|
||||
@@ -894,7 +951,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function authorize($action)
|
||||
{
|
||||
if (!$this->grav['user']->authenticated) {
|
||||
/** @var User $user */
|
||||
$user = $this->grav['user'];
|
||||
|
||||
if (!$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -902,8 +962,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
foreach ($action as $key => $perms) {
|
||||
$prefix = is_int($key) ? '' : $key . '.';
|
||||
$perms = $prefix ? (array) $perms : [$perms => true];
|
||||
foreach ($perms as $action => $authenticated) {
|
||||
if ($this->grav['user']->authorize($prefix . $action)) {
|
||||
foreach ($perms as $action2 => $authenticated) {
|
||||
if ($user->authorize($prefix . $action2)) {
|
||||
return $authenticated;
|
||||
}
|
||||
}
|
||||
@@ -970,6 +1030,18 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
return preg_replace($pattern, $replace, $subject, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig wrapper for PHP's preg_grep method
|
||||
*
|
||||
* @param $array
|
||||
* @param $regex
|
||||
* @param int $flags
|
||||
* @return array
|
||||
*/
|
||||
public function regexFilter($array, $regex, $flags = 0) {
|
||||
return preg_grep($regex, $array, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* redirect browser from twig
|
||||
*
|
||||
@@ -979,7 +1051,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
public function redirectFunc($url, $statusCode = 303)
|
||||
{
|
||||
header('Location: ' . $url, true, $statusCode);
|
||||
die();
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1006,7 +1078,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
{
|
||||
return (
|
||||
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
|
||||
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
|
||||
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1036,19 +1108,43 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
if ($exif_data) {
|
||||
if ($raw) {
|
||||
return $exif_data->getRawData();
|
||||
} else {
|
||||
}
|
||||
|
||||
return $exif_data->getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple function to read a file based on a filepath and output it
|
||||
*
|
||||
* @param $filepath
|
||||
* @return bool|string
|
||||
*/
|
||||
public function readFileFunc($filepath)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
if ($locator->isStream($filepath)) {
|
||||
$filepath = $locator->findResource($filepath);
|
||||
}
|
||||
|
||||
if (file_exists($filepath)) {
|
||||
return file_get_contents($filepath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a folder as Media and return a media object
|
||||
*
|
||||
* @param $media_dir
|
||||
* @return Media
|
||||
* @return Media|null
|
||||
*/
|
||||
public function mediaDirFunc($media_dir)
|
||||
{
|
||||
@@ -1063,6 +1159,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
return new Media($media_dir);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1075,26 +1172,72 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
var_dump($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a nicer more readable filesize based on bytes
|
||||
*
|
||||
* @param $bytes
|
||||
* @return string
|
||||
*/
|
||||
public function niceFilesizeFunc($bytes)
|
||||
{
|
||||
if ($bytes >= 1073741824)
|
||||
{
|
||||
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
|
||||
}
|
||||
elseif ($bytes >= 1048576)
|
||||
{
|
||||
$bytes = number_format($bytes / 1048576, 2) . ' MB';
|
||||
}
|
||||
elseif ($bytes >= 1024)
|
||||
{
|
||||
$bytes = number_format($bytes / 1024, 1) . ' KB';
|
||||
}
|
||||
elseif ($bytes > 1)
|
||||
{
|
||||
$bytes = $bytes . ' bytes';
|
||||
}
|
||||
elseif ($bytes == 1)
|
||||
{
|
||||
$bytes = $bytes . ' byte';
|
||||
}
|
||||
else
|
||||
{
|
||||
$bytes = '0 bytes';
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a nicer more readable number
|
||||
*
|
||||
* @param $number
|
||||
* @param int|float $n
|
||||
* @return bool|string
|
||||
*/
|
||||
public function niceNumberFunc($n)
|
||||
{
|
||||
|
||||
// first strip any formatting;
|
||||
$n = (0+str_replace(",", "", $n));
|
||||
$n = 0 + str_replace(',', '', $n);
|
||||
|
||||
// is this a number?
|
||||
if (!is_numeric($n)) return false;
|
||||
if (!is_numeric($n)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// now filter it;
|
||||
if ($n > 1000000000000) return round(($n/1000000000000), 2).' t';
|
||||
elseif ($n > 1000000000) return round(($n/1000000000), 2).' b';
|
||||
elseif ($n > 1000000) return round(($n/1000000), 2).' m';
|
||||
elseif ($n > 1000) return round(($n/1000), 2).' k';
|
||||
if ($n > 1000000000000) {
|
||||
return round(($n/1000000000000), 2).' t';
|
||||
}
|
||||
if ($n > 1000000000) {
|
||||
return round(($n/1000000000), 2).' b';
|
||||
}
|
||||
if ($n > 1000000) {
|
||||
return round(($n/1000000), 2).' m';
|
||||
}
|
||||
if ($n > 1000) {
|
||||
return round(($n/1000), 2).' k';
|
||||
}
|
||||
|
||||
return number_format($n);
|
||||
}
|
||||
@@ -1103,11 +1246,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
* Get a theme variable
|
||||
*
|
||||
* @param $var
|
||||
* @param bool $default
|
||||
* @return string
|
||||
*/
|
||||
public function themeVarFunc($var)
|
||||
public function themeVarFunc($var, $default = null)
|
||||
{
|
||||
return $this->config->get('theme.' . $var, false) ?: '';
|
||||
$header = $this->grav['page']->header();
|
||||
$header_classes = isset($header->$var) ? $header->$var : null;
|
||||
return $header_classes ?: $this->config->get('theme.' . $var, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1126,10 +1272,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
foreach ((array)$classes as $class) {
|
||||
if (!empty($body_classes) && Utils::contains($body_classes, $class)) {
|
||||
continue;
|
||||
} else {
|
||||
$val = $this->config->get('theme.' . $class, false) ? $class : false;
|
||||
$body_classes .= $val ? ' ' . $val : '';
|
||||
}
|
||||
|
||||
$val = $this->config->get('theme.' . $class, false) ? $class : false;
|
||||
$body_classes .= $val ? ' ' . $val : '';
|
||||
}
|
||||
|
||||
return $body_classes;
|
||||
@@ -1166,5 +1312,30 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump/Encode data into YAML format
|
||||
*
|
||||
* @param $data
|
||||
* @param $inline integer number of levels of inline syntax
|
||||
* @return mixed
|
||||
*/
|
||||
public function yamlEncodeFilter($data, $inline = 10)
|
||||
{
|
||||
return Yaml::dump($data, $inline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode/Parse data from YAML format
|
||||
*
|
||||
* @param $data
|
||||
* @return mixed
|
||||
*/
|
||||
public function yamlDecodeFilter($data)
|
||||
{
|
||||
return Yaml::parse($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,25 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Framework\Route\RouteFactory;
|
||||
use Grav\Framework\Uri\UriFactory;
|
||||
use Grav\Framework\Uri\UriPartsFilter;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class Uri
|
||||
{
|
||||
const HOSTNAME_REGEX = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/';
|
||||
|
||||
/** @var \Grav\Framework\Uri\Uri */
|
||||
protected static $currentUri;
|
||||
|
||||
/** @var \Grav\Framework\Route\Route */
|
||||
protected static $currentRoute;
|
||||
|
||||
public $url;
|
||||
|
||||
// Uri parts.
|
||||
@@ -38,6 +51,8 @@ class Uri
|
||||
protected $root;
|
||||
protected $root_path;
|
||||
protected $uri;
|
||||
protected $content_type;
|
||||
protected $post;
|
||||
|
||||
/**
|
||||
* Uri constructor.
|
||||
@@ -52,114 +67,6 @@ class Uri
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $env
|
||||
*/
|
||||
protected function createFromEnvironment(array $env)
|
||||
{
|
||||
// Build scheme.
|
||||
if (isset($env['REQUEST_SCHEME'])) {
|
||||
$this->scheme = $env['REQUEST_SCHEME'];
|
||||
} else {
|
||||
$https = isset($env['HTTPS']) ? $env['HTTPS'] : '';
|
||||
$this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
|
||||
}
|
||||
|
||||
// Build user and password.
|
||||
$this->user = isset($env['PHP_AUTH_USER']) ? $env['PHP_AUTH_USER'] : null;
|
||||
$this->password = isset($env['PHP_AUTH_PW']) ? $env['PHP_AUTH_PW'] : null;
|
||||
|
||||
// Build host.
|
||||
$hostname = 'localhost';
|
||||
if (isset($env['HTTP_HOST'])) {
|
||||
$hostname = $env['HTTP_HOST'];
|
||||
} elseif (isset($env['SERVER_NAME'])) {
|
||||
$hostname = $env['SERVER_NAME'];
|
||||
}
|
||||
// Remove port from HTTP_HOST generated $hostname
|
||||
$hostname = Utils::substrToString($hostname, ':');
|
||||
// Validate the hostname
|
||||
$this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
|
||||
|
||||
// Build port.
|
||||
$this->port = isset($env['SERVER_PORT']) ? (int)$env['SERVER_PORT'] : null;
|
||||
if ($this->hasStandardPort()) {
|
||||
$this->port = null;
|
||||
}
|
||||
|
||||
// Build path.
|
||||
$request_uri = isset($env['REQUEST_URI']) ? $env['REQUEST_URI'] : '';
|
||||
$this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH));
|
||||
|
||||
// Build query string.
|
||||
$this->query = isset($env['QUERY_STRING']) ? $env['QUERY_STRING'] : '';
|
||||
if ($this->query === '') {
|
||||
$this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
|
||||
}
|
||||
|
||||
// Support ngnix routes.
|
||||
if (strpos($this->query, '_url=') === 0) {
|
||||
parse_str($this->query, $query);
|
||||
unset($query['_url']);
|
||||
$this->query = http_build_query($query);
|
||||
}
|
||||
|
||||
// Build fragment.
|
||||
$this->fragment = null;
|
||||
|
||||
// Filter userinfo, path and query string.
|
||||
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
|
||||
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
|
||||
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
|
||||
$this->query = static::filterQuery($this->query);
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this Uri use a standard port?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasStandardPort()
|
||||
{
|
||||
return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*/
|
||||
protected function createFromString($url)
|
||||
{
|
||||
// Set Uri parts.
|
||||
$parts = parse_url($url);
|
||||
if ($parts === false) {
|
||||
throw new \RuntimeException('Malformed URL: ' . $url);
|
||||
}
|
||||
$this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null;
|
||||
$this->user = isset($parts['user']) ? $parts['user'] : null;
|
||||
$this->password = isset($parts['pass']) ? $parts['pass'] : null;
|
||||
$this->host = isset($parts['host']) ? $parts['host'] : null;
|
||||
$this->port = isset($parts['port']) ? (int)$parts['port'] : null;
|
||||
$this->path = isset($parts['path']) ? $parts['path'] : '';
|
||||
$this->query = isset($parts['query']) ? $parts['query'] : '';
|
||||
$this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null;
|
||||
|
||||
// Validate the hostname
|
||||
if ($this->host) {
|
||||
$this->host = $this->validateHostname($this->host) ? $this->host : 'unknown';
|
||||
}
|
||||
|
||||
// Filter userinfo, path, query string and fragment.
|
||||
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
|
||||
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
|
||||
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
|
||||
$this->query = static::filterQuery($this->query);
|
||||
$this->fragment = $this->fragment !== null ? static::filterQuery($this->fragment) : null;
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the URI class with a url passed via parameter.
|
||||
* Used for testing purposes.
|
||||
@@ -193,33 +100,6 @@ class Uri
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function reset()
|
||||
{
|
||||
// resets
|
||||
parse_str($this->query, $this->queries);
|
||||
$this->extension = null;
|
||||
$this->basename = null;
|
||||
$this->paths = [];
|
||||
$this->params = [];
|
||||
$this->env = $this->buildEnvironment();
|
||||
$this->uri = $this->path . (!empty($this->query) ? '?' . $this->query : '');
|
||||
|
||||
$this->base = $this->buildBaseUrl();
|
||||
$this->root_path = $this->buildRootPath();
|
||||
$this->root = $this->base . $this->root_path;
|
||||
$this->url = $this->base . $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the parameter regex based on the param_sep setting
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function paramsRegex()
|
||||
{
|
||||
return '/\/([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a hostname
|
||||
*
|
||||
@@ -232,45 +112,6 @@ class Uri
|
||||
return (bool)preg_match(static::HOSTNAME_REGEX, $hostname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URI with port if needed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function buildBaseUrl()
|
||||
{
|
||||
return $this->scheme() . $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Grav Root Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function buildRootPath()
|
||||
{
|
||||
// In Windows script path uses backslash, convert it:
|
||||
$scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']);
|
||||
$rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/'));
|
||||
|
||||
// check if userdir in the path and workaround PHP bug with PHP_SELF
|
||||
if (strpos($this->uri, '/~') !== false && strpos($scriptPath, '/~') === false) {
|
||||
$rootPath = substr($this->uri, 0, strpos($this->uri, '/', 1)) . $rootPath;
|
||||
}
|
||||
|
||||
return $rootPath;
|
||||
}
|
||||
|
||||
private function buildEnvironment()
|
||||
{
|
||||
// check for localhost variations
|
||||
if ($this->host === '127.0.0.1' || $this->host === '::1') {
|
||||
return 'localhost';
|
||||
}
|
||||
|
||||
return $this->host ?: 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the URI object based on the url set on the object
|
||||
*/
|
||||
@@ -278,7 +119,10 @@ class Uri
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
// add the port to the base for non-standard ports
|
||||
@@ -293,7 +137,12 @@ class Uri
|
||||
$custom_parts = parse_url($custom_base);
|
||||
$orig_root_path = $this->root_path;
|
||||
$this->root_path = isset($custom_parts['path']) ? rtrim($custom_parts['path'], '/') : '';
|
||||
$this->root = isset($custom_parts['scheme']) ? $custom_base : $this->base . $this->root_path;
|
||||
if (isset($custom_parts['scheme'])) {
|
||||
$this->base = $custom_parts['scheme'] . '://' . $custom_parts['host'];
|
||||
$this->root = $custom_base;
|
||||
} else {
|
||||
$this->root = $this->base . $this->root_path;
|
||||
}
|
||||
$this->uri = Utils::replaceFirstOccurrence($orig_root_path, $this->root_path, $this->uri);
|
||||
} else {
|
||||
$this->root = $this->base . $this->root_path;
|
||||
@@ -306,13 +155,7 @@ class Uri
|
||||
// remove the setup.php based base if set:
|
||||
$setup_base = $grav['pages']->base();
|
||||
if ($setup_base) {
|
||||
$uri = str_replace($setup_base, '', $uri);
|
||||
}
|
||||
|
||||
// If configured to, redirect trailing slash URI's with a 302 redirect
|
||||
$redirect = str_replace($this->root, '', rtrim($uri, '/'));
|
||||
if ($redirect && $uri !== '/' && $redirect !== $this->base() && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($uri, '/')) {
|
||||
$grav->redirect($redirect, 302);
|
||||
$uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri);
|
||||
}
|
||||
|
||||
// process params
|
||||
@@ -359,34 +202,12 @@ class Uri
|
||||
}
|
||||
|
||||
// Set some Grav stuff
|
||||
$grav['base_url_absolute'] = $grav['config']->get('system.custom_base_url') ?: $this->rootUrl(true);
|
||||
$grav['base_url_absolute'] = $config->get('system.custom_base_url') ?: $this->rootUrl(true);
|
||||
$grav['base_url_relative'] = $this->rootUrl(false);
|
||||
$grav['base_url'] = $grav['config']->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
|
||||
}
|
||||
$grav['base_url'] = $config->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
|
||||
|
||||
/**
|
||||
* Process any params based in this URL, supports any valid delimiter
|
||||
*
|
||||
* @param $uri
|
||||
* @param string $delimiter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function processParams($uri, $delimiter = ':')
|
||||
{
|
||||
if (strpos($uri, $delimiter) !== false) {
|
||||
preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$param = explode($delimiter, $match[1]);
|
||||
if (count($param) === 2) {
|
||||
$plain_var = filter_var($param[1], FILTER_SANITIZE_STRING);
|
||||
$this->params[$param[0]] = $plain_var;
|
||||
$uri = str_replace($match[0], '', $uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $uri;
|
||||
RouteFactory::setRoot($this->root_path);
|
||||
RouteFactory::setLanguage($language->getLanguageURLPrefix());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,6 +372,17 @@ class Uri
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function method()
|
||||
{
|
||||
$method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
|
||||
|
||||
if ($method === 'POST' && isset($_SERVER['X-HTTP-METHOD-OVERRIDE'])) {
|
||||
$method = strtoupper($_SERVER['X-HTTP-METHOD-OVERRIDE']);
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scheme of the URI
|
||||
*
|
||||
@@ -646,6 +478,21 @@ class Uri
|
||||
return $this->basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full uri
|
||||
*
|
||||
* @param bool $include_root
|
||||
* @return mixed
|
||||
*/
|
||||
public function uri($include_root = true)
|
||||
{
|
||||
if ($include_root) {
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
return str_replace($this->root_path, '', $this->uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base of the URI
|
||||
*
|
||||
@@ -666,16 +513,10 @@ class Uri
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
// Link processing should prepend language
|
||||
$language = $grav['language'];
|
||||
$language_append = '';
|
||||
if ($language->enabled()) {
|
||||
$language_append = $language->getLanguageURLPrefix();
|
||||
}
|
||||
/** @var Pages $pages */
|
||||
$pages = $grav['pages'];
|
||||
|
||||
$base = $grav['base_url_relative'];
|
||||
|
||||
return rtrim($base . $grav['pages']->base(), '/') . $language_append;
|
||||
return $pages->baseUrl(null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -737,6 +578,36 @@ class Uri
|
||||
return substr($referrer, strlen($root));
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return static::buildUrl($this->toArray());
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'scheme' => $this->scheme,
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
'user' => $this->user,
|
||||
'pass' => $this->password,
|
||||
'path' => $this->path,
|
||||
'params' => $this->params,
|
||||
'query' => $this->query,
|
||||
'fragment' => $this->fragment
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the parameter regex based on the param_sep setting
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function paramsRegex()
|
||||
{
|
||||
return '/\/([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IP address of the current user
|
||||
*
|
||||
@@ -761,7 +632,35 @@ class Uri
|
||||
}
|
||||
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current Uri.
|
||||
*
|
||||
* @return \Grav\Framework\Uri\Uri
|
||||
*/
|
||||
public static function getCurrentUri()
|
||||
{
|
||||
if (!static::$currentUri) {
|
||||
static::$currentUri = UriFactory::createFromEnvironment($_SERVER);
|
||||
}
|
||||
|
||||
return static::$currentUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current route.
|
||||
*
|
||||
* @return \Grav\Framework\Route\Route
|
||||
*/
|
||||
public static function getCurrentRoute()
|
||||
{
|
||||
if (!static::$currentRoute) {
|
||||
$uri = Grav::instance()['uri'];
|
||||
static::$currentRoute = RouteFactory::createFromParts($uri->toArray());
|
||||
}
|
||||
|
||||
return static::$currentRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -776,26 +675,6 @@ class Uri
|
||||
return Utils::startsWith($url, 'http');
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return static::buildUrl($this->toArray());
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'scheme' => $this->scheme,
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
'user' => $this->user,
|
||||
'pass' => $this->password,
|
||||
'path' => $this->path,
|
||||
'params' => $this->params,
|
||||
'query' => $this->query,
|
||||
'fragment' => $this->fragment
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The opposite of built-in PHP method parse_url()
|
||||
*
|
||||
@@ -805,18 +684,19 @@ class Uri
|
||||
*/
|
||||
public static function buildUrl($parsed_url)
|
||||
{
|
||||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : (isset($parsed_url['host']) ? '//' : '');
|
||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||||
$pass = ($user || $pass) ? "{$pass}@" : '';
|
||||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||
$path = !empty($parsed_url['params']) ? rtrim($path, '/') . static::buildParams($parsed_url['params']) : $path;
|
||||
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
|
||||
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . ':' : '';
|
||||
$authority = isset($parsed_url['host']) ? '//' : '';
|
||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||||
$pass = ($user || $pass) ? "{$pass}@" : '';
|
||||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||
$path = !empty($parsed_url['params']) ? rtrim($path, '/') . static::buildParams($parsed_url['params']) : $path;
|
||||
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
|
||||
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||||
|
||||
return "{$scheme}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
|
||||
return "{$scheme}{$authority}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1001,7 +881,26 @@ class Uri
|
||||
public static function parseUrl($url)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$parts = parse_url($url);
|
||||
|
||||
$encodedUrl = preg_replace_callback(
|
||||
'%[^:/@?&=#]+%usD',
|
||||
function ($matches) { return rawurlencode($matches[0]); },
|
||||
$url
|
||||
);
|
||||
|
||||
$parts = parse_url($encodedUrl);
|
||||
|
||||
if (false === $parts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($parts as $name => $value) {
|
||||
$parts[$name] = rawurldecode($value);
|
||||
}
|
||||
|
||||
if (!isset($parts['path'])) {
|
||||
$parts['path'] = '';
|
||||
}
|
||||
|
||||
list($stripped_path, $params) = static::extractParams($parts['path'], $grav['config']->get('system.param_sep'));
|
||||
|
||||
@@ -1200,13 +1099,7 @@ class Uri
|
||||
*/
|
||||
public static function filterUserInfo($info)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=]+|%(?![A-Fa-f0-9]{2}))/u',
|
||||
function ($match) {
|
||||
return rawurlencode($match[0]);
|
||||
},
|
||||
$info
|
||||
);
|
||||
return $info !== null ? UriPartsFilter::filterUserInfo($info) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1223,13 +1116,7 @@ class Uri
|
||||
*/
|
||||
public static function filterPath($path)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u',
|
||||
function ($match) {
|
||||
return rawurlencode($match[0]);
|
||||
},
|
||||
$path
|
||||
);
|
||||
return $path !== null ? UriPartsFilter::filterPath($path) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1240,12 +1127,254 @@ class Uri
|
||||
*/
|
||||
public static function filterQuery($query)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u',
|
||||
function ($match) {
|
||||
return rawurlencode($match[0]);
|
||||
},
|
||||
$query
|
||||
);
|
||||
return $query !== null ? UriPartsFilter::filterQueryOrFragment($query) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $env
|
||||
*/
|
||||
protected function createFromEnvironment(array $env)
|
||||
{
|
||||
// Build scheme.
|
||||
if (isset($env['HTTP_X_FORWARDED_PROTO'])) {
|
||||
$this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
|
||||
} elseif (isset($env['X-FORWARDED-PROTO'])) {
|
||||
$this->scheme = $env['X-FORWARDED-PROTO'];
|
||||
} elseif (isset($env['REQUEST_SCHEME'])) {
|
||||
$this->scheme = $env['REQUEST_SCHEME'];
|
||||
} else {
|
||||
$https = isset($env['HTTPS']) ? $env['HTTPS'] : '';
|
||||
$this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
|
||||
}
|
||||
|
||||
// Build user and password.
|
||||
$this->user = isset($env['PHP_AUTH_USER']) ? $env['PHP_AUTH_USER'] : null;
|
||||
$this->password = isset($env['PHP_AUTH_PW']) ? $env['PHP_AUTH_PW'] : null;
|
||||
|
||||
// Build host.
|
||||
$hostname = 'localhost';
|
||||
if (isset($env['HTTP_HOST'])) {
|
||||
$hostname = $env['HTTP_HOST'];
|
||||
} elseif (isset($env['SERVER_NAME'])) {
|
||||
$hostname = $env['SERVER_NAME'];
|
||||
}
|
||||
// Remove port from HTTP_HOST generated $hostname
|
||||
$hostname = Utils::substrToString($hostname, ':');
|
||||
// Validate the hostname
|
||||
$this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
|
||||
|
||||
// Build port.
|
||||
if (isset($env['HTTP_X_FORWARDED_PORT'])) {
|
||||
$this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
|
||||
} elseif (isset($env['X-FORWARDED-PORT'])) {
|
||||
$this->port = (int)$env['X-FORWARDED-PORT'];
|
||||
} elseif (isset($env['SERVER_PORT'])) {
|
||||
$this->port = (int)$env['SERVER_PORT'];
|
||||
} else {
|
||||
$this->port = null;
|
||||
}
|
||||
|
||||
if ($this->hasStandardPort()) {
|
||||
$this->port = null;
|
||||
}
|
||||
|
||||
// Build path.
|
||||
$request_uri = isset($env['REQUEST_URI']) ? $env['REQUEST_URI'] : '';
|
||||
$this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH));
|
||||
|
||||
// Build query string.
|
||||
$this->query = isset($env['QUERY_STRING']) ? $env['QUERY_STRING'] : '';
|
||||
if ($this->query === '') {
|
||||
$this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
|
||||
}
|
||||
|
||||
// Support ngnix routes.
|
||||
if (strpos($this->query, '_url=') === 0) {
|
||||
parse_str($this->query, $query);
|
||||
unset($query['_url']);
|
||||
$this->query = http_build_query($query);
|
||||
}
|
||||
|
||||
// Build fragment.
|
||||
$this->fragment = null;
|
||||
|
||||
// Filter userinfo, path and query string.
|
||||
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
|
||||
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
|
||||
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
|
||||
$this->query = static::filterQuery($this->query);
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this Uri use a standard port?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasStandardPort()
|
||||
{
|
||||
return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*/
|
||||
protected function createFromString($url)
|
||||
{
|
||||
// Set Uri parts.
|
||||
$parts = parse_url($url);
|
||||
if ($parts === false) {
|
||||
throw new \RuntimeException('Malformed URL: ' . $url);
|
||||
}
|
||||
$this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null;
|
||||
$this->user = isset($parts['user']) ? $parts['user'] : null;
|
||||
$this->password = isset($parts['pass']) ? $parts['pass'] : null;
|
||||
$this->host = isset($parts['host']) ? $parts['host'] : null;
|
||||
$this->port = isset($parts['port']) ? (int)$parts['port'] : null;
|
||||
$this->path = isset($parts['path']) ? $parts['path'] : '';
|
||||
$this->query = isset($parts['query']) ? $parts['query'] : '';
|
||||
$this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null;
|
||||
|
||||
// Validate the hostname
|
||||
if ($this->host) {
|
||||
$this->host = $this->validateHostname($this->host) ? $this->host : 'unknown';
|
||||
}
|
||||
// Filter userinfo, path, query string and fragment.
|
||||
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
|
||||
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
|
||||
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
|
||||
$this->query = static::filterQuery($this->query);
|
||||
$this->fragment = $this->fragment !== null ? static::filterQuery($this->fragment) : null;
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
protected function reset()
|
||||
{
|
||||
// resets
|
||||
parse_str($this->query, $this->queries);
|
||||
$this->extension = null;
|
||||
$this->basename = null;
|
||||
$this->paths = [];
|
||||
$this->params = [];
|
||||
$this->env = $this->buildEnvironment();
|
||||
$this->uri = $this->path . (!empty($this->query) ? '?' . $this->query : '');
|
||||
|
||||
$this->base = $this->buildBaseUrl();
|
||||
$this->root_path = $this->buildRootPath();
|
||||
$this->root = $this->base . $this->root_path;
|
||||
$this->url = $this->base . $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's post from either $_POST or JSON response object
|
||||
* By default returns all data, or can return a single item
|
||||
*
|
||||
* @param string $element
|
||||
* @param string $filter_type
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
public function post($element = null, $filter_type = null)
|
||||
{
|
||||
if (!$this->post) {
|
||||
$content_type = $this->getContentType();
|
||||
if ($content_type === 'application/json') {
|
||||
$json = file_get_contents('php://input');
|
||||
$this->post = json_decode($json, true);
|
||||
} elseif (!empty($_POST)) {
|
||||
$this->post = (array)$_POST;
|
||||
}
|
||||
|
||||
$event = new Event(['post' => &$this->post]);
|
||||
Grav::instance()->fireEvent('onHttpPostFilter', $event);
|
||||
}
|
||||
|
||||
if ($this->post && null !== $element) {
|
||||
$item = Utils::getDotNotation($this->post, $element);
|
||||
if ($filter_type) {
|
||||
$item = filter_var($item, $filter_type);
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content type from request
|
||||
*
|
||||
* @param bool $short
|
||||
* @return null|string
|
||||
*/
|
||||
private function getContentType($short = true)
|
||||
{
|
||||
if (isset($_SERVER['CONTENT_TYPE'])) {
|
||||
$content_type = $_SERVER['CONTENT_TYPE'];
|
||||
if ($short) {
|
||||
return Utils::substrToString($content_type,';');
|
||||
}
|
||||
return $content_type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URI with port if needed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function buildBaseUrl()
|
||||
{
|
||||
return $this->scheme() . $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Grav Root Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function buildRootPath()
|
||||
{
|
||||
// In Windows script path uses backslash, convert it:
|
||||
$scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']);
|
||||
$rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/'));
|
||||
|
||||
return $rootPath;
|
||||
}
|
||||
|
||||
private function buildEnvironment()
|
||||
{
|
||||
// check for localhost variations
|
||||
if ($this->host === '127.0.0.1' || $this->host === '::1') {
|
||||
return 'localhost';
|
||||
}
|
||||
|
||||
return $this->host ?: 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process any params based in this URL, supports any valid delimiter
|
||||
*
|
||||
* @param $uri
|
||||
* @param string $delimiter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function processParams($uri, $delimiter = ':')
|
||||
{
|
||||
if (strpos($uri, $delimiter) !== false) {
|
||||
preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$param = explode($delimiter, $match[1]);
|
||||
if (count($param) === 2) {
|
||||
$plain_var = filter_var($param[1], FILTER_SANITIZE_STRING);
|
||||
$this->params[$param[0]] = $plain_var;
|
||||
$uri = str_replace($match[0], '', $uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ class User extends Data
|
||||
$files = $account_dir ? array_diff(scandir($account_dir), ['.', '..']) : [];
|
||||
|
||||
// Try with username first, you never know!
|
||||
if (in_array('username', $fields)) {
|
||||
if (in_array('username', $fields, true)) {
|
||||
$user = User::load($query);
|
||||
unset($fields[array_search('username', $fields)]);
|
||||
unset($fields[array_search('username', $fields, true)]);
|
||||
} else {
|
||||
$user = User::load('');
|
||||
}
|
||||
@@ -95,11 +95,41 @@ class User extends Data
|
||||
public static function remove($username)
|
||||
{
|
||||
$file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT);
|
||||
if ($file_path && unlink($file_path)) {
|
||||
return true;
|
||||
|
||||
return $file_path && unlink($file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
$value = parent::offsetExists($offset);
|
||||
|
||||
// Handle special case where user was logged in before 'authorized' was added to the user object.
|
||||
if (false === $value && $offset === 'authorized') {
|
||||
$value = $this->offsetExists('authenticated');
|
||||
}
|
||||
|
||||
return false;
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
$value = parent::offsetGet($offset);
|
||||
|
||||
// Handle special case where user was logged in before 'authorized' was added to the user object.
|
||||
if (null === $value && $offset === 'authorized') {
|
||||
$value = $this->offsetGet('authenticated');
|
||||
$this->offsetSet($offset, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,6 +266,8 @@ class User extends Data
|
||||
*/
|
||||
public function authorise($action)
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use authorize() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->authorize($action);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,20 @@ abstract class Utils
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
// Get relative path to the resource (or false if not found).
|
||||
$resource = $locator->findResource($input, false);
|
||||
$parts = Uri::parseUrl($input);
|
||||
|
||||
if ($parts) {
|
||||
$resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
|
||||
|
||||
if (isset($parts['query'])) {
|
||||
$resource = $resource . '?' . $parts['query'];
|
||||
}
|
||||
} else {
|
||||
// Not a valid URL (can still be a stream).
|
||||
$resource = $locator->findResource($input, false);
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
$resource = $input;
|
||||
}
|
||||
@@ -262,7 +274,7 @@ abstract class Utils
|
||||
// is $break present between $limit and the end of the string?
|
||||
if ($up_to_break && false !== ($breakpoint = mb_strpos($string, $break, $limit))) {
|
||||
if ($breakpoint < mb_strlen($string) - 1) {
|
||||
$string = mb_substr($string, 0, $breakpoint) . $break;
|
||||
$string = mb_substr($string, 0, $breakpoint) . $pad;
|
||||
}
|
||||
} else {
|
||||
$string = mb_substr($string, 0, $limit) . $pad;
|
||||
@@ -296,10 +308,6 @@ abstract class Utils
|
||||
*/
|
||||
public static function truncateHtml($text, $length = 100, $ellipsis = '...')
|
||||
{
|
||||
if (mb_strlen($text) <= $length) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return Truncator::truncateLetters($text, $length, $ellipsis);
|
||||
}
|
||||
|
||||
@@ -470,6 +478,51 @@ abstract class Utils
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename
|
||||
*
|
||||
* @param string $filename Filename or path to file
|
||||
* @param string $default default value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMimeByFilename($filename, $default = 'application/octet-stream')
|
||||
{
|
||||
return static::getMimeByExtension(pathinfo($filename, PATHINFO_EXTENSION), $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on existing local file
|
||||
*
|
||||
* @param string $filename Path to the file
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public static function getMimeByLocalFile($filename, $default = 'application/octet-stream')
|
||||
{
|
||||
$type = false;
|
||||
|
||||
// For local files we can detect type by the file content.
|
||||
if (!stream_is_local($filename) || !file_exists($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prefer using finfo if it exists.
|
||||
if (\extension_loaded('fileinfo')) {
|
||||
$finfo = finfo_open(FILEINFO_SYMLINK | FILEINFO_MIME_TYPE);
|
||||
$type = finfo_file($finfo, $filename);
|
||||
finfo_close($finfo);
|
||||
} else {
|
||||
// Fall back to use getimagesize() if it is available (not recommended, but better than nothing)
|
||||
$info = @getimagesize($filename);
|
||||
if ($info) {
|
||||
$type = $info['mime'];
|
||||
}
|
||||
}
|
||||
|
||||
return $type ?: static::getMimeByFilename($filename, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename extension
|
||||
*
|
||||
@@ -512,6 +565,33 @@ abstract class Utils
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if filename is considered safe.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkFilename($filename)
|
||||
{
|
||||
$dangerous_extensions = Grav::instance()['config']->get('security.uploads_dangerous_extensions', []);
|
||||
array_walk($dangerous_extensions, function(&$val) {
|
||||
$val = '.' . $val;
|
||||
});
|
||||
|
||||
$extension = '.' . pathinfo($filename, PATHINFO_EXTENSION);
|
||||
|
||||
return !(
|
||||
// Empty filenames are not allowed.
|
||||
!$filename
|
||||
// Filename should not contain horizontal/vertical tabs, newlines, nils or back/forward slashes.
|
||||
|| strtr($filename, "\t\v\n\r\0\\/", '_______') !== $filename
|
||||
// Filename should not start or end with dot or space.
|
||||
|| trim($filename, '. ') !== $filename
|
||||
// Filename should not contain .php in it.
|
||||
|| static::contains($extension, $dangerous_extensions)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path by processing relative `.` and `..` syntax and merging path
|
||||
*
|
||||
@@ -688,6 +768,8 @@ abstract class Utils
|
||||
*/
|
||||
public static function resolve(array $array, $path, $default = null)
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDotNotation() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return static::getDotNotation($array, $path, $default);
|
||||
}
|
||||
|
||||
@@ -709,11 +791,11 @@ abstract class Utils
|
||||
* with reverse proxy setups.
|
||||
*
|
||||
* @param string $action
|
||||
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
*
|
||||
* @return string the nonce string
|
||||
*/
|
||||
private static function generateNonceString($action, $plusOneTick = false)
|
||||
private static function generateNonceString($action, $previousTick = false)
|
||||
{
|
||||
$username = '';
|
||||
if (isset(Grav::instance()['user'])) {
|
||||
@@ -724,29 +806,8 @@ abstract class Utils
|
||||
$token = session_id();
|
||||
$i = self::nonceTick();
|
||||
|
||||
if ($plusOneTick) {
|
||||
$i++;
|
||||
}
|
||||
|
||||
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
|
||||
}
|
||||
|
||||
//Added in version 1.0.8 to ensure that existing nonces are not broken.
|
||||
private static function generateNonceStringOldStyle($action, $plusOneTick = false)
|
||||
{
|
||||
if (isset(Grav::instance()['user'])) {
|
||||
$user = Grav::instance()['user'];
|
||||
$username = $user->username;
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
$username .= $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
} else {
|
||||
$username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
|
||||
}
|
||||
$token = session_id();
|
||||
$i = self::nonceTick();
|
||||
if ($plusOneTick) {
|
||||
$i++;
|
||||
if ($previousTick) {
|
||||
$i--;
|
||||
}
|
||||
|
||||
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
|
||||
@@ -772,33 +833,20 @@ abstract class Utils
|
||||
* action is the same for 12 hours.
|
||||
*
|
||||
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
|
||||
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
*
|
||||
* @return string the nonce
|
||||
*/
|
||||
public static function getNonce($action, $plusOneTick = false)
|
||||
public static function getNonce($action, $previousTick = false)
|
||||
{
|
||||
// Don't regenerate this again if not needed
|
||||
if (isset(static::$nonces[$action])) {
|
||||
return static::$nonces[$action];
|
||||
if (isset(static::$nonces[$action][$previousTick])) {
|
||||
return static::$nonces[$action][$previousTick];
|
||||
}
|
||||
$nonce = md5(self::generateNonceString($action, $plusOneTick));
|
||||
static::$nonces[$action] = $nonce;
|
||||
$nonce = md5(self::generateNonceString($action, $previousTick));
|
||||
static::$nonces[$action][$previousTick] = $nonce;
|
||||
|
||||
return static::$nonces[$action];
|
||||
}
|
||||
|
||||
//Added in version 1.0.8 to ensure that existing nonces are not broken.
|
||||
public static function getNonceOldStyle($action, $plusOneTick = false)
|
||||
{
|
||||
// Don't regenerate this again if not needed
|
||||
if (isset(static::$nonces[$action])) {
|
||||
return static::$nonces[$action];
|
||||
}
|
||||
$nonce = md5(self::generateNonceStringOldStyle($action, $plusOneTick));
|
||||
static::$nonces[$action] = $nonce;
|
||||
|
||||
return static::$nonces[$action];
|
||||
return static::$nonces[$action][$previousTick];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -822,20 +870,8 @@ abstract class Utils
|
||||
}
|
||||
|
||||
//Nonce generated 12-24 hours ago
|
||||
$plusOneTick = true;
|
||||
if ($nonce === self::getNonce($action, $plusOneTick)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Added in version 1.0.8 to ensure that existing nonces are not broken.
|
||||
//Nonce generated 0-12 hours ago
|
||||
if ($nonce === self::getNonceOldStyle($action)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Nonce generated 12-24 hours ago
|
||||
$plusOneTick = true;
|
||||
if ($nonce === self::getNonceOldStyle($action, $plusOneTick)) {
|
||||
$previousTick = true;
|
||||
if ($nonce === self::getNonce($action, $previousTick)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -966,6 +1002,32 @@ abstract class Utils
|
||||
return $ordered + $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array by a key value in the array
|
||||
*
|
||||
* @param $array
|
||||
* @param $array_key
|
||||
* @param int $direction
|
||||
* @param int $sort_flags
|
||||
* @return array
|
||||
*/
|
||||
public static function sortArrayByKey($array, $array_key, $direction = SORT_DESC, $sort_flags = SORT_REGULAR )
|
||||
{
|
||||
$output = [];
|
||||
|
||||
if (!is_array($array) || !$array) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
foreach ($array as $key => $row) {
|
||||
$output[$key] = $row[$array_key];
|
||||
}
|
||||
|
||||
array_multisort($output, $direction, $sort_flags, $array);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's path based on a token
|
||||
*
|
||||
|
||||
47
system/src/Grav/Common/Yaml.php
Normal file
47
system/src/Grav/Common/Yaml.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
|
||||
abstract class Yaml
|
||||
{
|
||||
/** @var YamlFormatter */
|
||||
private static $yaml;
|
||||
|
||||
public static function parse($data)
|
||||
{
|
||||
if (null === static::$yaml) {
|
||||
static::init();
|
||||
}
|
||||
|
||||
return static::$yaml->decode($data);
|
||||
}
|
||||
|
||||
public static function dump($data, $inline = null, $indent = null)
|
||||
{
|
||||
if (null === static::$yaml) {
|
||||
static::init();
|
||||
}
|
||||
|
||||
return static::$yaml->encode($data, $inline, $indent);
|
||||
}
|
||||
|
||||
private static function init()
|
||||
{
|
||||
$config = [
|
||||
'inline' => 5,
|
||||
'indent' => 2,
|
||||
'native' => true,
|
||||
'compat' => true
|
||||
];
|
||||
|
||||
static::$yaml = new YamlFormatter($config);
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,8 @@ class CleanCommand extends Command
|
||||
'vendor/league/climate/composer.json',
|
||||
'vendor/matthiasmullie/minify/bin',
|
||||
'vendor/matthiasmullie/minify/composer.json',
|
||||
'vendor/matthiasmullie/minify/docker-composer.yml',
|
||||
'vendor/matthiasmullie/minify/Dockerfile',
|
||||
'vendor/matthiasmullie/minify/CONTRIBUTING.md',
|
||||
'vendor/matthiasmullie/path-converter/composer.json',
|
||||
'vendor/maximebf/debugbar/bower.json',
|
||||
@@ -151,6 +153,7 @@ class CleanCommand extends Command
|
||||
'vendor/pimple/pimple/src/Pimple/Tests',
|
||||
'vendor/psr/container/composer.json',
|
||||
'vendor/psr/container/.gitignore',
|
||||
'vendor/psr/simple-cache/composer.json',
|
||||
'vendor/psr/log/composer.json',
|
||||
'vendor/psr/log/.gitignore',
|
||||
'vendor/rockettheme/toolbox/.git',
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class InstallCommand extends ConsoleCommand
|
||||
{
|
||||
@@ -71,20 +71,23 @@ class InstallCommand extends ConsoleCommand
|
||||
|
||||
// Look for dependencies file in ROOT and USER dir
|
||||
if (file_exists($this->user_path . $dependencies_file)) {
|
||||
$this->config = Yaml::parse(file_get_contents($this->user_path . $dependencies_file));
|
||||
$file = YamlFile::instance($this->user_path . $dependencies_file);
|
||||
} elseif (file_exists($this->destination . $dependencies_file)) {
|
||||
$this->config = Yaml::parse(file_get_contents($this->destination . $dependencies_file));
|
||||
$file = YamlFile::instance($this->destination . $dependencies_file);
|
||||
} else {
|
||||
$this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
|
||||
if ($this->input->getArgument('destination')) {
|
||||
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install a plugin or a theme? Make sure you use <cyan>bin/gpm install <something></cyan>, not <cyan>bin/grav install</cyan>. This command is only used to install Grav skeletons.');
|
||||
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install a plugin or a theme? Make sure you use <cyan>bin/gpm install <something></cyan>, not <cyan>bin/grav install</cyan>. This command is only used to install Grav skeletons.');
|
||||
} else {
|
||||
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.');
|
||||
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.');
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->config = $file->content();
|
||||
$file->free();
|
||||
|
||||
// If yaml config, process
|
||||
if ($this->config) {
|
||||
if (!$this->input->getOption('symlink')) {
|
||||
@@ -153,10 +156,22 @@ class InstallCommand extends ConsoleCommand
|
||||
|
||||
exec('cd ' . $this->destination);
|
||||
foreach ($this->config['links'] as $repo => $data) {
|
||||
$from = $this->local_config[$data['scm'] . '_repos'] . $data['src'];
|
||||
$repos = (array) $this->local_config[$data['scm'] . '_repos'];
|
||||
$from = false;
|
||||
$to = $this->destination . $data['path'];
|
||||
|
||||
if (file_exists($from)) {
|
||||
foreach ($repos as $repo) {
|
||||
$path = $repo . $data['src'];
|
||||
if (file_exists($path)) {
|
||||
$from = $path;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$from) {
|
||||
$this->output->writeln('<red>source for ' . $data['src'] . ' does not exists, skipping...</red>');
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
if (!file_exists($to)) {
|
||||
symlink($from, $to);
|
||||
$this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
|
||||
@@ -165,11 +180,7 @@ class InstallCommand extends ConsoleCommand
|
||||
$this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
|
||||
$this->output->writeln('');
|
||||
}
|
||||
} else {
|
||||
$this->output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
system/src/Grav/Console/Cli/SecurityCommand.php
Normal file
113
system/src/Grav/Console/Cli/SecurityCommand.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Console
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Security;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class SecurityCommand extends ConsoleCommand
|
||||
{
|
||||
/** @var ProgressBar $progress */
|
||||
protected $progress;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("security")
|
||||
->setDescription("Capable of running various Security checks")
|
||||
->setHelp('The <info>security</info> runs various security checks on your Grav site');
|
||||
|
||||
$this->source = getcwd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
|
||||
|
||||
/** @var Grav $grav */
|
||||
$grav = Grav::instance();
|
||||
|
||||
$grav['uri']->init();
|
||||
$grav['config']->init();
|
||||
$grav['debugger']->enabled(false);
|
||||
$grav['streams'];
|
||||
$grav['plugins']->init();
|
||||
$grav['themes']->init();
|
||||
|
||||
|
||||
$grav['twig']->init();
|
||||
$grav['pages']->init();
|
||||
|
||||
$this->progress = new ProgressBar($this->output, (count($grav['pages']->routes()) - 1));
|
||||
$this->progress->setFormat('Scanning <cyan>%current%</cyan> pages [<green>%bar%</green>] <white>%percent:3s%%</white> %elapsed:6s%');
|
||||
$this->progress->setBarWidth(100);
|
||||
|
||||
$io = new SymfonyStyle($this->input, $this->output);
|
||||
$io->title('Grav Security Check');
|
||||
|
||||
$output = Security::detectXssFromPages($grav['pages'], [$this, 'outputProgress']);
|
||||
|
||||
$io->newline(2);
|
||||
|
||||
if (!empty($output)) {
|
||||
|
||||
$counter = 1;
|
||||
foreach ($output as $route => $results) {
|
||||
|
||||
$results_parts = array_map(function($value, $key) {
|
||||
return $key.': \''.$value . '\'';
|
||||
}, array_values($results), array_keys($results));
|
||||
|
||||
$io->writeln($counter++ .' - <cyan>' . $route . '</cyan> → <red>' . implode(', ', $results_parts) . '</red>');
|
||||
}
|
||||
|
||||
$io->error('Security Scan complete: ' . count($output) . ' potential XSS issues found...');
|
||||
|
||||
} else {
|
||||
$io->success('Security Scan complete: No issues found...');
|
||||
}
|
||||
|
||||
$io->newline(1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $args
|
||||
*/
|
||||
public function outputProgress($args)
|
||||
{
|
||||
switch ($args['type']) {
|
||||
case 'count':
|
||||
$steps = $args['steps'];
|
||||
$freq = intval($steps > 100 ? round($steps / 100) : $steps);
|
||||
$this->progress->setMaxSteps($steps);
|
||||
$this->progress->setRedrawFrequency($freq);
|
||||
break;
|
||||
case 'progress':
|
||||
if (isset($args['complete']) && $args['complete']) {
|
||||
$this->progress->finish();
|
||||
} else {
|
||||
$this->progress->advance();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Console\Cli\ClearCacheCommand;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
trait ConsoleTrait
|
||||
{
|
||||
@@ -123,7 +123,9 @@ trait ConsoleTrait
|
||||
$local_config_file = $home_folder . '/.grav/config';
|
||||
|
||||
if (file_exists($local_config_file)) {
|
||||
$this->local_config = Yaml::parse(file_get_contents($local_config_file));
|
||||
$file = YamlFile::instance($local_config_file);
|
||||
$this->local_config = $file->content();
|
||||
$file->free();
|
||||
return $local_config_file;
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ class InstallCommand extends ConsoleCommand
|
||||
//We're done installing dependencies. Install the actual packages
|
||||
foreach ($this->data as $data) {
|
||||
foreach ($data as $package_name => $package) {
|
||||
if (in_array($package_name, array_keys($dependencies))) {
|
||||
if (array_key_exists($package_name, $dependencies)) {
|
||||
$this->output->writeln("<green>Package " . $package_name . " already installed as dependency</green>");
|
||||
} else {
|
||||
$is_valid_destination = Installer::isValidDestination($this->destination . DS . $package->install_path);
|
||||
@@ -444,18 +444,21 @@ class InstallCommand extends ConsoleCommand
|
||||
{
|
||||
$matches = $this->getGitRegexMatches($package);
|
||||
|
||||
foreach ($this->local_config as $path) {
|
||||
foreach ($this->local_config as $paths) {
|
||||
if (Utils::endsWith($matches[2], '.git')) {
|
||||
$repo_dir = preg_replace('/\.git$/', '', $matches[2]);
|
||||
} else {
|
||||
$repo_dir = $matches[2];
|
||||
}
|
||||
|
||||
$from = rtrim($path, '/') . '/' . $repo_dir;
|
||||
|
||||
if (file_exists($from)) {
|
||||
return $from;
|
||||
|
||||
$paths = (array) $paths;
|
||||
foreach ($paths as $repo) {
|
||||
$path = rtrim($repo, '/') . '/' . $repo_dir;
|
||||
if (file_exists($path)) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -102,8 +102,8 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
if (!$this->upgrader->meetsRequirements()) {
|
||||
$this->output->writeln("<red>ATTENTION:</red>");
|
||||
$this->output->writeln(" Grav has increased the minimum PHP requirement.");
|
||||
$this->output->writeln(" You are currently running PHP <red>" . PHP_VERSION . "</red>, but PHP <green>" . GRAV_PHP_MIN . "</green> is required.");
|
||||
$this->output->writeln(" Additional information: <white>http://getgrav.org/blog/changing-php-requirements-to-5.5</white>");
|
||||
$this->output->writeln(" You are currently running PHP <red>" . phpversion() . "</red>, but PHP <green>" . $this->upgrader->minPHPVersion() . "</green> is required.");
|
||||
$this->output->writeln(" Additional information: <white>http://getgrav.org/blog/changing-php-requirements</white>");
|
||||
$this->output->writeln("");
|
||||
$this->output->writeln("Selfupgrade aborted.");
|
||||
$this->output->writeln("");
|
||||
|
||||
@@ -185,7 +185,7 @@ class UninstallCommand extends ConsoleCommand
|
||||
if (is_array($dependency)) {
|
||||
$dependency = $dependency['name'];
|
||||
}
|
||||
if ($dependency === 'grav') {
|
||||
if ($dependency === 'grav' || $dependency === 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ namespace Grav\Console\Gpm;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\GPM\Upgrader;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class VersionCommand extends ConsoleCommand
|
||||
{
|
||||
@@ -84,7 +84,10 @@ class VersionCommand extends ConsoleCommand
|
||||
}
|
||||
}
|
||||
|
||||
$package_yaml = Yaml::parse(file_get_contents($blueprints_path));
|
||||
$file = YamlFile::instance($blueprints_path);
|
||||
$package_yaml = $file->content();
|
||||
$file->free();
|
||||
|
||||
$version = $package_yaml['version'];
|
||||
|
||||
if (!$version) {
|
||||
|
||||
@@ -16,21 +16,18 @@ use Grav\Framework\Cache\Exception\InvalidArgumentException;
|
||||
*/
|
||||
trait CacheTrait
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
private $namespace = '';
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
/** @var int|null */
|
||||
private $defaultLifetime = null;
|
||||
|
||||
/**
|
||||
* @var \stdClass
|
||||
*/
|
||||
/** @var \stdClass */
|
||||
private $miss;
|
||||
|
||||
/** @var bool */
|
||||
private $validation = true;
|
||||
|
||||
/**
|
||||
* Always call from constructor.
|
||||
*
|
||||
@@ -45,6 +42,14 @@ trait CacheTrait
|
||||
$this->miss = new \stdClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $validation
|
||||
*/
|
||||
public function setValidation($validation)
|
||||
{
|
||||
$this->validation = (bool) $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -307,6 +312,10 @@ trait CacheTrait
|
||||
*/
|
||||
protected function validateKeys($keys)
|
||||
{
|
||||
if (!$this->validation) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$this->validateKey($key);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
|
||||
if (!method_exists($this, 'createFrom')) {
|
||||
return new static(array_reverse($this->toArray()));
|
||||
}
|
||||
|
||||
return $this->createFrom(array_reverse($this->toArray()));
|
||||
}
|
||||
|
||||
@@ -42,11 +37,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
|
||||
$keys = $this->getKeys();
|
||||
shuffle($keys);
|
||||
|
||||
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
|
||||
if (!method_exists($this, 'createFrom')) {
|
||||
return new static(array_replace(array_flip($keys), $this->toArray()));
|
||||
}
|
||||
|
||||
return $this->createFrom(array_replace(array_flip($keys), $this->toArray()));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class ContentBlock implements ContentBlockInterface
|
||||
protected $tokenTemplate = '@@BLOCK-%s@@';
|
||||
protected $content = '';
|
||||
protected $blocks = [];
|
||||
protected $checksum;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
@@ -40,6 +41,7 @@ class ContentBlock implements ContentBlockInterface
|
||||
/**
|
||||
* @param array $serialized
|
||||
* @return ContentBlockInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function fromArray(array $serialized)
|
||||
{
|
||||
@@ -48,14 +50,14 @@ class ContentBlock implements ContentBlockInterface
|
||||
$id = isset($serialized['id']) ? $serialized['id'] : null;
|
||||
|
||||
if (!$type || !$id || !is_a($type, 'Grav\Framework\ContentBlock\ContentBlockInterface', true)) {
|
||||
throw new \RuntimeException('Bad data');
|
||||
throw new \InvalidArgumentException('Bad data');
|
||||
}
|
||||
|
||||
/** @var ContentBlockInterface $instance */
|
||||
$instance = new $type($id);
|
||||
$instance->build($serialized);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
|
||||
throw new \InvalidArgumentException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
@@ -104,9 +106,13 @@ class ContentBlock implements ContentBlockInterface
|
||||
$array = [
|
||||
'_type' => get_class($this),
|
||||
'_version' => $this->version,
|
||||
'id' => $this->id,
|
||||
'id' => $this->id
|
||||
];
|
||||
|
||||
if ($this->checksum) {
|
||||
$array['checksum'] = $this->checksum;
|
||||
}
|
||||
|
||||
if ($this->content) {
|
||||
$array['content'] = $this->content;
|
||||
}
|
||||
@@ -158,6 +164,7 @@ class ContentBlock implements ContentBlockInterface
|
||||
$this->checkVersion($serialized);
|
||||
|
||||
$this->id = isset($serialized['id']) ? $serialized['id'] : $this->generateId();
|
||||
$this->checksum = isset($serialized['checksum']) ? $serialized['checksum'] : null;
|
||||
|
||||
if (isset($serialized['content'])) {
|
||||
$this->setContent($serialized['content']);
|
||||
@@ -169,6 +176,25 @@ class ContentBlock implements ContentBlockInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $checksum
|
||||
* @return $this
|
||||
*/
|
||||
public function setChecksum($checksum)
|
||||
{
|
||||
$this->checksum = $checksum;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getChecksum()
|
||||
{
|
||||
return $this->checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return $this
|
||||
@@ -222,7 +248,7 @@ class ContentBlock implements ContentBlockInterface
|
||||
*/
|
||||
protected function checkVersion(array $serialized)
|
||||
{
|
||||
$version = isset($serialized['_version']) ? (string) $serialized['_version'] : '1';
|
||||
$version = isset($serialized['_version']) ? (int) $serialized['_version'] : 1;
|
||||
if ($version !== $this->version) {
|
||||
throw new \RuntimeException(sprintf('Unsupported version %s', $version));
|
||||
}
|
||||
|
||||
@@ -61,6 +61,17 @@ interface ContentBlockInterface extends \Serializable
|
||||
*/
|
||||
public function build(array $serialized);
|
||||
|
||||
/**
|
||||
* @param string $checksum
|
||||
* @return $this
|
||||
*/
|
||||
public function setChecksum($checksum);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getChecksum();
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return $this
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Grav\Framework\ContentBlock;
|
||||
*/
|
||||
class HtmlBlock extends ContentBlock implements HtmlBlockInterface
|
||||
{
|
||||
protected $version = 1;
|
||||
protected $frameworks = [];
|
||||
protected $styles = [];
|
||||
protected $scripts = [];
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
interface FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Get default file extension from current formatter (with dot).
|
||||
*
|
||||
* Default file extension is the first defined extension.
|
||||
*
|
||||
* @return string File extension (can be empty).
|
||||
*/
|
||||
public function getDefaultFileExtension();
|
||||
|
||||
/**
|
||||
* Get file extensions supported by current formatter (with dot).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedFileExtensions();
|
||||
|
||||
/**
|
||||
* Encode data into a string.
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function encode($data);
|
||||
|
||||
/**
|
||||
* Decode a string into data.
|
||||
*
|
||||
* @param string $data
|
||||
* @return array
|
||||
*/
|
||||
public function decode($data);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user