mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
504 Commits
1.7.0-rc.6
...
1.7.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fb6a39d9d | ||
|
|
9aeac93c9a | ||
|
|
6188076fe6 | ||
|
|
0fc7fd3411 | ||
|
|
8c845c77c1 | ||
|
|
078c8d23d5 | ||
|
|
cb373dae59 | ||
|
|
0a42a889ec | ||
|
|
e8f5080f35 | ||
|
|
00f73957dd | ||
|
|
3a0480e0d8 | ||
|
|
0cee7bcdc8 | ||
|
|
30401df4b7 | ||
|
|
f5d1e98491 | ||
|
|
8f9b2d22c1 | ||
|
|
2b17767b53 | ||
|
|
00a7094802 | ||
|
|
6eaf44e397 | ||
|
|
24b52c77fe | ||
|
|
7a9b906925 | ||
|
|
53bef264e7 | ||
|
|
75b74c4ab3 | ||
|
|
ca3a9aecd6 | ||
|
|
8f7902af94 | ||
|
|
8df3fa31fb | ||
|
|
ff7a1b861e | ||
|
|
d6a82e5c56 | ||
|
|
f714e6fa14 | ||
|
|
6817133819 | ||
|
|
76670e47a1 | ||
|
|
961b24a019 | ||
|
|
068de42e83 | ||
|
|
75f9c0a892 | ||
|
|
3a1b301b32 | ||
|
|
9bb1d99ae4 | ||
|
|
cf052b5bd4 | ||
|
|
65a18fd270 | ||
|
|
d4775cc970 | ||
|
|
07ee5b42f7 | ||
|
|
d5048fea10 | ||
|
|
f3149068e3 | ||
|
|
f5d3a5132a | ||
|
|
70e0ecfef5 | ||
|
|
631e0e0f3f | ||
|
|
b66287cbb9 | ||
|
|
9e6d38bc1a | ||
|
|
6cbe4aeec4 | ||
|
|
e219f56ff4 | ||
|
|
e16b29c566 | ||
|
|
1b3e3ede4d | ||
|
|
0a601f10c0 | ||
|
|
56ce4ab0f2 | ||
|
|
1f6c2f5a0a | ||
|
|
5f3ddd9389 | ||
|
|
fd0c9823fa | ||
|
|
d23588a217 | ||
|
|
ca53b8afca | ||
|
|
053bc03a54 | ||
|
|
78dc70bcdc | ||
|
|
ea00c044d3 | ||
|
|
11cd2b086e | ||
|
|
ae6f0b5505 | ||
|
|
247d1a9aa6 | ||
|
|
6273c2395d | ||
|
|
1396525251 | ||
|
|
de46afff1c | ||
|
|
54fd54d3f0 | ||
|
|
0b41eea2bb | ||
|
|
63207d370b | ||
|
|
e92d88df8b | ||
|
|
a9e6d3665f | ||
|
|
2453927ba3 | ||
|
|
94e6cb02f3 | ||
|
|
9b2b909139 | ||
|
|
c9a3f349ad | ||
|
|
439b539dcb | ||
|
|
ab7f0a2e95 | ||
|
|
b50dbf3ff0 | ||
|
|
d3f1f833ab | ||
|
|
3df099a0e3 | ||
|
|
54dccd11ef | ||
|
|
77ec847e20 | ||
|
|
8bf5fcb8ec | ||
|
|
ff41d76501 | ||
|
|
d9772ed5c6 | ||
|
|
8dba1b37c7 | ||
|
|
8b017dc647 | ||
|
|
c8ee05d671 | ||
|
|
40216e310d | ||
|
|
6a6f99e9ae | ||
|
|
14ad7cf3ac | ||
|
|
ce327dca42 | ||
|
|
ded7670ac3 | ||
|
|
5bf1ec0fd9 | ||
|
|
306c0505a6 | ||
|
|
325cb69a65 | ||
|
|
c3df9b6484 | ||
|
|
db6bfc00fc | ||
|
|
51b5d7a939 | ||
|
|
9490f62dee | ||
|
|
1661dc9ef7 | ||
|
|
aa7731dc5d | ||
|
|
4150564538 | ||
|
|
38043ebade | ||
|
|
9a694a8d3d | ||
|
|
f6997f54eb | ||
|
|
2631c18484 | ||
|
|
c0d819b97a | ||
|
|
972d32c969 | ||
|
|
7f0e51f92f | ||
|
|
6f78c2288f | ||
|
|
b331ba42b8 | ||
|
|
fb3efba204 | ||
|
|
58e09dcff4 | ||
|
|
747055f60e | ||
|
|
aa07c64440 | ||
|
|
6b9f1d8414 | ||
|
|
b4bc1d292a | ||
|
|
c98553d19b | ||
|
|
1ace31216f | ||
|
|
495cec930c | ||
|
|
44ad0ca1ea | ||
|
|
4ba8e9e99d | ||
|
|
1eb85b366d | ||
|
|
3821ae441f | ||
|
|
b01e5f7366 | ||
|
|
b375a543ec | ||
|
|
2c9d848af5 | ||
|
|
8d65c5c2c0 | ||
|
|
0af731b6a2 | ||
|
|
9d870b2c45 | ||
|
|
82abf87f75 | ||
|
|
81ef023b37 | ||
|
|
776fffb2dc | ||
|
|
cfd5d9e209 | ||
|
|
23716ff729 | ||
|
|
18921a4f14 | ||
|
|
569c42724b | ||
|
|
18abf7d644 | ||
|
|
12efdb9fe4 | ||
|
|
fdf884036d | ||
|
|
998018af3e | ||
|
|
cae71f3d60 | ||
|
|
f7e43fab35 | ||
|
|
4f7a4ac1e2 | ||
|
|
5fc7b34ce7 | ||
|
|
cbf73b2290 | ||
|
|
f344a8166e | ||
|
|
9532317928 | ||
|
|
4cf85c462d | ||
|
|
6fe2964f37 | ||
|
|
2108a902c2 | ||
|
|
1520eec677 | ||
|
|
e3ba3a3ea4 | ||
|
|
1c104f9358 | ||
|
|
f8b9536eb8 | ||
|
|
0e34628c6f | ||
|
|
614bc0b254 | ||
|
|
cc6eafdb09 | ||
|
|
df4cbdcc09 | ||
|
|
f5e53a9a4c | ||
|
|
46f654fcee | ||
|
|
aa3a3f4a17 | ||
|
|
59b3b6cc02 | ||
|
|
6d4b8e8401 | ||
|
|
9ac7a60835 | ||
|
|
790dbd381d | ||
|
|
ac6c8a985b | ||
|
|
fea22e0409 | ||
|
|
ac7d595a2d | ||
|
|
819e412e09 | ||
|
|
bdfec68340 | ||
|
|
0b7ef6c8fb | ||
|
|
9880ce977a | ||
|
|
e88f924274 | ||
|
|
fe53dc88e5 | ||
|
|
fdfd6558f2 | ||
|
|
6d9e3dcad1 | ||
|
|
20c4468edd | ||
|
|
7f80e650b1 | ||
|
|
1995837b3f | ||
|
|
88c0617279 | ||
|
|
b8b1bed7ed | ||
|
|
03c6e74c4d | ||
|
|
71639de5ec | ||
|
|
39310cd4af | ||
|
|
582352b2d2 | ||
|
|
f2c271f66d | ||
|
|
d99e1f519e | ||
|
|
c53e2843be | ||
|
|
f438ce04fb | ||
|
|
8be5952b92 | ||
|
|
cfc8610a16 | ||
|
|
ad8a5d9870 | ||
|
|
75cb4e392d | ||
|
|
ec9dd14cb4 | ||
|
|
6e8c852bfa | ||
|
|
f204979ada | ||
|
|
6275327b0a | ||
|
|
f1126e63b1 | ||
|
|
7157ed08f9 | ||
|
|
d33627f1fe | ||
|
|
c2611cfeaf | ||
|
|
056614fa6b | ||
|
|
4c19226959 | ||
|
|
d41a31f614 | ||
|
|
d83dc07368 | ||
|
|
f30d8748f1 | ||
|
|
b4710c292f | ||
|
|
c3cd399087 | ||
|
|
f8a2c94903 | ||
|
|
747b081809 | ||
|
|
55903dab11 | ||
|
|
a6c094f9aa | ||
|
|
b9b978e452 | ||
|
|
bad24f8a85 | ||
|
|
57cffbc4c8 | ||
|
|
86d71bf37f | ||
|
|
c6dd169916 | ||
|
|
cbc85b7a09 | ||
|
|
016af0f419 | ||
|
|
1903d44bf8 | ||
|
|
351c270e0e | ||
|
|
ab3d9f89ec | ||
|
|
97220a27df | ||
|
|
0310fdf99f | ||
|
|
f1a535b7d4 | ||
|
|
6879d8b6ce | ||
|
|
046c5f69ac | ||
|
|
40687f8cc0 | ||
|
|
b8f27ecfd0 | ||
|
|
16dd2ef9d0 | ||
|
|
aaff2f486f | ||
|
|
d9a62373b8 | ||
|
|
bef4f6a0db | ||
|
|
a66c282ee5 | ||
|
|
2d50cc2b2d | ||
|
|
82f1182503 | ||
|
|
360bed8697 | ||
|
|
e4a833b59e | ||
|
|
f8809d5d62 | ||
|
|
8adffb8714 | ||
|
|
9b6649174c | ||
|
|
7f03f7c844 | ||
|
|
843596944f | ||
|
|
0f6c5fe49d | ||
|
|
b94c4e775a | ||
|
|
fc97e88928 | ||
|
|
5b47e6130c | ||
|
|
86edc18f21 | ||
|
|
5ee36e786a | ||
|
|
c4e2e5da8d | ||
|
|
2c14ec0abd | ||
|
|
5b782fd04c | ||
|
|
bdd579ad2d | ||
|
|
d94fa8d2de | ||
|
|
c0f1d4e2da | ||
|
|
e82f49bdab | ||
|
|
b65d48541d | ||
|
|
e2f68194d7 | ||
|
|
bd6f8f4ae6 | ||
|
|
9d4cce93fc | ||
|
|
28ce18f8f0 | ||
|
|
c060d1a36e | ||
|
|
3a05dcf659 | ||
|
|
291ed38a5a | ||
|
|
411d689a48 | ||
|
|
df279f879f | ||
|
|
9e58bccd49 | ||
|
|
092f3b4e24 | ||
|
|
005bb77f55 | ||
|
|
258840d198 | ||
|
|
c483da9efe | ||
|
|
4b3f202c6f | ||
|
|
07571465a7 | ||
|
|
7d7590b914 | ||
|
|
f965961401 | ||
|
|
814ea7f81a | ||
|
|
91a0790695 | ||
|
|
2b8a6c6a89 | ||
|
|
55648d3452 | ||
|
|
60316d1b75 | ||
|
|
7826608384 | ||
|
|
de151c8a9d | ||
|
|
b0a1effaf9 | ||
|
|
fd52d124dd | ||
|
|
b772864f2d | ||
|
|
cdb10cfc1f | ||
|
|
da7f0b7dce | ||
|
|
25461f7ca8 | ||
|
|
da7df9f865 | ||
|
|
00c5dba210 | ||
|
|
f30219d85a | ||
|
|
223e75d326 | ||
|
|
46d4f4a481 | ||
|
|
419ebeafa8 | ||
|
|
4be7917d41 | ||
|
|
191686dffe | ||
|
|
fa791ed4ab | ||
|
|
e29f8a9657 | ||
|
|
e66f6583b1 | ||
|
|
399d90e6cd | ||
|
|
ec2eaae0d3 | ||
|
|
12389b1e0d | ||
|
|
ff6e5a20c3 | ||
|
|
7d6526f962 | ||
|
|
b8fad8452b | ||
|
|
5e9107f376 | ||
|
|
7faaff304a | ||
|
|
94acbf6005 | ||
|
|
a65c21acba | ||
|
|
bfbe4ce1b8 | ||
|
|
aaa636f357 | ||
|
|
d0241ba7ee | ||
|
|
b2230225cc | ||
|
|
e063718e4f | ||
|
|
03436a670f | ||
|
|
8be508de5d | ||
|
|
9370a86246 | ||
|
|
6c5237cdd8 | ||
|
|
2cf5ecd212 | ||
|
|
8e9125772e | ||
|
|
62e863dec0 | ||
|
|
e00098ea6a | ||
|
|
f12bd1b51f | ||
|
|
93942a74cf | ||
|
|
401fd04379 | ||
|
|
4fd54f1692 | ||
|
|
9565994e5c | ||
|
|
6876e1a38a | ||
|
|
7a8e737802 | ||
|
|
9f968611b1 | ||
|
|
3bd4f9499a | ||
|
|
069ef7b12c | ||
|
|
9d6207f80e | ||
|
|
22b493996c | ||
|
|
5284717570 | ||
|
|
96d11e4ffa | ||
|
|
a156247dc3 | ||
|
|
0e4a88d538 | ||
|
|
a2d1cdfc13 | ||
|
|
dcb0c490ba | ||
|
|
7687bdf751 | ||
|
|
85ef04abc4 | ||
|
|
69ae59b933 | ||
|
|
c5e538421e | ||
|
|
d7b1689047 | ||
|
|
bd8396ba6e | ||
|
|
859aff590b | ||
|
|
73d8a844d9 | ||
|
|
5db395799d | ||
|
|
ad1ecf21c3 | ||
|
|
497ca2a5cd | ||
|
|
4e578ed95e | ||
|
|
8b258c8828 | ||
|
|
148117edcb | ||
|
|
a57358f311 | ||
|
|
ee0ad1126d | ||
|
|
2db0002ffd | ||
|
|
a74e07c491 | ||
|
|
8a12abf795 | ||
|
|
62c7cfcd10 | ||
|
|
9257d6982f | ||
|
|
58da1cd489 | ||
|
|
69078aaf49 | ||
|
|
33aa0737a4 | ||
|
|
8b47608cc0 | ||
|
|
d25014779d | ||
|
|
5f0380f547 | ||
|
|
bdbd392c6e | ||
|
|
d53a594971 | ||
|
|
2604da4b04 | ||
|
|
77bc8029bb | ||
|
|
11e4a4cad8 | ||
|
|
e37f634a38 | ||
|
|
0a18502c2c | ||
|
|
b8c81c72e4 | ||
|
|
37c2219b87 | ||
|
|
62f6f3910a | ||
|
|
b450e6998e | ||
|
|
10800a0896 | ||
|
|
cf61525130 | ||
|
|
24db65cd90 | ||
|
|
daf7e66ec3 | ||
|
|
18c9a1793e | ||
|
|
2b0f235e23 | ||
|
|
24eca428a6 | ||
|
|
677b0b7055 | ||
|
|
10bd72ad51 | ||
|
|
4ddc98b2b6 | ||
|
|
c1f18f5ecf | ||
|
|
d16a88e731 | ||
|
|
39d0d640e6 | ||
|
|
bfb55f0e1d | ||
|
|
44b07712d5 | ||
|
|
c8ae9e4dcc | ||
|
|
52d1412e4c | ||
|
|
b820130636 | ||
|
|
7d4ecd4272 | ||
|
|
f907b589e1 | ||
|
|
a9d80d73d6 | ||
|
|
fe0ad9c9c2 | ||
|
|
dfa8ce1ad4 | ||
|
|
1ea5dad728 | ||
|
|
a2b81c9378 | ||
|
|
01b0b1602b | ||
|
|
7d4aef0f3b | ||
|
|
5485893c8b | ||
|
|
124543f4a7 | ||
|
|
e84488e126 | ||
|
|
ed2cf5174d | ||
|
|
d0d0a6c224 | ||
|
|
b04f4f0001 | ||
|
|
c822920c36 | ||
|
|
e6354236c1 | ||
|
|
068948e1a2 | ||
|
|
f8bf7ba010 | ||
|
|
586105907d | ||
|
|
abd56ffee3 | ||
|
|
5678b4d067 | ||
|
|
1abb940318 | ||
|
|
15af5bfae7 | ||
|
|
d550a016a9 | ||
|
|
9c8df27bf1 | ||
|
|
1ce0176ab6 | ||
|
|
5bc46c49d2 | ||
|
|
292d45a7db | ||
|
|
2d375954ab | ||
|
|
7ff6d2a828 | ||
|
|
ccac8a93bc | ||
|
|
bcbfa0e32a | ||
|
|
453cd62a51 | ||
|
|
56e1cbc78e | ||
|
|
58cd77449c | ||
|
|
db92c7b32d | ||
|
|
6ba54d2b3d | ||
|
|
987429208c | ||
|
|
2eae104c7a | ||
|
|
6f2be2a2d2 | ||
|
|
e75960fee3 | ||
|
|
ec71ccdd0d | ||
|
|
a5d47a36f0 | ||
|
|
866b63530a | ||
|
|
57feac985c | ||
|
|
c72c980ad2 | ||
|
|
790d29cbfb | ||
|
|
b9947f6984 | ||
|
|
79b7c3c38f | ||
|
|
a9df896538 | ||
|
|
ad713a1342 | ||
|
|
b58479cba6 | ||
|
|
107341f33f | ||
|
|
1ed0fcb379 | ||
|
|
63892782b4 | ||
|
|
f5a480c72e | ||
|
|
d80a29b34d | ||
|
|
5aefc60f2e | ||
|
|
683c1fe477 | ||
|
|
6b4692f6c7 | ||
|
|
1351f11551 | ||
|
|
58280b8d3f | ||
|
|
652ca75f40 | ||
|
|
4b5f1590aa | ||
|
|
152c987ed4 | ||
|
|
19311c7ec1 | ||
|
|
eec2d122cc | ||
|
|
62f39fe39c | ||
|
|
394fce3825 | ||
|
|
9c6934c0aa | ||
|
|
993bc5170b | ||
|
|
d285eda4c2 | ||
|
|
0bcbe39521 | ||
|
|
78502b2026 | ||
|
|
df0c806381 | ||
|
|
d089a1d9c8 | ||
|
|
e41072c448 | ||
|
|
36d18d531c | ||
|
|
fb18412fb3 | ||
|
|
9a8c2b9aa5 | ||
|
|
678b39a170 | ||
|
|
d03b0d92f4 | ||
|
|
11f9ba74e8 | ||
|
|
f4f5bffcd9 | ||
|
|
d763f9c63e | ||
|
|
44acd3a969 | ||
|
|
4d7510dc11 | ||
|
|
f4a3efc3bc | ||
|
|
40bc980084 | ||
|
|
cf62d1dfa2 | ||
|
|
378e59563d | ||
|
|
1196e06dd6 | ||
|
|
028bbf08c6 | ||
|
|
1d6a474b31 | ||
|
|
79e68eb5df | ||
|
|
13a1c7cfee | ||
|
|
a3812141dd | ||
|
|
5833f1e2da | ||
|
|
88311f21cf | ||
|
|
64b33d60f4 | ||
|
|
8ccbcf0488 | ||
|
|
4fb485d1dd | ||
|
|
244c34a536 | ||
|
|
52a704e53d | ||
|
|
5852ca4179 |
@@ -27,6 +27,9 @@ RewriteEngine On
|
||||
# If you experience problems on your site block out the operations listed below
|
||||
# This attempts to block the most common type of exploit `attempts` to Grav
|
||||
#
|
||||
# Block out any script trying to use twig tags in URL.
|
||||
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
|
||||
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
|
||||
# Block out any script trying to base64_encode data within the URL.
|
||||
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
|
||||
# Block out any script that includes a <script> tag in URL.
|
||||
|
||||
@@ -3,6 +3,7 @@ php:
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
- '7.3'
|
||||
- '7.4'
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
@@ -47,8 +48,8 @@ before_install:
|
||||
fi
|
||||
- if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "7.1" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
export TRAVIS_TAG=$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4);
|
||||
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.8 bash)";
|
||||
go get github.com/aktau/github-release;
|
||||
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.13 bash)";
|
||||
go get github.com/github-release/github-release;
|
||||
git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
|
||||
if [ ! -z "$TRAVIS_TAG" ]; then
|
||||
cd ${RT_DEVTOOLS};
|
||||
@@ -56,7 +57,7 @@ before_install:
|
||||
fi;
|
||||
fi
|
||||
before_script:
|
||||
- if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-rm xdebug.ini; fi
|
||||
- phpenv config-rm xdebug.ini
|
||||
script:
|
||||
- if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then
|
||||
vendor/bin/codecept run;
|
||||
|
||||
343
CHANGELOG.md
343
CHANGELOG.md
@@ -1,3 +1,246 @@
|
||||
# v1.7.0-rc.19
|
||||
## 12/02/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Updated composer libraries with latest Toolbox v1.5.6 that contains critical fixes
|
||||
|
||||
# v1.7.0-rc.18
|
||||
## 12/02/2020
|
||||
|
||||
1. [](#new)
|
||||
* Set minimum requirements to **PHP 7.3.6**
|
||||
* Updated Clockwork to v5.0
|
||||
* Added `FlexDirectoryInterface` interface
|
||||
* Renamed `PageCollectionInterface::nonModular()` into `PageCollectionInterface::pages()` and deprecated the old method
|
||||
* Renamed `PageCollectionInterface::modular()` into `PageCollectionInterface::modules()` and deprecated the old method'
|
||||
* Upgraded `bin/composer.phar` to `2.0.2` which is all new and much faster
|
||||
* Added search option `same_as` to Flex Objects
|
||||
* Added PHP 8 compatible `function_exists()`: `Utils::functionExists()`
|
||||
* New sites have `compatibility` features turned off by default, upgrading from older versions will keep the settings on
|
||||
1. [](#improved)
|
||||
* Updated bundled JQuery to latest version `3.5.1`
|
||||
* Forward a `sid` to GPM when downloading a premium package via CLI
|
||||
* Allow `JsonFormatter` options to be passed as a string
|
||||
* Hide Flex Pages frontend configuration (not ready for production use)
|
||||
* Improve Flex configuration: gather views together in blueprint
|
||||
* Added XSS detection to all forms. See [documentation](http://learn.grav.local/17/forms/forms/form-options#xss-checks)
|
||||
* Better handling of missing repository index [grav-plugin-admin#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916)
|
||||
* Added support for having all sites / environments under `user/env` folder [#3072](https://github.com/getgrav/grav/issues/3072)
|
||||
* Added `FlexObject::refresh()` method to make sure object is up to date
|
||||
1. [](#bugfix)
|
||||
* *Menu Visibility Requires Access* Security option setting wrong frontmatter [login#265](https://github.com/getgrav/grav-plugin-login/issues/265)
|
||||
* Accessing page with unsupported file extension (jpg, pdf, xsl) will use wrong mime type [#3031](https://github.com/getgrav/grav/issues/3031)
|
||||
* Fixed media crashing on a bad image
|
||||
* Fixed bug in collections where filter `type: false` did not work
|
||||
* Fixed `print_r()` in twig
|
||||
* Fixed sorting by groups in `Flex Users`
|
||||
* Changing `Flex Page` template causes the other language versions of that page to lose their content [admin#1958](https://github.com/getgrav/grav-plugin-admin/issues/1958)
|
||||
* Fixed plugins getting initialized multiple times (by CLI commands for example)
|
||||
* Fixed `header.admin.children_display_order` in Flex Pages to work just like with regular pages
|
||||
* Fixed `Utils::isFunctionDisabled()` method if there are spaces in `disable_functions` [#3023](https://github.com/getgrav/grav/issues/3023)
|
||||
* Fixed potential fatal error when creating flex index using cache [#3062](https://github.com/getgrav/grav/issues/3062)
|
||||
* Fixed fatal error in `CompiledFile` if the cached version is broken
|
||||
* Fixed updated media missing from media when editing Flex Object after page reload
|
||||
* Fixed issue with `config-default@` breaking on set [#1972](https://github.com/getgrav/grav-plugin-admin/issues/1971)
|
||||
* Escape titles in Flex pages list [flex-objects#84](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/84)
|
||||
* Fixed Purge successful message only working in Scheduler but broken in CLI and Admin [#1935](https://github.com/getgrav/grav-plugin-admin/issues/1935)
|
||||
* Fixed `system://` stream is causing issues in Admin, making Media tab to disappear and possibly causing other issues [#3072](https://github.com/getgrav/grav/issues/3072)
|
||||
* Fixed CLI self-upgrade from Grav 1.6 [#3079](https://github.com/getgrav/grav/issues/3079)
|
||||
* Fixed `bin/grav yamllinter -a` and `-f` not following symlinks [#3080](https://github.com/getgrav/grav/issues/3080)
|
||||
* Fixed `|safe_email` filter to return safe and escaped UTF-8 HTML [#3072](https://github.com/getgrav/grav/issues/3072)
|
||||
* Fixed exception in CLI GPM and backup commands when `php-zip` is not enabled [#3075](https://github.com/getgrav/grav/issues/3075)
|
||||
* Fix for XSS advisory [GHSA-cvmr-6428-87w9](https://github.com/getgrav/grav/security/advisories/GHSA-cvmr-6428-87w9)
|
||||
* Fixed Flex and Page ordering to be natural and case insensitive [flex-objects#87](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/87)
|
||||
* Fixed plugin/theme priority ordering to be numeric
|
||||
|
||||
# v1.7.0-rc.17
|
||||
## 10/07/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added a `Uri::getAllHeaders()` compatibility function
|
||||
1. [](#improved)
|
||||
* Fall back through various templates scenarios if they don't exist in theme to avoid unhelpful error.
|
||||
* Added default templates for `external.html.twig`, `default.html.twig`, and `modular.html.twig`
|
||||
* Improve Media classes
|
||||
* _POTENTIAL BREAKING CHANGE:_ Added reload argument to `FlexStorageInterface::getMetaData()`
|
||||
1. [](#bugfix)
|
||||
* Fixed `Security::sanitizeSVG()` creating an empty file if SVG file cannot be parsed
|
||||
* Fixed infinite loop in blueprints with `extend@` to a parent stream
|
||||
* Added missing `Stream::create()` method
|
||||
* Added missing `onBlueprintCreated` event for Flex Pages
|
||||
* Fixed `onBlueprintCreated` firing multiple times recursively
|
||||
* Fixed media upload failing with custom folders
|
||||
* Fixed `unset()` in `ObjectProperty` class
|
||||
* Fixed `FlexObject::freeMedia()` method causing media to become null
|
||||
* Fixed bug in `Flex Form` making it impossible to set nested values
|
||||
* Fixed `Flex User` avatar when using folder storage, also allow multiple images
|
||||
* Fixed Referer reference during GPM calls.
|
||||
* Fixed fatal error with toggled lists
|
||||
|
||||
# v1.7.0-rc.16
|
||||
## 09/01/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `svg_image()` twig function to make it easier to 'include' SVG source in Twig
|
||||
* Added a helper `Utils::fullPath()` to get the full path to a file be it stream, relative, etc.
|
||||
1. [](#improved)
|
||||
* Added `themes` to cached blueprints and configuration
|
||||
1. [](#bugfix)
|
||||
* Fixed `Flex Pages` issue with `getRoute()` returning path with language prefix for default language if set not to do that
|
||||
* Fixed `Flex Pages` bug where reordering pages causes page content to disappear if default language uses wrong extension (`.md` vs `.en.md`)
|
||||
* Fixed `Flex Pages` bug where `onAdminSave` passes page as `$event['page']` instead of `$event['object']` [#2995](https://github.com/getgrav/grav/issues/2995)
|
||||
* Fixed `Flex Pages` bug where changing a modular page template added duplicate file [admin#1899](https://github.com/getgrav/grav-plugin-admin/issues/1899)
|
||||
* Fixed `Flex Pages` bug where renaming slug causes bad ordering range after save [#2997](https://github.com/getgrav/grav/issues/2997)
|
||||
|
||||
# v1.7.0-rc.15
|
||||
## 07/22/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed Flex index file caching [#2962](https://github.com/getgrav/grav/issues/2962)
|
||||
* Fixed various issues with Exif data reading and images being incorrectly rotated [#1923](https://github.com/getgrav/grav-plugin-admin/issues/1923)
|
||||
|
||||
# v1.7.0-rc.14
|
||||
## 07/09/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added ability to `noprocess` specific items only in Link/Image Excerpts, e.g. `http://foo.com/page?id=foo&target=_blank&noprocess=id` [#2954](https://github.com/getgrav/grav/pull/2954)
|
||||
1. [](#bugfix)
|
||||
* Regression: Default language fix broke `Language::getLanguageURLPrefix()` and `Language::isIncludeDefaultLanguage()` methods when not using multi-language
|
||||
* Reverted `Language::getDefault()` and `Language::getLanguage()` to return false again because of plugin compatibility (updated docblocks)
|
||||
* Fixed UTF-8 issue in `Excerpts::getExcerptsFromHtml`
|
||||
* Fixed some compatibility issues with recent Changes to `Assets` handling
|
||||
* Fixed issue with `CSS_IMPORTS_REGEX` breaking with complex URLs [#2958](https://github.com/getgrav/grav/issues/2958)
|
||||
* Moved duplicated `CSS_IMPORT_REGEX` to local variable in `AssetUtilsTrait::moveImports()`
|
||||
* Fixed page media only accepting images [#2943](https://github.com/getgrav/grav/issues/2943)
|
||||
|
||||
# v1.7.0-rc.13
|
||||
## 07/01/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added support for uploading and deleting images directly in `Media`
|
||||
* Added new `onAfterCacheClear` event
|
||||
1. [](#improved)
|
||||
* Improved `CvsFormatter` to attempt to encode non-scalar variables into JSON before giving up
|
||||
* Moved image loading into its own trait to be used by images+static images
|
||||
* Adjusted asset types to enable extension of assets in class [#2937](https://github.com/getgrav/grav/pull/2937)
|
||||
* Composer update for vendor library updates
|
||||
* Updated bundled `composer.phar` to `2.0.0-dev`
|
||||
1. [](#bugfix)
|
||||
* Fixed `MediaUploadTrait::copyUploadedFile()` not adding uploaded media to the collection
|
||||
* Fixed regression in saving media to a new Flex Object [admin#1867](https://github.com/getgrav/grav-plugin-admin/issues/1867)
|
||||
* Fixed `Trying to get property 'username' of non-object` error in Flex [flex-objects#62](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/62)
|
||||
* Fixed retina images not working in Flex [flex-objects#64](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/64)
|
||||
* Fixed plugin initialization in CLI
|
||||
* Fixed broken logic in `Page::topParent()` when dealing with first-level pages
|
||||
* Fixed broken `Flex Page` authorization for groups
|
||||
* Fixed missing `onAdminSave` and `onAdminAfterSave` events when using `Flex Pages` and `Flex Users` [flex-objects#58](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/58)
|
||||
* Fixed new `User Group` allowing bad group name to be saved [admin#1917](https://github.com/getgrav/grav-plugin-admin/issues/1917)
|
||||
* Fixed `Language::getDefault()` returning false and not 'en'
|
||||
* Fixed non-text links in `Excerpts::getExcerptFromHtml`
|
||||
* Fixed CLI commands not properly intializing Plugins so events can fire
|
||||
|
||||
# v1.7.0-rc.12
|
||||
## 06/08/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Changed `Folder::hasChildren` to `Folder::countChildren`
|
||||
* Added `Content Editor` option to user account blueprint
|
||||
1. [](#bugfix)
|
||||
* Fixed new `Flex Page` not having correct form fields for the page type
|
||||
* Fixed new `Flex User` erroring out on save (thanks @mikebi42)
|
||||
* Fixed `Flex Object` request cache clear when saving object
|
||||
* Fixed blueprint value filtering in lists [#2923](https://github.com/getgrav/grav/issues/2923)
|
||||
* Fixed blueprint for `system.pages.hide_empty_folders` [#1925](https://github.com/getgrav/grav/issues/2925)
|
||||
* Fixed file field in `Flex Objects` (use `Grav\Common\Flex\Types\GenericObject` instead of `FlexObject`) [flex-objects#37](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/37)
|
||||
* Fixed saving nested file fields in `Flex Objects` [flex-objects#34](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/34)
|
||||
* JSON Route of homepage with no ‘route’ set is valid [form#425](https://github.com/getgrav/grav-plugin-form/issues/425)
|
||||
|
||||
# v1.7.0-rc.11
|
||||
## 05/14/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added support for native `loading=lazy` attributes on images. Can be set in `system.images.defaults` or per md image with `?loading=lazy` [#2910](https://github.com/getgrav/grav/issues/2910)
|
||||
1. [](#improved)
|
||||
* Added `PageCollection::all()` to mimic Pages class
|
||||
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
|
||||
* Updated `PHPUserAgentParser` to 1.0.0
|
||||
* Improved docblocks
|
||||
* Fixed some phpstan issues
|
||||
* Tighten vendor requirements
|
||||
1. [](#bugfix)
|
||||
* Fix for uppercase image extensions
|
||||
* Fix for `&` errors in HTML when passed to `Excerpts.php`
|
||||
|
||||
# v1.7.0-rc.10
|
||||
## 04/30/2020
|
||||
|
||||
1. [](#new)
|
||||
* Changed `Response::get()` used by **GPM/Admin** to use [Symfony HttpClient v4.4](https://symfony.com/doc/current/components/http_client.html) (`composer install --nodev` required for Git installations)
|
||||
* Added new `Excerpts::processLinkHtml()` method
|
||||
1. [](#bugfix)
|
||||
* Fixed `Flex Pages` admin with PHP `intl` extension enabled when using custom page order
|
||||
* Fixed saving non-numeric-prefix `Flex Page` changing to numeric-prefix [flex-objects#56](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/56)
|
||||
* Copying `Flex Page` in admin does nothing [flex-objects#55](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/55)
|
||||
* Force GPM progress to be between 0-100%
|
||||
|
||||
# v1.7.0-rc.9
|
||||
## 04/27/2020
|
||||
|
||||
1. [](#new)
|
||||
* Support for `webp` image format in Page Media [#1168](https://github.com/getgrav/grav/issues/1168)
|
||||
* Added `Route::getBase()` method
|
||||
1. [](#improved)
|
||||
* Support symlinks when saving `File`
|
||||
1. [](#bugfix)
|
||||
* Fixed flex objects with integer keys not working [#2863](https://github.com/getgrav/grav/issues/2863)
|
||||
* Fixed `Pages::instances()` returning null values when using `Flex Pages` [#2889](https://github.com/getgrav/grav/issues/2889)
|
||||
* Fixed Flex Page parent `header.admin.children_display_order` setting being ignored in Admin [#2881](https://github.com/getgrav/grav/issues/2881)
|
||||
* Implemented missing Flex `$pageCollection->batch()` and `$pageCollection->order()` methods
|
||||
* Fixed user avatar creation for new `Flex Users` when using folder storage
|
||||
* Fixed `Trying to access array offset on value of type null` PHP 7.4 error in `Plugin.php`
|
||||
* Fixed Gregwar Image library using `.jpeg` for cached images, rather use `.jpg`
|
||||
* Fixed `Flex Pages` with `00.home` page not having ordering set
|
||||
* Fixed `Flex Pages` not updating empty content on save [#2890](https://github.com/getgrav/grav/issues/2890)
|
||||
* Fixed creating new Flex User with file storage
|
||||
* Fixed saving new `Flex Object` with custom key
|
||||
* Fixed broken `Plugin::config()` method
|
||||
|
||||
# v1.7.0-rc.8
|
||||
## 03/19/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added `MediaTrait::freeMedia()` method to free media (and memory)
|
||||
* Added `Folder::hasChildren()` method to determine if a folder has child folders
|
||||
1. [](#improved)
|
||||
* Save memory when updating large flex indexes
|
||||
* Better `Content-Encoding` handling in Apache when content compression is disabled [#2619](https://github.com/getgrav/grav/issues/2619)
|
||||
1. [](#bugfix)
|
||||
* Fixed creating new `Flex User` when folder storage has been selected
|
||||
* Fixed some bugs in Flex root page methods
|
||||
* Fixed bad default redirect code in `ControllerResponseTrait::createRedirectResponse()`
|
||||
* Fixed issue with PHP `HTTP_X_HTTP_METHOD_OVERRIDE` [#2847](https://github.com/getgrav/grav/issues/2847)
|
||||
* Fixed numeric usernames not working in `Flex Users`
|
||||
* Implemented missing Flex `$page->move()` method
|
||||
|
||||
# v1.7.0-rc.7
|
||||
## 03/05/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added `Session::regenerateId()` method to properly prevent session fixation issues
|
||||
* Added configuration option `system.strict_mode.blueprint_compat` to maintain old `validation: strict` behavior [#1273](https://github.com/getgrav/grav/issues/1273)
|
||||
1. [](#improved)
|
||||
* Improved Flex events
|
||||
* Updated CLI commands to use the new methods to initialize Grav
|
||||
1. [](#bugfix)
|
||||
* Fixed Flex Pages having broken `isFirst()`, `isLast()`, `prevSibling()`, `nextSibling()` and `adjacentSibling()`
|
||||
* Fixed broken ordering sometimes when saving/moving visible `Flex Page` [#2837](https://github.com/getgrav/grav/issues/2837)
|
||||
* Fixed ordering being lost when saving modular `Flex Page`
|
||||
* Fixed `validation: strict` not working in blueprints (see `system.strict_mode.blueprint_compat` setting) [#1273](https://github.com/getgrav/grav/issues/1273)
|
||||
* Fixed `Blueprint::extend()` and `Blueprint::embed()` not initializing dynamic properties
|
||||
* Fixed fatal error on storing flex flash using new object without a key
|
||||
* Regression: Fixed unchecking toggleable having no effect in Flex forms
|
||||
* Fixed changing page template in Flex Pages [#2828](https://github.com/getgrav/grav/issues/2828)
|
||||
|
||||
# v1.7.0-rc.6
|
||||
## 02/11/2020
|
||||
|
||||
@@ -280,6 +523,106 @@
|
||||
* Optimization: Initialize debugbar only after the configuration has been loaded
|
||||
* Optimization: Combine some early Grav processors into a single one
|
||||
|
||||
# v1.6.29
|
||||
## 12/02/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added basic support for `user/config/versions.yaml`
|
||||
1. [](#improved)
|
||||
* Updated bundled JQuery to latest version `3.5.1`
|
||||
* Forward a `sid` to GPM when downloading a premium package via CLI
|
||||
* Better handling of missing repository index [grav-plugin-admin#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916)
|
||||
* Set `grav_cli` as referrer when using `Response` from CLI
|
||||
* Add option for timeout in `self-upgrade` command [#3013](https://github.com/getgrav/grav/pull/3013)
|
||||
* Allow to set SameSite from system.yaml [#3063](https://github.com/getgrav/grav/pull/3063)
|
||||
* Update media.yaml with some MS Office mimetypes [#3070](https://github.com/getgrav/grav/pull/3070)
|
||||
1. [](#bugfix)
|
||||
* Fixed hardcoded system folder in blueprints, config and language streams
|
||||
* Added `.htaccess` rule to block attempts to use Twig in the request URL
|
||||
* Fix compatibility with Symfony 4.2 and up. [#3048](https://github.com/getgrav/grav/pull/3048)
|
||||
* Fix failing example custom shceduled job. [#3050](https://github.com/getgrav/grav/pull/3050)
|
||||
* Fix for XSS advisory [GHSA-cvmr-6428-87w9](https://github.com/getgrav/grav/security/advisories/GHSA-cvmr-6428-87w9)
|
||||
* Fix uploads_dangerous_extensions checking [#3060](https://github.com/getgrav/grav/pull/3060)
|
||||
* Remove redundant prefixing of `.` to extension [#3060](https://github.com/getgrav/grav/pull/3060)
|
||||
* Check exact extension in checkFilename utility [#3061](https://github.com/getgrav/grav/pull/3061)
|
||||
|
||||
# v1.6.28
|
||||
## 10/07/2020
|
||||
|
||||
1. [](#new)
|
||||
* Back-ported twig `{% cache %}` tag from Grav 1.7
|
||||
* Back-ported `Utils::fullPath()` helper function from Grav 1.7
|
||||
* Back-ported `{{ svg_image() }}` Twig function from Grav 1.7
|
||||
* Back-ported `Folder::countChildren()` function from Grav 1.7
|
||||
1. [](#improved)
|
||||
* Use new `{{ theme_var() }}` enhanced logic from Grav 1.7
|
||||
* Improved `Excerpts` class with fixes and functionality from Grav 1.7
|
||||
* Ensure `onBlueprintCreated()` is initialized first
|
||||
* Do not cache default `404` error page
|
||||
* Composer update of vendor libraries
|
||||
* Switched `Caddyfile` to use new Caddy2 syntax + improved usability
|
||||
1. [](#bugfix)
|
||||
* Fixed Referer reference during GPM calls.
|
||||
* Fixed fatal error with toggled lists
|
||||
|
||||
# v1.6.27
|
||||
## 09/01/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Right trim route for safety
|
||||
* Use the proper ellipsis for summary [#2939](https://github.com/getgrav/grav/pull/2939)
|
||||
* Left pad schedule times with zeros [#2921](https://github.com/getgrav/grav/pull/2921)
|
||||
|
||||
# v1.6.26
|
||||
## 06/08/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added new configuration option to control the supported attributes in markdown links [#2882](https://github.com/getgrav/grav/issues/2882)
|
||||
1. [](#bugfix)
|
||||
* Fixed blueprint for `system.pages.hide_empty_folders` [#1925](https://github.com/getgrav/grav/issues/2925)
|
||||
* JSON Route of homepage with no ‘route’ set is valid
|
||||
* Fix case-insensitive search of location header [form#425](https://github.com/getgrav/grav-plugin-form/issues/425)
|
||||
|
||||
# v1.6.25
|
||||
## 05/14/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
|
||||
* Updated `PHPUserAgentParser` to 1.0.0
|
||||
* Bump `Go` to version 1.13 in `travis.yaml`
|
||||
|
||||
# v1.6.24
|
||||
## 04/27/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added support for `X-Forwarded-Host` [#2891](https://github.com/getgrav/grav/pull/2891)
|
||||
* Disable XDebug in Travis builds
|
||||
|
||||
# v1.6.23
|
||||
## 03/19/2020
|
||||
|
||||
1. [](#new)
|
||||
* Moved `Parsedown` 1.6 and `ParsedownExtra` 0.7 into `Grav\Framework\Parsedown` to allow fixes
|
||||
* Added `aliases.php` with references to direct `\Parsedown` and `\ParsedownExtra` references
|
||||
1. [](#improved)
|
||||
* Upgraded `jQuery` to latest 3.4.1 version [#2859](https://github.com/getgrav/grav/issues/2859)
|
||||
1. [](#bugfix)
|
||||
* Fixed PHP 7.4 issue in ParsedownExtra [#2832](https://github.com/getgrav/grav/issues/2832)
|
||||
* Fix for [user reported](https://twitter.com/OriginalSicksec) CVE path-based open redirect
|
||||
* Fix for `stream_set_option` error with PHP 7.4 via Toolbox#28 [#2850](https://github.com/getgrav/grav/issues/2850)
|
||||
|
||||
# v1.6.22
|
||||
## 03/05/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added `Pages::reset()` method
|
||||
1. [](#improved)
|
||||
* Updated Negotiation library to address issues [#2513](https://github.com/getgrav/grav/issues/2513)
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with search plugins not being able to switch between page translations
|
||||
* Fixed issues with `Pages::baseRoute()` not picking up active language reliably
|
||||
* Reverted `validation: strict` fix as it breaks sites, see [#1273](https://github.com/getgrav/grav/issues/1273)
|
||||
|
||||
# v1.6.21
|
||||
## 02/11/2020
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ The underlying architecture of Grav is designed to use well-established and _bes
|
||||
|
||||
# Requirements
|
||||
|
||||
- PHP 7.1.3 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
|
||||
- PHP 7.3.6 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
|
||||
|
||||
15
SECURITY.md
Normal file
15
SECURITY.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We are focusing our security updates on the following versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.7.x | :white_check_mark: |
|
||||
| 1.6.x | :white_check_mark: |
|
||||
| < 1.6 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please contact contact@getgrav.org with a detailed explaination of the security issue found and we will work with you to get it resolved as fast as possible.
|
||||
256
UPGRADE-1.7.md
256
UPGRADE-1.7.md
@@ -1,4 +1,6 @@
|
||||
# UPGRADE FROM 1.6 TO 1.7
|
||||
# UPGRADE FROM 1.6 TO 1.7.RC17
|
||||
|
||||
Grav 1.7 REQUIRES PHP 7.3.6
|
||||
|
||||
## ADMINISTRATORS
|
||||
|
||||
@@ -6,15 +8,38 @@
|
||||
|
||||
* Please run `bin/grav yamllinter` to find any YAML parsing errors in your site. You should run this command before and after upgrade. Grav falls back to older YAML parser if it detects an error, but it will slow down your site.
|
||||
|
||||
## Forms
|
||||
|
||||
* **BC BREAK** Fixed `validation: strict`. Please search through all your forms if you were using this feature. If you were, either remove the line or test if the form still works.
|
||||
* Added configuration option `system.strict_mode.blueprint_compat` to maintain old `validation: strict` behavior
|
||||
* If you disable compatibiity, form validation will be much more strict (recommended, but may break existing forms)
|
||||
|
||||
* DOCUMENT NEW FORM FLASH!
|
||||
|
||||
### Pages
|
||||
|
||||
* **BC BREAK** Fixed 404 error page when you go to non-routable page with routable child pages under it. Now you get redirected to the first routable child page instead. This is probably what you wanted in the first place. If you do not want this new behavior, you need to **TODO**
|
||||
|
||||
### Media
|
||||
|
||||
* Support for `webp` image format
|
||||
* Markdown: Added support for native `loading=lazy` attributes on images. Can be set in `system.images.defaults` or per md image with `?loading=lazy`
|
||||
* Added ability to `noprocess` specific items only in Link/Image Excerpts, e.g. `http://foo.com/page?id=foo&target=_blank&noprocess=id`
|
||||
|
||||
### Multi-language
|
||||
|
||||
* Improved language support
|
||||
* **BC BREAK** Please check that your fallback languages are correct. Old implementation had a fallback to any other language, now only default language is being used unless you use `system.languages.content_fallback` configuration option to override the default behavior.
|
||||
|
||||
### Admin
|
||||
|
||||
* If you upgrade from older 1.7 RC, you need to go to Flex Objects plugin settings and turn on `Pages`, `User Accounts` and `User Groups` directories (upgrading 1.6 automatically turns them on)
|
||||
* Disabling `User Accounts` and `User Groups` directories in Flex Objects plugin should be kept enabled; fine tuned **ACL** may not work without
|
||||
|
||||
### Sessions
|
||||
|
||||
* Session ID now changes on login to prevent session fixation issues
|
||||
|
||||
### CLI
|
||||
|
||||
* Added new `bin/grav server` CLI command to easily run Symfony or PHP built-in web servers
|
||||
@@ -22,7 +47,7 @@
|
||||
* Improved `Scheduler` cron command check and more useful CLI information
|
||||
* Added new `-r <job-id>` option for Scheduler CLI command to force-run a job
|
||||
* Improved `bin/grav yamllinter` CLI command by adding an option to find YAML Linting issues from the whole site or custom folder
|
||||
|
||||
|
||||
### Configuration
|
||||
|
||||
* Added new configuration option `system.debugger.provider` to choose between debugbar and clockwork
|
||||
@@ -31,6 +56,8 @@
|
||||
* Added new configuration option `system.languages.include_default_lang_file_extension` to keep default language in `.md` files if set to `false`
|
||||
* Added new configuration option `system.languages.content_fallback` to set fallback content languages individually for every language
|
||||
* Added new configuration option `security.sanitize_svg` to remove potentially dangerous code from SVG files
|
||||
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
|
||||
* Added new configuration option `system.strict_mode.blueprint_compat` to maintain old `validation: strict` behavior
|
||||
|
||||
### Debugging
|
||||
|
||||
@@ -40,56 +67,186 @@
|
||||
|
||||
## DEVELOPERS
|
||||
|
||||
### Use composer autoloader
|
||||
|
||||
* Please add `composer.json` file to your plugin and run `composer update --no-dev` (and remember to keep it updated):
|
||||
|
||||
composer.json
|
||||
```json
|
||||
{
|
||||
"name": "getgrav/grav-plugin-example",
|
||||
"type": "grav-plugin",
|
||||
"description": "Example plugin for Grav CMS",
|
||||
"keywords": ["example", "plugin"],
|
||||
"homepage": "https://github.com/getgrav/grav-plugin-example",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "...",
|
||||
"email": "...",
|
||||
"homepage": "...",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getgrav/grav-plugin-example/issues",
|
||||
"docs": "https://github.com/getgrav/grav-plugin-example/blob/master/README.md"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\Plugin\\Example\\": "classes/",
|
||||
"Grav\\Plugin\\Console\\": "cli/"
|
||||
},
|
||||
"classmap": [
|
||||
"example.php"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [Composer schema](https://getcomposer.org/doc/04-schema.md)
|
||||
|
||||
* Please use autoloader instead of `require` in the code:
|
||||
|
||||
example.php
|
||||
```php
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onPluginsInitialized' => [
|
||||
// This is only required in Grav 1.6. Grav 1.7 automatically calls $plugin->autolaod() method.
|
||||
['autoload', 100000],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Composer autoload.
|
||||
*
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public function autoload(): \Composer\Autoload\ClassLoader
|
||||
{
|
||||
return require __DIR__ . '/vendor/autoload.php';
|
||||
}
|
||||
```
|
||||
|
||||
* Plugins & Themes: Call `$plugin->autoload()` and `$theme->autoload()` automatically when object gets initialized
|
||||
* Make sure your code does not use `require` or `include` for loading classes
|
||||
|
||||
### Plugin/Theme Blueprints (`blueprints.yaml`)
|
||||
|
||||
* Please add:
|
||||
```yaml
|
||||
slug: folder-name
|
||||
type: plugin|theme
|
||||
```
|
||||
* Make sure you update your dependencies. I recommend setting Grav to either 1.6 or 1.7 and update your code/vendor to PHP 7.1
|
||||
```yaml
|
||||
dependencies:
|
||||
- { name: grav, version: '>=1.6.0' }
|
||||
```
|
||||
|
||||
### Sessions
|
||||
|
||||
* Added `Session::regenerateId()` method to properly prevent session fixation issues
|
||||
|
||||
### ACL
|
||||
|
||||
* `user.authorize()` now requires user to be authorized (passed 2FA check), unless the rule contains `login` in its name.
|
||||
* Added support for more advanced ACL (CRUD)
|
||||
|
||||
* **BC BREAK** `user.authorize()` and Flex `object.isAuthorized()` now have two deny states: `false` and `null`.
|
||||
* **BC BREAK** `user.authorize()` and Flex `object.isAuthorized()` now have two deny states: `false` and `null`.
|
||||
|
||||
Make sure you do not have strict checks against false: `$user->authorize($action) === false` (PHP) or `user.authorize(action) is same as(false)` (Twig).
|
||||
Make sure you do not have strict checks against false: `$user->authorize($action) === false` (PHP) or `user.authorize(action) is same as(false)` (Twig).
|
||||
|
||||
For the negative checks you should be using `!user->authorize($action)` (PHP) or `not user.authorize(action)` (Twig).
|
||||
|
||||
The change has been done to allow strong deny rules by chaining the actions if previous ones do not match: `user.authorize(action1) ?? user.authorize(action2) ?? user.authorize(action3)`.
|
||||
|
||||
|
||||
Note that Twig function `authorize()` will still **keeps** the old behavior!
|
||||
|
||||
### Pages
|
||||
|
||||
* Added experimental support for `Flex Pages` (**Flex-Objects** plugin required)
|
||||
* Added page specific permissions support for `Flex Pages`
|
||||
* Added experimental support for `Flex Pages` in the frontend (not recommended to use yet)
|
||||
* Admin uses `Flex Pages` by default (can be disabled from `Flex-Objects` plugin)
|
||||
* Added page specific admin permissions support for `Flex Pages`
|
||||
* Added root page support for `Flex Pages`
|
||||
* Fixed wrong `Pages::dispatch()` calls (with redirect) when we really meant to call `Pages::find()`
|
||||
* Added `Pages::getCollection()` method
|
||||
* Moved `collection()` and `evaluate()` logic from `Page` class into `Pages` class
|
||||
* **DEPRECATED** `$page->modular()` in favor of `$page->isModule()`
|
||||
* **DEPRECATED** `PageCollectionInterface::nonModular()` in favor of `PageCollectionInterface::pages()`
|
||||
* **DEPRECATED** `PageCollectionInterface::modular()` in favor of `PageCollectionInterface::modules()`
|
||||
|
||||
* **BC BREAK** Fixed `Page::modular()` and `Page::modularTwig()` returning `null` for folders and other non-initialized pages. Should not affect your code unless you were checking against `false` or `null`.
|
||||
* **BC BREAK** Always use `\Grav\Common\Page\Interfaces\PageInterface` instead of `\Grav\Common\Page\Page` in method signatures
|
||||
* Admin now uses `Flex Pages` by default, collection will behave in slightly different way
|
||||
* **BC BREAK** `$page->topParent()` may return page itself instead of null
|
||||
|
||||
### Media
|
||||
|
||||
* Added `MediaTrait::freeMedia()` method to free media (and memory)
|
||||
* Added support for uploading and deleting images directly in `Media` by using PSR-7
|
||||
* Adjusted asset types to enable extension of assets in class
|
||||
* **BC BREAK** Media no longer extends `Getters`, accessing `$media->$filename` no longer works, use `$media[$filename]` instead!
|
||||
|
||||
### Markdown
|
||||
|
||||
* **BC BREAK** Upgraded Parsedown to 1.7 for Parsedown-Extra 0.8. Plugins that extend Parsedown may need a fix to render as HTML
|
||||
* Added new `Excerpts::processLinkHtml()` method
|
||||
|
||||
### Users
|
||||
|
||||
* Added experimental support for `Flex Users` in the frontend (not recommended to use yet)
|
||||
* Admin uses `Flex Users` by default (can be disabled from `Flex-Objects` plugin)
|
||||
* Improved `Flex Users`: obey blueprints and allow Flex to be used in admin only
|
||||
* Improved `Flex Users`: user and group ACL now supports deny permissions
|
||||
* Changed `UserInterface::authorize()` to return `null` having the same meaning as `false` if access is denied because of no matching rule
|
||||
* **DEPRECATED** `Grav\Common\User\Group` in favor of `$grav['user_groups']`, which contains Flex UserGroup collection
|
||||
* **DEPRECATED** `\Grav\Common\User\Group` in favor of `$grav['user_groups']`, which contains Flex UserGroup collection
|
||||
* **BC BREAK** Always use `\Grav\Common\User\Interfaces\UserInterface` instead of `\Grav\Common\User\User` in method signatures
|
||||
|
||||
### Flex
|
||||
|
||||
* Do not use `Framework` Flex classes directly, it's better to use or extend classes under `Grav\Common\Flex\Types\Generic` namespace
|
||||
* Added `$grav['flex']` to access all registered Flex Directories
|
||||
* Added `FlexRegisterEvent` which triggers when `$grav['flex']` is being accessed the first time
|
||||
* Added `hasFlexFeature()` method to test if `FlexObject` or `FlexCollection` implements a given feature
|
||||
* Added `getFlexFeatures()` method to return all features that `FlexObject` or `FlexCollection` implements
|
||||
* Added `FlexStorage::getMetaData()` to get updated object meta information for listed keys
|
||||
* `FlexDirectory::getObject()` can now be called without any parameters to create a new object
|
||||
* Flex Directory: Implemented customizable configuration per flex type
|
||||
* **DEPRECATED** `FlexDirectory::update()` and `FlexDirectory::remove()`
|
||||
* **BC BREAK** Moved all Flex type classes under `Grav\Common\Flex`
|
||||
* **BC BREAK** `FlexStorageInterface::getStoragePath()` and `getMediaPath()` can now return null
|
||||
* **BC BREAK** Flex objects no longer return temporary key if they do not have one; empty key is returned instead
|
||||
* **BC BREAK** Added reload argument to `FlexStorageInterface::getMetaData()`
|
||||
* You can add `edit_list.html.twig` file to a form field in order to customize look in the listing view
|
||||
|
||||
### Templating
|
||||
|
||||
* Added support for Twig 2.12 (still using Twig 1.42)
|
||||
* Added support for Twig 2.12 (using Twig 1.43)
|
||||
* Added a new `{% cache %}` Twig tag eliminating need for `twigcache` extension.
|
||||
* Added `array_diff()` twig function
|
||||
* Added `template_from_string()` twig function
|
||||
* Improved twig `|array` filter to work with iterators and objects with `toArray()` method
|
||||
* Improved twig `authorize()` function to work better with nested rule parameters
|
||||
* Added a new `svg_image()` twig function to make it easier to 'include' SVG source in Twig
|
||||
* Improved `url()` twig function to take third parameter (`true`) to return URL on non-existing file instead of returning false
|
||||
* Improved `|array` twig filter to work with iterators and objects with `toArray()` method
|
||||
* Improved `authorize()` twig function to work better with nested rule parameters
|
||||
* Improved `|yaml_serialize` twig filter: added support for `JsonSerializable` objects and other array-like objects
|
||||
* Added `themes` to cached blueprints and configuration
|
||||
* Added default templates for `external.html.twig`, `default.html.twig`, and `modular.html.twig`
|
||||
|
||||
### Multi-language
|
||||
|
||||
@@ -98,28 +255,101 @@
|
||||
* Added `Language::getPageExtensions()` to get full list of supported page language extensions
|
||||
* **BC BREAK** Fixed `Language::getFallbackPageExtensions()` to fall back only to default language instead of going through all languages
|
||||
|
||||
### Blueprints
|
||||
|
||||
* Added `flatten_array` filter to form field validation
|
||||
* Added support for `security@: or: [admin.super, admin.pages]` in blueprints (nested AND/OR mode support)
|
||||
* Blueprint validation: Added `validate: value_type: bool|int|float|string|trim` to `array` to filter all the values inside the array
|
||||
* If your plugins has blueprints folder, initializing it in the event will be too late. Do this instead:
|
||||
|
||||
```php
|
||||
class MyPlugin extends Plugin
|
||||
{
|
||||
/** @var array */
|
||||
public $features = [
|
||||
'blueprints' => 0, // Use priority 0
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
* Use `Symfony EventDispatcher` directly instead of `rockettheme/toolbox` wrapper.
|
||||
* Added `$grav->dispatchEvent()` method for PSR-14 events
|
||||
* Added `PluginsLoadedEvent` which triggers after plugins have been loaded but not yet initialized
|
||||
* Added `SessionStartEvent` which triggers when session is started
|
||||
* Added `FlexRegisterEvent` which triggers when `$grav['flex']` is being accessed the first time
|
||||
* Added `PermissionsRegisterEvent` which triggers when `$grav['permissions']` is being accessed the first time
|
||||
* Added `onAfterCacheClear` event
|
||||
* Check `onAdminTwigTemplatePaths` event, it should NOT be:
|
||||
|
||||
```php
|
||||
public function onAdminTwigTemplatePaths($event)
|
||||
{
|
||||
// This code breaks all the other plugins in admin, including Flex Objects
|
||||
$event['paths'] = [__DIR__ . '/admin/themes/grav/templates'];
|
||||
}
|
||||
```
|
||||
but:
|
||||
```php
|
||||
public function onAdminTwigTemplatePaths($event)
|
||||
{
|
||||
// Add plugin template path for admin.
|
||||
$paths = $event['paths'];
|
||||
$paths[] = __DIR__ . '/admin/themes/grav/templates';
|
||||
$event['paths'] = $paths;
|
||||
}
|
||||
```
|
||||
|
||||
### Misc
|
||||
|
||||
* Added `Utils::isAssoc()` and `Utils::isNegative()` helper methods
|
||||
* Added `Utils::simpleTemplate()` method for very simple variable templating
|
||||
* Added `Utils::fullPath()` to get the full path to a file be it stream, relative, etc.
|
||||
* Support customizable null character replacement in `CSVFormatter::decode()`
|
||||
* Added new `Security::sanitizeSVG()` function
|
||||
* Added `$grav->close()` method to properly terminate the request with a response
|
||||
* Added `Folder::countChildren()` method to determine if a folder has child folders
|
||||
* Support symlinks when saving `File`
|
||||
* Added `Route::getBase()` method
|
||||
* **BC BREAK** Make `Route` objects immutable. This means that you need to do: `{% set route = route.withExtension('.html') %}` (for all `withX` methods) to keep the updated version.
|
||||
* Better `Content-Encoding` handling in Apache when content compression is disabled
|
||||
* Added a `Uri::getAllHeaders()` compatibility function
|
||||
|
||||
### CLI
|
||||
|
||||
* **BC BREAK** Many plugins initialize Grav in a wrong way, it is not safe to initialize plugins and theme by yourself
|
||||
* Following calls require Grav 1.6.21 or later, so it is recommended to set Grav dependency to that version
|
||||
* Inside `serve()` method:
|
||||
* Call `$this->setLanguage($langCode);` before doing anything else if you want to set the language (or use default)
|
||||
* Call one of following:
|
||||
* `$this->initializeGrav();` Already called if you're in `bin/plugin`, otherwise you may need to call this one
|
||||
* `$this->initializePlugins();` This initializes grav, plugins (up to `onPluginsInitialized`)
|
||||
* `$this->initializeThemes();` This initializes grav, plugins and theme
|
||||
* `$this->initializePages();` This initializes grav, plugins, theme and everything needed by pages
|
||||
* It is a good idea to prefix your CLI command classes with your plugin name, otherwise there may be naming conflicts (we already have some!)
|
||||
|
||||
### Composer dependencies
|
||||
|
||||
* Updated Symfony Components to 4.4, please update any deprecated features in your code
|
||||
* **BC BREAK** Please run `bin/grav yamllinter -f user://` to find any YAML parsing errors in your site (including your plugins and themes).
|
||||
|
||||
## PLUGINS
|
||||
|
||||
### Admin
|
||||
|
||||
* **BC BREAK** Admin will not initialize frontend pages anymore, this has been done to greatly speed up Admin plugin.
|
||||
* Added `Content Editor` option to user account blueprint
|
||||
|
||||
* **BC BREAK** Admin will not initialize frontend pages anymore, this has been done to greatly speed up Admin plugin.
|
||||
|
||||
Please call `$grav['admin']->enablePages()` or `{% do admin.enablePages() %}` if you need to access frontend pages. This call can be safely made multiple times.
|
||||
|
||||
|
||||
If you're using `Flex Pages`, please use Flex Directory instead, it will make your code so much faster.
|
||||
|
||||
* Admin now uses Flex for editing `Accounts` and `Pages`. If your plugin hooks into either of those, please make sure they still work.
|
||||
|
||||
* Admin cache is enabled by default, make sure your plugin clears cache when needed. Please avoid clearing all cache!
|
||||
|
||||
### Shortcode Core
|
||||
|
||||
* **DEPRECATED** Every shortcode needs to have `init()` method, classes without it will stop working in the future.
|
||||
|
||||
Binary file not shown.
@@ -12,60 +12,65 @@
|
||||
"homepage": "https://getgrav.org",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"php": "^7.3.6 || ^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-dom": "*",
|
||||
"symfony/polyfill-iconv": "^1.9",
|
||||
"symfony/polyfill-php72": "^1.9",
|
||||
"symfony/polyfill-php73": "^1.9",
|
||||
"ext-libxml": "*",
|
||||
"symfony/polyfill-mbstring": "~1.20",
|
||||
"symfony/polyfill-iconv": "^1.20",
|
||||
"symfony/polyfill-php74": "^1.20",
|
||||
"symfony/polyfill-php80": "^1.20",
|
||||
"psr/simple-cache": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"kodus/psr7-server": "*",
|
||||
"nyholm/psr7": "^1.0",
|
||||
"twig/twig": "~1.0",
|
||||
"nyholm/psr7": "^1.3",
|
||||
"twig/twig": "~1.44",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"erusev/parsedown-extra": "~0.8",
|
||||
"symfony/contracts": "~1.0",
|
||||
"symfony/yaml": "~4.4.0",
|
||||
"symfony/console": "~4.4.0",
|
||||
"symfony/event-dispatcher": "~4.4.0",
|
||||
"symfony/var-dumper": "~4.4.0",
|
||||
"symfony/process": "~4.4.0",
|
||||
"doctrine/cache": "^1.8",
|
||||
"doctrine/collections": "^1.5",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"filp/whoops": "~2.2",
|
||||
"symfony/contracts": "~1.1",
|
||||
"symfony/yaml": "4.4.16",
|
||||
"symfony/console": "~4.4",
|
||||
"symfony/event-dispatcher": "~4.4",
|
||||
"symfony/var-dumper": "~4.4",
|
||||
"symfony/process": "~4.4",
|
||||
"doctrine/cache": "^1.10",
|
||||
"doctrine/collections": "^1.6",
|
||||
"guzzlehttp/psr7": "^1.7",
|
||||
"filp/whoops": "~2.9",
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "2.*",
|
||||
"donatj/phpuseragentparser": "~0.10",
|
||||
"pimple/pimple": "~3.2",
|
||||
"rockettheme/toolbox": "dev-develop",
|
||||
"maximebf/debugbar": "~1.0",
|
||||
"league/climate": "^3.4",
|
||||
"monolog/monolog": "~1.25",
|
||||
"gregwar/image": "dev-php8",
|
||||
"gregwar/cache": "dev-php8",
|
||||
"donatj/phpuseragentparser": "~1.1",
|
||||
"pimple/pimple": "~3.3",
|
||||
"rockettheme/toolbox": "~1.5",
|
||||
"maximebf/debugbar": "~1.16",
|
||||
"league/climate": "^3.6",
|
||||
"antoligy/dom-string-iterators": "^1.0",
|
||||
"miljar/php-exif": "^0.6.4",
|
||||
"composer/ca-bundle": "^1.0",
|
||||
"miljar/php-exif": "^0.6",
|
||||
"composer/ca-bundle": "^1.2",
|
||||
"dragonmantank/cron-expression": "^1.2",
|
||||
"phive/twig-extensions-deferred": "^1.0",
|
||||
"willdurand/negotiation": "^2.3",
|
||||
"itsgoingd/clockwork": "@beta",
|
||||
"enshrined/svg-sanitize": "~0.1"
|
||||
"willdurand/negotiation": "^3.0",
|
||||
"itsgoingd/clockwork": "^5.0",
|
||||
"enshrined/svg-sanitize": "~0.13",
|
||||
"symfony/http-client": "^4.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.4",
|
||||
"codeception/codeception": "^2.5",
|
||||
"phpstan/phpstan": "^0.11",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.11",
|
||||
"phpunit/php-code-coverage": "~6.0",
|
||||
"fzaninotto/faker": "^1.8",
|
||||
"phpunit/php-code-coverage": "~6.1",
|
||||
"fzaninotto/faker": "^1.9",
|
||||
"victorjonsson/markdowndocs": "dev-master"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "Recommended for better performance",
|
||||
"ext-iconv": "Recommended for better performance",
|
||||
"ext-zend-opcache": "Recommended for better performance",
|
||||
"ext-intl": "Recommended for multi-language sites",
|
||||
"ext-memcache": "Needed to support Memcache servers",
|
||||
@@ -75,20 +80,21 @@
|
||||
"config": {
|
||||
"apcu-autoloader": true,
|
||||
"platform": {
|
||||
"php": "7.1.3"
|
||||
"php": "7.3.6"
|
||||
}
|
||||
},
|
||||
"repositories": [{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/itsgoingd/clockwork"
|
||||
"url": "https://github.com/getgrav/Cache"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/rockettheme/toolbox"
|
||||
"url": "https://github.com/getgrav/Image"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
@@ -105,12 +111,13 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"api-17": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.17.md",
|
||||
"api-16": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.16.md",
|
||||
"api-15": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.md",
|
||||
"post-create-project-cmd": "bin/grav install",
|
||||
"phpstan": "vendor/bin/phpstan analyse -l 3 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=340M",
|
||||
"phpstan-framework": "vendor/bin/phpstan analyse -l 7 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=128M",
|
||||
"phpstan-plugins": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=300M",
|
||||
"phpstan": "vendor/bin/phpstan analyse -l 3 -c ./tests/phpstan/phpstan.neon --memory-limit=400M system/src",
|
||||
"phpstan-framework": "vendor/bin/phpstan analyse -l 7 -c ./tests/phpstan/phpstan.neon --memory-limit=256M system/src/Grav/Framework",
|
||||
"phpstan-plugins": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/plugins.neon --memory-limit=400M user/plugins",
|
||||
"test": "vendor/bin/codecept run unit",
|
||||
"test-windows": "vendor\\bin\\codecept run unit"
|
||||
},
|
||||
|
||||
2644
composer.lock
generated
2644
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -10,14 +10,15 @@
|
||||
namespace Grav;
|
||||
|
||||
\define('GRAV_REQUEST_TIME', microtime(true));
|
||||
\define('GRAV_PHP_MIN', '7.1.3');
|
||||
\define('GRAV_PHP_MIN', '7.3.6');
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
die(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli-server') {
|
||||
$symfony_server = strpos(getenv('_'), 'symfony') !== false;
|
||||
$symfony_server = stripos(getenv('_'), 'symfony') !== false || stripos($_SERVER['SERVER_SOFTWARE'], 'symfony
|
||||
') !== false;
|
||||
if (!isset($_SERVER['PHP_CLI_ROUTER']) && !$symfony_server) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
|
||||
4
system/assets/jquery/jquery-3.x.min.js
vendored
4
system/assets/jquery/jquery-3.x.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -39,12 +39,13 @@ form:
|
||||
.command:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.COMMAND
|
||||
placeholder: 'cd ~;ls -lah;'
|
||||
placeholder: 'ls'
|
||||
validate:
|
||||
required: true
|
||||
.args:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
|
||||
placeholder: '-lah'
|
||||
.at:
|
||||
type: cron
|
||||
label: PLUGIN_ADMIN.SCHEDULER_RUNAT
|
||||
|
||||
@@ -241,13 +241,15 @@ form:
|
||||
type: commalist
|
||||
|
||||
pages.hide_empty_folders:
|
||||
type: selectize
|
||||
size: large
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
|
||||
help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
|
||||
classes: fancy
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: commalist
|
||||
type: bool
|
||||
|
||||
pages.url_taxonomy_filters:
|
||||
type: toggle
|
||||
@@ -544,6 +546,15 @@ form:
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
pages.markdown.valid_link_attributes:
|
||||
type: selectize
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES
|
||||
help: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES_HELP
|
||||
placeholder: "rel, target, id, class, classes"
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
caching:
|
||||
type: tab
|
||||
@@ -730,6 +741,64 @@ form:
|
||||
size: small
|
||||
label: PLUGIN_ADMIN.REDIS_PASSWORD
|
||||
|
||||
flex_caching:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.FLEX_CACHING
|
||||
|
||||
flex.cache.index.enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
flex.cache.index.lifetime:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME
|
||||
default: 60
|
||||
validate:
|
||||
type: int
|
||||
|
||||
flex.cache.object.enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
flex.cache.object.lifetime:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME
|
||||
default: 600
|
||||
validate:
|
||||
type: int
|
||||
|
||||
flex.cache.render.enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
flex.cache.render.lifetime:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME
|
||||
default: 600
|
||||
validate:
|
||||
type: int
|
||||
|
||||
twig:
|
||||
type: tab
|
||||
title: PLUGIN_ADMIN.TWIG_TEMPLATING
|
||||
@@ -777,7 +846,8 @@ form:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES
|
||||
help: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES_HELP
|
||||
highlight: 0
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
@@ -1114,13 +1184,24 @@ form:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION
|
||||
help: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION_HELP
|
||||
highlight: 0
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
images.defaults.loading:
|
||||
type: select
|
||||
size: small
|
||||
label: PLUGIN_ADMIN.IMAGES_LOADING
|
||||
help: PLUGIN_ADMIN.IMAGES_LOADING_HELP
|
||||
highlight: auto
|
||||
options:
|
||||
auto: Auto
|
||||
lazy: Lazy
|
||||
eager: Eager
|
||||
|
||||
images.seofriendly:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.IMAGES_SEOFRIENDLY
|
||||
@@ -1154,7 +1235,6 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
media.allowed_fallback_types:
|
||||
type: selectize
|
||||
size: large
|
||||
@@ -1264,6 +1344,12 @@ form:
|
||||
label: PLUGIN_ADMIN.SESSION_PATH
|
||||
help: PLUGIN_ADMIN.SESSION_PATH_HELP
|
||||
|
||||
session.samesite:
|
||||
type: text
|
||||
size: small
|
||||
label: PLUGIN_ADMIN.SESSION_SAMESITE
|
||||
help: PLUGIN_ADMIN.SESSION_SAMESITE_HELP
|
||||
|
||||
session.split:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.SESSION_SPLIT
|
||||
@@ -1432,17 +1518,68 @@ form:
|
||||
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
|
||||
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
|
||||
|
||||
accounts.type:
|
||||
type: hidden
|
||||
http_x_forwarded.protocol:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED_PROTO Enabled
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
accounts.storage:
|
||||
type: hidden
|
||||
http_x_forwarded.host:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED_HOST Enabled
|
||||
highlight: 0
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
http_x_forwarded.port:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED_PORT Enabled
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
http_x_forwarded.ip:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED IP Enabled
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
strict_mode.blueprint_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT
|
||||
highlight: 0
|
||||
default: 0
|
||||
help: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
strict_mode.yaml_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
|
||||
highlight: 1
|
||||
default: 1
|
||||
highlight: 0
|
||||
default: 0
|
||||
help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
@@ -1453,8 +1590,8 @@ form:
|
||||
strict_mode.twig_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
|
||||
highlight: 1
|
||||
default: 1
|
||||
highlight: 0
|
||||
default: 0
|
||||
help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
@@ -1472,18 +1609,21 @@ form:
|
||||
title: PLUGIN_ADMIN.EXPERIMENTAL
|
||||
underline: true
|
||||
|
||||
flex_pages:
|
||||
type: section
|
||||
title: Flex Pages
|
||||
# flex_pages:
|
||||
# type: section
|
||||
# title: Flex Pages
|
||||
#
|
||||
# pages.type:
|
||||
# type: select
|
||||
# label: PLUGIN_ADMIN.PAGES_TYPE
|
||||
# highlight: regular
|
||||
# help: PLUGIN_ADMIN.PAGES_TYPE_HELP
|
||||
# options:
|
||||
# regular: PLUGIN_ADMIN.REGULAR
|
||||
# flex: PLUGIN_ADMIN.FLEX
|
||||
|
||||
pages.type:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PAGES_TYPE
|
||||
highlight: stable
|
||||
help: PLUGIN_ADMIN.PAGES_TYPE_HELP
|
||||
options:
|
||||
regular: PLUGIN_ADMIN.REGULAR
|
||||
flex: PLUGIN_ADMIN.FLEX
|
||||
type: hidden
|
||||
|
||||
flex_accounts:
|
||||
type: section
|
||||
@@ -1506,61 +1646,3 @@ form:
|
||||
options:
|
||||
file: PLUGIN_ADMIN.FILE
|
||||
folder: PLUGIN_ADMIN.FOLDER
|
||||
|
||||
flex_caching:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.FLEX_CACHING
|
||||
|
||||
flex.cache.index.enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
flex.cache.index.lifetime:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME
|
||||
default: 60
|
||||
validate:
|
||||
type: int
|
||||
|
||||
flex.cache.object.enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
flex.cache.object.lifetime:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME
|
||||
default: 600
|
||||
validate:
|
||||
type: int
|
||||
|
||||
flex.cache.render.enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
flex.cache.render.lifetime:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME
|
||||
default: 600
|
||||
validate:
|
||||
type: int
|
||||
|
||||
@@ -2,7 +2,7 @@ title: Flex User Accounts
|
||||
description: Manage your User Accounts in Flex.
|
||||
type: flex-objects
|
||||
|
||||
# Deprecated in Grav 1.7.0-rc.4: file was renamed.
|
||||
# Deprecated in Grav 1.7.0-rc.4: file was renamed to user-accounts.yaml
|
||||
extends@:
|
||||
type: user-accounts
|
||||
context: blueprints://flex
|
||||
|
||||
17
system/blueprints/flex/configure/compat.yaml
Normal file
17
system/blueprints/flex/configure/compat.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
form:
|
||||
compatibility:
|
||||
type: tab
|
||||
title: Compatibility
|
||||
fields:
|
||||
object.compat.events:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: Admin event compatibility
|
||||
help: Enables onAdminSave and onAdminAfterSave events for plugins
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
@@ -7,6 +7,10 @@ extends@:
|
||||
type: default
|
||||
context: blueprints://pages
|
||||
|
||||
#
|
||||
# HIGHLY SPECIALIZED FLEX TYPE, AVOID USING PAGES AS BASE FOR YOUR OWN TYPE.
|
||||
#
|
||||
|
||||
# Flex configuration
|
||||
config:
|
||||
# Administration Configuration (needs Flex Objects plugin)
|
||||
@@ -99,6 +103,9 @@ config:
|
||||
label: PLUGIN_ADMIN.ADD
|
||||
|
||||
edit:
|
||||
title:
|
||||
template: "{% if object.root %}Root <small>( <root> ){% else %}{{ (form.value('header.title') ?? form.value('folder'))|e }} <small>( {{ (object.getRoute().toString(false) ?: '/')|e }} )</small>{% endif %}"
|
||||
|
||||
# TODO: not used yet
|
||||
buttons:
|
||||
back:
|
||||
@@ -181,6 +188,13 @@ config:
|
||||
- title
|
||||
- name
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
fields:
|
||||
import@:
|
||||
type: configure/compat
|
||||
context: blueprints://flex
|
||||
|
||||
# Regular form definition
|
||||
form:
|
||||
fields:
|
||||
|
||||
@@ -7,6 +7,10 @@ extends@:
|
||||
type: account
|
||||
context: blueprints://user
|
||||
|
||||
#
|
||||
# HIGHLY SPECIALIZED FLEX TYPE, AVOID USING USER ACCOUNTS AS BASE FOR YOUR OWN TYPE.
|
||||
#
|
||||
|
||||
# Flex configuration
|
||||
config:
|
||||
# Administration Configuration (needs Flex Objects plugin)
|
||||
@@ -52,6 +56,8 @@ config:
|
||||
username:
|
||||
link: edit
|
||||
search: true
|
||||
field:
|
||||
label: PLUGIN_ADMIN.USERNAME
|
||||
email:
|
||||
search: true
|
||||
fullname:
|
||||
@@ -66,7 +72,7 @@ config:
|
||||
# Edit view
|
||||
edit:
|
||||
title:
|
||||
template: '{{ object.fullname ?? object.username }} <{{ object.email }}>'
|
||||
template: "{{ form.value('fullname') ?? form.value('username') }} <{{ form.value('email') }}>"
|
||||
|
||||
# Configure view
|
||||
configure:
|
||||
@@ -108,6 +114,8 @@ config:
|
||||
folder: 'account://'
|
||||
pattern: '{FOLDER}/{KEY}{EXT}'
|
||||
indexed: true
|
||||
key: username
|
||||
case_sensitive: false
|
||||
search:
|
||||
options:
|
||||
contains: 1
|
||||
@@ -115,6 +123,13 @@ config:
|
||||
- key
|
||||
- email
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
fields:
|
||||
import@:
|
||||
type: configure/compat
|
||||
context: blueprints://flex
|
||||
|
||||
# Regular form definition
|
||||
form:
|
||||
fields:
|
||||
|
||||
@@ -65,7 +65,7 @@ config:
|
||||
# Edit view
|
||||
edit:
|
||||
title:
|
||||
template: '{{ object.readableName ?? object.groupname }}'
|
||||
template: "{{ form.value('readableName') ?? form.value('groupname') }}"
|
||||
|
||||
# Configure view
|
||||
configure:
|
||||
@@ -113,3 +113,10 @@ config:
|
||||
- key
|
||||
- groupname
|
||||
- description
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
fields:
|
||||
import@:
|
||||
type: configure/compat
|
||||
context: blueprints://flex
|
||||
|
||||
@@ -7,7 +7,7 @@ form:
|
||||
|
||||
fields:
|
||||
|
||||
header.visibility_requires_access:
|
||||
header.login.visibility_requires_access:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS
|
||||
|
||||
@@ -67,6 +67,15 @@ form:
|
||||
default: 'en'
|
||||
help: PLUGIN_ADMIN.LANGUAGE_HELP
|
||||
|
||||
content_editor:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.CONTENT_EDITOR
|
||||
size: medium
|
||||
classes: fancy
|
||||
data-options@: 'Grav\Plugin\Admin\Admin::contentEditor'
|
||||
default: 'default'
|
||||
help: PLUGIN_ADMIN.CONTENT_EDITOR_HELP
|
||||
|
||||
twofa_check:
|
||||
type: conditional
|
||||
condition: config.plugins.admin.twofa_enabled
|
||||
@@ -99,6 +108,8 @@ form:
|
||||
sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
|
||||
|
||||
|
||||
|
||||
|
||||
security:
|
||||
security@: admin.super
|
||||
title: PLUGIN_ADMIN.ACCESS_LEVELS
|
||||
|
||||
@@ -1,48 +1,55 @@
|
||||
title: Group
|
||||
rules:
|
||||
slug:
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
min: 1
|
||||
max: 200
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
validation: loose
|
||||
|
||||
fields:
|
||||
groupname:
|
||||
type: text
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.GROUP_NAME
|
||||
flex-disabled@: exists
|
||||
flex-readonly@: exists
|
||||
validate:
|
||||
required: true
|
||||
fields:
|
||||
groupname:
|
||||
type: text
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.GROUP_NAME
|
||||
flex-disabled@: exists
|
||||
flex-readonly@: exists
|
||||
validate:
|
||||
required: true
|
||||
rule: slug
|
||||
|
||||
readableName:
|
||||
type: text
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.DISPLAY_NAME
|
||||
readableName:
|
||||
type: text
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.DISPLAY_NAME
|
||||
|
||||
description:
|
||||
type: text
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.DESCRIPTION
|
||||
description:
|
||||
type: text
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.DESCRIPTION
|
||||
|
||||
icon:
|
||||
type: text
|
||||
size: small
|
||||
label: PLUGIN_ADMIN.ICON
|
||||
icon:
|
||||
type: text
|
||||
size: small
|
||||
label: PLUGIN_ADMIN.ICON
|
||||
|
||||
enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.ENABLED
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
access:
|
||||
type: permissions
|
||||
check_authorize: false
|
||||
label: PLUGIN_ADMIN.PERMISSIONS
|
||||
ignore_empty: true
|
||||
validate:
|
||||
type: array
|
||||
value_type: bool
|
||||
access:
|
||||
type: permissions
|
||||
check_authorize: false
|
||||
label: PLUGIN_ADMIN.PERMISSIONS
|
||||
ignore_empty: true
|
||||
validate:
|
||||
type: array
|
||||
value_type: bool
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
title: PLUGIN_ADMIN_PRO.ADD_GROUP
|
||||
|
||||
rules:
|
||||
slug:
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
min: 1
|
||||
max: 200
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
@@ -14,3 +20,4 @@ form:
|
||||
help: PLUGIN_ADMIN_PRO.GROUP_NAME_HELP
|
||||
validate:
|
||||
required: true
|
||||
rule: slug
|
||||
|
||||
@@ -24,6 +24,10 @@ types:
|
||||
type: image
|
||||
thumb: media/thumb-png.png
|
||||
mime: image/png
|
||||
webp:
|
||||
type: image
|
||||
thumb: media/thumb-webp.png
|
||||
mime: image/webp
|
||||
gif:
|
||||
type: animated
|
||||
thumb: media/thumb-gif.png
|
||||
@@ -103,7 +107,7 @@ types:
|
||||
docx:
|
||||
type: file
|
||||
thumb: media/thumb-docx.png
|
||||
mime: application/msword
|
||||
mime: application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
xls:
|
||||
type: file
|
||||
thumb: media/thumb-xls.png
|
||||
@@ -111,7 +115,7 @@ types:
|
||||
xlsx:
|
||||
type: file
|
||||
thumb: media/thumb-xlsx.png
|
||||
mime: application/vnd.ms-excel
|
||||
mime: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
ppt:
|
||||
type: file
|
||||
thumb: media/thumb-ppt.png
|
||||
@@ -119,7 +123,7 @@ types:
|
||||
pptx:
|
||||
type: file
|
||||
thumb: media/thumb-pptx.png
|
||||
mime: application/vnd.ms-powerpoint
|
||||
mime: application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pps:
|
||||
type: file
|
||||
thumb: media/thumb-pps.png
|
||||
|
||||
@@ -10,18 +10,24 @@ custom_base_url: '' # Set the base_url manually, e.
|
||||
username_regex: '^[a-z0-9_-]{3,16}$' # Only lowercase chars, digits, dashes, underscores. 3 - 16 chars
|
||||
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' # At least one number, one uppercase and lowercase letter, and be at least 8+ chars
|
||||
intl_enabled: true # Special logic for PHP International Extension (mod_intl)
|
||||
http_x_forwarded: # Configuration options for the various HTTP_X_FORWARD headers
|
||||
protocol: true
|
||||
host: false
|
||||
port: true
|
||||
ip: true
|
||||
|
||||
languages:
|
||||
supported: [] # List of languages supported. eg: [en, fr, de]
|
||||
default_lang: # Default is the first supported language. Must be one of the supported languages
|
||||
include_default_lang: true # Include the default lang prefix in all URLs
|
||||
include_default_lang_file_extension: true # If true, include language code for the default language in file extension: default.en.md
|
||||
pages_fallback_only: false # Only fallback to find page content through supported languages
|
||||
translations: true # If false, translation keys are used instead of translated strings
|
||||
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
|
||||
session_store_active: false # Store active language in session
|
||||
http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser
|
||||
override_locale: false # Override the default or system locale with language specific one
|
||||
content_fallback: {} # Custom language fallbacks. eg: {fr: ['fr', 'en']}
|
||||
pages_fallback_only: false # DEPRECATED: Use `content_fallback` instead
|
||||
|
||||
home:
|
||||
alias: '/home' # Default path for home, ie /
|
||||
@@ -56,6 +62,12 @@ pages:
|
||||
special_chars: # List of special characters to automatically convert to entities
|
||||
'>': 'gt'
|
||||
'<': 'lt'
|
||||
valid_link_attributes: # Valid attributes to pass through via markdown links
|
||||
- rel
|
||||
- target
|
||||
- id
|
||||
- class
|
||||
- classes
|
||||
types: [html,htm,xml,txt,json,rss,atom] # list of valid page types
|
||||
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
|
||||
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
|
||||
@@ -96,7 +108,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 (DEPRECATED, always enabled in strict mode)
|
||||
autoescape: true # 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
|
||||
@@ -137,8 +149,10 @@ images:
|
||||
cache_all: false # Cache all image by default
|
||||
cache_perms: '0755' # MUST BE IN QUOTES!! Default cache folder perms. Usually '0755' or '0775'
|
||||
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
|
||||
auto_fix_orientation: false # Automatically fix the image orientation based on the Exif data
|
||||
auto_fix_orientation: true # Automatically fix the image orientation based on the Exif data
|
||||
seofriendly: false # SEO-friendly processed image names
|
||||
defaults:
|
||||
loading: auto # Let browser pick [auto|lazy|eager]
|
||||
|
||||
media:
|
||||
enable_media_timestamp: false # Enable media timestamps
|
||||
@@ -154,6 +168,7 @@ session:
|
||||
uniqueness: path # Should sessions be `path` based or `security.salt` based
|
||||
secure: false # Set session secure. If true, indicates that communication for this cookie must be over an encrypted transmission. Enable this only on sites that run exclusively on HTTPS
|
||||
httponly: true # Set session HTTP only. If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed.
|
||||
samesite: # Set session SameSite. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
|
||||
split: true # Sessions should be independent between site and plugins (such as admin)
|
||||
path:
|
||||
|
||||
@@ -181,5 +196,6 @@ flex:
|
||||
lifetime: 600 # Lifetime of cached HTML in seconds (0 = infinite)
|
||||
|
||||
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)
|
||||
yaml_compat: false # Set to true to enable YAML backwards compatibility
|
||||
twig_compat: false # Set to true to enable deprecated Twig settings (autoescape: false)
|
||||
blueprint_compat: false # Set to true to enable backward compatible strict support for blueprints
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.7.0-rc.6');
|
||||
define('GRAV_VERSION', '1.7.0-rc.19');
|
||||
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
|
||||
define('GRAV_TESTING', true);
|
||||
define('DS', '/');
|
||||
if (!defined('DS')) {
|
||||
define('DS', '/');
|
||||
}
|
||||
|
||||
if (!defined('GRAV_PHP_MIN')) {
|
||||
define('GRAV_PHP_MIN', '7.1.3');
|
||||
define('GRAV_PHP_MIN', '7.3.6');
|
||||
}
|
||||
|
||||
// Directories and Paths
|
||||
|
||||
@@ -94,9 +94,10 @@ GRAV:
|
||||
YR_PLURAL: yrs
|
||||
DEC_PLURAL: decs
|
||||
FORM:
|
||||
VALIDATION_FAIL: <b>Validation failed:</b>
|
||||
INVALID_INPUT: Invalid input in
|
||||
MISSING_REQUIRED_FIELD: Missing required field:
|
||||
VALIDATION_FAIL: '<b>Validation failed:</b>'
|
||||
INVALID_INPUT: 'Invalid input in'
|
||||
MISSING_REQUIRED_FIELD: 'Missing required field:'
|
||||
XSS_ISSUES: "Potential XSS issues detected in '%s' field"
|
||||
MONTHS_OF_THE_YEAR: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
||||
DAYS_OF_THE_WEEK: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||||
YES: "Yes"
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
title: Not Found
|
||||
routable: false
|
||||
notfound: true
|
||||
expires: 0
|
||||
---
|
||||
|
||||
@@ -15,18 +15,28 @@ use Grav\Common\Assets\Traits\TestingAssetsTrait;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function call_user_func_array;
|
||||
use function count;
|
||||
use function func_get_args;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class Assets
|
||||
* @package Grav\Common
|
||||
*/
|
||||
class Assets extends PropertyObject
|
||||
{
|
||||
use TestingAssetsTrait;
|
||||
use LegacyAssetsTrait;
|
||||
|
||||
const CSS = 'css';
|
||||
const JS = 'js';
|
||||
const CSS_COLLECTION = 'assets_css';
|
||||
const JS_COLLECTION = 'assets_js';
|
||||
const CSS_TYPE = 'Css';
|
||||
const JS_TYPE = 'Js';
|
||||
const INLINE_CSS_TYPE = 'InlineCss';
|
||||
const INLINE_JS_TYPE = 'InlineJs';
|
||||
const CSS_TYPE = Assets\Css::Class;
|
||||
const JS_TYPE = Assets\Js::Class;
|
||||
const INLINE_CSS_TYPE = Assets\InlineCss::Class;
|
||||
const INLINE_JS_TYPE = Assets\InlineJs::Class;
|
||||
|
||||
/** @const Regex to match CSS and JavaScript files */
|
||||
const DEFAULT_REGEX = '/.\.(css|js)$/i';
|
||||
@@ -55,11 +65,19 @@ class Assets extends PropertyObject
|
||||
/** @var bool */
|
||||
protected $css_pipeline_before_excludes;
|
||||
/** @var bool */
|
||||
protected $inlinecss_pipeline_include_externals;
|
||||
/** @var bool */
|
||||
protected $inlinecss_pipeline_before_excludes;
|
||||
/** @var bool */
|
||||
protected $js_pipeline;
|
||||
/** @var bool */
|
||||
protected $js_pipeline_include_externals;
|
||||
/** @var bool */
|
||||
protected $js_pipeline_before_excludes;
|
||||
/** @var bool */
|
||||
protected $inlinejs_pipeline_include_externals;
|
||||
/** @var bool */
|
||||
protected $inlinejs_pipeline_before_excludes;
|
||||
/** @var array */
|
||||
protected $pipeline_options = [];
|
||||
|
||||
@@ -76,6 +94,8 @@ class Assets extends PropertyObject
|
||||
|
||||
/**
|
||||
* Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
@@ -106,7 +126,6 @@ class Assets extends PropertyObject
|
||||
* assets and/or collections that will be automatically added on startup.
|
||||
*
|
||||
* @param array $config Configurable options.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function config(array $config)
|
||||
@@ -133,24 +152,24 @@ class Assets extends PropertyObject
|
||||
* It automatically detects the asset type (JavaScript, CSS or collection).
|
||||
* You may add more than one asset passing an array as argument.
|
||||
*
|
||||
* @param array|string $asset
|
||||
* @param string|string[] $asset
|
||||
* @return $this
|
||||
*/
|
||||
public function add($asset)
|
||||
{
|
||||
$args = \func_get_args();
|
||||
$args = func_get_args();
|
||||
|
||||
// More than one asset
|
||||
if (\is_array($asset)) {
|
||||
if (is_array($asset)) {
|
||||
foreach ($asset as $a) {
|
||||
array_shift($args);
|
||||
$args = array_merge([$a], $args);
|
||||
\call_user_func_array([$this, 'add'], $args);
|
||||
call_user_func_array([$this, 'add'], $args);
|
||||
}
|
||||
} elseif (isset($this->collections[$asset])) {
|
||||
array_shift($args);
|
||||
$args = array_merge([$this->collections[$asset]], $args);
|
||||
\call_user_func_array([$this, 'add'], $args);
|
||||
call_user_func_array([$this, 'add'], $args);
|
||||
} else {
|
||||
// Get extension
|
||||
$extension = pathinfo(parse_url($asset, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
@@ -159,9 +178,9 @@ class Assets extends PropertyObject
|
||||
if (\strlen($extension) > 0) {
|
||||
$extension = strtolower($extension);
|
||||
if ($extension === 'css') {
|
||||
\call_user_func_array([$this, 'addCss'], $args);
|
||||
call_user_func_array([$this, 'addCss'], $args);
|
||||
} elseif ($extension === 'js') {
|
||||
\call_user_func_array([$this, 'addJs'], $args);
|
||||
call_user_func_array([$this, 'addJs'], $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,12 +188,20 @@ class Assets extends PropertyObject
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $collection
|
||||
* @param string $type
|
||||
* @param string|string[] $asset
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*/
|
||||
protected function addType($collection, $type, $asset, $options)
|
||||
{
|
||||
if (\is_array($asset)) {
|
||||
if (is_array($asset)) {
|
||||
foreach ($asset as $a) {
|
||||
$this->addType($collection, $type, $a, $options);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -186,7 +213,7 @@ class Assets extends PropertyObject
|
||||
// If pipeline disabled, set to position if provided, else after
|
||||
if (isset($options['pipeline'])) {
|
||||
if ($options['pipeline'] === false) {
|
||||
$exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS_TYPE : $this::CSS_TYPE;
|
||||
$exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS : $this::CSS;
|
||||
$excludes = strtolower($exclude_type . '_pipeline_before_excludes');
|
||||
if ($this->{$excludes}) {
|
||||
$default = 'after';
|
||||
@@ -204,11 +231,10 @@ class Assets extends PropertyObject
|
||||
$options['timestamp'] = $this->timestamp;
|
||||
|
||||
// Set order
|
||||
$options['order'] = \count($this->$collection);
|
||||
$options['order'] = count($this->$collection);
|
||||
|
||||
// Create asset of correct type
|
||||
$asset_class = "\\Grav\\Common\\Assets\\{$type}";
|
||||
$asset_object = new $asset_class();
|
||||
$asset_object = new $type();
|
||||
|
||||
// If exists
|
||||
if ($asset_object->init($asset, $options)) {
|
||||
@@ -225,7 +251,7 @@ class Assets extends PropertyObject
|
||||
*/
|
||||
public function addCss($asset)
|
||||
{
|
||||
return $this->addType(Assets::CSS_COLLECTION, Assets::CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::CSS_TYPE));
|
||||
return $this->addType($this::CSS_COLLECTION, $this::CSS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::CSS_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +261,7 @@ class Assets extends PropertyObject
|
||||
*/
|
||||
public function addInlineCss($asset)
|
||||
{
|
||||
return $this->addType(Assets::CSS_COLLECTION, Assets::INLINE_CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_CSS_TYPE));
|
||||
return $this->addType($this::CSS_COLLECTION, $this::INLINE_CSS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_CSS_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +271,7 @@ class Assets extends PropertyObject
|
||||
*/
|
||||
public function addJs($asset)
|
||||
{
|
||||
return $this->addType(Assets::JS_COLLECTION, Assets::JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::JS_TYPE));
|
||||
return $this->addType($this::JS_COLLECTION, $this::JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::JS_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,20 +281,19 @@ class Assets extends PropertyObject
|
||||
*/
|
||||
public function addInlineJs($asset)
|
||||
{
|
||||
return $this->addType(Assets::JS_COLLECTION, Assets::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_JS_TYPE));
|
||||
return $this->addType($this::JS_COLLECTION, $this::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_TYPE));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add/replace collection.
|
||||
*
|
||||
* @param string $collectionName
|
||||
* @param array $assets
|
||||
* @param string $collectionName
|
||||
* @param array $assets
|
||||
* @param bool $overwrite
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function registerCollection($collectionName, Array $assets, $overwrite = false)
|
||||
public function registerCollection($collectionName, array $assets, $overwrite = false)
|
||||
{
|
||||
if ($overwrite || !isset($this->collections[$collectionName])) {
|
||||
$this->collections[$collectionName] = $assets;
|
||||
@@ -277,6 +302,13 @@ class Assets extends PropertyObject
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $assets
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param bool $sort
|
||||
* @return array|false
|
||||
*/
|
||||
protected function filterAssets($assets, $key, $value, $sort = false)
|
||||
{
|
||||
$results = array_filter($assets, function ($asset) use ($key, $value) {
|
||||
@@ -284,8 +316,8 @@ class Assets extends PropertyObject
|
||||
if ($key === 'position' && $value === 'pipeline') {
|
||||
$type = $asset->getType();
|
||||
|
||||
if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline') {
|
||||
if ($this->{$type . '_pipeline_before_excludes'}) {
|
||||
if ($asset->getRemote() && $this->{strtolower($type) . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline') {
|
||||
if ($this->{strtolower($type) . '_pipeline_before_excludes'}) {
|
||||
$asset->setPosition('after');
|
||||
} else {
|
||||
$asset->setPosition('before');
|
||||
@@ -308,14 +340,25 @@ class Assets extends PropertyObject
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $assets
|
||||
* @return array
|
||||
*/
|
||||
protected function sortAssets($assets)
|
||||
{
|
||||
uasort($assets, static function ($a, $b) {
|
||||
return $b['priority'] <=> $a['priority'] ?: $a['order'] <=> $b['order'];
|
||||
});
|
||||
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $group
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function render($type, $group = 'head', $attributes = [])
|
||||
{
|
||||
$before_output = '';
|
||||
@@ -362,7 +405,6 @@ class Assets extends PropertyObject
|
||||
*
|
||||
* @param string $group name of the group
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function css($group = 'head', $attributes = [])
|
||||
@@ -375,7 +417,6 @@ class Assets extends PropertyObject
|
||||
*
|
||||
* @param string $group name of the group
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function js($group = 'head', $attributes = [])
|
||||
|
||||
@@ -14,7 +14,12 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
* Class BaseAsset
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
abstract class BaseAsset extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
@@ -22,9 +27,6 @@ abstract class BaseAsset extends PropertyObject
|
||||
protected const CSS_ASSET = true;
|
||||
protected const JS_ASSET = false;
|
||||
|
||||
/** @const Regex to match CSS import content */
|
||||
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
|
||||
|
||||
/** @var string|false */
|
||||
protected $asset;
|
||||
/** @var string */
|
||||
@@ -130,7 +132,7 @@ abstract class BaseAsset extends PropertyObject
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = new \SplFileInfo($path);
|
||||
$file = new SplFileInfo($path);
|
||||
|
||||
$asset = $this->buildLocalLink($file->getPathname());
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Css
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Css extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class InlineCss
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class InlineCss extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class InlineJs
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class InlineJs extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Js
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Js extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,12 @@ use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Class Pipeline
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Pipeline extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
@@ -30,9 +35,6 @@ class Pipeline extends PropertyObject
|
||||
/** @const Regex to match CSS sourcemap comments */
|
||||
protected const CSS_SOURCEMAP_REGEX = '{\/\*# (.*?) \*\/}';
|
||||
|
||||
/** @const Regex to match CSS import content */
|
||||
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
|
||||
|
||||
protected const FIRST_FORWARDSLASH_REGEX = '{^\/{1}\w}';
|
||||
|
||||
// Following variables come from the configuration:
|
||||
|
||||
@@ -9,13 +9,20 @@
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Closure;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Trait AssetUtilsTrait
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*/
|
||||
trait AssetUtilsTrait
|
||||
{
|
||||
/**
|
||||
* @var \Closure|null
|
||||
* @var Closure|null
|
||||
*
|
||||
* Closure used by the pipeline to fetch assets.
|
||||
*
|
||||
@@ -86,7 +93,7 @@ trait AssetUtilsTrait
|
||||
}
|
||||
|
||||
// TODO: looks like this is not being used.
|
||||
$file = $this->fetch_command instanceof \Closure ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
|
||||
$file = $this->fetch_command instanceof Closure ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
|
||||
|
||||
// No file found, skip it...
|
||||
if ($file === false) {
|
||||
@@ -123,9 +130,11 @@ trait AssetUtilsTrait
|
||||
*/
|
||||
protected function moveImports($file)
|
||||
{
|
||||
$regex = '{@import.*?["\']([^"\']+)["\'].*?;}';
|
||||
|
||||
$imports = [];
|
||||
|
||||
$file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) use (&$imports) {
|
||||
$file = (string)preg_replace_callback($regex, function ($matches) use (&$imports) {
|
||||
$imports[] = $matches[0];
|
||||
|
||||
return '';
|
||||
@@ -149,11 +158,11 @@ trait AssetUtilsTrait
|
||||
if (is_numeric($key)) {
|
||||
$key = $value;
|
||||
}
|
||||
if (\is_array($value)) {
|
||||
if (is_array($value)) {
|
||||
$value = implode(' ', $value);
|
||||
}
|
||||
|
||||
if (\in_array($key, $no_key, true)) {
|
||||
if (in_array($key, $no_key, true)) {
|
||||
$element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
|
||||
} else {
|
||||
$element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
|
||||
@@ -168,7 +177,7 @@ trait AssetUtilsTrait
|
||||
/**
|
||||
* Render Querystring
|
||||
*
|
||||
* @param string $asset
|
||||
* @param string|null $asset
|
||||
* @return string
|
||||
*/
|
||||
protected function renderQueryString($asset = null)
|
||||
|
||||
@@ -10,7 +10,14 @@
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Grav\Common\Assets;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
|
||||
/**
|
||||
* Trait LegacyAssetsTrait
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*/
|
||||
trait LegacyAssetsTrait
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -9,8 +9,17 @@
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use FilesystemIterator;
|
||||
use Grav\Common\Grav;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Trait TestingAssetsTrait
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*/
|
||||
trait TestingAssetsTrait
|
||||
{
|
||||
/**
|
||||
@@ -310,16 +319,16 @@ trait TestingAssetsTrait
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $pattern (regex)
|
||||
* @param string $ltrim Will be trimmed from the left of the file path
|
||||
* @param string|null $ltrim Will be trimmed from the left of the file path
|
||||
* @return array
|
||||
*/
|
||||
protected function rglob($directory, $pattern, $ltrim = null)
|
||||
{
|
||||
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
|
||||
$iterator = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator(
|
||||
$directory,
|
||||
\FilesystemIterator::SKIP_DOTS
|
||||
FilesystemIterator::SKIP_DOTS
|
||||
)), $pattern);
|
||||
$offset = \strlen($ltrim);
|
||||
$offset = strlen($ltrim);
|
||||
$files = [];
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
namespace Grav\Common\Backup;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use FilesystemIterator;
|
||||
use GlobIterator;
|
||||
use Grav\Common\Filesystem\Archiver;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Inflector;
|
||||
@@ -19,8 +23,16 @@ use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\JsonFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use SplFileInfo;
|
||||
use stdClass;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Class Backups
|
||||
* @package Grav\Common\Backup
|
||||
*/
|
||||
class Backups
|
||||
{
|
||||
protected const BACKUP_FILENAME_REGEXZ = "#(.*)--(\d*).zip#";
|
||||
@@ -33,32 +45,45 @@ class Backups
|
||||
/** @var array|null */
|
||||
protected static $backups;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = Grav::instance()['events'];
|
||||
$dispatcher = $grav['events'];
|
||||
$dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
|
||||
Grav::instance()->fireEvent('onBackupsInitialized', new Event(['backups' => $this]));
|
||||
|
||||
$grav->fireEvent('onBackupsInitialized', new Event(['backups' => $this]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
if (null === static::$backup_dir) {
|
||||
static::$backup_dir = Grav::instance()['locator']->findResource('backup://', true, true);
|
||||
$grav = Grav::instance();
|
||||
static::$backup_dir = $grav['locator']->findResource('backup://', true, true);
|
||||
Folder::create(static::$backup_dir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Event $event
|
||||
* @return void
|
||||
*/
|
||||
public function onSchedulerInitialized(Event $event)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Scheduler $scheduler */
|
||||
$scheduler = $event['scheduler'];
|
||||
|
||||
/** @var Inflector $inflector */
|
||||
$inflector = Grav::instance()['inflector'];
|
||||
$inflector = $grav['inflector'];
|
||||
|
||||
foreach (static::getBackupProfiles() as $id => $profile) {
|
||||
$at = $profile['schedule_at'];
|
||||
@@ -80,7 +105,7 @@ class Backups
|
||||
public function getBackupDownloadUrl($backup, $base_url)
|
||||
{
|
||||
$param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
|
||||
$download = urlencode(base64_encode($backup));
|
||||
$download = urlencode(base64_encode(basename($backup)));
|
||||
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim(
|
||||
$base_url,
|
||||
'/'
|
||||
@@ -132,19 +157,21 @@ class Backups
|
||||
{
|
||||
if ($force || null === static::$backups) {
|
||||
static::$backups = [];
|
||||
$backups_itr = new \GlobIterator(static::$backup_dir . '/*.zip', \FilesystemIterator::KEY_AS_FILENAME);
|
||||
$inflector = Grav::instance()['inflector'];
|
||||
|
||||
$grav = Grav::instance();
|
||||
$backups_itr = new GlobIterator(static::$backup_dir . '/*.zip', FilesystemIterator::KEY_AS_FILENAME);
|
||||
$inflector = $grav['inflector'];
|
||||
$long_date_format = DATE_RFC2822;
|
||||
|
||||
/**
|
||||
* @var string $name
|
||||
* @var \SplFileInfo $file
|
||||
* @var SplFileInfo $file
|
||||
*/
|
||||
foreach ($backups_itr as $name => $file) {
|
||||
if (preg_match(static::BACKUP_FILENAME_REGEXZ, $name, $matches)) {
|
||||
$date = \DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]);
|
||||
$date = DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]);
|
||||
$timestamp = $date->getTimestamp();
|
||||
$backup = new \stdClass();
|
||||
$backup = new stdClass();
|
||||
$backup->title = $inflector->titleize($matches[1]);
|
||||
$backup->time = $date;
|
||||
$backup->date = $date->format($long_date_format);
|
||||
@@ -170,17 +197,19 @@ class Backups
|
||||
*/
|
||||
public static function backup($id = 0, callable $status = null)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$profiles = static::getBackupProfiles();
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$locator = $grav['locator'];
|
||||
|
||||
if (isset($profiles[$id])) {
|
||||
$backup = (object) $profiles[$id];
|
||||
} else {
|
||||
throw new \RuntimeException('No backups defined...');
|
||||
throw new RuntimeException('No backups defined...');
|
||||
}
|
||||
|
||||
$name = Grav::instance()['inflector']->underscorize($backup->name);
|
||||
$name = $grav['inflector']->underscorize($backup->name);
|
||||
$date = date(static::BACKUP_DATE_FORMAT, time());
|
||||
$filename = trim($name, '_') . '--' . $date . '.zip';
|
||||
$destination = static::$backup_dir . DS . $filename;
|
||||
@@ -194,7 +223,7 @@ class Backups
|
||||
}
|
||||
|
||||
if (!file_exists($backup_root)) {
|
||||
throw new \RuntimeException("Backup location: {$backup_root} does not exist...");
|
||||
throw new RuntimeException("Backup location: {$backup_root} does not exist...");
|
||||
}
|
||||
|
||||
$options = [
|
||||
@@ -202,7 +231,6 @@ class Backups
|
||||
'exclude_paths' => static::convertExclude($backup->exclude_paths ?? ''),
|
||||
];
|
||||
|
||||
/** @var Archiver $archiver */
|
||||
$archiver = Archiver::create('zip');
|
||||
$archiver->setArchive($destination)->setOptions($options)->compress($backup_root, $status)->addEmptyFolders($options['exclude_paths'], $status);
|
||||
|
||||
@@ -221,16 +249,16 @@ class Backups
|
||||
}
|
||||
|
||||
// Log the backup
|
||||
Grav::instance()['log']->notice('Backup Created: ' . $destination);
|
||||
$grav['log']->notice('Backup Created: ' . $destination);
|
||||
|
||||
// Fire Finished event
|
||||
Grav::instance()->fireEvent('onBackupFinished', new Event(['backup' => $destination]));
|
||||
$grav->fireEvent('onBackupFinished', new Event(['backup' => $destination]));
|
||||
|
||||
// Purge anything required
|
||||
static::purge();
|
||||
|
||||
// Log
|
||||
$log = JsonFile::instance(Grav::instance()['locator']->findResource("log://backup.log", true, true));
|
||||
$log = JsonFile::instance($locator->findResource("log://backup.log", true, true));
|
||||
$log->content([
|
||||
'time' => time(),
|
||||
'location' => $destination
|
||||
@@ -241,7 +269,8 @@ class Backups
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function purge()
|
||||
{
|
||||
@@ -261,7 +290,7 @@ class Backups
|
||||
|
||||
case 'time':
|
||||
$last = end($backups);
|
||||
$now = new \DateTime();
|
||||
$now = new DateTime();
|
||||
$interval = $now->diff($last->time);
|
||||
if ($interval->days > $purge_config['max_backups_time']) {
|
||||
unlink($last->path);
|
||||
@@ -288,6 +317,7 @@ class Backups
|
||||
protected static function convertExclude($exclude)
|
||||
{
|
||||
$lines = preg_split("/[\s,]+/", $exclude);
|
||||
return array_map('trim', $lines, array_fill(0, \count($lines), '/'));
|
||||
|
||||
return array_map('trim', $lines, array_fill(0, count($lines), '/'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use function donatj\UserAgent\parse_user_agent;
|
||||
|
||||
/**
|
||||
* Internally uses the PhpUserAgent package https://github.com/donatj/PhpUserAgent
|
||||
*/
|
||||
|
||||
@@ -9,13 +9,21 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use DirectoryIterator;
|
||||
use \Doctrine\Common\Cache as DoctrineCache;
|
||||
use Exception;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Scheduler\Scheduler;
|
||||
use LogicException;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use function dirname;
|
||||
use function extension_loaded;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* The GravCache object is used throughout Grav to store and retrieve cached data.
|
||||
@@ -113,7 +121,6 @@ class Cache extends Getters
|
||||
* Initialization that sets a base key and the driver based on configuration settings
|
||||
*
|
||||
* @param Grav $grav
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init(Grav $grav)
|
||||
@@ -172,7 +179,7 @@ class Cache extends Getters
|
||||
$current = basename($this->cache_dir);
|
||||
$count = 0;
|
||||
|
||||
foreach (new \DirectoryIterator($cache_dir) as $file) {
|
||||
foreach (new DirectoryIterator($cache_dir) as $file) {
|
||||
$dir = $file->getBasename();
|
||||
if ($dir === $current || $file->isDot() || $file->isFile()) {
|
||||
continue;
|
||||
@@ -189,6 +196,7 @@ class Cache extends Getters
|
||||
* Public accessor to set the enabled state of the cache
|
||||
*
|
||||
* @param bool|int $enabled
|
||||
* @return void
|
||||
*/
|
||||
public function setEnabled($enabled)
|
||||
{
|
||||
@@ -265,7 +273,7 @@ class Cache extends Getters
|
||||
$driver = new DoctrineCache\MemcacheCache();
|
||||
$driver->setMemcache($memcache);
|
||||
} else {
|
||||
throw new \LogicException('Memcache PHP extension has not been installed');
|
||||
throw new LogicException('Memcache PHP extension has not been installed');
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -279,7 +287,7 @@ class Cache extends Getters
|
||||
$driver = new DoctrineCache\MemcachedCache();
|
||||
$driver->setMemcached($memcached);
|
||||
} else {
|
||||
throw new \LogicException('Memcached PHP extension has not been installed');
|
||||
throw new LogicException('Memcached PHP extension has not been installed');
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -306,7 +314,7 @@ class Cache extends Getters
|
||||
$driver = new DoctrineCache\RedisCache();
|
||||
$driver->setRedis($redis);
|
||||
} else {
|
||||
throw new \LogicException('Redis PHP extension has not been installed');
|
||||
throw new LogicException('Redis PHP extension has not been installed');
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -322,7 +330,6 @@ class Cache extends Getters
|
||||
* Gets a cached entry if it exists based on an id. If it does not exist, it returns false
|
||||
*
|
||||
* @param string $id the id of the cached entry
|
||||
*
|
||||
* @return mixed|bool returns the cached entry, can be any type, or false if doesn't exist
|
||||
*/
|
||||
public function fetch($id)
|
||||
@@ -339,7 +346,7 @@ class Cache extends Getters
|
||||
*
|
||||
* @param string $id the id of the cached entry
|
||||
* @param array|object|int $data the data for the cached entry to store
|
||||
* @param int $lifetime the lifetime to store the entry in seconds
|
||||
* @param int|null $lifetime the lifetime to store the entry in seconds
|
||||
*/
|
||||
public function save($id, $data, $lifetime = null)
|
||||
{
|
||||
@@ -397,6 +404,8 @@ class Cache extends Getters
|
||||
|
||||
/**
|
||||
* Getter method to get the cache key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
@@ -405,6 +414,9 @@ class Cache extends Getters
|
||||
|
||||
/**
|
||||
* Setter method to set key (Advanced)
|
||||
*
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
public function setKey($key)
|
||||
{
|
||||
@@ -416,7 +428,6 @@ class Cache extends Getters
|
||||
* Helper method to clear all Grav caches
|
||||
*
|
||||
* @param string $remove standard|all|assets-only|images-only|cache-only
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function clearCache($remove = 'standard')
|
||||
@@ -491,7 +502,7 @@ class Cache extends Getters
|
||||
if ($anything) {
|
||||
$output[] = '<red>Cleared: </red>' . $path . '/*';
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
// stream not found or another error while deleting files.
|
||||
$output[] = '<red>ERROR: </red>' . $e->getMessage();
|
||||
}
|
||||
@@ -514,9 +525,14 @@ class Cache extends Getters
|
||||
@opcache_reset();
|
||||
}
|
||||
|
||||
Grav::instance()->fireEvent('onAfterCacheClear', new Event(['remove' => $remove, 'output' => &$output]));
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidateCache()
|
||||
{
|
||||
$user_config = USER_DIR . 'config/system.yaml';
|
||||
@@ -538,6 +554,7 @@ class Cache extends Getters
|
||||
* Set the cache lifetime programmatically
|
||||
*
|
||||
* @param int $future timestamp
|
||||
* @return void
|
||||
*/
|
||||
public function setLifetime($future)
|
||||
{
|
||||
@@ -555,7 +572,7 @@ class Cache extends Getters
|
||||
/**
|
||||
* Retrieve the cache lifetime (in seconds)
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
*/
|
||||
public function getLifetime()
|
||||
{
|
||||
@@ -569,7 +586,7 @@ class Cache extends Getters
|
||||
/**
|
||||
* Returns the current driver name
|
||||
*
|
||||
* @return mixed
|
||||
* @return string
|
||||
*/
|
||||
public function getDriverName()
|
||||
{
|
||||
@@ -579,7 +596,7 @@ class Cache extends Getters
|
||||
/**
|
||||
* Returns the current driver setting
|
||||
*
|
||||
* @return mixed
|
||||
* @return string
|
||||
*/
|
||||
public function getDriverSetting()
|
||||
{
|
||||
@@ -603,20 +620,30 @@ class Cache extends Getters
|
||||
|
||||
/**
|
||||
* Static function to call as a scheduled Job to purge old Doctrine files
|
||||
*
|
||||
* @param bool $echo
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public static function purgeJob()
|
||||
public static function purgeJob($echo = false)
|
||||
{
|
||||
/** @var Cache $cache */
|
||||
$cache = Grav::instance()['cache'];
|
||||
$deleted_folders = $cache->purgeOldCache();
|
||||
$msg = 'Purged ' . $deleted_folders . ' old cache folders...';
|
||||
|
||||
echo 'Purged ' . $deleted_folders . ' old cache folders...';
|
||||
if ($echo) {
|
||||
echo $msg;
|
||||
} else {
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function to call as a scheduled Job to clear Grav cache
|
||||
*
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public static function clearJob($type)
|
||||
{
|
||||
@@ -626,6 +653,10 @@ class Cache extends Getters
|
||||
echo strip_tags(implode("\n", $result));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Event $event
|
||||
* @return void
|
||||
*/
|
||||
public function onSchedulerInitialized(Event $event)
|
||||
{
|
||||
/** @var Scheduler $scheduler */
|
||||
@@ -637,7 +668,7 @@ class Cache extends Getters
|
||||
$name = 'cache-purge';
|
||||
$logs = 'logs/' . $name . '.out';
|
||||
|
||||
$job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [], $name);
|
||||
$job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [true], $name);
|
||||
$job->at($at);
|
||||
$job->output($logs);
|
||||
$job->backlink('/config/system#caching');
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use function function_exists;
|
||||
|
||||
/**
|
||||
* Class Composer
|
||||
* @package Grav\Common
|
||||
*/
|
||||
class Composer
|
||||
{
|
||||
/** @const Default composer location */
|
||||
@@ -21,7 +27,7 @@ class Composer
|
||||
*/
|
||||
public static function getComposerLocation()
|
||||
{
|
||||
if (!\function_exists('shell_exec') || stripos(PHP_OS, 'win') === 0) {
|
||||
if (!function_exists('shell_exec') || stripos(PHP_OS, 'win') === 0) {
|
||||
return self::DEFAULT_PATH;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,17 @@
|
||||
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Exception;
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
use RuntimeException;
|
||||
use function get_class;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class CompiledBase
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
abstract class CompiledBase
|
||||
{
|
||||
/** @var int Version number for the compiled file. */
|
||||
@@ -41,12 +50,12 @@ abstract class CompiledBase
|
||||
* @param string $cacheFolder Cache folder to be used.
|
||||
* @param array $files List of files as returned from ConfigFileFinder class.
|
||||
* @param string $path Base path for the file list.
|
||||
* @throws \BadMethodCallException
|
||||
* @throws BadMethodCallException
|
||||
*/
|
||||
public function __construct($cacheFolder, array $files, $path)
|
||||
{
|
||||
if (!$cacheFolder) {
|
||||
throw new \BadMethodCallException('Cache folder not defined.');
|
||||
throw new BadMethodCallException('Cache folder not defined.');
|
||||
}
|
||||
|
||||
$this->path = $path ? rtrim($path, '\\/') . '/' : '';
|
||||
@@ -57,7 +66,7 @@ abstract class CompiledBase
|
||||
/**
|
||||
* Get filename for the compiled PHP file.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return $this
|
||||
*/
|
||||
public function name($name = null)
|
||||
@@ -71,6 +80,8 @@ abstract class CompiledBase
|
||||
|
||||
/**
|
||||
* Function gets called when cached configuration is saved.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modified()
|
||||
{
|
||||
@@ -133,11 +144,14 @@ abstract class CompiledBase
|
||||
* Create configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function createObject(array $data = []);
|
||||
|
||||
/**
|
||||
* Finalize configuration object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function finalizeObject();
|
||||
|
||||
@@ -185,9 +199,9 @@ abstract class CompiledBase
|
||||
}
|
||||
|
||||
$cache = include $filename;
|
||||
if (!\is_array($cache)
|
||||
if (!is_array($cache)
|
||||
|| !isset($cache['checksum'], $cache['data'], $cache['@class'])
|
||||
|| $cache['@class'] !== \get_class($this)
|
||||
|| $cache['@class'] !== get_class($this)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -209,7 +223,7 @@ abstract class CompiledBase
|
||||
* Save compiled file.
|
||||
*
|
||||
* @param string $filename
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function saveCompiledFile($filename)
|
||||
@@ -219,7 +233,7 @@ abstract class CompiledBase
|
||||
// Attempt to lock the file for writing.
|
||||
try {
|
||||
$file->lock(false);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
// Another process has locked the file; we will check this in a bit.
|
||||
}
|
||||
|
||||
@@ -229,7 +243,7 @@ abstract class CompiledBase
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'@class' => \get_class($this),
|
||||
'@class' => get_class($this),
|
||||
'timestamp' => time(),
|
||||
'checksum' => $this->checksum(),
|
||||
'files' => $this->files,
|
||||
|
||||
@@ -70,6 +70,8 @@ class CompiledBlueprints extends CompiledBase
|
||||
|
||||
/**
|
||||
* Finalize configuration object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function finalizeObject()
|
||||
{
|
||||
@@ -80,6 +82,7 @@ class CompiledBlueprints extends CompiledBase
|
||||
*
|
||||
* @param string $name Name of the position.
|
||||
* @param array $files Files to be loaded.
|
||||
* @return void
|
||||
*/
|
||||
protected function loadFile($name, $files)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
|
||||
/**
|
||||
* Class CompiledConfig
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class CompiledConfig extends CompiledBase
|
||||
{
|
||||
/** @var callable Blueprints loader. */
|
||||
@@ -60,6 +64,7 @@ class CompiledConfig extends CompiledBase
|
||||
* Create configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function createObject(array $data = [])
|
||||
{
|
||||
@@ -73,6 +78,8 @@ class CompiledConfig extends CompiledBase
|
||||
|
||||
/**
|
||||
* Finalize configuration object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function finalizeObject()
|
||||
{
|
||||
@@ -82,6 +89,8 @@ class CompiledConfig extends CompiledBase
|
||||
|
||||
/**
|
||||
* Function gets called when cached configuration is saved.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modified()
|
||||
{
|
||||
@@ -93,6 +102,7 @@ class CompiledConfig extends CompiledBase
|
||||
*
|
||||
* @param string $name Name of the position.
|
||||
* @param string $filename File to be loaded.
|
||||
* @return void
|
||||
*/
|
||||
protected function loadFile($name, $filename)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
|
||||
/**
|
||||
* Class CompiledLanguages
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class CompiledLanguages extends CompiledBase
|
||||
{
|
||||
/**
|
||||
@@ -30,6 +34,7 @@ class CompiledLanguages extends CompiledBase
|
||||
* Create configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function createObject(array $data = [])
|
||||
{
|
||||
@@ -38,6 +43,8 @@ class CompiledLanguages extends CompiledBase
|
||||
|
||||
/**
|
||||
* Finalize configuration object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function finalizeObject()
|
||||
{
|
||||
@@ -48,6 +55,8 @@ class CompiledLanguages extends CompiledBase
|
||||
|
||||
/**
|
||||
* Function gets called when cached configuration is saved.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modified()
|
||||
{
|
||||
@@ -59,6 +68,7 @@ class CompiledLanguages extends CompiledBase
|
||||
*
|
||||
* @param string $name Name of the position.
|
||||
* @param string $filename File to be loaded.
|
||||
* @return void
|
||||
*/
|
||||
protected function loadFile($name, $filename)
|
||||
{
|
||||
|
||||
@@ -15,6 +15,10 @@ use Grav\Common\Data\Data;
|
||||
use Grav\Common\Service\ConfigServiceProvider;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Config
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class Config extends Data
|
||||
{
|
||||
/** @var string */
|
||||
@@ -55,7 +59,7 @@ class Config extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $modified
|
||||
* @param bool|null $modified
|
||||
* @return bool
|
||||
*/
|
||||
public function modified($modified = null)
|
||||
@@ -68,7 +72,7 @@ class Config extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $timestamp
|
||||
* @param int|null $timestamp
|
||||
* @return int
|
||||
*/
|
||||
public function timestamp($timestamp = null)
|
||||
@@ -105,6 +109,9 @@ class Config extends Data
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function debug()
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
@@ -116,6 +123,9 @@ class Config extends Data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$setup = Grav::instance()['setup']->toArray();
|
||||
|
||||
@@ -9,8 +9,14 @@
|
||||
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RecursiveDirectoryIterator;
|
||||
|
||||
/**
|
||||
* Class ConfigFileFinder
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class ConfigFileFinder
|
||||
{
|
||||
/** @var string */
|
||||
@@ -41,6 +47,7 @@ class ConfigFileFinder
|
||||
foreach ($paths as $folder) {
|
||||
$list += $this->detectRecursive($folder, $pattern, $levels);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
@@ -62,6 +69,7 @@ class ConfigFileFinder
|
||||
|
||||
$list += $files[trim($path, '/')];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
@@ -79,6 +87,7 @@ class ConfigFileFinder
|
||||
foreach ($paths as $folder) {
|
||||
$list = array_merge_recursive($list, $this->detectAll($folder, $pattern, $levels));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
@@ -97,6 +106,7 @@ class ConfigFileFinder
|
||||
foreach ($folders as $folder) {
|
||||
$list += $this->detectInFolder($folder, $filename);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
@@ -104,7 +114,7 @@ class ConfigFileFinder
|
||||
* Find filename from a list of folders.
|
||||
*
|
||||
* @param array $folders
|
||||
* @param string $filename
|
||||
* @param string|null $filename
|
||||
* @return array
|
||||
*/
|
||||
public function locateInFolders(array $folders, $filename = null)
|
||||
@@ -114,6 +124,7 @@ class ConfigFileFinder
|
||||
$path = trim(Folder::getRelativePath($folder), '/');
|
||||
$list[$path] = $this->detectInFolder($folder, $filename);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
@@ -167,7 +178,7 @@ class ConfigFileFinder
|
||||
'filters' => [
|
||||
'pre-key' => $this->base,
|
||||
'key' => $pattern,
|
||||
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
|
||||
'value' => function (RecursiveDirectoryIterator $file) use ($path) {
|
||||
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
|
||||
}
|
||||
],
|
||||
@@ -188,7 +199,7 @@ class ConfigFileFinder
|
||||
* Detects all directories with the lookup file and returns them with last modification time.
|
||||
*
|
||||
* @param string $folder Location to look up from.
|
||||
* @param string $lookup Filename to be located (defaults to directory name).
|
||||
* @param string|null $lookup Filename to be located (defaults to directory name).
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
@@ -201,9 +212,7 @@ class ConfigFileFinder
|
||||
$list = [];
|
||||
|
||||
if (is_dir($folder)) {
|
||||
$iterator = new \DirectoryIterator($folder);
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
$iterator = new DirectoryIterator($folder);
|
||||
foreach ($iterator as $directory) {
|
||||
if (!$directory->isDir() || $directory->isDot()) {
|
||||
continue;
|
||||
@@ -245,7 +254,7 @@ class ConfigFileFinder
|
||||
'filters' => [
|
||||
'pre-key' => $this->base,
|
||||
'key' => $pattern,
|
||||
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
|
||||
'value' => function (RecursiveDirectoryIterator $file) use ($path) {
|
||||
return ["{$path}/{$file->getSubPathname()}" => $file->getMTime()];
|
||||
}
|
||||
],
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace Grav\Common\Config;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Languages
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class Languages extends Data
|
||||
{
|
||||
/** @var string|null */
|
||||
@@ -62,6 +66,9 @@ class Languages extends Data
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function reformat()
|
||||
{
|
||||
if (isset($this->items['plugins'])) {
|
||||
@@ -72,6 +79,7 @@ class Languages extends Data
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function mergeRecursive(array $data)
|
||||
{
|
||||
|
||||
@@ -9,13 +9,22 @@
|
||||
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Utils;
|
||||
use InvalidArgumentException;
|
||||
use Pimple\Container;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class Setup
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class Setup extends Data
|
||||
{
|
||||
/**
|
||||
@@ -60,13 +69,13 @@ class Setup extends Data
|
||||
'blueprints' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'],
|
||||
'' => ['environment://blueprints', 'user://blueprints', 'system://blueprints'],
|
||||
]
|
||||
],
|
||||
'config' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['environment://config', 'user://config', 'system/config'],
|
||||
'' => ['environment://config', 'user://config', 'system://config'],
|
||||
]
|
||||
],
|
||||
'plugins' => [
|
||||
@@ -90,7 +99,7 @@ class Setup extends Data
|
||||
'languages' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['environment://languages', 'user://languages', 'system/languages'],
|
||||
'' => ['environment://languages', 'user://languages', 'system://languages'],
|
||||
]
|
||||
],
|
||||
'cache' => [
|
||||
@@ -156,7 +165,7 @@ class Setup extends Data
|
||||
{
|
||||
// If no environment is set, make sure we get one (CLI or hostname).
|
||||
if (!static::$environment) {
|
||||
if (\defined('GRAV_CLI')) {
|
||||
if (defined('GRAV_CLI')) {
|
||||
static::$environment = 'cli';
|
||||
} else {
|
||||
/** @var ServerRequestInterface $request */
|
||||
@@ -168,12 +177,12 @@ class Setup extends Data
|
||||
}
|
||||
|
||||
// Resolve server aliases to the proper environment.
|
||||
$environment = $this->environments[static::$environment] ?? static::$environment;
|
||||
$environment = static::$environments[static::$environment] ?? static::$environment;
|
||||
|
||||
// Pre-load setup.php which contains our initial configuration.
|
||||
// Configuration may contain dynamic parts, which is why we need to always load it.
|
||||
// If "GRAV_SETUP_PATH" has been defined, use it, otherwise use defaults.
|
||||
$file = \defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
|
||||
$file = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
|
||||
$setup = is_file($file) ? (array) include $file : [];
|
||||
|
||||
// Add default streams defined in beginning of the class.
|
||||
@@ -187,13 +196,16 @@ class Setup extends Data
|
||||
|
||||
// Set up environment.
|
||||
$this->def('environment', $environment);
|
||||
$this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->get('environment')}"]]);
|
||||
$this->def(
|
||||
'streams.schemes.environment.prefixes',
|
||||
['' => ["user://env/{$this->get('environment')}", "user://{$this->get('environment')}"]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
@@ -221,7 +233,7 @@ class Setup extends Data
|
||||
} while (--$guard);
|
||||
|
||||
if (!$guard) {
|
||||
throw new \RuntimeException('Setup: Configuration reload loop detected!');
|
||||
throw new RuntimeException('Setup: Configuration reload loop detected!');
|
||||
}
|
||||
|
||||
// Make sure we have valid setup.
|
||||
@@ -234,7 +246,8 @@ class Setup extends Data
|
||||
* Initialize resource locator by using the configuration.
|
||||
*
|
||||
* @param UniformResourceLocator $locator
|
||||
* @throws \BadMethodCallException
|
||||
* @return void
|
||||
* @throws BadMethodCallException
|
||||
*/
|
||||
public function initializeLocator(UniformResourceLocator $locator)
|
||||
{
|
||||
@@ -280,24 +293,42 @@ class Setup extends Data
|
||||
|
||||
/**
|
||||
* @param UniformResourceLocator $locator
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \BadMethodCallException
|
||||
* @throws \RuntimeException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws BadMethodCallException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function check(UniformResourceLocator $locator)
|
||||
{
|
||||
$streams = $this->items['streams']['schemes'] ?? null;
|
||||
if (!\is_array($streams)) {
|
||||
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
|
||||
if (!is_array($streams)) {
|
||||
throw new InvalidArgumentException('Configuration is missing streams.schemes!');
|
||||
}
|
||||
$diff = array_keys(array_diff_key($this->streams, $streams));
|
||||
if ($diff) {
|
||||
throw new \InvalidArgumentException(
|
||||
throw new InvalidArgumentException(
|
||||
sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// If environment is found, remove all missing override locations (B/C compatibility).
|
||||
if ($locator->findResource('environment://', true)) {
|
||||
$prefixes = $this->get('streams.schemes.environment.prefixes.');
|
||||
$update = false;
|
||||
foreach ($prefixes as $i => $prefix) {
|
||||
if ($locator->findResource($prefix, true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
unset($prefixes[$i]);
|
||||
$update = true;
|
||||
}
|
||||
if ($update) {
|
||||
$this->set('streams.schemes.environment.prefixes', ['' => array_values($prefixes)]);
|
||||
$this->initializeLocator($locator);
|
||||
}
|
||||
}
|
||||
|
||||
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' => []]);
|
||||
@@ -315,8 +346,8 @@ class Setup extends Data
|
||||
$security_file->save();
|
||||
$security_file->free();
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
|
||||
} catch (RuntimeException $e) {
|
||||
throw new RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,20 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use RocketTheme\Toolbox\Blueprints\BlueprintForm;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function call_user_func_array;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_object;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Blueprint
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class Blueprint extends BlueprintForm
|
||||
{
|
||||
/** @var string */
|
||||
@@ -35,6 +48,9 @@ class Blueprint extends BlueprintForm
|
||||
/** @var array */
|
||||
protected $handlers = [];
|
||||
|
||||
/**
|
||||
* Clone blueprint.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->blueprintSchema) {
|
||||
@@ -84,9 +100,9 @@ class Blueprint extends BlueprintForm
|
||||
$current = $this->getDefaults();
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (\is_object($current) && isset($current->{$field})) {
|
||||
if (is_object($current) && isset($current->{$field})) {
|
||||
$current = $current->{$field};
|
||||
} elseif (\is_array($current) && isset($current[$field])) {
|
||||
} elseif (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return null;
|
||||
@@ -127,7 +143,7 @@ class Blueprint extends BlueprintForm
|
||||
$current = &$this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (\is_object($current)) {
|
||||
if (is_object($current)) {
|
||||
// Handle objects.
|
||||
if (!isset($current->{$field})) {
|
||||
$current->{$field} = [];
|
||||
@@ -136,7 +152,7 @@ class Blueprint extends BlueprintForm
|
||||
$current = &$current->{$field};
|
||||
} else {
|
||||
// Handle arrays and scalars.
|
||||
if (!\is_array($current)) {
|
||||
if (!is_array($current)) {
|
||||
$current = [$field => []];
|
||||
} elseif (!isset($current[$field])) {
|
||||
$current[$field] = [];
|
||||
@@ -164,12 +180,44 @@ class Blueprint extends BlueprintForm
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend blueprint with another blueprint.
|
||||
*
|
||||
* @param BlueprintForm|array $extends
|
||||
* @param bool $append
|
||||
* @return $this
|
||||
*/
|
||||
public function extend($extends, $append = false)
|
||||
{
|
||||
parent::extend($extends, $append);
|
||||
|
||||
$this->deepInit($this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @param string $separator
|
||||
* @param bool $append
|
||||
* @return $this
|
||||
*/
|
||||
public function embed($name, $value, $separator = '/', $append = false)
|
||||
{
|
||||
parent::embed($name, $value, $separator, $append);
|
||||
|
||||
$this->deepInit($this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two arrays by using blueprints.
|
||||
*
|
||||
* @param array $data1
|
||||
* @param array $data2
|
||||
* @param string $name Optional
|
||||
* @param string|null $name Optional
|
||||
* @param string $separator Optional
|
||||
* @return array
|
||||
*/
|
||||
@@ -212,13 +260,15 @@ class Blueprint extends BlueprintForm
|
||||
* Validate data against blueprints.
|
||||
*
|
||||
* @param array $data
|
||||
* @throws \RuntimeException
|
||||
* @param array $options
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function validate(array $data)
|
||||
public function validate(array $data, array $options = [])
|
||||
{
|
||||
$this->initInternals();
|
||||
|
||||
$this->blueprintSchema->validate($data);
|
||||
$this->blueprintSchema->validate($data, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,6 +316,7 @@ class Blueprint extends BlueprintForm
|
||||
/**
|
||||
* @param string $name
|
||||
* @param callable $callable
|
||||
* @return void
|
||||
*/
|
||||
public function addDynamicHandler(string $name, callable $callable): void
|
||||
{
|
||||
@@ -274,6 +325,8 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
/**
|
||||
* Initialize validator.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initInternals()
|
||||
{
|
||||
@@ -307,7 +360,7 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
/**
|
||||
* @param string|array $path
|
||||
* @param string $context
|
||||
* @param string|null $context
|
||||
* @return array
|
||||
*/
|
||||
protected function getFiles($path, $context = null)
|
||||
@@ -315,9 +368,13 @@ class Blueprint extends BlueprintForm
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if (\is_string($path) && !$locator->isStream($path)) {
|
||||
if (is_string($path) && !$locator->isStream($path)) {
|
||||
// Find path overrides.
|
||||
$paths = (array) ($this->overrides[$path] ?? null);
|
||||
if (null === $context) {
|
||||
$paths = (array) ($this->overrides[$path] ?? null);
|
||||
} else {
|
||||
$paths = [];
|
||||
}
|
||||
|
||||
// Add path pointing to default context.
|
||||
if ($context === null) {
|
||||
@@ -341,7 +398,7 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
$files = [];
|
||||
foreach ($paths as $lookup) {
|
||||
if (\is_string($lookup) && strpos($lookup, '://')) {
|
||||
if (is_string($lookup) && strpos($lookup, '://')) {
|
||||
$files = array_merge($files, $locator->findResources($lookup));
|
||||
} else {
|
||||
$files[] = $lookup;
|
||||
@@ -355,12 +412,13 @@ class Blueprint extends BlueprintForm
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicData(array &$field, $property, array &$call)
|
||||
{
|
||||
$params = $call['params'];
|
||||
|
||||
if (\is_array($params)) {
|
||||
if (is_array($params)) {
|
||||
$function = array_shift($params);
|
||||
} else {
|
||||
$function = $params;
|
||||
@@ -371,18 +429,18 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
$data = null;
|
||||
if (!$f) {
|
||||
if (\function_exists($o)) {
|
||||
$data = \call_user_func_array($o, $params);
|
||||
if (function_exists($o)) {
|
||||
$data = call_user_func_array($o, $params);
|
||||
}
|
||||
} else {
|
||||
if (method_exists($o, $f)) {
|
||||
$data = \call_user_func_array([$o, $f], $params);
|
||||
$data = call_user_func_array([$o, $f], $params);
|
||||
}
|
||||
}
|
||||
|
||||
// If function returns a value,
|
||||
if (null !== $data) {
|
||||
if (\is_array($data) && isset($field[$property]) && \is_array($field[$property])) {
|
||||
if (is_array($data) && isset($field[$property]) && is_array($field[$property])) {
|
||||
// Combine field and @data-field together.
|
||||
$field[$property] += $data;
|
||||
} else {
|
||||
@@ -396,15 +454,33 @@ class Blueprint extends BlueprintForm
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicConfig(array &$field, $property, array &$call)
|
||||
{
|
||||
$value = $call['params'];
|
||||
$params = $call['params'];
|
||||
if (is_array($params)) {
|
||||
$value = array_shift($params);
|
||||
$params = array_shift($params);
|
||||
} else {
|
||||
$value = $params;
|
||||
$params = [];
|
||||
}
|
||||
|
||||
$default = $field[$property] ?? null;
|
||||
$config = Grav::instance()['config']->get($value, $default);
|
||||
if (!empty($field['value_only'])) {
|
||||
$config = array_combine($config, $config);
|
||||
}
|
||||
|
||||
if (null !== $config) {
|
||||
$field[$property] = $config;
|
||||
if (!empty($params['append']) && is_array($config) && isset($field[$property]) && is_array($field[$property])) {
|
||||
// Combine field and @config-field together.
|
||||
$field[$property] += $config;
|
||||
} else {
|
||||
// Or create/replace field with @config-field.
|
||||
$field[$property] = $config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +488,7 @@ class Blueprint extends BlueprintForm
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicSecurity(array &$field, $property, array &$call)
|
||||
{
|
||||
@@ -433,6 +510,12 @@ class Blueprint extends BlueprintForm
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface $user
|
||||
* @param array $actions
|
||||
* @param string $op
|
||||
* @return bool
|
||||
*/
|
||||
protected function resolveActions(UserInterface $user, array $actions, string $op = 'and')
|
||||
{
|
||||
$c = $i = count($actions);
|
||||
@@ -455,6 +538,7 @@ class Blueprint extends BlueprintForm
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicScope(array &$field, $property, array &$call)
|
||||
{
|
||||
@@ -463,7 +547,7 @@ class Blueprint extends BlueprintForm
|
||||
}
|
||||
|
||||
$scopes = (array)$call['params'];
|
||||
$matches = \in_array($this->scope, $scopes, true);
|
||||
$matches = in_array($this->scope, $scopes, true);
|
||||
if ($this->scope && $property !== 'ignore') {
|
||||
$matches = !$matches;
|
||||
}
|
||||
@@ -481,7 +565,7 @@ class Blueprint extends BlueprintForm
|
||||
*/
|
||||
protected function addPropertyRecursive(array &$field, $property, $value)
|
||||
{
|
||||
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
|
||||
if (is_array($value) && isset($field[$property]) && is_array($field[$property])) {
|
||||
$field[$property] = array_merge_recursive($field[$property], $value);
|
||||
} else {
|
||||
$field[$property] = $value;
|
||||
|
||||
@@ -9,15 +9,26 @@
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
|
||||
use RocketTheme\Toolbox\Blueprints\BlueprintSchema as BlueprintSchemaBase;
|
||||
use RuntimeException;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class BlueprintSchema
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
{
|
||||
use Export;
|
||||
|
||||
/** @var array */
|
||||
protected $filter = ['validation' => true, 'xss_check' => true];
|
||||
|
||||
/** @var array */
|
||||
protected $ignoreFormKeys = [
|
||||
'title' => true,
|
||||
@@ -49,13 +60,15 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
* Validate data against blueprints.
|
||||
*
|
||||
* @param array $data
|
||||
* @throws \RuntimeException
|
||||
* @param array $options
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function validate(array $data)
|
||||
public function validate(array $data, array $options = [])
|
||||
{
|
||||
try {
|
||||
$messages = $this->validateArray($data, $this->nested, $this->items['']['form'] ?? []);
|
||||
} catch (\RuntimeException $e) {
|
||||
$validation = $this->items['']['form']['validation'] ?? 'loose';
|
||||
$messages = $this->validateArray($data, $this->nested, $validation === 'strict', $options['xss_check'] ?? true);
|
||||
} catch (RuntimeException $e) {
|
||||
throw (new ValidationException($e->getMessage(), $e->getCode(), $e))->setMessages();
|
||||
}
|
||||
|
||||
@@ -131,17 +144,19 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $rules
|
||||
* @param array $parent
|
||||
* @param bool $strict
|
||||
* @param bool $xss
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function validateArray(array $data, array $rules, array $parent)
|
||||
protected function validateArray(array $data, array $rules, bool $strict, bool $xss = true)
|
||||
{
|
||||
$messages = $this->checkRequired($data, $rules);
|
||||
|
||||
foreach ($data as $key => $child) {
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
$rule = is_string($val) ? $this->items[$val] : null;
|
||||
$checkXss = $xss;
|
||||
|
||||
if ($rule) {
|
||||
// Item has been defined in blueprints.
|
||||
@@ -151,12 +166,25 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
}
|
||||
|
||||
$messages += Validation::validate($child, $rule);
|
||||
} elseif (\is_array($child) && \is_array($val)) {
|
||||
|
||||
} elseif (is_array($child) && is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$messages += $this->validateArray($child, $val, $rule ?? []);
|
||||
} elseif (isset($parent['validation']) && $parent['validation'] === 'strict') {
|
||||
// Undefined/extra item.
|
||||
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
|
||||
$messages += $this->validateArray($child, $val, $strict);
|
||||
$checkXss = false;
|
||||
|
||||
} elseif ($strict) {
|
||||
// Undefined/extra item in strict mode.
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
if (!$config->get('system.strict_mode.blueprint_strict_compat', true)) {
|
||||
throw new RuntimeException(sprintf('%s is not defined in blueprints', $key));
|
||||
}
|
||||
|
||||
user_error(sprintf('Having extra key %s in your data is deprecated with blueprint having \'validation: strict\'', $key), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
if ($checkXss) {
|
||||
$messages += Validation::checkSafety($child, $rule ?: ['name' => $key]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,28 +203,9 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
{
|
||||
$results = [];
|
||||
|
||||
if ($missingValuesAsNull) {
|
||||
// First pass is to fill up all the fields with null.
|
||||
foreach ($rules as $key => $val) {
|
||||
if ($key && $key !== '*') {
|
||||
$rule = $this->items[$parent . $key] ?? null;
|
||||
|
||||
if (!$rule || !empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[$key] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $key => $field) {
|
||||
if (null === $field && !$missingValuesAsNull) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : $this->items[$parent . $key] ?? null;
|
||||
$rule = is_string($val) ? $this->items[$val] : $this->items[$parent . $key] ?? null;
|
||||
|
||||
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
|
||||
// Skip any data in the ignored field.
|
||||
@@ -204,11 +213,24 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($rule && $rule['type'] !== '_parent') {
|
||||
if (null === $field) {
|
||||
if ($missingValuesAsNull) {
|
||||
$results[$key] = null;
|
||||
} else {
|
||||
unset($results[$key]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$isParent = isset($val['*']);
|
||||
$type = $rule['type'] ?? null;
|
||||
|
||||
if (!$isParent && $type && $type !== '_parent') {
|
||||
$field = Validation::filter($field, $rule);
|
||||
} elseif (\is_array($field) && \is_array($val)) {
|
||||
} elseif (is_array($field) && is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$field = $this->filterArray($field, $val, $parent . $key . '.', $missingValuesAsNull, $keepEmptyValues);
|
||||
$k = $isParent ? '*' : $key;
|
||||
$field = $this->filterArray($field, $val, $parent . $k . '.', $missingValuesAsNull, $keepEmptyValues);
|
||||
|
||||
if (null === $field) {
|
||||
// Nested parent has no values.
|
||||
@@ -220,7 +242,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($keepEmptyValues || (null !== $field && (!\is_array($field) || !empty($field)))) {
|
||||
if ($keepEmptyValues || (null !== $field && (!is_array($field) || !empty($field)))) {
|
||||
$results[$key] = $field;
|
||||
}
|
||||
}
|
||||
@@ -228,6 +250,11 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
return $results ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $nested
|
||||
* @param string $parent
|
||||
* @return void
|
||||
*/
|
||||
protected function buildIgnoreNested(array $nested, $parent = '')
|
||||
{
|
||||
$ignore = true;
|
||||
@@ -263,8 +290,19 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
// Special toggle handling for all the nested data.
|
||||
$toggle = $toggles[$key] ?? [];
|
||||
if (!is_array($toggle)) {
|
||||
if (!$toggle) {
|
||||
$data[$key] = null;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$toggle = [];
|
||||
}
|
||||
// Recursively fetch the items.
|
||||
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggles[$key] ?? [], $value);
|
||||
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $value);
|
||||
} else {
|
||||
$field = $this->get($value);
|
||||
// Do not add the field if:
|
||||
@@ -299,7 +337,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
$messages = [];
|
||||
|
||||
foreach ($fields as $name => $field) {
|
||||
if (!\is_string($field)) {
|
||||
if (!is_string($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -340,6 +378,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicConfig(array &$field, $property, array &$call)
|
||||
{
|
||||
|
||||
@@ -9,9 +9,17 @@
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
|
||||
/**
|
||||
* Class Blueprints
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class Blueprints
|
||||
{
|
||||
/** @var array|string */
|
||||
@@ -34,7 +42,7 @@ class Blueprints
|
||||
*
|
||||
* @param string $type Blueprint type.
|
||||
* @return Blueprint
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function get($type)
|
||||
{
|
||||
@@ -65,10 +73,9 @@ class Blueprints
|
||||
if ($locator->isStream($this->search)) {
|
||||
$iterator = $locator->getIterator($this->search);
|
||||
} else {
|
||||
$iterator = new \DirectoryIterator($this->search);
|
||||
$iterator = new DirectoryIterator($this->search);
|
||||
}
|
||||
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
if (!$file->isFile() || '.' . $file->getExtension() !== YAML_EXT) {
|
||||
continue;
|
||||
@@ -92,7 +99,7 @@ class Blueprints
|
||||
{
|
||||
$blueprint = new Blueprint($name);
|
||||
|
||||
if (\is_array($this->search) || \is_object($this->search)) {
|
||||
if (is_array($this->search) || is_object($this->search)) {
|
||||
// Page types.
|
||||
$blueprint->setOverrides($this->search);
|
||||
$blueprint->setContext('blueprints://pages');
|
||||
@@ -102,7 +109,7 @@ class Blueprints
|
||||
|
||||
try {
|
||||
$blueprint->load()->init();
|
||||
} catch (\RuntimeException $e) {
|
||||
} catch (RuntimeException $e) {
|
||||
$log = Grav::instance()['log'];
|
||||
$log->error(sprintf('Blueprint %s cannot be loaded: %s', $name, $e->getMessage()));
|
||||
|
||||
|
||||
@@ -9,12 +9,22 @@
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Exception;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Countable;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
use RuntimeException;
|
||||
use function func_get_args;
|
||||
use function is_array;
|
||||
use function is_callable;
|
||||
use function is_object;
|
||||
|
||||
/**
|
||||
* Class Data
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable, ExportInterface
|
||||
{
|
||||
use NestedArrayAccessWithGetters, Countable, Export;
|
||||
@@ -23,7 +33,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
protected $gettersVariable = 'items';
|
||||
/** @var array */
|
||||
protected $items;
|
||||
/** @var Blueprint|null */
|
||||
/** @var Blueprint */
|
||||
protected $blueprints;
|
||||
/** @var FileInterface|null */
|
||||
protected $storage;
|
||||
@@ -40,7 +50,9 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
public function __construct(array $items = [], $blueprints = null)
|
||||
{
|
||||
$this->items = $items;
|
||||
$this->blueprints = $blueprints;
|
||||
if (null !== $blueprints) {
|
||||
$this->blueprints = $blueprints;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,20 +99,20 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
* @param mixed $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function join($name, $value, $separator = '.')
|
||||
{
|
||||
$old = $this->get($name, null, $separator);
|
||||
if ($old !== null) {
|
||||
if (!\is_array($old)) {
|
||||
throw new \RuntimeException('Value ' . $old);
|
||||
if (!is_array($old)) {
|
||||
throw new RuntimeException('Value ' . $old);
|
||||
}
|
||||
|
||||
if (\is_object($value)) {
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
} elseif (!\is_array($value)) {
|
||||
throw new \RuntimeException('Value ' . $value);
|
||||
} elseif (!is_array($value)) {
|
||||
throw new RuntimeException('Value ' . $value);
|
||||
}
|
||||
|
||||
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
|
||||
@@ -133,7 +145,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
*/
|
||||
public function joinDefaults($name, $value, $separator = '.')
|
||||
{
|
||||
if (\is_object($value)) {
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
@@ -154,14 +166,14 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
* @param array|object $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getJoined($name, $value, $separator = '.')
|
||||
{
|
||||
if (\is_object($value)) {
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
} elseif (!\is_array($value)) {
|
||||
throw new \RuntimeException('Value ' . $value);
|
||||
} elseif (!is_array($value)) {
|
||||
throw new RuntimeException('Value ' . $value);
|
||||
}
|
||||
|
||||
$old = $this->get($name, null, $separator);
|
||||
@@ -171,8 +183,8 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!\is_array($old)) {
|
||||
throw new \RuntimeException('Value ' . $old);
|
||||
if (!is_array($old)) {
|
||||
throw new RuntimeException('Value ' . $old);
|
||||
}
|
||||
|
||||
// Return joined data.
|
||||
@@ -210,7 +222,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
* Validate by blueprints.
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
@@ -251,18 +263,19 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
public function blueprints()
|
||||
{
|
||||
if (!$this->blueprints) {
|
||||
$this->blueprints = new Blueprint;
|
||||
} elseif (\is_callable($this->blueprints)) {
|
||||
$this->blueprints = new Blueprint();
|
||||
} elseif (is_callable($this->blueprints)) {
|
||||
// Lazy load blueprints.
|
||||
$blueprints = $this->blueprints;
|
||||
$this->blueprints = $blueprints();
|
||||
}
|
||||
|
||||
return $this->blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data if storage has been defined.
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
@@ -315,6 +328,9 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->items;
|
||||
|
||||
@@ -9,8 +9,13 @@
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Exception;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
|
||||
/**
|
||||
* Interface DataInterface
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
interface DataInterface
|
||||
{
|
||||
/**
|
||||
@@ -41,7 +46,7 @@ interface DataInterface
|
||||
/**
|
||||
* Validate by blueprints.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function validate();
|
||||
|
||||
|
||||
@@ -9,10 +9,29 @@
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use DateTime;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Security;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Traversable;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_float;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Validation
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class Validation
|
||||
{
|
||||
/**
|
||||
@@ -78,6 +97,91 @@ class Validation
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $field
|
||||
*/
|
||||
public static function checkSafety($value, array $field)
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
$type = $field['validate']['type'] ?? $field['type'] ?? 'text';
|
||||
$options = $field['xss_check'] ?? [];
|
||||
if ($options === false || $type === 'unset') {
|
||||
return $messages;
|
||||
}
|
||||
if (!is_array($options)) {
|
||||
$options = [];
|
||||
}
|
||||
|
||||
$name = ucfirst($field['label'] ?? $field['name'] ?? 'UNKNOWN');
|
||||
|
||||
/** @var UserInterface $user */
|
||||
$user = Grav::instance()['user'] ?? null;
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$xss_whitelist = $config->get('security.xss_whitelist', 'admin.super');
|
||||
|
||||
// Get language class.
|
||||
/** @var Language $language */
|
||||
$language = Grav::instance()['language'];
|
||||
|
||||
if (!static::authorize($xss_whitelist, $user)) {
|
||||
$defaults = Security::getXssDefaults();
|
||||
$options += $defaults;
|
||||
$options['enabled_rules'] += $defaults['enabled_rules'];
|
||||
if (!empty($options['safe_protocols'])) {
|
||||
$options['invalid_protocols'] = array_diff($options['invalid_protocols'], $options['safe_protocols']);
|
||||
}
|
||||
if (!empty($options['safe_tags'])) {
|
||||
$options['dangerous_tags'] = array_diff($options['dangerous_tags'], $options['safe_tags']);
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$violation = Security::detectXss($value, $options);
|
||||
if ($violation) {
|
||||
$messages[$name][] = $language->translate(['GRAV.FORM.XSS_ISSUES', $language->translate($name)], null, true);
|
||||
}
|
||||
} elseif (is_array($value)) {
|
||||
$violations = Security::detectXssFromArray($value, "{$name}.", $options);
|
||||
if ($violations) {
|
||||
$messages[$name][] = $language->translate(['GRAV.FORM.XSS_ISSUES', $language->translate($name)], null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user authorisation to the action.
|
||||
*
|
||||
* @param string|string[] $action
|
||||
* @param UserInterface|null $user
|
||||
* @return bool
|
||||
*/
|
||||
public static function authorize($action, UserInterface $user = null)
|
||||
{
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$action = (array)$action;
|
||||
foreach ($action as $a) {
|
||||
// Ignore 'admin.super' if it's not the only value to be checked.
|
||||
if ($a === 'admin.super' && count($action) > 1 && $user instanceof FlexObjectInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->authorize($a)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter value against a blueprint field definition.
|
||||
*
|
||||
@@ -123,7 +227,7 @@ class Validation
|
||||
*/
|
||||
public static function typeText($value, array $params, array $field)
|
||||
{
|
||||
if (!\is_string($value) && !is_numeric($value)) {
|
||||
if (!is_string($value) && !is_numeric($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -161,7 +265,7 @@ class Validation
|
||||
*/
|
||||
protected static function filterText($value, array $params, array $field)
|
||||
{
|
||||
if (!\is_string($value) && !is_numeric($value)) {
|
||||
if (!is_string($value) && !is_numeric($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -194,7 +298,7 @@ class Validation
|
||||
*/
|
||||
protected static function filterCommaList($value, array $params, array $field)
|
||||
{
|
||||
return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +309,18 @@ class Validation
|
||||
*/
|
||||
public static function typeCommaList($value, array $params, array $field)
|
||||
{
|
||||
return \is_array($value) ? true : self::typeText($value, $params, $field);
|
||||
return is_array($value) ? true : self::typeText($value, $params, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $params
|
||||
* @param array $field
|
||||
* @return array|array[]|false|string[]
|
||||
*/
|
||||
protected static function filterLines($value, array $params, array $field)
|
||||
{
|
||||
return is_array($value) ? $value : preg_split('/\s*[\r\n]+\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,7 +453,7 @@ class Validation
|
||||
*/
|
||||
public static function typeToggle($value, array $params, array $field)
|
||||
{
|
||||
if (\is_bool($value)) {
|
||||
if (is_bool($value)) {
|
||||
$value = (int)$value;
|
||||
}
|
||||
|
||||
@@ -430,7 +545,7 @@ class Validation
|
||||
{
|
||||
$format = Grav::instance()['config']->get('system.pages.dateformat.default');
|
||||
if ($format) {
|
||||
$converted = new \DateTime($value);
|
||||
$converted = new DateTime($value);
|
||||
return $converted->format($format);
|
||||
}
|
||||
return $value;
|
||||
@@ -483,7 +598,7 @@ class Validation
|
||||
*/
|
||||
public static function typeEmail($value, array $params, array $field)
|
||||
{
|
||||
$values = !\is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
|
||||
$values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
|
||||
|
||||
foreach ($values as $val) {
|
||||
if (!(self::typeText($val, $params, $field) && filter_var($val, FILTER_VALIDATE_EMAIL))) {
|
||||
@@ -518,17 +633,17 @@ class Validation
|
||||
*/
|
||||
public static function typeDatetime($value, array $params, array $field)
|
||||
{
|
||||
if ($value instanceof \DateTime) {
|
||||
if ($value instanceof DateTime) {
|
||||
return true;
|
||||
}
|
||||
if (!\is_string($value)) {
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($params['format'])) {
|
||||
return false !== strtotime($value);
|
||||
}
|
||||
|
||||
$dateFromFormat = \DateTime::createFromFormat($params['format'], $value);
|
||||
$dateFromFormat = DateTime::createFromFormat($params['format'], $value);
|
||||
|
||||
return $dateFromFormat && $value === date($params['format'], $dateFromFormat->getTimestamp());
|
||||
}
|
||||
@@ -624,21 +739,21 @@ class Validation
|
||||
*/
|
||||
public static function typeArray($value, array $params, array $field)
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($field['multiple'])) {
|
||||
if (isset($params['min']) && \count($value) < $params['min']) {
|
||||
if (isset($params['min']) && count($value) < $params['min']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['max']) && \count($value) > $params['max']) {
|
||||
if (isset($params['max']) && count($value) > $params['max']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$min = $params['min'] ?? 0;
|
||||
if (isset($params['step']) && (\count($value) - $min) % $params['step'] === 0) {
|
||||
if (isset($params['step']) && (count($value) - $min) % $params['step'] === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -686,7 +801,7 @@ class Validation
|
||||
$options = isset($field['options']) ? array_keys($field['options']) : [];
|
||||
$multi = $field['multiple'] ?? false;
|
||||
|
||||
if (\count($values) === 1 && isset($values[0]) && $values[0] === '') {
|
||||
if (count($values) === 1 && isset($values[0]) && $values[0] === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -700,7 +815,7 @@ class Validation
|
||||
|
||||
if ($multi) {
|
||||
foreach ($values as $key => $val) {
|
||||
if (\is_array($val)) {
|
||||
if (is_array($val)) {
|
||||
$val = implode(',', $val);
|
||||
$values[$key] = array_map('trim', explode(',', $val));
|
||||
} else {
|
||||
@@ -742,7 +857,7 @@ class Validation
|
||||
unset($values[$key]);
|
||||
}
|
||||
}
|
||||
if (\is_array($val)) {
|
||||
if (is_array($val)) {
|
||||
$val = static::arrayFilterRecurse($val, $params);
|
||||
if ($params['ignore_empty'] && empty($val)) {
|
||||
unset($values[$key]);
|
||||
@@ -792,7 +907,7 @@ class Validation
|
||||
*/
|
||||
public static function typeList($value, array $params, array $field)
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -827,7 +942,7 @@ class Validation
|
||||
*/
|
||||
public static function filterYaml($value, $params)
|
||||
{
|
||||
if (!\is_string($value)) {
|
||||
if (!is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -937,7 +1052,7 @@ class Validation
|
||||
*/
|
||||
public static function typeBool($value, $params)
|
||||
{
|
||||
return \is_bool($value) || $value == 1 || $value == 0;
|
||||
return is_bool($value) || $value == 1 || $value == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -947,7 +1062,7 @@ class Validation
|
||||
*/
|
||||
public static function validateBool($value, $params)
|
||||
{
|
||||
return \is_bool($value) || $value == 1 || $value == 0;
|
||||
return is_bool($value) || $value == 1 || $value == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -977,7 +1092,7 @@ class Validation
|
||||
*/
|
||||
public static function validateFloat($value, $params)
|
||||
{
|
||||
return \is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
|
||||
return is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1027,7 +1142,7 @@ class Validation
|
||||
*/
|
||||
public static function validateArray($value, $params)
|
||||
{
|
||||
return \is_array($value) || ($value instanceof \ArrayAccess && $value instanceof \Traversable && $value instanceof \Countable);
|
||||
return is_array($value) || ($value instanceof ArrayAccess && $value instanceof Traversable && $value instanceof Countable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
/**
|
||||
* Class ValidationException
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class ValidationException extends \RuntimeException
|
||||
{
|
||||
/** @var array */
|
||||
|
||||
@@ -25,6 +25,7 @@ use DebugBar\DataCollector\PhpInfoCollector;
|
||||
use DebugBar\DataCollector\RequestDataCollector;
|
||||
use DebugBar\DataCollector\TimeDataCollector;
|
||||
use DebugBar\DebugBar;
|
||||
use DebugBar\DebugBarException;
|
||||
use DebugBar\JavascriptRenderer;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Processors\ProcessorInterface;
|
||||
@@ -34,55 +35,59 @@ use Monolog\Logger;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use ReflectionObject;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Throwable;
|
||||
use Twig\Environment;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
use function array_slice;
|
||||
use function count;
|
||||
use function define;
|
||||
use function defined;
|
||||
use function extension_loaded;
|
||||
use function get_class;
|
||||
use function gettype;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_object;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Debugger
|
||||
* @package Grav\Common
|
||||
*/
|
||||
class Debugger
|
||||
{
|
||||
/** @var static */
|
||||
protected static $instance;
|
||||
|
||||
/** @var Grav|null */
|
||||
protected $grav;
|
||||
|
||||
/** @var Config|null */
|
||||
protected $config;
|
||||
|
||||
/** @var JavascriptRenderer|null */
|
||||
protected $renderer;
|
||||
|
||||
/** @var DebugBar|null */
|
||||
protected $debugbar;
|
||||
|
||||
/** @var Clockwork|null */
|
||||
protected $clockwork;
|
||||
|
||||
/** @var bool */
|
||||
protected $enabled = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $initialized = false;
|
||||
|
||||
/** @var array */
|
||||
protected $timers = [];
|
||||
|
||||
/** @var array */
|
||||
protected $deprecations = [];
|
||||
|
||||
/** @var callable|null */
|
||||
protected $errorHandler;
|
||||
|
||||
/** @var float */
|
||||
protected $requestTime;
|
||||
|
||||
/** @var float */
|
||||
protected $currentTime;
|
||||
|
||||
/** @var int */
|
||||
protected $profiling = 0;
|
||||
|
||||
/** @var bool */
|
||||
protected $censored = false;
|
||||
|
||||
@@ -95,8 +100,8 @@ class Debugger
|
||||
|
||||
$this->currentTime = microtime(true);
|
||||
|
||||
if (!\defined('GRAV_REQUEST_TIME')) {
|
||||
\define('GRAV_REQUEST_TIME', $this->currentTime);
|
||||
if (!defined('GRAV_REQUEST_TIME')) {
|
||||
define('GRAV_REQUEST_TIME', $this->currentTime);
|
||||
}
|
||||
|
||||
$this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
|
||||
@@ -105,6 +110,9 @@ class Debugger
|
||||
$this->setErrorHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Clockwork|null
|
||||
*/
|
||||
public function getClockwork(): ?Clockwork
|
||||
{
|
||||
return $this->enabled ? $this->clockwork : null;
|
||||
@@ -114,7 +122,7 @@ class Debugger
|
||||
* Initialize the debugger
|
||||
*
|
||||
* @return $this
|
||||
* @throws \DebugBar\DebugBarException
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
@@ -155,16 +163,17 @@ class Debugger
|
||||
$clockwork->addDataSource(new MonologDataSource($log));
|
||||
}
|
||||
|
||||
$clockwork->addDataSource(new TwigClockworkDataSource());
|
||||
|
||||
$timeLine = $clockwork->getTimeline();
|
||||
$timeline = $clockwork->timeline();
|
||||
if ($this->requestTime !== GRAV_REQUEST_TIME) {
|
||||
$timeLine->addEvent('server', 'Server', $this->requestTime, GRAV_REQUEST_TIME);
|
||||
$event = $timeline->event('Server');
|
||||
$event->finalize($this->requestTime, GRAV_REQUEST_TIME);
|
||||
}
|
||||
if ($this->currentTime !== GRAV_REQUEST_TIME) {
|
||||
$timeLine->addEvent('loading', 'Loading', GRAV_REQUEST_TIME, $this->currentTime);
|
||||
$event = $timeline->event('Loading');
|
||||
$event->finalize(GRAV_REQUEST_TIME, $this->currentTime);
|
||||
}
|
||||
$timeLine->addEvent('setup', 'Site Setup', $this->currentTime, microtime(true));
|
||||
$event = $timeline->event('Site Setup');
|
||||
$event->finalize($this->currentTime, microtime(true));
|
||||
}
|
||||
|
||||
if ($this->censored) {
|
||||
@@ -223,12 +232,14 @@ class Debugger
|
||||
$userData->counters([
|
||||
'Deprecated' => count($deprecations)
|
||||
]);
|
||||
/*
|
||||
foreach ($deprecations as &$deprecation) {
|
||||
$d = $deprecation;
|
||||
unset($d['message']);
|
||||
$this->clockwork->log('deprecated', $deprecation['message'], $d);
|
||||
}
|
||||
unset($deprecation);
|
||||
*/
|
||||
|
||||
$userData->table('Your site is using following deprecated features', $deprecations);
|
||||
}
|
||||
@@ -244,7 +255,7 @@ class Debugger
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$clockwork->getTimeline()->finalize($request->getAttribute('request_time'));
|
||||
$clockwork->timeline()->finalize($request->getAttribute('request_time'));
|
||||
|
||||
if ($this->censored) {
|
||||
$censored = 'CENSORED';
|
||||
@@ -325,28 +336,30 @@ class Debugger
|
||||
return new Response(200, $headers, json_encode($data));
|
||||
}
|
||||
|
||||
protected function addMeasures()
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function addMeasures(): void
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nowTime = microtime(true);
|
||||
$clkTimeLine = $this->clockwork ? $this->clockwork->getTimeline() : null;
|
||||
$clkTimeLine = $this->clockwork ? $this->clockwork->timeline() : null;
|
||||
$debTimeLine = $this->debugbar ? $this->debugbar['time'] : null;
|
||||
foreach ($this->timers as $name => $data) {
|
||||
$description = $data[0];
|
||||
$startTime = $data[1] ?? null;
|
||||
$endTime = $data[2] ?? $nowTime;
|
||||
if ($endTime - $startTime < 0.001) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($clkTimeLine) {
|
||||
$clkTimeLine->addEvent($name, $description ?? $name, $startTime, $endTime);
|
||||
}
|
||||
$event = $clkTimeLine->event($description);
|
||||
$event->finalize($startTime, $endTime);
|
||||
} elseif ($debTimeLine) {
|
||||
if ($endTime - $startTime < 0.001) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($debTimeLine) {
|
||||
$debTimeLine->addMeasure($description ?? $name, $startTime, $endTime);
|
||||
}
|
||||
}
|
||||
@@ -356,8 +369,7 @@ class Debugger
|
||||
/**
|
||||
* Set/get the enabled state of the debugger
|
||||
*
|
||||
* @param bool $state If null, the method returns the enabled value. If set, the method sets the enabled state
|
||||
*
|
||||
* @param bool|null $state If null, the method returns the enabled value. If set, the method sets the enabled state
|
||||
* @return bool
|
||||
*/
|
||||
public function enabled($state = null)
|
||||
@@ -401,7 +413,7 @@ class Debugger
|
||||
$this->renderer = $this->debugbar->getJavascriptRenderer();
|
||||
$this->renderer->setIncludeVendors(false);
|
||||
|
||||
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
|
||||
[$css_files, $js_files] = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
|
||||
|
||||
foreach ((array)$css_files as $css) {
|
||||
$assets->addCss($css);
|
||||
@@ -418,6 +430,10 @@ class Debugger
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getCaller($limit = 2)
|
||||
{
|
||||
$trace = debug_backtrace(false, $limit);
|
||||
@@ -429,9 +445,8 @@ class Debugger
|
||||
* Adds a data collector
|
||||
*
|
||||
* @param DataCollectorInterface $collector
|
||||
*
|
||||
* @return $this
|
||||
* @throws \DebugBar\DebugBarException
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
public function addCollector($collector)
|
||||
{
|
||||
@@ -446,9 +461,8 @@ class Debugger
|
||||
* Returns a data collector
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return DataCollectorInterface|null
|
||||
* @throws \DebugBar\DebugBarException
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
public function getCollector($name)
|
||||
{
|
||||
@@ -520,7 +534,7 @@ class Debugger
|
||||
* Hierarchical Profiler support.
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param string $message
|
||||
* @param string|null $message
|
||||
* @return mixed
|
||||
*/
|
||||
public function profile(callable $callable, string $message = null)
|
||||
@@ -532,8 +546,20 @@ class Debugger
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function addTwigProfiler(Environment $twig): void
|
||||
{
|
||||
$clockwork = $this->getClockwork();
|
||||
if ($clockwork) {
|
||||
$source = new TwigClockworkDataSource($twig);
|
||||
$source->listenToEvents();
|
||||
$clockwork->addDataSource($source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start profiling code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startProfiling(): void
|
||||
{
|
||||
@@ -548,7 +574,7 @@ class Debugger
|
||||
/**
|
||||
* Stop profiling code. Returns profiling array or null if profiling couldn't be done.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string|null $message
|
||||
* @return array|null
|
||||
*/
|
||||
public function stopProfiling(string $message = null): ?array
|
||||
@@ -577,6 +603,10 @@ class Debugger
|
||||
return $timings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $timings
|
||||
* @return array
|
||||
*/
|
||||
protected function buildProfilerTimings(array $timings): array
|
||||
{
|
||||
// Filter method calls which take almost no time.
|
||||
@@ -615,6 +645,10 @@ class Debugger
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $call
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
protected function parseProfilerCall(?string $call)
|
||||
{
|
||||
if (null === $call) {
|
||||
@@ -650,7 +684,6 @@ class Debugger
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function startTimer($name, $description = null)
|
||||
@@ -664,7 +697,6 @@ class Debugger
|
||||
* Stop the named timer
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function stopTimer($name)
|
||||
@@ -683,7 +715,6 @@ class Debugger
|
||||
* @param mixed $message
|
||||
* @param string $label
|
||||
* @param mixed|bool $isString
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addMessage($message, $label = 'info', $isString = true)
|
||||
@@ -732,24 +763,26 @@ class Debugger
|
||||
* @param string $name
|
||||
* @param object $event
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @param float|null $time
|
||||
* @return $this
|
||||
*/
|
||||
public function addEvent(string $name, $event, EventDispatcherInterface $dispatcher)
|
||||
public function addEvent(string $name, $event, EventDispatcherInterface $dispatcher, float $time = null)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
if ($this->clockwork) {
|
||||
$data = null;
|
||||
if ($event && method_exists($event, '__debugInfo')) {
|
||||
$data = $event;
|
||||
}
|
||||
if ($this->enabled && $this->clockwork) {
|
||||
$time = $time ?? microtime(true);
|
||||
$duration = (microtime(true) - $time) * 1000;
|
||||
|
||||
$listeners = [];
|
||||
foreach ($dispatcher->getListeners($name) as $listener) {
|
||||
$listeners[] = $this->resolveCallable($listener);
|
||||
}
|
||||
|
||||
$this->clockwork->addEvent($name, $data, microtime(true), ['listeners' => $listeners]);
|
||||
$data = null;
|
||||
if ($event && method_exists($event, '__debugInfo')) {
|
||||
$data = $event;
|
||||
}
|
||||
|
||||
$listeners = [];
|
||||
foreach ($dispatcher->getListeners($name) as $listener) {
|
||||
$listeners[] = $this->resolveCallable($listener);
|
||||
}
|
||||
|
||||
$this->clockwork->addEvent($name, $data, $time, ['listeners' => $listeners, 'duration' => $duration]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -758,10 +791,10 @@ class Debugger
|
||||
/**
|
||||
* Dump exception into the Messages tab of the Debug Bar
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @param Throwable $e
|
||||
* @return Debugger
|
||||
*/
|
||||
public function addException(\Throwable $e)
|
||||
public function addException(Throwable $e)
|
||||
{
|
||||
if ($this->initialized && $this->enabled) {
|
||||
if ($this->debugbar) {
|
||||
@@ -780,6 +813,9 @@ class Debugger
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setErrorHandler()
|
||||
{
|
||||
$this->errorHandler = set_error_handler(
|
||||
@@ -834,7 +870,7 @@ class Debugger
|
||||
if ($arg instanceof \SplFileInfo) {
|
||||
$arg = $arg->getPathname();
|
||||
}
|
||||
if (\is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
|
||||
if (is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
|
||||
$errfile = $arg;
|
||||
$errline = 0;
|
||||
|
||||
@@ -852,18 +888,18 @@ class Debugger
|
||||
if (isset($current['args'])) {
|
||||
$args = [];
|
||||
foreach ($current['args'] as $arg) {
|
||||
if (\is_string($arg)) {
|
||||
if (is_string($arg)) {
|
||||
$arg = "'" . $arg . "'";
|
||||
if (mb_strlen($arg) > 100) {
|
||||
$arg = 'string';
|
||||
}
|
||||
} elseif (\is_bool($arg)) {
|
||||
} elseif (is_bool($arg)) {
|
||||
$arg = $arg ? 'true' : 'false';
|
||||
} elseif (\is_scalar($arg)) {
|
||||
} elseif (is_scalar($arg)) {
|
||||
$arg = $arg;
|
||||
} elseif (\is_object($arg)) {
|
||||
} elseif (is_object($arg)) {
|
||||
$arg = get_class($arg) . ' $object';
|
||||
} elseif (\is_array($arg)) {
|
||||
} elseif (is_array($arg)) {
|
||||
$arg = '$array';
|
||||
} else {
|
||||
$arg = '$object';
|
||||
@@ -879,7 +915,7 @@ class Debugger
|
||||
|
||||
$reflection = null;
|
||||
if ($object instanceof TemplateWrapper) {
|
||||
$reflection = new \ReflectionObject($object);
|
||||
$reflection = new ReflectionObject($object);
|
||||
$property = $reflection->getProperty('template');
|
||||
$property->setAccessible(true);
|
||||
$object = $property->getValue($object);
|
||||
@@ -989,6 +1025,9 @@ class Debugger
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getDeprecations(): array
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
@@ -1004,6 +1043,10 @@ class Debugger
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
protected function addDeprecations()
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
@@ -1022,6 +1065,10 @@ class Debugger
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $deprecated
|
||||
* @return array
|
||||
*/
|
||||
protected function getDepracatedMessage($deprecated)
|
||||
{
|
||||
$scope = $deprecated['scope'];
|
||||
@@ -1059,6 +1106,10 @@ class Debugger
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $trace
|
||||
* @return string
|
||||
*/
|
||||
protected function getFunction($trace)
|
||||
{
|
||||
if (!isset($trace['function'])) {
|
||||
@@ -1068,6 +1119,10 @@ class Debugger
|
||||
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callable
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveCallable(callable $callable)
|
||||
{
|
||||
if (is_array($callable)) {
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Errors;
|
||||
|
||||
use Whoops\Handler\Handler;
|
||||
|
||||
/**
|
||||
* Class BareHandler
|
||||
* @package Grav\Common\Errors
|
||||
*/
|
||||
class BareHandler extends Handler
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -9,11 +9,20 @@
|
||||
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Grav;
|
||||
use Whoops;
|
||||
use function is_int;
|
||||
|
||||
/**
|
||||
* Class Errors
|
||||
* @package Grav\Common\Errors
|
||||
*/
|
||||
class Errors
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function resetHandlers()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
@@ -59,7 +68,7 @@ class Errors
|
||||
$whoops->prependHandler(function ($exception, $inspector, $run) use ($logger) {
|
||||
try {
|
||||
$logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString());
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,10 +9,15 @@
|
||||
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use RuntimeException;
|
||||
use Whoops\Handler\Handler;
|
||||
use Whoops\Util\Misc;
|
||||
use Whoops\Util\TemplateHelper;
|
||||
|
||||
/**
|
||||
* Class SimplePageHandler
|
||||
* @package Grav\Common\Errors
|
||||
*/
|
||||
class SimplePageHandler extends Handler
|
||||
{
|
||||
/** @var array */
|
||||
@@ -62,7 +67,7 @@ class SimplePageHandler extends Handler
|
||||
/**
|
||||
* @param string $resource
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function getResource($resource)
|
||||
{
|
||||
@@ -85,7 +90,7 @@ class SimplePageHandler extends Handler
|
||||
}
|
||||
|
||||
// If we got this far, nothing was found.
|
||||
throw new \RuntimeException(
|
||||
throw new RuntimeException(
|
||||
"Could not find resource '{$resource}' in any resource paths (searched: " . implode(', ', $this->searchPaths). ')'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
/**
|
||||
* Class SystemFacade
|
||||
* @package Grav\Common\Errors
|
||||
*/
|
||||
class SystemFacade extends \Whoops\Util\SystemFacade
|
||||
{
|
||||
/** @var callable */
|
||||
@@ -26,6 +30,8 @@ class SystemFacade extends \Whoops\Util\SystemFacade
|
||||
|
||||
/**
|
||||
* Special case to deal with Fatal errors and the like.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleShutdown()
|
||||
{
|
||||
|
||||
@@ -9,15 +9,24 @@
|
||||
|
||||
namespace Grav\Common\File;
|
||||
|
||||
use Exception;
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use function function_exists;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* Trait CompiledFile
|
||||
* @package Grav\Common\File
|
||||
*/
|
||||
trait CompiledFile
|
||||
{
|
||||
/**
|
||||
* Get/set parsed file contents.
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return string|array
|
||||
* @return array
|
||||
*/
|
||||
public function content($var = null)
|
||||
{
|
||||
@@ -28,9 +37,12 @@ trait CompiledFile
|
||||
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
|
||||
|
||||
$modified = $this->modified();
|
||||
|
||||
if (!$modified) {
|
||||
return $this->decode($this->raw());
|
||||
try {
|
||||
return $this->decode($this->raw());
|
||||
} catch (Throwable $e) {
|
||||
// If the compiled file is broken, we can safely ignore the error and continue.
|
||||
}
|
||||
}
|
||||
|
||||
$class = get_class($this);
|
||||
@@ -46,7 +58,7 @@ trait CompiledFile
|
||||
// Attempt to lock the file for writing.
|
||||
try {
|
||||
$file->lock(false);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
// Another process has locked the file; we will check this in a bit.
|
||||
}
|
||||
|
||||
@@ -75,8 +87,8 @@ trait CompiledFile
|
||||
|
||||
$this->content = $cache['data'];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException(sprintf('Failed to read %s: %s', basename($this->filename), $e->getMessage()), 500, $e);
|
||||
} catch (Exception $e) {
|
||||
throw new RuntimeException(sprintf('Failed to read %s: %s', basename($this->filename), $e->getMessage()), 500, $e);
|
||||
}
|
||||
|
||||
return parent::content($var);
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\JsonFile;
|
||||
|
||||
/**
|
||||
* Class CompiledJsonFile
|
||||
* @package Grav\Common\File
|
||||
*/
|
||||
class CompiledJsonFile extends JsonFile
|
||||
{
|
||||
use CompiledFile;
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
|
||||
/**
|
||||
* Class CompiledMarkdownFile
|
||||
* @package Grav\Common\File
|
||||
*/
|
||||
class CompiledMarkdownFile extends MarkdownFile
|
||||
{
|
||||
use CompiledFile;
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
/**
|
||||
* Class CompiledYamlFile
|
||||
* @package Grav\Common\File
|
||||
*/
|
||||
class CompiledYamlFile extends YamlFile
|
||||
{
|
||||
use CompiledFile;
|
||||
|
||||
@@ -9,8 +9,16 @@
|
||||
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
use FilesystemIterator;
|
||||
use Grav\Common\Utils;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use function function_exists;
|
||||
|
||||
/**
|
||||
* Class Archiver
|
||||
* @package Grav\Common\Filesystem
|
||||
*/
|
||||
abstract class Archiver
|
||||
{
|
||||
/** @var array */
|
||||
@@ -42,6 +50,7 @@ abstract class Archiver
|
||||
public function setArchive($archive_file)
|
||||
{
|
||||
$this->archive_file = $archive_file;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -52,11 +61,12 @@ abstract class Archiver
|
||||
public function setOptions($options)
|
||||
{
|
||||
// Set infinite PHP execution time if possible.
|
||||
if (function_exists('set_time_limit') && !Utils::isFunctionDisabled('set_time_limit')) {
|
||||
if (Utils::functionExists('set_time_limit')) {
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
$this->options = $options + $this->options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -83,15 +93,15 @@ abstract class Archiver
|
||||
|
||||
/**
|
||||
* @param string $rootPath
|
||||
* @return \RecursiveIteratorIterator
|
||||
* @return RecursiveIteratorIterator
|
||||
*/
|
||||
protected function getArchiveFiles($rootPath)
|
||||
{
|
||||
$exclude_paths = $this->options['exclude_paths'];
|
||||
$exclude_files = $this->options['exclude_files'];
|
||||
$dirItr = new \RecursiveDirectoryIterator($rootPath, \RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::UNIX_PATHS);
|
||||
$dirItr = new RecursiveDirectoryIterator($rootPath, RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS);
|
||||
$filterItr = new RecursiveDirectoryFilterIterator($dirItr, $rootPath, $exclude_paths, $exclude_files);
|
||||
$files = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$files = new RecursiveIteratorIterator($filterItr, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,18 @@
|
||||
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Exception;
|
||||
use FilesystemIterator;
|
||||
use Grav\Common\Grav;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function is_callable;
|
||||
|
||||
abstract class Folder
|
||||
{
|
||||
@@ -30,16 +40,15 @@ abstract class Folder
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
$filter = new RecursiveFolderFilterIterator($directory);
|
||||
$iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $dir) {
|
||||
$dir_modified = $dir->getMTime();
|
||||
if ($dir_modified > $last_modified) {
|
||||
@@ -67,23 +76,23 @@ abstract class Folder
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
$recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
|
||||
$recursive = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator = new RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
/** @var RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $filepath => $file) {
|
||||
try {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -102,17 +111,17 @@ abstract class Folder
|
||||
$files = [];
|
||||
|
||||
if (file_exists($path)) {
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = $file->getPathname() . '?'. $file->getMTime();
|
||||
@@ -201,12 +210,12 @@ abstract class Folder
|
||||
* @param string $path
|
||||
* @param array $params
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function all($path, array $params = [])
|
||||
{
|
||||
if (!$path) {
|
||||
throw new \RuntimeException("Path doesn't exist.");
|
||||
throw new RuntimeException("Path doesn't exist.");
|
||||
}
|
||||
if (!file_exists($path)) {
|
||||
return [];
|
||||
@@ -225,26 +234,26 @@ abstract class Folder
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($recursive) {
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
|
||||
+ \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS;
|
||||
$flags = RecursiveDirectoryIterator::SKIP_DOTS + FilesystemIterator::UNIX_PATHS
|
||||
+ FilesystemIterator::CURRENT_AS_SELF + FilesystemIterator::FOLLOW_SYMLINKS;
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator->setMaxDepth(max($levels, -1));
|
||||
} else {
|
||||
if ($locator->isStream($path)) {
|
||||
$iterator = $locator->getIterator($path);
|
||||
} else {
|
||||
$iterator = new \FilesystemIterator($path);
|
||||
$iterator = new FilesystemIterator($path);
|
||||
}
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
/** @var RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
// Ignore hidden files.
|
||||
if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) {
|
||||
@@ -291,8 +300,9 @@ abstract class Folder
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @param string $ignore Ignore files matching pattern (regular expression).
|
||||
* @throws \RuntimeException
|
||||
* @param string|null $ignore Ignore files matching pattern (regular expression).
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function copy($source, $target, $ignore = null)
|
||||
{
|
||||
@@ -300,7 +310,7 @@ abstract class Folder
|
||||
$target = rtrim($target, '\\/');
|
||||
|
||||
if (!is_dir($source)) {
|
||||
throw new \RuntimeException('Cannot copy non-existing folder.');
|
||||
throw new RuntimeException('Cannot copy non-existing folder.');
|
||||
}
|
||||
|
||||
// Make sure that path to the target exists before copying.
|
||||
@@ -330,7 +340,7 @@ abstract class Folder
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message'] ?? 'Unknown error');
|
||||
throw new RuntimeException($error['message'] ?? 'Unknown error');
|
||||
}
|
||||
|
||||
// Make sure that the change will be detected when caching.
|
||||
@@ -342,13 +352,14 @@ abstract class Folder
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function move($source, $target)
|
||||
{
|
||||
if (!file_exists($source) || !is_dir($source)) {
|
||||
// Rename fails if source folder does not exist.
|
||||
throw new \RuntimeException('Cannot move non-existing folder.');
|
||||
throw new RuntimeException('Cannot move non-existing folder.');
|
||||
}
|
||||
|
||||
// Don't do anything if the source is the same as the new target
|
||||
@@ -358,7 +369,7 @@ abstract class Folder
|
||||
|
||||
if (file_exists($target)) {
|
||||
// Rename fails if target folder exists.
|
||||
throw new \RuntimeException('Cannot move files to existing folder/file.');
|
||||
throw new RuntimeException('Cannot move files to existing folder/file.');
|
||||
}
|
||||
|
||||
// Make sure that path to the target exists before moving.
|
||||
@@ -390,7 +401,7 @@ abstract class Folder
|
||||
* @param string $target
|
||||
* @param bool $include_target
|
||||
* @return bool
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function delete($target, $include_target = true)
|
||||
{
|
||||
@@ -402,7 +413,7 @@ abstract class Folder
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message']);
|
||||
throw new RuntimeException($error['message']);
|
||||
}
|
||||
|
||||
// Make sure that the change will be detected when caching.
|
||||
@@ -417,7 +428,8 @@ abstract class Folder
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function mkdir($folder)
|
||||
{
|
||||
@@ -426,7 +438,8 @@ abstract class Folder
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function create($folder)
|
||||
{
|
||||
@@ -441,7 +454,7 @@ abstract class Folder
|
||||
// Take yet another look, make sure that the folder doesn't exist.
|
||||
clearstatcache(true, $folder);
|
||||
if (!@is_dir($folder)) {
|
||||
throw new \RuntimeException(sprintf('Unable to create directory: %s', $folder));
|
||||
throw new RuntimeException(sprintf('Unable to create directory: %s', $folder));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,9 +464,8 @@ abstract class Folder
|
||||
*
|
||||
* @param string $src
|
||||
* @param string $dest
|
||||
*
|
||||
* @return bool
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function rcopy($src, $dest)
|
||||
{
|
||||
@@ -470,8 +482,7 @@ abstract class Folder
|
||||
}
|
||||
|
||||
// Open the source directory to read in files
|
||||
$i = new \DirectoryIterator($src);
|
||||
/** @var \DirectoryIterator $f */
|
||||
$i = new DirectoryIterator($src);
|
||||
foreach ($i as $f) {
|
||||
if ($f->isFile()) {
|
||||
copy($f->getRealPath(), "{$dest}/" . $f->getFilename());
|
||||
@@ -484,6 +495,22 @@ abstract class Folder
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a directory contain children
|
||||
*
|
||||
* @param string $directory
|
||||
* @return int|false
|
||||
*/
|
||||
public static function countChildren($directory)
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
return false;
|
||||
}
|
||||
$directories = glob($directory . '/*', GLOB_ONLYDIR);
|
||||
|
||||
return count($directories);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @param bool $include_target
|
||||
|
||||
@@ -9,7 +9,16 @@
|
||||
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
|
||||
use RecursiveFilterIterator;
|
||||
use RecursiveIterator;
|
||||
use SplFileInfo;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Class RecursiveDirectoryFilterIterator
|
||||
* @package Grav\Common\Filesystem
|
||||
*/
|
||||
class RecursiveDirectoryFilterIterator extends RecursiveFilterIterator
|
||||
{
|
||||
/** @var string */
|
||||
protected static $root;
|
||||
@@ -21,12 +30,12 @@ class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
|
||||
/**
|
||||
* Create a RecursiveFilterIterator from a RecursiveIterator
|
||||
*
|
||||
* @param \RecursiveIterator $iterator
|
||||
* @param RecursiveIterator $iterator
|
||||
* @param string $root
|
||||
* @param array $ignore_folders
|
||||
* @param array $ignore_files
|
||||
*/
|
||||
public function __construct(\RecursiveIterator $iterator, $root, $ignore_folders, $ignore_files)
|
||||
public function __construct(RecursiveIterator $iterator, $root, $ignore_folders, $ignore_files)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
|
||||
@@ -42,7 +51,7 @@ class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/** @var \SplFileInfo $file */
|
||||
/** @var SplFileInfo $file */
|
||||
$file = $this->current();
|
||||
$filename = $file->getFilename();
|
||||
$relative_filename = str_replace($this::$root . '/', '', $file->getPathname());
|
||||
@@ -61,7 +70,7 @@ class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RecursiveDirectoryFilterIterator|\RecursiveFilterIterator
|
||||
* @return RecursiveDirectoryFilterIterator|RecursiveFilterIterator
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
|
||||
@@ -10,7 +10,14 @@
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RecursiveIterator;
|
||||
use SplFileInfo;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Class RecursiveFolderFilterIterator
|
||||
* @package Grav\Common\Filesystem
|
||||
*/
|
||||
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
/** @var array */
|
||||
@@ -19,10 +26,10 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
|
||||
/**
|
||||
* Create a RecursiveFilterIterator from a RecursiveIterator
|
||||
*
|
||||
* @param \RecursiveIterator $iterator
|
||||
* @param RecursiveIterator $iterator
|
||||
* @param array $ignore_folders
|
||||
*/
|
||||
public function __construct(\RecursiveIterator $iterator, $ignore_folders = [])
|
||||
public function __construct(RecursiveIterator $iterator, $ignore_folders = [])
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
|
||||
@@ -40,7 +47,7 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/** @var \SplFileInfo $current */
|
||||
/** @var SplFileInfo $current */
|
||||
$current = $this->current();
|
||||
|
||||
return $current->isDir() && !in_array($current->getFilename(), $this::$ignore_folders, true);
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use ZipArchive;
|
||||
use function extension_loaded;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Class ZipArchiver
|
||||
* @package Grav\Common\Filesystem
|
||||
*/
|
||||
class ZipArchiver extends Archiver
|
||||
{
|
||||
/**
|
||||
@@ -18,21 +28,22 @@ class ZipArchiver extends Archiver
|
||||
*/
|
||||
public function extract($destination, callable $status = null)
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$zip = new ZipArchive();
|
||||
$archive = $zip->open($this->archive_file);
|
||||
|
||||
if ($archive === true) {
|
||||
Folder::create($destination);
|
||||
|
||||
if (!$zip->extractTo($destination)) {
|
||||
throw new \RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
|
||||
throw new RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
|
||||
throw new RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,16 +54,16 @@ class ZipArchiver extends Archiver
|
||||
public function compress($source, callable $status = null)
|
||||
{
|
||||
if (!extension_loaded('zip')) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
|
||||
throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
|
||||
}
|
||||
|
||||
if (!file_exists($source)) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
|
||||
throw new InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
if (!$zip->open($this->archive_file, \ZipArchive::CREATE)) {
|
||||
throw new \InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
|
||||
$zip = new ZipArchive();
|
||||
if (!$zip->open($this->archive_file, ZipArchive::CREATE)) {
|
||||
throw new InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
|
||||
}
|
||||
|
||||
// Get real path for our folder
|
||||
@@ -98,12 +109,12 @@ class ZipArchiver extends Archiver
|
||||
public function addEmptyFolders($folders, callable $status = null)
|
||||
{
|
||||
if (!extension_loaded('zip')) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
|
||||
throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip = new ZipArchive();
|
||||
if (!$zip->open($this->archive_file)) {
|
||||
throw new \InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened...');
|
||||
throw new InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened...');
|
||||
}
|
||||
|
||||
$status && $status([
|
||||
|
||||
@@ -29,14 +29,22 @@ trait FlexCollectionTrait
|
||||
public function triggerEvent(string $name, $event = null)
|
||||
{
|
||||
if (null === $event) {
|
||||
$event = new Event(['collection' => $this]);
|
||||
$event = new Event([
|
||||
'type' => 'flex',
|
||||
'directory' => $this->getFlexDirectory(),
|
||||
'collection' => $this
|
||||
]);
|
||||
}
|
||||
if (strpos($name, 'onFlexCollection') !== 0 && strpos($name, 'on') === 0) {
|
||||
$name = 'onFlexCollection' . substr($name, 2);
|
||||
}
|
||||
|
||||
$container = $this->getContainer();
|
||||
$container->fireEvent($name, $event);
|
||||
if ($event instanceof Event) {
|
||||
$container->fireEvent($name, $event);
|
||||
} else {
|
||||
$container->dispatchEvent($event);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -28,15 +28,34 @@ trait FlexObjectTrait
|
||||
*/
|
||||
public function triggerEvent(string $name, $event = null)
|
||||
{
|
||||
$events = [
|
||||
'onRender' => 'onFlexObjectRender',
|
||||
'onBeforeSave' => 'onFlexObjectBeforeSave',
|
||||
'onAfterSave' => 'onFlexObjectAfterSave',
|
||||
'onBeforeDelete' => 'onFlexObjectBeforeDelete',
|
||||
'onAfterDelete' => 'onFlexObjectAfterDelete'
|
||||
];
|
||||
|
||||
if (null === $event) {
|
||||
$event = new Event(['object' => $this]);
|
||||
$event = new Event([
|
||||
'type' => 'flex',
|
||||
'directory' => $this->getFlexDirectory(),
|
||||
'object' => $this
|
||||
]);
|
||||
}
|
||||
if (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) {
|
||||
|
||||
if (isset($events['name'])) {
|
||||
$name = $events['name'];
|
||||
} elseif (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) {
|
||||
$name = 'onFlexObject' . substr($name, 2);
|
||||
}
|
||||
|
||||
$container = $this->getContainer();
|
||||
$container->fireEvent($name, $event);
|
||||
if ($event instanceof Event) {
|
||||
$container->fireEvent($name, $event);
|
||||
} else {
|
||||
$container->dispatchEvent($event);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Grav\Common\Flex\Types\Generic;
|
||||
use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Common\Flex\Traits\FlexObjectTrait;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
|
||||
/**
|
||||
* Class GenericObject
|
||||
@@ -23,4 +24,5 @@ class GenericObject extends FlexObject
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexObjectTrait;
|
||||
use FlexMediaTrait;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,22 @@ namespace Grav\Common\Flex\Types\Pages;
|
||||
|
||||
use Grav\Common\Flex\Traits\FlexCollectionTrait;
|
||||
use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Header;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Flex\Pages\FlexPageCollection;
|
||||
use Collator;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function extension_loaded;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class GravPageCollection
|
||||
@@ -67,6 +77,8 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
'dateRange' => true,
|
||||
'visible' => true,
|
||||
'nonVisible' => true,
|
||||
'pages' => true,
|
||||
'modules' => true,
|
||||
'modular' => true,
|
||||
'nonModular' => true,
|
||||
'published' => true,
|
||||
@@ -76,6 +88,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
'ofType' => true,
|
||||
'ofOneOfTheseTypes' => true,
|
||||
'ofOneOfTheseAccessLevels' => true,
|
||||
'withOrdered' => true,
|
||||
'withModules' => true,
|
||||
'withPages' => true,
|
||||
'withTranslation' => true,
|
||||
@@ -91,7 +104,6 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function getRoot()
|
||||
{
|
||||
/** @var PageIndex $index */
|
||||
$index = $this->getIndex();
|
||||
|
||||
return $index->getRoot();
|
||||
@@ -139,7 +151,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
public function addPage(PageInterface $page)
|
||||
{
|
||||
if (!$page instanceof FlexObjectInterface) {
|
||||
throw new \InvalidArgumentException('$page is not a flex page.');
|
||||
throw new InvalidArgumentException('$page is not a flex page.');
|
||||
}
|
||||
|
||||
// FIXME: support other keys.
|
||||
@@ -157,7 +169,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +180,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +226,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function append($items)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,7 +237,14 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function batch($size): array
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
$chunks = $this->chunk($size);
|
||||
|
||||
$list = [];
|
||||
foreach ($chunks as $chunk) {
|
||||
$list[] = $this->createFrom($chunk);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,13 +252,164 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @param string $sort_flags
|
||||
* @param array|null $manual
|
||||
* @param string|null $sort_flags
|
||||
* @return static
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
if (!$this->count()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($by === 'random') {
|
||||
return $this->shuffle();
|
||||
}
|
||||
|
||||
$keys = $this->buildSort($by, $dir, $manual, $sort_flags);
|
||||
|
||||
return $this->createFrom(array_replace(array_flip($keys), $this->toArray()) ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $order_by
|
||||
* @param string $order_dir
|
||||
* @param array|null $manual
|
||||
* @param int|null $sort_flags
|
||||
* @return array
|
||||
*/
|
||||
protected function buildSort($order_by = 'default', $order_dir = 'asc', $manual = null, $sort_flags = null): array
|
||||
{
|
||||
// do this header query work only once
|
||||
$header_query = null;
|
||||
$header_default = null;
|
||||
if (strpos($order_by, 'header.') === 0) {
|
||||
$query = explode('|', str_replace('header.', '', $order_by), 2);
|
||||
$header_query = array_shift($query) ?? '';
|
||||
$header_default = array_shift($query);
|
||||
}
|
||||
|
||||
$list = [];
|
||||
foreach ($this as $key => $child) {
|
||||
switch ($order_by) {
|
||||
case 'title':
|
||||
$list[$key] = $child->title();
|
||||
break;
|
||||
case 'date':
|
||||
$list[$key] = $child->date();
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'modified':
|
||||
$list[$key] = $child->modified();
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'publish_date':
|
||||
$list[$key] = $child->publishDate();
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'unpublish_date':
|
||||
$list[$key] = $child->unpublishDate();
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'slug':
|
||||
$list[$key] = $child->slug();
|
||||
break;
|
||||
case 'basename':
|
||||
$list[$key] = basename($key);
|
||||
break;
|
||||
case 'folder':
|
||||
$list[$key] = $child->folder();
|
||||
break;
|
||||
case 'manual':
|
||||
case 'default':
|
||||
default:
|
||||
if (is_string($header_query)) {
|
||||
/** @var Header $child_header */
|
||||
$child_header = $child->header();
|
||||
$header_value = $child_header->get($header_query);
|
||||
if (is_array($header_value)) {
|
||||
$list[$key] = implode(',', $header_value);
|
||||
} elseif ($header_value) {
|
||||
$list[$key] = $header_value;
|
||||
} else {
|
||||
$list[$key] = $header_default ?: $key;
|
||||
}
|
||||
$sort_flags = $sort_flags ?: SORT_REGULAR;
|
||||
break;
|
||||
}
|
||||
$list[$key] = $key;
|
||||
$sort_flags = $sort_flags ?: SORT_REGULAR;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $sort_flags) {
|
||||
$sort_flags = SORT_NATURAL | SORT_FLAG_CASE;
|
||||
}
|
||||
|
||||
// else just sort the list according to specified key
|
||||
if (extension_loaded('intl') && Grav::instance()['config']->get('system.intl_enabled')) {
|
||||
$locale = setlocale(LC_COLLATE, '0'); //`setlocale` with a '0' param returns the current locale set
|
||||
$col = Collator::create($locale);
|
||||
if ($col) {
|
||||
if (($sort_flags & SORT_NATURAL) === SORT_NATURAL) {
|
||||
$list = preg_replace_callback('~([0-9]+)\.~', static function ($number) {
|
||||
return sprintf('%032d.', $number[0]);
|
||||
}, $list);
|
||||
if (!is_array($list)) {
|
||||
throw new RuntimeException('Internal Error');
|
||||
}
|
||||
|
||||
$list_vals = array_values($list);
|
||||
if (is_numeric(array_shift($list_vals))) {
|
||||
$sort_flags = Collator::SORT_REGULAR;
|
||||
} else {
|
||||
$sort_flags = Collator::SORT_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
$col->asort($list, $sort_flags);
|
||||
} else {
|
||||
asort($list, $sort_flags);
|
||||
}
|
||||
} else {
|
||||
asort($list, $sort_flags);
|
||||
}
|
||||
|
||||
// Move manually ordered items into the beginning of the list. Order of the unlisted items does not change.
|
||||
if (is_array($manual) && !empty($manual)) {
|
||||
$i = count($manual);
|
||||
$new_list = [];
|
||||
foreach ($list as $key => $dummy) {
|
||||
$child = $this[$key];
|
||||
$order = array_search($child->slug, $manual, true);
|
||||
if ($order === false) {
|
||||
$order = $i++;
|
||||
}
|
||||
$new_list[$key] = (int)$order;
|
||||
}
|
||||
|
||||
$list = $new_list;
|
||||
|
||||
// Apply manual ordering to the list.
|
||||
asort($list, SORT_NUMERIC);
|
||||
}
|
||||
|
||||
if ($order_dir !== 'asc') {
|
||||
$list = array_reverse($list);
|
||||
}
|
||||
|
||||
return array_keys($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimicks Pages class.
|
||||
*
|
||||
* @return $this
|
||||
* @deprecated 1.7 Not needed anymore in Flex Pages (does nothing).
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,11 +480,32 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
* Creates new collection with only pages
|
||||
*
|
||||
* @return static The collection with only modular pages
|
||||
* @return static The collection with only pages
|
||||
*/
|
||||
public function modular()
|
||||
public function pages()
|
||||
{
|
||||
$entries = [];
|
||||
/**
|
||||
* @var int|string $key
|
||||
* @var PageInterface|null $object
|
||||
*/
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && !$object->isModule()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modules
|
||||
*
|
||||
* @return static The collection with only modules
|
||||
*/
|
||||
public function modules()
|
||||
{
|
||||
$entries = [];
|
||||
/**
|
||||
@@ -331,24 +522,23 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
* Alias of modules()
|
||||
*
|
||||
* @return static The collection with only non-modular pages
|
||||
* @return static
|
||||
*/
|
||||
public function modular()
|
||||
{
|
||||
return $this->modules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of pages()
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function nonModular()
|
||||
{
|
||||
$entries = [];
|
||||
/**
|
||||
* @var int|string $key
|
||||
* @var PageInterface|null $object
|
||||
*/
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && !$object->isModule()) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createFrom($entries);
|
||||
return $this->pages();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,7 +637,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
{
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && \in_array($object->template(), $types, true)) {
|
||||
if ($object && in_array($object->template(), $types, true)) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
@@ -466,19 +656,19 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
$entries = [];
|
||||
foreach ($this as $key => $object) {
|
||||
if ($object && isset($object->header()->access)) {
|
||||
if (\is_array($object->header()->access)) {
|
||||
if (is_array($object->header()->access)) {
|
||||
//Multiple values for access
|
||||
$valid = false;
|
||||
|
||||
foreach ($object->header()->access as $index => $accessLevel) {
|
||||
if (\is_array($accessLevel)) {
|
||||
if (is_array($accessLevel)) {
|
||||
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
|
||||
if (\in_array($innerAccessLevel, $accessLevels)) {
|
||||
if (in_array($innerAccessLevel, $accessLevels)) {
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (\in_array($index, $accessLevels)) {
|
||||
if (in_array($index, $accessLevels)) {
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
@@ -488,7 +678,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
}
|
||||
} else {
|
||||
//Single value for access
|
||||
if (\in_array($object->header()->access, $accessLevels)) {
|
||||
if (in_array($object->header()->access, $accessLevels)) {
|
||||
$entries[$key] = $object;
|
||||
}
|
||||
}
|
||||
@@ -498,6 +688,17 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
return $this->createFrom($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return FlexCollectionInterface|FlexPageCollection
|
||||
*/
|
||||
public function withOrdered(bool $bool = true)
|
||||
{
|
||||
$list = array_keys(array_filter($this->call('isOrdered', [$bool])));
|
||||
|
||||
return $this->select($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return FlexCollectionInterface|FlexPageCollection
|
||||
@@ -509,7 +710,6 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
return $this->select($list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return FlexCollectionInterface|FlexPageCollection
|
||||
|
||||
@@ -11,38 +11,34 @@ declare(strict_types=1);
|
||||
|
||||
namespace Grav\Common\Flex\Types\Pages;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\File\CompiledJsonFile;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Common\Flex\Traits\FlexIndexTrait;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Header;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
|
||||
use Grav\Framework\Flex\Pages\FlexPageIndex;
|
||||
use RuntimeException;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class GravPageObject
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*
|
||||
* @method PageIndex dateRange($startDate, $endDate = false, $field = null)
|
||||
* @method PageIndex visible()
|
||||
* @method PageIndex nonVisible()
|
||||
* @method PageIndex modular()
|
||||
* @method PageIndex nonModular()
|
||||
* @method PageIndex published()
|
||||
* @method PageIndex nonPublished()
|
||||
* @method PageIndex routable()
|
||||
* @method PageIndex nonRoutable()
|
||||
* @method PageIndex ofType(string $type)
|
||||
* @method PageIndex ofOneOfTheseTypes(array $types)
|
||||
* @method PageIndex ofOneOfTheseAccessLevels(array $accessLevels)
|
||||
* @method PageIndex withModules(bool $bool = true)
|
||||
* @method PageIndex withPages(bool $bool = true)
|
||||
* @method PageIndex withTranslation(bool $bool = true, string $languageCode = null, bool $fallback = null)
|
||||
*/
|
||||
class PageIndex extends FlexPageIndex
|
||||
class PageIndex extends FlexPageIndex implements PageCollectionInterface
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexIndexTrait;
|
||||
@@ -80,15 +76,19 @@ class PageIndex extends FlexPageIndex
|
||||
// Load saved index.
|
||||
$index = static::loadIndex($storage);
|
||||
|
||||
$timestamp = $index['timestamp'] ?? 0;
|
||||
if ($timestamp && $timestamp > time() - 2) {
|
||||
return $index['index'];
|
||||
}
|
||||
$version = $index['version'] ?? 0;
|
||||
$force = static::VERSION !== $version;
|
||||
|
||||
// TODO: Following check flex index to be out of sync after some saves, disabled until better solution is found.
|
||||
//$timestamp = $index['timestamp'] ?? 0;
|
||||
//if (!$force && $timestamp && $timestamp > time() - 1) {
|
||||
// return $index['index'];
|
||||
//}
|
||||
|
||||
// Load up to date index.
|
||||
$entries = parent::loadEntriesFromStorage($storage);
|
||||
|
||||
return static::updateIndexFile($storage, $index['index'], $entries, ['include_missing' => true]);
|
||||
return static::updateIndexFile($storage, $index['index'], $entries, ['include_missing' => true, 'force_update' => $force]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +135,7 @@ class PageIndex extends FlexPageIndex
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$message = sprintf('Flex Pages: root page is broken in storage: %s', $row['__ERROR']);
|
||||
|
||||
$debugger->addException(new \RuntimeException($message));
|
||||
$debugger->addException(new RuntimeException($message));
|
||||
$debugger->addMessage($message, 'error');
|
||||
|
||||
$row = ['__META' => $root];
|
||||
@@ -274,12 +274,20 @@ class PageIndex extends FlexPageIndex
|
||||
*/
|
||||
public function getLevelListing(array $options): array
|
||||
{
|
||||
// Undocumented B/C
|
||||
$order = $options['order'] ?? 'asc';
|
||||
if ($order === SORT_ASC) {
|
||||
$options['order'] = 'asc';
|
||||
} elseif ($order === SORT_DESC) {
|
||||
$options['order'] = 'desc';
|
||||
}
|
||||
|
||||
$options += [
|
||||
'field' => null,
|
||||
'route' => null,
|
||||
'leaf_route' => null,
|
||||
'sortby' => null,
|
||||
'order' => SORT_ASC,
|
||||
'order' => 'asc',
|
||||
'lang' => null,
|
||||
'filters' => [],
|
||||
];
|
||||
@@ -368,7 +376,8 @@ class PageIndex extends FlexPageIndex
|
||||
'type' => 'root',
|
||||
'modified' => $page->modified(),
|
||||
'size' => 0,
|
||||
'symlink' => false
|
||||
'symlink' => false,
|
||||
'has-children' => false
|
||||
];
|
||||
} else {
|
||||
$response[] = [
|
||||
@@ -402,21 +411,38 @@ class PageIndex extends FlexPageIndex
|
||||
$children = $page->children()->getIndex();
|
||||
$selectedChildren = $children->filterBy($filters, true);
|
||||
|
||||
/** @var Header $header */
|
||||
$header = $page->header();
|
||||
|
||||
if (!$field && $header->get('admin.children_display_order') === 'collection' && ($orderby = $header->get('content.order.by'))) {
|
||||
// Use custom sorting by page header.
|
||||
$sortby = $orderby;
|
||||
$order = $header->get('content.order.dir', $order);
|
||||
$custom = $header->get('content.order.custom');
|
||||
}
|
||||
|
||||
if ($sortby) {
|
||||
// Sort children.
|
||||
$selectedChildren = $selectedChildren->order($sortby, $order, $custom ?? null);
|
||||
}
|
||||
|
||||
/** @var PageObject $child */
|
||||
foreach ($selectedChildren as $child) {
|
||||
$selected = $child->path() === $extra;
|
||||
$includeChildren = \is_array($leaf) && !empty($leaf) && $selected;
|
||||
$includeChildren = is_array($leaf) && !empty($leaf) && $selected;
|
||||
if ($field) {
|
||||
$child_count = count($child->children());
|
||||
$payload = [
|
||||
'name' => $child->title(),
|
||||
'name' => $child->menu(),
|
||||
'value' => $child->rawRoute(),
|
||||
'item-key' => basename($child->rawRoute() ?? ''),
|
||||
'filename' => $child->folder(),
|
||||
'extension' => $child->extension(),
|
||||
'type' => 'dir',
|
||||
'modified' => $child->modified(),
|
||||
'size' => count($child->children()),
|
||||
'symlink' => false
|
||||
'size' => $child_count,
|
||||
'symlink' => false,
|
||||
'has-children' => $child_count > 0
|
||||
];
|
||||
} else {
|
||||
// TODO: all these features are independent from each other, we cannot just have one icon/color to catch all.
|
||||
@@ -452,7 +478,7 @@ class PageIndex extends FlexPageIndex
|
||||
'visible' => $child->visible(),
|
||||
'routable' => $child->routable(),
|
||||
'tags' => $tags,
|
||||
'actions' => null,
|
||||
'actions' => $this->getListingActions($child),
|
||||
];
|
||||
$extras = array_filter($extras, static function ($v) {
|
||||
return $v !== null;
|
||||
@@ -463,7 +489,7 @@ class PageIndex extends FlexPageIndex
|
||||
$payload = [
|
||||
'item-key' => basename($child->rawRoute() ?? $child->getKey()),
|
||||
'icon' => $icon,
|
||||
'title' => $child->title(),
|
||||
'title' => htmlspecialchars($child->menu()),
|
||||
'route' => [
|
||||
'display' => $child->getRoute()->toString(false) ?: '/',
|
||||
'raw' => $child->rawRoute(),
|
||||
@@ -490,11 +516,6 @@ class PageIndex extends FlexPageIndex
|
||||
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_NOT_FOUND';
|
||||
}
|
||||
|
||||
// Sorting
|
||||
if ($sortby) {
|
||||
$response = Utils::sortArrayByKey($response, $sortby, $order);
|
||||
}
|
||||
|
||||
if ($field) {
|
||||
$temp_array = [];
|
||||
foreach ($response as $index => $item) {
|
||||
@@ -508,9 +529,31 @@ class PageIndex extends FlexPageIndex
|
||||
return [$status, $msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED', $response, $path];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PageObject $object
|
||||
* @return array
|
||||
*/
|
||||
protected function getListingActions(PageObject $object): array
|
||||
{
|
||||
$actions = [];
|
||||
if ($object->isAuthorized('read')) {
|
||||
$actions[] = 'preview';
|
||||
$actions[] = 'edit';
|
||||
}
|
||||
if ($object->isAuthorized('update')) {
|
||||
$actions[] = 'copy';
|
||||
$actions[] = 'move';
|
||||
}
|
||||
if ($object->isAuthorized('delete')) {
|
||||
$actions[] = 'delete';
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FlexStorageInterface $storage
|
||||
* @return CompiledJsonFile|\Grav\Common\File\CompiledYamlFile|null
|
||||
* @return CompiledJsonFile|CompiledYamlFile|null
|
||||
*/
|
||||
protected static function getIndexFile(FlexStorageInterface $storage)
|
||||
{
|
||||
@@ -542,4 +585,408 @@ class PageIndex extends FlexPageIndex
|
||||
|
||||
return date($dateFormat, $timestamp) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
* @param PageInterface $page
|
||||
*
|
||||
* @return PageCollection
|
||||
*/
|
||||
public function addPage(PageInterface $page)
|
||||
{
|
||||
return $this->getCollection()->addPage($page);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a copy of this collection
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
return $this->getCollection()->merge($collection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
return $this->getCollection()->intersect($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param int $size
|
||||
* @return PageCollectionInterface[]
|
||||
*/
|
||||
public function batch($size)
|
||||
{
|
||||
return $this->getCollection()->batch($size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
* @param PageInterface|string|null $key
|
||||
*
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function remove($key = null)
|
||||
{
|
||||
return $this->getCollection()->remove($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder collection.
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @param string $sort_flags
|
||||
*
|
||||
* @return PageCollectionInterface
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('order', [$by, $dir, $manual, $sort_flags]);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the first in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is first.
|
||||
*/
|
||||
public function isFirst($path): bool
|
||||
{
|
||||
/** @var bool $result */
|
||||
$result = $this->__call('isFirst', [$path]);
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the last in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is last.
|
||||
*/
|
||||
public function isLast($path): bool
|
||||
{
|
||||
/** @var bool $result */
|
||||
$result = $this->__call('isLast', [$path]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface|null The previous item.
|
||||
*/
|
||||
public function prevSibling($path)
|
||||
{
|
||||
/** @var PageInterface|null $result */
|
||||
$result = $this->__call('prevSibling', [$path]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface|null The next item.
|
||||
*/
|
||||
public function nextSibling($path)
|
||||
{
|
||||
/** @var PageInterface|null $result */
|
||||
$result = $this->__call('nextSibling', [$path]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $direction either -1 or +1
|
||||
*
|
||||
* @return PageInterface|PageCollectionInterface|false The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1)
|
||||
{
|
||||
/** @var PageInterface|PageCollectionInterface|false $result */
|
||||
$result = $this->__call('adjacentSibling', [$path, $direction]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @param string $path the path the item
|
||||
*
|
||||
* @return int|null The index of the current page, null if not found.
|
||||
*/
|
||||
public function currentPosition($path): ?int
|
||||
{
|
||||
/** @var int|null $result */
|
||||
$result = $this->__call('currentPosition', [$path]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges of either the page date field (default) or
|
||||
* an arbitrary datetime page field where end date is optional
|
||||
* Dates can be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param string $startDate
|
||||
* @param bool $endDate
|
||||
* @param string|null $field
|
||||
*
|
||||
* @return PageCollectionInterface
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false, $field = null)
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('dateRange', [$startDate, $endDate, $field]);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimicks Pages class.
|
||||
*
|
||||
* @return $this
|
||||
* @deprecated 1.7 Not needed anymore in Flex Pages (does nothing).
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only visible pages
|
||||
*/
|
||||
public function visible()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('visible', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('nonVisible', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-modular pages
|
||||
*/
|
||||
public function pages()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('pages', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only modular pages
|
||||
*/
|
||||
public function modules()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('modules', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only modular pages
|
||||
*/
|
||||
public function modular()
|
||||
{
|
||||
return $this->modules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-modular pages
|
||||
*/
|
||||
public function nonModular()
|
||||
{
|
||||
return $this->pages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only published pages
|
||||
*/
|
||||
public function published()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('published', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('nonPublished', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only routable pages
|
||||
*/
|
||||
public function routable()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('routable', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable()
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('nonRoutable', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of the specified type
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofType($type)
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('ofType', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified types
|
||||
*
|
||||
* @param string[] $types
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseTypes($types)
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('ofOneOfTheseTypes', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified access levels
|
||||
*
|
||||
* @param array $accessLevels
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseAccessLevels($accessLevels)
|
||||
{
|
||||
/** @var PageCollectionInterface $collection */
|
||||
$collection = $this->__call('ofOneOfTheseAccessLevels', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts collection into an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->getCollection()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function toExtendedArray()
|
||||
{
|
||||
return $this->getCollection()->toExtendedArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,21 +20,33 @@ use Grav\Common\Flex\Types\Pages\Traits\PageLegacyTrait;
|
||||
use Grav\Common\Flex\Types\Pages\Traits\PageRoutableTrait;
|
||||
use Grav\Common\Flex\Types\Pages\Traits\PageTranslateTrait;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Flex\Pages\FlexPageObject;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Framework\Route\RouteFactory;
|
||||
use Grav\Plugin\Admin\Admin;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
use function array_key_exists;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function func_get_args;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class GravPageObject
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string $route
|
||||
* @property string $folder
|
||||
* @property int|false $order
|
||||
@@ -71,6 +83,9 @@ class PageObject extends FlexPageObject
|
||||
] + parent::getCachedMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(): void
|
||||
{
|
||||
if (!$this->_initialized) {
|
||||
@@ -86,7 +101,17 @@ class PageObject extends FlexPageObject
|
||||
public function getRoute($query = []): Route
|
||||
{
|
||||
$route = RouteFactory::createFromString($this->route());
|
||||
if (\is_array($query)) {
|
||||
if ($lang = $route->getLanguage()) {
|
||||
$grav = Grav::instance();
|
||||
if (!$grav['config']->get('system.languages.include_default_lang')) {
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
if ($lang === $language->getDefault()) {
|
||||
$route = $route->withLanguage('');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($query)) {
|
||||
foreach ($query as $key => $value) {
|
||||
$route = $route->withQueryParam($key, $value);
|
||||
}
|
||||
@@ -102,7 +127,7 @@ class PageObject extends FlexPageObject
|
||||
*/
|
||||
public function getFormValue(string $name, $default = null, string $separator = null)
|
||||
{
|
||||
$test = new \stdClass();
|
||||
$test = new stdClass();
|
||||
|
||||
$value = $this->pageContentValue($name, $test);
|
||||
if ($value !== $test) {
|
||||
@@ -147,77 +172,209 @@ class PageObject extends FlexPageObject
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|bool $reorder
|
||||
* @return FlexObject|\Grav\Framework\Flex\Interfaces\FlexObjectInterface
|
||||
* @param array $variables
|
||||
* @return array
|
||||
*/
|
||||
public function save($reorder = true)
|
||||
protected function onBeforeSave(array $variables)
|
||||
{
|
||||
// Reorder siblings.
|
||||
if ($reorder === true && !$this->root()) {
|
||||
$reorder = $this->_reorder ?: false;
|
||||
$reorder = $variables[0] ?? true;
|
||||
|
||||
$meta = $this->getMetaData();
|
||||
if (($meta['copy'] ?? false) === true) {
|
||||
$this->folder = $this->getKey();
|
||||
}
|
||||
|
||||
// Figure out storage path to the new route.
|
||||
$parentKey = $this->getProperty('parent_key');
|
||||
if ($parentKey !== '') {
|
||||
$parentRoute = $this->getProperty('route');
|
||||
|
||||
// Root page cannot be moved.
|
||||
if ($this->root()) {
|
||||
throw new RuntimeException(sprintf('Root page cannot be moved to %s', $parentRoute));
|
||||
}
|
||||
|
||||
// Make sure page isn't being moved under itself.
|
||||
$key = $this->getKey();
|
||||
if ($key === $parentKey || strpos($parentKey, $key . '/') === 0) {
|
||||
throw new RuntimeException(sprintf('Page /%s cannot be moved to %s', $key, $parentRoute));
|
||||
}
|
||||
|
||||
/** @var PageObject|null $parent */
|
||||
$parent = $parentKey !== false ? $this->getFlexDirectory()->getObject($parentKey, 'storage_key') : null;
|
||||
if (!$parent) {
|
||||
// Page cannot be moved to non-existing location.
|
||||
throw new RuntimeException(sprintf('Page /%s cannot be moved to non-existing path %s', $key, $parentRoute));
|
||||
}
|
||||
|
||||
// TODO: make sure that the page doesn't exist yet if moved/copied.
|
||||
}
|
||||
|
||||
if ($reorder === true && !$this->root()) {
|
||||
$reorder = $this->_reorder;
|
||||
}
|
||||
|
||||
// Force automatic reorder if item is supposed to be added to the last.
|
||||
if (!is_array($reorder) && (int)$this->order() >= 999999) {
|
||||
$reorder = [];
|
||||
}
|
||||
|
||||
// Reorder siblings.
|
||||
$siblings = is_array($reorder) ? $this->reorderSiblings($reorder) : [];
|
||||
|
||||
/** @var static $instance */
|
||||
$instance = parent::save();
|
||||
$data = $this->prepareStorage();
|
||||
unset($data['header']);
|
||||
|
||||
foreach ($siblings as $sibling) {
|
||||
$data = $sibling->prepareStorage();
|
||||
unset($data['header']);
|
||||
}
|
||||
|
||||
return ['reorder' => $reorder, 'siblings' => $siblings];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $variables
|
||||
* @return array
|
||||
*/
|
||||
protected function onSave(array $variables): array
|
||||
{
|
||||
/** @var PageCollection $siblings */
|
||||
$siblings = $variables['siblings'];
|
||||
foreach ($siblings as $sibling) {
|
||||
$sibling->save(false);
|
||||
}
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $variables
|
||||
*/
|
||||
protected function onAfterSave(array $variables): void
|
||||
{
|
||||
$this->getFlexDirectory()->reloadIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|bool $reorder
|
||||
* @return FlexObject|FlexObjectInterface
|
||||
*/
|
||||
public function save($reorder = true)
|
||||
{
|
||||
$variables = $this->onBeforeSave(func_get_args());
|
||||
|
||||
// Backwards compatibility with older plugins.
|
||||
$fireEvents = $reorder && $this->isAdminSite() && $this->getFlexDirectory()->getConfig('object.compat.events', true);
|
||||
$grav = $this->getContainer();
|
||||
if ($fireEvents) {
|
||||
$self = $this;
|
||||
$grav->fireEvent('onAdminSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => &$self]));
|
||||
if ($self !== $this) {
|
||||
throw new RuntimeException('Switching Flex Page object during onAdminSave event is not supported! Please update plugin.');
|
||||
}
|
||||
}
|
||||
|
||||
/** @var static $instance */
|
||||
$instance = parent::save();
|
||||
$variables = $this->onSave($variables);
|
||||
|
||||
$this->onAfterSave($variables);
|
||||
|
||||
// Backwards compatibility with older plugins.
|
||||
if ($fireEvents) {
|
||||
$grav->fireEvent('onAdminAfterSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => $this]));
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare move page to new location. Moves also everything that's under the current page.
|
||||
*
|
||||
* You need to call $this->save() in order to perform the move.
|
||||
*
|
||||
* @param PageInterface $parent New parent page.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function move(PageInterface $parent)
|
||||
{
|
||||
if (!$parent instanceof FlexObjectInterface) {
|
||||
throw new RuntimeException('Failed: Parent is not Flex Object');
|
||||
}
|
||||
|
||||
$this->_reorder = [];
|
||||
$this->setProperty('parent_key', $parent->getStorageKey());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ordering
|
||||
* @return PageCollection
|
||||
*/
|
||||
protected function reorderSiblings(array $ordering)
|
||||
{
|
||||
$filesystem = Filesystem::getInstance(false);
|
||||
|
||||
$storageKey = $this->getStorageKey();
|
||||
$oldParentKey = ltrim($filesystem->dirname("/$storageKey"), '/');
|
||||
$newParentKey = $this->getProperty('parent_key');
|
||||
|
||||
$slug = basename($this->getKey());
|
||||
$order = $oldParentKey === $newParentKey ? $this->order() : false;
|
||||
$k = $slug !== '' ? array_search($slug, $ordering, true) : false;
|
||||
if ($order === false) {
|
||||
if ($k !== false) {
|
||||
unset($ordering[$k]);
|
||||
}
|
||||
} elseif ($k === false) {
|
||||
$ordering[999999] = $slug;
|
||||
}
|
||||
$ordering = array_values($ordering);
|
||||
|
||||
$parent = $this->parent();
|
||||
if (!$parent) {
|
||||
throw new RuntimeException('Cannot reorder a page which has no parent');
|
||||
}
|
||||
|
||||
/** @var PageCollection|null $siblings */
|
||||
$siblings = $parent ? $parent->children() : null;
|
||||
$siblings = $parent->children();
|
||||
|
||||
/** @var PageCollection|null $siblings */
|
||||
$siblings = $siblings ? $siblings->withVisible()->getCollection() : null;
|
||||
if ($siblings) {
|
||||
$ordering = array_flip($ordering);
|
||||
if ($storageKey !== null) {
|
||||
$siblings = $siblings->getCollection()->withOrdered()->orderBy(['order' => 'ASC']);
|
||||
|
||||
$storageKey = $this->getMasterKey();
|
||||
$filesystem = Filesystem::getInstance(false);
|
||||
$oldParentKey = ltrim($filesystem->dirname("/{$storageKey}"), '/');
|
||||
$newParentKey = $this->getProperty('parent_key');
|
||||
$isMoved = $oldParentKey !== $newParentKey;
|
||||
$order = !$isMoved ? $this->order() : false;
|
||||
|
||||
if ($storageKey !== null) {
|
||||
if ($order !== false) {
|
||||
// Add current page back to the list if it's ordered.
|
||||
$siblings->set($storageKey, $this);
|
||||
} else {
|
||||
// Remove old copy of the current page from the siblings list.
|
||||
$siblings->remove($storageKey);
|
||||
if (isset($ordering[$slug])) {
|
||||
$siblings->set($storageKey, $this);
|
||||
}
|
||||
}
|
||||
$count = count($ordering);
|
||||
foreach ($siblings as $sibling) {
|
||||
$newOrder = $ordering[basename($sibling->getKey())] ?? null;
|
||||
$oldOrder = $sibling->order();
|
||||
$sibling->order(null !== $newOrder ? $newOrder + 1 : $oldOrder + $count);
|
||||
}
|
||||
|
||||
// Add missing siblings into the end of the list, keeping the previous ordering between them.
|
||||
foreach ($siblings as $sibling) {
|
||||
$basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
|
||||
if (!in_array($basename, $ordering, true)) {
|
||||
$ordering[] = $basename;
|
||||
}
|
||||
/** @var PageCollection $siblings */
|
||||
$siblings = $siblings->orderBy(['order' => 'ASC']);
|
||||
$siblings->removeElement($this);
|
||||
} else {
|
||||
/** @var PageCollection $siblings */
|
||||
$siblings = $this->getFlexDirectory()->createCollection([]);
|
||||
}
|
||||
|
||||
// Reorder.
|
||||
$ordering = array_flip(array_values($ordering));
|
||||
$count = count($ordering);
|
||||
foreach ($siblings as $sibling) {
|
||||
$basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
|
||||
$newOrder = $ordering[$basename] ?? null;
|
||||
$newOrder = null !== $newOrder ? $newOrder + 1 : (int)$sibling->order() + $count;
|
||||
$sibling->order($newOrder);
|
||||
}
|
||||
|
||||
$siblings = $siblings->orderBy(['order' => 'ASC']);
|
||||
$siblings->removeElement($this);
|
||||
|
||||
// If menu item was moved, just make it to be the last in order.
|
||||
if ($isMoved && $order !== false) {
|
||||
$parentKey = $this->getProperty('parent_key');
|
||||
$newParent = $this->getFlexDirectory()->getObject($parentKey, 'storage_key');
|
||||
$newSiblings = $newParent->children()->getCollection()->withOrdered();
|
||||
$order = 0;
|
||||
foreach ($newSiblings as $sibling) {
|
||||
$order = max($order, (int)$sibling->order());
|
||||
}
|
||||
$this->order($order + 1);
|
||||
}
|
||||
|
||||
return $siblings;
|
||||
@@ -259,12 +416,19 @@ class PageObject extends FlexPageObject
|
||||
$template = $this->getProperty('template') . ($name ? '.' . $name : '');
|
||||
|
||||
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
|
||||
} catch (\RuntimeException $e) {
|
||||
} catch (RuntimeException $e) {
|
||||
$template = 'default' . ($name ? '.' . $name : '');
|
||||
|
||||
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
|
||||
}
|
||||
|
||||
$isNew = $blueprint->get('initialized', false) === false;
|
||||
if ($isNew === true && $name === '') {
|
||||
// Support onBlueprintCreated event just like in Pages::blueprints($template)
|
||||
$blueprint->set('initialized', true);
|
||||
Grav::instance()->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $template]));
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
@@ -372,64 +536,38 @@ class PageObject extends FlexPageObject
|
||||
*/
|
||||
protected function filterElements(array &$elements, bool $extended = false): void
|
||||
{
|
||||
// Deal with ordering=bool and order=page1,page2,page3.
|
||||
if (array_key_exists('ordering', $elements) && array_key_exists('order', $elements)) {
|
||||
$ordering = (bool)($elements['ordering'] ?? false);
|
||||
$slug = preg_replace(static::PAGE_ORDER_PREFIX_REGEX, '', $this->getProperty('folder'));
|
||||
$list = !empty($elements['order']) ? explode(',', $elements['order']) : [];
|
||||
if ($ordering) {
|
||||
$order = array_search($slug, $list, true);
|
||||
if ($order !== false) {
|
||||
$order++;
|
||||
} else {
|
||||
$order = $this->getProperty('order') ?: 1;
|
||||
}
|
||||
} else {
|
||||
$order = false;
|
||||
}
|
||||
|
||||
$this->_reorder = $list;
|
||||
$elements['order'] = $order;
|
||||
}
|
||||
|
||||
// Change storage location if needed.
|
||||
// Change parent page if needed.
|
||||
if (array_key_exists('route', $elements) && isset($elements['folder'], $elements['name'])) {
|
||||
$elements['template'] = $elements['name'];
|
||||
$parentRoute = $elements['route'] ?? '';
|
||||
|
||||
// Figure out storage path to the new route.
|
||||
$parentKey = trim($parentRoute, '/');
|
||||
$parentKey = trim($elements['route'] ?? '', '/');
|
||||
if ($parentKey !== '') {
|
||||
// Make sure page isn't being moved under itself.
|
||||
$key = $this->getKey();
|
||||
if ($key === $parentKey || strpos($parentKey, $key . '/') === 0) {
|
||||
throw new \RuntimeException(sprintf('Page %s cannot be moved to %s', '/' . $key, $parentRoute));
|
||||
}
|
||||
|
||||
/** @var PageObject|null $parent */
|
||||
$parent = $this->getFlexDirectory()->getObject($parentKey);
|
||||
if (!$parent) {
|
||||
// Page cannot be moved to non-existing location.
|
||||
throw new \RuntimeException(sprintf('Page %s cannot be moved to non-existing path %s', '/' . $key, $parentRoute));
|
||||
}
|
||||
|
||||
// If parent changes and page is visible, move it to be the last item.
|
||||
if (!empty($elements['order']) && $parent !== $this->parent()) {
|
||||
/** @var PageCollection $children */
|
||||
$children = $parent->children();
|
||||
$siblings = $children->visible()->sort(['order' => 'ASC']);
|
||||
if ($siblings->count()) {
|
||||
$elements['order'] = ((int)$siblings->last()->order()) + 1;
|
||||
} else {
|
||||
$elements['order'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$parentKey = $parent->getStorageKey();
|
||||
$parentKey = $parent ? $parent->getStorageKey() : $parentKey;
|
||||
}
|
||||
|
||||
$elements['parent_key'] = $parentKey;
|
||||
}
|
||||
|
||||
// Deal with ordering=bool and order=page1,page2,page3.
|
||||
if ($this->root()) {
|
||||
// Root page doesn't have ordering.
|
||||
unset($elements['ordering'], $elements['order']);
|
||||
} elseif (array_key_exists('ordering', $elements) && array_key_exists('order', $elements)) {
|
||||
// Store ordering.
|
||||
$this->_reorder = !empty($elements['order']) ? explode(',', $elements['order']) : [];
|
||||
|
||||
$order = false;
|
||||
if ((bool)($elements['ordering'] ?? false)) {
|
||||
$order = 999999;
|
||||
}
|
||||
|
||||
$this->order();
|
||||
$elements['order'] = $order;
|
||||
}
|
||||
|
||||
parent::filterElements($elements, true);
|
||||
}
|
||||
|
||||
@@ -440,7 +578,7 @@ class PageObject extends FlexPageObject
|
||||
{
|
||||
$meta = $this->getMetaData();
|
||||
$oldLang = $meta['lang'] ?? '';
|
||||
$newLang = $this->getProperty('lang');
|
||||
$newLang = $this->getProperty('lang') ?? '';
|
||||
|
||||
// Always clone the page to the new language.
|
||||
if ($oldLang !== $newLang) {
|
||||
|
||||
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Grav\Common\Flex\Types\Pages\Storage;
|
||||
|
||||
use FilesystemIterator;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Flex\Types\Pages\PageIndex;
|
||||
use Grav\Common\Grav;
|
||||
@@ -19,6 +20,11 @@ use Grav\Framework\Filesystem\Filesystem;
|
||||
use Grav\Framework\Flex\Storage\FolderStorage;
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use SplFileInfo;
|
||||
use function assert;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class GravPageStorage
|
||||
@@ -51,8 +57,8 @@ class PageStorage extends FolderStorage
|
||||
{
|
||||
parent::initOptions($options);
|
||||
|
||||
$this->flags = \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
|
||||
| \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS;
|
||||
$this->flags = FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::CURRENT_AS_FILEINFO
|
||||
| FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS;
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
@@ -102,7 +108,7 @@ class PageStorage extends FolderStorage
|
||||
} else {
|
||||
$frontmatter = $file->raw();
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
} catch (RuntimeException $e) {
|
||||
$frontmatter = 'ERROR: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
@@ -119,7 +125,7 @@ class PageStorage extends FolderStorage
|
||||
$file = $this->getFile($path);
|
||||
try {
|
||||
$raw = $file->raw();
|
||||
} catch (\RuntimeException $e) {
|
||||
} catch (RuntimeException $e) {
|
||||
$raw = 'ERROR: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
@@ -139,9 +145,9 @@ class PageStorage extends FolderStorage
|
||||
if ($key !== '') {
|
||||
$key .= '/';
|
||||
}
|
||||
$order = $keys['order'] ?? 0;
|
||||
$order = $keys['order'] ?? null;
|
||||
$folder = $keys['folder'] ?? 'undefined';
|
||||
$key .= $order ? sprintf('%02d.%s', $order, $folder) : $folder;
|
||||
$key .= is_numeric($order) ? sprintf('%02d.%s', $order, $folder) : $folder;
|
||||
}
|
||||
|
||||
$params = $includeParams ? $this->buildStorageKeyParams($keys) : '';
|
||||
@@ -204,27 +210,36 @@ class PageStorage extends FolderStorage
|
||||
|
||||
/**
|
||||
* @param array $row
|
||||
* @param bool $setDefaultLang
|
||||
* @return array
|
||||
*/
|
||||
public function extractKeysFromRow(array $row): array
|
||||
public function extractKeysFromRow(array $row, bool $setDefaultLang = true): array
|
||||
{
|
||||
$meta = $row['__META'] ?? null;
|
||||
$storageKey = $row['storage_key'] ?? $meta['storage_key'] ?? '';
|
||||
$keyMeta = $storageKey !== '' ? $this->extractKeysFromStorageKey($storageKey) : null;
|
||||
$parentKey = $row['parent_key'] ?? $meta['parent_key'] ?? $keyMeta['parent_key'] ?? '';
|
||||
$order = $row['order'] ?? $meta['order'] ?? $keyMeta['order'] ?? '';
|
||||
$order = $row['order'] ?? $meta['order'] ?? $keyMeta['order'] ?? null;
|
||||
$folder = $row['folder'] ?? $meta['folder'] ?? $keyMeta['folder'] ?? '';
|
||||
$template = $row['template'] ?? $meta['template'] ?? $keyMeta['template'] ?? '';
|
||||
$lang = $row['lang'] ?? $meta['lang'] ?? $keyMeta['lang'] ?? '';
|
||||
|
||||
// Handle default language, if it should be saved without language extension.
|
||||
if ($lang && !$this->include_default_lang_file_extension && empty($meta['markdown'][$lang])) {
|
||||
if ($setDefaultLang && empty($meta['markdown'][$lang])) {
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $langauge */
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
if ($lang === $language->getDefault()) {
|
||||
$lang = '';
|
||||
$default = $language->getDefault();
|
||||
// Make sure that the default language file doesn't exist before overriding it.
|
||||
if (empty($meta['markdown'][$default])) {
|
||||
if ($this->include_default_lang_file_extension) {
|
||||
if ($lang === '') {
|
||||
$lang = $language->getDefault();
|
||||
}
|
||||
} elseif ($lang === $language->getDefault()) {
|
||||
$lang = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +247,7 @@ class PageStorage extends FolderStorage
|
||||
'key' => null,
|
||||
'params' => null,
|
||||
'parent_key' => $parentKey,
|
||||
'order' => (int)$order,
|
||||
'order' => is_numeric($order) ? (int)$order : null,
|
||||
'folder' => $folder,
|
||||
'template' => $template,
|
||||
'lang' => $lang
|
||||
@@ -271,7 +286,7 @@ class PageStorage extends FolderStorage
|
||||
'key' => $key,
|
||||
'params' => $params,
|
||||
'parent_key' => $parentKey,
|
||||
'order' => (int)$order,
|
||||
'order' => is_numeric($order) ? (int)$order : null,
|
||||
'folder' => $folder,
|
||||
'template' => $template,
|
||||
'lang' => $language
|
||||
@@ -352,7 +367,7 @@ class PageStorage extends FolderStorage
|
||||
|
||||
try {
|
||||
if ($key === '' && empty($row['root'])) {
|
||||
throw new \RuntimeException('No storage key given');
|
||||
throw new RuntimeException('No storage key given');
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
@@ -365,7 +380,7 @@ class PageStorage extends FolderStorage
|
||||
$oldKey = $row['__META']['storage_key'] ?? null;
|
||||
if (is_string($oldKey)) {
|
||||
// Initialize all old key-related variables.
|
||||
$oldKeys = $this->extractKeysFromRow(['__META' => $row['__META']]);
|
||||
$oldKeys = $this->extractKeysFromRow(['__META' => $row['__META']], false);
|
||||
$oldFolder = $this->buildFolder($oldKeys);
|
||||
$oldFilename = $this->buildFilename($oldKeys);
|
||||
|
||||
@@ -395,7 +410,7 @@ class PageStorage extends FolderStorage
|
||||
$toPath = $locator->isStream($newFilepath) ? $locator->findResource($newFilepath, true, true) : $newFilepath;
|
||||
$success = $file->rename($toPath);
|
||||
if (!$success) {
|
||||
throw new \RuntimeException("Changing page template failed: {$oldFilepath} => {$newFilepath}");
|
||||
throw new RuntimeException("Changing page template failed: {$oldFilepath} => {$newFilepath}");
|
||||
}
|
||||
$debugger->addMessage("Page template changed: {$oldFilename} => {$newFilename}", 'debug');
|
||||
} else {
|
||||
@@ -430,10 +445,10 @@ class PageStorage extends FolderStorage
|
||||
if ($locator->isStream($newFolder)) {
|
||||
$locator->clearCache();
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
} catch (RuntimeException $e) {
|
||||
$name = isset($file) ? $file->filename() : $newKey;
|
||||
|
||||
throw new \RuntimeException(sprintf('Flex saveRow(%s): %s', $name, $e->getMessage()));
|
||||
throw new RuntimeException(sprintf('Flex saveRow(%s): %s', $name, $e->getMessage()));
|
||||
}
|
||||
|
||||
$row['__META'] = $this->getObjectMeta($newKey, true);
|
||||
@@ -477,6 +492,8 @@ class PageStorage extends FolderStorage
|
||||
*/
|
||||
protected function buildIndex(): array
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
return $this->getIndexMeta();
|
||||
}
|
||||
|
||||
@@ -506,9 +523,9 @@ class PageStorage extends FolderStorage
|
||||
|
||||
if (is_string($path) && file_exists($path)) {
|
||||
$modified = filemtime($path);
|
||||
$iterator = new \FilesystemIterator($path, $this->flags);
|
||||
$iterator = new FilesystemIterator($path, $this->flags);
|
||||
|
||||
/** @var \SplFileInfo $info */
|
||||
/** @var SplFileInfo $info */
|
||||
foreach ($iterator as $k => $info) {
|
||||
// Ignore all hidden files if set.
|
||||
if ($k === '' || ($this->ignore_hidden && $k[0] === '.')) {
|
||||
@@ -517,14 +534,14 @@ class PageStorage extends FolderStorage
|
||||
|
||||
if ($info->isDir()) {
|
||||
// Ignore all folders in ignore list.
|
||||
if ($this->ignore_folders && \in_array($k, $this->ignore_folders, true)) {
|
||||
if ($this->ignore_folders && in_array($k, $this->ignore_folders, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$children[$k] = false;
|
||||
} else {
|
||||
// Ignore all files in ignore list.
|
||||
if ($this->ignore_files && \in_array($k, $this->ignore_files, true)) {
|
||||
if ($this->ignore_files && in_array($k, $this->ignore_files, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -546,8 +563,8 @@ class PageStorage extends FolderStorage
|
||||
$rawRoute = trim(preg_replace(PageIndex::PAGE_ROUTE_REGEX, '/', "/{$key}") ?? '', '/');
|
||||
$route = PageIndex::normalizeRoute($rawRoute);
|
||||
|
||||
ksort($markdown, SORT_NATURAL);
|
||||
ksort($children, SORT_NATURAL);
|
||||
ksort($markdown, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
ksort($children, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
|
||||
$file = array_key_first($markdown[''] ?? (reset($markdown) ?: []));
|
||||
|
||||
@@ -596,6 +613,7 @@ class PageStorage extends FolderStorage
|
||||
if ($current === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
$meta = $this->getObjectMeta($current);
|
||||
$storage_key = $meta['storage_key'];
|
||||
|
||||
@@ -610,7 +628,7 @@ class PageStorage extends FolderStorage
|
||||
$list[$storage_key] = $meta;
|
||||
} while ($queue);
|
||||
|
||||
ksort($list, SORT_NATURAL);
|
||||
ksort($list, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
|
||||
// Update parent timestamps.
|
||||
foreach (array_reverse($list) as $storage_key => $meta) {
|
||||
@@ -644,6 +662,6 @@ class PageStorage extends FolderStorage
|
||||
*/
|
||||
protected function getNewKey(): string
|
||||
{
|
||||
throw new \RuntimeException('Generating random key is disabled for pages');
|
||||
throw new RuntimeException('Generating random key is disabled for pages');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Interfaces\FlexIndexInterface;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Implements PageLegacyInterface.
|
||||
@@ -174,7 +177,7 @@ trait PageLegacyTrait
|
||||
* @param string|array $params
|
||||
* @param bool $pagination
|
||||
* @return PageCollectionInterface|Collection
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function collection($params = 'content', $pagination = true)
|
||||
{
|
||||
@@ -186,7 +189,7 @@ trait PageLegacyTrait
|
||||
// Look into a page header field.
|
||||
$params = (array)$this->getFormValue('header.' . $params);
|
||||
} elseif (!is_array($params)) {
|
||||
throw new \InvalidArgumentException('Argument should be either header variable name or array of parameters');
|
||||
throw new InvalidArgumentException('Argument should be either header variable name or array of parameters');
|
||||
}
|
||||
|
||||
if (!$pagination) {
|
||||
|
||||
@@ -17,6 +17,7 @@ use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Implements PageRoutableInterface.
|
||||
@@ -37,7 +38,7 @@ trait PageRoutableTrait
|
||||
}
|
||||
|
||||
if (null !== $var) {
|
||||
throw new \RuntimeException('Not Implemented');
|
||||
throw new RuntimeException('Not Implemented');
|
||||
}
|
||||
|
||||
if ($this->root()) {
|
||||
|
||||
@@ -90,8 +90,17 @@ trait PageTranslateTrait
|
||||
$list[$languageCode ?: $defaultCode] = $route ?? '';
|
||||
}
|
||||
|
||||
return array_filter($list, static function ($var) {
|
||||
$list = array_filter($list, static function ($var) {
|
||||
return null !== $var;
|
||||
});
|
||||
|
||||
// Hack to get the same result as with old pages.
|
||||
foreach ($list as &$path) {
|
||||
if ($path === '') {
|
||||
$path = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ use Grav\Common\Flex\Traits\FlexObjectTrait;
|
||||
use Grav\Common\User\Access;
|
||||
use Grav\Common\User\Interfaces\UserGroupInterface;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
use function is_bool;
|
||||
|
||||
/**
|
||||
* Flex User Group
|
||||
@@ -29,10 +31,10 @@ class UserGroupObject extends FlexObject implements UserGroupInterface
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexObjectTrait;
|
||||
use FlexMediaTrait;
|
||||
|
||||
/** @var Access|null */
|
||||
/** @var Access */
|
||||
protected $_access;
|
||||
|
||||
/** @var array|null */
|
||||
protected $access;
|
||||
|
||||
@@ -57,10 +59,8 @@ class UserGroupObject extends FlexObject implements UserGroupInterface
|
||||
{
|
||||
if ($scope === 'test') {
|
||||
$scope = null;
|
||||
} else {
|
||||
if (!$this->getProperty('enabled', true)) {
|
||||
return null;
|
||||
}
|
||||
} elseif (!$this->getProperty('enabled', true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$access = $this->getAccess();
|
||||
|
||||
@@ -13,8 +13,28 @@ namespace Grav\Common\Flex\Types\Users\Storage;
|
||||
|
||||
use Grav\Framework\Flex\Storage\FileStorage;
|
||||
|
||||
/**
|
||||
* Class UserFileStorage
|
||||
* @package Grav\Common\Flex\Types\Users\Storage
|
||||
*/
|
||||
class UserFileStorage extends FileStorage
|
||||
{
|
||||
/** @var bool */
|
||||
public $caseSensitive;
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeKey(string $key): string
|
||||
{
|
||||
if ($this->caseSensitive === true) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
return mb_strtolower($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FlexStorageInterface::getMediaPath()
|
||||
@@ -40,4 +60,15 @@ class UserFileStorage extends FileStorage
|
||||
$row['access'] = $access;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
protected function initOptions(array $options): void
|
||||
{
|
||||
parent::initOptions($options);
|
||||
|
||||
$this->caseSensitive = $options['case_sensitive'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,28 @@ namespace Grav\Common\Flex\Types\Users\Storage;
|
||||
|
||||
use Grav\Framework\Flex\Storage\FolderStorage;
|
||||
|
||||
/**
|
||||
* Class UserFolderStorage
|
||||
* @package Grav\Common\Flex\Types\Users\Storage
|
||||
*/
|
||||
class UserFolderStorage extends FolderStorage
|
||||
{
|
||||
/** @var bool */
|
||||
public $caseSensitive;
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeKey(string $key): string
|
||||
{
|
||||
if ($this->caseSensitive === true) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
return mb_strtolower($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the row for saving and returns the storage key for the record.
|
||||
*
|
||||
@@ -30,4 +50,15 @@ class UserFolderStorage extends FolderStorage
|
||||
$row['access'] = $access;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
protected function initOptions(array $options): void
|
||||
{
|
||||
parent::initOptions($options);
|
||||
|
||||
$this->caseSensitive = $options['case_sensitive'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Flex\Types\Users\Traits;
|
||||
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\StaticImageMedium;
|
||||
|
||||
/**
|
||||
* Trait UserObjectLegacyTrait
|
||||
* @package Grav\Common\Flex\Types\Users\Traits
|
||||
*/
|
||||
trait UserObjectLegacyTrait
|
||||
{
|
||||
/**
|
||||
* Merge two configurations together.
|
||||
*
|
||||
* @param array $data
|
||||
* @return $this
|
||||
* @deprecated 1.6 Use `->update($data)` instead (same but with data validation & filtering, file upload support).
|
||||
*/
|
||||
public function merge(array $data)
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED);
|
||||
|
||||
$this->setElements($this->getBlueprint()->mergeData($this->toArray(), $data));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return media object for the User's avatar.
|
||||
*
|
||||
* @return ImageMedium|StaticImageMedium|null
|
||||
* @deprecated 1.6 Use ->getAvatarImage() method instead.
|
||||
*/
|
||||
public function getAvatarMedia()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarImage() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getAvatarImage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the User's avatar URL
|
||||
*
|
||||
* @return string
|
||||
* @deprecated 1.6 Use ->getAvatarUrl() method instead.
|
||||
*/
|
||||
public function avatarUrl()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarUrl() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getAvatarUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user authorization to the action.
|
||||
* Ensures backwards compatibility
|
||||
*
|
||||
* @param string $action
|
||||
* @return bool
|
||||
* @deprecated 1.5 Use ->authorize() method instead.
|
||||
*/
|
||||
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) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Countable interface.
|
||||
*
|
||||
* @return int
|
||||
* @deprecated 1.6 Method makes no sense for user account.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
|
||||
|
||||
return \count($this->jsonSerialize());
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Framework\Flex\FlexCollection;
|
||||
use function is_string;
|
||||
|
||||
class UserCollection extends FlexCollection implements UserCollectionInterface
|
||||
{
|
||||
@@ -43,7 +44,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
|
||||
public function load($username): UserInterface
|
||||
{
|
||||
if ($username !== '') {
|
||||
$key = mb_strtolower($username);
|
||||
$key = $this->filterUsername($username);
|
||||
$user = $this->get($key);
|
||||
if ($user) {
|
||||
return $user;
|
||||
@@ -84,7 +85,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
|
||||
} elseif ($field === 'flex_key') {
|
||||
$user = $this->withKeyField('flex_key')->get($query);
|
||||
} elseif ($field === 'username') {
|
||||
$user = $this->get(mb_strtolower($query));
|
||||
$user = $this->get($this->filterUsername($query));
|
||||
} else {
|
||||
$user = parent::find($query, $field);
|
||||
}
|
||||
@@ -114,4 +115,18 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
|
||||
|
||||
return $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function filterUsername(string $key)
|
||||
{
|
||||
$storage = $this->getFlexDirectory()->getStorage();
|
||||
if (method_exists($storage, 'normalizeKey')) {
|
||||
return $storage->normalizeKey($key);
|
||||
}
|
||||
|
||||
return mb_strtolower($key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,14 @@ use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Framework\Flex\FlexIndex;
|
||||
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
|
||||
use Monolog\Logger;
|
||||
use function count;
|
||||
use function is_string;
|
||||
use function method_exists;
|
||||
|
||||
class UserIndex extends FlexIndex
|
||||
{
|
||||
public const VERSION = parent::VERSION . '.1';
|
||||
|
||||
use FlexGravTrait;
|
||||
use FlexIndexTrait;
|
||||
|
||||
@@ -33,12 +38,35 @@ class UserIndex extends FlexIndex
|
||||
public static function loadEntriesFromStorage(FlexStorageInterface $storage): array
|
||||
{
|
||||
// Load saved index.
|
||||
$index = static::loadEntriesFromIndex($storage);
|
||||
$index = static::loadIndex($storage);
|
||||
|
||||
$version = $index['version'] ?? 0;
|
||||
$force = static::VERSION !== $version;
|
||||
|
||||
// TODO: Following check flex index to be out of sync after some saves, disabled until better solution is found.
|
||||
//$timestamp = $index['timestamp'] ?? 0;
|
||||
//if (!$force && $timestamp && $timestamp > time() - 1) {
|
||||
// return $index['index'];
|
||||
//}
|
||||
|
||||
// Load up to date index.
|
||||
$entries = parent::loadEntriesFromStorage($storage);
|
||||
|
||||
return static::updateIndexFile($storage, $index, $entries);
|
||||
return static::updateIndexFile($storage, $index['index'], $entries, ['force_update' => $force]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $meta
|
||||
* @param array $data
|
||||
* @param FlexStorageInterface $storage
|
||||
* @return void
|
||||
*/
|
||||
public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage)
|
||||
{
|
||||
// Username can also be number and stored as such.
|
||||
$key = (string)($data['username'] ?? $meta['key'] ?? $meta['storage_key']);
|
||||
$meta['key'] = static::filterUsername($key, $storage);
|
||||
$meta['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +80,7 @@ class UserIndex extends FlexIndex
|
||||
public function load($username): UserInterface
|
||||
{
|
||||
if ($username !== '') {
|
||||
$key = mb_strtolower($username);
|
||||
$key = static::filterUsername($username, $this->getFlexDirectory()->getStorage());
|
||||
$user = $this->get($key);
|
||||
if ($user) {
|
||||
return $user;
|
||||
@@ -95,7 +123,7 @@ class UserIndex extends FlexIndex
|
||||
} elseif ($field === 'email') {
|
||||
$user = $this->withKeyField('email')->get($query);
|
||||
} elseif ($field === 'username') {
|
||||
$user = $this->get(mb_strtolower($query));
|
||||
$user = $this->get(static::filterUsername($query, $this->getFlexDirectory()->getStorage()));
|
||||
} else {
|
||||
$user = $this->__call('find', [$query, $field]);
|
||||
}
|
||||
@@ -109,13 +137,17 @@ class UserIndex extends FlexIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $entry
|
||||
* @param array $data
|
||||
* @param string $key
|
||||
* @param FlexStorageInterface $storage
|
||||
* @return string
|
||||
*/
|
||||
protected static function updateIndexData(array &$entry, array $data)
|
||||
protected static function filterUsername(string $key, FlexStorageInterface $storage): string
|
||||
{
|
||||
$entry['key'] = mb_strtolower($entry['key']);
|
||||
$entry['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null;
|
||||
if (method_exists($storage, 'normalizeKey')) {
|
||||
return $storage->normalizeKey($key);
|
||||
}
|
||||
|
||||
return mb_strtolower($key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +172,7 @@ class UserIndex extends FlexIndex
|
||||
*/
|
||||
protected static function onChanges(array $entries, array $added, array $updated, array $removed)
|
||||
{
|
||||
$message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', \count($entries), \count($added), \count($updated), \count($removed));
|
||||
$message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', count($entries), count($added), count($updated), count($removed));
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
|
||||
@@ -11,22 +11,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace Grav\Common\Flex\Types\Users;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Common\Flex\Traits\FlexObjectTrait;
|
||||
use Grav\Common\Flex\Types\Users\Traits\UserObjectLegacyTrait;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaUploadInterface;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Medium\MediumFactory;
|
||||
use Grav\Common\Page\Medium\StaticImageMedium;
|
||||
use Grav\Common\User\Access;
|
||||
use Grav\Common\User\Authentication;
|
||||
use Grav\Common\Flex\Types\UserGroups\UserGroupCollection;
|
||||
use Grav\Common\Flex\Types\UserGroups\UserGroupIndex;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\User\Traits\UserTrait;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
use Grav\Framework\Flex\Flex;
|
||||
@@ -35,10 +36,15 @@ use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Storage\FileStorage;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Grav\Framework\Media\Interfaces\MediaManipulationInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function assert;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_object;
|
||||
|
||||
/**
|
||||
* Flex User
|
||||
@@ -59,7 +65,7 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
* @property bool $authenticated
|
||||
* @property bool $authorized
|
||||
*/
|
||||
class UserObject extends FlexObject implements UserInterface, MediaManipulationInterface, \Countable
|
||||
class UserObject extends FlexObject implements UserInterface, \Countable
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexObjectTrait;
|
||||
@@ -68,6 +74,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
getMediaFolder as private getFlexMediaFolder;
|
||||
}
|
||||
use UserTrait;
|
||||
use UserObjectLegacyTrait;
|
||||
|
||||
/** @var array|null */
|
||||
protected $_uploads_original;
|
||||
@@ -75,10 +82,10 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
/** @var FileInterface|null */
|
||||
protected $_storage;
|
||||
|
||||
/** @var UserGroupCollection|UserGroupIndex|null */
|
||||
/** @var UserGroupIndex */
|
||||
protected $_groups;
|
||||
|
||||
/** @var Access|null */
|
||||
/** @var Access */
|
||||
protected $_access;
|
||||
|
||||
/** @var array|null */
|
||||
@@ -120,6 +127,33 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
$this->defProperty('state', 'enabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function onPrepareRegistration(): void
|
||||
{
|
||||
if (!$this->getProperty('access')) {
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$groups = $config->get('plugins.login.user_registration.groups', '');
|
||||
$access = $config->get('plugins.login.user_registration.access', ['site' => ['login' => true]]);
|
||||
|
||||
$this->setProperty('groups', $groups);
|
||||
$this->setProperty('access', $access);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get content editor will fall back if not set
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentEditor(): string
|
||||
{
|
||||
return $this->getProperty('content_editor', 'default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
@@ -127,7 +161,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $default Default value (or null).
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return mixed Value.
|
||||
*/
|
||||
public function get($name, $default = null, $separator = null)
|
||||
@@ -142,7 +176,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value New value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
*/
|
||||
public function set($name, $value, $separator = null)
|
||||
@@ -158,7 +192,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
* @example $data->undef('this.is.my.nested.variable');
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
*/
|
||||
public function undef($name, $separator = null)
|
||||
@@ -175,7 +209,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $default Default value (or null).
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
*/
|
||||
public function def($name, $default = null, $separator = null)
|
||||
@@ -247,7 +281,8 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
{
|
||||
$value = parent::getFormValue($name, null, $separator);
|
||||
|
||||
if ($name === 'avatar') {
|
||||
$settings = $this->getFieldSettings($name);
|
||||
if ($settings['media_field'] ?? false === true) {
|
||||
return $this->parseFileProperty($value);
|
||||
}
|
||||
|
||||
@@ -320,23 +355,23 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function join($name, $value, $separator = null)
|
||||
{
|
||||
$separator = $separator ?? '.';
|
||||
$old = $this->get($name, null, $separator);
|
||||
if ($old !== null) {
|
||||
if (!\is_array($old)) {
|
||||
throw new \RuntimeException('Value ' . $old);
|
||||
if (!is_array($old)) {
|
||||
throw new RuntimeException('Value ' . $old);
|
||||
}
|
||||
|
||||
if (\is_object($value)) {
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
} elseif (!\is_array($value)) {
|
||||
throw new \RuntimeException('Value ' . $value);
|
||||
} elseif (!is_array($value)) {
|
||||
throw new RuntimeException('Value ' . $value);
|
||||
}
|
||||
|
||||
$value = $this->getBlueprint()->mergeData($old, $value, $name, $separator);
|
||||
@@ -364,12 +399,12 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
*/
|
||||
public function joinDefaults($name, $value, $separator = null)
|
||||
{
|
||||
if (\is_object($value)) {
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
@@ -387,17 +422,17 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
* Get value from the configuration and join it with given data.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param array|object $value Value to be joined.
|
||||
* @param array|object $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getJoined($name, $value, $separator = null)
|
||||
{
|
||||
if (\is_object($value)) {
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
} elseif (!\is_array($value)) {
|
||||
throw new \RuntimeException('Value ' . $value);
|
||||
} elseif (!is_array($value)) {
|
||||
throw new RuntimeException('Value ' . $value);
|
||||
}
|
||||
|
||||
$old = $this->get($name, null, $separator);
|
||||
@@ -407,8 +442,8 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!\is_array($old)) {
|
||||
throw new \RuntimeException('Value ' . $old);
|
||||
if (!is_array($old)) {
|
||||
throw new RuntimeException('Value ' . $old);
|
||||
}
|
||||
|
||||
// Return joined data.
|
||||
@@ -501,6 +536,8 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
|
||||
/**
|
||||
* Save user without the username
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
@@ -521,7 +558,98 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
$this->setProperty('hashed_password', Authentication::create($password));
|
||||
}
|
||||
|
||||
return parent::save();
|
||||
// Backwards compatibility with older plugins.
|
||||
$fireEvents = $this->isAdminSite() && $this->getFlexDirectory()->getConfig('object.compat.events', true);
|
||||
$grav = $this->getContainer();
|
||||
if ($fireEvents) {
|
||||
$self = $this;
|
||||
$grav->fireEvent('onAdminSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => &$self]));
|
||||
if ($self !== $this) {
|
||||
throw new RuntimeException('Switching Flex User object during onAdminSave event is not supported! Please update plugin.');
|
||||
}
|
||||
}
|
||||
|
||||
$instance = parent::save();
|
||||
|
||||
// Backwards compatibility with older plugins.
|
||||
if ($fireEvents) {
|
||||
$grav->fireEvent('onAdminAfterSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => $this]));
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function prepareStorage(): array
|
||||
{
|
||||
$elements = parent::prepareStorage();
|
||||
|
||||
// Do not save authorization information.
|
||||
unset($elements['authenticated'], $elements['authorized']);
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MediaCollectionInterface
|
||||
*/
|
||||
public function getMedia()
|
||||
{
|
||||
/** @var Media $media */
|
||||
$media = $this->getFlexMedia();
|
||||
|
||||
// Deal with shared avatar folder.
|
||||
$path = $this->getAvatarFile();
|
||||
if ($path && !$media[$path] && is_file($path)) {
|
||||
$medium = MediumFactory::fromFile($path);
|
||||
if ($medium) {
|
||||
$media->add($path, $medium);
|
||||
$name = basename($path);
|
||||
if ($name !== $path) {
|
||||
$media->add($name, $medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMediaFolder(): ?string
|
||||
{
|
||||
$folder = $this->getFlexMediaFolder();
|
||||
|
||||
// Check for shared media
|
||||
if (!$folder && !$this->getFlexDirectory()->getMediaFolder()) {
|
||||
$this->_loadMedia = false;
|
||||
$folder = $this->getBlueprint()->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
|
||||
}
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Blueprint
|
||||
*/
|
||||
protected function doGetBlueprint(string $name = ''): Blueprint
|
||||
{
|
||||
$blueprint = $this->getFlexDirectory()->getBlueprint($name ? '.' . $name : $name);
|
||||
|
||||
// HACK: With folder storage we need to ignore the avatar destination.
|
||||
if ($this->getFlexDirectory()->getMediaFolder()) {
|
||||
$field = $blueprint->get('form/fields/avatar');
|
||||
if ($field) {
|
||||
unset($field['destination']);
|
||||
$blueprint->set('form/fields/avatar', $field);
|
||||
}
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -541,91 +669,236 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
return parent::isAuthorizedOverride($user, $action, $scope, $isMe);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getAvatarFile(): ?string
|
||||
{
|
||||
$avatars = $this->getElement('avatar');
|
||||
if (is_array($avatars) && $avatars) {
|
||||
$avatar = array_shift($avatars);
|
||||
|
||||
return $avatar['path'] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated media collection (original images).
|
||||
*
|
||||
* @return MediaCollectionInterface Representation of associated media.
|
||||
*/
|
||||
protected function getOriginalMedia()
|
||||
{
|
||||
$folder = $this->getMediaFolder();
|
||||
if ($folder) {
|
||||
$folder .= '/original';
|
||||
}
|
||||
|
||||
return (new Media($folder ?? '', $this->getMediaOrder()))->setTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
*/
|
||||
protected function setUpdatedMedia(array $files): void
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
$media = $this->getMedia();
|
||||
|
||||
$list = [];
|
||||
$list_original = [];
|
||||
foreach ($files as $field => $group) {
|
||||
if ($field === '') {
|
||||
continue;
|
||||
}
|
||||
$field = (string)$field;
|
||||
|
||||
// Load settings for the field.
|
||||
$settings = $this->getMediaFieldSettings($field);
|
||||
|
||||
foreach ($group as $filename => $file) {
|
||||
if ($file) {
|
||||
// File upload.
|
||||
$filename = $file->getClientFilename();
|
||||
|
||||
/** @var FormFlashFile $file */
|
||||
$data = $file->jsonSerialize();
|
||||
unset($data['tmp_name'], $data['path']);
|
||||
} else {
|
||||
// File delete.
|
||||
$data = null;
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
// Check file upload against media limits.
|
||||
$filename = $media->checkUploadedFile($file, $filename, $settings);
|
||||
}
|
||||
|
||||
$self = $settings['self'];
|
||||
if ($this->_loadMedia && $self) {
|
||||
$filepath = $filename;
|
||||
} else {
|
||||
$filepath = "{$settings['destination']}/{$filename}";
|
||||
|
||||
// For backwards compatibility we are always using relative path from the installation root.
|
||||
if ($locator->isStream($filepath)) {
|
||||
$filepath = $locator->findResource($filepath, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for original images.
|
||||
if (strpos($field, '/original')) {
|
||||
if ($this->_loadMedia && $self) {
|
||||
$list_original[$filename] = [$file, $settings];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[$filename] = [$file, $settings];
|
||||
|
||||
if (null !== $data) {
|
||||
$data['name'] = $filename;
|
||||
$data['path'] = $filepath;
|
||||
|
||||
$this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
|
||||
} else {
|
||||
$this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_uploads = $list;
|
||||
$this->_uploads_original = $list_original;
|
||||
}
|
||||
|
||||
protected function saveUpdatedMedia(): void
|
||||
{
|
||||
$media = $this->getMedia();
|
||||
if (!$media instanceof MediaUploadInterface) {
|
||||
throw new RuntimeException('Internal error UO101');
|
||||
}
|
||||
|
||||
// Upload/delete original sized images.
|
||||
/**
|
||||
* @var string $filename
|
||||
* @var UploadedFileInterface|array|null $file
|
||||
*/
|
||||
foreach ($this->_uploads_original ?? [] as $filename => $file) {
|
||||
$filename = 'original/' . $filename;
|
||||
if (is_array($file)) {
|
||||
[$file, $settings] = $file;
|
||||
} else {
|
||||
$settings = null;
|
||||
}
|
||||
if ($file instanceof UploadedFileInterface) {
|
||||
$media->copyUploadedFile($file, $filename, $settings);
|
||||
} else {
|
||||
$media->deleteFile($filename, $settings);
|
||||
}
|
||||
}
|
||||
|
||||
// Upload/delete altered files.
|
||||
/**
|
||||
* @var string $filename
|
||||
* @var UploadedFileInterface|array|null $file
|
||||
*/
|
||||
foreach ($this->getUpdatedMedia() as $filename => $file) {
|
||||
if (is_array($file)) {
|
||||
[$file, $settings] = $file;
|
||||
} else {
|
||||
$settings = null;
|
||||
}
|
||||
if ($file instanceof UploadedFileInterface) {
|
||||
$media->copyUploadedFile($file, $filename, $settings);
|
||||
} else {
|
||||
$media->deleteFile($filename, $settings);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setUpdatedMedia([]);
|
||||
$this->clearMediaCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|mixed $value
|
||||
* @return array|mixed
|
||||
*/
|
||||
protected function parseFileProperty($value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$originalMedia = $this->getOriginalMedia();
|
||||
$resizedMedia = $this->getMedia();
|
||||
|
||||
$list = [];
|
||||
foreach ($value as $filename => $info) {
|
||||
if (!is_array($info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Medium|null $thumbFile */
|
||||
$thumbFile = $resizedMedia[$filename];
|
||||
/** @var Medium|null $imageFile */
|
||||
$imageFile = $originalMedia[$filename] ?? $thumbFile;
|
||||
if ($thumbFile && $imageFile) {
|
||||
$list[$filename] = [
|
||||
'name' => $info['name'] ?? null,
|
||||
'type' => $info['type'] ?? null,
|
||||
'size' => $info['size'] ?? null,
|
||||
'path' => $info['path'] ?? null,
|
||||
'image_url' => $imageFile->url(),
|
||||
'thumb_url' => $thumbFile->url(),
|
||||
'cropData' => (object)($imageFile->metadata()['upload']['crop'] ?? [])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function prepareStorage(): array
|
||||
protected function doSerialize(): array
|
||||
{
|
||||
$elements = parent::prepareStorage();
|
||||
|
||||
// Do not save authorization information.
|
||||
unset($elements['authenticated'], $elements['authorized']);
|
||||
|
||||
return $elements;
|
||||
return [
|
||||
'type' => $this->getFlexType(),
|
||||
'key' => $this->getKey(),
|
||||
'elements' => $this->jsonSerialize(),
|
||||
'storage' => $this->getMetaData()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two configurations together.
|
||||
*
|
||||
* @param array $data
|
||||
* @return $this
|
||||
* @deprecated 1.6 Use `->update($data)` instead (same but with data validation & filtering, file upload support).
|
||||
* @return UserGroupIndex
|
||||
*/
|
||||
public function merge(array $data)
|
||||
protected function getUserGroups()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED);
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->setElements($this->getBlueprint()->mergeData($this->toArray(), $data));
|
||||
/** @var Flex $flex */
|
||||
$flex = $grav['flex'];
|
||||
|
||||
return $this;
|
||||
/** @var UserGroupCollection|null $groups */
|
||||
$groups = $flex->getDirectory('user-groups');
|
||||
if ($groups) {
|
||||
/** @var UserGroupIndex $index */
|
||||
$index = $groups->getIndex();
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
return $grav['user_groups'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return media object for the User's avatar.
|
||||
*
|
||||
* @return ImageMedium|StaticImageMedium|null
|
||||
* @deprecated 1.6 Use ->getAvatarImage() method instead.
|
||||
*/
|
||||
public function getAvatarMedia()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarImage() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getAvatarImage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the User's avatar URL
|
||||
*
|
||||
* @return string
|
||||
* @deprecated 1.6 Use ->getAvatarUrl() method instead.
|
||||
*/
|
||||
public function avatarUrl()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarUrl() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getAvatarUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user authorization to the action.
|
||||
* Ensures backwards compatibility
|
||||
*
|
||||
* @param string $action
|
||||
* @return bool
|
||||
* @deprecated 1.5 Use ->authorize() method instead.
|
||||
*/
|
||||
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) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Countable interface.
|
||||
*
|
||||
* @return int
|
||||
* @deprecated 1.6 Method makes no sense for user account.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
|
||||
|
||||
return \count($this->jsonSerialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserGroupIndex|UserGroupCollection
|
||||
* @return UserGroupIndex
|
||||
*/
|
||||
protected function getGroups()
|
||||
{
|
||||
@@ -680,238 +953,4 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MediaCollectionInterface
|
||||
*/
|
||||
public function getMedia()
|
||||
{
|
||||
/** @var Media $media */
|
||||
$media = $this->getFlexMedia();
|
||||
|
||||
// Deal with shared avatar folder.
|
||||
$path = $this->getAvatarFile();
|
||||
if ($path && !$media[$path] && is_file($path)) {
|
||||
$medium = MediumFactory::fromFile($path);
|
||||
if ($medium) {
|
||||
$media->add($path, $medium);
|
||||
$name = basename($path);
|
||||
if ($name !== $path) {
|
||||
$media->add($name, $medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMediaFolder(): ?string
|
||||
{
|
||||
$folder = $this->getFlexMediaFolder();
|
||||
if (!$folder) {
|
||||
// Shared media!
|
||||
$this->_loadMedia = false;
|
||||
$folder = $this->getBlueprint()->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
|
||||
}
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getAvatarFile(): ?string
|
||||
{
|
||||
$avatars = $this->getElement('avatar');
|
||||
if (\is_array($avatars) && $avatars) {
|
||||
$avatar = array_shift($avatars);
|
||||
|
||||
return $avatar['path'] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated media collection (original images).
|
||||
*
|
||||
* @return MediaCollectionInterface Representation of associated media.
|
||||
*/
|
||||
protected function getOriginalMedia()
|
||||
{
|
||||
$folder = $this->getMediaFolder();
|
||||
if ($folder) {
|
||||
$folder .= '/original';
|
||||
}
|
||||
|
||||
return (new Media($folder ?? '', $this->getMediaOrder()))->setTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
*/
|
||||
protected function setUpdatedMedia(array $files): void
|
||||
{
|
||||
// For shared media folder we need to keep path for backwards compatibility.
|
||||
$folder = $this->getMediaFolder();
|
||||
if (!$folder) {
|
||||
throw new \RuntimeException('No media folder support');
|
||||
}
|
||||
|
||||
$list = [];
|
||||
$list_original = [];
|
||||
foreach ($files as $field => $group) {
|
||||
foreach ($group as $filename => $file) {
|
||||
if ($file) {
|
||||
$filename = $file->getClientFilename();
|
||||
|
||||
/** @var FormFlashFile $file */
|
||||
$data = $file->jsonSerialize();
|
||||
unset($data['tmp_name'], $data['path']);
|
||||
} else {
|
||||
$data = null;
|
||||
}
|
||||
|
||||
$settings = $this->getBlueprint()->schema()->getProperty($field);
|
||||
|
||||
// Generate random name if required
|
||||
if ($settings['random_name'] ?? false) {
|
||||
$extension = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$data['name'] = $filename = Utils::generateRandomString(15) . '.' . $extension;
|
||||
}
|
||||
|
||||
if ($this->_loadMedia) {
|
||||
$filepath = $filename;
|
||||
} else {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$filepath = $locator->findResource($folder, false, true) . '/' . $filename;
|
||||
if ($data) {
|
||||
$data['path'] = $filepath;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_loadMedia && strpos($field, '/original')) {
|
||||
// Special handling for original images.
|
||||
$list_original[$filename] = $file;
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[$filename] = $file;
|
||||
|
||||
if ($data) {
|
||||
$this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
|
||||
} else {
|
||||
$this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_uploads = $list;
|
||||
$this->_uploads_original = $list_original;
|
||||
}
|
||||
|
||||
protected function saveUpdatedMedia(): void
|
||||
{
|
||||
// Upload/delete original sized images.
|
||||
/** @var FormFlashFile|null $file */
|
||||
foreach ($this->_uploads_original ?? [] as $name => $file) {
|
||||
$name = 'original/' . $name;
|
||||
if ($file) {
|
||||
$this->uploadMediaFile($file, $name);
|
||||
} else {
|
||||
$this->deleteMediaFile($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string $filename
|
||||
* @var UploadedFileInterface|null $file
|
||||
*/
|
||||
foreach ($this->getUpdatedMedia() as $filename => $file) {
|
||||
if ($file) {
|
||||
$this->uploadMediaFile($file, $filename);
|
||||
} else {
|
||||
$this->deleteMediaFile($filename);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setUpdatedMedia([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $value
|
||||
* @return array
|
||||
*/
|
||||
protected function parseFileProperty($value)
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$originalMedia = $this->getOriginalMedia();
|
||||
$resizedMedia = $this->getMedia();
|
||||
|
||||
$list = [];
|
||||
foreach ($value as $filename => $info) {
|
||||
if (!\is_array($info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Medium|null $thumbFile */
|
||||
$thumbFile = $resizedMedia[$filename];
|
||||
/** @var Medium|null $imageFile */
|
||||
$imageFile = $originalMedia[$filename] ?? $thumbFile;
|
||||
if ($thumbFile && $imageFile) {
|
||||
$list[$filename] = [
|
||||
'name' => $info['name'],
|
||||
'type' => $info['type'],
|
||||
'size' => $info['size'],
|
||||
'image_url' => $imageFile->url(),
|
||||
'thumb_url' => $thumbFile->url(),
|
||||
'cropData' => (object)($imageFile->metadata()['upload']['crop'] ?? [])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function doSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->getFlexType(),
|
||||
'key' => $this->getKey(),
|
||||
'elements' => $this->jsonSerialize(),
|
||||
'storage' => $this->getMetaData()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserGroupCollection|UserGroupIndex
|
||||
*/
|
||||
protected function getUserGroups()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Flex $flex */
|
||||
$flex = $grav['flex'];
|
||||
|
||||
/** @var UserGroupCollection|null $groups */
|
||||
$groups = $flex->getDirectory('user-groups');
|
||||
if ($groups) {
|
||||
/** @var UserGroupIndex $index */
|
||||
$index = $groups->getIndex();
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
return $grav['user_groups'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,12 @@ namespace Grav\Common\Form;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Framework\Form\FormFlash as FrameworkFormFlash;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class FormFlash
|
||||
* @package Grav\Common\Form
|
||||
*/
|
||||
class FormFlash extends FrameworkFormFlash
|
||||
{
|
||||
/**
|
||||
@@ -26,7 +31,7 @@ class FormFlash extends FrameworkFormFlash
|
||||
continue;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
if (\is_array($file)) {
|
||||
if (is_array($file)) {
|
||||
$file['tmp_name'] = $this->getTmpDir() . '/' . $file['tmp_name'];
|
||||
$fields[$field][$file['path'] ?? $file['name']] = $file;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
/**
|
||||
* Class AbstractCollection
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
abstract class AbstractCollection extends Iterator
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
/**
|
||||
* Class AbstractPackageCollection
|
||||
* @package Grav\Common\GPM\Common
|
||||
*/
|
||||
abstract class AbstractPackageCollection extends Iterator
|
||||
{
|
||||
/** @var string */
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
/**
|
||||
* Class CachedCollection
|
||||
* @package Grav\Common\GPM\Common
|
||||
*/
|
||||
class CachedCollection extends Iterator
|
||||
{
|
||||
/** @var array */
|
||||
|
||||
@@ -9,18 +9,28 @@
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use RuntimeException;
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
|
||||
/**
|
||||
* Class GPM
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
class GPM extends Iterator
|
||||
{
|
||||
/** @var Local\Packages Local installed Packages */
|
||||
private $installed;
|
||||
|
||||
/** @var Remote\Packages Remote available Packages */
|
||||
private $repository;
|
||||
|
||||
@@ -29,7 +39,6 @@ class GPM extends Iterator
|
||||
|
||||
/** @var array Internal cache */
|
||||
protected $cache;
|
||||
|
||||
/** @var array */
|
||||
protected $install_paths = [
|
||||
'plugins' => 'user/plugins/%name%',
|
||||
@@ -51,7 +60,7 @@ class GPM extends Iterator
|
||||
try {
|
||||
$this->repository = new Remote\Packages($refresh, $callback);
|
||||
$this->grav = new Remote\GravCore($refresh, $callback);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +223,6 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getUpdatable($list_type_update = ['plugins' => true, 'themes' => true])
|
||||
{
|
||||
|
||||
$items = ['total' => 0];
|
||||
foreach ($list_type_update as $type => $type_updatable) {
|
||||
if ($type_updatable === false) {
|
||||
@@ -225,6 +233,7 @@ class GPM extends Iterator
|
||||
$items[$type] = $to_update;
|
||||
$items['total'] += count($to_update);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
@@ -237,6 +246,11 @@ class GPM extends Iterator
|
||||
public function getUpdatablePlugins()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
if (!$this->repository) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$repository = $this->repository['plugins'];
|
||||
|
||||
// local cache to speed things up
|
||||
@@ -318,6 +332,11 @@ class GPM extends Iterator
|
||||
public function getUpdatableThemes()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
if (!$this->repository) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$repository = $this->repository['themes'];
|
||||
|
||||
// local cache to speed things up
|
||||
@@ -365,6 +384,10 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getReleaseType($package_name)
|
||||
{
|
||||
if (!$this->repository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repository = $this->repository['plugins'];
|
||||
if (isset($repository[$package_name])) {
|
||||
return $repository[$package_name]->release_type;
|
||||
@@ -412,7 +435,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getRepositoryPlugin($slug)
|
||||
{
|
||||
return @$this->repository['plugins'][$slug];
|
||||
return $this->repository['plugins'][$slug] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,7 +445,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getRepositoryPlugins()
|
||||
{
|
||||
return $this->repository['plugins'];
|
||||
return $this->repository['plugins'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,7 +456,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getRepositoryTheme($slug)
|
||||
{
|
||||
return @$this->repository['themes'][$slug];
|
||||
return $this->repository['themes'][$slug] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,7 +466,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getRepositoryThemes()
|
||||
{
|
||||
return $this->repository['themes'];
|
||||
return $this->repository['themes'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -483,14 +506,14 @@ class GPM extends Iterator
|
||||
|
||||
if (!$themes && !$plugins) {
|
||||
if (!is_writable(ROOT_DIR . '/cache/gpm')) {
|
||||
throw new \RuntimeException("The cache/gpm folder is not writable. Please check the folder permissions.");
|
||||
throw new RuntimeException("The cache/gpm folder is not writable. Please check the folder permissions.");
|
||||
}
|
||||
|
||||
if ($ignore_exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new \RuntimeException("GPM not reachable. Please check your internet connection or check the Grav site is reachable");
|
||||
throw new RuntimeException("GPM not reachable. Please check your internet connection or check the Grav site is reachable");
|
||||
}
|
||||
|
||||
if ($themes) {
|
||||
@@ -517,7 +540,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @param string $package_file
|
||||
* @param string $tmp
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public static function downloadPackage($package_file, $tmp)
|
||||
{
|
||||
@@ -525,7 +548,7 @@ class GPM extends Iterator
|
||||
$filename = basename($package['path']);
|
||||
|
||||
if (Grav::instance()['config']->get('system.gpm.official_gpm_only') && $package['host'] !== 'getgrav.org') {
|
||||
throw new \RuntimeException("Only official GPM URLs are allowed. You can modify this behavior in the System configuration.");
|
||||
throw new RuntimeException('Only official GPM URLs are allowed. You can modify this behavior in the System configuration.');
|
||||
}
|
||||
|
||||
$output = Response::get($package_file, []);
|
||||
@@ -544,7 +567,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @param string $package_file
|
||||
* @param string $tmp
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public static function copyPackage($package_file, $tmp)
|
||||
{
|
||||
@@ -564,7 +587,7 @@ class GPM extends Iterator
|
||||
* Try to guess the package type from the source files
|
||||
*
|
||||
* @param string $source
|
||||
* @return bool|string
|
||||
* @return string|false
|
||||
*/
|
||||
public static function getPackageType($source)
|
||||
{
|
||||
@@ -608,7 +631,7 @@ class GPM extends Iterator
|
||||
* Try to guess the package name from the source files
|
||||
*
|
||||
* @param string $source
|
||||
* @return bool|string
|
||||
* @return string|false
|
||||
*/
|
||||
public static function getPackageName($source)
|
||||
{
|
||||
@@ -628,7 +651,7 @@ class GPM extends Iterator
|
||||
* Find/Parse the blueprint file
|
||||
*
|
||||
* @param string $source
|
||||
* @return array|bool
|
||||
* @return array|false
|
||||
*/
|
||||
public static function getBlueprints($source)
|
||||
{
|
||||
@@ -755,8 +778,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @param string $package_slug
|
||||
* @param string $dependency_slug
|
||||
*
|
||||
* @return mixed
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getVersionOfDependencyRequiredByPackage($package_slug, $dependency_slug)
|
||||
{
|
||||
@@ -778,7 +800,7 @@ class GPM extends Iterator
|
||||
* @param string $version_with_operator
|
||||
* @param array $ignore_packages_list
|
||||
* @return bool
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function checkNoOtherPackageNeedsThisDependencyInALowerVersion(
|
||||
$slug,
|
||||
@@ -806,7 +828,7 @@ class GPM extends Iterator
|
||||
);
|
||||
if (!$compatible) {
|
||||
if (!in_array($dependent_package, $ignore_packages_list, true)) {
|
||||
throw new \RuntimeException(
|
||||
throw new RuntimeException(
|
||||
"Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
|
||||
2
|
||||
);
|
||||
@@ -823,7 +845,8 @@ class GPM extends Iterator
|
||||
* Check the passed packages list can be updated
|
||||
*
|
||||
* @param array $packages_names_list
|
||||
* @throws \Exception
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function checkPackagesCanBeInstalled($packages_names_list)
|
||||
{
|
||||
@@ -845,14 +868,14 @@ class GPM extends Iterator
|
||||
* `update` means the package is already installed and must be updated as a dependency needs a higher version.
|
||||
*
|
||||
* @param array $packages
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
* @return array
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getDependencies($packages)
|
||||
{
|
||||
$dependencies = $this->calculateMergedDependenciesOfPackages($packages);
|
||||
foreach ($dependencies as $dependency_slug => $dependencyVersionWithOperator) {
|
||||
if (\in_array($dependency_slug, $packages, true)) {
|
||||
if (in_array($dependency_slug, $packages, true)) {
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
@@ -866,7 +889,7 @@ class GPM extends Iterator
|
||||
) === 1
|
||||
) {
|
||||
//Needs a Grav update first
|
||||
throw new \RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
|
||||
throw new RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
|
||||
}
|
||||
|
||||
unset($dependencies[$dependency_slug]);
|
||||
@@ -881,7 +904,7 @@ class GPM extends Iterator
|
||||
) === 1
|
||||
) {
|
||||
//Needs a Grav update first
|
||||
throw new \RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
|
||||
throw new RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
|
||||
}
|
||||
|
||||
unset($dependencies[$dependency_slug]);
|
||||
@@ -913,7 +936,7 @@ class GPM extends Iterator
|
||||
);
|
||||
|
||||
if (!$compatible) {
|
||||
throw new \RuntimeException(
|
||||
throw new RuntimeException(
|
||||
'Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
|
||||
2
|
||||
);
|
||||
@@ -926,7 +949,7 @@ class GPM extends Iterator
|
||||
|
||||
if ($this->firstVersionIsLower($latestRelease, $dependencyVersion)) {
|
||||
//throw an exception if a required version cannot be found in the GPM yet
|
||||
throw new \RuntimeException(
|
||||
throw new RuntimeException(
|
||||
'Dependency <cyan>' . $package_yaml['name'] . '</cyan> is required in version <cyan>' . $dependencyVersion . '</cyan> which is higher than the latest release, <cyan>' . $latestRelease . '</cyan>. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
|
||||
1
|
||||
);
|
||||
@@ -955,7 +978,7 @@ class GPM extends Iterator
|
||||
);
|
||||
|
||||
if (!$compatible) {
|
||||
throw new \Exception(
|
||||
throw new RuntimeException(
|
||||
'Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the latest release available, and it cannot be installed. This package must be updated. Please get in touch with its developer.',
|
||||
2
|
||||
);
|
||||
@@ -975,6 +998,7 @@ class GPM extends Iterator
|
||||
|
||||
/**
|
||||
* @param array $dependencies_slugs
|
||||
* @return void
|
||||
*/
|
||||
public function checkNoOtherPackageNeedsTheseDependenciesInALowerVersion($dependencies_slugs)
|
||||
{
|
||||
@@ -1003,7 +1027,7 @@ class GPM extends Iterator
|
||||
* @param string $packageName The package information
|
||||
* @param array $dependencies The dependencies array
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
private function calculateMergedDependenciesOfPackage($packageName, $dependencies)
|
||||
{
|
||||
@@ -1046,7 +1070,7 @@ class GPM extends Iterator
|
||||
|
||||
$current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($current_package_version_information);
|
||||
if (!$current_package_version_number) {
|
||||
throw new \RuntimeException(
|
||||
throw new RuntimeException(
|
||||
'Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
|
||||
1
|
||||
);
|
||||
@@ -1076,7 +1100,7 @@ class GPM extends Iterator
|
||||
$current_package_version_number
|
||||
);
|
||||
if (!$compatible) {
|
||||
throw new \RuntimeException(
|
||||
throw new RuntimeException(
|
||||
'Dependency ' . $current_package_name . ' is required in two incompatible versions',
|
||||
2
|
||||
);
|
||||
@@ -1095,8 +1119,7 @@ class GPM extends Iterator
|
||||
* Calculates and merges the dependencies of the passed packages
|
||||
*
|
||||
* @param array $packages
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
* @return array
|
||||
*/
|
||||
public function calculateMergedDependenciesOfPackages($packages)
|
||||
{
|
||||
@@ -1119,7 +1142,7 @@ class GPM extends Iterator
|
||||
* $versionInformation == '' => returns null
|
||||
*
|
||||
* @param string $version
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public function calculateVersionNumberFromDependencyVersion($version)
|
||||
{
|
||||
@@ -1182,12 +1205,12 @@ class GPM extends Iterator
|
||||
$version1array = explode('.', $version1);
|
||||
$version2array = explode('.', $version2);
|
||||
|
||||
if (\count($version1array) > \count($version2array)) {
|
||||
list($version1array, $version2array) = [$version2array, $version1array];
|
||||
if (count($version1array) > count($version2array)) {
|
||||
[$version1array, $version2array] = [$version2array, $version1array];
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
while ($i < \count($version1array) - 1) {
|
||||
while ($i < count($version1array) - 1) {
|
||||
if ($version1array[$i] != $version2array[$i]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,18 @@
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use ZipArchive;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Installer
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
class Installer
|
||||
{
|
||||
/** @const No error */
|
||||
@@ -62,7 +71,7 @@ class Installer
|
||||
* @param string $zip the local path to ZIP package
|
||||
* @param string $destination The local path to the Grav Instance
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
* @param string $extracted The local path to the extacted ZIP package
|
||||
* @param string|null $extracted The local path to the extacted ZIP package
|
||||
* @param bool $keepExtracted True if you want to keep the original files
|
||||
* @return bool True if everything went fine, False otherwise.
|
||||
*/
|
||||
@@ -157,11 +166,11 @@ class Installer
|
||||
*
|
||||
* @param string $zip_file
|
||||
* @param string $destination
|
||||
* @return bool|string
|
||||
* @return string|false
|
||||
*/
|
||||
public static function unZip($zip_file, $destination)
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$zip = new ZipArchive();
|
||||
$archive = $zip->open($zip_file);
|
||||
|
||||
if ($archive === true) {
|
||||
@@ -185,6 +194,7 @@ class Installer
|
||||
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
self::$error_zip = $archive;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,7 +203,7 @@ class Installer
|
||||
*
|
||||
* @param string $installer_file_folder The folder path that contains install.php
|
||||
* @param bool $is_install True if install, false if removal
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
private static function loadInstaller($installer_file_folder, $is_install)
|
||||
{
|
||||
@@ -203,12 +213,12 @@ class Installer
|
||||
|
||||
$install_file = $installer_file_folder . DS . 'install.php';
|
||||
|
||||
if (file_exists($install_file)) {
|
||||
require_once $install_file;
|
||||
} else {
|
||||
if (!file_exists($install_file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
require_once $install_file;
|
||||
|
||||
if ($is_install) {
|
||||
$slug = '';
|
||||
if (($pos = strpos($installer_file_folder, 'grav-plugin-')) !== false) {
|
||||
@@ -281,8 +291,8 @@ class Installer
|
||||
*/
|
||||
public static function sophisticatedInstall($source_path, $install_path, $ignores = [], $keep_source = false)
|
||||
{
|
||||
foreach (new \DirectoryIterator($source_path) as $file) {
|
||||
if ($file->isLink() || $file->isDot() || \in_array($file->getFilename(), $ignores, true)) {
|
||||
foreach (new DirectoryIterator($source_path) as $file) {
|
||||
if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -373,7 +383,7 @@ class Installer
|
||||
self::$error = self::NOT_DIRECTORY;
|
||||
}
|
||||
|
||||
if (\count($exclude) && \in_array(self::$error, $exclude, true)) {
|
||||
if (count($exclude) && in_array(self::$error, $exclude, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -456,35 +466,35 @@ class Installer
|
||||
$msg = 'Unable to extract the package. ';
|
||||
if (self::$error_zip) {
|
||||
switch (self::$error_zip) {
|
||||
case \ZipArchive::ER_EXISTS:
|
||||
case ZipArchive::ER_EXISTS:
|
||||
$msg .= 'File already exists.';
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_INCONS:
|
||||
case ZipArchive::ER_INCONS:
|
||||
$msg .= 'Zip archive inconsistent.';
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_MEMORY:
|
||||
case ZipArchive::ER_MEMORY:
|
||||
$msg .= 'Memory allocation failure.';
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_NOENT:
|
||||
case ZipArchive::ER_NOENT:
|
||||
$msg .= 'No such file.';
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_NOZIP:
|
||||
case ZipArchive::ER_NOZIP:
|
||||
$msg .= 'Not a zip archive.';
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_OPEN:
|
||||
case ZipArchive::ER_OPEN:
|
||||
$msg .= "Can't open file.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_READ:
|
||||
case ZipArchive::ER_READ:
|
||||
$msg .= 'Read error.';
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_SEEK:
|
||||
case ZipArchive::ER_SEEK:
|
||||
$msg .= 'Seek error.';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Grav\Common\GPM;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Class Licenses
|
||||
@@ -35,7 +36,6 @@ class Licenses
|
||||
public static function set($slug, $license)
|
||||
{
|
||||
$licenses = self::getLicenseFile();
|
||||
/** @var array $data */
|
||||
$data = (array)$licenses->content();
|
||||
$slug = strtolower($slug);
|
||||
|
||||
@@ -43,7 +43,7 @@ class Licenses
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\is_string($license)) {
|
||||
if (!is_string($license)) {
|
||||
if (isset($data['licenses'][$slug])) {
|
||||
unset($data['licenses'][$slug]);
|
||||
} else {
|
||||
@@ -62,13 +62,12 @@ class Licenses
|
||||
/**
|
||||
* Returns the license for a Premium package
|
||||
*
|
||||
* @param string $slug
|
||||
* @param string|null $slug
|
||||
* @return array|string
|
||||
*/
|
||||
public static function get($slug = null)
|
||||
{
|
||||
$licenses = self::getLicenseFile();
|
||||
/** @var array $data */
|
||||
$data = (array)$licenses->content();
|
||||
$licenses->free();
|
||||
$slug = strtolower($slug);
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
|
||||
|
||||
/**
|
||||
* Class AbstractPackageCollection
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
abstract class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,12 @@ namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
use Parsedown;
|
||||
|
||||
/**
|
||||
* Class Package
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Package extends BasePackage
|
||||
{
|
||||
/** @var array */
|
||||
@@ -29,7 +34,7 @@ class Package extends BasePackage
|
||||
|
||||
$this->settings = $package->toArray();
|
||||
|
||||
$html_description = \Parsedown::instance()->line($this->__get('description'));
|
||||
$html_description = Parsedown::instance()->line($this->__get('description'));
|
||||
$this->data->set('slug', $package->__get('slug'));
|
||||
$this->data->set('description_html', $html_description);
|
||||
$this->data->set('description_plain', strip_tags($html_description));
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GPM\Common\CachedCollection;
|
||||
|
||||
/**
|
||||
* Class Packages
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Packages extends CachedCollection
|
||||
{
|
||||
public function __construct()
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
/**
|
||||
* Class Plugins
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/** @var string */
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
/**
|
||||
* Class Themes
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Themes extends AbstractPackageCollection
|
||||
{
|
||||
/** @var string */
|
||||
@@ -21,6 +25,9 @@ class Themes extends AbstractPackageCollection
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(Grav::instance()['themes']->all());
|
||||
/** @var \Grav\Common\Themes $themes */
|
||||
$themes = Grav::instance()['themes'];
|
||||
|
||||
parent::__construct($themes->all());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,24 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
|
||||
use Grav\Common\GPM\Response;
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class AbstractPackageCollection
|
||||
* @package Grav\Common\GPM\Remote
|
||||
*/
|
||||
class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
/** @var string The cached data previously fetched */
|
||||
protected $raw;
|
||||
/** @var string */
|
||||
protected $repository;
|
||||
/** @var FilesystemCache */
|
||||
protected $cache;
|
||||
|
||||
/** @var int The lifetime to store the entry in seconds */
|
||||
private $lifetime = 86400;
|
||||
|
||||
/** @var string */
|
||||
protected $repository;
|
||||
|
||||
/** @var FilesystemCache */
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* AbstractPackageCollection constructor.
|
||||
*
|
||||
@@ -39,7 +42,7 @@ class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
parent::__construct();
|
||||
if ($repository === null) {
|
||||
throw new \RuntimeException('A repository is required to indicate the origin of the remote collection');
|
||||
throw new RuntimeException('A repository is required to indicate the origin of the remote collection');
|
||||
}
|
||||
|
||||
$channel = Grav::instance()['config']->get('system.gpm.releases', 'stable');
|
||||
@@ -51,7 +54,7 @@ class AbstractPackageCollection extends BaseCollection
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
foreach (json_decode($this->raw, true) as $slug => $data) {
|
||||
// Temporarily fix for using multisites
|
||||
// Temporarily fix for using multi-sites
|
||||
if (isset($data['install_path'])) {
|
||||
$path = preg_replace('~^user/~i', 'user://', $data['install_path']);
|
||||
$data['install_path'] = Grav::instance()['locator']->findResource($path, false, true);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user