mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
925 Commits
1.7.0-rc.2
...
1.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbfc108fec | ||
|
|
e8612833c3 | ||
|
|
953b500fa0 | ||
|
|
1aa15b24a2 | ||
|
|
b6d720791a | ||
|
|
21c6b56a27 | ||
|
|
2c866f3c10 | ||
|
|
3d510a338e | ||
|
|
95c4438091 | ||
|
|
76e887e3fb | ||
|
|
72b1a18744 | ||
|
|
679a27a5ff | ||
|
|
55241864bc | ||
|
|
a8a960d10b | ||
|
|
ea519c66f2 | ||
|
|
51bff9f1f0 | ||
|
|
aeb9777972 | ||
|
|
3b61aedc66 | ||
|
|
a35b9b1279 | ||
|
|
0ba23ca278 | ||
|
|
2a67b5b827 | ||
|
|
0918419191 | ||
|
|
a4481bfd33 | ||
|
|
fcf24a40e2 | ||
|
|
59a70e0077 | ||
|
|
5037941897 | ||
|
|
3dfbe8e49c | ||
|
|
1cd34736ed | ||
|
|
1b6e4d4300 | ||
|
|
b598f8f972 | ||
|
|
7c145c7b1e | ||
|
|
38030c348d | ||
|
|
625aa3f1bd | ||
|
|
8e337647a2 | ||
|
|
7cfc8dc00e | ||
|
|
30195b6c9e | ||
|
|
a628cb6775 | ||
|
|
531ce433fe | ||
|
|
849097921a | ||
|
|
c8af0d8a38 | ||
|
|
9a1ab6ee0b | ||
|
|
04615b3a17 | ||
|
|
70ff5c2e94 | ||
|
|
b82d51e3cc | ||
|
|
4d26acfd66 | ||
|
|
42f976c82a | ||
|
|
6477d13bea | ||
|
|
ba1e106aaf | ||
|
|
5f9fa481cb | ||
|
|
84554ae71f | ||
|
|
5f9c434aea | ||
|
|
a0c3140e6d | ||
|
|
440fa263c2 | ||
|
|
f5b10564ca | ||
|
|
eb98597220 | ||
|
|
6992d52767 | ||
|
|
3a31d54600 | ||
|
|
3d5b54515a | ||
|
|
b9f1741ea1 | ||
|
|
0fa5273c56 | ||
|
|
916b7cfc44 | ||
|
|
317efc182a | ||
|
|
4bcfcfc4f8 | ||
|
|
02b3b9a4d7 | ||
|
|
57d0fc3774 | ||
|
|
2d1bca11df | ||
|
|
ac0de93139 | ||
|
|
9e39860868 | ||
|
|
b13a4d331c | ||
|
|
065cd5b7b7 | ||
|
|
1d34b5389e | ||
|
|
23df7cc90d | ||
|
|
c9a5d5a406 | ||
|
|
a5d2c86e59 | ||
|
|
f095cc760b | ||
|
|
c256b0675d | ||
|
|
a7af12d026 | ||
|
|
d7995e9be4 | ||
|
|
af2f4b66b9 | ||
|
|
15464aa10c | ||
|
|
7cfc02e7d9 | ||
|
|
c6b0a50e4e | ||
|
|
ac4f093590 | ||
|
|
a03746145b | ||
|
|
3a3b84af61 | ||
|
|
3ba68bf5c5 | ||
|
|
454e9c1d8e | ||
|
|
ee9fb4aeec | ||
|
|
ba2c224d5d | ||
|
|
515eab7dad | ||
|
|
18b0e01a3e | ||
|
|
57c6414d1c | ||
|
|
fdfcaaeb39 | ||
|
|
561b91894c | ||
|
|
aea01a3937 | ||
|
|
9b34178332 | ||
|
|
eced8facb8 | ||
|
|
074e13325e | ||
|
|
d7378f87c5 | ||
|
|
4091c16e97 | ||
|
|
56408b37ac | ||
|
|
fd18eeb62c | ||
|
|
cbd4ed311f | ||
|
|
8122703e3b | ||
|
|
2920b6143b | ||
|
|
8870463afc | ||
|
|
23fa2324a8 | ||
|
|
0df1082778 | ||
|
|
6647b8da3c | ||
|
|
f0b9411e19 | ||
|
|
5c1cc5cdd7 | ||
|
|
df9eb60ffa | ||
|
|
9893792768 | ||
|
|
379033aae4 | ||
|
|
193ab52a35 | ||
|
|
9eb6662db8 | ||
|
|
5e48146f59 | ||
|
|
627702a3a1 | ||
|
|
d9ee6de3d0 | ||
|
|
14df5a6d5f | ||
|
|
2738107b1e | ||
|
|
e53f26ca13 | ||
|
|
d3e80d62e5 | ||
|
|
b7aa20ed88 | ||
|
|
26da4a1aa3 | ||
|
|
4345a629f1 | ||
|
|
5bb4ca7822 | ||
|
|
79ee06f518 | ||
|
|
bc2435efe9 | ||
|
|
504a29f496 | ||
|
|
0948e9db9d | ||
|
|
c298464314 | ||
|
|
67a00a799c | ||
|
|
589c9e4445 | ||
|
|
d1925c8935 | ||
|
|
2923658bb9 | ||
|
|
c333da60d6 | ||
|
|
cf62db1329 | ||
|
|
625a39a892 | ||
|
|
ffc93a77a9 | ||
|
|
0cee2b6a97 | ||
|
|
a24ec2c433 | ||
|
|
e5727462e7 | ||
|
|
c1cb4e192f | ||
|
|
b0c12063a1 | ||
|
|
e4b1e87b9a | ||
|
|
6e136bf83f | ||
|
|
36504d7123 | ||
|
|
8ac4615117 | ||
|
|
7ed04ed3b0 | ||
|
|
c5b2440488 | ||
|
|
37ce236a43 | ||
|
|
31ab55558c | ||
|
|
c51830feec | ||
|
|
291e91891a | ||
|
|
307ede3183 | ||
|
|
d4a20c71c2 | ||
|
|
ec68068b97 | ||
|
|
d9c1445542 | ||
|
|
26f4d05e87 | ||
|
|
b6c941fc3e | ||
|
|
cedcc845d5 | ||
|
|
02a3ed2e0e | ||
|
|
a729daa3d3 | ||
|
|
ba23cceae7 | ||
|
|
d3e9083869 | ||
|
|
09d92ebab6 | ||
|
|
2777bedb51 | ||
|
|
f181e1d237 | ||
|
|
8f7dc43e1f | ||
|
|
249c2f1e69 | ||
|
|
6a4686d17b | ||
|
|
3529943bb0 | ||
|
|
1c63f4bf46 | ||
|
|
380177b777 | ||
|
|
bc22c8d2b1 | ||
|
|
c5cdeaef07 | ||
|
|
a1fb4a2487 | ||
|
|
d34593928d | ||
|
|
5184f8e6a3 | ||
|
|
ea358c239f | ||
|
|
2cf90cc60b | ||
|
|
db24d3e53e | ||
|
|
6a0caebe2e | ||
|
|
ac85946e0f | ||
|
|
fc55a8e49b | ||
|
|
b093aa9fa6 | ||
|
|
e60e9fa3dd | ||
|
|
29bcbf042f | ||
|
|
19719ecac1 | ||
|
|
4d288ad2ee | ||
|
|
eccaf4acff | ||
|
|
c2a2ef212e | ||
|
|
47ab76c7e3 | ||
|
|
dd30b96210 | ||
|
|
3e053784c2 | ||
|
|
7b97cd0cf1 | ||
|
|
809767883b | ||
|
|
d8e38665f1 | ||
|
|
f25ea8f056 | ||
|
|
7c0ba80530 | ||
|
|
6eb083bebe | ||
|
|
3a822f7bc4 | ||
|
|
9a2268a54e | ||
|
|
611171371b | ||
|
|
e1d4fe36f4 | ||
|
|
079d8c19a4 | ||
|
|
418c6dd7f8 | ||
|
|
08304d5064 | ||
|
|
5cdeb28e6b | ||
|
|
c5efd17a9c | ||
|
|
6276ead820 | ||
|
|
b5cdc12478 | ||
|
|
ff511b8968 | ||
|
|
037a84c46f | ||
|
|
3d0f29a172 | ||
|
|
85c239bc18 | ||
|
|
1fb6a39d9d | ||
|
|
9aeac93c9a | ||
|
|
6188076fe6 | ||
|
|
0fc7fd3411 | ||
|
|
078c8d23d5 | ||
|
|
8c845c77c1 | ||
|
|
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 | ||
|
|
04820b8adc | ||
|
|
5be92b7a8a | ||
|
|
addecbbb22 | ||
|
|
ec8bf9357a | ||
|
|
432b4b1e68 | ||
|
|
ea5935b1d1 | ||
|
|
65db6ea26c | ||
|
|
a77b5e69e7 | ||
|
|
c9da739ea3 | ||
|
|
fa3c9095c7 | ||
|
|
9e4ee6ec91 | ||
|
|
95442ef0b5 | ||
|
|
1f92ec715a | ||
|
|
f81503dd70 | ||
|
|
0642e34a77 | ||
|
|
4e8e3e1865 | ||
|
|
48170d2fa0 | ||
|
|
8936e82e9b | ||
|
|
e0162d276d | ||
|
|
14ff7fae33 | ||
|
|
2c78b3efca | ||
|
|
5104bde6d4 | ||
|
|
59f05ec8a2 | ||
|
|
20047fbdaf | ||
|
|
81dd33c2e9 | ||
|
|
1cf390206e | ||
|
|
8169a6f3f8 | ||
|
|
2165ded8d4 | ||
|
|
ef80e28d1d | ||
|
|
234abb38d9 | ||
|
|
3a722ba44e | ||
|
|
3f56c5a727 | ||
|
|
e55b239536 | ||
|
|
c2f374f0db | ||
|
|
29c6a70611 | ||
|
|
0bbbe96ae0 | ||
|
|
40010f7ff7 | ||
|
|
49aae0c50e | ||
|
|
5ea7ec1013 | ||
|
|
75ee0670f6 | ||
|
|
6e327880a3 | ||
|
|
ceaa20d5d4 | ||
|
|
10589e7940 | ||
|
|
5e9a7a112a | ||
|
|
a672a765b4 | ||
|
|
4794097992 | ||
|
|
f012d2d13a | ||
|
|
d0a22ad416 | ||
|
|
6dee662b40 | ||
|
|
1d8b87e33f | ||
|
|
5db91538c6 | ||
|
|
7d6fb9eee6 | ||
|
|
694b14511d | ||
|
|
bbab97137f | ||
|
|
3408db0c9b | ||
|
|
1bbf8dffeb | ||
|
|
887b34dd31 | ||
|
|
0f5166d690 | ||
|
|
e507300134 | ||
|
|
e38c5cac4a | ||
|
|
463a55897c | ||
|
|
192cc4eb9b | ||
|
|
e5fe28b720 | ||
|
|
defc70e656 | ||
|
|
d589dbcbea | ||
|
|
3ed8620d7a | ||
|
|
bc5501eecb | ||
|
|
2ae6bac390 | ||
|
|
118621cb8b | ||
|
|
3118ed5f56 | ||
|
|
ac17fc8efd | ||
|
|
85c1ec67c2 | ||
|
|
959a2ec379 | ||
|
|
1372c9b1cc | ||
|
|
67dd5f256d | ||
|
|
2c3ced3fba | ||
|
|
a592f6fe0b | ||
|
|
9123cb7796 | ||
|
|
df09c01a25 | ||
|
|
b97301d82d | ||
|
|
6b887a98cd | ||
|
|
18a26b42e2 | ||
|
|
85de4ed0e3 | ||
|
|
80125ce298 | ||
|
|
27542d4fa4 | ||
|
|
a47e446b60 | ||
|
|
471fb4da6f | ||
|
|
a2a6888982 | ||
|
|
864a938f8d | ||
|
|
bdd30238bf | ||
|
|
528ce7131e | ||
|
|
53bd1641bb | ||
|
|
b10725cebc | ||
|
|
719cc5466a | ||
|
|
e373cf18e0 | ||
|
|
7ef5fa5630 | ||
|
|
d139b0388a | ||
|
|
014ab5d7ee | ||
|
|
64f3949967 | ||
|
|
7913edd34b | ||
|
|
2136dc34fe | ||
|
|
c559b42151 | ||
|
|
a33d974c74 | ||
|
|
ede7af6b9d | ||
|
|
5cd4bf5c98 | ||
|
|
045fae9b6f | ||
|
|
bbd46644e0 | ||
|
|
3e67c0a878 | ||
|
|
924f01158d | ||
|
|
8d5dd60fb4 | ||
|
|
958ea586ec | ||
|
|
5f6cc58186 | ||
|
|
426f59e41a | ||
|
|
fff9fa0ca5 | ||
|
|
57aced69cf | ||
|
|
afac6baa11 | ||
|
|
b4630aeb38 | ||
|
|
cac02663e6 | ||
|
|
e59f1e5a82 | ||
|
|
90d077d7c2 | ||
|
|
a77d6bcfa0 | ||
|
|
bee5abfbf0 | ||
|
|
664447a67b | ||
|
|
b850443090 | ||
|
|
98d0c760a9 | ||
|
|
76bb7fe4af | ||
|
|
26584b9909 | ||
|
|
6a9724dd3e | ||
|
|
c7a41ddfda | ||
|
|
2df05dd16d | ||
|
|
50c2c55554 | ||
|
|
d72b99c5b2 | ||
|
|
5adadfdb40 | ||
|
|
7574195ca2 | ||
|
|
5b5ef98495 | ||
|
|
226e5350b7 | ||
|
|
b9b83c3b16 | ||
|
|
971fb2b19a | ||
|
|
378b60783c | ||
|
|
d901558481 | ||
|
|
1515ee9193 | ||
|
|
9c34471800 | ||
|
|
6f16f6f134 | ||
|
|
7c0dcd6808 | ||
|
|
33790dbb33 | ||
|
|
ea8b7b7a3a | ||
|
|
8714aa9202 | ||
|
|
3731d61b78 | ||
|
|
927e99fcb3 | ||
|
|
ef6ec100f2 | ||
|
|
386a39e274 | ||
|
|
38d3b5cf43 | ||
|
|
5718299210 | ||
|
|
2e64d560b1 | ||
|
|
82270e0c13 | ||
|
|
ca22b56148 | ||
|
|
eca3896bfc | ||
|
|
1c2c38545a | ||
|
|
464e1fc6c7 | ||
|
|
02b8499b0c | ||
|
|
b5caa41386 | ||
|
|
a8ff0e3892 | ||
|
|
ebab884441 | ||
|
|
2c95992eb1 | ||
|
|
1666774a1e | ||
|
|
3cc0dc08db | ||
|
|
f9bcf48700 | ||
|
|
8a618fee64 | ||
|
|
a6f7637134 | ||
|
|
f04ef0b359 | ||
|
|
608457bd01 | ||
|
|
508cf1ffdb | ||
|
|
6f38933e81 | ||
|
|
8ff2ca4c5a | ||
|
|
6a0d5c69ab | ||
|
|
2747877195 | ||
|
|
584b33d41a | ||
|
|
f4330ff77d | ||
|
|
60c43184cb | ||
|
|
2fe2eb9f21 | ||
|
|
04ee52d1ad | ||
|
|
99e047171f | ||
|
|
ddc8668837 | ||
|
|
e568b992a9 | ||
|
|
23221a8f8f | ||
|
|
4eed10cd4b | ||
|
|
da02c0992a | ||
|
|
2437112f3c | ||
|
|
276b4bb0ab | ||
|
|
acf271344d | ||
|
|
7b5d6f7031 | ||
|
|
767ce29e39 | ||
|
|
daad16ef1b | ||
|
|
1718135614 | ||
|
|
a269d49392 | ||
|
|
3a8775f545 | ||
|
|
842dc0d49e | ||
|
|
3f9ed2f344 | ||
|
|
5640baa175 | ||
|
|
f102a8cb3d | ||
|
|
a65c468e81 | ||
|
|
4cc3f44caa | ||
|
|
8afe004a7f | ||
|
|
185e9b5272 |
@@ -13,5 +13,5 @@ indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# 2 space indentation
|
||||
[*.{yaml,.yml}]
|
||||
[*.{yaml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
66
.github/workflows/build.yaml
vendored
Normal file
66
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Release Builds
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!github.event.release.prerelease"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.3
|
||||
extensions: opcache, gd
|
||||
coverage: none
|
||||
env:
|
||||
COMPOSER_TOKEN: ${{ secrets.GLOBAL_TOKEN }}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get -y update -qq < /dev/null > /dev/null
|
||||
sudo apt-get -y install -qq git zip < /dev/null > /dev/null
|
||||
|
||||
- name: Retrieval of Builder Scripts
|
||||
run: |
|
||||
# Real Grav URL
|
||||
curl --silent -H "Authorization: token ${{ secrets.GLOBAL_TOKEN }}" -H "Accept: application/vnd.github.v3.raw" ${{ secrets.BUILD_SCRIPT_URL }} --output build-grav.sh
|
||||
|
||||
# Development Local URL
|
||||
# curl ${{ secrets.BUILD_SCRIPT_URL }} --output build-grav.sh
|
||||
|
||||
- name: Grav Builder
|
||||
run: |
|
||||
bash ./build-grav.sh
|
||||
|
||||
- name: Upload Grav Release Assets
|
||||
id: upload-release-asset
|
||||
uses: alexellis/upload-assets@0.2.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GLOBAL_TOKEN }}
|
||||
with:
|
||||
asset_paths: '["./grav-dist/*.zip"]'
|
||||
|
||||
slack:
|
||||
name: Slack
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- uses: technote-space/workflow-conclusion-action@v2
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: failure
|
||||
fields: repo,message,author,action
|
||||
icon_emoji: ':octocat:'
|
||||
author_name: 'Github Action Build'
|
||||
text: '🚚 Automated Build Failure'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GLOBAL_TOKEN }}
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
72
.github/workflows/tests.yaml
vendored
Normal file
72
.github/workflows/tests.yaml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: PHP Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
|
||||
jobs:
|
||||
|
||||
unit-tests:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php: [ 8.0, 7.4, 7.3]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: opcache, gd
|
||||
coverage: none
|
||||
env:
|
||||
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update composer
|
||||
run: composer update
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Run test suite
|
||||
run: vendor/bin/codecept run
|
||||
|
||||
slack:
|
||||
name: Slack
|
||||
needs: unit-tests
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- uses: technote-space/workflow-conclusion-action@v2
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: failure
|
||||
fields: repo,message,author,action
|
||||
icon_emoji: ':octocat:'
|
||||
author_name: 'Github Action Tests'
|
||||
text: '💥 Automated Test Failure'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -44,3 +44,4 @@ tests/_support/_generated/*
|
||||
tests/cache/*
|
||||
tests/error.log
|
||||
system/templates/testing/*
|
||||
/user/config/versions.yaml
|
||||
|
||||
@@ -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.
|
||||
|
||||
14
.travis.yml
14
.travis.yml
@@ -3,10 +3,9 @@ php:
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
- '7.3'
|
||||
- '7.4'
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- master
|
||||
- build_test
|
||||
notifications:
|
||||
email:
|
||||
@@ -45,10 +44,11 @@ before_install:
|
||||
- if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then
|
||||
composer install --dev --prefer-dist;
|
||||
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;
|
||||
- |
|
||||
if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "7.1" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
export TRAVIS_TAG=$(curl -H "Authorization: token ${GH_TOKEN}" --fail -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.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 +56,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;
|
||||
|
||||
590
CHANGELOG.md
590
CHANGELOG.md
@@ -1,3 +1,444 @@
|
||||
# v1.7.4
|
||||
## 02/01/2021
|
||||
|
||||
1. [](#new)
|
||||
* Added `FlexForm::setSubmitMethod()` to customize form submit action
|
||||
1. [](#improved)
|
||||
* Improved GPM error handling
|
||||
1. [](#bugfix)
|
||||
* Fixed `bin/gpm uninstall` script not working because of bad typehint [#3172](https://github.com/getgrav/grav/issues/3172)
|
||||
* Fixed `login: visibility_requires_access` not working in pages [#3176](https://github.com/getgrav/grav/issues/3176)
|
||||
* Fixed cannot change image format [#3173](https://github.com/getgrav/grav/issues/3173)
|
||||
* Fixed saving page in expert mode [#3174](https://github.com/getgrav/grav/issues/3174)
|
||||
* Fixed exception in `$flexPage->frontmatter()` method when setting value
|
||||
* Fixed `onBlueprintCreated` event being called multiple times in `Flex Pages` [grav-plugin-flex-objects#97](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/97)
|
||||
* Fixed wrong ordering in page collections if `intl` extension has been enabled [#3167](https://github.com/getgrav/grav/issues/3167)
|
||||
* Fixed page redirect to the first visible child page (needs to be routable and published, too)
|
||||
* Fixed untranslated module pages showing up in the menu
|
||||
* Fixed page save failing because of uploaded images [#3191](https://github.com/getgrav/grav/issues/3191)
|
||||
* Fixed incorrect config lookup for loading in `ImageLoadingTrait` [#3192](https://github.com/getgrav/grav/issues/3192)
|
||||
|
||||
# v1.7.3
|
||||
## 01/21/2021
|
||||
|
||||
1. [](#improved)
|
||||
* IMPORTANT - Please [checkout the process](https://getgrav.org/blog/grav-170-cli-self-upgrade-bug) to `self-upgrade` from CLI if you are on **Grav 1.7.0 or 1.7.1**
|
||||
* Added support for symlinking individual plugins and themes by using `bin/grav install -p myplugin` or `-t mytheme`
|
||||
* Added support for symlinking plugins and themes with `hebe.json` file to support custom folder structures
|
||||
* Added support for running post-install scripts in `bin/gpm selfupgrade` if Grav was updated manually
|
||||
1. [](#bugfix)
|
||||
* Fixed default GPM Channel back to 'stable' - this was inadvertently left as 'testing' [#3163](https://github.com/getgrav/grav/issues/3163)
|
||||
* Fixed broken stream initialization if `environment://` paths aren't streams
|
||||
* Fixed Clockwork debugger in sub-folder multi-site setups
|
||||
* Fixed `Unsupported option "curl" passed to "Symfony\Component\HttpClient\CurlHttpClient"` in `bin/gpm selfupgrade` [#3165](https://github.com/getgrav/grav/issues/3165)
|
||||
|
||||
# v1.7.2
|
||||
## 01/21/2021
|
||||
|
||||
1. [](#improved)
|
||||
* This release was pulled due to a bug in the installer, 1.7.3 replaces it.
|
||||
|
||||
# v1.7.1
|
||||
## 01/20/2021
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed fatal error when `site.taxonomies` contains a bad value
|
||||
* Sanitize valid Page extensions from `Page::template_format()`
|
||||
* Fixed `bin/gpm index` erroring out [#3158](https://github.com/getgrav/grav/issues/3158)
|
||||
* Fixed `bin/gpm selfupgrade` failing to report failed Grav update [#3116](https://github.com/getgrav/grav/issues/3116)
|
||||
* Fixed `bin/gpm selfupgrade` error on `Call to undefined method` [#3160](https://github.com/getgrav/grav/issues/3160)
|
||||
* Flex Pages: Fixed fatal error when trying to move a page to Root (/) [#3161](https://github.com/getgrav/grav/issues/3161)
|
||||
* Fixed twig parsing errors in pages where twig is parsed after markdown [#3162](https://github.com/getgrav/grav/issues/3162)
|
||||
* Fixed `lighttpd.conf` access-deny rule [#1876](https://github.com/getgrav/grav/issues/1876)
|
||||
* Fixed page metadata being double-escaped [#3121](https://github.com/getgrav/grav/issues/3121)
|
||||
|
||||
# v1.7.0
|
||||
## 01/19/2021
|
||||
|
||||
1. [](#new)
|
||||
* Requires **PHP 7.3.6**
|
||||
* Read about this release in the [Grav 1.7 Released](https://getgrav.org/blog/grav-1.7-released) blog post
|
||||
* Read the full list of all changes in the [Changelog on GitHub](https://github.com/getgrav/grav/blob/1.7.0/CHANGELOG.md)
|
||||
* Please read [Grav 1.7 Upgrade Guide](https://learn.getgrav.org/17/advanced/grav-development/grav-17-upgrade-guide) before upgrading!
|
||||
* Added support for overriding configuration by using environment variables
|
||||
* Use PHP 7.4 serialization (the old `Serializable` methods are now final and cannot be overridden)
|
||||
* Enabled `ETag` setting by default for 304 responses
|
||||
* Added `FlexCollection::getDistinctValues()` to get all the assigned values from the field
|
||||
* `Flex Pages` method `$page->header()` returns `\Grav\Common\Page\Header` object, old `Page` class still returns `stdClass`
|
||||
1. [](#improved)
|
||||
* Make it possible to use an absolute path when loading a blueprint
|
||||
* Make serialize methods final in `ContentBlock`, `AbstractFile`, `FormTrait`, `ObjectCollectionTrait` and `ObjectTrait`
|
||||
* Added support for relative paths in `PageObject::getLevelListing()` [#3110](https://github.com/getgrav/grav/issues/3110)
|
||||
* Better `--env` and `--lang` support for `bin/grav`, `bin/gpm` and `bin/plugin` console commands
|
||||
* **BC BREAK** Shorthand for `--env`: `-e` will not work anymore as it conflicts with some plugins
|
||||
* Added support for locking the `start` and `limit` in a Page Collection
|
||||
1. [](#bugfix)
|
||||
* Fixed port issue with `system.custom_base_url`
|
||||
* Hide errors with `exif_read_data` in `ImageFile`
|
||||
* Fixed unserialize in `MarkdownFormatter` and `Framework\File` classes
|
||||
* Fixed pages with session messages should never be cached [#3108](https://github.com/getgrav/grav/issues/3108)
|
||||
* Fixed `Filesystem::normalize()` with dot-dot paths
|
||||
* Fixed Flex sorting issues [grav-plugin-flex-objects#92](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/92)
|
||||
* Fixed Clockwork missing dumped arrays and objects
|
||||
* Fixed fatal error in PHP 8 when trying to access root page
|
||||
* Fixed Array->String conversion error when `languages:translations: false` [admin#1896](https://github.com/getgrav/grav-plugin-admin/issues/1896)
|
||||
* Fixed `Inflector` methods when translation is missing `GRAV.INFLECTOR_*` translations
|
||||
* Fixed exception when changing parent of new page [grav-plugin-admin#2018](https://github.com/getgrav/grav-plugin-admin/issues/2018)
|
||||
* Fixed ordering issue with moving pages [grav-plugin-admin#2015](https://github.com/getgrav/grav-plugin-admin/issues/2015)
|
||||
* Fixed Flex Pages cache not invalidating if saving an old `Page` object [#3152](https://github.com/getgrav/grav/issues/3152)
|
||||
* Fixed multiple issues with `system.language.translations: false`
|
||||
* Fixed page collections containing dummy items for untranslated default language [#2985](https://github.com/getgrav/grav/issues/2985)
|
||||
* Fixed streams in `setup.php` being overridden by `system/streams.yaml` [#2450](https://github.com/getgrav/grav/issues/2450)
|
||||
* Fixed `ERR_TOO_MANY_REDIRECTS` with HTTPS = 'On' [#3155](https://github.com/getgrav/grav/issues/3155)
|
||||
* Fixed page collection pagination not behaving as it did in Grav 1.6
|
||||
|
||||
# v1.7.0-rc.20
|
||||
## 12/15/2020
|
||||
|
||||
1. [](#new)
|
||||
* Update phpstan to version 0.12
|
||||
* Auto-Escape enabled by default. Manually enable **Twig Compatibility** and disable **Auto-Escape** to use the old setting.
|
||||
* Updated unit tests to use codeception 4.1
|
||||
* Added support for setting `GRAV_ENVIRONMENT` by using environment variable or a constant
|
||||
* Added support for setting `GRAV_SETUP_PATH` by using environment variable (constant already worked)
|
||||
* Added support for setting `GRAV_ENVIRONMENTS_PATH` by using environment variable or a constant
|
||||
* Added support for setting `GRAV_ENVIRONMENT_PATH` by using environment variable or a constant
|
||||
1. [](#improved)
|
||||
* Improved `bin/grav install` command
|
||||
1. [](#bugfix)
|
||||
* Fixed potential error when upgrading Grav
|
||||
* Fixed broken list in `bin/gpm index` [#3092](https://github.com/getgrav/grav/issues/3092)
|
||||
* Fixed CLI/GPM command failures returning 0 (success) value [#3017](https://github.com/getgrav/grav/issues/3017)
|
||||
* Fixed unimplemented `PageObject::getOriginal()` call [#3098](https://github.com/getgrav/grav/issues/3098)
|
||||
* Fixed `Argument 1 passed to Grav\Common\User\DataUser\User::filterUsername() must be of the type string` [#3101](https://github.com/getgrav/grav/issues/3101)
|
||||
* Fixed broken check if php exif module is enabled in `ImageFile::fixOrientation()`
|
||||
* Fixed `StaticResizeTrait::resize()` bad image height/width attributes if `null` values are passed to the method
|
||||
* Fixed twig script/style tag `{% script 'file.js' at 'bottom' %}`, replaces broken `in` operator [#3084](https://github.com/getgrav/grav/issues/3084)
|
||||
* Fixed dropped query params when `?` is preceded with `/` [#2964](https://github.com/getgrav/grav/issues/2964)
|
||||
|
||||
# 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](https://learn.getgrav.org/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
|
||||
|
||||
1. [](#new)
|
||||
* Plugins & Themes: Call `$plugin->autoload()` and `$theme->autoload()` automatically when object gets initialized
|
||||
* CLI: Added `$grav->initializeCli()` method
|
||||
* Flex Directory: Implemented customizable configuration
|
||||
* Flex Storages: Added support for renaming directory entries
|
||||
1. [](#improved)
|
||||
* Vendor updates to latest
|
||||
1. [](#bugfix)
|
||||
* Regression: Fixed fatal error in blueprints [#2811](https://github.com/getgrav/grav/issues/2811)
|
||||
* Regression: Fixed bad method call in FlexDirectory::getAuthorizeRule()
|
||||
* Regression: Fixed fatal error in admin if the site has custom permissions in `onAdminRegisterPermissions`
|
||||
* Regression: Fixed flex user index with folder storage
|
||||
* Regression: Fixed fatal error in `bin/plugin` command
|
||||
* Fixed `FlexObject::triggerEvent()` not emitting events [#2816](https://github.com/getgrav/grav/issues/2816)
|
||||
* Grav 1.7: Fixed saving Flex configuration with ignored values becoming null
|
||||
* Grav 1.7: Fixed `bin/plugin` initialization
|
||||
* Grav 1.7: Fixed Flex Page cache key not taking account active language
|
||||
|
||||
# v1.7.0-rc.5
|
||||
## 02/03/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Regression: Flex not working in PHP 7.2 or older
|
||||
* Fixed creating first user from admin not clearing Flex User directory cache [#2809](https://github.com/getgrav/grav/issues/2809)
|
||||
* Fixed Flex Pages allowing root page to be deleted
|
||||
|
||||
# v1.7.0-rc.4
|
||||
## 02/03/2020
|
||||
|
||||
1. [](#new)
|
||||
* _POTENTIAL BREAKING CHANGE:_ Upgraded Parsedown to 1.7 for Parsedown-Extra 0.8. Plugins that extend Parsedown may need a fix to render as HTML
|
||||
* Added `$grav['flex']` to access all registered Flex Directories
|
||||
* Added `$grav->dispatchEvent()` method for PSR-14 events
|
||||
* Added `FlexRegisterEvent` which triggers when `$grav['flex']` is being accessed the first time
|
||||
* Added Flex cache configuration options
|
||||
* Added `PluginsLoadedEvent` which triggers after plugins have been loaded but not yet initialized
|
||||
* Added `SessionStartEvent` which triggers when session is started
|
||||
* Added `PermissionsRegisterEvent` which triggers when `$grav['permissions']` is being accessed the first time
|
||||
* Added support for Flex Directory specific configuration
|
||||
* Added support for more advanced ACL
|
||||
* Added `flatten_array` filter to form field validation
|
||||
* Added support for `security@: or: [admin.super, admin.pages]` in blueprints (nested AND/OR mode support)
|
||||
1. [](#improved)
|
||||
* Blueprint validation: Added `validate: value_type: bool|int|float|string|trim` to `array` to filter all the values inside the array
|
||||
* Twig `url()` takes now third parameter (`true`) to return URL on non-existing file instead of returning false
|
||||
1. [](#bugfix)
|
||||
* Grav 1.7: Fixed blueprint loading issues [#2782](https://github.com/getgrav/grav/issues/2782)
|
||||
* Fixed PHP 7.4 compatibility issue with `Stream`
|
||||
* Fixed new `Flex Users` being stored with wrong filename, login issues [#2785](https://github.com/getgrav/grav/issues/2785)
|
||||
* Fixed `ignore_empty: true` not removing empty values in blueprint filtering
|
||||
* Fixed `{{ false|string }}` twig to return '0' instead of ''
|
||||
* Fixed twig `url()` failing if stream has extra slash in it (e.g. `user:///data`)
|
||||
* Fixed `Blueprint::filter()` returning null instead of array if there is nothing to return
|
||||
* Fixed `Cannot use a scalar value as an array` error in `Utils::arrayUnflattenDotNotation()`, ignore nested structure instead
|
||||
* Fixed `Route` instance in multi-site setups
|
||||
* Fixed `system.translations: false` breaking `Inflector` methods
|
||||
* Fixed filtering ignored (eg. `security@: admin.super`) fields causing `Flex Objects` to lose data on save
|
||||
* Grav 1.7: Fixed `Flex Pages` unserialize issues if Flex-Objects Plugin has not been installed
|
||||
* Grav 1.7: Require Flex-Objects Plugin to edit Flex Accounts
|
||||
* Grav 1.7: Fixed bad result on testing `isPage()` when using Flex Pages
|
||||
|
||||
# v1.7.0-rc.3
|
||||
## 01/02/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added root page support for `Flex Pages`
|
||||
1. [](#improved)
|
||||
* Twig filter `|yaml_serialize`: added support for `JsonSerializable` objects and other array-like objects
|
||||
* Added support for returning Flex Page specific permissions for admin and testing
|
||||
* Updated copyright dates to `2020`
|
||||
* Various vendor updates
|
||||
1. [](#bugfix)
|
||||
* Grav 1.7: Fixed error on page initialization [#2753](https://github.com/getgrav/grav/issues/2753)
|
||||
* Fixed checking ACL for another user (who is not currently logged in) in a Flex Object or Directory
|
||||
* Fixed bug in Windows where `Filesystem::dirname()` returns backslashes
|
||||
* Fixed Flex object issues in Windows [#2773](https://github.com/getgrav/grav/issues/2773)
|
||||
|
||||
# v1.7.0-rc.2
|
||||
## 12/04/2019
|
||||
|
||||
@@ -200,6 +641,153 @@
|
||||
* Optimization: Initialize debugbar only after the configuration has been loaded
|
||||
* Optimization: Combine some early Grav processors into a single one
|
||||
|
||||
# v1.6.31
|
||||
## 12/14/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Allow all CSS and JS via `robots.txt` [#2006](https://github.com/getgrav/grav/issues/2006) [#3067](https://github.com/getgrav/grav/issues/3067)
|
||||
1. [](#bugfix)
|
||||
* Fixed `pages` field escaping issues, needs admin update, too [admin#1990](https://github.com/getgrav/grav-plugin-admin/issues/1990)
|
||||
* Fix `svg-image` issue with classes applied to all elements [#3068](https://github.com/getgrav/grav/issues/3068)
|
||||
|
||||
# v1.6.30
|
||||
## 12/03/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Rollback `samesite` cookie logic as it causes issues with PHP < 7.3 [#309](https://github.com/getgrav/grav/issues/3089)
|
||||
* Fixed issue with `.travis.yml` due to GitHub API deprecated functionality
|
||||
|
||||
# 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
|
||||
|
||||
1. [](#new)
|
||||
* Added `ConsoleCommand::setLanguage()` method to set language to be used from CLI
|
||||
* Added `ConsoleCommand::initializeGrav()` method to properly set up Grav instance to be used from CLI
|
||||
* Added `ConsoleCommand::initializePlugins()`method to properly set up all plugins to be used from CLI
|
||||
* Added `ConsoleCommand::initializeThemes()`method to properly set up current theme to be used from CLI
|
||||
* Added `ConsoleCommand::initializePages()` method to properly set up pages to be used from CLI
|
||||
1. [](#improved)
|
||||
* Vendor updates
|
||||
1. [](#bugfix)
|
||||
* Fixed `bin/plugin` CLI calling `$themes->init()` way too early (removed it, use above methods instead)
|
||||
* Fixed call to `$grav['page']` crashing CLI
|
||||
* Fixed encoding problems when PHP INI setting `default_charset` is not `utf-8` [#2154](https://github.com/getgrav/grav/issues/2154)
|
||||
|
||||
# v1.6.20
|
||||
## 02/03/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed incorrect routing caused by `str_replace()` in `Uri::init()` [#2754](https://github.com/getgrav/grav/issues/2754)
|
||||
* Fixed session cookie is being set twice in the HTTP header [#2745](https://github.com/getgrav/grav/issues/2745)
|
||||
* Fixed session not restarting if user was invalid (downgrading from Grav 1.7)
|
||||
* Fixed filesystem iterator calls with non-existing folders
|
||||
* Fixed `checkbox` field not being saved, requires also Form v4.0.2 [#1225](https://github.com/getgrav/grav/issues/1225)
|
||||
* Fixed `validation: strict` not working in blueprints [#1273](https://github.com/getgrav/grav/issues/1273)
|
||||
* Fixed `Data::filter()` removing empty fields (such as empty list) by default [#2805](https://github.com/getgrav/grav/issues/2805)
|
||||
* Fixed fatal error with non-integer page param value [#2803](https://github.com/getgrav/grav/issues/2803)
|
||||
* Fixed `Assets::addInlineJs()` parameter type mismatch between v1.5 and v1.6 [#2659](https://github.com/getgrav/grav/issues/2659)
|
||||
* Fixed `site.metadata` saving issues [#2615](https://github.com/getgrav/grav/issues/2615)
|
||||
|
||||
# v1.6.19
|
||||
## 12/04/2019
|
||||
|
||||
@@ -208,7 +796,7 @@
|
||||
1. [](#bugfix)
|
||||
* Fixed fatal error when calling `{{ grav.undefined }}`
|
||||
* Fixed multiple issues when there are no pages in the site
|
||||
* PHP 7.4 fix for [#2750](https://github.com/getgrav/grav/issues/2750)
|
||||
* PHP 7.4 fix for [#2750](https://github.com/getgrav/grav/issues/2750)
|
||||
|
||||
# v1.6.18
|
||||
## 12/02/2019
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Grav
|
||||
Copyright (c) 2021 Grav
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
20
README.md
20
README.md
@@ -21,9 +21,13 @@ 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
|
||||
|
||||
# Documentation
|
||||
|
||||
The full documentation can be found from [learn.getgrav.org](https://learn.getgrav.org).
|
||||
|
||||
# QuickStart
|
||||
|
||||
These are the options to get Grav:
|
||||
@@ -84,6 +88,11 @@ To update plugins and themes:
|
||||
$ bin/gpm update
|
||||
```
|
||||
|
||||
## Upgrading from older version
|
||||
|
||||
* [Upgrading to Grav 1.7](https://learn.getgrav.org/16/advanced/grav-development/grav-17-upgrade-guide)
|
||||
* [Upgrading to Grav 1.6](https://learn.getgrav.org/16/advanced/grav-development/grav-16-upgrade-guide)
|
||||
* [Upgrading from Grav <1.6](https://learn.getgrav.org/16/advanced/grav-development/grav-15-upgrade-guide)
|
||||
|
||||
# Contributing
|
||||
We appreciate any contribution to Grav, whether it is related to bugs, grammar, or simply a suggestion or improvement! Please refer to the [Contributing guide](CONTRIBUTING.md) for more guidance on this topic.
|
||||
@@ -128,7 +137,14 @@ See [LICENSE](LICENSE.txt)
|
||||
|
||||
# Running Tests
|
||||
|
||||
First install the dev dependencies by running `composer update` from the Grav root.
|
||||
First install the dev dependencies by running `composer install` from the Grav root.
|
||||
|
||||
Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.
|
||||
Windows users should use the `composer test-windows` command.
|
||||
You can also run a single unit test file, e.g. `composer test tests/unit/Grav/Common/AssetsTest.php`
|
||||
|
||||
To run phpstan tests, you should run:
|
||||
|
||||
* `composer phpstan` for global tests
|
||||
* `composer phpstan-framework` for more strict tests
|
||||
* `composer phpstan-plugins` to test all installed plugins
|
||||
|
||||
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.
|
||||
125
UPGRADE-1.7.md
125
UPGRADE-1.7.md
@@ -1,125 +0,0 @@
|
||||
# UPGRADE FROM 1.6 TO 1.7
|
||||
|
||||
## ADMINISTRATORS
|
||||
|
||||
### YAML files
|
||||
|
||||
* 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.
|
||||
|
||||
### 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**
|
||||
|
||||
### 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.
|
||||
|
||||
### CLI
|
||||
|
||||
* Added new `bin/grav server` CLI command to easily run Symfony or PHP built-in web servers
|
||||
* Added new `bin/grav page-system-validator [-r|--record] [-c|--check]` CLI command to test Flex Pages
|
||||
* 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
|
||||
* Added new configuration option `system.debugger.censored` to hide potentially sensitive information
|
||||
* Added new configuration option `system.pages.type` to enable Flex Pages
|
||||
* 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
|
||||
|
||||
### Debugging
|
||||
|
||||
* Added support for [Clockwork](https://underground.works/clockwork) developer tools (now default debugger)
|
||||
* Added support for [Tideways XHProf](https://github.com/tideways/php-xhprof-extension) PHP Extension for profiling method calls
|
||||
* Added Twig profiling for Clockwork debugger
|
||||
|
||||
## DEVELOPERS
|
||||
|
||||
### ACL
|
||||
|
||||
* `user.authorize()` now requires user to be authorized (passed 2FA check), unless the rule contains `login` in its name.
|
||||
|
||||
* **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).
|
||||
|
||||
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`
|
||||
* 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()`
|
||||
* **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`.
|
||||
|
||||
### Users
|
||||
|
||||
* 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
|
||||
|
||||
### Flex
|
||||
|
||||
* 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
|
||||
* **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
|
||||
|
||||
### Templating
|
||||
|
||||
* Added support for Twig 2.12 (still using Twig 1.42)
|
||||
* 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
|
||||
|
||||
### Multi-language
|
||||
|
||||
* Improved language support for `Route` class
|
||||
* Translations: rename MODULAR to MODULE everywhere
|
||||
* 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
|
||||
|
||||
### Events
|
||||
|
||||
* Use `Symfony EventDispatcher` directly instead of `rockettheme/toolbox` wrapper.
|
||||
|
||||
### Misc
|
||||
|
||||
* Added `Utils::isAssoc()` and `Utils::isNegative()` helper methods
|
||||
* Added `Utils::simpleTemplate()` method for very simple variable templating
|
||||
* 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
|
||||
* **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.
|
||||
|
||||
### 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).
|
||||
|
||||
### Admin
|
||||
|
||||
* **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.
|
||||
Binary file not shown.
41
bin/gpm
41
bin/gpm
@@ -2,8 +2,8 @@
|
||||
<?php
|
||||
|
||||
use Grav\Common\Composer;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Console\Application\GpmApplication;
|
||||
|
||||
\define('GRAV_CLI', true);
|
||||
\define('GRAV_REQUEST_TIME', microtime(true));
|
||||
@@ -28,6 +28,13 @@ if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
@@ -36,37 +43,7 @@ if (!function_exists('curl_version')) {
|
||||
exit('FATAL: GPM requires PHP Curl module to be installed');
|
||||
}
|
||||
|
||||
$climate = new League\CLImate\CLImate;
|
||||
$climate->arguments->add([
|
||||
'environment' => [
|
||||
'prefix' => 'e',
|
||||
'longPrefix' => 'env',
|
||||
'description' => 'Configuration Environment',
|
||||
'defaultValue' => 'localhost'
|
||||
]
|
||||
]);
|
||||
$climate->arguments->parse();
|
||||
|
||||
// Set up environment based on params.
|
||||
$environment = $climate->arguments->get('environment');
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav->setup($environment);
|
||||
|
||||
$grav['config']->init();
|
||||
$grav['uri']->init();
|
||||
$grav['users'];
|
||||
|
||||
$app = new Application('Grav Package Manager', GRAV_VERSION);
|
||||
$app->addCommands(array(
|
||||
new \Grav\Console\Gpm\IndexCommand(),
|
||||
new \Grav\Console\Gpm\VersionCommand(),
|
||||
new \Grav\Console\Gpm\InfoCommand(),
|
||||
new \Grav\Console\Gpm\InstallCommand(),
|
||||
new \Grav\Console\Gpm\UninstallCommand(),
|
||||
new \Grav\Console\Gpm\UpdateCommand(),
|
||||
new \Grav\Console\Gpm\SelfupgradeCommand(),
|
||||
new \Grav\Console\Gpm\DirectInstallCommand(),
|
||||
));
|
||||
|
||||
$app = new GpmApplication('Grav Package Manager', GRAV_VERSION);
|
||||
$app->run();
|
||||
|
||||
46
bin/grav
46
bin/grav
@@ -3,8 +3,7 @@
|
||||
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Console\Cli;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Grav\Console\Application\GravApplication;
|
||||
|
||||
\define('GRAV_CLI', true);
|
||||
\define('GRAV_REQUEST_TIME', microtime(true));
|
||||
@@ -25,45 +24,22 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
$climate = new League\CLImate\CLImate;
|
||||
$climate->arguments->add([
|
||||
'environment' => [
|
||||
'prefix' => 'e',
|
||||
'longPrefix' => 'env',
|
||||
'description' => 'Configuration Environment',
|
||||
'defaultValue' => 'localhost'
|
||||
]
|
||||
]);
|
||||
$climate->arguments->parse();
|
||||
|
||||
// Set up environment based on params.
|
||||
$environment = $climate->arguments->get('environment');
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav->setup($environment);
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
|
||||
if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
|
||||
$app = new Application('Grav CLI Application', GRAV_VERSION);
|
||||
$app->addCommands(array(
|
||||
new Cli\InstallCommand(),
|
||||
new Cli\ComposerCommand(),
|
||||
new Cli\SandboxCommand(),
|
||||
new Cli\CleanCommand(),
|
||||
new Cli\ClearCacheCommand(),
|
||||
new Cli\BackupCommand(),
|
||||
new Cli\NewProjectCommand(),
|
||||
new Cli\SchedulerCommand(),
|
||||
new Cli\SecurityCommand(),
|
||||
new Cli\LogViewerCommand(),
|
||||
new Cli\YamlLinterCommand(),
|
||||
new Cli\ServerCommand(),
|
||||
new Cli\PageSystemValidatorCommand(),
|
||||
));
|
||||
$app = new GravApplication('Grav CLI Application', GRAV_VERSION);
|
||||
$app->run();
|
||||
|
||||
126
bin/plugin
126
bin/plugin
@@ -2,12 +2,8 @@
|
||||
<?php
|
||||
|
||||
use Grav\Common\Composer;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Console\Application\PluginApplication;
|
||||
|
||||
\define('GRAV_CLI', true);
|
||||
\define('GRAV_REQUEST_TIME', microtime(true));
|
||||
@@ -32,119 +28,19 @@ if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
|
||||
$climate = new League\CLImate\CLImate;
|
||||
$climate->arguments->add([
|
||||
'environment' => [
|
||||
'prefix' => 'e',
|
||||
'longPrefix' => 'env',
|
||||
'description' => 'Configuration Environment',
|
||||
'defaultValue' => 'localhost'
|
||||
]
|
||||
]);
|
||||
$climate->arguments->parse();
|
||||
|
||||
$environment = $climate->arguments->get('environment');
|
||||
|
||||
// Bootstrap Grav container.
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav->setup($environment);
|
||||
|
||||
$grav['config']->init();
|
||||
$grav['uri']->init();
|
||||
$grav['users'];
|
||||
$grav['plugins']->init();
|
||||
$grav['themes']->init();
|
||||
|
||||
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
|
||||
$pattern = '([A-Z]\w+Command\.php)';
|
||||
|
||||
// get arguments and strip the application name
|
||||
if (null === $argv) {
|
||||
$argv = $_SERVER['argv'];
|
||||
}
|
||||
|
||||
$bin = array_shift($argv);
|
||||
$name = array_shift($argv);
|
||||
$argv = array_merge([$bin], $argv);
|
||||
|
||||
$input = new ArgvInput($argv);
|
||||
|
||||
/** @var \Grav\Common\Data\Data $plugin */
|
||||
$plugin = $grav['plugins']->get($name);
|
||||
|
||||
$output = new ConsoleOutput();
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
|
||||
$output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
|
||||
|
||||
if (!$name) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<red>Usage:</red>');
|
||||
$output->writeln(" {$bin} [slug] [command] [arguments]");
|
||||
$output->writeln('');
|
||||
$output->writeln('<red>Example:</red>');
|
||||
$output->writeln(" {$bin} error log -l 1 --trace");
|
||||
$list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 2]);
|
||||
|
||||
$total = 0;
|
||||
if (count($list)) {
|
||||
$available = [];
|
||||
$output->writeln('');
|
||||
$output->writeln('<red>Plugins with CLI available:</red>');
|
||||
foreach ($list as $index => $entry) {
|
||||
$split = explode('/', $entry);
|
||||
$entry = array_shift($split);
|
||||
$index = str_pad($index++ + 1, 2, '0', STR_PAD_LEFT);
|
||||
$total = str_pad($total++ + 1, 2, '0', STR_PAD_LEFT);
|
||||
if (\in_array($entry, $available, true)) {
|
||||
$total--;
|
||||
continue;
|
||||
}
|
||||
|
||||
$available[] = $entry;
|
||||
$commands_count = $index - $total + 1;
|
||||
$output->writeln(' ' . $total . '. <red>' . str_pad($entry, 15) . "</red> <white>{$bin} {$entry} list</white>");
|
||||
}
|
||||
}
|
||||
|
||||
exit;
|
||||
} else {
|
||||
if (is_null($plugin)) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name plugin not found</red>");
|
||||
die;
|
||||
}
|
||||
|
||||
if (!$plugin->enabled) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name not enabled</red>");
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
if ($plugin === null) {
|
||||
$output->writeln("<red>Grav Plugin <white>'{$name}'</white> is not installed</red>");
|
||||
exit;
|
||||
}
|
||||
|
||||
$path = 'plugins://' . $name . '/cli';
|
||||
|
||||
try {
|
||||
$commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]);
|
||||
} catch (\RuntimeException $e) {
|
||||
$output->writeln("<red>No Console Commands for <white>'{$name}'</white> where found in <white>'{$path}'</white></red>");
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach ($commands as $command_path) {
|
||||
$full_path = $grav['locator']->findResource("plugins://{$name}/cli/{$command_path}");
|
||||
require_once $full_path;
|
||||
|
||||
$command_class = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command_path);
|
||||
$command = new $command_class();
|
||||
$app->add($command);
|
||||
}
|
||||
|
||||
$app->run($input);
|
||||
$app = new PluginApplication('Grav Plugins Commands', GRAV_VERSION);
|
||||
$app->run();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
actor: Tester
|
||||
bootstrap: _bootstrap.php
|
||||
paths:
|
||||
tests: tests
|
||||
log: tests/_output
|
||||
@@ -6,7 +7,6 @@ paths:
|
||||
support: tests/_support
|
||||
envs: tests/_envs
|
||||
settings:
|
||||
bootstrap: _bootstrap.php
|
||||
colors: true
|
||||
memory_limit: 1024M
|
||||
extensions:
|
||||
|
||||
102
composer.json
102
composer.json
@@ -12,60 +12,71 @@
|
||||
"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",
|
||||
"erusev/parsedown": "1.6.4",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"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",
|
||||
"nyholm/psr7": "^1.3",
|
||||
"twig/twig": "~1.44",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"erusev/parsedown-extra": "~0.8",
|
||||
"symfony/contracts": "~1.1",
|
||||
"symfony/yaml": "~4.4",
|
||||
"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": "~1.4",
|
||||
"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": "~4.0",
|
||||
"enshrined/svg-sanitize": "^0.10.0"
|
||||
"willdurand/negotiation": "^3.0",
|
||||
"itsgoingd/clockwork": "^5.0",
|
||||
"enshrined/svg-sanitize": "~0.13",
|
||||
"symfony/http-client": "^4.4",
|
||||
"composer/semver": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.4",
|
||||
"phpstan/phpstan": "^0.11",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.11",
|
||||
"phpunit/php-code-coverage": "~6.0",
|
||||
"fzaninotto/faker": "^1.8",
|
||||
"victorjonsson/markdowndocs": "dev-master"
|
||||
"codeception/codeception": "^4.1",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12",
|
||||
"phpunit/php-code-coverage": "~9.2",
|
||||
"victorjonsson/markdowndocs": "dev-master",
|
||||
"codeception/module-asserts": "^1.3",
|
||||
"codeception/module-phpbrowser": "^1.0"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*"
|
||||
},
|
||||
"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,7 +86,7 @@
|
||||
"config": {
|
||||
"apcu-autoloader": true,
|
||||
"platform": {
|
||||
"php": "7.1.3"
|
||||
"php": "7.3.6"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
@@ -85,7 +96,11 @@
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/itsgoingd/clockwork"
|
||||
"url": "https://github.com/getgrav/Cache"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/getgrav/Image"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
@@ -102,12 +117,11 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"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",
|
||||
"api-17": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.17.md",
|
||||
"post-create-project-cmd": "bin/grav install",
|
||||
"phpstan": "vendor/bin/phpstan analyse -l 3 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=300M",
|
||||
"phpstan-framework": "vendor/bin/phpstan analyse -l 7 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=300M",
|
||||
"phpstan-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=300M",
|
||||
"phpstan": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src",
|
||||
"phpstan-framework": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src/Grav/Framework system/src/Grav/Events system/src/Grav/Installer",
|
||||
"phpstan-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"
|
||||
},
|
||||
|
||||
4158
composer.lock
generated
4158
composer.lock
generated
File diff suppressed because it is too large
Load Diff
24
index.php
24
index.php
@@ -10,19 +10,30 @@
|
||||
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>");
|
||||
}
|
||||
}
|
||||
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
@@ -35,15 +46,6 @@ $loader = require $autoload;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
// Set internal encoding if mbstring loaded
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
// Get the Grav instance
|
||||
$grav = Grav::instance(
|
||||
array(
|
||||
|
||||
2
now.json
2
now.json
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "*.php", "use": "@now/php" }]
|
||||
}
|
||||
|
||||
@@ -11,3 +11,6 @@ Allow: /user/pages/
|
||||
Allow: /user/themes/
|
||||
Allow: /user/images/
|
||||
Allow: /
|
||||
Allow: *.css$
|
||||
Allow: *.js$
|
||||
Allow: /system/*.js$
|
||||
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
|
||||
@@ -385,8 +387,8 @@ form:
|
||||
|
||||
languages.translations:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.TRANSLATIONS_ENABLED
|
||||
help: PLUGIN_ADMIN.TRANSLATIONS_ENABLED_HELP
|
||||
label: PLUGIN_ADMIN.LANGUAGE_TRANSLATIONS
|
||||
help: PLUGIN_ADMIN.LANGUAGE_TRANSLATIONS_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
@@ -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
|
||||
@@ -1039,8 +1109,8 @@ form:
|
||||
|
||||
debugger.censored:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.SHOW_SENSITIVE
|
||||
help: PLUGIN_ADMIN.SHOW_SENSITIVE_HELP
|
||||
label: PLUGIN_ADMIN.DEBUGGER_CENSORED
|
||||
help: PLUGIN_ADMIN.DEBUGGER_CENSORED_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
@@ -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,14 +1609,25 @@ form:
|
||||
title: PLUGIN_ADMIN.EXPERIMENTAL
|
||||
underline: true
|
||||
|
||||
# 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:
|
||||
page: PLUGIN_ADMIN.REGULAR
|
||||
flex: PLUGIN_ADMIN.FLEX
|
||||
type: hidden
|
||||
|
||||
flex_accounts:
|
||||
type: section
|
||||
title: Flex Accounts
|
||||
|
||||
accounts.type:
|
||||
type: select
|
||||
@@ -1487,7 +1635,7 @@ form:
|
||||
highlight: stable
|
||||
help: PLUGIN_ADMIN.ACCOUNTS_TYPE_HELP
|
||||
options:
|
||||
data: PLUGIN_ADMIN.REGULAR
|
||||
regular: PLUGIN_ADMIN.REGULAR
|
||||
flex: PLUGIN_ADMIN.FLEX
|
||||
|
||||
accounts.storage:
|
||||
|
||||
@@ -1,98 +1,8 @@
|
||||
title: Flex Accounts
|
||||
title: Flex User Accounts
|
||||
description: Manage your User Accounts in Flex.
|
||||
type: flex-objects
|
||||
|
||||
# Extends user account
|
||||
# Deprecated in Grav 1.7.0-rc.4: file was renamed to user-accounts.yaml
|
||||
extends@:
|
||||
type: account
|
||||
context: blueprints://user
|
||||
|
||||
# Flex configuration
|
||||
config:
|
||||
# Administration Configuration (needs Flex-Objects plugin)
|
||||
admin:
|
||||
# Admin menu
|
||||
menu:
|
||||
list:
|
||||
route: '/accounts'
|
||||
title: PLUGIN_ADMIN.USER_ACCOUNTS
|
||||
icon: fa-user
|
||||
authorize: ['admin.users', 'admin.accounts', 'admin.super']
|
||||
priority: 6
|
||||
|
||||
# Admin template type (folder)
|
||||
template: grav-accounts
|
||||
|
||||
# List view
|
||||
list:
|
||||
# Fields shown in the list view
|
||||
fields:
|
||||
username:
|
||||
link: edit
|
||||
search: true
|
||||
email:
|
||||
search: true
|
||||
fullname:
|
||||
search: true
|
||||
# Extra options
|
||||
options:
|
||||
per_page: 20
|
||||
order:
|
||||
by: username
|
||||
dir: asc
|
||||
|
||||
# Edit view
|
||||
edit:
|
||||
title:
|
||||
template: '{{ object.fullname ?? object.username }} <{{ object.email }}>'
|
||||
|
||||
# Site Configuration
|
||||
site:
|
||||
# Hide from flex types
|
||||
hidden: true
|
||||
templates:
|
||||
collection:
|
||||
# Lookup for the template layout files for collections of objects
|
||||
paths:
|
||||
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
|
||||
object:
|
||||
# Lookup for the template layout files for objects
|
||||
paths:
|
||||
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
|
||||
defaults:
|
||||
# Default template {TYPE}; overridden by filename of this blueprint if template folder exists
|
||||
type: grav-accounts
|
||||
# Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates)
|
||||
layout: default
|
||||
|
||||
# Data Configuration
|
||||
data:
|
||||
object: 'Grav\Common\Flex\Users\UserObject'
|
||||
collection: 'Grav\Common\Flex\Users\UserCollection'
|
||||
index: 'Grav\Common\Flex\Users\UserIndex'
|
||||
storage:
|
||||
class: 'Grav\Common\Flex\Users\Storage\UserFileStorage'
|
||||
options:
|
||||
formatter:
|
||||
class: 'Grav\Framework\File\Formatter\YamlFormatter'
|
||||
folder: 'account://'
|
||||
pattern: '{FOLDER}/{KEY}{EXT}'
|
||||
key: storage_key
|
||||
indexed: true
|
||||
search:
|
||||
options:
|
||||
contains: 1
|
||||
fields:
|
||||
- key
|
||||
- email
|
||||
|
||||
# Regular form definition
|
||||
form:
|
||||
fields:
|
||||
username:
|
||||
flex-disabled@: exists
|
||||
disabled: false
|
||||
flex-readonly@: exists
|
||||
readonly: false
|
||||
validate:
|
||||
required: true
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
title: Flex Pages
|
||||
title: Pages
|
||||
description: Manage your Grav Pages in Flex.
|
||||
type: flex-objects
|
||||
|
||||
@@ -7,21 +7,39 @@ 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)
|
||||
# Administration Configuration (needs Flex Objects plugin)
|
||||
admin:
|
||||
# Admin router
|
||||
router:
|
||||
path: '/pages'
|
||||
|
||||
# Permissions
|
||||
permissions:
|
||||
# Primary permissions
|
||||
admin.pages:
|
||||
type: crudl
|
||||
label: Pages
|
||||
admin.configuration.pages:
|
||||
type: default
|
||||
label: Pages Configuration
|
||||
|
||||
# Admin menu
|
||||
menu:
|
||||
list:
|
||||
route: '/pages'
|
||||
title: PLUGIN_ADMIN.PAGES
|
||||
icon: fa-file-text
|
||||
authorize: ['admin.pages', 'admin.super']
|
||||
authorize: ['admin.pages.list', 'admin.super']
|
||||
priority: 5
|
||||
|
||||
# Admin template type (folder)
|
||||
template: grav-pages
|
||||
template: pages
|
||||
|
||||
# Allowed admin actions
|
||||
actions:
|
||||
@@ -85,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:
|
||||
@@ -113,6 +134,10 @@ config:
|
||||
preview:
|
||||
enabled: true
|
||||
|
||||
# Configure view
|
||||
configure:
|
||||
authorize: 'admin.configuration.pages'
|
||||
|
||||
# Site Configuration
|
||||
site:
|
||||
# Hide from flex types
|
||||
@@ -138,11 +163,11 @@ config:
|
||||
|
||||
# Data Configuration
|
||||
data:
|
||||
object: 'Grav\Common\Flex\Pages\PageObject'
|
||||
collection: 'Grav\Common\Flex\Pages\PageCollection'
|
||||
index: 'Grav\Common\Flex\Pages\PageIndex'
|
||||
object: 'Grav\Common\Flex\Types\Pages\PageObject'
|
||||
collection: 'Grav\Common\Flex\Types\Pages\PageCollection'
|
||||
index: 'Grav\Common\Flex\Types\Pages\PageIndex'
|
||||
storage:
|
||||
class: 'Grav\Common\Flex\Pages\PageStorage'
|
||||
class: 'Grav\Common\Flex\Types\Pages\Storage\PageStorage'
|
||||
options:
|
||||
formatter:
|
||||
class: 'Grav\Framework\File\Formatter\MarkdownFormatter'
|
||||
@@ -163,9 +188,25 @@ config:
|
||||
- title
|
||||
- name
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
fields:
|
||||
import@:
|
||||
type: configure/compat
|
||||
context: blueprints://flex
|
||||
|
||||
# Regular form definition
|
||||
form:
|
||||
fields:
|
||||
lang:
|
||||
type: hidden
|
||||
value: ''
|
||||
|
||||
tabs:
|
||||
fields:
|
||||
security:
|
||||
type: tab
|
||||
title: PLUGIN_ADMIN.SECURITY
|
||||
import@:
|
||||
type: partials/security
|
||||
context: blueprints://pages
|
||||
|
||||
70
system/blueprints/flex/shared/configure.yaml
Normal file
70
system/blueprints/flex/shared/configure.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
form:
|
||||
validation: loose
|
||||
|
||||
fields:
|
||||
tabs:
|
||||
type: tabs
|
||||
fields:
|
||||
cache:
|
||||
type: tab
|
||||
title: Caching
|
||||
fields:
|
||||
object.cache.index.enabled:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED
|
||||
highlight: 1
|
||||
config-default@: system.flex.cache.index.enabled
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
object.cache.index.lifetime:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME
|
||||
config-default@: system.flex.cache.index.lifetime
|
||||
validate:
|
||||
type: int
|
||||
|
||||
object.cache.object.enabled:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED
|
||||
highlight: 1
|
||||
config-default@: system.flex.cache.object.enabled
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
object.cache.object.lifetime:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME
|
||||
config-default@: system.flex.cache.object.lifetime
|
||||
validate:
|
||||
type: int
|
||||
|
||||
object.cache.render.enabled:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED
|
||||
highlight: 1
|
||||
config-default@: system.flex.cache.render.enabled
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
object.cache.render.lifetime:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME
|
||||
config-default@: system.flex.cache.render.lifetime
|
||||
validate:
|
||||
type: int
|
||||
142
system/blueprints/flex/user-accounts.yaml
Normal file
142
system/blueprints/flex/user-accounts.yaml
Normal file
@@ -0,0 +1,142 @@
|
||||
title: User Accounts
|
||||
description: Manage your User Accounts in Flex.
|
||||
type: flex-objects
|
||||
|
||||
# Extends user account
|
||||
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)
|
||||
admin:
|
||||
# Admin router
|
||||
router:
|
||||
path: '/accounts/users'
|
||||
actions:
|
||||
configure:
|
||||
path: '/accounts/configure'
|
||||
redirects:
|
||||
'/user': '/accounts/users'
|
||||
'/accounts': '/accounts/users'
|
||||
|
||||
# Permissions
|
||||
permissions:
|
||||
# Primary permissions
|
||||
admin.users:
|
||||
type: crudl
|
||||
label: User Accounts
|
||||
admin.configuration.users:
|
||||
type: default
|
||||
label: Accounts Configuration
|
||||
|
||||
# Admin menu
|
||||
menu:
|
||||
base:
|
||||
location: '/accounts'
|
||||
route: '/accounts/users'
|
||||
index: 0
|
||||
title: PLUGIN_ADMIN.ACCOUNTS
|
||||
icon: fa-users
|
||||
authorize: ['admin.users.list', 'admin.super']
|
||||
priority: 6
|
||||
|
||||
# Admin template type (folder)
|
||||
template: user-accounts
|
||||
|
||||
# List view
|
||||
list:
|
||||
# Fields shown in the list view
|
||||
fields:
|
||||
username:
|
||||
link: edit
|
||||
search: true
|
||||
field:
|
||||
label: PLUGIN_ADMIN.USERNAME
|
||||
email:
|
||||
search: true
|
||||
fullname:
|
||||
search: true
|
||||
# Extra options
|
||||
options:
|
||||
per_page: 20
|
||||
order:
|
||||
by: username
|
||||
dir: asc
|
||||
|
||||
# Edit view
|
||||
edit:
|
||||
title:
|
||||
template: "{{ form.value('fullname') ?? form.value('username') }} <{{ form.value('email') }}>"
|
||||
|
||||
# Configure view
|
||||
configure:
|
||||
hidden: true
|
||||
authorize: 'admin.configuration.users'
|
||||
form: 'accounts'
|
||||
title:
|
||||
template: "{{ 'PLUGIN_ADMIN.ACCOUNTS'|tu }} {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }}"
|
||||
|
||||
# Site Configuration
|
||||
site:
|
||||
# Hide from flex types
|
||||
hidden: true
|
||||
templates:
|
||||
collection:
|
||||
# Lookup for the template layout files for collections of objects
|
||||
paths:
|
||||
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
|
||||
object:
|
||||
# Lookup for the template layout files for objects
|
||||
paths:
|
||||
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
|
||||
defaults:
|
||||
# Default template {TYPE}; overridden by filename of this blueprint if template folder exists
|
||||
type: user-accounts
|
||||
# Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates)
|
||||
layout: default
|
||||
|
||||
# Data Configuration
|
||||
data:
|
||||
object: 'Grav\Common\Flex\Types\Users\UserObject'
|
||||
collection: 'Grav\Common\Flex\Types\Users\UserCollection'
|
||||
index: 'Grav\Common\Flex\Types\Users\UserIndex'
|
||||
storage:
|
||||
class: 'Grav\Common\Flex\Types\Users\Storage\UserFileStorage'
|
||||
options:
|
||||
formatter:
|
||||
class: 'Grav\Framework\File\Formatter\YamlFormatter'
|
||||
folder: 'account://'
|
||||
pattern: '{FOLDER}/{KEY}{EXT}'
|
||||
indexed: true
|
||||
key: username
|
||||
case_sensitive: false
|
||||
search:
|
||||
options:
|
||||
contains: 1
|
||||
fields:
|
||||
- key
|
||||
- email
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
fields:
|
||||
import@:
|
||||
type: configure/compat
|
||||
context: blueprints://flex
|
||||
|
||||
# Regular form definition
|
||||
form:
|
||||
fields:
|
||||
username:
|
||||
flex-disabled@: exists
|
||||
disabled: false
|
||||
flex-readonly@: exists
|
||||
readonly: false
|
||||
validate:
|
||||
required: true
|
||||
@@ -1,4 +1,4 @@
|
||||
title: Flex User Groups
|
||||
title: User Groups
|
||||
description: Manage your User Groups in Flex.
|
||||
type: flex-objects
|
||||
|
||||
@@ -9,19 +9,40 @@ extends@:
|
||||
|
||||
# Flex configuration
|
||||
config:
|
||||
# Administration Configuration (needs Flex-Objects plugin)
|
||||
# Administration Configuration (needs Flex Objects plugin)
|
||||
admin:
|
||||
# Admin router
|
||||
router:
|
||||
path: '/accounts/groups'
|
||||
actions:
|
||||
configure:
|
||||
path: '/accounts/configure'
|
||||
redirects:
|
||||
'/accounts': '/accounts/groups'
|
||||
|
||||
# Permissions
|
||||
permissions:
|
||||
# Primary permissions
|
||||
admin.users:
|
||||
type: crudl
|
||||
label: User Accounts
|
||||
admin.configuration.users:
|
||||
type: default
|
||||
label: Accounts Configuration
|
||||
|
||||
# Admin menu
|
||||
menu:
|
||||
list:
|
||||
route: '/user-groups'
|
||||
title: PLUGIN_ADMIN.USER_GROUPS
|
||||
base:
|
||||
location: '/accounts'
|
||||
route: '/accounts/groups'
|
||||
index: 1
|
||||
title: PLUGIN_ADMIN.ACCOUNTS
|
||||
icon: fa-users
|
||||
authorize: ['admin.users', 'admin.accounts', 'admin.super']
|
||||
authorize: ['admin.users.list', 'admin.super']
|
||||
priority: 6
|
||||
|
||||
# Admin template type (folder)
|
||||
template: grav-user-groups
|
||||
template: user-groups
|
||||
|
||||
# List view
|
||||
list:
|
||||
@@ -44,7 +65,15 @@ config:
|
||||
# Edit view
|
||||
edit:
|
||||
title:
|
||||
template: '{{ object.readableName ?? object.groupname }}'
|
||||
template: "{{ form.value('readableName') ?? form.value('groupname') }}"
|
||||
|
||||
# Configure view
|
||||
configure:
|
||||
hidden: true
|
||||
authorize: 'admin.configuration.users'
|
||||
form: 'accounts'
|
||||
title:
|
||||
template: "{{ 'PLUGIN_ADMIN.ACCOUNTS'|tu }} {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }}"
|
||||
|
||||
# Site Configuration
|
||||
site:
|
||||
@@ -61,15 +90,15 @@ config:
|
||||
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
|
||||
defaults:
|
||||
# Default template {TYPE}; overridden by filename of this blueprint if template folder exists
|
||||
type: grav-user-groups
|
||||
type: user-groups
|
||||
# Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates)
|
||||
layout: default
|
||||
|
||||
# Data Configuration
|
||||
data:
|
||||
object: 'Grav\Common\Flex\UserGroups\UserGroupObject'
|
||||
collection: 'Grav\Common\Flex\UserGroups\UserGroupCollection'
|
||||
index: 'Grav\Common\Flex\UserGroups\UserGroupIndex'
|
||||
object: 'Grav\Common\Flex\Types\UserGroups\UserGroupObject'
|
||||
collection: 'Grav\Common\Flex\Types\UserGroups\UserGroupCollection'
|
||||
index: 'Grav\Common\Flex\Types\UserGroups\UserGroupIndex'
|
||||
storage:
|
||||
class: 'Grav\Framework\Flex\Storage\SimpleStorage'
|
||||
options:
|
||||
@@ -84,3 +113,10 @@ config:
|
||||
- key
|
||||
- groupname
|
||||
- description
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
fields:
|
||||
import@:
|
||||
type: configure/compat
|
||||
context: blueprints://flex
|
||||
|
||||
@@ -34,5 +34,3 @@ form:
|
||||
help: '"desc" or "asc" are valid values'
|
||||
placeholder: desc
|
||||
size: small
|
||||
|
||||
|
||||
|
||||
71
system/blueprints/pages/partials/security.yaml
Normal file
71
system/blueprints/pages/partials/security.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
form:
|
||||
fields:
|
||||
_site:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.PAGE_ACCESS
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
|
||||
header.login.visibility_requires_access:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS
|
||||
help: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
header.access:
|
||||
type: acl_picker
|
||||
label: PLUGIN_ADMIN.PAGE_ACCESS
|
||||
help: PLUGIN_ADMIN.PAGE_ACCESS_HELP
|
||||
ignore_empty: true
|
||||
data_type: access
|
||||
validate:
|
||||
type: array
|
||||
value_type: bool
|
||||
|
||||
_admin:
|
||||
security@: {or: [admin.super, admin.configuration.pages]}
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.PAGE PERMISSIONS
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
|
||||
header.permissions.inherit:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.PAGE_INHERIT_PERMISSIONS
|
||||
help: PLUGIN_ADMIN.PAGE_INHERIT_PERMISSIONS_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.permissions.authors:
|
||||
type: list
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.PAGE_AUTHORS
|
||||
help: PLUGIN_ADMIN.PAGE_AUTHORS_HELP
|
||||
|
||||
fields:
|
||||
value:
|
||||
type: text
|
||||
placeholder: PLUGIN_ADMIN.USERNAME
|
||||
style: vertical
|
||||
|
||||
header.permissions.groups:
|
||||
ignore@: true
|
||||
type: acl_picker
|
||||
label: PLUGIN_ADMIN.PAGE_GROUPS
|
||||
help: PLUGIN_ADMIN.PAGE_GROUPS_HELP
|
||||
ignore_empty: true
|
||||
data_type: permissions
|
||||
16
system/blueprints/pages/root.yaml
Normal file
16
system/blueprints/pages/root.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
title: PLUGIN_ADMIN.ROOT
|
||||
|
||||
rules:
|
||||
slug:
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
min: 1
|
||||
max: 200
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
|
||||
fields:
|
||||
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
@@ -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
|
||||
@@ -121,7 +132,9 @@ form:
|
||||
access:
|
||||
security@: admin.super
|
||||
type: permissions
|
||||
check_authorize: true
|
||||
label: PLUGIN_ADMIN.PERMISSIONS
|
||||
ignore_empty: true
|
||||
validate:
|
||||
type: array
|
||||
value_type: bool
|
||||
|
||||
@@ -1,46 +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
|
||||
label: PLUGIN_ADMIN.PERMISSIONS
|
||||
ignore_empty: true
|
||||
validate:
|
||||
type: array
|
||||
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
|
||||
|
||||
53
system/config/permissions.yaml
Normal file
53
system/config/permissions.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
actions:
|
||||
site:
|
||||
type: access
|
||||
label: Site
|
||||
admin:
|
||||
type: access
|
||||
label: Admin
|
||||
admin.pages:
|
||||
type: access
|
||||
label: Pages
|
||||
admin.users:
|
||||
type: access
|
||||
label: User Accounts
|
||||
|
||||
types:
|
||||
default:
|
||||
type: access
|
||||
|
||||
crud:
|
||||
type: compact
|
||||
letters:
|
||||
c:
|
||||
action: create
|
||||
label: PLUGIN_ADMIN.CREATE
|
||||
r:
|
||||
action: read
|
||||
label: PLUGIN_ADMIN.READ
|
||||
u:
|
||||
action: update
|
||||
label: PLUGIN_ADMIN.UPDATE
|
||||
d:
|
||||
action: delete
|
||||
label: PLUGIN_ADMIN.DELETE
|
||||
|
||||
crudp:
|
||||
type: crud
|
||||
letters:
|
||||
p:
|
||||
action: publish
|
||||
label: PLUGIN_ADMIN.PUBLISH
|
||||
|
||||
crudl:
|
||||
type: crud
|
||||
letters:
|
||||
l:
|
||||
action: list
|
||||
label: PLUGIN_ADMIN.LIST
|
||||
|
||||
crudpl:
|
||||
type: crud
|
||||
use:
|
||||
- crudp
|
||||
- crudl
|
||||
@@ -1,16 +0,0 @@
|
||||
schemes:
|
||||
image:
|
||||
type: Stream
|
||||
paths:
|
||||
- user://images
|
||||
- system://images
|
||||
|
||||
page:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://pages
|
||||
|
||||
account:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://accounts
|
||||
@@ -10,25 +10,31 @@ 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 # Enable translations by default
|
||||
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 /
|
||||
hide_in_urls: false # Hide the home route in URLs
|
||||
|
||||
pages:
|
||||
type: page # EXPERIMENTAL: Page type: page or flex
|
||||
type: regular # EXPERIMENTAL: Page type: regular or flex
|
||||
theme: quark # Default theme (defaults to "quark" theme)
|
||||
order:
|
||||
by: default # Order pages by "default", "alpha" or "date"
|
||||
@@ -56,12 +62,18 @@ 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)
|
||||
cache_control: # Can be blank for no setting, or a valid `cache-control` text value
|
||||
last_modified: false # Set the last modified date header based on file modification timestamp
|
||||
etag: false # Set the etag header tag
|
||||
etag: true # Set the etag header tag
|
||||
vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
|
||||
redirect_default_route: false # Automatically redirect to a page's default route
|
||||
redirect_default_code: 302 # Default code to use for redirects
|
||||
@@ -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,20 +168,34 @@ 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: Lax # Set session SameSite. Possible values are Lax, Strict and None. 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:
|
||||
|
||||
gpm:
|
||||
releases: testing # Set to either 'stable' or 'testing'
|
||||
releases: stable # Set to either 'stable' or 'testing'
|
||||
proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
|
||||
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
|
||||
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
|
||||
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
|
||||
|
||||
accounts:
|
||||
type: data # EXPERIMENTAL: Account type: data or flex
|
||||
type: regular # EXPERIMENTAL: Account type: regular or flex
|
||||
storage: file # EXPERIMENTAL: Flex storage type: file or folder
|
||||
|
||||
flex:
|
||||
cache:
|
||||
index:
|
||||
enabled: true # Set to true to enable Flex index caching. Is used to cache timestamps in files
|
||||
lifetime: 60 # Lifetime of cached index in seconds (0 = infinite)
|
||||
object:
|
||||
enabled: true # Set to true to enable Flex object caching. Is used to cache object data
|
||||
lifetime: 600 # Lifetime of cached objects in seconds (0 = infinite)
|
||||
render:
|
||||
enabled: true # Set to true to enable Flex render caching. Is used to cache rendered output
|
||||
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
|
||||
|
||||
@@ -2,28 +2,58 @@
|
||||
/**
|
||||
* @package Grav\Core
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.7.0-rc.2');
|
||||
define('GRAV_TESTING', true);
|
||||
define('DS', '/');
|
||||
define('GRAV_VERSION', '1.7.4');
|
||||
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
|
||||
define('GRAV_TESTING', false);
|
||||
|
||||
// PHP minimum requirement
|
||||
if (!defined('GRAV_PHP_MIN')) {
|
||||
define('GRAV_PHP_MIN', '7.1.3');
|
||||
define('GRAV_PHP_MIN', '7.3.6');
|
||||
}
|
||||
|
||||
// Directory separator
|
||||
if (!defined('DS')) {
|
||||
define('DS', '/');
|
||||
}
|
||||
|
||||
// Directories and Paths
|
||||
if (!defined('GRAV_ROOT')) {
|
||||
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, DS, getcwd()));
|
||||
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, getenv('GRAV_ROOT') ?: getcwd()), DS);
|
||||
define('GRAV_ROOT', $path);
|
||||
}
|
||||
define('ROOT_DIR', GRAV_ROOT . '/');
|
||||
define('USER_PATH', 'user/');
|
||||
if (!defined('GRAV_USER_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_USER_PATH') ?: 'user', DS);
|
||||
define('GRAV_USER_PATH', $path);
|
||||
}
|
||||
if (!defined('GRAV_CACHE_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_CACHE_PATH') ?: 'cache', DS);
|
||||
define('GRAV_CACHE_PATH', $path);
|
||||
}
|
||||
if (!defined('GRAV_LOG_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_LOG_PATH') ?: 'logs', DS);
|
||||
define('GRAV_LOG_PATH', $path);
|
||||
}
|
||||
if (!defined('GRAV_TMP_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_TMP_PATH') ?: 'tmp', DS);
|
||||
define('GRAV_TMP_PATH', $path);
|
||||
}
|
||||
if (!defined('GRAV_BACKUP_PATH')) {
|
||||
$path = rtrim(getenv('GRAV_BACKUP_PATH') ?: 'backup', DS);
|
||||
define('GRAV_BACKUP_PATH', $path);
|
||||
}
|
||||
unset($path);
|
||||
|
||||
define('USER_PATH', GRAV_USER_PATH . DS);
|
||||
define('CACHE_PATH', GRAV_CACHE_PATH . DS);
|
||||
define('ROOT_DIR', GRAV_ROOT . DS);
|
||||
define('USER_DIR', ROOT_DIR . USER_PATH);
|
||||
define('CACHE_DIR', ROOT_DIR . 'cache/');
|
||||
define('CACHE_DIR', ROOT_DIR . CACHE_PATH);
|
||||
|
||||
// DEPRECATED: Do not use!
|
||||
define('ASSETS_DIR', ROOT_DIR . 'assets/');
|
||||
@@ -36,7 +66,7 @@ define('LIB_DIR', SYSTEM_DIR .'src/');
|
||||
define('PLUGINS_DIR', USER_DIR .'plugins/');
|
||||
define('THEMES_DIR', USER_DIR .'themes/');
|
||||
define('VENDOR_DIR', ROOT_DIR .'vendor/');
|
||||
define('LOG_DIR', ROOT_DIR .'logs/');
|
||||
define('LOG_DIR', ROOT_DIR . GRAV_LOG_PATH . DS);
|
||||
// END DEPRECATED
|
||||
|
||||
// Some extensions
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* @package Grav\Core
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ GRAV:
|
||||
- 'Freitag'
|
||||
- 'Samstag'
|
||||
- 'Sonntag'
|
||||
YES: 'Ja'
|
||||
NO: 'Nein'
|
||||
CRON:
|
||||
EVERY: jede
|
||||
EVERY_HOUR: jede Stunde
|
||||
|
||||
@@ -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
|
||||
---
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Core
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -3,30 +3,41 @@
|
||||
/**
|
||||
* @package Grav\Common
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Closure;
|
||||
use Grav\Common\Assets\Pipeline;
|
||||
use Grav\Common\Assets\Traits\LegacyAssetsTrait;
|
||||
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,15 +66,23 @@ 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 = [];
|
||||
|
||||
/** @var \Closure|string */
|
||||
/** @var Closure|string */
|
||||
protected $fetch_command;
|
||||
/** @var string */
|
||||
protected $autoload;
|
||||
@@ -76,6 +95,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 +127,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,35 +153,35 @@ 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);
|
||||
|
||||
// JavaScript or CSS
|
||||
if (\strlen($extension) > 0) {
|
||||
if ($extension !== '') {
|
||||
$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 +189,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 +214,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 +232,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 +252,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 +262,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 +272,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 +282,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 +303,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 +317,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,17 +341,25 @@ class Assets extends PropertyObject
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $assets
|
||||
* @return array
|
||||
*/
|
||||
protected function sortAssets($assets)
|
||||
{
|
||||
uasort($assets, function ($a, $b) {
|
||||
if ($a['priority'] == $b['priority']) {
|
||||
return $a['order'] - $b['order'];
|
||||
}
|
||||
return $b['priority'] - $a['priority'];
|
||||
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 = '';
|
||||
@@ -365,7 +406,6 @@ class Assets extends PropertyObject
|
||||
*
|
||||
* @param string $group name of the group
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function css($group = 'head', $attributes = [])
|
||||
@@ -378,7 +418,6 @@ class Assets extends PropertyObject
|
||||
*
|
||||
* @param string $group name of the group
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function js($group = 'head', $attributes = [])
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -222,6 +224,7 @@ abstract class BaseAsset extends PropertyObject
|
||||
* @param string $file
|
||||
* @param string $dir
|
||||
* @param bool $local
|
||||
* @return string
|
||||
*/
|
||||
protected function cssRewrite($file, $dir, $local)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Css
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Css extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class InlineCss
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class InlineCss extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class InlineJs
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class InlineJs extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Js
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Js extends BaseAsset
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -15,8 +15,15 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use MatthiasMullie\Minify\CSS;
|
||||
use MatthiasMullie\Minify\JS;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Class Pipeline
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Pipeline extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
@@ -30,9 +37,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:
|
||||
@@ -129,7 +133,7 @@ class Pipeline extends PropertyObject
|
||||
|
||||
// Minify if required
|
||||
if ($this->shouldMinify('css')) {
|
||||
$minifier = new \MatthiasMullie\Minify\CSS();
|
||||
$minifier = new CSS();
|
||||
$minifier->add($buffer);
|
||||
$buffer = $minifier->minify();
|
||||
}
|
||||
@@ -192,7 +196,7 @@ class Pipeline extends PropertyObject
|
||||
|
||||
// Minify if required
|
||||
if ($this->shouldMinify('js')) {
|
||||
$minifier = new \MatthiasMullie\Minify\JS();
|
||||
$minifier = new JS();
|
||||
$minifier->add($buffer);
|
||||
$buffer = $minifier->minify();
|
||||
}
|
||||
@@ -220,7 +224,7 @@ class Pipeline extends PropertyObject
|
||||
* @param string $file the css source file
|
||||
* @param string $dir , $local relative path to the css file
|
||||
* @param bool $local is this a local or remote asset
|
||||
* @return mixed
|
||||
* @return string
|
||||
*/
|
||||
protected function cssRewrite($file, $dir, $local)
|
||||
{
|
||||
|
||||
@@ -3,19 +3,27 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Closure;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use function dirname;
|
||||
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.
|
||||
*
|
||||
@@ -73,7 +81,7 @@ trait AssetUtilsTrait
|
||||
if (0 === strpos($link, '//')) {
|
||||
$link = 'http:' . $link;
|
||||
}
|
||||
$relative_dir = \dirname($relative_path);
|
||||
$relative_dir = dirname($relative_path);
|
||||
} else {
|
||||
// Fix to remove relative dir if grav is in one
|
||||
if (($this->base_url !== '/') && Utils::startsWith($relative_path, $this->base_url)) {
|
||||
@@ -81,12 +89,12 @@ trait AssetUtilsTrait
|
||||
$relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
|
||||
}
|
||||
|
||||
$relative_dir = \dirname($relative_path);
|
||||
$relative_dir = dirname($relative_path);
|
||||
$link = ROOT_DIR . $relative_path;
|
||||
}
|
||||
|
||||
// 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 +131,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 +159,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 +178,7 @@ trait AssetUtilsTrait
|
||||
/**
|
||||
* Render Querystring
|
||||
*
|
||||
* @param string $asset
|
||||
* @param string|null $asset
|
||||
* @return string
|
||||
*/
|
||||
protected function renderQueryString($asset = null)
|
||||
|
||||
@@ -3,14 +3,21 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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
|
||||
{
|
||||
/**
|
||||
@@ -50,7 +57,11 @@ trait LegacyAssetsTrait
|
||||
// special case to handle old attributes being passed in
|
||||
if (isset($arguments['attributes'])) {
|
||||
$old_attributes = $arguments['attributes'];
|
||||
$arguments = array_merge($arguments, $old_attributes);
|
||||
if (is_array($old_attributes)) {
|
||||
$arguments = array_merge($arguments, $old_attributes);
|
||||
} else {
|
||||
$arguments['type'] = $old_attributes;
|
||||
}
|
||||
}
|
||||
unset($arguments['attributes']);
|
||||
|
||||
|
||||
@@ -3,14 +3,23 @@
|
||||
/**
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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) {
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
/**
|
||||
* @package Grav\Common\Backup
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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,
|
||||
'/'
|
||||
@@ -126,25 +151,27 @@ class Backups
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
* @return array|null
|
||||
* @return array
|
||||
*/
|
||||
public static function getAvailableBackups($force = false)
|
||||
{
|
||||
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), '/'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
/**
|
||||
* @package Grav\Common
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function donatj\UserAgent\parse_user_agent;
|
||||
|
||||
/**
|
||||
* Internally uses the PhpUserAgent package https://github.com/donatj/PhpUserAgent
|
||||
*/
|
||||
@@ -24,7 +27,7 @@ class Browser
|
||||
{
|
||||
try {
|
||||
$this->useragent = parse_user_agent();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,27 @@
|
||||
/**
|
||||
* @package Grav\Common
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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');
|
||||
|
||||
@@ -3,12 +3,18 @@
|
||||
/**
|
||||
* @package Grav\Common
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,23 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
@@ -146,6 +160,7 @@ abstract class CompiledBase
|
||||
*
|
||||
* @param string $name Name of the position.
|
||||
* @param string|string[] $filename File(s) to be loaded.
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function loadFile($name, $filename);
|
||||
|
||||
@@ -185,9 +200,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 +224,8 @@ abstract class CompiledBase
|
||||
* Save compiled file.
|
||||
*
|
||||
* @param string $filename
|
||||
* @throws \RuntimeException
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function saveCompiledFile($filename)
|
||||
@@ -219,7 +235,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 +245,7 @@ abstract class CompiledBase
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'@class' => \get_class($this),
|
||||
'@class' => get_class($this),
|
||||
'timestamp' => time(),
|
||||
'checksum' => $this->checksum(),
|
||||
'files' => $this->files,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use function is_callable;
|
||||
|
||||
/**
|
||||
* Class CompiledConfig
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class CompiledConfig extends CompiledBase
|
||||
{
|
||||
/** @var callable Blueprints loader. */
|
||||
@@ -60,10 +65,11 @@ class CompiledConfig extends CompiledBase
|
||||
* Create configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function createObject(array $data = [])
|
||||
{
|
||||
if ($this->withDefaults && empty($data) && \is_callable($this->callable)) {
|
||||
if ($this->withDefaults && empty($data) && is_callable($this->callable)) {
|
||||
$blueprints = $this->callable;
|
||||
$data = $blueprints()->getDefaults();
|
||||
}
|
||||
@@ -73,6 +79,8 @@ class CompiledConfig extends CompiledBase
|
||||
|
||||
/**
|
||||
* Finalize configuration object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function finalizeObject()
|
||||
{
|
||||
@@ -82,6 +90,8 @@ class CompiledConfig extends CompiledBase
|
||||
|
||||
/**
|
||||
* Function gets called when cached configuration is saved.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modified()
|
||||
{
|
||||
@@ -93,6 +103,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)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,12 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Service\ConfigServiceProvider;
|
||||
use Grav\Common\Utils;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class Config
|
||||
* @package Grav\Common\Config
|
||||
*/
|
||||
class Config extends Data
|
||||
{
|
||||
/** @var string */
|
||||
@@ -55,7 +60,7 @@ class Config extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $modified
|
||||
* @param bool|null $modified
|
||||
* @return bool
|
||||
*/
|
||||
public function modified($modified = null)
|
||||
@@ -68,7 +73,7 @@ class Config extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $timestamp
|
||||
* @param int|null $timestamp
|
||||
* @return int
|
||||
*/
|
||||
public function timestamp($timestamp = null)
|
||||
@@ -105,6 +110,9 @@ class Config extends Data
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function debug()
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
@@ -116,11 +124,14 @@ class Config extends Data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$setup = Grav::instance()['setup']->toArray();
|
||||
foreach ($setup as $key => $value) {
|
||||
if ($key === 'streams' || !\is_array($value)) {
|
||||
if ($key === 'streams' || !is_array($value)) {
|
||||
// Optimized as streams and simple values are fully defined in setup.
|
||||
$this->items[$key] = $value;
|
||||
} else {
|
||||
|
||||
@@ -3,14 +3,20 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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()];
|
||||
}
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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 */
|
||||
@@ -25,7 +29,7 @@ class Languages extends Data
|
||||
|
||||
/**
|
||||
* @param string|null $checksum
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function checksum($checksum = 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)
|
||||
{
|
||||
|
||||
@@ -3,19 +3,28 @@
|
||||
/**
|
||||
* @package Grav\Common\Config
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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
|
||||
{
|
||||
/**
|
||||
@@ -28,29 +37,58 @@ class Setup extends Data
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string Current environment normalized to lower case.
|
||||
* @var string|null Current environment normalized to lower case.
|
||||
*/
|
||||
public static $environment;
|
||||
|
||||
/** @var array */
|
||||
protected $streams = [
|
||||
'system' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['system'],
|
||||
]
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['user'],
|
||||
'' => [] // Set in constructor
|
||||
]
|
||||
],
|
||||
'cache' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => [], // Set in constructor
|
||||
'images' => ['images']
|
||||
]
|
||||
],
|
||||
'log' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => [] // Set in constructor
|
||||
]
|
||||
],
|
||||
'tmp' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => [] // Set in constructor
|
||||
]
|
||||
],
|
||||
'backup' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => [] // Set in constructor
|
||||
]
|
||||
],
|
||||
'environment' => [
|
||||
'type' => 'ReadOnlyStream'
|
||||
// If not defined, environment will be set up in the constructor.
|
||||
],
|
||||
'system' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['system'],
|
||||
]
|
||||
],
|
||||
'asset' => [
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
@@ -60,13 +98,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,36 +128,7 @@ class Setup extends Data
|
||||
'languages' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['environment://languages', 'user://languages', 'system/languages'],
|
||||
]
|
||||
],
|
||||
'cache' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['cache'],
|
||||
'images' => ['images']
|
||||
]
|
||||
],
|
||||
'log' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['logs']
|
||||
]
|
||||
],
|
||||
'backup' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['backup']
|
||||
]
|
||||
],
|
||||
'tmp' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['tmp']
|
||||
'' => ['environment://languages', 'user://languages', 'system://languages'],
|
||||
]
|
||||
],
|
||||
'image' => [
|
||||
@@ -154,27 +163,56 @@ class Setup extends Data
|
||||
*/
|
||||
public function __construct($container)
|
||||
{
|
||||
// Configure main streams.
|
||||
$this->streams['user']['prefixes'][''] = [GRAV_USER_PATH];
|
||||
$this->streams['cache']['prefixes'][''] = [GRAV_CACHE_PATH];
|
||||
$this->streams['log']['prefixes'][''] = [GRAV_LOG_PATH];
|
||||
$this->streams['tmp']['prefixes'][''] = [GRAV_TMP_PATH];
|
||||
$this->streams['backup']['prefixes'][''] = [GRAV_BACKUP_PATH];
|
||||
|
||||
// If environment is not set, look for the environment variable and then the constant.
|
||||
$environment = static::$environment ??
|
||||
(defined('GRAV_ENVIRONMENT') ? GRAV_ENVIRONMENT : (getenv('GRAV_ENVIRONMENT') ?: null));
|
||||
|
||||
// If no environment is set, make sure we get one (CLI or hostname).
|
||||
if (!static::$environment) {
|
||||
if (\defined('GRAV_CLI')) {
|
||||
static::$environment = 'cli';
|
||||
if (null === $environment) {
|
||||
if (defined('GRAV_CLI')) {
|
||||
$environment = 'cli';
|
||||
} else {
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = $container['request'];
|
||||
$host = $request->getUri()->getHost();
|
||||
|
||||
static::$environment = Utils::substrToString($host, ':');
|
||||
$environment = Utils::substrToString($host, ':');
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve server aliases to the proper environment.
|
||||
$environment = $this->environments[static::$environment] ?? static::$environment;
|
||||
static::$environment = static::$environments[$environment] ?? $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';
|
||||
$setup = is_file($file) ? (array) include $file : [];
|
||||
// If GRAV_SETUP_PATH has been defined, use it, otherwise use defaults.
|
||||
$setupFile = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : (getenv('GRAV_SETUP_PATH') ?: null);
|
||||
if (null !== $setupFile) {
|
||||
// Make sure that the custom setup file exists. Terminates the script if not.
|
||||
if (!str_starts_with($setupFile, '/')) {
|
||||
$setupFile = GRAV_ROOT . '/' . $setupFile;
|
||||
}
|
||||
if (!is_file($setupFile)) {
|
||||
echo 'GRAV_SETUP_PATH is defined but does not point to existing setup file.';
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
$setupFile = GRAV_ROOT . '/setup.php';
|
||||
if (!is_file($setupFile)) {
|
||||
$setupFile = GRAV_ROOT . '/' . GRAV_USER_PATH . '/setup.php';
|
||||
}
|
||||
if (!is_file($setupFile)) {
|
||||
$setupFile = null;
|
||||
}
|
||||
}
|
||||
$setup = $setupFile ? (array) include $setupFile : [];
|
||||
|
||||
// Add default streams defined in beginning of the class.
|
||||
if (!isset($setup['streams']['schemes'])) {
|
||||
@@ -185,15 +223,37 @@ class Setup extends Data
|
||||
// Initialize class.
|
||||
parent::__construct($setup);
|
||||
|
||||
$this->def('environment', static::$environment);
|
||||
|
||||
// Figure out path for the current environment.
|
||||
$envPath = defined('GRAV_ENVIRONMENT_PATH') ? GRAV_ENVIRONMENT_PATH : (getenv('GRAV_ENVIRONMENT_PATH') ?: null);
|
||||
if (null === $envPath) {
|
||||
// Find common path for all environments and append current environment into it.
|
||||
$envPath = defined('GRAV_ENVIRONMENTS_PATH') ? GRAV_ENVIRONMENTS_PATH : (getenv('GRAV_ENVIRONMENTS_PATH') ?: null);
|
||||
if (null !== $envPath) {
|
||||
$envPath .= '/';
|
||||
} else {
|
||||
// Use default location. Start with Grav 1.7 default.
|
||||
$envPath = GRAV_ROOT. '/' . GRAV_USER_PATH . '/env';
|
||||
if (is_dir($envPath)) {
|
||||
$envPath = 'user://env/';
|
||||
} else {
|
||||
// Fallback to Grav 1.6 default.
|
||||
$envPath = 'user://';
|
||||
}
|
||||
}
|
||||
$envPath .= $this->get('environment');
|
||||
}
|
||||
|
||||
// Set up environment.
|
||||
$this->def('environment', $environment);
|
||||
$this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->get('environment')}"]]);
|
||||
$this->def('environment', static::$environment);
|
||||
$this->def('streams.schemes.environment.prefixes', ['' => [$envPath]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
@@ -221,7 +281,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 +294,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 +341,51 @@ class Setup extends Data
|
||||
|
||||
/**
|
||||
* @param UniformResourceLocator $locator
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \BadMethodCallException
|
||||
* @throws \RuntimeException
|
||||
* @return void
|
||||
* @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)) {
|
||||
$force = $this->get('streams.schemes.environment.force', false);
|
||||
if (!$force) {
|
||||
$prefixes = $this->get('streams.schemes.environment.prefixes.');
|
||||
$update = false;
|
||||
foreach ($prefixes as $i => $prefix) {
|
||||
if ($locator->isStream($prefix)) {
|
||||
if ($locator->findResource($prefix, true)) {
|
||||
break;
|
||||
}
|
||||
} elseif (file_exists($prefix)) {
|
||||
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 +403,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Data
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,21 @@ 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;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Class Blueprint
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class Blueprint extends BlueprintForm
|
||||
{
|
||||
/** @var string */
|
||||
@@ -23,7 +37,7 @@ class Blueprint extends BlueprintForm
|
||||
/** @var string|null */
|
||||
protected $scope;
|
||||
|
||||
/** @var BlueprintSchema|null */
|
||||
/** @var BlueprintSchema */
|
||||
protected $blueprintSchema;
|
||||
|
||||
/** @var object|null */
|
||||
@@ -35,6 +49,9 @@ class Blueprint extends BlueprintForm
|
||||
/** @var array */
|
||||
protected $handlers = [];
|
||||
|
||||
/**
|
||||
* Clone blueprint.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->blueprintSchema) {
|
||||
@@ -44,6 +61,7 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
/**
|
||||
* @param string $scope
|
||||
* @return void
|
||||
*/
|
||||
public function setScope($scope)
|
||||
{
|
||||
@@ -52,6 +70,7 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @return void
|
||||
*/
|
||||
public function setObject($object)
|
||||
{
|
||||
@@ -73,6 +92,29 @@ class Blueprint extends BlueprintForm
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array|mixed|null
|
||||
* @since 1.7
|
||||
*/
|
||||
public function getDefaultValue(string $name)
|
||||
{
|
||||
$path = explode('.', $name) ?: [];
|
||||
$current = $this->getDefaults();
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current) && isset($current->{$field})) {
|
||||
$current = $current->{$field};
|
||||
} elseif (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nested structure containing default values defined in the blueprints.
|
||||
*
|
||||
@@ -104,7 +146,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} = [];
|
||||
@@ -113,7 +155,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] = [];
|
||||
@@ -141,12 +183,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
|
||||
*/
|
||||
@@ -189,13 +263,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +286,7 @@ class Blueprint extends BlueprintForm
|
||||
{
|
||||
$this->initInternals();
|
||||
|
||||
return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues);
|
||||
return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues) ?? [];
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +319,7 @@ class Blueprint extends BlueprintForm
|
||||
/**
|
||||
* @param string $name
|
||||
* @param callable $callable
|
||||
* @return void
|
||||
*/
|
||||
public function addDynamicHandler(string $name, callable $callable): void
|
||||
{
|
||||
@@ -251,6 +328,8 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
/**
|
||||
* Initialize validator.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initInternals()
|
||||
{
|
||||
@@ -284,7 +363,7 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
/**
|
||||
* @param string|array $path
|
||||
* @param string $context
|
||||
* @param string|null $context
|
||||
* @return array
|
||||
*/
|
||||
protected function getFiles($path, $context = null)
|
||||
@@ -292,16 +371,24 @@ 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)) {
|
||||
if (is_file($path)) {
|
||||
return [$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) {
|
||||
$context = $this->context;
|
||||
}
|
||||
|
||||
if ($context && $context[\strlen($context)-1] !== '/') {
|
||||
if ($context && $context[strlen($context)-1] !== '/') {
|
||||
$context .= '/';
|
||||
}
|
||||
|
||||
@@ -318,7 +405,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;
|
||||
@@ -332,12 +419,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;
|
||||
@@ -348,18 +436,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 {
|
||||
@@ -373,15 +461,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +495,7 @@ class Blueprint extends BlueprintForm
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicSecurity(array &$field, $property, array &$call)
|
||||
{
|
||||
@@ -401,18 +508,44 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
/** @var UserInterface|null $user */
|
||||
$user = $grav['user'] ?? null;
|
||||
foreach ($actions as $action) {
|
||||
if (!$user || !$user->authorize($action)) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
$success = null !== $user;
|
||||
if ($success) {
|
||||
$success = $this->resolveActions($user, $actions);
|
||||
}
|
||||
if (!$success) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface|null $user
|
||||
* @param array $actions
|
||||
* @param string $op
|
||||
* @return bool
|
||||
*/
|
||||
protected function resolveActions(UserInterface $user, array $actions, string $op = 'and')
|
||||
{
|
||||
$c = $i = count($actions);
|
||||
foreach ($actions as $key => $action) {
|
||||
if (!is_int($key) && is_array($actions)) {
|
||||
$i -= $this->resolveActions($user, $action, $key);
|
||||
} elseif ($user->authorize($action)) {
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
|
||||
if ($op === 'and') {
|
||||
return $i === 0;
|
||||
}
|
||||
|
||||
return $c !== $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicScope(array &$field, $property, array &$call)
|
||||
{
|
||||
@@ -421,7 +554,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;
|
||||
}
|
||||
@@ -436,10 +569,11 @@ class Blueprint extends BlueprintForm
|
||||
* @param array $field
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -3,21 +3,32 @@
|
||||
/**
|
||||
* @package Grav\Common\Data
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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,16 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
* 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 = [])
|
||||
{
|
||||
try {
|
||||
$messages = $this->validateArray($data, $this->nested);
|
||||
} 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();
|
||||
}
|
||||
|
||||
@@ -71,7 +85,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
*/
|
||||
public function processForm(array $data, array $toggles = [])
|
||||
{
|
||||
return $this->processFormRecursive($data, $toggles, $this->nested);
|
||||
return $this->processFormRecursive($data, $toggles, $this->nested) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +98,9 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
*/
|
||||
public function filter(array $data, $missingValuesAsNull = false, $keepEmptyValues = false)
|
||||
{
|
||||
return $this->filterArray($data, $this->nested, $missingValuesAsNull, $keepEmptyValues);
|
||||
$this->buildIgnoreNested($this->nested);
|
||||
|
||||
return $this->filterArray($data, $this->nested, '', $missingValuesAsNull, $keepEmptyValues) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,16 +145,19 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $rules
|
||||
* @param bool $strict
|
||||
* @param bool $xss
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function validateArray(array $data, array $rules)
|
||||
protected function validateArray(array $data, array $rules, bool $strict, bool $xss = true)
|
||||
{
|
||||
$messages = $this->checkRequired($data, $rules);
|
||||
|
||||
foreach ($data as $key => $field) {
|
||||
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.
|
||||
@@ -147,13 +166,26 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$messages += Validation::validate($field, $rule);
|
||||
} elseif (\is_array($field) && \is_array($val)) {
|
||||
$messages += Validation::validate($child, $rule);
|
||||
|
||||
} elseif (is_array($child) && is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$messages += $this->validateArray($field, $val);
|
||||
} elseif (isset($rules['validation']) && $rules['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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,50 +195,55 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $rules
|
||||
* @param string $parent
|
||||
* @param bool $missingValuesAsNull
|
||||
* @param bool $keepEmptyValues
|
||||
* @return array
|
||||
* @return array|null
|
||||
*/
|
||||
protected function filterArray(array $data, array $rules, $missingValuesAsNull, $keepEmptyValues)
|
||||
protected function filterArray(array $data, array $rules, string $parent, bool $missingValuesAsNull, bool $keepEmptyValues)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
if ($missingValuesAsNull) {
|
||||
// First pass is to fill up all the fields with null. This is done to lock the ordering of the fields.
|
||||
foreach ($rules as $key => $rule) {
|
||||
if ($key && !isset($results[$key])) {
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
|
||||
if (empty($rule['disabled']) && empty($rule['validate']['ignore'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $key => $field) {
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
$rule = is_string($val) ? $this->items[$val] : $this->items[$parent . $key] ?? null;
|
||||
|
||||
if ($rule) {
|
||||
// Item has been defined in blueprints.
|
||||
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
|
||||
// Skip any data in the ignored field.
|
||||
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
|
||||
// Skip any data in the ignored field.
|
||||
unset($results[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
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)) {
|
||||
// Array has been defined in blueprints.
|
||||
$k = $isParent ? '*' : $key;
|
||||
$field = $this->filterArray($field, $val, $parent . $k . '.', $missingValuesAsNull, $keepEmptyValues);
|
||||
|
||||
if (null === $field) {
|
||||
// Nested parent has no values.
|
||||
unset($results[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$field = Validation::filter($field, $rule);
|
||||
} elseif (\is_array($field) && \is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$field = $this->filterArray($field, $val, $missingValuesAsNull, $keepEmptyValues);
|
||||
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
|
||||
// Skip any extra data.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($keepEmptyValues || (null !== $field && (!\is_array($field) || !empty($field)))) {
|
||||
if ($keepEmptyValues || (null !== $field && (!is_array($field) || !empty($field)))) {
|
||||
$results[$key] = $field;
|
||||
}
|
||||
}
|
||||
@@ -214,6 +251,31 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
return $results ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $nested
|
||||
* @param string $parent
|
||||
* @return bool
|
||||
*/
|
||||
protected function buildIgnoreNested(array $nested, $parent = '')
|
||||
{
|
||||
$ignore = true;
|
||||
foreach ($nested as $key => $val) {
|
||||
$key = $parent . $key;
|
||||
if (is_array($val)) {
|
||||
$ignore = $this->buildIgnoreNested($val, $key . '.') && $ignore; // Keep the order!
|
||||
} else {
|
||||
$child = $this->items[$key] ?? null;
|
||||
$ignore = $ignore && (!$child || !empty($child['disabled']) || !empty($child['validate']['ignore']));
|
||||
}
|
||||
}
|
||||
if ($ignore) {
|
||||
$key = trim($parent, '.');
|
||||
$this->items[$key]['validate']['ignore'] = true;
|
||||
}
|
||||
|
||||
return $ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $data
|
||||
* @param array $toggles
|
||||
@@ -231,8 +293,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:
|
||||
@@ -267,7 +340,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
$messages = [];
|
||||
|
||||
foreach ($fields as $name => $field) {
|
||||
if (!\is_string($field)) {
|
||||
if (!is_string($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -308,6 +381,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)
|
||||
{
|
||||
|
||||
@@ -3,15 +3,23 @@
|
||||
/**
|
||||
* @package Grav\Common\Data
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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()));
|
||||
|
||||
|
||||
@@ -3,34 +3,48 @@
|
||||
/**
|
||||
* @package Grav\Common\Data
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use ArrayAccess;
|
||||
use Exception;
|
||||
use JsonSerializable;
|
||||
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 implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable, ExportInterface
|
||||
/**
|
||||
* Class Data
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class Data implements DataInterface, ArrayAccess, \Countable, JsonSerializable, ExportInterface
|
||||
{
|
||||
use NestedArrayAccessWithGetters, Countable, Export;
|
||||
|
||||
/** @var string */
|
||||
protected $gettersVariable = 'items';
|
||||
|
||||
/** @var array */
|
||||
protected $items;
|
||||
|
||||
/** @var Blueprint|null */
|
||||
/** @var Blueprint|callable|null */
|
||||
protected $blueprints;
|
||||
|
||||
/** @var FileInterface|null */
|
||||
protected $storage;
|
||||
|
||||
/** @var bool */
|
||||
private $missingValuesAsNull = false;
|
||||
/** @var bool */
|
||||
private $keepEmptyValues = true;
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
* @param Blueprint|callable|null $blueprints
|
||||
@@ -38,7 +52,31 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setKeepEmptyValues(bool $value)
|
||||
{
|
||||
$this->keepEmptyValues = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setMissingValuesAsNull(bool $value)
|
||||
{
|
||||
$this->missingValuesAsNull = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,20 +101,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);
|
||||
@@ -109,7 +147,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;
|
||||
}
|
||||
|
||||
@@ -130,14 +168,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);
|
||||
@@ -147,8 +185,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.
|
||||
@@ -186,7 +224,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
* Validate by blueprints.
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
@@ -201,8 +239,8 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
public function filter()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$missingValuesAsNull = (bool)(array_shift($args) ?: false);
|
||||
$keepEmptyValues = (bool)(array_shift($args) ?: false);
|
||||
$missingValuesAsNull = (bool)(array_shift($args) ?? $this->missingValuesAsNull);
|
||||
$keepEmptyValues = (bool)(array_shift($args) ?? $this->keepEmptyValues);
|
||||
|
||||
$this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);
|
||||
|
||||
@@ -227,18 +265,21 @@ 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
|
||||
*
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
@@ -291,6 +332,9 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->items;
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
/**
|
||||
* @package Grav\Common\Data
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Exception;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
|
||||
/**
|
||||
* Interface DataInterface
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
interface DataInterface
|
||||
{
|
||||
/**
|
||||
@@ -35,28 +40,37 @@ interface DataInterface
|
||||
|
||||
/**
|
||||
* Return blueprints.
|
||||
*
|
||||
* @return Blueprint
|
||||
*/
|
||||
public function blueprints();
|
||||
|
||||
/**
|
||||
* Validate by blueprints.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function validate();
|
||||
|
||||
/**
|
||||
* Filter all items by using blueprints.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filter();
|
||||
|
||||
/**
|
||||
* Get extra items which haven't been defined in blueprints.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extra();
|
||||
|
||||
/**
|
||||
* Save data into the file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save();
|
||||
|
||||
|
||||
@@ -3,16 +3,36 @@
|
||||
/**
|
||||
* @package Grav\Common\Data
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Class Validation
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class Validation
|
||||
{
|
||||
/**
|
||||
@@ -44,7 +64,7 @@ class Validation
|
||||
$name = ucfirst($field['label'] ?? $field['name']);
|
||||
$message = (string) isset($field['validate']['message'])
|
||||
? $language->translate($field['validate']['message'])
|
||||
: $language->translate('GRAV.FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
|
||||
: $language->translate('GRAV.FORM.INVALID_INPUT') . ' "' . $language->translate($name) . '"';
|
||||
|
||||
|
||||
// Validate type with fallback type text.
|
||||
@@ -78,6 +98,92 @@ class Validation
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $field
|
||||
* @return array
|
||||
*/
|
||||
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 +229,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;
|
||||
}
|
||||
|
||||
@@ -133,16 +239,16 @@ class Validation
|
||||
$value = trim($value);
|
||||
}
|
||||
|
||||
if (isset($params['min']) && \strlen($value) < $params['min']) {
|
||||
if (isset($params['min']) && strlen($value) < $params['min']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['max']) && \strlen($value) > $params['max']) {
|
||||
if (isset($params['max']) && strlen($value) > $params['max']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$min = $params['min'] ?? 0;
|
||||
if (isset($params['step']) && (\strlen($value) - $min) % $params['step'] === 0) {
|
||||
if (isset($params['step']) && (strlen($value) - $min) % $params['step'] === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -161,7 +267,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 '';
|
||||
}
|
||||
|
||||
@@ -176,11 +282,14 @@ class Validation
|
||||
* @param mixed $value
|
||||
* @param array $params
|
||||
* @param array $field
|
||||
* @return bool
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function filterCheckbox($value, array $params, array $field)
|
||||
{
|
||||
return (bool) $value;
|
||||
$value = (string)$value;
|
||||
$field_value = (string)($field['value'] ?? '1');
|
||||
|
||||
return $value === $field_value ? $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,7 +300,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +311,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,7 +455,7 @@ class Validation
|
||||
*/
|
||||
public static function typeToggle($value, array $params, array $field)
|
||||
{
|
||||
if (\is_bool($value)) {
|
||||
if (is_bool($value)) {
|
||||
$value = (int)$value;
|
||||
}
|
||||
|
||||
@@ -427,7 +547,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;
|
||||
@@ -480,7 +600,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))) {
|
||||
@@ -499,7 +619,6 @@ class Validation
|
||||
* @param array $field Blueprint for the field.
|
||||
* @return bool True if validation succeeded.
|
||||
*/
|
||||
|
||||
public static function typeUrl($value, array $params, array $field)
|
||||
{
|
||||
return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL);
|
||||
@@ -515,17 +634,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());
|
||||
}
|
||||
@@ -621,21 +740,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;
|
||||
}
|
||||
}
|
||||
@@ -658,6 +777,19 @@ class Validation
|
||||
return !($options && array_diff($value, $options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $params
|
||||
* @param array $field
|
||||
* @return array|null
|
||||
*/
|
||||
protected static function filterFlatten_array($value, $params, $field)
|
||||
{
|
||||
$value = static::filterArray($value, $params, $field);
|
||||
|
||||
return Utils::arrayUnflattenDotNotation($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $params
|
||||
@@ -670,7 +802,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;
|
||||
}
|
||||
|
||||
@@ -684,7 +816,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 {
|
||||
@@ -693,19 +825,75 @@ class Validation
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
|
||||
foreach ($values as $key => $val) {
|
||||
if ($val === '') {
|
||||
$ignoreEmpty = isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty']);
|
||||
$valueType = $params['value_type'] ?? null;
|
||||
$keyType = $params['key_type'] ?? null;
|
||||
if ($ignoreEmpty || $valueType || $keyType) {
|
||||
$values = static::arrayFilterRecurse($values, ['value_type' => $valueType, 'key_type' => $keyType, 'ignore_empty' => $ignoreEmpty]);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected static function arrayFilterRecurse(array $values, array $params): array
|
||||
{
|
||||
foreach ($values as $key => &$val) {
|
||||
if ($params['key_type']) {
|
||||
switch ($params['key_type']) {
|
||||
case 'int':
|
||||
$result = is_int($key);
|
||||
break;
|
||||
case 'string':
|
||||
$result = is_string($key);
|
||||
break;
|
||||
default:
|
||||
$result = false;
|
||||
}
|
||||
if (!$result) {
|
||||
unset($values[$key]);
|
||||
} elseif (\is_array($val)) {
|
||||
foreach ($val as $inner_key => $inner_value) {
|
||||
if ($inner_value === '') {
|
||||
unset($val[$inner_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($val)) {
|
||||
$val = static::arrayFilterRecurse($val, $params);
|
||||
if ($params['ignore_empty'] && empty($val)) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
} else {
|
||||
if ($params['value_type'] && $val !== '' && $val !== null) {
|
||||
switch ($params['value_type']) {
|
||||
case 'bool':
|
||||
if (Utils::isPositive($val)) {
|
||||
$val = true;
|
||||
} elseif (Utils::isNegative($val)) {
|
||||
$val = false;
|
||||
} else {
|
||||
// Ignore invalid bool values.
|
||||
$val = null;
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
$val = (int)$val;
|
||||
break;
|
||||
case 'float':
|
||||
$val = (float)$val;
|
||||
break;
|
||||
case 'string':
|
||||
$val = (string)$val;
|
||||
break;
|
||||
case 'trim':
|
||||
$val = trim($val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$values[$key] = $val;
|
||||
if ($params['ignore_empty'] && ($val === '' || $val === null)) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +908,7 @@ class Validation
|
||||
*/
|
||||
public static function typeList($value, array $params, array $field)
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -755,7 +943,7 @@ class Validation
|
||||
*/
|
||||
public static function filterYaml($value, $params)
|
||||
{
|
||||
if (!\is_string($value)) {
|
||||
if (!is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -865,7 +1053,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -875,7 +1063,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -905,7 +1093,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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -955,7 +1143,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,15 +3,20 @@
|
||||
/**
|
||||
* @package Grav\Common\Data
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RuntimeException;
|
||||
|
||||
class ValidationException extends \RuntimeException
|
||||
/**
|
||||
* Class ValidationException
|
||||
* @package Grav\Common\Data
|
||||
*/
|
||||
class ValidationException extends RuntimeException
|
||||
{
|
||||
/** @var array */
|
||||
protected $messages = [];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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,61 @@ 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 SplFileInfo;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Throwable;
|
||||
use Twig\Environment;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
use function array_slice;
|
||||
use function call_user_func;
|
||||
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 +102,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 +112,9 @@ class Debugger
|
||||
$this->setErrorHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Clockwork|null
|
||||
*/
|
||||
public function getClockwork(): ?Clockwork
|
||||
{
|
||||
return $this->enabled ? $this->clockwork : null;
|
||||
@@ -114,7 +124,7 @@ class Debugger
|
||||
* Initialize the debugger
|
||||
*
|
||||
* @return $this
|
||||
* @throws \DebugBar\DebugBarException
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
@@ -155,16 +165,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 +234,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 +257,7 @@ class Debugger
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$clockwork->getTimeline()->finalize($request->getAttribute('request_time'));
|
||||
$clockwork->timeline()->finalize($request->getAttribute('request_time'));
|
||||
|
||||
if ($this->censored) {
|
||||
$censored = 'CENSORED';
|
||||
@@ -266,7 +279,8 @@ class Debugger
|
||||
->withHeader('X-Clockwork-Id', $clockworkRequest->id)
|
||||
->withHeader('X-Clockwork-Version', $clockwork::VERSION);
|
||||
|
||||
$basePath = Grav::instance()['uri']->rootUrl();
|
||||
$grav = Grav::instance();
|
||||
$basePath = $this->grav['base_url_relative'] . $grav['pages']->base();
|
||||
if ($basePath) {
|
||||
$response = $response->withHeader('X-Clockwork-Path', $basePath . '/__clockwork/');
|
||||
}
|
||||
@@ -325,28 +339,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 +372,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 +416,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 +433,10 @@ class Debugger
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getCaller($limit = 2)
|
||||
{
|
||||
$trace = debug_backtrace(false, $limit);
|
||||
@@ -429,9 +448,8 @@ class Debugger
|
||||
* Adds a data collector
|
||||
*
|
||||
* @param DataCollectorInterface $collector
|
||||
*
|
||||
* @return $this
|
||||
* @throws \DebugBar\DebugBarException
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
public function addCollector($collector)
|
||||
{
|
||||
@@ -446,9 +464,8 @@ class Debugger
|
||||
* Returns a data collector
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return DataCollectorInterface|null
|
||||
* @throws \DebugBar\DebugBarException
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
public function getCollector($name)
|
||||
{
|
||||
@@ -520,7 +537,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,14 +549,27 @@ 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
|
||||
{
|
||||
if ($this->enabled && extension_loaded('tideways_xhprof')) {
|
||||
$this->profiling++;
|
||||
if ($this->profiling === 1) {
|
||||
// @phpstan-ignore-next-line
|
||||
\tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_NO_BUILTINS);
|
||||
}
|
||||
}
|
||||
@@ -548,7 +578,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
|
||||
@@ -557,6 +587,7 @@ class Debugger
|
||||
if ($this->enabled && extension_loaded('tideways_xhprof')) {
|
||||
$profiling = $this->profiling - 1;
|
||||
if ($profiling === 0) {
|
||||
// @phpstan-ignore-next-line
|
||||
$timings = \tideways_xhprof_disable();
|
||||
$timings = $this->buildProfilerTimings($timings);
|
||||
|
||||
@@ -577,6 +608,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 +650,10 @@ class Debugger
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $call
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
protected function parseProfilerCall(?string $call)
|
||||
{
|
||||
if (null === $call) {
|
||||
@@ -650,7 +689,6 @@ class Debugger
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function startTimer($name, $description = null)
|
||||
@@ -664,7 +702,6 @@ class Debugger
|
||||
* Stop the named timer
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function stopTimer($name)
|
||||
@@ -683,7 +720,6 @@ class Debugger
|
||||
* @param mixed $message
|
||||
* @param string $label
|
||||
* @param mixed|bool $isString
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addMessage($message, $label = 'info', $isString = true)
|
||||
@@ -710,32 +746,49 @@ class Debugger
|
||||
}
|
||||
|
||||
if ($this->clockwork) {
|
||||
$context = $isString;
|
||||
if (!is_scalar($message)) {
|
||||
$isString = $message;
|
||||
$message = '';
|
||||
} elseif (is_bool($isString)) {
|
||||
$isString = [];
|
||||
$context = $message;
|
||||
$message = gettype($context);
|
||||
}
|
||||
if (!is_array($isString)) {
|
||||
$isString = [gettype($isString) => $isString];
|
||||
if (is_bool($context)) {
|
||||
$context = [];
|
||||
} elseif (!is_array($context)) {
|
||||
$type = gettype($context);
|
||||
$context = [$type => $context];
|
||||
}
|
||||
$this->clockwork->log($label, $message, $isString);
|
||||
|
||||
$this->clockwork->log($label, $message, $context);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addEvent(string $name, ?Event $event, EventDispatcherInterface $dispatcher)
|
||||
/**
|
||||
* @param string $name
|
||||
* @param object $event
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @param float|null $time
|
||||
* @return $this
|
||||
*/
|
||||
public function addEvent(string $name, $event, EventDispatcherInterface $dispatcher, float $time = null)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
if ($this->clockwork) {
|
||||
$listeners = [];
|
||||
foreach ($dispatcher->getListeners($name) as $listener) {
|
||||
$listeners[] = $this->resolveCallable($listener);
|
||||
}
|
||||
$this->clockwork->addEvent($name, null, microtime(true), ['listeners' => $listeners]);
|
||||
if ($this->enabled && $this->clockwork) {
|
||||
$time = $time ?? microtime(true);
|
||||
$duration = (microtime(true) - $time) * 1000;
|
||||
|
||||
$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;
|
||||
@@ -744,14 +797,14 @@ 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) {
|
||||
$this->debugbar['exceptions']->addException($e);
|
||||
$this->debugbar['exceptions']->addThrowable($e);
|
||||
}
|
||||
|
||||
if ($this->clockwork) {
|
||||
@@ -766,6 +819,9 @@ class Debugger
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setErrorHandler()
|
||||
{
|
||||
$this->errorHandler = set_error_handler(
|
||||
@@ -784,7 +840,7 @@ class Debugger
|
||||
{
|
||||
if ($errno !== E_USER_DEPRECATED && $errno !== E_DEPRECATED) {
|
||||
if ($this->errorHandler) {
|
||||
return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
|
||||
return call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -817,10 +873,10 @@ class Debugger
|
||||
foreach ($backtrace as $current) {
|
||||
if (isset($current['args'])) {
|
||||
foreach ($current['args'] as $arg) {
|
||||
if ($arg instanceof \SplFileInfo) {
|
||||
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;
|
||||
|
||||
@@ -838,18 +894,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';
|
||||
@@ -865,7 +921,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);
|
||||
@@ -975,6 +1031,9 @@ class Debugger
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getDeprecations(): array
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
@@ -990,6 +1049,10 @@ class Debugger
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
protected function addDeprecations()
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
@@ -1008,6 +1071,10 @@ class Debugger
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $deprecated
|
||||
* @return array
|
||||
*/
|
||||
protected function getDepracatedMessage($deprecated)
|
||||
{
|
||||
$scope = $deprecated['scope'];
|
||||
@@ -1045,6 +1112,10 @@ class Debugger
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $trace
|
||||
* @return string
|
||||
*/
|
||||
protected function getFunction($trace)
|
||||
{
|
||||
if (!isset($trace['function'])) {
|
||||
@@ -1054,6 +1125,10 @@ class Debugger
|
||||
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callable
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveCallable(callable $callable)
|
||||
{
|
||||
if (is_array($callable)) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Errors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Grav\Common\Errors;
|
||||
|
||||
use Whoops\Handler\Handler;
|
||||
|
||||
/**
|
||||
* Class BareHandler
|
||||
* @package Grav\Common\Errors
|
||||
*/
|
||||
class BareHandler extends Handler
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -3,17 +3,29 @@
|
||||
/**
|
||||
* @package Grav\Common\Errors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Grav;
|
||||
use Whoops;
|
||||
use Whoops\Handler\JsonResponseHandler;
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Run;
|
||||
use Whoops\Util\Misc;
|
||||
use function is_int;
|
||||
|
||||
/**
|
||||
* Class Errors
|
||||
* @package Grav\Common\Errors
|
||||
*/
|
||||
class Errors
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function resetHandlers()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
@@ -22,7 +34,7 @@ class Errors
|
||||
|
||||
// Setup Whoops-based error handler
|
||||
$system = new SystemFacade;
|
||||
$whoops = new Whoops\Run($system);
|
||||
$whoops = new Run($system);
|
||||
|
||||
$verbosity = 1;
|
||||
|
||||
@@ -36,7 +48,7 @@ class Errors
|
||||
|
||||
switch ($verbosity) {
|
||||
case 1:
|
||||
$error_page = new Whoops\Handler\PrettyPageHandler;
|
||||
$error_page = new PrettyPageHandler();
|
||||
$error_page->setPageTitle('Crikey! There was an error...');
|
||||
$error_page->addResourcePath(GRAV_ROOT . '/system/assets');
|
||||
$error_page->addCustomCss('whoops.css');
|
||||
@@ -50,16 +62,16 @@ class Errors
|
||||
break;
|
||||
}
|
||||
|
||||
if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) {
|
||||
$whoops->prependHandler(new Whoops\Handler\JsonResponseHandler);
|
||||
if ($jsonRequest || Misc::isAjaxRequest()) {
|
||||
$whoops->prependHandler(new JsonResponseHandler());
|
||||
}
|
||||
|
||||
if (isset($config['log']) && $config['log']) {
|
||||
$logger = $grav['log'];
|
||||
$whoops->prependHandler(function ($exception, $inspector, $run) use ($logger) {
|
||||
$whoops->pushHandler(function ($exception, $inspector, $run) use ($logger) {
|
||||
try {
|
||||
$logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString());
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,16 +3,23 @@
|
||||
/**
|
||||
* @package Grav\Common\Errors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use ErrorException;
|
||||
use InvalidArgumentException;
|
||||
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 */
|
||||
@@ -43,7 +50,7 @@ class SimplePageHandler extends Handler
|
||||
}
|
||||
$message = $inspector->getException()->getMessage();
|
||||
|
||||
if ($inspector->getException() instanceof \ErrorException) {
|
||||
if ($inspector->getException() instanceof ErrorException) {
|
||||
$code = Misc::translateErrorCode($code);
|
||||
}
|
||||
|
||||
@@ -62,7 +69,7 @@ class SimplePageHandler extends Handler
|
||||
/**
|
||||
* @param string $resource
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function getResource($resource)
|
||||
{
|
||||
@@ -85,18 +92,19 @@ 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). ')'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
public function addResourcePath($path)
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
throw new \InvalidArgumentException(
|
||||
throw new InvalidArgumentException(
|
||||
"'{$path}' is not a valid directory"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
/**
|
||||
* @package Grav\Common\Errors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -3,21 +3,30 @@
|
||||
/**
|
||||
* @package Grav\Common\File
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\File
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\File
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\File
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,14 +3,22 @@
|
||||
/**
|
||||
* @package Grav\Common\Filesystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,29 @@
|
||||
/**
|
||||
* @package Grav\Common\Filesystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class Folder
|
||||
* @package Grav\Common\Filesystem
|
||||
*/
|
||||
abstract class Folder
|
||||
{
|
||||
/**
|
||||
@@ -22,20 +36,23 @@ abstract class Folder
|
||||
*/
|
||||
public static function lastModifiedFolder($path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @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) {
|
||||
@@ -55,27 +72,31 @@ abstract class Folder
|
||||
*/
|
||||
public static function lastModifiedFile($path, $extensions = 'md|yaml')
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @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());
|
||||
}
|
||||
}
|
||||
@@ -91,21 +112,24 @@ abstract class Folder
|
||||
*/
|
||||
public static function hashAllFiles($path)
|
||||
{
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
$files = [];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
if (file_exists($path)) {
|
||||
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = $file->getPathname() . '?'. $file->getMTime();
|
||||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = $file->getPathname() . '?'. $file->getMTime();
|
||||
}
|
||||
}
|
||||
|
||||
return md5(serialize($files));
|
||||
@@ -190,12 +214,15 @@ 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 [];
|
||||
}
|
||||
|
||||
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
|
||||
@@ -211,26 +238,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()) {
|
||||
@@ -277,8 +304,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)
|
||||
{
|
||||
@@ -286,7 +314,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.
|
||||
@@ -316,7 +344,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.
|
||||
@@ -328,13 +356,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
|
||||
@@ -344,7 +373,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.
|
||||
@@ -376,7 +405,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)
|
||||
{
|
||||
@@ -388,7 +417,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.
|
||||
@@ -403,7 +432,8 @@ abstract class Folder
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function mkdir($folder)
|
||||
{
|
||||
@@ -412,7 +442,8 @@ abstract class Folder
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function create($folder)
|
||||
{
|
||||
@@ -427,7 +458,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,9 +468,8 @@ abstract class Folder
|
||||
*
|
||||
* @param string $src
|
||||
* @param string $dest
|
||||
*
|
||||
* @return bool
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function rcopy($src, $dest)
|
||||
{
|
||||
@@ -456,8 +486,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());
|
||||
@@ -470,6 +499,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
|
||||
|
||||
@@ -3,13 +3,22 @@
|
||||
/**
|
||||
* @package Grav\Common\Filesystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -3,14 +3,21 @@
|
||||
/**
|
||||
* @package Grav\Common\Filesystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
@@ -3,12 +3,22 @@
|
||||
/**
|
||||
* @package Grav\Common\Filesystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
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([
|
||||
|
||||
@@ -1,428 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Flex\Pages;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Flex\Pages\Traits\PageContentTrait;
|
||||
use Grav\Common\Flex\Pages\Traits\PageLegacyTrait;
|
||||
use Grav\Common\Flex\Pages\Traits\PageRoutableTrait;
|
||||
use Grav\Common\Flex\Pages\Traits\PageTranslateTrait;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class GravPageObject
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*
|
||||
* @property string $name
|
||||
* @property string $route
|
||||
* @property string $folder
|
||||
* @property int|false $order
|
||||
* @property string $template
|
||||
* @property string $language
|
||||
*/
|
||||
class PageObject extends FlexPageObject
|
||||
{
|
||||
use PageContentTrait;
|
||||
use PageLegacyTrait;
|
||||
use PageRoutableTrait;
|
||||
use PageTranslateTrait;
|
||||
|
||||
/** @var string Language code, eg: 'en' */
|
||||
protected $language;
|
||||
|
||||
/** @var string File format, eg. 'md' */
|
||||
protected $format;
|
||||
|
||||
/** @var bool */
|
||||
private $_initialized = false;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getCachedMethods(): array
|
||||
{
|
||||
return [
|
||||
'path' => true,
|
||||
'full_order' => true,
|
||||
'filterBy' => true,
|
||||
] + parent::getCachedMethods();
|
||||
}
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
if (!$this->_initialized) {
|
||||
Grav::instance()->fireEvent('onPageProcessed', new Event(['page' => $this]));
|
||||
$this->_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $query
|
||||
* @return Route
|
||||
*/
|
||||
public function getRoute($query = []): Route
|
||||
{
|
||||
$route = RouteFactory::createFromString($this->route());
|
||||
if (\is_array($query)) {
|
||||
foreach ($query as $key => $value) {
|
||||
$route = $route->withQueryParam($key, $value);
|
||||
}
|
||||
} else {
|
||||
$route = $route->withAddedPath($query);
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc PageInterface
|
||||
*/
|
||||
public function getFormValue(string $name, $default = null, string $separator = null)
|
||||
{
|
||||
$test = new \stdClass();
|
||||
|
||||
$value = $this->pageContentValue($name, $test);
|
||||
if ($value !== $test) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
switch ($name) {
|
||||
case 'name':
|
||||
// TODO: this should not be template!
|
||||
return $this->getProperty('template');
|
||||
case 'route':
|
||||
$key = dirname($this->hasKey() ? '/' . $this->getKey() : '/');
|
||||
return $key !== '/' ? $key : null;
|
||||
case 'full_route':
|
||||
return $this->hasKey() ? '/' . $this->getKey() : '';
|
||||
case 'full_order':
|
||||
return $this->full_order();
|
||||
case 'lang':
|
||||
return $this->getLanguage() ?? '';
|
||||
case 'translations':
|
||||
return $this->getLanguages();
|
||||
}
|
||||
|
||||
return parent::getFormValue($name, $default, $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|bool $reorder
|
||||
* @return FlexObject|\Grav\Framework\Flex\Interfaces\FlexObjectInterface
|
||||
*/
|
||||
public function save($reorder = true)
|
||||
{
|
||||
// Reorder siblings.
|
||||
if ($reorder === true) {
|
||||
$reorder = $this->_reorder ?: false;
|
||||
}
|
||||
$siblings = is_array($reorder) ? $this->reorderSiblings($reorder) : [];
|
||||
|
||||
/** @var static $instance */
|
||||
$instance = parent::save();
|
||||
|
||||
foreach ($siblings as $sibling) {
|
||||
$sibling->save(false);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ordering
|
||||
* @return PageCollection
|
||||
*/
|
||||
protected function reorderSiblings(array $ordering)
|
||||
{
|
||||
$storageKey = $this->getStorageKey();
|
||||
$oldParentKey = ltrim(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();
|
||||
|
||||
/** @var PageCollection|null $siblings */
|
||||
$siblings = $parent ? $parent->children() : null;
|
||||
/** @var PageCollection|null $siblings */
|
||||
$siblings = $siblings ? $siblings->withVisible()->getCollection() : null;
|
||||
if ($siblings) {
|
||||
$ordering = array_flip($ordering);
|
||||
if ($storageKey !== null) {
|
||||
$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);
|
||||
}
|
||||
/** @var PageCollection $siblings */
|
||||
$siblings = $siblings->orderBy(['order' => 'ASC']);
|
||||
$siblings->removeElement($this);
|
||||
} else {
|
||||
/** @var PageCollection $siblings */
|
||||
$siblings = $this->getFlexDirectory()->createCollection([]);
|
||||
}
|
||||
|
||||
return $siblings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function full_order(): string
|
||||
{
|
||||
$route = $this->path() . '/' . $this->folder();
|
||||
|
||||
return preg_replace(PageIndex::ORDER_LIST_REGEX, '\\1', $route) ?? $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Blueprint
|
||||
*/
|
||||
protected function doGetBlueprint(string $name = ''): Blueprint
|
||||
{
|
||||
try {
|
||||
// Make sure that pages has been initialized.
|
||||
Pages::getTypes();
|
||||
|
||||
// TODO: We need to move raw blueprint logic to Grav itself to remove admin dependency here.
|
||||
if ($name === 'raw') {
|
||||
// Admin RAW mode.
|
||||
/** @var Admin|null $admin */
|
||||
$admin = Grav::instance()['admin'] ?? null;
|
||||
if ($admin) {
|
||||
$template = $this->isModule() ? 'modular_raw' : 'raw';
|
||||
|
||||
return $admin->blueprints("admin/pages/{$template}");
|
||||
}
|
||||
}
|
||||
|
||||
$template = $this->getProperty('template') . ($name ? '.' . $name : '');
|
||||
|
||||
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
|
||||
} catch (\RuntimeException $e) {
|
||||
$template = 'default' . ($name ? '.' . $name : '');
|
||||
|
||||
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function getLevelListing(array $options): array
|
||||
{
|
||||
$index = $this->getFlexDirectory()->getIndex();
|
||||
|
||||
return method_exists($index, 'getLevelListing') ? $index->getLevelListing($options) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter page (true/false) by given filters.
|
||||
*
|
||||
* - search: string
|
||||
* - extension: string
|
||||
* - module: bool
|
||||
* - visible: bool
|
||||
* - routable: bool
|
||||
* - published: bool
|
||||
* - page: bool
|
||||
* - translated: bool
|
||||
*
|
||||
* @param array $filters
|
||||
* @param bool $recursive
|
||||
* @return bool
|
||||
*/
|
||||
public function filterBy(array $filters, bool $recursive = false): bool
|
||||
{
|
||||
foreach ($filters as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'search':
|
||||
$matches = $this->search((string)$value) > 0.0;
|
||||
break;
|
||||
case 'page_type':
|
||||
$types = $value ? explode(',', $value) : [];
|
||||
$matches = in_array($this->template(), $types, true);
|
||||
break;
|
||||
case 'extension':
|
||||
$matches = Utils::contains((string)$value, $this->extension());
|
||||
break;
|
||||
case 'routable':
|
||||
$matches = $this->isRoutable() === (bool)$value;
|
||||
break;
|
||||
case 'published':
|
||||
$matches = $this->isPublished() === (bool)$value;
|
||||
break;
|
||||
case 'visible':
|
||||
$matches = $this->isVisible() === (bool)$value;
|
||||
break;
|
||||
case 'module':
|
||||
$matches = $this->isModule() === (bool)$value;
|
||||
break;
|
||||
case 'page':
|
||||
$matches = $this->isPage() === (bool)$value;
|
||||
break;
|
||||
case 'folder':
|
||||
$matches = $this->isPage() === !$value;
|
||||
break;
|
||||
case 'translated':
|
||||
$matches = $this->hasTranslation() === (bool)$value;
|
||||
break;
|
||||
default:
|
||||
$matches = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If current filter does not match, we still may have match as a parent.
|
||||
if ($matches === false) {
|
||||
return $recursive && $this->children()->getIndex()->filterBy($filters, true)->count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
$list = parent::__debugInfo();
|
||||
|
||||
return $list + [
|
||||
'_content_meta:private' => $this->getContentMeta(),
|
||||
'_content:private' => $this->getRawContent()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
* @param bool $extended
|
||||
*/
|
||||
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(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.
|
||||
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, '/');
|
||||
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();
|
||||
}
|
||||
|
||||
$elements['parent_key'] = $parentKey;
|
||||
}
|
||||
parent::filterElements($elements, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function prepareStorage(): array
|
||||
{
|
||||
$meta = $this->getMetaData();
|
||||
$oldLang = $meta['lang'] ?? '';
|
||||
$newLang = $this->getProperty('lang');
|
||||
|
||||
// Always clone the page to the new language.
|
||||
if ($oldLang !== $newLang) {
|
||||
$meta['clone'] = true;
|
||||
}
|
||||
|
||||
// Make sure that certain elements are always sent to the storage layer.
|
||||
$elements = [
|
||||
'__META' => $meta,
|
||||
'storage_key' => $this->getStorageKey(),
|
||||
'parent_key' => $this->getProperty('parent_key'),
|
||||
'order' => $this->getProperty('order'),
|
||||
'folder' => preg_replace('|^\d+\.|', '', $this->getProperty('folder')),
|
||||
'template' => preg_replace('|modular/|', '', $this->getProperty('template')),
|
||||
'lang' => $newLang
|
||||
] + parent::prepareStorage();
|
||||
|
||||
return $elements;
|
||||
}
|
||||
}
|
||||
51
system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php
Normal file
51
system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Traits;
|
||||
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Trait FlexCollectionTrait
|
||||
* @package Grav\Common\Flex\Traits
|
||||
*/
|
||||
trait FlexCollectionTrait
|
||||
{
|
||||
use FlexCommonTrait;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param object|null $event
|
||||
* @return $this
|
||||
*/
|
||||
public function triggerEvent(string $name, $event = null)
|
||||
{
|
||||
if (null === $event) {
|
||||
$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();
|
||||
if ($event instanceof Event) {
|
||||
$container->fireEvent($name, $event);
|
||||
} else {
|
||||
$container->dispatchEvent($event);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
54
system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php
Normal file
54
system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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\Traits;
|
||||
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
* Trait FlexCommonTrait
|
||||
* @package Grav\Common\Flex\Traits
|
||||
*/
|
||||
trait FlexCommonTrait
|
||||
{
|
||||
/**
|
||||
* @param string $layout
|
||||
* @return Template|TemplateWrapper
|
||||
* @throws LoaderError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
protected function getTemplate($layout)
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $container['twig'];
|
||||
|
||||
try {
|
||||
return $twig->twig()->resolveTemplate($this->getTemplatePaths($layout));
|
||||
} catch (LoaderError $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$debugger->addException($e);
|
||||
|
||||
return $twig->twig()->resolveTemplate(['flex/404.html.twig']);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function getTemplatePaths(string $layout): array;
|
||||
abstract protected function getContainer(): Grav;
|
||||
}
|
||||
74
system/src/Grav/Common/Flex/Traits/FlexGravTrait.php
Normal file
74
system/src/Grav/Common/Flex/Traits/FlexGravTrait.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?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\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Framework\Flex\Flex;
|
||||
|
||||
/**
|
||||
* Implements Grav specific logic
|
||||
*/
|
||||
trait FlexGravTrait
|
||||
{
|
||||
/**
|
||||
* @return Grav
|
||||
*/
|
||||
protected function getContainer(): Grav
|
||||
{
|
||||
return Grav::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Flex
|
||||
*/
|
||||
protected function getFlexContainer(): Flex
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
|
||||
/** @var Flex $flex */
|
||||
$flex = $container['flex'];
|
||||
|
||||
return $flex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserInterface|null
|
||||
*/
|
||||
protected function getActiveUser(): ?UserInterface
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
|
||||
/** @var UserInterface|null $user */
|
||||
$user = $container['user'] ?? null;
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAdminSite(): bool
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
|
||||
return isset($container['admin']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getAuthorizeScope(): string
|
||||
{
|
||||
return $this->isAdminSite() ? 'admin' : 'site';
|
||||
}
|
||||
}
|
||||
20
system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php
Normal file
20
system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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\Traits;
|
||||
|
||||
/**
|
||||
* Trait FlexIndexTrait
|
||||
* @package Grav\Common\Flex\Traits
|
||||
*/
|
||||
trait FlexIndexTrait
|
||||
{
|
||||
}
|
||||
62
system/src/Grav/Common/Flex/Traits/FlexObjectTrait.php
Normal file
62
system/src/Grav/Common/Flex/Traits/FlexObjectTrait.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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\Traits;
|
||||
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Trait FlexObjectTrait
|
||||
* @package Grav\Common\Flex\Traits
|
||||
*/
|
||||
trait FlexObjectTrait
|
||||
{
|
||||
use FlexCommonTrait;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param object|null $event
|
||||
* @return $this
|
||||
*/
|
||||
public function triggerEvent(string $name, $event = null)
|
||||
{
|
||||
$events = [
|
||||
'onRender' => 'onFlexObjectRender',
|
||||
'onBeforeSave' => 'onFlexObjectBeforeSave',
|
||||
'onAfterSave' => 'onFlexObjectAfterSave',
|
||||
'onBeforeDelete' => 'onFlexObjectBeforeDelete',
|
||||
'onAfterDelete' => 'onFlexObjectAfterDelete'
|
||||
];
|
||||
|
||||
if (null === $event) {
|
||||
$event = new Event([
|
||||
'type' => 'flex',
|
||||
'directory' => $this->getFlexDirectory(),
|
||||
'object' => $this
|
||||
]);
|
||||
}
|
||||
|
||||
if (isset($events['name'])) {
|
||||
$name = $events['name'];
|
||||
} elseif (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) {
|
||||
$name = 'onFlexObject' . substr($name, 2);
|
||||
}
|
||||
|
||||
$container = $this->getContainer();
|
||||
if ($event instanceof Event) {
|
||||
$container->fireEvent($name, $event);
|
||||
} else {
|
||||
$container->dispatchEvent($event);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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\Generic;
|
||||
|
||||
use Grav\Common\Flex\Traits\FlexCollectionTrait;
|
||||
use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Framework\Flex\FlexCollection;
|
||||
|
||||
/**
|
||||
* Class GenericCollection
|
||||
* @package Grav\Common\Flex\Generic
|
||||
*
|
||||
* @extends FLexCollection<string,GenericObject>
|
||||
*/
|
||||
class GenericCollection extends FlexCollection
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexCollectionTrait;
|
||||
}
|
||||
29
system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php
Normal file
29
system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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\Generic;
|
||||
|
||||
use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Common\Flex\Traits\FlexIndexTrait;
|
||||
use Grav\Framework\Flex\FlexIndex;
|
||||
|
||||
/**
|
||||
* Class GenericIndex
|
||||
* @package Grav\Common\Flex\Generic
|
||||
*
|
||||
* @extends FLexIndex<string,GenericObject,GenericCollection>
|
||||
* @mixin GenericCollection
|
||||
*/
|
||||
class GenericIndex extends FlexIndex
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexIndexTrait;
|
||||
}
|
||||
28
system/src/Grav/Common/Flex/Types/Generic/GenericObject.php
Normal file
28
system/src/Grav/Common/Flex/Types/Generic/GenericObject.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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\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
|
||||
* @package Grav\Common\Flex\Generic
|
||||
*/
|
||||
class GenericObject extends FlexObject
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexObjectTrait;
|
||||
use FlexMediaTrait;
|
||||
}
|
||||
@@ -5,23 +5,38 @@ declare(strict_types=1);
|
||||
/**
|
||||
* @package Grav\Common\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Flex\Pages;
|
||||
namespace Grav\Common\Flex\Types\Pages;
|
||||
|
||||
use Exception;
|
||||
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
|
||||
* @package Grav\Plugin\FlexObjects\Types\GravPages
|
||||
*
|
||||
* @extends FlexPageCollection<string,PageObject>
|
||||
*
|
||||
* Incompatibilities with Grav\Common\Page\Collection:
|
||||
* $page = $collection->key() will not work at all
|
||||
* $clone = clone $collection does not clone objects inside the collection, does it matter?
|
||||
@@ -31,9 +46,18 @@ use Grav\Framework\Flex\Pages\FlexPageCollection;
|
||||
* $collection->filter() incompatible method signature (takes closure instead of callable)
|
||||
* $collection->prev() does not rewind the internal pointer
|
||||
* AND most methods are immutable; they do not update the current collection, but return updated one
|
||||
*
|
||||
* @method static shuffle()
|
||||
* @method static select(array $keys)
|
||||
* @method static unselect(array $keys)
|
||||
* @method static createFrom(array $elements, string $keyField = null)
|
||||
* @method PageIndex getIndex()
|
||||
*/
|
||||
class PageCollection extends FlexPageCollection implements PageCollectionInterface
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexCollectionTrait;
|
||||
|
||||
/** @var array|null */
|
||||
protected $_params;
|
||||
|
||||
@@ -62,6 +86,8 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
'dateRange' => true,
|
||||
'visible' => true,
|
||||
'nonVisible' => true,
|
||||
'pages' => true,
|
||||
'modules' => true,
|
||||
'modular' => true,
|
||||
'nonModular' => true,
|
||||
'published' => true,
|
||||
@@ -71,6 +97,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
'ofType' => true,
|
||||
'ofOneOfTheseTypes' => true,
|
||||
'ofOneOfTheseAccessLevels' => true,
|
||||
'withOrdered' => true,
|
||||
'withModules' => true,
|
||||
'withPages' => true,
|
||||
'withTranslation' => true,
|
||||
@@ -82,11 +109,10 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PageInterface|FlexObjectInterface
|
||||
* @return PageObject
|
||||
*/
|
||||
public function getRoot()
|
||||
{
|
||||
/** @var PageIndex $index */
|
||||
$index = $this->getIndex();
|
||||
|
||||
return $index->getRoot();
|
||||
@@ -134,7 +160,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.
|
||||
@@ -152,7 +178,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,13 +189,14 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return previous item.
|
||||
*
|
||||
* @return PageInterface|false
|
||||
* @phpstan-return PageObject|false
|
||||
*/
|
||||
public function prev()
|
||||
{
|
||||
@@ -184,6 +211,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
* Return nth item.
|
||||
* @param int $key
|
||||
* @return PageInterface|bool
|
||||
* @phpstan-return PageObject|false
|
||||
*/
|
||||
public function nth($key)
|
||||
{
|
||||
@@ -209,7 +237,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*/
|
||||
public function append($items)
|
||||
{
|
||||
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +248,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,13 +263,165 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @param string $sort_flags
|
||||
* @param array|null $manual
|
||||
* @param int|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) {
|
||||
$col->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,7 +434,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
* @param string|false $endDate
|
||||
* @param string|null $field
|
||||
* @return static
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false, $field = null)
|
||||
{
|
||||
@@ -305,11 +492,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 = [];
|
||||
/**
|
||||
@@ -326,24 +534,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,7 +649,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;
|
||||
}
|
||||
}
|
||||
@@ -461,19 +668,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;
|
||||
}
|
||||
}
|
||||
@@ -483,7 +690,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;
|
||||
}
|
||||
}
|
||||
@@ -495,7 +702,18 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return FlexCollectionInterface|FlexPageCollection
|
||||
* @return static
|
||||
*/
|
||||
public function withOrdered(bool $bool = true)
|
||||
{
|
||||
$list = array_keys(array_filter($this->call('isOrdered', [$bool])));
|
||||
|
||||
return $this->select($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return static
|
||||
*/
|
||||
public function withModules(bool $bool = true)
|
||||
{
|
||||
@@ -504,10 +722,9 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
return $this->select($list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param bool $bool
|
||||
* @return FlexCollectionInterface|FlexPageCollection
|
||||
* @return static
|
||||
*/
|
||||
public function withPages(bool $bool = true)
|
||||
{
|
||||
@@ -520,7 +737,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
* @param bool $bool
|
||||
* @param string|null $languageCode
|
||||
* @param bool|null $fallback
|
||||
* @return FlexCollectionInterface|FlexPageCollection
|
||||
* @return static
|
||||
*/
|
||||
public function withTranslation(bool $bool = true, string $languageCode = null, bool $fallback = null)
|
||||
{
|
||||
@@ -543,7 +760,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
*
|
||||
* @param array $filters
|
||||
* @param bool $recursive
|
||||
* @return FlexCollectionInterface
|
||||
* @return static
|
||||
*/
|
||||
public function filterBy(array $filters, bool $recursive = false)
|
||||
{
|
||||
@@ -556,7 +773,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function toExtendedArray(): array
|
||||
{
|
||||
@@ -566,6 +783,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
$entries[$object->route()] = $object->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
@@ -5,42 +5,48 @@ declare(strict_types=1);
|
||||
/**
|
||||
* @package Grav\Common\Flex
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Flex\Pages;
|
||||
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 InvalidArgumentException;
|
||||
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)
|
||||
* @extends FlexPageIndex<string,PageObject,PageCollection>
|
||||
* @mixin PageCollection
|
||||
*
|
||||
* @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;
|
||||
|
||||
public const VERSION = parent::VERSION . '.5';
|
||||
public const ORDER_LIST_REGEX = '/(\/\d+)\.[^\/]+/u';
|
||||
public const PAGE_ROUTE_REGEX = '/\/\d+\./u';
|
||||
@@ -74,15 +80,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]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,8 +120,42 @@ class PageIndex extends FlexPageIndex
|
||||
{
|
||||
$root = $this->_root;
|
||||
if (is_array($root)) {
|
||||
$directory = $this->getFlexDirectory();
|
||||
$storage = $directory->getStorage();
|
||||
|
||||
$defaults = [
|
||||
'header' => [
|
||||
'routable' => false,
|
||||
'permissions' => [
|
||||
'inherit' => false
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$row = $storage->readRows(['' => null])[''] ?? null;
|
||||
if (null !== $row) {
|
||||
if (isset($row['__ERROR'])) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$message = sprintf('Flex Pages: root page is broken in storage: %s', $row['__ERROR']);
|
||||
|
||||
$debugger->addException(new RuntimeException($message));
|
||||
$debugger->addMessage($message, 'error');
|
||||
|
||||
$row = ['__META' => $root];
|
||||
}
|
||||
|
||||
} else {
|
||||
$row = ['__META' => $root];
|
||||
}
|
||||
|
||||
$row = array_merge_recursive($defaults, $row);
|
||||
|
||||
/** @var PageObject $root */
|
||||
$root = $this->getFlexDirectory()->createObject(['__META' => $root], '/');
|
||||
$root = $this->getFlexDirectory()->createObject($row, '/', false);
|
||||
$root->name('root.md');
|
||||
$root->root(true);
|
||||
|
||||
$this->_root = $root;
|
||||
}
|
||||
|
||||
@@ -165,7 +209,7 @@ class PageIndex extends FlexPageIndex
|
||||
*
|
||||
* @param array $filters
|
||||
* @param bool $recursive
|
||||
* @return FlexCollectionInterface
|
||||
* @return static
|
||||
*/
|
||||
public function filterBy(array $filters, bool $recursive = false)
|
||||
{
|
||||
@@ -221,7 +265,7 @@ class PageIndex extends FlexPageIndex
|
||||
|
||||
/**
|
||||
* @param array $filters
|
||||
* @return FlexCollectionInterface
|
||||
* @return static
|
||||
*/
|
||||
protected function filterByParent(array $filters)
|
||||
{
|
||||
@@ -234,12 +278,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' => [],
|
||||
];
|
||||
@@ -254,7 +306,7 @@ class PageIndex extends FlexPageIndex
|
||||
/**
|
||||
* @param array $entries
|
||||
* @param string|null $keyField
|
||||
* @return $this|FlexPageIndex
|
||||
* @return static
|
||||
*/
|
||||
protected function createFrom(array $entries, string $keyField = null)
|
||||
{
|
||||
@@ -328,27 +380,29 @@ class PageIndex extends FlexPageIndex
|
||||
'type' => 'root',
|
||||
'modified' => $page->modified(),
|
||||
'size' => 0,
|
||||
'symlink' => false
|
||||
'symlink' => false,
|
||||
'has-children' => false
|
||||
];
|
||||
} else {
|
||||
$response[] = [
|
||||
'item-key' => '',
|
||||
'item-key' => '-root-',
|
||||
'icon' => 'root',
|
||||
'title' => '<root>',
|
||||
'route' => '/',
|
||||
'raw_route' => null,
|
||||
'title' => 'Root', // FIXME
|
||||
'route' => [
|
||||
'display' => '<root>', // FIXME
|
||||
'raw' => '_root',
|
||||
],
|
||||
'modified' => $page->modified(),
|
||||
'child_count' => 0,
|
||||
'extras' => [
|
||||
'template' => null,
|
||||
'template' => $page->template(),
|
||||
//'lang' => null,
|
||||
//'translated' => null,
|
||||
'langs' => [],
|
||||
'published' => false,
|
||||
'published_date' => null,
|
||||
'unpublished_date' => null,
|
||||
'visible' => false,
|
||||
'routable' => false,
|
||||
'tags' => ['non-routable'],
|
||||
'actions' => [],
|
||||
'tags' => ['root', 'non-routable'],
|
||||
'actions' => ['edit'], // FIXME
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -361,21 +415,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.
|
||||
@@ -411,7 +482,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;
|
||||
@@ -422,7 +493,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(),
|
||||
@@ -449,11 +520,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) {
|
||||
@@ -467,9 +533,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)
|
||||
{
|
||||
@@ -501,4 +589,384 @@ 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 PageCollection
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
return $this->getCollection()->merge($collection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return PageCollection
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
return $this->getCollection()->intersect($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param int $size
|
||||
* @return PageCollection[]
|
||||
*/
|
||||
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 static
|
||||
*/
|
||||
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 PageObject|null The previous item.
|
||||
*/
|
||||
public function prevSibling($path)
|
||||
{
|
||||
/** @var PageObject|null $result */
|
||||
$result = $this->__call('prevSibling', [$path]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
* @return PageObject|null The next item.
|
||||
*/
|
||||
public function nextSibling($path)
|
||||
{
|
||||
/** @var PageObject|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 PageObject|false The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1)
|
||||
{
|
||||
/** @var PageObject|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 static
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false, $field = null)
|
||||
{
|
||||
$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 static The collection with only visible pages
|
||||
*/
|
||||
public function visible()
|
||||
{
|
||||
$collection = $this->__call('visible', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return static The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible()
|
||||
{
|
||||
$collection = $this->__call('nonVisible', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return static The collection with only non-modular pages
|
||||
*/
|
||||
public function pages()
|
||||
{
|
||||
$collection = $this->__call('pages', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return static The collection with only modular pages
|
||||
*/
|
||||
public function modules()
|
||||
{
|
||||
$collection = $this->__call('modules', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return static The collection with only modular pages
|
||||
*/
|
||||
public function modular()
|
||||
{
|
||||
return $this->modules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return static The collection with only non-modular pages
|
||||
*/
|
||||
public function nonModular()
|
||||
{
|
||||
return $this->pages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return static The collection with only published pages
|
||||
*/
|
||||
public function published()
|
||||
{
|
||||
$collection = $this->__call('published', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return static The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished()
|
||||
{
|
||||
$collection = $this->__call('nonPublished', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return static The collection with only routable pages
|
||||
*/
|
||||
public function routable()
|
||||
{
|
||||
$collection = $this->__call('routable', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return static The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable()
|
||||
{
|
||||
$collection = $this->__call('nonRoutable', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of the specified type
|
||||
*
|
||||
* @param string $type
|
||||
* @return static The collection
|
||||
*/
|
||||
public function ofType($type)
|
||||
{
|
||||
$collection = $this->__call('ofType', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified types
|
||||
*
|
||||
* @param string[] $types
|
||||
* @return static The collection
|
||||
*/
|
||||
public function ofOneOfTheseTypes($types)
|
||||
{
|
||||
$collection = $this->__call('ofOneOfTheseTypes', []);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified access levels
|
||||
*
|
||||
* @param array $accessLevels
|
||||
* @return static The collection
|
||||
*/
|
||||
public function ofOneOfTheseAccessLevels($accessLevels)
|
||||
{
|
||||
$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();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user