mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
744 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
846a0baed8 | ||
|
|
cb8ea7780f | ||
|
|
5400bd3951 | ||
|
|
03521cd3f6 | ||
|
|
1eb0e37214 | ||
|
|
3e769618ec | ||
|
|
9319d5c0aa | ||
|
|
eb94940df6 | ||
|
|
a4c8c53939 | ||
|
|
567dd0d2c6 | ||
|
|
25c44816c0 | ||
|
|
1a692b348e | ||
|
|
17ed48e677 | ||
|
|
685033bb02 | ||
|
|
b86c982ba1 | ||
|
|
a0148f36fd | ||
|
|
b88de0cd3b | ||
|
|
3207efd383 | ||
|
|
31dd235b50 | ||
|
|
bb1cdb17f5 | ||
|
|
f35287cb7a | ||
|
|
391f7d3be4 | ||
|
|
76d0583b00 | ||
|
|
7857568c92 | ||
|
|
bfe94bc5d0 | ||
|
|
cf3bcf6d7f | ||
|
|
d2aa775ee8 | ||
|
|
ce800ccd92 | ||
|
|
2c57453608 | ||
|
|
03f729602b | ||
|
|
21f064b1d0 | ||
|
|
b7f6b827e4 | ||
|
|
a33e2ed226 | ||
|
|
ef7806b509 | ||
|
|
e268cda1b5 | ||
|
|
3ae5e3c569 | ||
|
|
304c7519d1 | ||
|
|
a4a8a422a5 | ||
|
|
6700003dd2 | ||
|
|
93f99fcff1 | ||
|
|
a03f54902a | ||
|
|
4112d363dd | ||
|
|
3f1c6dd662 | ||
|
|
89b2da636f | ||
|
|
c010ae3a97 | ||
|
|
f619c8f49f | ||
|
|
a27a1a3fd1 | ||
|
|
f7470ace97 | ||
|
|
cb6b362e20 | ||
|
|
c6200f386a | ||
|
|
60423e4a28 | ||
|
|
f40410816e | ||
|
|
63a2ffc0b1 | ||
|
|
fad428b94b | ||
|
|
8b251ca350 | ||
|
|
05bd5cb964 | ||
|
|
76a6b77065 | ||
|
|
5049086062 | ||
|
|
f04dece44c | ||
|
|
621f38d856 | ||
|
|
ccc65aa420 | ||
|
|
7ddf35d3fa | ||
|
|
689c65c5e4 | ||
|
|
e571f3bcfd | ||
|
|
adf80c3f87 | ||
|
|
fed9862e44 | ||
|
|
7bc924b6d6 | ||
|
|
c881624801 | ||
|
|
3c63993db8 | ||
|
|
ac26c3ab4e | ||
|
|
2dedd9d1a0 | ||
|
|
c1ef8b6399 | ||
|
|
c2f2f7b009 | ||
|
|
c2c5afbb05 | ||
|
|
19eae2300f | ||
|
|
0ddd2941e9 | ||
|
|
6863d70f0b | ||
|
|
9f782a4aed | ||
|
|
bc844ba0d3 | ||
|
|
af97876794 | ||
|
|
92f319c188 | ||
|
|
010872fc1e | ||
|
|
259d8356b0 | ||
|
|
4cd4bb4202 | ||
|
|
fdb19b1a90 | ||
|
|
c9385632a9 | ||
|
|
0c90b5c7b2 | ||
|
|
ac4650632e | ||
|
|
aae644ca33 | ||
|
|
6aabb83204 | ||
|
|
593f7bfe27 | ||
|
|
77d1bf5ca8 | ||
|
|
bc8f742565 | ||
|
|
632c48e1e6 | ||
|
|
42b2f99c6d | ||
|
|
e518e3ca92 | ||
|
|
e013cf70db | ||
|
|
bdf80fd920 | ||
|
|
ce282c47cf | ||
|
|
23a25b8df2 | ||
|
|
8363da35c6 | ||
|
|
ff973a0634 | ||
|
|
2b504149f8 | ||
|
|
031df8de2c | ||
|
|
229d3acc99 | ||
|
|
195bdd71a2 | ||
|
|
f6da7c9344 | ||
|
|
50ebdeddad | ||
|
|
4f4974aae4 | ||
|
|
977b7d2936 | ||
|
|
c4797a2d6f | ||
|
|
e293050ea1 | ||
|
|
4c2ed0ee3d | ||
|
|
c550faa843 | ||
|
|
e433150a5a | ||
|
|
ceb3626c94 | ||
|
|
2d33f745ba | ||
|
|
4cf0a71a45 | ||
|
|
7f526cea76 | ||
|
|
6e39107d45 | ||
|
|
9ddfced969 | ||
|
|
dde6b2cd8b | ||
|
|
a9894902be | ||
|
|
62c668b32c | ||
|
|
f48c8fb50a | ||
|
|
0ac7c4b6bf | ||
|
|
5bdf22764f | ||
|
|
f48a05e247 | ||
|
|
dc0b7cd3d2 | ||
|
|
d40a71757b | ||
|
|
bd702b4c21 | ||
|
|
b843167d6b | ||
|
|
be7517def5 | ||
|
|
b3f085c27d | ||
|
|
c7647ef20b | ||
|
|
c56471fe73 | ||
|
|
c87279c350 | ||
|
|
ed4040c6d0 | ||
|
|
7345c75ac0 | ||
|
|
3b7bd3bb71 | ||
|
|
9f2ecfadf7 | ||
|
|
f7fa6f114d | ||
|
|
8c99758a7a | ||
|
|
9614932e07 | ||
|
|
b8715d9730 | ||
|
|
c9563ac6a3 | ||
|
|
0b20bf1909 | ||
|
|
45d39df057 | ||
|
|
b7ffd3c721 | ||
|
|
70f15de701 | ||
|
|
4446770dda | ||
|
|
e2389db483 | ||
|
|
7be42080da | ||
|
|
22e550ab40 | ||
|
|
c515996adc | ||
|
|
fb3ee1187d | ||
|
|
8cbba11955 | ||
|
|
00971dee24 | ||
|
|
e8b7b40535 | ||
|
|
15113d0acb | ||
|
|
5bd79dc072 | ||
|
|
0f75f6b1e1 | ||
|
|
33229f0f26 | ||
|
|
5bbedb40fc | ||
|
|
d1164a14c1 | ||
|
|
ce4b227328 | ||
|
|
5b2de3795f | ||
|
|
3b25cc1a79 | ||
|
|
a6e951a4dc | ||
|
|
ea734567ea | ||
|
|
d32ec013dd | ||
|
|
b568ad8f56 | ||
|
|
04541dac38 | ||
|
|
c8287d12fa | ||
|
|
8fdf517613 | ||
|
|
0725a4729a | ||
|
|
a67b1f7350 | ||
|
|
a7abd91868 | ||
|
|
7d9ea51ea5 | ||
|
|
f284147b31 | ||
|
|
0f71e1e795 | ||
|
|
14780c3bb1 | ||
|
|
1409b7284b | ||
|
|
c7e3b4d026 | ||
|
|
139d9f8531 | ||
|
|
b9a569b71f | ||
|
|
8991af149e | ||
|
|
fccebb83c2 | ||
|
|
dbfd2373fe | ||
|
|
2fda197eff | ||
|
|
fbf09a7741 | ||
|
|
45e26c0936 | ||
|
|
92159d1df8 | ||
|
|
3ba491d02c | ||
|
|
b3047f7156 | ||
|
|
ed818b55de | ||
|
|
2a308d2a08 | ||
|
|
8fa8b55c7e | ||
|
|
08fc3918a7 | ||
|
|
32162532d5 | ||
|
|
d012c8ed2d | ||
|
|
e66b89f08c | ||
|
|
94d019982b | ||
|
|
29d2eed5fb | ||
|
|
50940da168 | ||
|
|
d1060940f0 | ||
|
|
a05df55b15 | ||
|
|
d5e71072c0 | ||
|
|
099589da90 | ||
|
|
8784372d41 | ||
|
|
f2f00bb09b | ||
|
|
7262fbac55 | ||
|
|
bb0635c36f | ||
|
|
a66ce64171 | ||
|
|
ea4690db3f | ||
|
|
47859d496e | ||
|
|
9d6cc2cbcc | ||
|
|
0b7d2e6d7e | ||
|
|
99783a6ab9 | ||
|
|
1a5102e47b | ||
|
|
dd6986e5ff | ||
|
|
05bea0bf50 | ||
|
|
96e8ab4610 | ||
|
|
5626f56595 | ||
|
|
614b126939 | ||
|
|
d38a0973c6 | ||
|
|
a910568144 | ||
|
|
4a558ca89d | ||
|
|
a45c31b952 | ||
|
|
2abc5d82da | ||
|
|
1aec50ca65 | ||
|
|
3c24dcc5aa | ||
|
|
d3265baa4e | ||
|
|
50cdf01fae | ||
|
|
36688c9451 | ||
|
|
cbfd6936e6 | ||
|
|
2d3e452baf | ||
|
|
6879a2d54c | ||
|
|
a2aaa2ce79 | ||
|
|
088075888e | ||
|
|
16ce10a5c7 | ||
|
|
87d16e36cb | ||
|
|
19fc443518 | ||
|
|
376c436318 | ||
|
|
edb102b1dd | ||
|
|
dc28ba5d1a | ||
|
|
91f43c1613 | ||
|
|
318319731d | ||
|
|
4b46ea241c | ||
|
|
2a66f6b441 | ||
|
|
60bbb32833 | ||
|
|
31063c5749 | ||
|
|
210c66de21 | ||
|
|
45525af846 | ||
|
|
2c247a4529 | ||
|
|
2fac370741 | ||
|
|
454b3a61e7 | ||
|
|
376cc0c095 | ||
|
|
21b1323d29 | ||
|
|
fb549701ff | ||
|
|
d4cb85174c | ||
|
|
d596dd98dd | ||
|
|
16ba82b795 | ||
|
|
78279e4d7a | ||
|
|
699fade3d1 | ||
|
|
fdd94633a8 | ||
|
|
2f1e0c6be2 | ||
|
|
d0cded11fb | ||
|
|
8cd962e0f3 | ||
|
|
9bb7d0c37b | ||
|
|
f41ba3172b | ||
|
|
aecdbaaa27 | ||
|
|
e572015913 | ||
|
|
e11c426c38 | ||
|
|
e2aff43963 | ||
|
|
7551d0c69a | ||
|
|
7ac6e436fa | ||
|
|
10ca2ef3fd | ||
|
|
04e197f9cc | ||
|
|
f2638f17c3 | ||
|
|
051a7e66f6 | ||
|
|
ff1db8ece8 | ||
|
|
3bfe32eda1 | ||
|
|
588bd0168c | ||
|
|
e81980b6c0 | ||
|
|
e33693f224 | ||
|
|
74747844e4 | ||
|
|
9b2221f702 | ||
|
|
cac8503c72 | ||
|
|
7c37216d6a | ||
|
|
359c33dae5 | ||
|
|
a9b517386c | ||
|
|
7202766cb5 | ||
|
|
9bd62558c1 | ||
|
|
ac8887129c | ||
|
|
2d4eb6d364 | ||
|
|
046a50ffcd | ||
|
|
fc863c560f | ||
|
|
9928b75d0d | ||
|
|
a54d116be3 | ||
|
|
7046c104d6 | ||
|
|
bb0ee34082 | ||
|
|
8950e6661f | ||
|
|
112dd6f4ae | ||
|
|
9426b42a33 | ||
|
|
e6bf5d9ea5 | ||
|
|
ce9e955f21 | ||
|
|
5025a430b6 | ||
|
|
458f6cb55d | ||
|
|
b379b38fff | ||
|
|
0711875200 | ||
|
|
767ac573af | ||
|
|
c96bacc320 | ||
|
|
8aec9f7c15 | ||
|
|
a66fede72d | ||
|
|
5586c1923c | ||
|
|
01467e1b32 | ||
|
|
6be04d6406 | ||
|
|
a7176c4a6e | ||
|
|
67484a1a90 | ||
|
|
783d148551 | ||
|
|
5134b28bad | ||
|
|
c10882f290 | ||
|
|
19bbfd0a50 | ||
|
|
3f6b5e35de | ||
|
|
002902c9d9 | ||
|
|
d93e17cff4 | ||
|
|
9fa92b504f | ||
|
|
74d25d240a | ||
|
|
a136241952 | ||
|
|
c89f683b8c | ||
|
|
45e79f88a3 | ||
|
|
bdd0c1f4c7 | ||
|
|
2bfcd88bfd | ||
|
|
d18f0bf751 | ||
|
|
3d983d8377 | ||
|
|
6c912d126a | ||
|
|
48b02b0f05 | ||
|
|
523b6376bb | ||
|
|
c276457390 | ||
|
|
d983a908e4 | ||
|
|
538fa23b9d | ||
|
|
d2bfacad4e | ||
|
|
cc68065f94 | ||
|
|
00f6738b7e | ||
|
|
9310c3f12f | ||
|
|
0fc1f5492f | ||
|
|
4562de083d | ||
|
|
7250a3ecc1 | ||
|
|
ac1874795b | ||
|
|
2fb8fb62c6 | ||
|
|
fd9816c177 | ||
|
|
5a6e32fce7 | ||
|
|
774850f30d | ||
|
|
41f4d269b0 | ||
|
|
18e79ce4fe | ||
|
|
92ce0aa816 | ||
|
|
5ca9180033 | ||
|
|
1856d1f8c7 | ||
|
|
af9810d1b3 | ||
|
|
a454312bd2 | ||
|
|
88063c2a60 | ||
|
|
8c010fbd3c | ||
|
|
7f615f19f7 | ||
|
|
5f7b70748b | ||
|
|
79268130f4 | ||
|
|
8b78b4997e | ||
|
|
e224841650 | ||
|
|
a69e5fa115 | ||
|
|
539b7931e6 | ||
|
|
f303c1243f | ||
|
|
347d8ecebe | ||
|
|
f977092115 | ||
|
|
5430792687 | ||
|
|
e591212166 | ||
|
|
77f19974f5 | ||
|
|
8b34e7dc19 | ||
|
|
3d4fb6a7d4 | ||
|
|
af7d3ccf83 | ||
|
|
a8b6841923 | ||
|
|
e09ab139c8 | ||
|
|
fe6a0a27b3 | ||
|
|
2f4c32e682 | ||
|
|
fdf2884f97 | ||
|
|
8d8129b49e | ||
|
|
a811ee67cc | ||
|
|
f9bfed2b22 | ||
|
|
3991f51cdf | ||
|
|
0902840aa4 | ||
|
|
df475af2fd | ||
|
|
9fb56df9a0 | ||
|
|
b11b39752f | ||
|
|
8cd361ca41 | ||
|
|
4c04dfc747 | ||
|
|
6cc3d15ee7 | ||
|
|
c0bf787dc7 | ||
|
|
4452cf3d1b | ||
|
|
9fa97ad46b | ||
|
|
6c3e1d83a7 | ||
|
|
9f2d1b48e6 | ||
|
|
fd7fd2fb0c | ||
|
|
a58ea17867 | ||
|
|
093e101f1b | ||
|
|
93dd673639 | ||
|
|
71b25190b8 | ||
|
|
1d90cecc11 | ||
|
|
e771393489 | ||
|
|
122107d1f8 | ||
|
|
ebd81a0dc8 | ||
|
|
69282e4423 | ||
|
|
ce71ac352e | ||
|
|
7738e052e1 | ||
|
|
f43e047497 | ||
|
|
9eb06da9a9 | ||
|
|
ce4a9a02d4 | ||
|
|
a8a82e1a3c | ||
|
|
5abc9b320b | ||
|
|
0aba432688 | ||
|
|
d80429a0ea | ||
|
|
d9145b0ebc | ||
|
|
c05252a570 | ||
|
|
acd81eb7c3 | ||
|
|
a3c58fcc5a | ||
|
|
16b541a8ee | ||
|
|
d705530e64 | ||
|
|
84873484d5 | ||
|
|
eb1883854e | ||
|
|
9cb83ba368 | ||
|
|
82bc6fb308 | ||
|
|
5aa95c0b7e | ||
|
|
419b46afb0 | ||
|
|
0b607c5197 | ||
|
|
9da1ec836e | ||
|
|
5639941ff3 | ||
|
|
9d59db5adc | ||
|
|
033f43b82f | ||
|
|
91f4dc1a79 | ||
|
|
9fe24272e3 | ||
|
|
b156c8752a | ||
|
|
93c51584db | ||
|
|
f36f31dfcf | ||
|
|
5cef486981 | ||
|
|
5db100ae49 | ||
|
|
46bcd1b095 | ||
|
|
1fc668f0fc | ||
|
|
5589b48e01 | ||
|
|
cc28f85fde | ||
|
|
a6d46ebcd7 | ||
|
|
ac3b0ba3ec | ||
|
|
e4ff2ea39d | ||
|
|
4653df1350 | ||
|
|
e3c5234038 | ||
|
|
4f0e1ea8b0 | ||
|
|
eaa05ee252 | ||
|
|
0c4d0b5646 | ||
|
|
00ac1da29d | ||
|
|
6ac126867e | ||
|
|
ea7ddeaf55 | ||
|
|
f536dbfc20 | ||
|
|
7261cea92f | ||
|
|
41d9b52005 | ||
|
|
50529ae711 | ||
|
|
1bfa2f31e7 | ||
|
|
2b8e3d16a3 | ||
|
|
c1e9cc2f02 | ||
|
|
a39656d536 | ||
|
|
103369cc64 | ||
|
|
0ab39a9b85 | ||
|
|
af582f88fc | ||
|
|
cccd084a55 | ||
|
|
bde5e65d10 | ||
|
|
874bfe47b8 | ||
|
|
31c12c9c0d | ||
|
|
ed8903ece3 | ||
|
|
5c92069e52 | ||
|
|
79229f6b35 | ||
|
|
f9272887f5 | ||
|
|
aa9af76f02 | ||
|
|
967202cbc3 | ||
|
|
a7007eabfa | ||
|
|
0e75f999cc | ||
|
|
4e3b5039da | ||
|
|
7a2af8e63e | ||
|
|
3fe18a9213 | ||
|
|
bfa9cc294e | ||
|
|
81c4ecaabd | ||
|
|
2f3f4a1137 | ||
|
|
c50cba64bb | ||
|
|
5b2aa6ead9 | ||
|
|
6a3f6b9be2 | ||
|
|
61169d868c | ||
|
|
72023fcd4a | ||
|
|
7c160b9b08 | ||
|
|
e8e90892aa | ||
|
|
16ad95c205 | ||
|
|
d509ed0a34 | ||
|
|
6d0539c793 | ||
|
|
260f200d6f | ||
|
|
2d95b3bb09 | ||
|
|
0276ce94df | ||
|
|
978dc571bb | ||
|
|
059189ec77 | ||
|
|
1af0e7068e | ||
|
|
c8d6bfa455 | ||
|
|
1ad80acd72 | ||
|
|
e19b15cacd | ||
|
|
2178971e5e | ||
|
|
ffecd8bb87 | ||
|
|
4326e059a5 | ||
|
|
adaadf3c60 | ||
|
|
c4d590674d | ||
|
|
a86159a02c | ||
|
|
88c9436df9 | ||
|
|
b26d938147 | ||
|
|
97808a8b6a | ||
|
|
bf39adbda7 | ||
|
|
ec82760cbf | ||
|
|
3f6901c036 | ||
|
|
6d27da4f9b | ||
|
|
ee623596a8 | ||
|
|
9df91e482e | ||
|
|
5739e9729f | ||
|
|
eba77a8028 | ||
|
|
82533c17e6 | ||
|
|
fd64fd0822 | ||
|
|
04fb7326e5 | ||
|
|
1d8f41df35 | ||
|
|
1b01dff466 | ||
|
|
e2021d1075 | ||
|
|
75d0b687c1 | ||
|
|
d0e6b23637 | ||
|
|
e9f4c7d9a5 | ||
|
|
a3b76252d1 | ||
|
|
231e278c76 | ||
|
|
5f41beccde | ||
|
|
a44193db16 | ||
|
|
c6dcb22d56 | ||
|
|
ff3ebb1f17 | ||
|
|
8e489f6f09 | ||
|
|
618a461835 | ||
|
|
281eb14178 | ||
|
|
1cbb39a38b | ||
|
|
b50ec3fedd | ||
|
|
5f03dfa8ed | ||
|
|
fa92ec0141 | ||
|
|
193eda633c | ||
|
|
da0fafa800 | ||
|
|
bfb5a74197 | ||
|
|
cceaeb8d42 | ||
|
|
2a43983ede | ||
|
|
cd5707f2d2 | ||
|
|
5667f8b023 | ||
|
|
09248f2917 | ||
|
|
162409053f | ||
|
|
528f85642f | ||
|
|
9d1b50ffbb | ||
|
|
95a806c8f8 | ||
|
|
7a38ac0810 | ||
|
|
0fec4c003b | ||
|
|
80d08eace7 | ||
|
|
e7568bf8d0 | ||
|
|
353f69deb3 | ||
|
|
e2917e36f8 | ||
|
|
8ddfa571d3 | ||
|
|
393c8cab2a | ||
|
|
cb03c7ab0d | ||
|
|
bce0797c55 | ||
|
|
d234b8e212 | ||
|
|
481e61b08b | ||
|
|
f8fe108db5 | ||
|
|
103abc5f6d | ||
|
|
f515438c62 | ||
|
|
470bac23bc | ||
|
|
123c75ae2d | ||
|
|
d05c049d4c | ||
|
|
c7ece89fea | ||
|
|
4c9e02d648 | ||
|
|
57ab99fb13 | ||
|
|
217db86b10 | ||
|
|
e5037eb69b | ||
|
|
8da3eb32f0 | ||
|
|
24d1d4774e | ||
|
|
b0f37079b0 | ||
|
|
026646cc46 | ||
|
|
258f55fe96 | ||
|
|
d2cd2a3772 | ||
|
|
c8db29ce46 | ||
|
|
8a4a810771 | ||
|
|
412216188d | ||
|
|
9103ea0c52 | ||
|
|
ea1c26caa3 | ||
|
|
0977d330e6 | ||
|
|
f7c2df0050 | ||
|
|
e690e50d86 | ||
|
|
0b0ce2eb27 | ||
|
|
cc81ea7e3e | ||
|
|
32dfc001af | ||
|
|
a0f5e35ad5 | ||
|
|
971508f613 | ||
|
|
144d64e0c8 | ||
|
|
a999046882 | ||
|
|
943921eadb | ||
|
|
0f87946276 | ||
|
|
9e032a7120 | ||
|
|
17e218bdb3 | ||
|
|
cb705f2d96 | ||
|
|
ac8483f894 | ||
|
|
3098ac9998 | ||
|
|
30246a8666 | ||
|
|
f606999c40 | ||
|
|
affa768efb | ||
|
|
c73e0d140d | ||
|
|
615c91b1c5 | ||
|
|
a0bc5bf765 | ||
|
|
2ab433c339 | ||
|
|
267fa23d51 | ||
|
|
e6565d9394 | ||
|
|
3b848e4a91 | ||
|
|
891751d09e | ||
|
|
15db488abc | ||
|
|
e40ea38dd6 | ||
|
|
bbc8f6b293 | ||
|
|
2c4a724fc0 | ||
|
|
848e3e473f | ||
|
|
263b7b3b1c | ||
|
|
48d7332e6a | ||
|
|
3568c25ef1 | ||
|
|
3948aa77a8 | ||
|
|
7ca98baa34 | ||
|
|
0d0c22c940 | ||
|
|
1ac4014d64 | ||
|
|
627646bf35 | ||
|
|
67a325e8e9 | ||
|
|
69e345f64f | ||
|
|
ac7d1459d6 | ||
|
|
f66bc37b1b | ||
|
|
37980d15e4 | ||
|
|
3fc6c47b5e | ||
|
|
dee24787ba | ||
|
|
7d9142fa03 | ||
|
|
51cca81bc8 | ||
|
|
8de6231116 | ||
|
|
e0ade6c658 | ||
|
|
8cbe0ec591 | ||
|
|
015490fc21 | ||
|
|
a8c5d70967 | ||
|
|
f95d6b11de | ||
|
|
a33bfd8cb0 | ||
|
|
188e7ddb75 | ||
|
|
d3d02a2c79 | ||
|
|
365f7bb596 | ||
|
|
a737cfc2ec | ||
|
|
6ce32e753d | ||
|
|
8f8e0b08a4 | ||
|
|
a702af2348 | ||
|
|
4b6be8e513 | ||
|
|
aa9d77cf8b | ||
|
|
bea19e008f | ||
|
|
4e3db05d10 | ||
|
|
9b3b463909 | ||
|
|
ad5d9eb3b7 | ||
|
|
3c1673d112 | ||
|
|
8f9780a443 | ||
|
|
2935d3cc0b | ||
|
|
fbc7ca4ef2 | ||
|
|
e3626fcbcd | ||
|
|
1e39c7d925 | ||
|
|
8a37278f79 | ||
|
|
c1638a2b9d | ||
|
|
aee8895f8d | ||
|
|
b442aa2dd6 | ||
|
|
65720df28d | ||
|
|
4a1989e3a8 | ||
|
|
a585430a5c | ||
|
|
c1073fec0c | ||
|
|
9367228800 | ||
|
|
6add01da97 | ||
|
|
74b838c1bc | ||
|
|
a5a97b5396 | ||
|
|
ca519dd5c4 | ||
|
|
7a622c6f5b | ||
|
|
b507815eef | ||
|
|
f3aec65624 | ||
|
|
447abb928b | ||
|
|
045fce62a6 | ||
|
|
c56efb9a10 | ||
|
|
eba9002400 | ||
|
|
7d5426144d | ||
|
|
ec3b5d6c73 | ||
|
|
3f0af44e8c | ||
|
|
ec8cbbb920 | ||
|
|
5e07b1ebfd | ||
|
|
ea227913da | ||
|
|
dd4a2aa288 | ||
|
|
48d8284124 | ||
|
|
fae64f5b7a | ||
|
|
9d0e917e60 | ||
|
|
a6483f0333 | ||
|
|
77172e7978 | ||
|
|
05b2296594 | ||
|
|
49244c3f0b | ||
|
|
ba9e813fbb | ||
|
|
cb4428068d | ||
|
|
ecb412ea6d | ||
|
|
3794d757f4 | ||
|
|
7561ace5e2 | ||
|
|
a85c2638e4 | ||
|
|
dcc4335f8e | ||
|
|
19b23746dc | ||
|
|
1295637355 | ||
|
|
620c204128 | ||
|
|
3952e5d73e | ||
|
|
1a2d528131 | ||
|
|
a42144c3f7 | ||
|
|
53cec9b620 | ||
|
|
e3da090768 | ||
|
|
285874caf5 | ||
|
|
b0bf847208 | ||
|
|
846c836cb8 | ||
|
|
fcedae6085 | ||
|
|
0c374eac23 | ||
|
|
e91b9c84bf | ||
|
|
9f2236dea3 | ||
|
|
8374d03a36 | ||
|
|
419bceb47a | ||
|
|
54de6f2ce5 | ||
|
|
5f8b37507e | ||
|
|
b716ca3270 | ||
|
|
fe73d829e5 | ||
|
|
36181dbce1 | ||
|
|
57f969c806 | ||
|
|
215c8da542 | ||
|
|
c1b2b5f56f | ||
|
|
41aadf25ed | ||
|
|
0e9417ab1b | ||
|
|
69ca8bc934 | ||
|
|
ae2b6e1f74 | ||
|
|
9ed16512fe | ||
|
|
ec8c3d9e60 | ||
|
|
0c0cc03394 | ||
|
|
fee9518134 | ||
|
|
7612cee9d6 | ||
|
|
558a7b9323 | ||
|
|
ab868a4c13 |
@@ -11,3 +11,8 @@ trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# 2 space indentation
|
||||
[*.yaml, *.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
4
.gitignore
vendored
Executable file → Normal file
4
.gitignore
vendored
Executable file → Normal file
@@ -1,5 +1,7 @@
|
||||
# Composer
|
||||
composer.lock
|
||||
.composer
|
||||
vendor/
|
||||
|
||||
# Sass
|
||||
.sass-cache
|
||||
@@ -7,6 +9,8 @@ composer.lock
|
||||
# Grav Specific
|
||||
cache/*
|
||||
!cache/.*
|
||||
assets/*
|
||||
!assets/.*
|
||||
logs/*
|
||||
!logs/.*
|
||||
images/*
|
||||
|
||||
65
.htaccess
Executable file → Normal file
65
.htaccess
Executable file → Normal file
@@ -1,29 +1,56 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# access site
|
||||
## Begin RewriteBase
|
||||
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
|
||||
# You should change the '/' to your appropriate subfolder. For example if you have
|
||||
# your Grav install at the root of your site '/' should work, else it might be something
|
||||
# along the lines of: RewriteBase /<your_sub_folder>
|
||||
##
|
||||
|
||||
# RewriteBase /
|
||||
|
||||
## End - RewriteBase
|
||||
|
||||
## Begin - Exploits
|
||||
# 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 base64_encode data within the URL.
|
||||
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
|
||||
# Block out any script that includes a <script> tag in URL.
|
||||
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
|
||||
# Block out any script trying to set a PHP GLOBALS variable via URL.
|
||||
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
|
||||
# Block out any script trying to modify a _REQUEST variable via URL.
|
||||
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
|
||||
# Return 403 Forbidden header and show the content of the root homepage
|
||||
RewriteRule .* index.php [F]
|
||||
#
|
||||
## End - Exploits
|
||||
|
||||
## Begin - Index
|
||||
# If the requested path and file is not /index.php and the request
|
||||
# has not already been internally rewritten to the index.php script
|
||||
RewriteCond %{REQUEST_URI} !^/index\.php
|
||||
# and the requested path and file doesn't directly match a physical file
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# and the requested path and file doesn't directly match a physical folder
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule . index.php [L]
|
||||
# internally rewrite the request to the index.php script
|
||||
RewriteRule .* index.php [L]
|
||||
## End - Index
|
||||
|
||||
# block various user files from being accessed directly
|
||||
RewriteRule ^user/accounts/(.*)$ error [R=301,L]
|
||||
RewriteRule ^user/config/(.*)$ error [R=301,L]
|
||||
RewriteRule ^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ error [R=301,L]
|
||||
|
||||
# block cache
|
||||
RewriteRule ^cache/(.*) error [R=301,L]
|
||||
|
||||
# block bin
|
||||
RewriteRule ^bin/(.*)$ error [R=301,L]
|
||||
|
||||
# block system
|
||||
RewriteRule ^system/(.*)$ error [R=301,L]
|
||||
|
||||
# block vendor
|
||||
RewriteRule ^vendor/(.*)$ error [R=301,L]
|
||||
## Begin - Security
|
||||
# Block all direct access for these folders
|
||||
RewriteRule ^(cache|bin|logs)/(.*) error [L]
|
||||
# Block access to specific file types for these folders
|
||||
RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L]
|
||||
## End - Security
|
||||
|
||||
</IfModule>
|
||||
|
||||
# Prevent file browsing
|
||||
# Begin - Prevent Browsing
|
||||
Options -Indexes
|
||||
# End - Prevent Browsing
|
||||
|
||||
338
CHANGELOG.md
Normal file
338
CHANGELOG.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# v0.9.16
|
||||
## 01/30/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added **Retina** and **Responsive** image support via Grav media and `srcset` image attribute
|
||||
* Added image debug option that overlays responsive resolution
|
||||
* Added a new image cache stream
|
||||
2. [](#improved)
|
||||
* Improved the markdown Lightbox functionality to better mimic Twig version
|
||||
* Fullsize Lightbox can now have filters applied
|
||||
* Added a new `mergeConfig()` method to Plugin class to merge system + page header configuration
|
||||
* Added a new `disable()` method to Plugin class to programatically disable a plugin
|
||||
* Updated Parsedown and Parsedown Extra to address bugs
|
||||
* Various PSR fixes
|
||||
3. [](#bugfix)
|
||||
* Fix bug with image dispatch in traditionally _non-routable_ pages
|
||||
* Fix for markdown link not working on non-current pages
|
||||
* Fix for markdown images not being found on homepage
|
||||
|
||||
# v0.9.15
|
||||
## 01/23/2015
|
||||
|
||||
3. [](#bugfix)
|
||||
* Typo in video mime types
|
||||
* Fix for old `markdown_extra` system setting not getting picked up
|
||||
* Fix in regex for Markdown links with numeric values in path
|
||||
* Fix for broken image routing mechanism that got broken at some point
|
||||
* Fix for markdown images/links in pages with page slug override
|
||||
|
||||
# v0.9.14
|
||||
## 01/23/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added **GZip** support
|
||||
* Added multiple configurations via `setup.php`
|
||||
* Added base structure for unit tests
|
||||
* New `onPageContentRaw()` plugin event that processes before any page processing
|
||||
* Added ability to dynamically set Metadata on page
|
||||
* Added ability to dynamically configure Markdown processing via Parsedown options
|
||||
2. [](#improved)
|
||||
* Refactored `page.content()` method to be more flexible and reliable
|
||||
* Various updates and fixes for streams resulting in better multi-site support
|
||||
* Updated Twig, Parsedown, ParsedownExtra, DoctrineCache libraries
|
||||
* Refactored Parsedown trait
|
||||
* Force modular pages to be non-visible in menus
|
||||
* Moved RewriteBase before Exploits in `.htaccess`
|
||||
* Added standard video formats to Media support
|
||||
* Added priority for inline assets
|
||||
* Check for uniqueness when adding multiple inline assets
|
||||
* Improved support for Twig-based URLs inside Markdown links and images
|
||||
* Improved Twig `url()` function
|
||||
3. [](#bugfix)
|
||||
* Fix for HTML entities quotes in Metadata values
|
||||
* Fix for `published` setting to have precedent of `publish_date` and `unpublish_date`
|
||||
* Fix for `onShutdown()` events not closing connections properly in **php-fpm** environments
|
||||
|
||||
# v0.9.13
|
||||
## 01/09/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added new published `true|false` state in page headers
|
||||
* Added `publish_date` in page headers to automatically publish page
|
||||
* Added `unpublish_date` in page headers to automatically unpublish page
|
||||
* Added `dateRange()` capability for collections
|
||||
* Added ability to dynamically control Cache lifetime programatically
|
||||
* Added ability to sort by anything in the page header. E.g. `sort: header.taxonomy.year`
|
||||
* Added various helper methods to collections: `copy, nonVisible, modular, nonModular, published, nonPublished, nonRoutable`
|
||||
2. [](#improved)
|
||||
* Modified all Collection methods so they can be chained together: `$collection->published()->visible()`
|
||||
* Set default Cache lifetime to default of 1 week (604800 seconds) - was infinite
|
||||
* House-cleaning of some unused methods in Pages object
|
||||
3. [](#bugfix)
|
||||
* Fix `uninstall` GPM command that was broken in last release
|
||||
* Fix for intermittent `undefined index` error when working with Collections
|
||||
* Fix for date of some pages being set to incorrect future timestamps
|
||||
|
||||
# v0.9.12
|
||||
## 01/06/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added an all-access robots.txt file for search engines
|
||||
* Added new GPM `uninstall` command
|
||||
* Added support for **in-page** Twig processing in **modular** pages
|
||||
* Added configurable support for `undefined` Twig functions and filters
|
||||
2. [](#improved)
|
||||
* Fall back to default `.html` template if error occurs on non-html pages
|
||||
* Added ability to have PSR-1 friendly plugin names (CamelCase, no-dashes)
|
||||
* Fix to `composer.json` to deter API rate-limit errors
|
||||
* Added **non-exception-throwing** handler for undefined methods on `Medium` objects
|
||||
3. [](#bugfix)
|
||||
* Fix description for `self-upgrade` method of GPM command
|
||||
* Fix for incorrect version number when performing GPM `update`
|
||||
* Fix for argument description of GPM `install` command
|
||||
* Fix for recalcitrant CodeKit mac application
|
||||
|
||||
# v0.9.11
|
||||
## 12/21/2014
|
||||
|
||||
1. [](#new)
|
||||
* Added support for simple redirects as well as routes
|
||||
2. [](#improved)
|
||||
* Handle Twig errors more cleanly
|
||||
3. [](#bugfix)
|
||||
* Fix for error caused by invalid or missing user agent string
|
||||
* Fix for directory relative links and URL fragments (#pagelink)
|
||||
* Fix for relative links with no subfolder in `base_url`
|
||||
|
||||
# v0.9.10
|
||||
## 12/12/2014
|
||||
|
||||
1. [](#new)
|
||||
* Added Facebook-style `nicetime` date Twig filter
|
||||
2. [](#improved)
|
||||
* Moved `clear-cache` functionality into Cache object required for Admin plugin
|
||||
3. [](#bugfix)
|
||||
* Fix for undefined index with previous/next buttons
|
||||
|
||||
# v0.9.9
|
||||
## 12/05/2014
|
||||
|
||||
1. [](#new)
|
||||
* Added new `@page` collection type
|
||||
* Added `ksort` and `contains` Twig filters
|
||||
* Added `gist` Twig function
|
||||
2. [](#improved)
|
||||
* Refactored Page previous/next/adjacent functionality
|
||||
* Updated to Symfony 2.6 for yaml/console/event-dispatcher libraries
|
||||
* More PSR code fixes
|
||||
3. [](#bugfix)
|
||||
* Fix for over-escaped apostrophes in YAML
|
||||
|
||||
# v0.9.8
|
||||
## 12/01/2014
|
||||
|
||||
1. [](#new)
|
||||
* Added configuration option to set default lifetime on cache saves
|
||||
* Added ability to set HTTP status code from page header
|
||||
* Implemented simple wild-card custom routing
|
||||
2. [](#improved)
|
||||
* Fixed elusive double load to fully cache issue (crossing fingers...)
|
||||
* Ensure Twig tags are treated as block items in markdown
|
||||
* Removed some older deprecated methods
|
||||
* Ensure onPageContentProcessed() event only fires when not cached
|
||||
* More PSR code fixes
|
||||
3. [](#bugfix)
|
||||
* Fix issue with miscalculation of blog separator location `===`
|
||||
|
||||
# v0.9.7
|
||||
## 11/24/2014
|
||||
|
||||
1. [](#improved)
|
||||
* Nginx configuration updated
|
||||
* Added gitter.im badge to README
|
||||
* Removed `set_time_limit()` and put checks around `ignore_user_abort`
|
||||
* More PSR code fixes
|
||||
2. [](#bugfix)
|
||||
* Fix issue with non-valid asset path showing up when they shouldn't
|
||||
* Fix for JS asset pipeline and scripts that don't end in `;`
|
||||
* Fix for schema-based markdown URLs broken routes (eg `mailto:`)
|
||||
|
||||
# v0.9.6
|
||||
## 11/17/2014
|
||||
|
||||
1. [](#improved)
|
||||
* Moved base_url variables into Grav container
|
||||
* Forced media sorting to use natural sort order by default
|
||||
* Various PSR code tidying
|
||||
* Added filename, extension, thumb to all medium objects
|
||||
2. [](#bugfix)
|
||||
* Fix for infinite loop in page.content()
|
||||
* Fix hostname for configuration overrides
|
||||
* Fix for cached configuration
|
||||
* Fix for relative URLs in markdown on installs with no base_url
|
||||
* Fix for page media images with uppercase extension
|
||||
|
||||
# v0.9.5
|
||||
## 11/09/2014
|
||||
|
||||
1. [](#new)
|
||||
* Added quality setting to medium for compression configuration of images
|
||||
* Added new onPageContentProcessed() event that is post-content processing but pre-caching
|
||||
2. [](#improved)
|
||||
* Added support for AND and OR taxonomy filtering. AND by default (was OR)
|
||||
* Added specific clearing options for CLI clear-cache command
|
||||
* Moved environment method to URI so it can be accessible in plugins and themes
|
||||
* Set Grav's output variable to public so it can be manipulated in onOutputGenerated event
|
||||
* Updated vendor libraries to latest versions
|
||||
* Better handing of 'home' in active menu state detection
|
||||
* Various PSR code tidying
|
||||
* Improved some error messages and notices
|
||||
3. [](#bugfix)
|
||||
* Force route rebuild when configuration changes
|
||||
* Fix for 'installed undefined' error in CLI versions command
|
||||
* Do not remove the JSON/Text error handlers
|
||||
* Fix for supporting inline JS and CSS when Asset pipeline enabled
|
||||
* Fix for Data URLs in CSS being badly formed
|
||||
* Fix Markdown links with fragment and query elements
|
||||
|
||||
# v0.9.4
|
||||
## 10/29/2014
|
||||
|
||||
1. [](#new)
|
||||
* New improved Debugbar with messages, timing, config, twig information
|
||||
* New exception handling system utilizing Whoops
|
||||
* New logging system utilizing Monolog
|
||||
* Support for auto-detecting environment configuration
|
||||
* New version command for CLI
|
||||
* Integrate Twig dump() calls into Debugbar
|
||||
2. [](#improved)
|
||||
* Selfupgrade now clears cache on successful upgrade
|
||||
* Selfupgrade now supports files without extensions
|
||||
* Improved error messages when plugin is missing
|
||||
* Improved security in .htaccess
|
||||
* Support CSS/JS/Image assets in vendor/system folders via .htaccess
|
||||
* Add support for system timers
|
||||
* Improved and optimized configuration loading
|
||||
* Automatically disable Debugbar on non-HTML pages
|
||||
* Disable Debugbar by default
|
||||
3. [](#bugfix)
|
||||
* More YAML blueprint fixes
|
||||
* Fix potential double // in assets
|
||||
* Load debugger as early as possible
|
||||
|
||||
# v0.9.3
|
||||
## 10/09/2014
|
||||
|
||||
1. [](#new)
|
||||
* GPM (Grav Package Manager) Added
|
||||
* Support for multiple Grav configurations
|
||||
* Dynamic media support via URL
|
||||
* Added inlineCss and inlineJs support for Assets
|
||||
2. [](#improved)
|
||||
* YAML caching for increased performance
|
||||
* Use stream wrapper in pages, plugins and themes
|
||||
* Switched to RocketTheme toolbox for some core functionality
|
||||
* Renamed `setup` CLI command to `sandbox`
|
||||
* Broke cache types out into multiple directories in the cache folder
|
||||
* Removed vendor libs from github repository
|
||||
* Various PSR cleanup of code
|
||||
* Various Blueprint updates to support upcoming Admin plugin
|
||||
* Added ability to filter page children for normal/modular/all
|
||||
* Added `sort_by_key` twig filter
|
||||
* Added `visible()` and `routable()` filters to page collections
|
||||
* Use session class in shutdown process
|
||||
* Improvements to modular page loading
|
||||
* Various code cleanup and optimizations
|
||||
3. [](#bugfix)
|
||||
* Fixed file checking not updating the last modified time. For real this time!
|
||||
* Switched debugger to PRODUCTION mode by default
|
||||
* Various fixes in URI class for increased reliability
|
||||
|
||||
# v0.9.2
|
||||
## 09/15/2014
|
||||
|
||||
1. [](#new)
|
||||
* New flexible site and page metadata support including ObjectGraph and Facebook
|
||||
* New method to get user IP address in URI object
|
||||
* Added new onShutdown() event that fires after connection is closed for Async features
|
||||
2. [](#improved)
|
||||
* Skip assets pipeline minify on Windows platforms by default due to PHP issue 47689
|
||||
* Fixed multiple level menus not highlighting correctly
|
||||
* Updated some blueprints in preparation for admin plugin
|
||||
* Fail gracefully when theme does not exist
|
||||
* Add stream support into ResourceLocator::addPath()
|
||||
* Separate themes from plugins, add themes:// stream and onTask events
|
||||
* Added barDump() to Debugger
|
||||
* Removed stray test page
|
||||
* Override modified only if a non-markdown file was modified
|
||||
* Added assets attributes support
|
||||
* Auto-run composer install when running the Grav CLI
|
||||
* Vendor folder removed from repository
|
||||
* Minor configuration performance optimizations
|
||||
* Minor debugger performance optimizations
|
||||
3. [](#bugfix)
|
||||
* Fix url() twig function when Grav isn't installed at root
|
||||
* Workaround for PHP bug 52065
|
||||
* Fixed getList() method on Pages object that was not working
|
||||
* Fix for open_basedir error
|
||||
* index.php now warns if not running on PHP 5.4
|
||||
* Removed memcached option (redundant)
|
||||
* Removed memcache from auto setup, added memcache server configuration option
|
||||
* Fix broken password validation
|
||||
* Back to proper PSR-4 Autoloader
|
||||
|
||||
# v0.9.1
|
||||
## 09/02/2014
|
||||
|
||||
1. [](#new)
|
||||
* Added new `theme://` PHP stream for current theme
|
||||
2. [](#improved)
|
||||
* Default to new `file` modification checking rather than `folder`
|
||||
* Added support for various markdown link formats to convert to Grav-friendly URLs
|
||||
* Moved configure() from Theme to Themes class
|
||||
* Fix autoloading without composer update -o
|
||||
* Added support for Twig url method
|
||||
* Minor code cleanup
|
||||
3. [](#bugfix)
|
||||
* Fixed issue with page changes not being picked up
|
||||
* Fixed Minify to provide `@supports` tag compatibility
|
||||
* Fixed ResourceLocator not working with multiple paths
|
||||
* Fixed issue with Markdown process not stripping LFs
|
||||
* Restrict file type extensions for added security
|
||||
* Fixed template inheritance
|
||||
* Moved Browser class to proper location
|
||||
|
||||
# v0.9.0
|
||||
## 08/25/2014
|
||||
|
||||
1. [](#new)
|
||||
* Addition of Dependency Injection Container
|
||||
* Refactored plugins to use Symfony Event Dispatcher
|
||||
* New Asset Manager to provide unified management of JavaScript and CSS
|
||||
* Asset Pipelining to provide unification, minify, and optimazation of JavaScript and CSS
|
||||
* Grav Media support directly in Markdown syntax
|
||||
* Additional Grav Generator meta tag in default themes
|
||||
* Added support for PHP Stream Wrapper for resource location
|
||||
* Markdown Extra support
|
||||
* Browser object for fast browser detection
|
||||
2. [](#improved)
|
||||
* PSR-4 Autoloader mechanism
|
||||
* Tracy Debugger new `detect` option to detect running environment
|
||||
* Added new `random` collection sort option
|
||||
* Make media images progressive by default
|
||||
* Additional URI filtering for improved security
|
||||
* Safety checks to ensure PHP 5.4.0+
|
||||
* Move to Slidebars side navigation in default Antimatter theme
|
||||
* Updates to `.htaccess` including section on `RewriteBase` which is needed for some hosting providers
|
||||
3. [](#bugfix)
|
||||
* Fixed issue when installing in an apache userdir (~username) folder
|
||||
* Various mobile CSS issues in default themes
|
||||
* Various minor bug fixes
|
||||
|
||||
|
||||
# v0.8.0
|
||||
## 08/13/2014
|
||||
|
||||
1. [](#new)
|
||||
* Initial Release
|
||||
55
README.md
55
README.md
@@ -1,13 +1,19 @@
|
||||
#  Grav
|
||||
[](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principals to other flat-file CMS platforms, but has a different design philosophy than most.
|
||||
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principals to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
|
||||
|
||||
The underlying architecture of Grav has been designed to use well-established and _best-in-class_ technologies, where applicable, to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
|
||||
|
||||
* [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface
|
||||
* [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation
|
||||
* [YAML](http://yaml.org): for simple configuration
|
||||
* [Doctrine Cache](http://docs.doctrine-project.org/en/2.0.x/reference/caching.html): layer for incredible performance
|
||||
* [Parsedown](http://parsedown.org/): for fast Markdown and Mardown Extra support
|
||||
* [Doctrine Cache](http://docs.doctrine-project.org/en/2.0.x/reference/caching.html): layer for performance
|
||||
* [Pimple Dependency Injection Container](http://pimple.sensiolabs.org/): for extensibility and maintainability
|
||||
* [Symfony Event Dispacher](http://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
|
||||
* [Symfony Console](http://symfony.com/doc/current/components/console/introduction.html): for CLI interface
|
||||
* [Gregwar Image Library](https://github.com/Gregwar/Image): for dynamic image manipulation
|
||||
|
||||
# QuickStart
|
||||
|
||||
@@ -33,6 +39,47 @@ You can download a **ready-built** package from the [Downloads page on http://ge
|
||||
|
||||
Check out the [install procedures](http://learn.getgrav.org/basics/installation) for more information.
|
||||
|
||||
# Adding Functionality
|
||||
|
||||
You can download manually from the [Downloads page on http://getgrav.org](http://getgrav.org/downloads), but the preferred solution is to use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
|
||||
|
||||
```
|
||||
$ bin/gpm index
|
||||
```
|
||||
|
||||
This will display all the available plugins and then you can install one ore more with:
|
||||
|
||||
```
|
||||
$ bin/gpm install <plugin/theme>
|
||||
```
|
||||
|
||||
# Updating
|
||||
|
||||
To update Grav you should use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
|
||||
|
||||
```
|
||||
$ bin/gpm selfupgrade
|
||||
```
|
||||
|
||||
To update plugins and themes:
|
||||
|
||||
```
|
||||
$ bin/gpm update
|
||||
```
|
||||
|
||||
|
||||
# Contributing
|
||||
We appreciate any contribution to Grav, whether it is related to bugs, grammar, or simply a suggestion or improvement.
|
||||
However, we ask that any contribution follow our simple guidelines in order to be properly received.
|
||||
|
||||
All our projects follow the [GitFlow branching model][gitflow-model], from development to release. If you are not familiar with it, there are several guides and tutorials to make you understand what it is about.
|
||||
|
||||
You will probably want to get started by installing [this very good collection of git extensions][gitflow-extensions].
|
||||
|
||||
What you mainly want to know is that:
|
||||
|
||||
- All the main activity happens in the `develop` branch. Any pull request should be addressed only to that branch. We will not consider pull requests made to the `master`.
|
||||
- It's very well appreciated, and highly suggested, to start a new feature whenever you want to make changes or add functionalities. It will make it much easier for us to just checkout your feature branch and test it, before merging it into `develop`
|
||||
|
||||
# Getting Started
|
||||
|
||||
@@ -51,3 +98,7 @@ Check out the [install procedures](http://learn.getgrav.org/basics/installation)
|
||||
# License
|
||||
|
||||
See [LICENSE](LICENSE)
|
||||
|
||||
|
||||
[gitflow-model]: http://nvie.com/posts/a-successful-git-branching-model/
|
||||
[gitflow-extensions]: https://github.com/nvie/gitflow
|
||||
|
||||
0
assets/.gitkeep
Normal file
0
assets/.gitkeep
Normal file
BIN
bin/composer.phar
Executable file
BIN
bin/composer.phar
Executable file
Binary file not shown.
45
bin/gpm
Executable file
45
bin/gpm
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
define('GRAV_CLI', true);
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
$autoload = require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav['config']->init();
|
||||
$grav['streams'];
|
||||
$grav['plugins']->init();
|
||||
$grav['themes']->init();
|
||||
|
||||
$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(),
|
||||
));
|
||||
$app->run();
|
||||
26
bin/grav
26
bin/grav
@@ -1,9 +1,21 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
define('GRAV_CLI', true);
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
require_once(__DIR__ . '/../system/autoload.php');
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
@@ -15,11 +27,11 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
|
||||
$app = new Application('Grav CLI Application', '0.1.0');
|
||||
$app->addCommands(array(
|
||||
new Grav\Console\InstallCommand(),
|
||||
new Grav\Console\SetupCommand(),
|
||||
new Grav\Console\CleanCommand(),
|
||||
new Grav\Console\ClearCacheCommand(),
|
||||
new Grav\Console\BackupCommand(),
|
||||
new Grav\Console\NewProjectCommand(),
|
||||
new Grav\Console\Cli\InstallCommand(),
|
||||
new Grav\Console\Cli\SandboxCommand(),
|
||||
new Grav\Console\Cli\CleanCommand(),
|
||||
new Grav\Console\Cli\ClearCacheCommand(),
|
||||
new Grav\Console\Cli\BackupCommand(),
|
||||
new Grav\Console\Cli\NewProjectCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
{
|
||||
"name": "rhuk/grav",
|
||||
"name": "getgrav/grav",
|
||||
"type": "library",
|
||||
"description": "Grav is a powerful flat CMS influenced by Pico, Stacey, Kirby and others...",
|
||||
"keywords": ["cms"],
|
||||
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
|
||||
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"],
|
||||
"homepage": "http://getgrav.org",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"twig/twig": "~1.16",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~2.6",
|
||||
"symfony/console": "~2.6",
|
||||
"symfony/event-dispatcher": "~2.6",
|
||||
"doctrine/cache": "~1.3",
|
||||
"maximebf/debugbar": "dev-master",
|
||||
"filp/whoops": "1.2.*@dev",
|
||||
"monolog/monolog": "~1.1",
|
||||
"gregwar/image": "~2.0",
|
||||
"ircmaxell/password-compat": "1.0.*",
|
||||
"mrclay/minify": "dev-master",
|
||||
"donatj/phpuseragentparser": "dev-master",
|
||||
"pimple/pimple": "~3.0",
|
||||
"rockettheme/toolbox": "dev-develop"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"name": "Andy Miller",
|
||||
"email": "rhuk@getgrav.org"
|
||||
"type": "vcs",
|
||||
"no-api": true,
|
||||
"url": "https://github.com/rockettheme/toolbox"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.10",
|
||||
"twig/twig": "1.16.*@dev",
|
||||
"erusev/parsedown": "dev-master",
|
||||
"symfony/yaml": "2.5.*@dev",
|
||||
"symfony/console": "2.5.*@dev",
|
||||
"doctrine/cache": "1.4.*@dev",
|
||||
"tracy/tracy": "dev-master",
|
||||
"gregwar/image": "dev-master",
|
||||
"ircmaxell/password-compat": "1.0.*"
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\": "system/src/Grav"
|
||||
},
|
||||
"files": ["system/defines.php"]
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["VERSION"]
|
||||
}
|
||||
}
|
||||
|
||||
56
htaccess.txt
Normal file
56
htaccess.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
## Begin RewriteBase
|
||||
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
|
||||
# You should change the '/' to your appropriate subfolder. For example if you have
|
||||
# your Grav install at the root of your site '/' should work, else it might be something
|
||||
# along the lines of: RewriteBase /<your_sub_folder>
|
||||
##
|
||||
|
||||
# RewriteBase /
|
||||
|
||||
## End - RewriteBase
|
||||
|
||||
## Begin - Exploits
|
||||
# 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 base64_encode data within the URL.
|
||||
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
|
||||
# Block out any script that includes a <script> tag in URL.
|
||||
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
|
||||
# Block out any script trying to set a PHP GLOBALS variable via URL.
|
||||
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
|
||||
# Block out any script trying to modify a _REQUEST variable via URL.
|
||||
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
|
||||
# Return 403 Forbidden header and show the content of the root homepage
|
||||
RewriteRule .* index.php [F]
|
||||
#
|
||||
## End - Exploits
|
||||
|
||||
## Begin - Index
|
||||
# If the requested path and file is not /index.php and the request
|
||||
# has not already been internally rewritten to the index.php script
|
||||
RewriteCond %{REQUEST_URI} !^/index\.php
|
||||
# and the requested path and file doesn't directly match a physical file
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# and the requested path and file doesn't directly match a physical folder
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# internally rewrite the request to the index.php script
|
||||
RewriteRule .* index.php [L]
|
||||
## End - Index
|
||||
|
||||
## Begin - Security
|
||||
# Block all direct access for these folders
|
||||
RewriteRule ^(cache|bin|logs)/(.*) error [L]
|
||||
# Block access to specific file types for these folders
|
||||
RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L]
|
||||
## End - Security
|
||||
|
||||
</IfModule>
|
||||
|
||||
# Begin - Prevent Browsing
|
||||
Options -Indexes
|
||||
# End - Prevent Browsing
|
||||
43
index.php
43
index.php
@@ -1,46 +1,35 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
namespace Grav;
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
throw new \RuntimeException(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
exit(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
}
|
||||
|
||||
use Tracy\Debugger;
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
exit('Please run: <i>bin/grav install</i>');
|
||||
}
|
||||
|
||||
// Register system libraries to the auto-loader.
|
||||
$loader = require_once __DIR__ . '/system/autoload.php';
|
||||
use Grav\Common\Grav;
|
||||
|
||||
// Register the auto-loader.
|
||||
$loader = require_once $autoload;
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
|
||||
// Start the timer and enable debugger in production mode as we do not have system configuration yet.
|
||||
// Debugger catches all errors and logs them, for example if the script doesn't have write permissions.
|
||||
Debugger::timer();
|
||||
Debugger::enable(Debugger::DEVELOPMENT, is_dir(LOG_DIR) ? LOG_DIR : null);
|
||||
|
||||
$grav = new Grav;
|
||||
$grav = Grav::instance(
|
||||
array(
|
||||
'loader' => $loader
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
// Register all the Grav bits into registry.
|
||||
$registry = Registry::instance();
|
||||
$registry->store('autoloader', $loader);
|
||||
$registry->store('Grav', $grav);
|
||||
$registry->store('Uri', new Uri);
|
||||
$registry->store('Config', Config::instance(CACHE_DIR . 'config.php'));
|
||||
$registry->store('Cache', new Cache);
|
||||
$registry->store('Twig', new Twig);
|
||||
$registry->store('Pages', new Page\Pages);
|
||||
$registry->store('Taxonomy', new Taxonomy);
|
||||
|
||||
$grav->process();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$grav->fireEvent('onFatalException', $e);
|
||||
$grav->fireEvent('onFatalException');
|
||||
throw $e;
|
||||
}
|
||||
|
||||
ob_end_flush();
|
||||
|
||||
32
nginx.conf
Executable file → Normal file
32
nginx.conf
Executable file → Normal file
@@ -27,26 +27,26 @@ http {
|
||||
}
|
||||
|
||||
location /user {
|
||||
rewrite ^/user/accounts/(.*)$ /error redirect;
|
||||
rewrite ^/user/config/(.*)$ /error redirect;
|
||||
rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
|
||||
}
|
||||
rewrite ^/user/accounts/(.*)$ /error redirect;
|
||||
rewrite ^/user/config/(.*)$ /error redirect;
|
||||
rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
|
||||
}
|
||||
|
||||
location /cache {
|
||||
rewrite ^/cache/(.*) /error redirect;
|
||||
}
|
||||
location /cache {
|
||||
rewrite ^/cache/(.*) /error redirect;
|
||||
}
|
||||
|
||||
location /bin {
|
||||
rewrite ^/bin/(.*)$ /error redirect;
|
||||
}
|
||||
location /bin {
|
||||
rewrite ^/bin/(.*)$ /error redirect;
|
||||
}
|
||||
|
||||
location /system {
|
||||
rewrite ^/system/(.*)$ /error redirect;
|
||||
}
|
||||
location /system {
|
||||
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
|
||||
}
|
||||
|
||||
location /vendor {
|
||||
rewrite ^/vendor/(.*)$ /error redirect;
|
||||
}
|
||||
location /vendor {
|
||||
rewrite ^/vendor/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
|
||||
}
|
||||
|
||||
# Remember to change 127.0.0.1:9000 to the Ip/port
|
||||
# you configured php-cgi.exe to run from
|
||||
|
||||
2
robots.txt
Normal file
2
robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
54
system/assets/debugger.css
Normal file
54
system/assets/debugger.css
Normal file
@@ -0,0 +1,54 @@
|
||||
div.phpdebugbar {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.phpdebugbar pre {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div > * {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
|
||||
background-image: url(grav.png);
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-restore-btn {
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
|
||||
background: #3DB9EC;
|
||||
color: #fff;
|
||||
margin-top: -1px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.phpdebugbar input[type=text] {
|
||||
padding: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar pre, .phpdebugbar code {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
BIN
system/assets/grav.png
Normal file
BIN
system/assets/grav.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 548 B |
BIN
system/assets/responsive-overlays/1x.png
Normal file
BIN
system/assets/responsive-overlays/1x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
system/assets/responsive-overlays/2x.png
Normal file
BIN
system/assets/responsive-overlays/2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
system/assets/responsive-overlays/3x.png
Normal file
BIN
system/assets/responsive-overlays/3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
system/assets/responsive-overlays/4x.png
Normal file
BIN
system/assets/responsive-overlays/4x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
system/assets/responsive-overlays/unknown.png
Normal file
BIN
system/assets/responsive-overlays/unknown.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
110
system/assets/whoops.css
Normal file
110
system/assets/whoops.css
Normal file
@@ -0,0 +1,110 @@
|
||||
body {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
body header {
|
||||
background: #349886;
|
||||
border-left: 8px solid #29796B;
|
||||
}
|
||||
|
||||
body .clipboard {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcAQMAAABIw03XAAAAA3NCSVQICAjb4U/gAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAFnRFWHRDcmVhdGlvbiBUaW1lADEwLzE1LzE0xr/LJAAAADhJREFUCJlj+P///wcGBPGDQR5E8OMi2IEEczOIaAQRHSCioQBGHAAR/7AT/z+DiA8MMALVXhABAJf9Sr5aY+UFAAAAAElFTkSuQmCC);
|
||||
}
|
||||
|
||||
body .exc-title-primary {
|
||||
color: #1C3631;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
body .exc-title {
|
||||
color: #2F5B52;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
body .data-table-container label {
|
||||
color: #0082BA;
|
||||
}
|
||||
|
||||
body .frame {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
body .frames-container {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body .active .frame-class {
|
||||
color: #E3D8E9;
|
||||
}
|
||||
|
||||
body .frame-class {
|
||||
color: #9055AF;
|
||||
}
|
||||
|
||||
body .frame.active {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
background-color: #9055AF;
|
||||
}
|
||||
|
||||
body .frame:not(.active):hover {
|
||||
background: #e9e9e9;
|
||||
}
|
||||
|
||||
body .frame-file, body .data-table tbody {
|
||||
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
body .frame-code {
|
||||
background: #305669;
|
||||
border-left: 8px solid #253A47;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
body .frame-code .frame-file {
|
||||
background: #253A47;
|
||||
color: #eee;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
body .frame-code .frame-file strong {
|
||||
color: #fff;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body .frame-comments {
|
||||
background: #283E4D;
|
||||
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
body .frame-comments.empty:before {
|
||||
color: #789AAB;
|
||||
}
|
||||
|
||||
body .details-container {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
body .details {
|
||||
background-color: #eee;
|
||||
border-left: 8px solid #ddd;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
body .code-block {
|
||||
background: #2C4454;
|
||||
box-shadow: none;
|
||||
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
body .handler.active {
|
||||
background: #666;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../system/defines.php');
|
||||
|
||||
// Use composer auto-loader and just add our namespace into it.
|
||||
$loader = require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
$loader->addPsr4('Grav\\', LIB_DIR . 'Grav');
|
||||
|
||||
return $loader;
|
||||
@@ -1,5 +1,5 @@
|
||||
title: Media
|
||||
validation: loose
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
70
system/blueprints/config/site.yaml
Normal file
70
system/blueprints/config/site.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
title: Site
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
content:
|
||||
type: section
|
||||
title: Defaults
|
||||
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Site Title
|
||||
size: large
|
||||
placeholder: "Site wide title"
|
||||
help: Default title for your site
|
||||
|
||||
author.name:
|
||||
type: text
|
||||
size: large
|
||||
label: Default Author
|
||||
|
||||
author.email:
|
||||
type: text
|
||||
size: large
|
||||
label: Default Email
|
||||
|
||||
taxonomies:
|
||||
type: text
|
||||
size: large
|
||||
label: Taxonomy Types
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
metadata:
|
||||
type: array
|
||||
label: Metadata
|
||||
placeholder_key: Name
|
||||
placeholder_value: Content
|
||||
|
||||
blog:
|
||||
type: section
|
||||
title: Blog
|
||||
|
||||
fields:
|
||||
blog.route:
|
||||
type: text
|
||||
size: large
|
||||
label: Blog URL
|
||||
|
||||
summary.size:
|
||||
type: text
|
||||
size: x-small
|
||||
label: Summary Size
|
||||
validate:
|
||||
type: int
|
||||
min: 0
|
||||
max: 65536
|
||||
|
||||
routes:
|
||||
type: section
|
||||
title: Routes
|
||||
|
||||
fields:
|
||||
routes:
|
||||
type: array
|
||||
label: Custom
|
||||
placeholder_key: /your/alias
|
||||
placeholder_value: /your/route
|
||||
7
system/blueprints/config/streams.yaml
Normal file
7
system/blueprints/config/streams.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
title: File Streams
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
schemes.xxx:
|
||||
type: array
|
||||
364
system/blueprints/config/system.yaml
Normal file
364
system/blueprints/config/system.yaml
Normal file
@@ -0,0 +1,364 @@
|
||||
title: System
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
content:
|
||||
type: section
|
||||
title: Content
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
home.alias:
|
||||
type: pages
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Home Page
|
||||
show_all: false
|
||||
show_modular: false
|
||||
show_root: false
|
||||
help: "The page that Grav will use as the default landing page"
|
||||
|
||||
pages.theme:
|
||||
type: themeselect
|
||||
classes: fancy
|
||||
size: medium
|
||||
label: Default Theme
|
||||
help: "Set the theme (defaults to 'default')"
|
||||
|
||||
pages.markdown_extra:
|
||||
type: toggle
|
||||
label: Markdown Extra
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.process:
|
||||
type: checkboxes
|
||||
label: Process
|
||||
default: [markdown: true, twig: true]
|
||||
options:
|
||||
markdown: Markdown
|
||||
twig: Twig
|
||||
use: keys
|
||||
|
||||
pages.dateformat.short:
|
||||
type: select
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Short Date Format
|
||||
help: "Set the short date format"
|
||||
default: 'jS M Y'
|
||||
options:
|
||||
'F jS \\a\\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
|
||||
pages.dateformat.long:
|
||||
type: select
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Long Date Format
|
||||
help: "Set the long date format"
|
||||
options:
|
||||
'F jS \a\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
|
||||
pages.order.by:
|
||||
type: select
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Default Ordering
|
||||
options:
|
||||
default: Default - based on folder name
|
||||
folder: Folder - based on prefix-less folder name
|
||||
title: Title - based on title field in header
|
||||
date: Date - based on date field in header
|
||||
|
||||
pages.order.dir:
|
||||
type: toggle
|
||||
label: Default Order Direction
|
||||
highlight: asc
|
||||
default: desc
|
||||
options:
|
||||
asc: Ascending
|
||||
desc: Descending
|
||||
|
||||
pages.list.count:
|
||||
type: text
|
||||
size: x-small
|
||||
label: Default Item Count
|
||||
help: "Default max pages count"
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
|
||||
|
||||
|
||||
events:
|
||||
type: section
|
||||
title: Events
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
pages.events.page:
|
||||
type: toggle
|
||||
label: Page events
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.events.twig:
|
||||
type: toggle
|
||||
label: Twig events
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
caching:
|
||||
type: section
|
||||
title: Caching
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
cache.enabled:
|
||||
type: toggle
|
||||
label: Caching
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
cache.check.method:
|
||||
type: select
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Cache Check Method
|
||||
options:
|
||||
file: File
|
||||
folder: Folder
|
||||
none: None
|
||||
|
||||
cache.driver:
|
||||
type: select
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Cache driver
|
||||
options:
|
||||
auto: Auto detect
|
||||
file: File
|
||||
apc: APC
|
||||
xcache: XCache
|
||||
memcache: MemCache
|
||||
wincache: WinCache
|
||||
|
||||
cache.prefix:
|
||||
type: text
|
||||
size: x-small
|
||||
label: Cache Prefix
|
||||
placeholder: "Derived from base URL (override by entering random string)"
|
||||
|
||||
twig:
|
||||
type: section
|
||||
title: Twig Templating
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
twig.cache:
|
||||
type: toggle
|
||||
label: Twig caching
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.debug:
|
||||
type: toggle
|
||||
label: Twig debug
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.auto_reload:
|
||||
type: toggle
|
||||
label: Detect changes
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.autoescape:
|
||||
type: toggle
|
||||
label: Autoescape variables
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets:
|
||||
type: section
|
||||
title: Assets
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
assets.css_pipeline:
|
||||
type: toggle
|
||||
label: CSS Pipeline
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.css_minify:
|
||||
type: toggle
|
||||
label: CSS Minify
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.css_minify_windows:
|
||||
type: toggle
|
||||
label: CSS Minify Windows Override
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.css_rewrite:
|
||||
type: toggle
|
||||
label: CSS Rewrite
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.js_pipeline:
|
||||
type: toggle
|
||||
label: JavaScript Pipeline
|
||||
highlight: 01
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.js_minify:
|
||||
type: toggle
|
||||
label: JavaScript Minify
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger:
|
||||
type: section
|
||||
title: Debugger
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
debugger.enabled:
|
||||
type: toggle
|
||||
label: Debugger
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.mode:
|
||||
type: select
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Mode
|
||||
options:
|
||||
detect: Auto-Detect
|
||||
development: Development
|
||||
production: Production
|
||||
|
||||
debugger.strict:
|
||||
type: toggle
|
||||
label: Strict
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.max_depth:
|
||||
type: select
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Detail Level
|
||||
placeholder: "How many nested levels to display for objects or arrays"
|
||||
options:
|
||||
1: 1 level
|
||||
2: 2 levels
|
||||
3: 3 levels
|
||||
4: 4 levels
|
||||
5: 5 levels
|
||||
6: 6 levels
|
||||
7: 7 levels
|
||||
8: 8 levels
|
||||
9: 9 levels
|
||||
10: 10 levels
|
||||
validate:
|
||||
type: number
|
||||
|
||||
debugger.log.enabled:
|
||||
type: toggle
|
||||
label: Logging
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.shutdown.close_connection:
|
||||
type: toggle
|
||||
label: Shutdown Close Connection
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
54
system/blueprints/pages/modular_new.yaml
Normal file
54
system/blueprints/pages/modular_new.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
section:
|
||||
type: section
|
||||
title: Add Modular Content
|
||||
|
||||
title:
|
||||
type: text
|
||||
label: Page Title
|
||||
validate:
|
||||
required: true
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder Name
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Page
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'': '- Select -'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
type:
|
||||
type: select
|
||||
classes: fancy
|
||||
label: Modular Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::modularTypes'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
modular:
|
||||
type: hidden
|
||||
default: 1
|
||||
validate:
|
||||
type: bool
|
||||
84
system/blueprints/pages/modular_raw.yaml
Normal file
84
system/blueprints/pages/modular_raw.yaml
Normal file
@@ -0,0 +1,84 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
content:
|
||||
type: tab
|
||||
title: Content
|
||||
|
||||
fields:
|
||||
frontmatter:
|
||||
type: frontmatter
|
||||
label: Frontmatter
|
||||
|
||||
|
||||
content:
|
||||
type: markdown
|
||||
label: Content
|
||||
|
||||
uploads:
|
||||
type: uploads
|
||||
label: Page Media
|
||||
|
||||
|
||||
options:
|
||||
type: tab
|
||||
title: Options
|
||||
|
||||
fields:
|
||||
|
||||
columns:
|
||||
type: columns
|
||||
|
||||
fields:
|
||||
column1:
|
||||
type: column
|
||||
|
||||
fields:
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Filename
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
type:
|
||||
type: select
|
||||
classes: fancy
|
||||
label: Modular Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::modularTypes'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
column2:
|
||||
type: column
|
||||
|
||||
fields:
|
||||
order:
|
||||
type: order
|
||||
label: Ordering
|
||||
|
||||
48
system/blueprints/pages/new.yaml
Normal file
48
system/blueprints/pages/new.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
section:
|
||||
type: section
|
||||
title: Add Page
|
||||
|
||||
title:
|
||||
type: text
|
||||
label: Page Title
|
||||
validate:
|
||||
required: true
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder Name
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Parent Page
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
type:
|
||||
type: select
|
||||
classes: fancy
|
||||
label: Display Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
validate:
|
||||
required: true
|
||||
@@ -5,14 +5,14 @@ rules:
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'': '- Root -'
|
||||
|
||||
title:
|
||||
type: text
|
||||
label: Title
|
||||
validate:
|
||||
required: true
|
||||
|
||||
folder:
|
||||
type: text
|
||||
@@ -21,8 +21,22 @@ form:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
type:
|
||||
type: select
|
||||
label: Page Type
|
||||
classes: fancy
|
||||
label: Display Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
validate:
|
||||
required: true
|
||||
84
system/blueprints/pages/raw.yaml
Normal file
84
system/blueprints/pages/raw.yaml
Normal file
@@ -0,0 +1,84 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
content:
|
||||
type: tab
|
||||
title: Content
|
||||
|
||||
fields:
|
||||
frontmatter:
|
||||
type: frontmatter
|
||||
label: Frontmatter
|
||||
|
||||
|
||||
content:
|
||||
type: markdown
|
||||
label: Content
|
||||
|
||||
uploads:
|
||||
type: uploads
|
||||
label: Page Media
|
||||
|
||||
|
||||
options:
|
||||
type: tab
|
||||
title: Options
|
||||
|
||||
fields:
|
||||
|
||||
columns:
|
||||
type: columns
|
||||
|
||||
fields:
|
||||
column1:
|
||||
type: column
|
||||
|
||||
fields:
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder Name
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
type:
|
||||
type: select
|
||||
classes: fancy
|
||||
label: Display Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
column2:
|
||||
type: column
|
||||
|
||||
fields:
|
||||
order:
|
||||
type: order
|
||||
label: Ordering
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
title: Site settings
|
||||
validation: strict
|
||||
|
||||
form:
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Site title
|
||||
|
||||
description:
|
||||
type: textarea
|
||||
label: Description
|
||||
|
||||
summary.size:
|
||||
type: text
|
||||
label: Summary size
|
||||
validate:
|
||||
type: int
|
||||
min: 0
|
||||
max: 65536
|
||||
|
||||
author.name:
|
||||
type: text
|
||||
label: Default author
|
||||
|
||||
author.email:
|
||||
type: text
|
||||
label: Default email
|
||||
|
||||
taxonomies:
|
||||
type: text
|
||||
label: Taxonomy types
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
blog.route:
|
||||
type: text
|
||||
label: Blog URL
|
||||
|
||||
routes:
|
||||
type: array
|
||||
label: Custom routes
|
||||
@@ -1,275 +0,0 @@
|
||||
title: Configuration
|
||||
validation: strict
|
||||
|
||||
form:
|
||||
fields:
|
||||
basics:
|
||||
type: section
|
||||
title: Basics
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Site Title
|
||||
placeholder: "Site wide title"
|
||||
help: Default title for your site
|
||||
|
||||
base_url_absolute:
|
||||
type: text
|
||||
label: Absolute Base URL
|
||||
placeholder: "Override Absolute base URL (e.g. http://example.com)"
|
||||
help: You can provide a base URL to use rather than letting Grav guess what it is
|
||||
|
||||
base_url_relative:
|
||||
type: text
|
||||
label: Relative Base URL
|
||||
placeholder: "Override Relative base URL (e.g. /subdirectory/site)"
|
||||
help: You can provide a base URL to use rather than letting Grav guess what it is
|
||||
|
||||
pages.dateformat.short:
|
||||
type: select
|
||||
label: PHP date format
|
||||
help: "Set the PHP date format"
|
||||
default: 'jS M Y'
|
||||
options:
|
||||
'F jS \\a\\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
|
||||
pages.dateformat.long:
|
||||
type: select
|
||||
label: Default date format
|
||||
help: "Set default date format rather than default for |date() filter"
|
||||
options:
|
||||
'F jS \a\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
|
||||
pages.theme:
|
||||
type: themeselect
|
||||
label: Default Theme
|
||||
help: "Set the theme (defaults to 'default')"
|
||||
|
||||
content:
|
||||
type: section
|
||||
title: Content
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
home.alias:
|
||||
type: pages
|
||||
label: Home page
|
||||
show_all: false
|
||||
show_modular: false
|
||||
show_root: false
|
||||
help: "The page that Grav will use as the default landing page"
|
||||
|
||||
pages.order.by:
|
||||
type: select
|
||||
label: Default ordering
|
||||
options:
|
||||
default: Default - based on folder name
|
||||
folder: Folder - based on prefix-less folder name
|
||||
title: Title - based on title field in header
|
||||
date: Date - based on date field in header
|
||||
|
||||
pages.order.dir:
|
||||
type: toggle
|
||||
label: Ordering direction
|
||||
default: asc
|
||||
options:
|
||||
asc: Ascending
|
||||
desc: Descending
|
||||
|
||||
pages.list.count:
|
||||
type: text
|
||||
label: Item count
|
||||
help: "Default max pages count"
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
|
||||
pages.process:
|
||||
type: checkboxes
|
||||
label: Process
|
||||
default: [markdown: true, twig: true]
|
||||
options:
|
||||
markdown: Markdown
|
||||
twig: Twig
|
||||
use: keys
|
||||
|
||||
events:
|
||||
type: section
|
||||
title: Events
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
pages.events.page:
|
||||
type: toggle
|
||||
label: Page events
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.events.twig:
|
||||
type: toggle
|
||||
label: Twig events
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
caching:
|
||||
type: section
|
||||
title: Caching
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
cache.enabled:
|
||||
type: toggle
|
||||
label: Caching
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
cache.check.pages:
|
||||
type: toggle
|
||||
label: Detect changes in pages
|
||||
help: Be careful changing this setting. If you disable this setting, Grav no longer automatically updates the pages when the underlaying files are changed.
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
cache.prefix:
|
||||
type: text
|
||||
label: Cache prefix
|
||||
placeholder: "Derived from base URL (override by entering random string)"
|
||||
|
||||
cache.driver:
|
||||
type: select
|
||||
label: Cache driver
|
||||
options:
|
||||
auto: Auto detect
|
||||
file: File
|
||||
apc: APC
|
||||
xcache: XCache
|
||||
memcache: MemCache
|
||||
memcached: MemCached
|
||||
wincache: WinCache
|
||||
|
||||
twig:
|
||||
type: section
|
||||
title: Twig Templating
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
twig.cache:
|
||||
type: toggle
|
||||
label: Twig caching
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.debug:
|
||||
type: toggle
|
||||
label: Twig debug
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.auto_reload:
|
||||
type: toggle
|
||||
label: Detect changes
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.autoescape:
|
||||
type: toggle
|
||||
label: Autoescape variables
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger:
|
||||
type: section
|
||||
title: Debugger
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
debugger.enabled:
|
||||
type: toggle
|
||||
label: Debugger
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.max_depth:
|
||||
type: select
|
||||
label: Detail levels
|
||||
placeholder: "How many nested levels to display for objects or arrays"
|
||||
options:
|
||||
1: 1 level
|
||||
2: 2 levels
|
||||
3: 3 levels
|
||||
4: 4 levels
|
||||
5: 5 levels
|
||||
6: 6 levels
|
||||
7: 7 levels
|
||||
8: 8 levels
|
||||
9: 9 levels
|
||||
10: 10 levels
|
||||
validate:
|
||||
type: number
|
||||
|
||||
debugger.log.enabled:
|
||||
type: toggle
|
||||
label: Logging
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.log.timing:
|
||||
type: toggle
|
||||
label: Log timings
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
86
system/blueprints/user/account.yaml
Normal file
86
system/blueprints/user/account.yaml
Normal file
@@ -0,0 +1,86 @@
|
||||
title: Site
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
content:
|
||||
type: section
|
||||
title: Account
|
||||
|
||||
fields:
|
||||
username:
|
||||
type: text
|
||||
size: large
|
||||
label: Username
|
||||
readonly: true
|
||||
|
||||
email:
|
||||
type: text
|
||||
size: large
|
||||
label: Email
|
||||
validate:
|
||||
required: true
|
||||
|
||||
password:
|
||||
type: password
|
||||
size: large
|
||||
label: Password
|
||||
validate:
|
||||
required: true
|
||||
|
||||
|
||||
|
||||
fullname:
|
||||
type: text
|
||||
size: large
|
||||
label: Full name
|
||||
validate:
|
||||
required: true
|
||||
|
||||
title:
|
||||
type: text
|
||||
size: large
|
||||
label: Title
|
||||
|
||||
admin:
|
||||
type: section
|
||||
title: Admin Access
|
||||
|
||||
fields:
|
||||
access.admin.super:
|
||||
type: toggle
|
||||
label: Super user
|
||||
default: 0
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
access.admin.login:
|
||||
type: toggle
|
||||
label: Admin login
|
||||
default: 0
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
site:
|
||||
type: section
|
||||
title: Site Access
|
||||
|
||||
fields:
|
||||
access.site.login:
|
||||
type: toggle
|
||||
label: Site login
|
||||
default: 1
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
15
system/blueprints/user/account_new.yaml
Normal file
15
system/blueprints/user/account_new.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
title: Add Account
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
content:
|
||||
type: section
|
||||
title: Add Account
|
||||
|
||||
username:
|
||||
type: text
|
||||
label: Username
|
||||
validate:
|
||||
required: true
|
||||
@@ -1,3 +1,12 @@
|
||||
defaults:
|
||||
type: file
|
||||
thumb: media/thumb.png
|
||||
mime: application/octet-stream
|
||||
image:
|
||||
filters:
|
||||
default:
|
||||
- enableProgressive
|
||||
|
||||
jpg:
|
||||
type: image
|
||||
thumb: media/thumb-jpg.png
|
||||
@@ -31,6 +40,31 @@ swf:
|
||||
type: video
|
||||
thumb: media/thumb-swf.png
|
||||
mime: video/x-flv
|
||||
flv:
|
||||
type: video
|
||||
thumb: media/thumb-flv.png
|
||||
mime: video/x-flv
|
||||
|
||||
mp3:
|
||||
type: audio
|
||||
thumb: media/thumb-mp3.png
|
||||
mime: audio/mp3
|
||||
ogg:
|
||||
type: audio
|
||||
thumb: media/thumb-ogg.png
|
||||
mime: audio/ogg
|
||||
wma:
|
||||
type: audio
|
||||
thumb: media/thumb-wma.png
|
||||
mime: audio/wma
|
||||
m4a:
|
||||
type: audio
|
||||
thumb: media/thumb-m4a.png
|
||||
mime: audio/m4a
|
||||
wav:
|
||||
type: audio
|
||||
thumb: media/thumb-wav.png
|
||||
mime: audio/wav
|
||||
|
||||
txt:
|
||||
type: file
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
title: Grav # Name of the site
|
||||
title: Grav # Name of the site
|
||||
author:
|
||||
name: John Appleseed # Default author name
|
||||
email: 'john@email.com' # Default author email
|
||||
taxonomies: [category,tag] # Arbitrary list of taxonomy types
|
||||
name: John Appleseed # Default author name
|
||||
email: 'john@email.com' # Default author email
|
||||
taxonomies: [category,tag] # Arbitrary list of taxonomy types
|
||||
blog:
|
||||
route: '/blog' # Route to blog
|
||||
description: 'Grav Site Description' # Site description
|
||||
route: '/blog' # Route to blog
|
||||
metadata:
|
||||
description: 'My Grav Site' # Site description
|
||||
summary:
|
||||
size: 300 # Maximum length of summary (characters)
|
||||
size: 300 # Maximum length of summary (characters)
|
||||
routes:
|
||||
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
|
||||
/another/one/here: '/blog/sample-3' # Another alias for /blog/sample-3
|
||||
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
|
||||
/another/one/here: '/blog/sample-3' # Another alias for /blog/sample-3
|
||||
/new/*: '/blog/*' # Wildcard any /new/my-page URL to /blog/my-page Route
|
||||
|
||||
20
system/config/streams.yaml
Normal file
20
system/config/streams.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
schemes:
|
||||
asset:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- assets
|
||||
|
||||
image:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://images
|
||||
|
||||
page:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://pages
|
||||
|
||||
account:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://accounts
|
||||
@@ -1,3 +1,5 @@
|
||||
absolute_urls: false # Absolute or relative URLs for `base_url`
|
||||
|
||||
home:
|
||||
alias: '/home' # Default path for home, ie /
|
||||
|
||||
@@ -11,29 +13,56 @@ pages:
|
||||
dateformat:
|
||||
short: 'jS M Y' # Short date format
|
||||
long: 'F jS \a\t g:ia' # Long date format
|
||||
publish_dates: true # automatically publish/unpublish based on dates
|
||||
process:
|
||||
markdown: true # Process Markdown
|
||||
twig: false # Process Twig
|
||||
events:
|
||||
page: false # Enable page level events
|
||||
page: true # Enable page level events
|
||||
twig: true # Enable twig level events
|
||||
markdown:
|
||||
extra: false # Enable support for Markdown Extra support (GFM by default)
|
||||
auto_line_breaks: false # Enable automatic line breaks
|
||||
auto_url_links: false # Enable automatic HTML links
|
||||
escape_markup: false # Escape markup tags into entities
|
||||
special_chars: # List of special characters to automatically convert to entities
|
||||
'>': 'gt'
|
||||
'<': 'lt'
|
||||
|
||||
cache:
|
||||
enabled: true # Set to true to enable caching
|
||||
check:
|
||||
pages: true # Check to see if page has been modifying to flush the cache
|
||||
driver: auto # One of: auto|file|apc|xcache|memcache|memcached|wincache
|
||||
method: file # Method to check for updates in pages: file|folder|none
|
||||
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
|
||||
prefix: 'g' # Cache prefix string (prevents cache conflicts)
|
||||
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
|
||||
gzip: false # GZip compress the page output
|
||||
|
||||
twig:
|
||||
cache: false # Set to true to enable twig caching
|
||||
debug: true # Enable Twig debug
|
||||
auto_reload: true # Refresh cache on changes
|
||||
autoescape: false # Autoescape Twig vars
|
||||
cache: true # Set to true to enable twig caching
|
||||
debug: false # Enable Twig debug
|
||||
auto_reload: true # Refresh cache on changes
|
||||
autoescape: false # Autoescape Twig vars
|
||||
undefined_functions: true # Allow undefined functions
|
||||
undefined_filters: true # Allow undefined filters
|
||||
|
||||
assets: # Configuration for Assets Manager (JS, CSS)
|
||||
css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
|
||||
css_minify: true # Minify the CSS during pipelining
|
||||
css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize
|
||||
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
|
||||
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
|
||||
js_minify: true # Minify the JS during pipelining
|
||||
|
||||
errors:
|
||||
display: true # Display full backtrace-style error page
|
||||
log: true # Log errors to /logs folder
|
||||
|
||||
debugger:
|
||||
enabled: true # Enable Grav debugger
|
||||
max_depth: 10 # How many nested levels to display for objects or arrays
|
||||
log:
|
||||
enabled: true # Enable logging
|
||||
timing: false # Enable timing logging
|
||||
enabled: false # Enable Grav debugger and following settings
|
||||
twig: true # Enable debugging of Twig templates
|
||||
shutdown:
|
||||
close_connection: true # Close the connection before calling onShutdown(). false for debugging
|
||||
|
||||
images:
|
||||
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
|
||||
|
||||
@@ -2,28 +2,31 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '0.8.0');
|
||||
define('GRAV_VERSION', '0.9.16');
|
||||
define('DS', '/');
|
||||
|
||||
// Directories and Paths
|
||||
if (!defined('ROOT_DIR')) {
|
||||
define('ROOT_DIR', getcwd() .'/');
|
||||
if (!defined('GRAV_ROOT')) {
|
||||
define('GRAV_ROOT', getcwd());
|
||||
}
|
||||
define('ROOT_DIR', GRAV_ROOT . '/');
|
||||
define('USER_PATH', 'user/');
|
||||
define('USER_DIR', ROOT_DIR . USER_PATH);
|
||||
define('SYSTEM_DIR', ROOT_DIR .'system/');
|
||||
define('CACHE_DIR', ROOT_DIR .'cache/');
|
||||
define('ASSETS_DIR', ROOT_DIR . 'assets/');
|
||||
define('CACHE_DIR', ROOT_DIR . 'cache/');
|
||||
define('IMAGES_DIR', ROOT_DIR . 'images/');
|
||||
define('LOG_DIR', ROOT_DIR .'logs/');
|
||||
define('VENDOR_DIR', ROOT_DIR .'vendor/');
|
||||
define('LIB_DIR', SYSTEM_DIR .'src/');
|
||||
define('ACCOUNTS_DIR', USER_DIR .'accounts/');
|
||||
define('DATA_DIR', USER_DIR .'data/');
|
||||
define('PAGES_DIR', USER_DIR .'pages/');
|
||||
define('BLOCKS_DIR', USER_DIR .'blocks/');
|
||||
|
||||
// DEPRECATED: Do not use!
|
||||
define('DATA_DIR', USER_DIR .'data/');
|
||||
define('LIB_DIR', SYSTEM_DIR .'src/');
|
||||
define('PLUGINS_DIR', USER_DIR .'plugins/');
|
||||
define('THEMES_DIR', USER_DIR .'themes/');
|
||||
define('VENDOR_DIR', ROOT_DIR .'vendor/');
|
||||
// END DEPRECATED
|
||||
|
||||
// Some extensions
|
||||
define('CONTENT_EXT', '.md');
|
||||
|
||||
904
system/src/Grav/Common/Assets.php
Normal file
904
system/src/Grav/Common/Assets.php
Normal file
@@ -0,0 +1,904 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use FilesystemIterator;
|
||||
use Grav\Common\Config\Config;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
|
||||
define('CSS_ASSET', true);
|
||||
define('JS_ASSET', false);
|
||||
|
||||
/**
|
||||
* Handles Asset management (CSS & JS) and also pipelining (combining into a single file for each asset)
|
||||
*
|
||||
* Based on stolz/assets (https://github.com/Stolz/Assets) package modified for use with Grav
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Assets
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/** @const Regex to match CSS and JavaScript files */
|
||||
const DEFAULT_REGEX = '/.\.(css|js)$/i';
|
||||
|
||||
/** @const Regex to match CSS files */
|
||||
const CSS_REGEX = '/.\.css$/i';
|
||||
|
||||
/** @const Regex to match JavaScript files */
|
||||
const JS_REGEX = '/.\.js$/i';
|
||||
|
||||
/** @const Regex to match CSS urls */
|
||||
const CSS_URL_REGEX = '{url\([\'\"]?((?!http|//).*?)[\'\"]?\)}';
|
||||
|
||||
/** @const Regex to match CSS sourcemap comments */
|
||||
const CSS_SOURCEMAP_REGEX = '{\/\*# (.*) \*\/}';
|
||||
|
||||
/** @const Regex to match CSS import content */
|
||||
const CSS_IMPORT_REGEX = '{@import(.*);}';
|
||||
|
||||
|
||||
/**
|
||||
* Closure used by the pipeline to fetch assets.
|
||||
*
|
||||
* Useful when file_get_contents() function is not available in your PHP
|
||||
* instalation or when you want to apply any kind of preprocessing to
|
||||
* your assets before they get pipelined.
|
||||
*
|
||||
* The closure will receive as the only parameter a string with the path/URL of the asset and
|
||||
* it should return the content of the asset file as a string.
|
||||
*
|
||||
* @var Closure
|
||||
*/
|
||||
protected $fetch_command;
|
||||
|
||||
// Configuration toggles to enable/disable the pipelining feature
|
||||
protected $css_pipeline = false;
|
||||
protected $js_pipeline = false;
|
||||
|
||||
// The asset holding arrays
|
||||
protected $collections = array();
|
||||
protected $css = array();
|
||||
protected $js = array();
|
||||
protected $inline_css = array();
|
||||
protected $inline_js = array();
|
||||
|
||||
// Some configuration variables
|
||||
protected $config;
|
||||
protected $base_url;
|
||||
|
||||
// Default values for pipeline settings
|
||||
protected $css_minify = true;
|
||||
protected $css_minify_windows = false;
|
||||
protected $css_rewrite = true;
|
||||
protected $js_minify = true;
|
||||
|
||||
// Arrays to hold assets that should NOT be pipelined
|
||||
protected $css_no_pipeline = array();
|
||||
protected $js_no_pipeline = array();
|
||||
|
||||
|
||||
public function __construct(array $options = array())
|
||||
{
|
||||
// Forward config options
|
||||
if ($options) {
|
||||
$this->config((array)$options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up configuration options.
|
||||
*
|
||||
* All the class properties except 'js' and 'css' are accepted here.
|
||||
* Also, an extra option 'autoload' may be passed containing an array of
|
||||
* assets and/or collections that will be automatically added on startup.
|
||||
*
|
||||
* @param array $config Configurable options.
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function config(array $config)
|
||||
{
|
||||
// Set pipeline modes
|
||||
if (isset($config['css_pipeline'])) {
|
||||
$this->css_pipeline = $config['css_pipeline'];
|
||||
}
|
||||
|
||||
if (isset($config['js_pipeline'])) {
|
||||
$this->js_pipeline = $config['js_pipeline'];
|
||||
}
|
||||
|
||||
// Pipeline requires public dir
|
||||
if (($this->js_pipeline || $this->css_pipeline) && !is_dir(ASSETS_DIR)) {
|
||||
throw new \Exception('Assets: Public dir not found');
|
||||
}
|
||||
|
||||
// Set custom pipeline fetch command
|
||||
if (isset($config['fetch_command']) and ($config['fetch_command'] instanceof Closure)) {
|
||||
$this->fetch_command = $config['fetch_command'];
|
||||
}
|
||||
|
||||
// Set CSS Minify state
|
||||
if (isset($config['css_minify'])) {
|
||||
$this->css_minify = $config['css_minify'];
|
||||
}
|
||||
|
||||
if (isset($config['css_minify_windows'])) {
|
||||
$this->css_minify_windows = $config['css_minify_windows'];
|
||||
}
|
||||
|
||||
if (isset($config['css_rewrite'])) {
|
||||
$this->css_rewrite = $config['css_rewrite'];
|
||||
}
|
||||
|
||||
// Set JS Minify state
|
||||
if (isset($config['js_minify'])) {
|
||||
$this->js_minify = $config['js_minify'];
|
||||
}
|
||||
|
||||
// Set collections
|
||||
if (isset($config['collections']) and is_array($config['collections'])) {
|
||||
$this->collections = $config['collections'];
|
||||
}
|
||||
|
||||
// Autoload assets
|
||||
if (isset($config['autoload']) and is_array($config['autoload'])) {
|
||||
foreach ($config['autoload'] as $asset) {
|
||||
$this->add($asset);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
$base_url = self::$grav['base_url'];
|
||||
$asset_config = (array)$config->get('system.assets');
|
||||
|
||||
$this->config($asset_config);
|
||||
$this->base_url = $base_url . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an asset or a collection of assets.
|
||||
*
|
||||
* It automatically detects the asset type (JavaScript, CSS or collection).
|
||||
* You may add more than one asset passing an array as argument.
|
||||
*
|
||||
* @param mixed $asset
|
||||
* @param int $priority the priority, bigger comes first
|
||||
* @param bool $pipeline false if this should not be pipelined
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function add($asset, $priority = 10, $pipeline = true)
|
||||
{
|
||||
// More than one asset
|
||||
if (is_array($asset)) {
|
||||
foreach ($asset as $a) {
|
||||
$this->add($a, $priority, $pipeline);
|
||||
}
|
||||
} elseif (isset($this->collections[$asset])) {
|
||||
$this->add($this->collections[$asset], $priority, $pipeline);
|
||||
} else {
|
||||
// Get extension
|
||||
$extension = pathinfo(parse_url($asset, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
|
||||
// JavaScript or CSS
|
||||
if (strlen($extension) > 0) {
|
||||
$extension = strtolower($extension);
|
||||
if ($extension === 'css') {
|
||||
$this->addCss($asset, $priority, $pipeline);
|
||||
} elseif ($extension === 'js') {
|
||||
$this->addJs($asset, $priority, $pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CSS asset.
|
||||
*
|
||||
* It checks for duplicates.
|
||||
* You may add more than one asset passing an array as argument.
|
||||
*
|
||||
* @param mixed $asset
|
||||
* @param int $priority the priority, bigger comes first
|
||||
* @param bool $pipeline false if this should not be pipelined
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCss($asset, $priority = 10, $pipeline = true)
|
||||
{
|
||||
if (is_array($asset)) {
|
||||
foreach ($asset as $a) {
|
||||
$this->addCss($a, $priority, $pipeline);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$this->isRemoteLink($asset)) {
|
||||
$asset = $this->buildLocalLink($asset);
|
||||
}
|
||||
|
||||
$key = md5($asset);
|
||||
if ($asset && !array_key_exists($key, $this->css)) {
|
||||
$this->css[$key] = [
|
||||
'asset' => $asset,
|
||||
'priority' => $priority,
|
||||
'order' => count($this->css),
|
||||
'pipeline' => $pipeline
|
||||
];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JavaScript asset.
|
||||
*
|
||||
* It checks for duplicates.
|
||||
* You may add more than one asset passing an array as argument.
|
||||
*
|
||||
* @param mixed $asset
|
||||
* @param int $priority the priority, bigger comes first
|
||||
* @param bool $pipeline false if this should not be pipelined
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addJs($asset, $priority = 10, $pipeline = true)
|
||||
{
|
||||
if (is_array($asset)) {
|
||||
foreach ($asset as $a) {
|
||||
$this->addJs($a, $priority, $pipeline);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$this->isRemoteLink($asset)) {
|
||||
$asset = $this->buildLocalLink($asset);
|
||||
}
|
||||
|
||||
$key = md5($asset);
|
||||
if ($asset && !array_key_exists($key, $this->js)) {
|
||||
$this->js[$key] = [
|
||||
'asset' => $asset,
|
||||
'priority' => $priority,
|
||||
'order' => count($this->js),
|
||||
'pipeline' => $pipeline
|
||||
];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an inline CSS asset.
|
||||
*
|
||||
* It checks for duplicates.
|
||||
* For adding chunks of string-based inline CSS
|
||||
*
|
||||
* @param mixed $asset
|
||||
* @param int $priority the priority, bigger comes first
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addInlineCss($asset, $priority = 10)
|
||||
{
|
||||
$key = md5($asset);
|
||||
if (is_string($asset) && !array_key_exists($key, $this->inline_css)) {
|
||||
$this->inline_css[$key] = [
|
||||
'priority' => $priority,
|
||||
'order' => count($this->inline_css),
|
||||
'asset' => $asset
|
||||
];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an inline JS asset.
|
||||
*
|
||||
* It checks for duplicates.
|
||||
* For adding chunks of string-based inline JS
|
||||
*
|
||||
* @param mixed $asset
|
||||
* @param int $priority the priority, bigger comes first
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addInlineJs($asset, $priority = 10)
|
||||
{
|
||||
$key = md5($asset);
|
||||
if (is_string($asset) && !array_key_exists($key, $this->inline_js)) {
|
||||
$this->inline_js[$key] = [
|
||||
'priority' => $priority,
|
||||
'order' => count($this->inline_js),
|
||||
'asset' => $asset
|
||||
];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the CSS link tags.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function css($attributes = [])
|
||||
{
|
||||
if (!$this->css) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort array by priorities (larger priority first)
|
||||
if (self::$grav) {
|
||||
usort($this->css, function ($a, $b) {
|
||||
if ($a['priority'] == $b['priority']) {
|
||||
return $b['order'] - $a['order'];
|
||||
}
|
||||
return $a['priority'] - $b['priority'];
|
||||
});
|
||||
|
||||
usort($this->inline_css, function ($a, $b) {
|
||||
if ($a['priority'] == $b['priority']) {
|
||||
return $b['order'] - $a['order'];
|
||||
}
|
||||
return $a['priority'] - $b['priority'];
|
||||
});
|
||||
}
|
||||
$this->css = array_reverse($this->css);
|
||||
$this->inline_css = array_reverse($this->inline_css);
|
||||
|
||||
$attributes = $this->attributes(array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes));
|
||||
|
||||
$output = '';
|
||||
if ($this->css_pipeline) {
|
||||
$output .= '<link href="' . $this->pipeline(CSS_ASSET) . '"' . $attributes . ' />' . "\n";
|
||||
|
||||
foreach ($this->css_no_pipeline as $file) {
|
||||
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
|
||||
}
|
||||
} else {
|
||||
foreach ($this->css as $file) {
|
||||
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Render Inline CSS
|
||||
if (count($this->inline_css) > 0) {
|
||||
$output .= "<style>\n";
|
||||
foreach ($this->inline_css as $inline) {
|
||||
$output .= $inline['asset'] . "\n";
|
||||
}
|
||||
$output .= "</style>\n";
|
||||
}
|
||||
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the JavaScript script tags.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function js($attributes = [])
|
||||
{
|
||||
if (!$this->js) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort array by priorities (larger priority first)
|
||||
usort($this->js, function ($a, $b) {
|
||||
if ($a['priority'] == $b['priority']) {
|
||||
return $b['order'] - $a['order'];
|
||||
}
|
||||
return $a['priority'] - $b['priority'];
|
||||
});
|
||||
|
||||
usort($this->inline_js, function ($a, $b) {
|
||||
if ($a['priority'] == $b['priority']) {
|
||||
return $b['order'] - $a['order'];
|
||||
}
|
||||
return $a['priority'] - $b['priority'];
|
||||
});
|
||||
|
||||
$this->js = array_reverse($this->js);
|
||||
$this->inline_js = array_reverse($this->inline_js);
|
||||
|
||||
$attributes = $this->attributes(array_merge(['type' => 'text/javascript'], $attributes));
|
||||
|
||||
$output = '';
|
||||
if ($this->js_pipeline) {
|
||||
$output .= '<script src="' . $this->pipeline(JS_ASSET) . '"' . $attributes . ' ></script>' . "\n";
|
||||
foreach ($this->js_no_pipeline as $file) {
|
||||
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ></script>' . "\n";
|
||||
}
|
||||
} else {
|
||||
foreach ($this->js as $file) {
|
||||
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ></script>' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Render Inline JS
|
||||
if (count($this->inline_js) > 0) {
|
||||
$output .= "<script>\n";
|
||||
foreach ($this->inline_js as $inline) {
|
||||
$output .= $inline['asset'] . "\n";
|
||||
}
|
||||
$output .= "</script>\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Minifiy and concatenate CSS / JS files.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function pipeline($css = true)
|
||||
{
|
||||
/** @var Cache $cache */
|
||||
$cache = self::$grav['cache'];
|
||||
$key = '?' . $cache->getKey();
|
||||
|
||||
if ($css) {
|
||||
$file = md5(json_encode($this->css) . $this->js_minify . $this->css_minify . $this->css_rewrite) . '.css';
|
||||
foreach ($this->css as $id => $asset) {
|
||||
if (!$asset['pipeline']) {
|
||||
$this->css_no_pipeline[] = $asset;
|
||||
unset($this->css[$id]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$file = md5(json_encode($this->js) . $this->js_minify . $this->css_minify . $this->css_rewrite) . '.js';
|
||||
foreach ($this->js as $id => $asset) {
|
||||
if (!$asset['pipeline']) {
|
||||
$this->js_no_pipeline[] = $asset;
|
||||
unset($this->js[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$relative_path = "{$this->base_url}" . basename(ASSETS_DIR) . "/{$file}";
|
||||
$absolute_path = ASSETS_DIR . $file;
|
||||
|
||||
// If pipeline exist return it
|
||||
if (file_exists($absolute_path)) {
|
||||
return $relative_path . $key;
|
||||
}
|
||||
|
||||
$css_minify = $this->css_minify;
|
||||
|
||||
// If this is a Windows server, and minify_windows is false (default value) skip the
|
||||
// minification process because it will cause Apache to die/crash due to insufficient
|
||||
// ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689
|
||||
if (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN' && !$this->css_minify_windows) {
|
||||
$css_minify = false;
|
||||
}
|
||||
|
||||
// Concatenate files
|
||||
if ($css) {
|
||||
$buffer = $this->gatherLinks($this->css, CSS_ASSET);
|
||||
if ($css_minify) {
|
||||
$min = new \CSSmin();
|
||||
$buffer = $min->run($buffer);
|
||||
}
|
||||
} else {
|
||||
$buffer = $this->gatherLinks($this->js, JS_ASSET);
|
||||
if ($this->js_minify) {
|
||||
$buffer = \JSMin::minify($buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Write file
|
||||
file_put_contents($absolute_path, $buffer);
|
||||
|
||||
return $relative_path . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/replace collection.
|
||||
*
|
||||
* @param string $collectionName
|
||||
* @param array $assets
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function registerCollection($collectionName, Array $assets)
|
||||
{
|
||||
$this->collections[$collectionName] = $assets;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
return $this->resetCss()->resetJs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset JavaScript assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetJs()
|
||||
{
|
||||
$this->js = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset CSS assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCss()
|
||||
{
|
||||
$this->css = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all CSS assets already added.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCss()
|
||||
{
|
||||
return $this->css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all JavaScript assets already added.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJs()
|
||||
{
|
||||
return $this->js;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all CSS assets within $directory (relative to public dir).
|
||||
*
|
||||
* @param string $directory Relative to $this->public_dir
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirCss($directory)
|
||||
{
|
||||
return $this->addDir($directory, self::CSS_REGEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all assets matching $pattern within $directory.
|
||||
*
|
||||
* @param string $directory Relative to $this->public_dir
|
||||
* @param string $pattern (regex)
|
||||
*
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
|
||||
{
|
||||
// Check if public_dir exists
|
||||
if (!is_dir(ASSETS_DIR)) {
|
||||
throw new Exception('Assets: Public dir not found');
|
||||
}
|
||||
|
||||
// Get files
|
||||
$files = $this->rglob(ASSETS_DIR . DIRECTORY_SEPARATOR . $directory, $pattern, ASSETS_DIR);
|
||||
|
||||
// No luck? Nothing to do
|
||||
if (!$files) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Add CSS files
|
||||
if ($pattern === self::CSS_REGEX) {
|
||||
$this->css = array_unique(array_merge($this->css, $files));
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Add JavaScript files
|
||||
if ($pattern === self::JS_REGEX) {
|
||||
$this->js = array_unique(array_merge($this->js, $files));
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Unknown pattern. We must poll to know the extension :(
|
||||
foreach ($files as $asset) {
|
||||
$info = pathinfo($asset);
|
||||
if (isset($info['extension'])) {
|
||||
$ext = strtolower($info['extension']);
|
||||
if ($ext === 'css' and !in_array($asset, $this->css)) {
|
||||
$this->css[] = $asset;
|
||||
} elseif ($ext === 'js' and !in_array($asset, $this->js)) {
|
||||
$this->js[] = $asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a link is local or remote.
|
||||
*
|
||||
* Undestands both "http://" and "https://" as well as protocol agnostic links "//"
|
||||
*
|
||||
* @param string $link
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isRemoteLink($link)
|
||||
{
|
||||
return ('http://' === substr($link, 0, 7) or 'https://' === substr($link, 0, 8)
|
||||
or '//' === substr($link, 0, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build local links including grav asset shortcodes
|
||||
*
|
||||
* @param string $asset the asset string reference
|
||||
*
|
||||
* @return string the final link url to the asset
|
||||
*/
|
||||
protected function buildLocalLink($asset)
|
||||
{
|
||||
try {
|
||||
$asset = self::$grav['locator']->findResource($asset, false);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return $asset ? $this->base_url . ltrim($asset, '/') : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an HTML attribute string from an array.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function attributes(array $attributes)
|
||||
{
|
||||
$html = '';
|
||||
|
||||
foreach ($attributes as $key => $value) {
|
||||
// For numeric keys we will assume that the key and the value are the same
|
||||
// as this will convert HTML attributes such as "required" to a correct
|
||||
// form like required="required" instead of using incorrect numerics.
|
||||
if (is_numeric($key)) {
|
||||
$key = $value;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$value = implode(' ', $value);
|
||||
}
|
||||
|
||||
$element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
|
||||
$html .= ' ' . $element;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and concatenate the content of several links.
|
||||
*
|
||||
* @param array $links
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function gatherLinks(array $links, $css = true)
|
||||
{
|
||||
|
||||
|
||||
$buffer = '';
|
||||
$local = true;
|
||||
|
||||
foreach ($links as $asset) {
|
||||
$link = $asset['asset'];
|
||||
$relative_path = $link;
|
||||
|
||||
if ($this->isRemoteLink($link)) {
|
||||
$local = false;
|
||||
if ('//' === substr($link, 0, 2)) {
|
||||
$link = 'http:' . $link;
|
||||
}
|
||||
} else {
|
||||
// Fix to remove relative dir if grav is in one
|
||||
if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) {
|
||||
$relative_path = str_replace($this->base_url, '/', $link);
|
||||
}
|
||||
|
||||
$relative_dir = dirname($relative_path);
|
||||
$link = ROOT_DIR . $relative_path;
|
||||
}
|
||||
|
||||
$file = ($this->fetch_command instanceof Closure) ? $this->fetch_command->__invoke($link) : file_get_contents($link);
|
||||
|
||||
// Double check last character being
|
||||
if (!$css) {
|
||||
$file = rtrim($file, ' ;') . ';';
|
||||
}
|
||||
|
||||
// If this is CSS + the file is local + rewrite enabled
|
||||
if ($css && $local && $this->css_rewrite) {
|
||||
$file = $this->cssRewrite($file, $relative_dir);
|
||||
}
|
||||
|
||||
$buffer .= $file;
|
||||
}
|
||||
|
||||
// Pull out @imports and move to top
|
||||
if ($css) {
|
||||
$buffer = $this->moveImports($buffer);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds relative CSS urls() and rewrites the URL with an absolute one
|
||||
*
|
||||
* @param $file the css source file
|
||||
* @param $relative_path relative path to the css file
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function cssRewrite($file, $relative_path)
|
||||
{
|
||||
// Strip any sourcemap comments
|
||||
$file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file);
|
||||
|
||||
// Find any css url() elements, grab the URLs and calculate an absolute path
|
||||
// Then replace the old url with the new one
|
||||
$file = preg_replace_callback(
|
||||
self::CSS_URL_REGEX,
|
||||
function ($matches) use ($relative_path) {
|
||||
|
||||
$old_url = $matches[1];
|
||||
|
||||
// ensure this is not a data url
|
||||
if (strpos($old_url, 'data:') === 0) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$newpath = array();
|
||||
$paths = explode('/', $old_url);
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if ($path == '..') {
|
||||
$relative_path = dirname($relative_path);
|
||||
} else {
|
||||
$newpath[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
$new_url = rtrim($this->base_url, '/') . $relative_path . '/' . implode('/', $newpath);
|
||||
|
||||
return str_replace($old_url, $new_url, $matches[0]);
|
||||
},
|
||||
$file
|
||||
);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves @import statements to the top of the file per the CSS specification
|
||||
*
|
||||
* @param string $file the file containing the combined CSS files
|
||||
*
|
||||
* @return string the modified file with any @imports at the top of the file
|
||||
*/
|
||||
protected function moveImports($file)
|
||||
{
|
||||
$this->imports = array();
|
||||
|
||||
$file = preg_replace_callback(
|
||||
self::CSS_IMPORT_REGEX,
|
||||
function ($matches) {
|
||||
$this->imports[] = $matches[0];
|
||||
return '';
|
||||
},
|
||||
$file
|
||||
);
|
||||
|
||||
return implode("\n", $this->imports) . "\n\n" . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively get files matching $pattern within $directory.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $pattern (regex)
|
||||
* @param string $ltrim Will be trimed from the left of the file path
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function rglob($directory, $pattern, $ltrim = null)
|
||||
{
|
||||
$iterator = new RegexIterator(
|
||||
new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(
|
||||
$directory,
|
||||
FilesystemIterator::SKIP_DOTS
|
||||
)
|
||||
),
|
||||
$pattern
|
||||
);
|
||||
$offset = strlen($ltrim);
|
||||
$files = array();
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = substr($file->getPathname(), $offset);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all JavaScript assets within $directory.
|
||||
*
|
||||
* @param string $directory Relative to $this->public_dir
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirJs($directory)
|
||||
{
|
||||
return $this->addDir($directory, self::JS_REGEX);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $a
|
||||
* @param $b
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function priorityCompare($a, $b)
|
||||
{
|
||||
return $a ['priority'] - $b ['priority'];
|
||||
}
|
||||
|
||||
}
|
||||
41
system/src/Grav/Common/Browser.php
Normal file
41
system/src/Grav/Common/Browser.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* Simple wrapper for the very simple parse_user_agent() function
|
||||
*/
|
||||
class Browser
|
||||
{
|
||||
|
||||
protected $useragent = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
try {
|
||||
$this->useragent = parse_user_agent();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)");
|
||||
}
|
||||
}
|
||||
|
||||
public function getBrowser()
|
||||
{
|
||||
return strtolower($this->useragent['browser']);
|
||||
}
|
||||
|
||||
public function getPlatform()
|
||||
{
|
||||
return strtolower($this->useragent['platform']);
|
||||
}
|
||||
|
||||
public function getLongVersion()
|
||||
{
|
||||
return $this->useragent['version'];
|
||||
}
|
||||
|
||||
public function getVersion()
|
||||
{
|
||||
$version = explode('.', $this->getLongVersion());
|
||||
return intval($version[0]);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use \Doctrine\Common\Cache\Cache as DoctrineCache;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
/**
|
||||
* The GravCache object is used throughout Grav to store and retrieve cached data.
|
||||
* It uses DoctrineCache library and supports a variety of caching mechanisms. Those include:
|
||||
@@ -22,8 +26,13 @@ class Cache extends Getters
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
protected $lifetime;
|
||||
protected $now;
|
||||
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Cache\Cache
|
||||
* @var DoctrineCache
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
@@ -32,25 +41,100 @@ class Cache extends Getters
|
||||
*/
|
||||
protected $enabled;
|
||||
|
||||
protected $cache_dir;
|
||||
|
||||
protected static $standard_remove = [
|
||||
'cache/twig/',
|
||||
'cache/doctrine/',
|
||||
'cache/compiled/',
|
||||
'cache/validated-',
|
||||
'images/',
|
||||
'assets/',
|
||||
];
|
||||
|
||||
protected static $all_remove = [
|
||||
'cache/',
|
||||
'images/',
|
||||
'assets/'
|
||||
];
|
||||
|
||||
protected static $assets_remove = [
|
||||
'assets/'
|
||||
];
|
||||
|
||||
protected static $images_remove = [
|
||||
'images/'
|
||||
];
|
||||
|
||||
protected static $cache_remove = [
|
||||
'cache/'
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @params Grav $grav
|
||||
*/
|
||||
public function __construct(Grav $grav)
|
||||
{
|
||||
$this->init($grav);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization that sets a base key and the driver based on configuration settings
|
||||
*
|
||||
* @param Grav $grav
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
public function init(Grav $grav)
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = Registry::get('Config');
|
||||
$prefix = $config->get('system.cache.prefix');
|
||||
/** @var Uri $uri */
|
||||
$uri = Registry::get('Uri');
|
||||
$this->config = $grav['config'];
|
||||
$this->now = time();
|
||||
|
||||
$this->enabled = (bool) $config->get('system.cache.enabled');
|
||||
$this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $grav['uri'];
|
||||
|
||||
$prefix = $this->config->get('system.cache.prefix');
|
||||
|
||||
$this->enabled = (bool) $this->config->get('system.cache.enabled');
|
||||
|
||||
// Cache key allows us to invalidate all cache on configuration changes.
|
||||
$this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $config->key . GRAV_VERSION), 2, 8);
|
||||
$this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
|
||||
|
||||
switch ($this->getCacheDriverName($config->get('system.cache.driver'))) {
|
||||
$this->driver = $this->getCacheDriver();
|
||||
|
||||
// Set the cache namespace to our unique key
|
||||
$this->driver->setNamespace($this->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically picks the cache mechanism to use. If you pick one manually it will use that
|
||||
* If there is no config option for $driver in the config, or it's set to 'auto', it will
|
||||
* pick the best option based on which cache extensions are installed.
|
||||
*
|
||||
* @return DoctrineCacheDriver The cache driver to use
|
||||
*/
|
||||
public function getCacheDriver()
|
||||
{
|
||||
$setting = $this->config->get('system.cache.driver');
|
||||
$driver_name = 'file';
|
||||
|
||||
if (!$setting || $setting == 'auto') {
|
||||
if (extension_loaded('apc')) {
|
||||
$driver_name = 'apc';
|
||||
} elseif (extension_loaded('wincache')) {
|
||||
$driver_name = 'wincache';
|
||||
} elseif (extension_loaded('xcache')) {
|
||||
$driver_name = 'xcache';
|
||||
}
|
||||
} else {
|
||||
$driver_name = $setting;
|
||||
}
|
||||
|
||||
switch ($driver_name) {
|
||||
case 'apc':
|
||||
$driver = new \Doctrine\Common\Cache\ApcCache();
|
||||
break;
|
||||
@@ -64,44 +148,19 @@ class Cache extends Getters
|
||||
break;
|
||||
|
||||
case 'memcache':
|
||||
$memcache = new \Memcache();
|
||||
$memcache->connect($this->config->get('system.cache.memcache.server','localhost'),
|
||||
$this->config->get('system.cache.memcache.port', 11211));
|
||||
$driver = new \Doctrine\Common\Cache\MemcacheCache();
|
||||
break;
|
||||
|
||||
case 'memcached':
|
||||
$driver = new \Doctrine\Common\Cache\MemcachedCache();
|
||||
$driver->setMemcache($memcache);
|
||||
break;
|
||||
|
||||
default:
|
||||
$driver = new \Doctrine\Common\Cache\FilesystemCache(CACHE_DIR);
|
||||
$driver = new \Doctrine\Common\Cache\FilesystemCache($this->cache_dir);
|
||||
break;
|
||||
}
|
||||
$this->driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically picks the cache mechanism to use. If you pick one manually it will use that
|
||||
* If there is no config option for $driver in the config, or it's set to 'auto', it will
|
||||
* pick the best option based on which cache extensions are installed.
|
||||
*
|
||||
* @param string $setting
|
||||
* @return string The name of the best cache driver to use
|
||||
*/
|
||||
protected function getCacheDriverName($setting = null)
|
||||
{
|
||||
|
||||
if (!$setting || $setting == 'auto') {
|
||||
if (extension_loaded('apc') && ini_get('apc.enabled')) {
|
||||
return 'apc';
|
||||
} elseif (extension_loaded('wincache')) {
|
||||
return 'wincache';
|
||||
} elseif (extension_loaded('xcache') && ini_get('xcache.size') && ini_get('xcache.cacher')) {
|
||||
return 'xcache';
|
||||
} else {
|
||||
return 'file';
|
||||
}
|
||||
} else {
|
||||
return $setting;
|
||||
}
|
||||
return $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +172,6 @@ class Cache extends Getters
|
||||
public function fetch($id)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$id = $this->key . $id;
|
||||
return $this->driver->fetch($id);
|
||||
} else {
|
||||
return false;
|
||||
@@ -130,7 +188,10 @@ class Cache extends Getters
|
||||
public function save($id, $data, $lifetime = null)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$id = $this->key . $id;
|
||||
|
||||
if ($lifetime == null) {
|
||||
$lifetime = $this->getLifetime();
|
||||
}
|
||||
$this->driver->save($id, $data, $lifetime);
|
||||
}
|
||||
}
|
||||
@@ -142,4 +203,103 @@ class Cache extends Getters
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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')
|
||||
{
|
||||
|
||||
$output = [];
|
||||
$user_config = USER_DIR . 'config/system.yaml';
|
||||
|
||||
switch($remove) {
|
||||
case 'all':
|
||||
$remove_paths = self::$all_remove;
|
||||
break;
|
||||
case 'assets-only':
|
||||
$remove_paths = self::$assets_remove;
|
||||
break;
|
||||
case 'images-only':
|
||||
$remove_paths = self::$images_remove;
|
||||
break;
|
||||
case 'cache-only':
|
||||
$remove_paths = self::$cache_remove;
|
||||
break;
|
||||
default:
|
||||
$remove_paths = self::$standard_remove;
|
||||
}
|
||||
|
||||
|
||||
foreach ($remove_paths as $path) {
|
||||
|
||||
$anything = false;
|
||||
$files = glob(ROOT_DIR . $path . '*');
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
if (@unlink($file)) {
|
||||
$anything = true;
|
||||
}
|
||||
} elseif (is_dir($file)) {
|
||||
if (@Folder::delete($file)) {
|
||||
$anything = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($anything) {
|
||||
$output[] = '<red>Cleared: </red>' . $path . '*';
|
||||
}
|
||||
}
|
||||
|
||||
$output[] = '';
|
||||
|
||||
if (($remove == 'all' || $remove == 'standard') && file_exists($user_config)) {
|
||||
touch($user_config);
|
||||
|
||||
$output[] = '<red>Touched: </red>' . $user_config;
|
||||
$output[] = '';
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the cache lifetime programatically
|
||||
*
|
||||
* @param int $future timestamp
|
||||
*/
|
||||
public function setLifetime($future)
|
||||
{
|
||||
if (!$future) {
|
||||
return;
|
||||
}
|
||||
|
||||
$interval = $future - $this->now;
|
||||
if ($interval > 0 && $interval < $this->getLifetime()) {
|
||||
$this->lifetime = $interval;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the cache lifetime (in seconds)
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLifetime()
|
||||
{
|
||||
if ($this->lifetime === null) {
|
||||
$this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default
|
||||
}
|
||||
|
||||
return $this->lifetime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Filesystem\File;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
/**
|
||||
* The Config class contains configuration information.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Config extends Data
|
||||
{
|
||||
/**
|
||||
* @var string Configuration location in the disk.
|
||||
*/
|
||||
public $filename;
|
||||
|
||||
/**
|
||||
* @var string MD5 from the files.
|
||||
*/
|
||||
public $key;
|
||||
|
||||
/**
|
||||
* @var array Configuration file list.
|
||||
*/
|
||||
public $files = array();
|
||||
|
||||
/**
|
||||
* @var bool Flag to tell if configuration needs to be saved.
|
||||
*/
|
||||
public $updated = false;
|
||||
public $issues = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($filename)
|
||||
{
|
||||
$this->filename = realpath(dirname($filename)) . '/' . basename($filename);
|
||||
|
||||
$this->reload(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reload of the configuration from the disk.
|
||||
*
|
||||
* @param bool $force
|
||||
* @return $this
|
||||
*/
|
||||
public function reload($force = true)
|
||||
{
|
||||
// Build file map.
|
||||
$files = $this->build();
|
||||
$key = md5(serialize($files) . GRAV_VERSION);
|
||||
|
||||
if ($force || $key != $this->key) {
|
||||
// First take non-blocking lock to the file.
|
||||
File\Config::instance($this->filename)->lock(false);
|
||||
|
||||
// Reset configuration.
|
||||
$this->items = array();
|
||||
$this->files = array();
|
||||
$this->init($files);
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration into file.
|
||||
*
|
||||
* Note: Only saves the file if updated flag is set!
|
||||
*
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
// If configuration was updated, store it as cached version.
|
||||
try {
|
||||
$file = File\Config::instance($this->filename);
|
||||
|
||||
// Only save configuration file if it wasn't locked. Also invalidate opcache after saving.
|
||||
// This prevents us from saving the file multiple times in a row and gives faster recovery.
|
||||
if ($file->locked() !== false) {
|
||||
$file->save($this);
|
||||
$file->unlock();
|
||||
}
|
||||
$this->updated = false;
|
||||
} catch (\Exception $e) {
|
||||
$this->issues[] = 'Writing configuration into cache failed.';
|
||||
//throw new \RuntimeException('Writing configuration into cache failed.', 500, $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets configuration instance.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return \Grav\Common\Config
|
||||
*/
|
||||
public static function instance($filename)
|
||||
{
|
||||
// Load cached version if available..
|
||||
if (file_exists($filename)) {
|
||||
clearstatcache(true, $filename);
|
||||
require_once $filename;
|
||||
|
||||
if (class_exists('\Grav\Config')) {
|
||||
$instance = new \Grav\Config($filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Or initialize new configuration object..
|
||||
if (!isset($instance)) {
|
||||
$instance = new static($filename);
|
||||
}
|
||||
|
||||
// If configuration was updated, store it as cached version.
|
||||
if ($instance->updated) {
|
||||
$instance->save();
|
||||
}
|
||||
|
||||
|
||||
// If not set, add manually current base url.
|
||||
if (empty($instance->items['system']['base_url_absolute'])) {
|
||||
$instance->items['system']['base_url_absolute'] = Registry::get('Uri')->rootUrl(true);
|
||||
}
|
||||
|
||||
if (empty($instance->items['system']['base_url_relative'])) {
|
||||
$instance->items['system']['base_url_relative'] = Registry::get('Uri')->rootUrl(false);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert configuration into an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return array('key' => $this->key, 'files' => $this->files, 'items' => $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize object by loading all the configuration files.
|
||||
*
|
||||
* @param array $files
|
||||
*/
|
||||
protected function init(array $files)
|
||||
{
|
||||
$this->updated = true;
|
||||
|
||||
// Combine all configuration files into one larger lookup table (only keys matter).
|
||||
$allFiles = $files['user'] + $files['plugins'] + $files['system'];
|
||||
|
||||
// Then sort the files to have all parent nodes first.
|
||||
// This is to make sure that child nodes override parents content.
|
||||
uksort(
|
||||
$allFiles,
|
||||
function($a, $b) {
|
||||
$diff = substr_count($a, '/') - substr_count($b, '/');
|
||||
return $diff ? $diff : strcmp($a, $b);
|
||||
}
|
||||
);
|
||||
|
||||
$systemBlueprints = new Blueprints(SYSTEM_DIR . 'blueprints');
|
||||
$pluginBlueprints = new Blueprints(USER_DIR);
|
||||
|
||||
$items = array();
|
||||
foreach ($allFiles as $name => $dummy) {
|
||||
$lookup = array(
|
||||
'system' => SYSTEM_DIR . 'config/' . $name . YAML_EXT,
|
||||
'plugins' => USER_DIR . $name . '/' . basename($name) . YAML_EXT,
|
||||
'user' => USER_DIR . 'config/' . $name . YAML_EXT,
|
||||
);
|
||||
if (strpos($name, 'plugins/') === 0) {
|
||||
$blueprint = $pluginBlueprints->get("{$name}/blueprints");
|
||||
} else {
|
||||
$blueprint = $systemBlueprints->get($name);
|
||||
}
|
||||
|
||||
$data = new Data(array(), $blueprint);
|
||||
foreach ($lookup as $key => $path) {
|
||||
if (is_file($path)) {
|
||||
$data->merge(File\Yaml::instance($path)->content());
|
||||
}
|
||||
}
|
||||
// $data->validate();
|
||||
// $data->filter();
|
||||
|
||||
// Find the current sub-tree location.
|
||||
$current = &$items;
|
||||
$parts = explode('/', $name);
|
||||
foreach ($parts as $part) {
|
||||
if (!isset($current[$part])) {
|
||||
$current[$part] = array();
|
||||
}
|
||||
$current = &$current[$part];
|
||||
}
|
||||
|
||||
// Handle both updated and deleted configuration files.
|
||||
$current = $data->toArray();
|
||||
}
|
||||
|
||||
$this->items = $items;
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of configuration files with their timestamps. Used for loading settings and caching them.
|
||||
*
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function build()
|
||||
{
|
||||
// Find all plugins with default configuration options.
|
||||
$plugins = array();
|
||||
$iterator = new \DirectoryIterator(PLUGINS_DIR);
|
||||
|
||||
/** @var \DirectoryIterator $plugin */
|
||||
foreach ($iterator as $plugin) {
|
||||
$name = $plugin->getBasename();
|
||||
$file = $plugin->getPathname() . DS . $name . YAML_EXT;
|
||||
|
||||
if (!is_file($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$modified = filemtime($file);
|
||||
$plugins["plugins/{$name}"] = $modified;
|
||||
}
|
||||
|
||||
// Find all system and user configuration files.
|
||||
$options = array(
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => array('key' => '|\.yaml$|'),
|
||||
'key' => 'SubPathname',
|
||||
'value' => 'MTime'
|
||||
);
|
||||
|
||||
$system = Folder::all(SYSTEM_DIR . 'config', $options);
|
||||
$user = Folder::all(USER_DIR . 'config', $options);
|
||||
|
||||
return array('system' => $system, 'plugins' => $plugins, 'user' => $user);
|
||||
}
|
||||
}
|
||||
208
system/src/Grav/Common/Config/Blueprints.php
Normal file
208
system/src/Grav/Common/Config/Blueprints.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RocketTheme\Toolbox\Blueprints\Blueprints as BaseBlueprints;
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* The Blueprints class contains configuration rules.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Blueprints extends BaseBlueprints
|
||||
{
|
||||
protected $grav;
|
||||
protected $files = [];
|
||||
protected $blueprints;
|
||||
|
||||
public function __construct(array $serialized = null, Grav $grav = null)
|
||||
{
|
||||
parent::__construct($serialized);
|
||||
$this->grav = $grav ?: Grav::instance();
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
$blueprints = $locator->findResources('blueprints://config');
|
||||
$plugins = $locator->findResources('plugins://');
|
||||
|
||||
$blueprintFiles = $this->getBlueprintFiles($blueprints, $plugins);
|
||||
|
||||
$this->loadCompiledBlueprints($plugins + $blueprints, $blueprintFiles);
|
||||
}
|
||||
|
||||
protected function loadCompiledBlueprints($blueprints, $blueprintFiles)
|
||||
{
|
||||
$checksum = md5(serialize($blueprints));
|
||||
$filename = CACHE_DIR . 'compiled/blueprints/' . $checksum .'.php';
|
||||
$checksum .= ':'.md5(serialize($blueprintFiles));
|
||||
$class = get_class($this);
|
||||
$file = PhpFile::instance($filename);
|
||||
|
||||
if ($file->exists()) {
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
} else {
|
||||
$cache = null;
|
||||
}
|
||||
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (
|
||||
!is_array($cache)
|
||||
|| empty($cache['checksum'])
|
||||
|| empty($cache['$class'])
|
||||
|| $cache['checksum'] != $checksum
|
||||
|| $cache['@class'] != $class
|
||||
) {
|
||||
// Attempt to lock the file for writing.
|
||||
$file->lock(false);
|
||||
|
||||
// Load blueprints.
|
||||
$this->blueprints = new Blueprints();
|
||||
foreach ($blueprintFiles as $key => $files) {
|
||||
$this->loadBlueprints($key);
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'checksum' => $checksum,
|
||||
'files' => $blueprintFiles,
|
||||
'data' => $this->blueprints->toArray()
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
}
|
||||
} else {
|
||||
$this->blueprints = new Blueprints($cache['data']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load global blueprints.
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $files
|
||||
*/
|
||||
public function loadBlueprints($key, array $files = null)
|
||||
{
|
||||
if (is_null($files)) {
|
||||
$files = $this->files[$key];
|
||||
}
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->blueprints->embed($name, $file->content(), '/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all blueprint files (including plugins).
|
||||
*
|
||||
* @param array $blueprints
|
||||
* @param array $plugins
|
||||
* @return array
|
||||
*/
|
||||
protected function getBlueprintFiles(array $blueprints, array $plugins)
|
||||
{
|
||||
$list = [];
|
||||
foreach (array_reverse($plugins) as $folder) {
|
||||
$list += $this->detectPlugins($folder, true);
|
||||
}
|
||||
foreach (array_reverse($blueprints) as $folder) {
|
||||
$list += $this->detectConfig($folder, true);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects all plugins with a configuration file and returns last modification time.
|
||||
*
|
||||
* @param string $lookup Location to look up from.
|
||||
* @param bool $blueprints
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function detectPlugins($lookup = SYSTEM_DIR, $blueprints = false)
|
||||
{
|
||||
$find = $blueprints ? 'blueprints.yaml' : '.yaml';
|
||||
$location = $blueprints ? 'blueprintFiles' : 'configFiles';
|
||||
$path = trim(Folder::getRelativePath($lookup), '/');
|
||||
if (isset($this->{$location}[$path])) {
|
||||
return [$path => $this->{$location}[$path]];
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
if (is_dir($lookup)) {
|
||||
$iterator = new \DirectoryIterator($lookup);
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
if (!$directory->isDir() || $directory->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $directory->getBasename();
|
||||
$filename = "{$path}/{$name}/" . ($find && $find[0] != '.' ? $find : $name . $find);
|
||||
|
||||
if (is_file($filename)) {
|
||||
$list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->{$location}[$path] = $list;
|
||||
|
||||
return [$path => $list];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects all plugins with a configuration file and returns last modification time.
|
||||
*
|
||||
* @param string $lookup Location to look up from.
|
||||
* @param bool $blueprints
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function detectConfig($lookup = SYSTEM_DIR, $blueprints = false)
|
||||
{
|
||||
$location = $blueprints ? 'blueprintFiles' : 'configFiles';
|
||||
$path = trim(Folder::getRelativePath($lookup), '/');
|
||||
if (isset($this->{$location}[$path])) {
|
||||
return [$path => $this->{$location}[$path]];
|
||||
}
|
||||
|
||||
if (is_dir($lookup)) {
|
||||
// Find all system and user configuration files.
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => [
|
||||
'key' => '|\.yaml$|',
|
||||
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
|
||||
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
|
||||
}],
|
||||
'key' => 'SubPathname'
|
||||
];
|
||||
|
||||
$list = Folder::all($lookup, $options);
|
||||
} else {
|
||||
$list = [];
|
||||
}
|
||||
|
||||
$this->{$location}[$path] = $list;
|
||||
|
||||
return [$path => $list];
|
||||
}
|
||||
}
|
||||
386
system/src/Grav/Common/Config/Config.php
Normal file
386
system/src/Grav/Common/Config/Config.php
Normal file
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RocketTheme\Toolbox\Blueprints\Blueprints;
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* The Config class contains configuration information.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Config extends Data
|
||||
{
|
||||
protected $grav;
|
||||
protected $streams = [
|
||||
'system' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['system'],
|
||||
]
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user'],
|
||||
]
|
||||
],
|
||||
'blueprints' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user://blueprints', 'system/blueprints'],
|
||||
]
|
||||
],
|
||||
'config' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user://config', 'system/config'],
|
||||
]
|
||||
],
|
||||
'plugins' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user://plugins'],
|
||||
]
|
||||
],
|
||||
'plugin' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user://plugins'],
|
||||
]
|
||||
],
|
||||
'themes' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user://themes'],
|
||||
]
|
||||
],
|
||||
'cache' => [
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
'' => ['cache'],
|
||||
'images' => ['images']
|
||||
]
|
||||
],
|
||||
'log' => [
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
'' => ['logs']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
protected $blueprintFiles = [];
|
||||
protected $configFiles = [];
|
||||
protected $checksum;
|
||||
protected $timestamp;
|
||||
|
||||
protected $configLookup;
|
||||
protected $blueprintLookup;
|
||||
protected $pluginLookup;
|
||||
|
||||
protected $finder;
|
||||
protected $environment;
|
||||
protected $messages = [];
|
||||
|
||||
public function __construct(array $items = array(), Grav $grav = null, $environment = null)
|
||||
{
|
||||
$this->grav = $grav ?: Grav::instance();
|
||||
$this->finder = new ConfigFinder;
|
||||
$this->environment = $environment ?: 'localhost';
|
||||
$this->messages[] = 'Environment Name: ' . $this->environment;
|
||||
|
||||
if (isset($items['@class'])) {
|
||||
if ($items['@class'] != get_class($this)) {
|
||||
throw new \InvalidArgumentException('Unrecognized config cache file!');
|
||||
}
|
||||
// Loading pre-compiled configuration.
|
||||
$this->timestamp = (int) $items['timestamp'];
|
||||
$this->checksum = $items['checksum'];
|
||||
$this->items = (array) $items['data'];
|
||||
} else {
|
||||
// Make sure that
|
||||
if (!isset($items['streams']['schemes'])) {
|
||||
$items['streams']['schemes'] = [];
|
||||
}
|
||||
$items['streams']['schemes'] += $this->streams;
|
||||
|
||||
$items = $this->autoDetectEnvironmentConfig($items);
|
||||
$this->messages[] = $items['streams']['schemes']['config']['prefixes'][''];
|
||||
|
||||
parent::__construct($items);
|
||||
}
|
||||
$this->check();
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->checksum();
|
||||
}
|
||||
|
||||
public function reload()
|
||||
{
|
||||
$this->check();
|
||||
$this->init();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function check()
|
||||
{
|
||||
$streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
|
||||
if (!is_array($streams)) {
|
||||
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
|
||||
}
|
||||
$diff = array_keys(array_diff_key($this->streams, $streams));
|
||||
if ($diff) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function debug()
|
||||
{
|
||||
foreach ($this->messages as $message) {
|
||||
$this->grav['debugger']->addMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
$this->configLookup = $locator->findResources('config://');
|
||||
$this->blueprintLookup = $locator->findResources('blueprints://config');
|
||||
$this->pluginLookup = $locator->findResources('plugins://');
|
||||
|
||||
if (!isset($this->checksum)) {
|
||||
$this->messages[] = 'No cached configuration, compiling new configuration..';
|
||||
} elseif ($this->checksum() != $this->checksum) {
|
||||
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
|
||||
} else {
|
||||
$this->messages[] = 'Configuration checksum matches, using cached version.';
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
|
||||
$this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
|
||||
|
||||
$this->initializeLocator($locator);
|
||||
}
|
||||
|
||||
public function checksum()
|
||||
{
|
||||
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
|
||||
$checkConfig = $this->get('system.cache.check.config', true);
|
||||
$checkSystem = $this->get('system.cache.check.system', true);
|
||||
|
||||
if (!$checkBlueprints && !$checkConfig && !$checkSystem) {
|
||||
$this->messages[] = 'Skip configuration timestamp check.';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate checksum according to the configuration settings.
|
||||
if (!$checkConfig) {
|
||||
$this->messages[] = 'Check configuration timestamps from system.yaml files.';
|
||||
// Just check changes in system.yaml files and ignore all the other files.
|
||||
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
|
||||
} else {
|
||||
$this->messages[] = 'Check configuration timestamps from all configuration files.';
|
||||
// Check changes in all configuration files.
|
||||
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
|
||||
}
|
||||
|
||||
if ($checkBlueprints) {
|
||||
$this->messages[] = 'Check blueprint timestamps from all blueprint files.';
|
||||
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
|
||||
} else {
|
||||
$cb = [];
|
||||
}
|
||||
|
||||
return md5(json_encode([$cc, $cb]));
|
||||
}
|
||||
|
||||
protected function autoDetectEnvironmentConfig($items)
|
||||
{
|
||||
$environment = $this->environment;
|
||||
$env_stream = 'user://'.$environment.'/config';
|
||||
|
||||
if (file_exists(USER_DIR.$environment.'/config')) {
|
||||
array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null)
|
||||
{
|
||||
$checksum = md5(json_encode($blueprints));
|
||||
$filename = $filename
|
||||
? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php'
|
||||
: CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
|
||||
$file = PhpFile::instance($filename);
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
$blueprintFiles = $this->finder->locateBlueprintFiles($blueprints, $plugins);
|
||||
$checksum .= ':'.md5(json_encode($blueprintFiles));
|
||||
$class = get_class($this);
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (
|
||||
!is_array($cache)
|
||||
|| !isset($cache['checksum'])
|
||||
|| !isset($cache['@class'])
|
||||
|| $cache['checksum'] != $checksum
|
||||
|| $cache['@class'] != $class
|
||||
) {
|
||||
// Attempt to lock the file for writing.
|
||||
$file->lock(false);
|
||||
|
||||
// Load blueprints.
|
||||
$this->blueprints = new Blueprints;
|
||||
foreach ($blueprintFiles as $files) {
|
||||
$this->loadBlueprintFiles($files);
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'checksum' => $checksum,
|
||||
'files' => $blueprintFiles,
|
||||
'data' => $this->blueprints->toArray()
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$this->messages[] = 'Saving compiled blueprints.';
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
}
|
||||
} else {
|
||||
$this->blueprints = new Blueprints($cache['data']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadCompiledConfig($configs, $plugins, $filename = null)
|
||||
{
|
||||
$checksum = md5(json_encode($configs));
|
||||
$filename = $filename
|
||||
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
|
||||
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
|
||||
$file = PhpFile::instance($filename);
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
|
||||
$checksum .= ':'.md5(json_encode($configFiles));
|
||||
$class = get_class($this);
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (
|
||||
!is_array($cache)
|
||||
|| !isset($cache['checksum'])
|
||||
|| !isset($cache['@class'])
|
||||
|| $cache['checksum'] != $checksum
|
||||
|| $cache['@class'] != $class
|
||||
) {
|
||||
// Attempt to lock the file for writing.
|
||||
$file->lock(false);
|
||||
|
||||
// Load configuration.
|
||||
foreach ($configFiles as $files) {
|
||||
$this->loadConfigFiles($files);
|
||||
}
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'timestamp' => time(),
|
||||
'checksum' => $this->checksum(),
|
||||
'data' => $this->toArray()
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$this->messages[] = 'Saving compiled configuration.';
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $cache['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load blueprints.
|
||||
*
|
||||
* @param array $files
|
||||
*/
|
||||
public function loadBlueprintFiles(array $files)
|
||||
{
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->blueprints->embed($name, $file->content(), '/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration.
|
||||
*
|
||||
* @param array $files
|
||||
*/
|
||||
public function loadConfigFiles(array $files)
|
||||
{
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->join($name, $file->content(), '/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize resource locator by using the configuration.
|
||||
*
|
||||
* @param UniformResourceLocator $locator
|
||||
*/
|
||||
public function initializeLocator(UniformResourceLocator $locator)
|
||||
{
|
||||
$locator->reset();
|
||||
|
||||
$schemes = (array) $this->get('streams.schemes', []);
|
||||
|
||||
foreach ($schemes as $scheme => $config) {
|
||||
if (isset($config['paths'])) {
|
||||
$locator->addPath($scheme, '', $config['paths']);
|
||||
}
|
||||
if (isset($config['prefixes'])) {
|
||||
foreach ($config['prefixes'] as $prefix => $paths) {
|
||||
$locator->addPath($scheme, $prefix, $paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available streams and their types from the configuration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getStreams()
|
||||
{
|
||||
$schemes = [];
|
||||
foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
|
||||
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
|
||||
if ($type[0] != '\\') {
|
||||
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
|
||||
}
|
||||
|
||||
$schemes[$scheme] = $type;
|
||||
}
|
||||
|
||||
return $schemes;
|
||||
}
|
||||
}
|
||||
146
system/src/Grav/Common/Config/ConfigFinder.php
Normal file
146
system/src/Grav/Common/Config/ConfigFinder.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
/**
|
||||
* The Configuration Finder class.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class ConfigFinder
|
||||
{
|
||||
/**
|
||||
* Get all locations for blueprint files (including plugins).
|
||||
*
|
||||
* @param array $blueprints
|
||||
* @param array $plugins
|
||||
* @return array
|
||||
*/
|
||||
public function locateBlueprintFiles(array $blueprints, array $plugins)
|
||||
{
|
||||
$list = [];
|
||||
foreach (array_reverse($plugins) as $folder) {
|
||||
$list += $this->detectInFolder($folder, 'blueprints');
|
||||
}
|
||||
foreach (array_reverse($blueprints) as $folder) {
|
||||
$list += $this->detectRecursive($folder);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all locations for configuration files (including plugins).
|
||||
*
|
||||
* @param array $configs
|
||||
* @param array $plugins
|
||||
* @return array
|
||||
*/
|
||||
public function locateConfigFiles(array $configs, array $plugins)
|
||||
{
|
||||
$list = [];
|
||||
foreach (array_reverse($plugins) as $folder) {
|
||||
$list += $this->detectInFolder($folder);
|
||||
}
|
||||
foreach (array_reverse($configs) as $folder) {
|
||||
$list += $this->detectRecursive($folder);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all locations for a single configuration file.
|
||||
*
|
||||
* @param array $folders Locations to look up from.
|
||||
* @param string $name Filename to be located.
|
||||
* @return array
|
||||
*/
|
||||
public function locateConfigFile(array $folders, $name)
|
||||
{
|
||||
$filename = "{$name}.yaml";
|
||||
|
||||
$list = [];
|
||||
foreach ($folders as $folder) {
|
||||
$path = trim(Folder::getRelativePath($folder), '/');
|
||||
|
||||
if (is_file("{$folder}/{$filename}")) {
|
||||
$modified = filemtime("{$folder}/{$filename}");
|
||||
} else {
|
||||
$modified = 0;
|
||||
}
|
||||
$list[$path] = [$name => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects all plugins with a configuration file and returns them with last modification time.
|
||||
*
|
||||
* @param string $folder Location to look up from.
|
||||
* @param string $lookup Filename to be located.
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function detectInFolder($folder, $lookup = null)
|
||||
{
|
||||
$path = trim(Folder::getRelativePath($folder), '/');
|
||||
|
||||
$list = [];
|
||||
|
||||
if (is_dir($folder)) {
|
||||
$iterator = new \DirectoryIterator($folder);
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
if (!$directory->isDir() || $directory->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $directory->getBasename();
|
||||
$find = ($lookup ?: $name) . '.yaml';
|
||||
$filename = "{$path}/{$name}/$find";
|
||||
|
||||
if (file_exists($filename)) {
|
||||
$list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$path => $list];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects all plugins with a configuration file and returns them with last modification time.
|
||||
*
|
||||
* @param string $folder Location to look up from.
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function detectRecursive($folder)
|
||||
{
|
||||
$path = trim(Folder::getRelativePath($folder), '/');
|
||||
|
||||
if (is_dir($folder)) {
|
||||
// Find all system and user configuration files.
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => [
|
||||
'key' => '|\.yaml$|',
|
||||
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
|
||||
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
|
||||
}
|
||||
],
|
||||
'key' => 'SubPathname'
|
||||
];
|
||||
|
||||
$list = Folder::all($folder, $options);
|
||||
} else {
|
||||
$list = [];
|
||||
}
|
||||
|
||||
return [$path => $list];
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use \Grav\Common\Registry;
|
||||
use \Symfony\Component\Yaml\Yaml;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
|
||||
/**
|
||||
* Blueprint handles the inside logic of blueprints.
|
||||
@@ -12,26 +11,41 @@ use \Symfony\Component\Yaml\Yaml;
|
||||
*/
|
||||
class Blueprint
|
||||
{
|
||||
use Export;
|
||||
|
||||
public $name;
|
||||
|
||||
public $initialized = false;
|
||||
protected $blueprints;
|
||||
|
||||
protected $items;
|
||||
protected $context;
|
||||
protected $fields;
|
||||
protected $rules = array();
|
||||
protected $nested = array();
|
||||
protected $filter = ['validation' => 1];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @param Blueprints $context
|
||||
*/
|
||||
public function __construct($name, array $data, Blueprints $context)
|
||||
public function __construct($name, array $data = array(), Blueprints $context = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->blueprints = $data;
|
||||
$this->items = $data;
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter for inherited properties.
|
||||
*
|
||||
* @param array $filter List of field names to be inherited.
|
||||
*/
|
||||
public function setFilter(array $filter)
|
||||
{
|
||||
$this->filter = array_flip($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
@@ -46,7 +60,7 @@ class Blueprint
|
||||
public function get($name, $default = null, $separator = '.')
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = $this->blueprints;
|
||||
$current = $this->items;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current) && isset($current->{$field})) {
|
||||
$current = $current->{$field};
|
||||
@@ -72,7 +86,7 @@ class Blueprint
|
||||
public function set($name, $value, $separator = '.')
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = &$this->blueprints;
|
||||
$current = &$this->items;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current)) {
|
||||
// Handle objects.
|
||||
@@ -102,8 +116,8 @@ class Blueprint
|
||||
public function fields()
|
||||
{
|
||||
if (!isset($this->fields)) {
|
||||
$this->fields = isset($this->blueprints['form']['fields']) ? $this->blueprints['form']['fields'] : array();
|
||||
$this->getFields($this->fields);
|
||||
$this->fields = [];
|
||||
$this->embed('', $this->items);
|
||||
}
|
||||
|
||||
return $this->fields;
|
||||
@@ -132,9 +146,10 @@ class Blueprint
|
||||
*
|
||||
* @param array $data1
|
||||
* @param array $data2
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function mergeData(array $data1, array $data2)
|
||||
public function mergeData(array $data1, array $data2, $name = null)
|
||||
{
|
||||
// Initialize data
|
||||
$this->fields();
|
||||
@@ -168,6 +183,71 @@ class Blueprint
|
||||
return $this->extraArray($data, $this->nested, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend blueprint with another blueprint.
|
||||
*
|
||||
* @param Blueprint $extends
|
||||
* @param bool $append
|
||||
*/
|
||||
public function extend(Blueprint $extends, $append = false)
|
||||
{
|
||||
$blueprints = $append ? $this->items : $extends->toArray();
|
||||
$appended = $append ? $extends->toArray() : $this->items;
|
||||
|
||||
$bref_stack = array(&$blueprints);
|
||||
$head_stack = array($appended);
|
||||
|
||||
do {
|
||||
end($bref_stack);
|
||||
|
||||
$bref = &$bref_stack[key($bref_stack)];
|
||||
$head = array_pop($head_stack);
|
||||
|
||||
unset($bref_stack[key($bref_stack)]);
|
||||
|
||||
foreach (array_keys($head) as $key) {
|
||||
if (isset($key, $bref[$key]) && is_array($bref[$key]) && is_array($head[$key])) {
|
||||
$bref_stack[] = &$bref[$key];
|
||||
$head_stack[] = $head[$key];
|
||||
} else {
|
||||
$bref = array_merge($bref, array($key => $head[$key]));
|
||||
}
|
||||
}
|
||||
} while(count($head_stack));
|
||||
|
||||
$this->items = $blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object into an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
return ['name' => $this->name, 'items' => $this->items, 'rules' => $this->rules, 'nested' => $this->nested];
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed an array to the blueprint.
|
||||
*
|
||||
* @param $name
|
||||
* @param array $value
|
||||
* @param string $separator
|
||||
*/
|
||||
public function embed($name, array $value, $separator = '.')
|
||||
{
|
||||
|
||||
if (!isset($value['form']['fields']) || !is_array($value['form']['fields'])) {
|
||||
return;
|
||||
}
|
||||
// Initialize data
|
||||
$this->fields();
|
||||
$prefix = $name ? strtr($name, $separator, '.') . '.' : '';
|
||||
$params = array_intersect_key($this->filter, $value);
|
||||
$this->parseFormFields($value['form']['fields'], $params, $prefix, $this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $rules
|
||||
@@ -188,7 +268,7 @@ class Blueprint
|
||||
} elseif (is_array($field) && is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$this->validateArray($field, $val);
|
||||
} elseif (isset($this->blueprints['validation']) && $this->blueprints['validation'] == 'strict') {
|
||||
} elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') {
|
||||
// Undefined/extra item.
|
||||
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
|
||||
}
|
||||
@@ -214,7 +294,7 @@ class Blueprint
|
||||
} elseif (is_array($field) && is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$field = $this->filterArray($field, $val);
|
||||
} elseif (isset($this->blueprints['validation']) && $this->blueprints['validation'] == 'strict') {
|
||||
} elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') {
|
||||
$field = null;
|
||||
}
|
||||
|
||||
@@ -282,26 +362,32 @@ class Blueprint
|
||||
* Gets all field definitions from the blueprints.
|
||||
*
|
||||
* @param array $fields
|
||||
* @param array $params
|
||||
* @param string $prefix
|
||||
* @param array $current
|
||||
* @internal
|
||||
*/
|
||||
protected function getFields(array &$fields)
|
||||
protected function parseFormFields(array &$fields, $params, $prefix, array &$current)
|
||||
{
|
||||
// Go though all the fields in current level.
|
||||
foreach ($fields as $key => &$field) {
|
||||
$current[$key] = &$field;
|
||||
// Set name from the array key.
|
||||
$field['name'] = $key;
|
||||
$field['name'] = $prefix . $key;
|
||||
$field += $params;
|
||||
|
||||
if (isset($field['fields'])) {
|
||||
// Recursively get all the nested fields.
|
||||
$this->getFields($field['fields']);
|
||||
$newParams = array_intersect_key($this->filter, $field);
|
||||
$this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
|
||||
} else {
|
||||
// Add rule.
|
||||
$this->rules[$key] = &$field;
|
||||
$this->addProperty($key);
|
||||
$this->rules[$prefix . $key] = &$field;
|
||||
$this->addProperty($prefix . $key);
|
||||
|
||||
foreach ($field as $name => $value) {
|
||||
// Support nested blueprints.
|
||||
if ($name == '@import') {
|
||||
if ($this->context && $name == '@import') {
|
||||
$values = (array) $value;
|
||||
if (!isset($field['fields'])) {
|
||||
$field['fields'] = array();
|
||||
@@ -380,8 +466,8 @@ class Blueprint
|
||||
*/
|
||||
protected function getRule($rule)
|
||||
{
|
||||
if (isset($this->blueprints['rules'][$rule]) && is_array($this->blueprints['rules'][$rule])) {
|
||||
return $this->blueprints['rules'][$rule];
|
||||
if (isset($this->items['rules'][$rule]) && is_array($this->items['rules'][$rule])) {
|
||||
return $this->items['rules'][$rule];
|
||||
}
|
||||
return array();
|
||||
}
|
||||
@@ -405,69 +491,4 @@ class Blueprint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert blueprints into an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert blueprints into YAML string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toYaml()
|
||||
{
|
||||
return Yaml::dump($this->blueprints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert blueprints into JSON string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode($this->blueprints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend blueprint with another blueprint.
|
||||
*
|
||||
* @param Blueprint $extends
|
||||
* @param bool $append
|
||||
*/
|
||||
public function extend(Blueprint $extends, $append = false)
|
||||
{
|
||||
$blueprints = $append ? $this->blueprints : $extends->toArray();
|
||||
$appended = $append ? $extends->toArray() : $this->blueprints;
|
||||
|
||||
$bref_stack = array(&$blueprints);
|
||||
$head_stack = array($appended);
|
||||
|
||||
do {
|
||||
end($bref_stack);
|
||||
|
||||
$bref = &$bref_stack[key($bref_stack)];
|
||||
$head = array_pop($head_stack);
|
||||
|
||||
unset($bref_stack[key($bref_stack)]);
|
||||
|
||||
foreach (array_keys($head) as $key) {
|
||||
if (isset($key, $bref[$key]) && is_array($bref[$key]) && is_array($head[$key])) {
|
||||
$bref_stack[] = &$bref[$key];
|
||||
$head_stack[] = $head[$key];
|
||||
} else {
|
||||
$bref = array_merge($bref, array($key => $head[$key]));
|
||||
}
|
||||
}
|
||||
} while(count($head_stack));
|
||||
|
||||
$this->blueprints = $blueprints;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
|
||||
/**
|
||||
* Blueprints class keeps track on blueprint instances.
|
||||
@@ -16,11 +16,15 @@ class Blueprints
|
||||
protected $instances = array();
|
||||
|
||||
/**
|
||||
* @param string $search Search path.
|
||||
* @param string|array $search Search path.
|
||||
*/
|
||||
public function __construct($search)
|
||||
{
|
||||
$this->search = rtrim($search, '\\/') . '/';
|
||||
if (!is_string($search)) {
|
||||
$this->search = $search;
|
||||
} else {
|
||||
$this->search = rtrim($search, '\\/') . '/';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,11 +37,18 @@ class Blueprints
|
||||
public function get($type)
|
||||
{
|
||||
if (!isset($this->instances[$type])) {
|
||||
if (is_file($this->search . $type . YAML_EXT)) {
|
||||
$blueprints = (array) Yaml::parse($this->search . $type . YAML_EXT);
|
||||
if (is_string($this->search)) {
|
||||
$filename = $this->search . $type . YAML_EXT;
|
||||
} else {
|
||||
$filename = isset($this->search[$type]) ? $this->search[$type] : '';
|
||||
}
|
||||
|
||||
if ($filename && is_file($filename)) {
|
||||
$file = CompiledYamlFile::instance($filename);
|
||||
$blueprints = $file->content();
|
||||
} else {
|
||||
// throw new \RuntimeException("Blueprints for '{$type}' cannot be found! {$this->search}{$type}");
|
||||
$blueprints = array();
|
||||
$blueprints = [];
|
||||
}
|
||||
|
||||
$blueprint = new Blueprint($type, $blueprints, $this);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Filesystem\FileInterface;
|
||||
use \Grav\Common\Getters;
|
||||
use \Grav\Common\Filesystem\File;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Countable;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
|
||||
/**
|
||||
* Recursive data object
|
||||
@@ -11,8 +13,10 @@ use \Grav\Common\Filesystem\File;
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Data extends Getters implements DataInterface
|
||||
class Data implements DataInterface
|
||||
{
|
||||
use ArrayAccessWithGetters, Countable, Export;
|
||||
|
||||
protected $gettersVariable = 'items';
|
||||
protected $items;
|
||||
|
||||
@@ -22,7 +26,7 @@ class Data extends Getters implements DataInterface
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @var File\General
|
||||
* @var File
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
@@ -121,17 +125,64 @@ class Data extends Getters implements DataInterface
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $default Default value (or null).
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return mixed Value.
|
||||
*/
|
||||
public function def($name, $default = null, $separator = '.')
|
||||
{
|
||||
$this->set($name, $this->get($name, $default, $separator), $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two values together by using blueprints if available.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
*/
|
||||
public function join($name, $value, $separator = '.')
|
||||
{
|
||||
$old = $this->get($name, null, $separator);
|
||||
if ($old === null) {
|
||||
// Variable does not exist yet: just use the incoming value.
|
||||
} elseif ($this->blueprints) {
|
||||
// Blueprints: join values by using blueprints.
|
||||
$value = $this->blueprints->mergeData($old, $value, $name, $separator);
|
||||
} else {
|
||||
// No blueprints: replace existing top level variables with the new ones.
|
||||
$value = array_merge($old, $value);
|
||||
}
|
||||
|
||||
$this->set($name, $value, $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two values together by using blueprints if available.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value Value to be joined.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
*/
|
||||
public function joinDefaults($name, $value, $separator = '.')
|
||||
{
|
||||
$old = $this->get($name, null, $separator);
|
||||
if ($old === null) {
|
||||
// Variable does not exist yet: just use the incoming value.
|
||||
} elseif ($this->blueprints) {
|
||||
// Blueprints: join values by using blueprints.
|
||||
$value = $this->blueprints->mergeData($value, $old, $name, $separator);
|
||||
} else {
|
||||
// No blueprints: replace existing top level variables with the new ones.
|
||||
$value = array_merge($value, $old);
|
||||
}
|
||||
|
||||
$this->set($name, $value, $separator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Merge two sets of data together.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function merge(array $data)
|
||||
{
|
||||
@@ -142,6 +193,21 @@ class Data extends Getters implements DataInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default data to the set.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaults(array $data)
|
||||
{
|
||||
if ($this->blueprints) {
|
||||
$this->items = $this->blueprints->mergeData($data, $this->items);
|
||||
} else {
|
||||
$this->items = array_merge($data, $this->items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return blueprints.
|
||||
*
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Filesystem\FileInterface;
|
||||
use \Grav\Common\Filesystem\File;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
|
||||
/**
|
||||
* Data interface
|
||||
|
||||
@@ -114,12 +114,6 @@ class Validation
|
||||
|
||||
protected static function filterText($value, array $params, array $field)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
var_dump($value);
|
||||
var_dump($params);
|
||||
var_dump($field);
|
||||
die();
|
||||
}
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
|
||||
121
system/src/Grav/Common/Debugger.php
Normal file
121
system/src/Grav/Common/Debugger.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use DebugBar\Bridge\Twig\TraceableTwigEnvironment;
|
||||
use DebugBar\JavascriptRenderer;
|
||||
use DebugBar\StandardDebugBar;
|
||||
//use \Tracy\Debugger as TracyDebugger;
|
||||
|
||||
/**
|
||||
* Class Debugger
|
||||
* @package Grav\Common
|
||||
*/
|
||||
class Debugger
|
||||
{
|
||||
protected $grav;
|
||||
protected $debugbar;
|
||||
protected $renderer;
|
||||
protected $enabled;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->debugbar = new StandardDebugBar();
|
||||
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->grav = Grav::instance();
|
||||
|
||||
if ($this->enabled()) {
|
||||
$this->debugbar->addCollector(new \DebugBar\DataCollector\ConfigCollector((array)$this->grav['config']->get('system')));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function enabled($state = null)
|
||||
{
|
||||
if (isset($state)) {
|
||||
$this->enabled = $state;
|
||||
} else {
|
||||
if (!isset($this->enabled)) {
|
||||
$this->enabled = $this->grav['config']->get('system.debugger.enabled');
|
||||
}
|
||||
}
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function addAssets()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
|
||||
$assets = $this->grav['assets'];
|
||||
|
||||
$this->renderer = $this->debugbar->getJavascriptRenderer();
|
||||
$this->renderer->setIncludeVendors(false);
|
||||
|
||||
// Get the required CSS files
|
||||
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
|
||||
foreach ($css_files as $css) {
|
||||
$assets->addCss($css);
|
||||
}
|
||||
|
||||
$assets->addCss('/system/assets/debugger.css');
|
||||
|
||||
foreach ($js_files as $js) {
|
||||
$assets->addJs($js);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCollector($collector)
|
||||
{
|
||||
$this->debugbar->addCollector($collector);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCollector($collector)
|
||||
{
|
||||
return $this->debugbar->getCollector($collector);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
echo $this->renderer->render();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sendDataInHeaders()
|
||||
{
|
||||
$this->debugbar->sendDataInHeaders();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function startTimer($name, $desription = null)
|
||||
{
|
||||
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
|
||||
$this->debugbar['time']->startMeasure($name, $desription);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function stopTimer($name)
|
||||
{
|
||||
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
|
||||
$this->debugbar['time']->stopMeasure($name);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function addMessage($message, $label = 'info', $isString = true)
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
$this->debugbar['messages']->addMessage($message, $label, $isString);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
55
system/src/Grav/Common/Errors/Errors.php
Normal file
55
system/src/Grav/Common/Errors/Errors.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Whoops\Handler\CallbackHandler;
|
||||
use Whoops\Handler\HandlerInterface;
|
||||
use Whoops\Handler\JsonResponseHandler;
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Handler\PlainTextHandler;
|
||||
use Whoops\Run;
|
||||
|
||||
/**
|
||||
* Class Debugger
|
||||
* @package Grav\Common
|
||||
*/
|
||||
class Errors extends \Whoops\Run
|
||||
{
|
||||
|
||||
public function pushHandler($handler, $key = null)
|
||||
{
|
||||
if (is_callable($handler)) {
|
||||
$handler = new CallbackHandler($handler);
|
||||
}
|
||||
|
||||
if (!$handler instanceof HandlerInterface) {
|
||||
throw new InvalidArgumentException(
|
||||
"Argument to " . __METHOD__ . " must be a callable, or instance of"
|
||||
. "Whoops\\Handler\\HandlerInterface"
|
||||
);
|
||||
}
|
||||
|
||||
// Store with key if provided
|
||||
if ($key) {
|
||||
$this->handlerStack[$key] = $handler;
|
||||
} else {
|
||||
$this->handlerStack[] = $handler;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resetHandlers()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$config = $grav['config']->get('system.errors');
|
||||
if (isset($config['display']) && !$config['display']) {
|
||||
unset($this->handlerStack['pretty']);
|
||||
$this->handlerStack = array('simple' => new SimplePageHandler()) + $this->handlerStack;
|
||||
}
|
||||
if (isset($config['log']) && !$config['log']) {
|
||||
unset($this->handlerStack['log']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
51
system/src/Grav/Common/Errors/Resources/error.css
Normal file
51
system/src/Grav/Common/Errors/Resources/error.css
Normal file
@@ -0,0 +1,51 @@
|
||||
html, body {
|
||||
height: 100%
|
||||
}
|
||||
body {
|
||||
margin:0 3rem;
|
||||
padding:0;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
|
||||
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
|
||||
display: -ms-flexbox; /* TWEENER - IE 10 */
|
||||
display: -webkit-flex; /* NEW - Chrome */
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container {
|
||||
margin: 0rem;
|
||||
max-width: 600px;
|
||||
padding-bottom:5rem;
|
||||
}
|
||||
|
||||
header {
|
||||
color: #000;
|
||||
font-size: 4rem;
|
||||
letter-spacing: 2px;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
p {
|
||||
font-family: Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
code {
|
||||
font-weight: bold;
|
||||
}
|
||||
26
system/src/Grav/Common/Errors/Resources/layout.html.php
Normal file
26
system/src/Grav/Common/Errors/Resources/layout.html.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Layout template file for Whoops's pretty error output.
|
||||
*/
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Whoops there was an error!</title>
|
||||
<style><?php echo $stylesheet ?></style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="details">
|
||||
<header>
|
||||
Server Error
|
||||
</header>
|
||||
<p>We're sorry! The server has encountered an internal error and was unable to complete your request.
|
||||
Please contact the system administrator for more information.</p>
|
||||
<h6>For further details please review your <code>logs/</code> folder, or enable displaying of errors in your system configuration.</h6>
|
||||
<h6>Error Code: <b><?php echo $code ?></b></h6>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
91
system/src/Grav/Common/Errors/SimplePageHandler.php
Normal file
91
system/src/Grav/Common/Errors/SimplePageHandler.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use Whoops\Handler\Handler;
|
||||
use Whoops\Util\Misc;
|
||||
use Whoops\Util\TemplateHelper;
|
||||
|
||||
class SimplePageHandler extends Handler
|
||||
{
|
||||
private $searchPaths = array();
|
||||
private $resourceCache = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Add the default, local resource search path:
|
||||
$this->searchPaths[] = __DIR__ . "/Resources";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$exception = $this->getException();
|
||||
$inspector = $this->getInspector();
|
||||
$run = $this->getRun();
|
||||
|
||||
$helper = new TemplateHelper();
|
||||
$templateFile = $this->getResource("layout.html.php");
|
||||
$cssFile = $this->getResource("error.css");
|
||||
|
||||
$code = $inspector->getException()->getCode();
|
||||
|
||||
if ($inspector->getException() instanceof \ErrorException) {
|
||||
$code = Misc::translateErrorCode($code);
|
||||
}
|
||||
|
||||
$vars = array(
|
||||
"stylesheet" => file_get_contents($cssFile),
|
||||
"code" => $code,
|
||||
);
|
||||
|
||||
$helper->setVariables($vars);
|
||||
$helper->render($templateFile);
|
||||
|
||||
return Handler::QUIT;
|
||||
}
|
||||
|
||||
protected function getResource($resource)
|
||||
{
|
||||
// If the resource was found before, we can speed things up
|
||||
// by caching its absolute, resolved path:
|
||||
if (isset($this->resourceCache[$resource])) {
|
||||
return $this->resourceCache[$resource];
|
||||
}
|
||||
|
||||
// Search through available search paths, until we find the
|
||||
// resource we're after:
|
||||
foreach ($this->searchPaths as $path) {
|
||||
$fullPath = $path . "/$resource";
|
||||
|
||||
if (is_file($fullPath)) {
|
||||
// Cache the result:
|
||||
$this->resourceCache[$resource] = $fullPath;
|
||||
return $fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, nothing was found.
|
||||
throw new RuntimeException(
|
||||
"Could not find resource '$resource' in any resource paths."
|
||||
. "(searched: " . join(", ", $this->searchPaths). ")"
|
||||
);
|
||||
}
|
||||
|
||||
public function addResourcePath($path)
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
throw new InvalidArgumentException(
|
||||
"'$path' is not a valid directory"
|
||||
);
|
||||
}
|
||||
|
||||
array_unshift($this->searchPaths, $path);
|
||||
}
|
||||
|
||||
public function getResourcePaths()
|
||||
{
|
||||
return $this->searchPaths;
|
||||
}
|
||||
}
|
||||
70
system/src/Grav/Common/File/CompiledFile.php
Normal file
70
system/src/Grav/Common/File/CompiledFile.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
|
||||
/**
|
||||
* Class CompiledFile
|
||||
* @package Grav\Common\File
|
||||
*
|
||||
* @property string $filename
|
||||
* @property string $extension
|
||||
* @property string $raw
|
||||
* @property array|string $content
|
||||
*/
|
||||
trait CompiledFile
|
||||
{
|
||||
/**
|
||||
* Get/set parsed file contents.
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return string
|
||||
*/
|
||||
public function content($var = null)
|
||||
{
|
||||
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
||||
if ($var === null && $this->raw === null && $this->content === null) {
|
||||
$key = md5($this->filename);
|
||||
$file = PhpFile::instance(CACHE_DIR . "/compiled/files/{$key}{$this->extension}.php");
|
||||
$modified = $this->modified();
|
||||
|
||||
if (!$modified) {
|
||||
return $this->decode($this->raw());
|
||||
}
|
||||
|
||||
$class = get_class($this);
|
||||
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (
|
||||
!isset($cache['@class'])
|
||||
|| $cache['@class'] != $class
|
||||
|| $cache['modified'] != $modified
|
||||
|| $cache['filename'] != $this->filename
|
||||
) {
|
||||
// Attempt to lock the file for writing.
|
||||
$file->lock(false);
|
||||
|
||||
// Decode RAW file into compiled array.
|
||||
$data = $this->decode($this->raw());
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'filename' => $this->filename,
|
||||
'modified' => $modified,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
$this->content = $cache['data'];
|
||||
}
|
||||
|
||||
return parent::content($var);
|
||||
}
|
||||
}
|
||||
9
system/src/Grav/Common/File/CompiledMarkdownFile.php
Normal file
9
system/src/Grav/Common/File/CompiledMarkdownFile.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
|
||||
class CompiledMarkdownFile extends MarkdownFile
|
||||
{
|
||||
use CompiledFile;
|
||||
}
|
||||
9
system/src/Grav/Common/File/CompiledYamlFile.php
Normal file
9
system/src/Grav/Common/File/CompiledYamlFile.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
class CompiledYamlFile extends YamlFile
|
||||
{
|
||||
use CompiledFile;
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* File handling class.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Config extends General
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '.php';
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Saves configuration file and invalidates opcache.
|
||||
*
|
||||
* @param mixed $data Optional data to be saved, usually array.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data = null)
|
||||
{
|
||||
parent::save($data);
|
||||
|
||||
// Invalidate configuration file from the opcache.
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
// PHP 5.5.5+
|
||||
@opcache_invalidate($this->filename);
|
||||
} elseif (function_exists('apc_invalidate')) {
|
||||
// APC
|
||||
@apc_invalidate($this->filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param \Grav\Common\Config $var
|
||||
* @return \Grav\Common\Config
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
if (!($var instanceof \Grav\Common\Config)) {
|
||||
throw new \RuntimeException('Provided data is not configuration');
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode configuration object into RAW string (PHP class).
|
||||
*
|
||||
* @param \Grav\Common\Config $var
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
if (!($var instanceof \Grav\Common\Config)) {
|
||||
throw new \RuntimeException('Provided data is not configuration');
|
||||
}
|
||||
|
||||
// Build the object variables string
|
||||
$vars = array();
|
||||
$options = $var->toArray();
|
||||
|
||||
foreach ($options as $k => $v) {
|
||||
if (is_int($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = " . $v . ";";
|
||||
} elseif (is_bool($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = " . ($v ? 'true' : 'false') . ";";
|
||||
} elseif (is_scalar($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = '" . addcslashes($v, '\\\'') . "';";
|
||||
} elseif (is_array($v) || is_object($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = " . $this->encodeArray((array) $v) . ";";
|
||||
}
|
||||
}
|
||||
$vars = implode("\n", $vars);
|
||||
|
||||
return "<?php\nnamespace Grav;\n\nclass Config extends \\Grav\\Common\\Config {\n {$vars}\n}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get an array as an exported string.
|
||||
*
|
||||
* @param array $a The array to get as a string.
|
||||
* @param int $level Used internally to indent rows.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function encodeArray($a, $level = 1)
|
||||
{
|
||||
$r = array();
|
||||
foreach ($a as $k => $v) {
|
||||
if (is_array($v) || is_object($v)) {
|
||||
$r[] = '"' . $k . '" => ' . $this->encodeArray((array) $v, $level+1);
|
||||
} elseif (is_int($v)) {
|
||||
$r[] = "'" . $k . "' => " . $v;
|
||||
} elseif (is_bool($v)) {
|
||||
$r[] = "'" . $k . "' => " . ($v ? 'true' : 'false');
|
||||
} else {
|
||||
$r[] .= "'" . $k . "' => " . "'" . addslashes($v) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
$tabs = str_repeat("\t", $level);
|
||||
return "array(\n\t{$tabs}" . implode(",\n\t{$tabs}", $r) . "\n{$tabs})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return \Grav\Common\Config
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
// TODO: improve this one later, works only for single file...
|
||||
return class_exists('\Grav\Config') ? new \Grav\Config($this->filename) : new Config($this->filename);
|
||||
}
|
||||
}
|
||||
@@ -1,352 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
use Grav\Common\Filesystem\FileInterface;
|
||||
|
||||
/**
|
||||
* General file handling class.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class General implements FileInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $locked;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var string Raw file contents.
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* @var array Parsed file contents.
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Get file instance.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return FileInterface
|
||||
*/
|
||||
public static function instance($filename)
|
||||
{
|
||||
if (!isset(static::$instances[$filename])) {
|
||||
static::$instances[$filename] = new static;
|
||||
static::$instances[$filename]->init($filename);
|
||||
}
|
||||
return static::$instances[$filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent constructor from being used.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent cloning.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function __clone()
|
||||
{
|
||||
//Me not like clones! Me smash clones!
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filename.
|
||||
*
|
||||
* @param $filename
|
||||
*/
|
||||
protected function init($filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the file location.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function filename($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->filename = $var;
|
||||
}
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return basename of the file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function basename()
|
||||
{
|
||||
return basename($this->filename, $this->extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file exits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
return is_file($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int|bool Timestamp or false if file doesn't exist.
|
||||
*/
|
||||
public function modified()
|
||||
{
|
||||
return is_file($this->filename) ? filemtime($this->filename) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock file for writing. You need to manually unlock().
|
||||
*
|
||||
* @param bool $block For non-blocking lock, set the parameter to false.
|
||||
* @return bool
|
||||
*/
|
||||
public function lock($block = true)
|
||||
{
|
||||
if (!$this->handle) {
|
||||
if (!$this->mkdir(dirname($this->filename))) {
|
||||
throw new \RuntimeException('Creating directory failed for ' . $this->filename);
|
||||
}
|
||||
$this->handle = fopen($this->filename, 'wb+');
|
||||
}
|
||||
$lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;
|
||||
return $this->locked = flock($this->handle, $lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if file has been locked for writing.
|
||||
*
|
||||
* @return bool|null True = locked, false = failed, null = not locked.
|
||||
*/
|
||||
public function locked()
|
||||
{
|
||||
return $this->locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock()
|
||||
{
|
||||
if (!$this->handle) {
|
||||
return;
|
||||
}
|
||||
if ($this->locked) {
|
||||
flock($this->handle, LOCK_UN);
|
||||
$this->locked = null;
|
||||
}
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file can be written.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function writable()
|
||||
{
|
||||
return is_writable($this->filename) || $this->writableDir(dirname($this->filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Load a file and return RAW file contents.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
$this->raw = $this->exists() ? (string) file_get_contents($this->filename) : '';
|
||||
$this->content = null;
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set raw file contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function raw($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->raw = (string) $var;
|
||||
$this->content = null;
|
||||
}
|
||||
|
||||
if (!is_string($this->raw)) {
|
||||
$this->raw = $this->load();
|
||||
}
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set parsed file contents.
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return string
|
||||
*/
|
||||
public function content($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->content = $this->check($var);
|
||||
|
||||
// Update RAW, too.
|
||||
$this->raw = $this->encode($this->content);
|
||||
|
||||
} elseif ($this->content === null) {
|
||||
// Decode RAW file.
|
||||
$this->content = $this->decode($this->raw());
|
||||
}
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save file.
|
||||
*
|
||||
* @param mixed $data Optional data to be saved, usually array.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data = null)
|
||||
{
|
||||
if ($data !== null) {
|
||||
$this->content($data);
|
||||
}
|
||||
|
||||
if (!$this->locked) {
|
||||
// Obtain blocking lock or fail.
|
||||
if (!$this->lock()) {
|
||||
throw new \RuntimeException('Obtaining write lock failed on file: ' . $this->filename);
|
||||
}
|
||||
$lock = true;
|
||||
}
|
||||
|
||||
if (@fwrite($this->handle, $this->raw()) === false) {
|
||||
$this->unlock();
|
||||
throw new \RuntimeException('Saving file failed: ' . $this->filename);
|
||||
}
|
||||
|
||||
if (isset($lock)) {
|
||||
$this->unlock();
|
||||
}
|
||||
|
||||
// Touch the directory as well, thus marking it modified.
|
||||
@touch(dirname($this->filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file from filesystem.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
return unlink($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* Override in derived class.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (string) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* Override in derived class.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
return (string) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* Override in derived class.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
return (string) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
protected function mkdir($dir)
|
||||
{
|
||||
return is_dir($dir) || mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
protected function writableDir($dir)
|
||||
{
|
||||
if ($dir && !file_exists($dir)) {
|
||||
return $this->writableDir(dirname($dir));
|
||||
}
|
||||
|
||||
return $dir && is_dir($dir) && is_writable($dir);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* File handling class for JSON.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Json extends General
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '.json';
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (array) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
return (string) json_encode($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
return (array) json_decode($var);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* File handling class for Log files.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Log extends General
|
||||
{
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->extension = '.log';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (array) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string (unsupported).
|
||||
*
|
||||
* @param string $var
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
throw new \Exception('Saving log file is forbidden.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
$lines = (array) preg_split('#(\r\n|\n|\r)#', $var);
|
||||
|
||||
$results = array();
|
||||
foreach ($lines as $line) {
|
||||
preg_match('#^\[(.*)\] (.*) @ (.*) @@ (.*)$#', $line, $matches);
|
||||
if ($matches) {
|
||||
$results[] = ['date' => $matches[1], 'message' => $matches[2], 'url' => $matches[3], 'file' => $matches[4]];
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
|
||||
/**
|
||||
* File handling class.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Markdown extends General
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '.md';
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Get/set file header.
|
||||
*
|
||||
* @param array $var
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function header(array $var = null)
|
||||
{
|
||||
$content = $this->content();
|
||||
|
||||
if ($var !== null) {
|
||||
$content['header'] = $var;
|
||||
$this->content($content);
|
||||
}
|
||||
|
||||
return $content['header'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set markdown content.
|
||||
*
|
||||
* @param string $var
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function markdown($var = null)
|
||||
{
|
||||
$content = $this->content();
|
||||
|
||||
if ($var !== null) {
|
||||
$content['markdown'] = (string) $var;
|
||||
$this->content($content);
|
||||
}
|
||||
|
||||
return $content['markdown'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
$var = (array) $var;
|
||||
if (!isset($var['header']) || !is_array($var['header'])) {
|
||||
$var['header'] = array();
|
||||
}
|
||||
if (!isset($var['markdown']) || !is_string($var['markdown'])) {
|
||||
$var['markdown'] = '';
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
// Create Markdown file with YAML header.
|
||||
$o = (!empty($var['header']) ? "---\n" . trim(YamlParser::dump($var['header'])) . "\n---\n\n" : '') . $var['markdown'];
|
||||
|
||||
// Normalize line endings to Unix style.
|
||||
$o = preg_replace("/(\r\n|\r)/", "\n", $o);
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
$content = array();
|
||||
|
||||
// Normalize line endings to Unix style.
|
||||
$var = preg_replace("/(\r\n|\r)/", "\n", $var);
|
||||
|
||||
// Parse header.
|
||||
preg_match("/---\n(.+?)\n---(\n\n|$)/uism", $this->raw(), $m);
|
||||
$content['header'] = isset($m[1]) ? YamlParser::parse(preg_replace("/\n\t/", "\n ", $m[1])) : array();
|
||||
|
||||
// Strip header to get content.
|
||||
$content['markdown'] = trim(preg_replace("/---\n(.+?)\n---(\n\n|$)/uism", '', $var));
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
|
||||
/**
|
||||
* File handling class for YAML.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Yaml extends General
|
||||
{
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->extension = YAML_EXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (array) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
return (string) YamlParser::dump($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
return (array) YamlParser::parse($var);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
/**
|
||||
* File interface.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
interface FileInterface
|
||||
{
|
||||
/**
|
||||
* Get file instance.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return mixed
|
||||
*/
|
||||
public static function instance($filename);
|
||||
|
||||
/**
|
||||
* Check if file exits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists();
|
||||
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int Timestamp
|
||||
*/
|
||||
public function modified();
|
||||
|
||||
/**
|
||||
* Lock file for writing. Lock gets automatically released during the save().
|
||||
*
|
||||
* @param bool $block For non-blocking lock, set the parameter to false.
|
||||
* @return bool
|
||||
*/
|
||||
public function lock($block = true);
|
||||
|
||||
/**
|
||||
* Returns true if file has been locked for writing.
|
||||
*
|
||||
* @return bool|null True = locked, false = failed, null = not locked.
|
||||
*/
|
||||
public function locked();
|
||||
|
||||
/**
|
||||
* Unlock file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock();
|
||||
|
||||
/**
|
||||
* Check if file can be written.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function writable();
|
||||
|
||||
/**
|
||||
* (Re)Load a file and return its contents.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function load();
|
||||
|
||||
/**
|
||||
* Get/set raw file contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function raw($var = null);
|
||||
|
||||
/**
|
||||
* Get/set parsed file contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function content($var = null);
|
||||
|
||||
/**
|
||||
* Save file.
|
||||
*
|
||||
* @param string $data Optional data to be saved.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data = null);
|
||||
|
||||
/**
|
||||
* Delete file from filesystem.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete();
|
||||
}
|
||||
@@ -12,16 +12,16 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively find the last modified time under given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModified($path)
|
||||
public static function lastModifiedFolder($path)
|
||||
{
|
||||
$last_modified = 0;
|
||||
|
||||
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$dir_modified = $file->getMTime();
|
||||
@@ -29,34 +29,98 @@ abstract class Folder
|
||||
$last_modified = $dir_modified;
|
||||
}
|
||||
}
|
||||
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative path between target and base path. If path isn't relative, return full path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $base
|
||||
* @return string
|
||||
*/
|
||||
public static function getRelativePath($path, $base = GRAV_ROOT)
|
||||
{
|
||||
if ($base) {
|
||||
$base = preg_replace('![\\|/]+!', '/', $base);
|
||||
$path = preg_replace('![\\|/]+!', '/', $path);
|
||||
if (strpos($path, $base) === 0) {
|
||||
$path = ltrim(substr($path, strlen($base)), '/');
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift first directory out of the path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function shift(&$path)
|
||||
{
|
||||
$parts = explode('/', trim($path, '/'), 2);
|
||||
$result = array_shift($parts);
|
||||
$path = array_shift($parts);
|
||||
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find the last modified time under given path by file.
|
||||
*
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFile($path)
|
||||
{
|
||||
$last_modified = 0;
|
||||
|
||||
$dirItr = new \RecursiveDirectoryIterator($path);
|
||||
$filterItr = new GravRecursiveFilterIterator($dirItr);
|
||||
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($itr as $file) {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
}
|
||||
}
|
||||
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return recursive list of all files and directories under given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $params
|
||||
* @param string $path
|
||||
* @param array $params
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function all($path, array $params = array())
|
||||
{
|
||||
$path = realpath($path);
|
||||
|
||||
if ($path === false) {
|
||||
throw new \RuntimeException("Path to {$path} doesn't exist.");
|
||||
}
|
||||
|
||||
$compare = $params['compare'] ? 'get' . $params['compare'] : null;
|
||||
$pattern = $params['pattern'] ? $params['pattern'] : null;
|
||||
$filters = $params['filters'] ? $params['filters'] : null;
|
||||
$key = $params['key'] ? 'get' . $params['key'] : null;
|
||||
$value = $params['value'] ? 'get' . $params['value'] : 'SubPathname';
|
||||
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
|
||||
$pattern = isset($params['pattern']) ? $params['pattern'] : null;
|
||||
$filters = isset($params['filters']) ? $params['filters'] : null;
|
||||
$recursive = isset($params['recursive']) ? $params['recursive'] : true;
|
||||
$key = isset($params['key']) ? 'get' . $params['key'] : null;
|
||||
$value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
|
||||
|
||||
$directory = new \RecursiveDirectoryIterator($path,
|
||||
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
if ($recursive) {
|
||||
$directory = new \RecursiveDirectoryIterator($path,
|
||||
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
} else {
|
||||
$iterator = new \FilesystemIterator($path);
|
||||
}
|
||||
|
||||
$results = array();
|
||||
|
||||
@@ -72,20 +136,30 @@ abstract class Folder
|
||||
$fileKey = preg_replace($filters['key'], '', $fileKey);
|
||||
}
|
||||
if (isset($filters['value'])) {
|
||||
$filePath = preg_replace($filters['value'], '', $filePath);
|
||||
$filter = $filters['value'];
|
||||
if (is_callable($filter)) {
|
||||
$filePath = call_user_func($filter, $file);
|
||||
} else {
|
||||
$filePath = preg_replace($filter, '', $filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fileKey !== null) {
|
||||
$results[$fileKey] = $filePath;
|
||||
} else {
|
||||
$results[] = $filePath;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively copy directory in filesystem.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function copy($source, $target)
|
||||
@@ -129,8 +203,8 @@ abstract class Folder
|
||||
/**
|
||||
* Move directory in filesystem.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function move($source, $target)
|
||||
@@ -160,6 +234,7 @@ abstract class Folder
|
||||
*
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($target)
|
||||
{
|
||||
@@ -176,6 +251,26 @@ abstract class Folder
|
||||
|
||||
// Make sure that the change will be detected when caching.
|
||||
@touch(dirname($target));
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
public static function mkdir($folder)
|
||||
{
|
||||
if (is_dir($folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$success = @mkdir($folder, 0777, true);
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,23 +294,21 @@ abstract class Folder
|
||||
|
||||
return @rmdir($folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected static function mkdir($folder)
|
||||
{
|
||||
if (is_dir($folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$success = @mkdir($folder, 0777, true);
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GravRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
public static $FILTERS = array(
|
||||
'..', '.DS_Store'
|
||||
);
|
||||
|
||||
public function accept()
|
||||
{
|
||||
return !in_array(
|
||||
$this->current()->getFilename(),
|
||||
self::$FILTERS,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
364
system/src/Grav/Common/GPM/GPM.php
Normal file
364
system/src/Grav/Common/GPM/GPM.php
Normal file
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class GPM extends Iterator
|
||||
{
|
||||
/**
|
||||
* Local installed Packages
|
||||
* @var Local\Packages
|
||||
*/
|
||||
private $installed;
|
||||
|
||||
/**
|
||||
* Remote available Packages
|
||||
* @var Remote\Packages
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var Remote\Grav
|
||||
*/
|
||||
public $grav;
|
||||
|
||||
/**
|
||||
* Internal cache
|
||||
* @var
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Creates a new GPM instance with Local and Remote packages available
|
||||
* @param boolean $refresh Applies to Remote Packages only and forces a refetch of data
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$this->installed = new Local\Packages();
|
||||
$this->repository = new Remote\Packages($refresh, $callback);
|
||||
$this->grav = new Remote\Grav($refresh, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installed packages
|
||||
* @return Iterator The installed packages
|
||||
*/
|
||||
public function getInstalled()
|
||||
{
|
||||
return $this->installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of locally installed packages
|
||||
* @return integer Amount of installed packages
|
||||
*/
|
||||
public function countInstalled()
|
||||
{
|
||||
$installed = $this->getInstalled();
|
||||
|
||||
return count($installed['plugins']) + count($installed['themes']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance of a specific Plugin
|
||||
* @param string $slug The slug of the Plugin
|
||||
* @return Local\Package The instance of the Plugin
|
||||
*/
|
||||
public function getInstalledPlugin($slug)
|
||||
{
|
||||
return $this->installed['plugins'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installed plugins
|
||||
* @return Iterator The installed plugins
|
||||
*/
|
||||
public function getInstalledPlugins()
|
||||
{
|
||||
return $this->installed['plugins'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Plugin is installed
|
||||
* @param string $slug The slug of the Plugin
|
||||
* @return boolean True if the Plugin has been installed. False otherwise
|
||||
*/
|
||||
public function isPluginInstalled($slug)
|
||||
{
|
||||
return isset($this->installed['plugins'][$slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance of a specific Theme
|
||||
* @param string $slug The slug of the Theme
|
||||
* @return Local\Package The instance of the Theme
|
||||
*/
|
||||
public function getInstalledTheme($slug)
|
||||
{
|
||||
return $this->installed['themes'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installed themes
|
||||
* @return Iterator The installed themes
|
||||
*/
|
||||
public function getInstalledThemes()
|
||||
{
|
||||
return $this->installed['themes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Theme is installed
|
||||
* @param string $slug The slug of the Theme
|
||||
* @return boolean True if the Theme has been installed. False otherwise
|
||||
*/
|
||||
public function isThemeInstalled($slug)
|
||||
{
|
||||
return isset($this->installed['themes'][$slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of updates available
|
||||
* @return integer Amount of available updates
|
||||
*/
|
||||
public function countUpdates()
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
$count += count($this->getUpdatablePlugins());
|
||||
$count += count($this->getUpdatableThemes());
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Plugins and Themes that can be updated.
|
||||
* Plugins and Themes are extended with the `available` property that relies to the remote version
|
||||
* @return array Array of updatable Plugins and Themes.
|
||||
* Format: ['total' => int, 'plugins' => array, 'themes' => array]
|
||||
*/
|
||||
public function getUpdatable()
|
||||
{
|
||||
$plugins = $this->getUpdatablePlugins();
|
||||
$themes = $this->getUpdatableThemes();
|
||||
|
||||
$items = [
|
||||
'total' => count($plugins)+count($themes),
|
||||
'plugins' => $plugins,
|
||||
'themes' => $themes
|
||||
];
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Plugins that can be updated.
|
||||
* The Plugins are extended with the `available` property that relies to the remote version
|
||||
* @return Iterator Array of updatable Plugins
|
||||
*/
|
||||
public function getUpdatablePlugins()
|
||||
{
|
||||
$items = [];
|
||||
$repository = $this->repository['plugins'];
|
||||
|
||||
// local cache to speed things up
|
||||
if (isset($this->cache[__METHOD__])) {
|
||||
return $this->cache[__METHOD__];
|
||||
}
|
||||
|
||||
foreach ($this->installed['plugins'] as $slug => $plugin) {
|
||||
if (!isset($repository[$slug]) || $plugin->symlink) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$local_version = $plugin->version ? $plugin->version : 'Unknown';
|
||||
$remote_version = $repository[$slug]->version;
|
||||
|
||||
if (version_compare($local_version, $remote_version) < 0) {
|
||||
$repository[$slug]->available = $remote_version;
|
||||
$repository[$slug]->version = $local_version;
|
||||
$items[$slug] = $repository[$slug];
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache[__METHOD__] = $items;
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Plugin or Theme is updatable
|
||||
* @param string $slug The slug of the package
|
||||
* @return boolean True if updatable. False otherwise or if not found
|
||||
*/
|
||||
public function isUpdatable($slug)
|
||||
{
|
||||
return $this->isPluginUpdatable($slug) || $this->isThemeUpdatable($slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Plugin is updatable
|
||||
* @param string $plugin The slug of the Plugin
|
||||
* @return boolean True if the Plugin is updatable. False otherwise
|
||||
*/
|
||||
public function isPluginUpdatable($plugin)
|
||||
{
|
||||
return array_key_exists($plugin, (array) $this->getUpdatablePlugins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Themes that can be updated.
|
||||
* The Themes are extended with the `available` property that relies to the remote version
|
||||
* @return Iterator Array of updatable Themes
|
||||
*/
|
||||
public function getUpdatableThemes()
|
||||
{
|
||||
$items = [];
|
||||
$repository = $this->repository['themes'];
|
||||
|
||||
// local cache to speed things up
|
||||
if (isset($this->cache[__METHOD__])) {
|
||||
return $this->cache[__METHOD__];
|
||||
}
|
||||
|
||||
foreach ($this->installed['themes'] as $slug => $plugin) {
|
||||
if (!isset($repository[$slug]) || $plugin->symlink) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$local_version = $plugin->version ? $plugin->version : 'Unknown';
|
||||
$remote_version = $repository[$slug]->version;
|
||||
|
||||
if (version_compare($local_version, $remote_version) < 0) {
|
||||
$repository[$slug]->available = $remote_version;
|
||||
$repository[$slug]->version = $local_version;
|
||||
$items[$slug] = $repository[$slug];
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache[__METHOD__] = $items;
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Theme is Updatable
|
||||
* @param string $theme The slug of the Theme
|
||||
* @return boolean True if the Theme is updatable. False otherwise
|
||||
*/
|
||||
public function isThemeUpdatable($theme)
|
||||
{
|
||||
return array_key_exists($theme, (array) $this->getUpdatableThemes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Plugin from the repository
|
||||
* @param string $slug The slug of the Plugin
|
||||
* @return mixed Package if found, NULL if not
|
||||
*/
|
||||
public function getRepositoryPlugin($slug)
|
||||
{
|
||||
return @$this->repository['plugins'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Plugins available in the repository
|
||||
* @return Iterator The Plugins remotely available
|
||||
*/
|
||||
public function getRepositoryPlugins()
|
||||
{
|
||||
return $this->repository['plugins'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Theme from the repository
|
||||
* @param string $slug The slug of the Theme
|
||||
* @return mixed Package if found, NULL if not
|
||||
*/
|
||||
public function getRepositoryTheme($slug)
|
||||
{
|
||||
return @$this->repository['themes'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Themes available in the repository
|
||||
* @return Iterator The Themes remotely available
|
||||
*/
|
||||
public function getRepositoryThemes()
|
||||
{
|
||||
return $this->repository['themes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Plugins and Themes available in the repository
|
||||
* @return array Array of available Plugins and Themes
|
||||
* Format: ['plugins' => array, 'themes' => array]
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a Package in the repository
|
||||
* @param string $search Can be either the slug or the name
|
||||
* @return Remote\Package Package if found, FALSE if not
|
||||
*/
|
||||
public function findPackage($search)
|
||||
{
|
||||
$search = strtolower($search);
|
||||
if ($found = $this->getRepositoryTheme($search)) {
|
||||
return $found;
|
||||
}
|
||||
|
||||
if ($found = $this->getRepositoryPlugin($search)) {
|
||||
return $found;
|
||||
}
|
||||
|
||||
foreach ($this->getRepositoryThemes() as $slug => $theme) {
|
||||
if ($search == $slug || $search == $theme->name) {
|
||||
return $theme;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getRepositoryPlugins() as $slug => $plugin) {
|
||||
if ($search == $slug || $search == $plugin->name) {
|
||||
return $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Plugins and Themes available in the repository
|
||||
* @return array Array of available Plugins and Themes
|
||||
* Format: ['plugins' => array, 'themes' => array]
|
||||
*/
|
||||
/**
|
||||
* Searches for a list of Packages in the repository
|
||||
* @param array $searches An array of either slugs or names
|
||||
* @return array Array of found Packages
|
||||
* Format: ['total' => int, 'not_found' => array, <found-slugs>]
|
||||
*/
|
||||
public function findPackages($searches = [])
|
||||
{
|
||||
$packages = ['total' => 0, 'not_found' => []];
|
||||
|
||||
foreach ($searches as $search) {
|
||||
if ($found = $this->findPackage($search)) {
|
||||
if (!isset($packages[$found->package_type])) {
|
||||
$packages[$found->package_type] = [];
|
||||
}
|
||||
|
||||
$packages[$found->package_type][$found->slug] = $found;
|
||||
$packages['total']++;
|
||||
} else {
|
||||
$packages['not_found'][] = $search;
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
}
|
||||
293
system/src/Grav/Common/GPM/Installer.php
Normal file
293
system/src/Grav/Common/GPM/Installer.php
Normal file
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
class Installer
|
||||
{
|
||||
/** @const No error */
|
||||
const OK = 0;
|
||||
/** @const Target already exists */
|
||||
const EXISTS = 1;
|
||||
/** @const Target is a symbolic link */
|
||||
const IS_LINK = 2;
|
||||
/** @const Target doesn't exist */
|
||||
const NOT_FOUND = 4;
|
||||
/** @const Target is not a directory */
|
||||
const NOT_DIRECTORY = 8;
|
||||
/** @const Target is not a Grav instance */
|
||||
const NOT_GRAV_ROOT = 16;
|
||||
/** @const Error while trying to open the ZIP package */
|
||||
const ZIP_OPEN_ERROR = 32;
|
||||
/** @const Error while trying to extract the ZIP package */
|
||||
const ZIP_EXTRACT_ERROR = 64;
|
||||
|
||||
/**
|
||||
* Destination folder on which validation checks are applied
|
||||
* @var string
|
||||
*/
|
||||
protected static $target;
|
||||
|
||||
/**
|
||||
* Error Code
|
||||
* @var integer
|
||||
*/
|
||||
protected static $error = 0;
|
||||
|
||||
/**
|
||||
* Default options for the install
|
||||
* @var array
|
||||
*/
|
||||
protected static $options = [
|
||||
'overwrite' => true,
|
||||
'ignore_symlinks' => true,
|
||||
'sophisticated' => false,
|
||||
'install_path' => '',
|
||||
'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK]
|
||||
];
|
||||
|
||||
/**
|
||||
* Installs a given package to a given destination.
|
||||
*
|
||||
* @param string $package The local path to the ZIP package
|
||||
* @param string $destination The local path to the Grav Instance
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function install($package, $destination, $options = [])
|
||||
{
|
||||
$destination = rtrim($destination, DS);
|
||||
$options = array_merge(self::$options, $options);
|
||||
$install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS);
|
||||
|
||||
if (!self::isGravInstance($destination) || !self::isValidDestination($install_path, $options['exclude_checks'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
|
||||
self::lastErrorCode() == self::EXISTS && !$options['overwrite']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($package);
|
||||
$tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid();
|
||||
|
||||
if ($archive !== true) {
|
||||
self::$error = self::ZIP_OPEN_ERROR;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$unzip = $zip->extractTo($tmp);
|
||||
|
||||
if (!$unzip) {
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
|
||||
$zip->close();
|
||||
Folder::delete($tmp);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!$options['sophisticated']) {
|
||||
self::nonSophisticatedInstall($zip, $install_path, $tmp);
|
||||
} else {
|
||||
self::sophisticatedInstall($zip, $install_path, $tmp);
|
||||
}
|
||||
|
||||
Folder::delete($tmp);
|
||||
$zip->close();
|
||||
|
||||
self::$error = self::OK;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public static function nonSophisticatedInstall($zip, $install_path, $tmp)
|
||||
{
|
||||
$container = $zip->getNameIndex(0); // TODO: better way of determining if zip has container folder
|
||||
if (file_exists($install_path)) {
|
||||
Folder::delete($install_path);
|
||||
}
|
||||
|
||||
Folder::move($tmp . DS . $container, $install_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function sophisticatedInstall($zip, $install_path, $tmp)
|
||||
{
|
||||
for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
|
||||
$filename = $zip->getNameIndex($i);
|
||||
$fileinfo = pathinfo($filename);
|
||||
$depth = count(explode(DS, rtrim($filename, '/')));
|
||||
|
||||
if ($depth > 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $install_path . DS . $fileinfo['basename'];
|
||||
|
||||
if (is_link($path)) {
|
||||
continue;
|
||||
} else {
|
||||
if (is_dir($path)) {
|
||||
Folder::delete($path);
|
||||
Folder::move($tmp . DS . $filename, $path);
|
||||
|
||||
if ($fileinfo['basename'] == 'bin') {
|
||||
foreach (glob($path . DS . '*') as $file) {
|
||||
@chmod($file, 0755);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@unlink($path);
|
||||
@copy($tmp . DS . $filename, $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unnstalls one or more given package
|
||||
*
|
||||
* @param string $package The slug of the package(s)
|
||||
* @param array $options Options to use for uninstalling
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function uninstall($path, $options = [])
|
||||
{
|
||||
$options = array_merge(self::$options, $options);
|
||||
if (!self::isValidDestination($path, $options['exclude_checks'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Folder::delete($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a set of checks on the destination and sets the Error if any
|
||||
*
|
||||
* @param string $destination The directory to run validations at
|
||||
* @param array $exclude An array of constants to exclude from the validation
|
||||
*
|
||||
* @return boolean True if validation passed. False otherwise
|
||||
*/
|
||||
public static function isValidDestination($destination, $exclude = [])
|
||||
{
|
||||
self::$error = 0;
|
||||
self::$target = $destination;
|
||||
|
||||
if (is_link($destination)) {
|
||||
self::$error = self::IS_LINK;
|
||||
} elseif (file_exists($destination)) {
|
||||
self::$error = self::EXISTS;
|
||||
} elseif (!file_exists($destination)) {
|
||||
self::$error = self::NOT_FOUND;
|
||||
} elseif (!is_dir($destination)) {
|
||||
self::$error = self::NOT_DIRECTORY;
|
||||
}
|
||||
|
||||
if (count($exclude) && in_array(self::$error, $exclude)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !(self::$error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given path is a Grav Instance
|
||||
*
|
||||
* @param string $target The local path to the Grav Instance
|
||||
*
|
||||
* @return boolean True if is a Grav Instance. False otherwise
|
||||
*/
|
||||
public static function isGravInstance($target)
|
||||
{
|
||||
self::$error = 0;
|
||||
self::$target = $target;
|
||||
|
||||
if (
|
||||
!file_exists($target . DS . 'index.php') ||
|
||||
!file_exists($target . DS . 'bin') ||
|
||||
!file_exists($target . DS . 'user') ||
|
||||
!file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml')
|
||||
) {
|
||||
self::$error = self::NOT_GRAV_ROOT;
|
||||
}
|
||||
|
||||
return !self::$error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error occurred in a string message format
|
||||
* @return string The message of the last error
|
||||
*/
|
||||
public static function lastErrorMsg()
|
||||
{
|
||||
$msg = 'Unknown Error';
|
||||
|
||||
switch (self::$error) {
|
||||
case 0:
|
||||
$msg = 'No Error';
|
||||
break;
|
||||
|
||||
case self::EXISTS:
|
||||
$msg = 'The target path "' . self::$target . '" already exists';
|
||||
break;
|
||||
|
||||
case self::IS_LINK:
|
||||
$msg = 'The target path "' . self::$target . '" is a symbolic link';
|
||||
break;
|
||||
|
||||
case self::NOT_FOUND:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to exist';
|
||||
break;
|
||||
|
||||
case self::NOT_DIRECTORY:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to be a folder';
|
||||
break;
|
||||
|
||||
case self::NOT_GRAV_ROOT:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance';
|
||||
break;
|
||||
|
||||
case self::ZIP_OPEN_ERROR:
|
||||
$msg = 'Unable to open the package file';
|
||||
break;
|
||||
|
||||
case self::ZIP_EXTRACT_ERROR:
|
||||
$msg = 'An error occurred while extracting the package';
|
||||
break;
|
||||
|
||||
default:
|
||||
return 'Unknown error';
|
||||
break;
|
||||
}
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code of the occurred error
|
||||
* @return integer The code of the last error
|
||||
*/
|
||||
public static function lastErrorCode()
|
||||
{
|
||||
return self::$error;
|
||||
}
|
||||
}
|
||||
32
system/src/Grav/Common/GPM/Local/Collection.php
Normal file
32
system/src/Grav/Common/GPM/Local/Collection.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Collection extends Iterator
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
88
system/src/Grav/Common/GPM/Local/Package.php
Normal file
88
system/src/Grav/Common/GPM/Local/Package.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
/**
|
||||
* Class Package
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Package
|
||||
{
|
||||
/**
|
||||
* @var Data
|
||||
*/
|
||||
protected $data;
|
||||
/**
|
||||
* @var \Grav\Common\Data\Blueprint
|
||||
*/
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @param Data $package
|
||||
* @param bool $package_type
|
||||
*/
|
||||
public function __construct(Data $package, $package_type = false)
|
||||
{
|
||||
$this->data = $package;
|
||||
$this->blueprints = $this->data->blueprints();
|
||||
|
||||
if ($package_type) {
|
||||
$html_description = \Parsedown::instance()->line($this->blueprints->get('description'));
|
||||
$this->blueprints->set('package_type', $package_type);
|
||||
$this->blueprints->set('slug', $this->blueprints->name);
|
||||
$this->blueprints->set('description_html', $html_description);
|
||||
$this->blueprints->set('description_plain', strip_tags($html_description));
|
||||
$this->blueprints->set('symlink', is_link(USER_DIR . $package_type . DS . $this->blueprints->name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->data['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->blueprints->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return $this->blueprints->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->blueprints->toArray();
|
||||
}
|
||||
}
|
||||
28
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
28
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Packages extends Iterator
|
||||
{
|
||||
private $plugins;
|
||||
private $themes;
|
||||
protected static $cache;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[__METHOD__])) {
|
||||
self::$cache[__METHOD__] = [
|
||||
'plugins' => new Plugins(),
|
||||
'themes' => new Themes()
|
||||
];
|
||||
}
|
||||
|
||||
$this->plugins = self::$cache[__METHOD__]['plugins'];
|
||||
$this->themes = self::$cache[__METHOD__]['themes'];
|
||||
|
||||
$this->append(['plugins' => $this->plugins]);
|
||||
$this->append(['themes' => $this->themes]);
|
||||
}
|
||||
}
|
||||
26
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
26
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
/**
|
||||
* Class Plugins
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Plugins extends Collection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type = 'plugins';
|
||||
|
||||
/**
|
||||
* Local Plugins Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$grav = self::$grav;
|
||||
|
||||
foreach ($grav['plugins']->all() as $name => $data) {
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
15
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
class Themes extends Collection
|
||||
{
|
||||
private $type = 'themes';
|
||||
public function __construct()
|
||||
{
|
||||
$grav = self::$grav;
|
||||
|
||||
foreach ($grav['themes']->all() as $name => $data) {
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
70
system/src/Grav/Common/GPM/Remote/Collection.php
Normal file
70
system/src/Grav/Common/GPM/Remote/Collection.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
use \Doctrine\Common\Cache\Cache as DoctrineCache;
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
|
||||
class Collection extends Iterator {
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* The cached data previously fetched
|
||||
* @var string
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* The lifetime to store the entry in seconds
|
||||
* @var integer
|
||||
*/
|
||||
private $lifetime = 86400;
|
||||
private $repository;
|
||||
private $cache;
|
||||
|
||||
private $plugins, $themes;
|
||||
|
||||
public function __construct($repository = null) {
|
||||
if ($repository == null) {
|
||||
throw new \RuntimeException("A repository is required for storing the cache");
|
||||
}
|
||||
|
||||
$cache_dir = self::$grav['locator']->findResource('cache://gpm', true, true);
|
||||
$this->cache = new FilesystemCache($cache_dir);
|
||||
|
||||
$this->repository = $repository;
|
||||
$this->raw = $this->cache->fetch(md5($this->repository));
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function fetch($refresh = false, $callback = null) {
|
||||
if (!$this->raw || $refresh) {
|
||||
$response = Response::get($this->repository, [], $callback);
|
||||
$this->raw = $response;
|
||||
$this->cache->save(md5($this->repository), $this->raw, $this->lifetime);
|
||||
}
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
}
|
||||
86
system/src/Grav/Common/GPM/Remote/Grav.php
Normal file
86
system/src/Grav/Common/GPM/Remote/Grav.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Grav extends Collection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/grav.json';
|
||||
private $data;
|
||||
|
||||
private $version;
|
||||
private $date;
|
||||
|
||||
/**
|
||||
* @param bool $refresh
|
||||
* @param null $callback
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
$this->version = @$this->data->version ?: '-';
|
||||
$this->date = @$this->data->date ?: '-';
|
||||
|
||||
foreach ($this->data->assets as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of assets associated to the latest version of Grav
|
||||
* @return array list of assets
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->data->assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the changelog list for each version of Grav
|
||||
* @param string $diff the version number to start the diff from
|
||||
*
|
||||
* @return array changelog list for each version
|
||||
*/
|
||||
public function getChangelog($diff = null)
|
||||
{
|
||||
if (!$diff) {
|
||||
return $this->data->changelog;
|
||||
}
|
||||
|
||||
$diffLog = [];
|
||||
foreach ($this->data->changelog as $version => $changelog) {
|
||||
preg_match("/[\d\.]+/", $version, $cleanVersion);
|
||||
|
||||
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; }
|
||||
|
||||
$diffLog[$version] = $changelog;
|
||||
}
|
||||
|
||||
return $diffLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest version of Grav available remotely
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the release date of the latest Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function isUpdatable()
|
||||
{
|
||||
return version_compare(GRAV_VERSION, $this->getVersion(), '<');
|
||||
}
|
||||
}
|
||||
32
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
32
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Package {
|
||||
public function __construct($package, $package_type = false) {
|
||||
$this->data = $package;
|
||||
if ($package_type) {
|
||||
$this->data->package_type = $package_type;
|
||||
}
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
return $this->data->$key;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
return json_encode($this->data);
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
}
|
||||
28
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
28
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Packages extends Iterator
|
||||
{
|
||||
private $plugins;
|
||||
private $themes;
|
||||
protected static $cache;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[__METHOD__])) {
|
||||
self::$cache[__METHOD__] = [
|
||||
'plugins' => new Plugins($refresh, $callback),
|
||||
'themes' => new Themes($refresh, $callback)
|
||||
];
|
||||
}
|
||||
|
||||
$this->plugins = self::$cache[__METHOD__]['plugins']->toArray();
|
||||
$this->themes = self::$cache[__METHOD__]['themes']->toArray();
|
||||
|
||||
$this->append(['plugins' => $this->plugins]);
|
||||
$this->append(['themes' => $this->themes]);
|
||||
}
|
||||
}
|
||||
21
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
21
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Plugins extends Collection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/plugins.json';
|
||||
private $type = 'plugins';
|
||||
private $data;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
foreach ($this->data as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
21
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Themes extends Collection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/themes.json';
|
||||
private $type = 'themes';
|
||||
private $data;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
foreach ($this->data as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
220
system/src/Grav/Common/GPM/Response.php
Normal file
220
system/src/Grav/Common/GPM/Response.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* The callback for the progress
|
||||
* @var callable Either a function or callback in array notation
|
||||
*/
|
||||
public static $callback = null;
|
||||
|
||||
/**
|
||||
* Which method to use for HTTP calls, can be 'curl', 'fopen' or 'auto'. Auto is default and fopen is the preferred method
|
||||
* @var string
|
||||
*/
|
||||
private static $method = 'auto';
|
||||
|
||||
/**
|
||||
* Default parameters for `curl` and `fopen`
|
||||
* @var array
|
||||
*/
|
||||
private static $defaults = [
|
||||
|
||||
'curl' => [
|
||||
CURLOPT_REFERER => 'Grav GPM',
|
||||
CURLOPT_USERAGENT => 'Grav GPM',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_HEADER => false,
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//CURLOPT_NOPROGRESS => false,
|
||||
//CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
|
||||
],
|
||||
'fopen' => [
|
||||
'method' => 'GET',
|
||||
'user_agent' => 'Grav GPM',
|
||||
'max_redirects' => 5,
|
||||
'follow_location' => 1,
|
||||
'timeout' => 15,
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//'notification' => [$this, 'progress']
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets the preferred method to use for making HTTP calls.
|
||||
* @param string $method Default is `auto`
|
||||
*/
|
||||
public static function setMethod($method = 'auto')
|
||||
{
|
||||
if (!in_array($method, ['auto', 'curl', 'fopen'])) {
|
||||
$method = 'auto';
|
||||
}
|
||||
|
||||
self::$method = $method;
|
||||
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the URL by using the preferred method
|
||||
* @param string $uri URL to call
|
||||
* @param array $options An array of parameters for both `curl` and `fopen`
|
||||
* @return string The response of the request
|
||||
*/
|
||||
public static function get($uri = '', $options = [], $callback = null)
|
||||
{
|
||||
if (!self::isCurlAvailable() && !self::isFopenAvailable()) {
|
||||
throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
|
||||
}
|
||||
|
||||
$options = array_replace_recursive(self::$defaults, $options);
|
||||
$method = 'get' . ucfirst(strtolower(self::$method));
|
||||
|
||||
self::$callback = $callback;
|
||||
|
||||
return static::$method($uri, $options, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress normalized for cURL and Fopen
|
||||
* @param args Variable length of arguments passed in by stream method
|
||||
* @return array Normalized array with useful data.
|
||||
* Format: ['code' => int|false, 'filesize' => bytes, 'transferred' => bytes, 'percent' => int]
|
||||
*/
|
||||
public static function progress()
|
||||
{
|
||||
static $filesize = null;
|
||||
|
||||
$args = func_get_args();
|
||||
$isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) == 'curl';
|
||||
|
||||
$notification_code = !$isCurlResource ? $args[0] : false;
|
||||
$bytes_transferred = $isCurlResource ? $args[2] : $args[4];
|
||||
|
||||
if ($isCurlResource) {
|
||||
$filesize = $args[1];
|
||||
} elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) {
|
||||
$filesize = $args[5];
|
||||
}
|
||||
|
||||
if ($bytes_transferred > 0) {
|
||||
if ($notification_code == STREAM_NOTIFY_PROGRESS|STREAM_NOTIFY_COMPLETED || $isCurlResource) {
|
||||
|
||||
$progress = [
|
||||
'code' => $notification_code,
|
||||
'filesize' => $filesize,
|
||||
'transferred' => $bytes_transferred,
|
||||
'percent' => $filesize <= 0 ? '-' : round(($bytes_transferred * 100) / $filesize, 1)
|
||||
];
|
||||
|
||||
if (self::$callback !== null) {
|
||||
call_user_func_array(self::$callback, [$progress]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cURL is available
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isCurlAvailable()
|
||||
{
|
||||
return function_exists('curl_version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the remote fopen request is enabled in PHP
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isFopenAvailable()
|
||||
{
|
||||
return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically picks the preferred method
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getAuto()
|
||||
{
|
||||
if (self::isFopenAvailable()) {
|
||||
return self::getFopen(func_get_args());
|
||||
}
|
||||
|
||||
if (self::isCurlAvailable()) {
|
||||
return self::getCurl(func_get_args());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via cURL
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getCurl()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$uri = $args[0];
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
$ch = curl_init($uri);
|
||||
curl_setopt_array($ch, $options['curl']);
|
||||
|
||||
if ($callback) {
|
||||
curl_setopt_array(
|
||||
$ch,
|
||||
[
|
||||
CURLOPT_NOPROGRESS => false,
|
||||
CURLOPT_PROGRESSFUNCTION => ['self', 'progress']
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if ($errno = curl_errno($ch)) {
|
||||
$error_message = curl_strerror($errno);
|
||||
throw new \RuntimeException("cURL error ({$errno}):\n {$error_message}");
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via fopen
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getFopen()
|
||||
{
|
||||
if (count($args = func_get_args()) == 1) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
$uri = $args[0];
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
if ($callback) {
|
||||
$options['fopen']['notification'] = ['self', 'progress'];
|
||||
}
|
||||
|
||||
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
|
||||
$content = @file_get_contents($uri, false, $stream);
|
||||
|
||||
if ($content === false) {
|
||||
throw new \RuntimeException("Error while trying to download '$uri'");
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
86
system/src/Grav/Common/GPM/Upgrader.php
Normal file
86
system/src/Grav/Common/GPM/Upgrader.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\Installer;
|
||||
|
||||
class Upgrader
|
||||
{
|
||||
/**
|
||||
* Remote details about latest Grav version
|
||||
* @var Packages
|
||||
*/
|
||||
private $remote;
|
||||
|
||||
/**
|
||||
* Internal cache
|
||||
* @var Iterator
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Creates a new GPM instance with Local and Remote packages available
|
||||
* @param boolean $refresh Applies to Remote Packages only and forces a refetch of data
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$this->remote = new Remote\Grav($refresh, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the release date of the latest version of Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getReleaseDate()
|
||||
{
|
||||
return $this->remote->getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the installed Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalVersion()
|
||||
{
|
||||
return GRAV_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the remotely available Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteVersion()
|
||||
{
|
||||
return $this->remote->getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of assets available to download remotely
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->remote->getAssets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the changelog list for each version of Grav
|
||||
* @param string $diff the version number to start the diff from
|
||||
*
|
||||
* @return array return the chagenlog list for each version
|
||||
*/
|
||||
public function getChangelog($diff = null)
|
||||
{
|
||||
return $this->remote->getChangelog($diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the currently installed Grav is upgradable to a newer version
|
||||
* @return boolean True if it's upgradable, False otherwise.
|
||||
*/
|
||||
public function isUpgradable()
|
||||
{
|
||||
return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<");
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ abstract class Getters implements \ArrayAccess, \Countable
|
||||
*/
|
||||
public function __get($offset)
|
||||
{
|
||||
return $this->offsetGet($offset);
|
||||
return $this->offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use \Tracy\Debugger;
|
||||
use \Grav\Common\Page\Page;
|
||||
use \Grav\Common\Page\Pages;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Service\ConfigServiceProvider;
|
||||
use Grav\Common\Service\ErrorServiceProvider;
|
||||
use Grav\Common\Service\LoggerServiceProvider;
|
||||
use Grav\Common\Service\StreamsServiceProvider;
|
||||
use RocketTheme\Toolbox\DI\Container;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use Grav\Common\Page\Medium;
|
||||
|
||||
/**
|
||||
* Grav
|
||||
@@ -12,126 +18,221 @@ use \Grav\Common\Page\Pages;
|
||||
* @author Andy Miller
|
||||
* @link http://www.rockettheme.com
|
||||
* @license http://opensource.org/licenses/MIT
|
||||
* @version 0.1
|
||||
*
|
||||
* Originally based on Pico by Gilbert Pellegrom - http://pico.dev7studios.com
|
||||
* Influeced by Pico, Stacey, Kirby, PieCrust and other great platforms...
|
||||
*
|
||||
* @property Plugins $plugins
|
||||
* @property Config $config
|
||||
* @property Cache $cache
|
||||
* @property Uri $uri
|
||||
* @property Pages $pages
|
||||
* @property Page $page
|
||||
* Influenced by Pico, Stacey, Kirby, PieCrust and other great platforms...
|
||||
*/
|
||||
class Grav extends Getters
|
||||
class Grav extends Container
|
||||
{
|
||||
/**
|
||||
* @var string Grav output.
|
||||
* @var string
|
||||
*/
|
||||
protected $output;
|
||||
public $output;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @var static
|
||||
*/
|
||||
protected $plugins;
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
public static function instance(array $values = array())
|
||||
{
|
||||
if (!self::$instance) {
|
||||
self::$instance = static::load($values);
|
||||
|
||||
/**
|
||||
* @var Cache
|
||||
*/
|
||||
protected $cache;
|
||||
GravTrait::setGrav(self::$instance);
|
||||
|
||||
/**
|
||||
* @var Uri
|
||||
*/
|
||||
protected $uri;
|
||||
} elseif ($values) {
|
||||
$instance = self::$instance;
|
||||
foreach ($values as $key => $value) {
|
||||
$instance->offsetSet($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Pages
|
||||
*/
|
||||
protected $pages;
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
protected static function load(array $values)
|
||||
{
|
||||
$container = new static($values);
|
||||
|
||||
/**
|
||||
* @var Twig
|
||||
*/
|
||||
protected $twig;
|
||||
$container['grav'] = $container;
|
||||
|
||||
/**
|
||||
* @var Taxonomy
|
||||
*/
|
||||
protected $taxonomy;
|
||||
$container['debugger'] = new Debugger();
|
||||
$container['debugger']->startTimer('_init', 'Initialize');
|
||||
|
||||
$container->register(new LoggerServiceProvider);
|
||||
|
||||
$container->register(new ErrorServiceProvider);
|
||||
|
||||
$container['uri'] = function ($c) {
|
||||
return new Uri($c);
|
||||
};
|
||||
|
||||
$container['task'] = function ($c) {
|
||||
return !empty($_POST['task']) ? $_POST['task'] : $c['uri']->param('task');
|
||||
};
|
||||
|
||||
$container['events'] = function ($c) {
|
||||
return new EventDispatcher;
|
||||
};
|
||||
$container['cache'] = function ($c) {
|
||||
return new Cache($c);
|
||||
};
|
||||
$container['plugins'] = function ($c) {
|
||||
return new Plugins($c);
|
||||
};
|
||||
$container['themes'] = function ($c) {
|
||||
return new Themes($c);
|
||||
};
|
||||
$container['twig'] = function ($c) {
|
||||
return new Twig($c);
|
||||
};
|
||||
$container['taxonomy'] = function ($c) {
|
||||
return new Taxonomy($c);
|
||||
};
|
||||
$container['pages'] = function ($c) {
|
||||
return new Page\Pages($c);
|
||||
};
|
||||
$container['assets'] = function ($c) {
|
||||
return new Assets();
|
||||
};
|
||||
$container['page'] = function ($c) {
|
||||
/** @var Pages $pages */
|
||||
$pages = $c['pages'];
|
||||
|
||||
// If base URI is set, we want to remove it from the URL.
|
||||
$path = '/' . ltrim(Folder::getRelativePath($c['uri']->route(), $pages->base()), '/');
|
||||
|
||||
$page = $pages->dispatch($path);
|
||||
|
||||
if (!$page || !$page->routable()) {
|
||||
|
||||
// special case where a media file is requested
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
$page = $c['pages']->dispatch($path_parts['dirname'], true);
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
$media_file = urldecode($path_parts['basename']);
|
||||
if (isset($media[$media_file])) {
|
||||
$medium = $media[$media_file];
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($c['uri']->query(null, true) as $action => $params) {
|
||||
if (in_array($action, Medium::$valid_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
}
|
||||
header('Content-type: '. $medium->get('mime'));
|
||||
echo file_get_contents($medium->path());
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
// If no page found, fire event
|
||||
$event = $c->fireEvent('onPageNotFound');
|
||||
|
||||
if (isset($event->page)) {
|
||||
$page = $event->page;
|
||||
} else {
|
||||
throw new \RuntimeException('Page Not Found', 404);
|
||||
}
|
||||
}
|
||||
return $page;
|
||||
};
|
||||
$container['output'] = function ($c) {
|
||||
return $c['twig']->processSite($c['uri']->extension());
|
||||
};
|
||||
$container['browser'] = function ($c) {
|
||||
return new Browser();
|
||||
};
|
||||
|
||||
$container['base_url_absolute'] = function ($c) {
|
||||
return $c['config']->get('system.base_url_absolute') ?: $c['uri']->rootUrl(true);
|
||||
};
|
||||
$container['base_url_relative'] = function ($c) {
|
||||
return $c['config']->get('system.base_url_relative') ?: $c['uri']->rootUrl(false);
|
||||
};
|
||||
$container['base_url'] = function ($c) {
|
||||
return $c['config']->get('system.absolute_urls') ? $c['base_url_absolute'] : $c['base_url_relative'];
|
||||
};
|
||||
|
||||
$container->register(new StreamsServiceProvider);
|
||||
$container->register(new ConfigServiceProvider);
|
||||
|
||||
$container['debugger']->stopTimer('_init');
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
public function process()
|
||||
{
|
||||
// Get the URI and URL (needed for configuration)
|
||||
$this->uri = Registry::get('Uri');
|
||||
|
||||
// Get the Configuration settings and caching
|
||||
$this->config = Registry::get('Config');
|
||||
|
||||
Debugger::$logDirectory = $this->config->get('system.debugger.log.enabled') ? LOG_DIR : null;
|
||||
Debugger::$maxDepth = $this->config->get('system.debugger.max_depth');
|
||||
|
||||
// Switch debugger into development mode if configured
|
||||
if ($this->config->get('system.debugger.enabled')) {
|
||||
if (function_exists('ini_set')) {
|
||||
ini_set('display_errors', true);
|
||||
}
|
||||
Debugger::$productionMode = Debugger::DEVELOPMENT;
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_start('ob_gzhandler');
|
||||
}
|
||||
|
||||
// Get the Caching setup
|
||||
$this->cache = Registry::get('Cache');
|
||||
$this->cache->init();
|
||||
|
||||
// Get Plugins
|
||||
$plugins = new Plugins();
|
||||
$this->plugins = $plugins->load();
|
||||
$this->fireEvent('onAfterInitPlugins');
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
|
||||
// Get current theme and hook it into plugins.
|
||||
$themes = new Themes();
|
||||
$this->plugins['Theme'] = $themes->load();
|
||||
// Initialize configuration.
|
||||
$debugger->startTimer('_config', 'Configuration');
|
||||
$this['config']->init();
|
||||
$this['errors']->resetHandlers();
|
||||
$debugger->init();
|
||||
$this['config']->debug();
|
||||
$debugger->stopTimer('_config');
|
||||
|
||||
// Get twig object
|
||||
$this->twig = Registry::get('Twig');
|
||||
$this->twig->init();
|
||||
$debugger->startTimer('streams', 'Streams');
|
||||
$this['streams'];
|
||||
$debugger->stopTimer('streams');
|
||||
|
||||
// Get all the Pages that Grav knows about
|
||||
$this->pages = Registry::get('Pages');
|
||||
$this->pages->init();
|
||||
$this->fireEvent('onAfterGetPages');
|
||||
$debugger->startTimer('plugins', 'Plugins');
|
||||
$this['plugins']->init();
|
||||
$this->fireEvent('onPluginsInitialized');
|
||||
$debugger->stopTimer('plugins');
|
||||
|
||||
// Get the taxonomy and set it on the grav object
|
||||
$this->taxonomy = Registry::get('Taxonomy');
|
||||
$debugger->startTimer('themes', 'Themes');
|
||||
$this['themes']->init();
|
||||
$this->fireEvent('onThemeInitialized');
|
||||
$debugger->stopTimer('themes');
|
||||
|
||||
// Get current page
|
||||
$this->page = $this->pages->dispatch($this->uri->route());
|
||||
$this->fireEvent('onAfterGetPage');
|
||||
|
||||
// If there's no page, throw exception
|
||||
if (!$this->page) {
|
||||
throw new \RuntimeException('Page Not Found', 404);
|
||||
$task = $this['task'];
|
||||
if ($task) {
|
||||
$this->fireEvent('onTask.' . $task);
|
||||
}
|
||||
|
||||
$this['assets']->init();
|
||||
$this->fireEvent('onAssetsInitialized');
|
||||
|
||||
$debugger->startTimer('twig', 'Twig');
|
||||
$this['twig']->init();
|
||||
$debugger->stopTimer('twig');
|
||||
|
||||
$debugger->startTimer('pages', 'Pages');
|
||||
$this['pages']->init();
|
||||
$this->fireEvent('onPagesInitialized');
|
||||
$debugger->stopTimer('pages');
|
||||
|
||||
$this->fireEvent('onPageInitialized');
|
||||
|
||||
$debugger->addAssets();
|
||||
|
||||
// Process whole page as required
|
||||
$this->output = $this->twig->processSite($this->uri->extension());
|
||||
$this->fireEvent('onAfterGetOutput');
|
||||
$debugger->startTimer('render', 'Render');
|
||||
$this->output = $this['output'];
|
||||
$this->fireEvent('onOutputGenerated');
|
||||
$debugger->stopTimer('render');
|
||||
|
||||
// Set the header type
|
||||
$this->header();
|
||||
|
||||
echo $this->output;
|
||||
$debugger->render();
|
||||
|
||||
$this->fireEvent('onOutputRendered');
|
||||
|
||||
register_shutdown_function([$this, 'shutdown']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +243,14 @@ class Grav extends Getters
|
||||
*/
|
||||
public function redirect($route, $code = 303)
|
||||
{
|
||||
header("Location: " . rtrim($this->uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
header("Location: " . rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
|
||||
exit();
|
||||
}
|
||||
|
||||
@@ -174,40 +282,68 @@ class Grav extends Getters
|
||||
*/
|
||||
public function header()
|
||||
{
|
||||
header('Content-type: ' . $this->mime($this->uri->extension()));
|
||||
$extension = $this['uri']->extension();
|
||||
header('Content-type: ' . $this->mime($extension));
|
||||
|
||||
// Set debugger data in headers
|
||||
if (!($extension == null || $extension == 'html')) {
|
||||
$this['debugger']->enabled(false);
|
||||
// $this['debugger']->sendDataInHeaders();
|
||||
}
|
||||
|
||||
// Set HTTP response code
|
||||
if (isset($this['page']->header()->http_response_code)) {
|
||||
http_response_code($this['page']->header()->http_response_code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message.
|
||||
* Fires an event with optional parameters.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $eventName
|
||||
* @param Event $event
|
||||
* @return Event
|
||||
*/
|
||||
protected static function log($message)
|
||||
public function fireEvent($eventName, Event $event = null)
|
||||
{
|
||||
if (Debugger::$logDirectory) {
|
||||
Debugger::log(sprintf($message, Debugger::timer() * 1000));
|
||||
}
|
||||
/** @var EventDispatcher $events */
|
||||
$events = $this['events'];
|
||||
return $events->dispatch($eventName, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any hooks and runs them.
|
||||
* Set the final content length for the page and flush the buffer
|
||||
*
|
||||
*/
|
||||
public function fireEvent()
|
||||
public function shutdown()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$hook_id = array_shift($args);
|
||||
$no_timing_hooks = array('onAfterPageProcessed','onAfterFolderProcessed', 'onAfterCollectionProcessed');
|
||||
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
|
||||
|
||||
if (!empty($this->plugins)) {
|
||||
foreach ($this->plugins as $plugin) {
|
||||
if (is_callable(array($plugin, $hook_id))) {
|
||||
call_user_func_array(array($plugin, $hook_id), $args);
|
||||
}
|
||||
if (function_exists('ignore_user_abort')) {
|
||||
@ignore_user_abort(true);
|
||||
}
|
||||
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_end_flush(); // gzhandler buffer
|
||||
}
|
||||
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
header("Connection: close\r\n");
|
||||
|
||||
ob_end_flush(); // regular buffer
|
||||
ob_flush();
|
||||
flush();
|
||||
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
@fastcgi_finish_request();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($this->config && $this->config->get('system.debugger.log.timing') && !in_array($hook_id, $no_timing_hooks)) {
|
||||
static::log($hook_id.': %f ms');
|
||||
}
|
||||
$this->fireEvent('onShutdown');
|
||||
}
|
||||
}
|
||||
|
||||
26
system/src/Grav/Common/GravTrait.php
Normal file
26
system/src/Grav/Common/GravTrait.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
trait GravTrait
|
||||
{
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
protected static $grav;
|
||||
|
||||
/**
|
||||
* @return Grav
|
||||
*/
|
||||
public function getGrav()
|
||||
{
|
||||
return self::$grav;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Grav $grav
|
||||
*/
|
||||
public static function setGrav(Grav $grav)
|
||||
{
|
||||
self::$grav = $grav;
|
||||
}
|
||||
}
|
||||
@@ -90,9 +90,8 @@ class Inflector
|
||||
/**
|
||||
* Singularizes English nouns.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word English noun to singularize
|
||||
* @param int $count
|
||||
* @return string Singular noun.
|
||||
*/
|
||||
public static function singularize($word, $count = 1)
|
||||
@@ -353,10 +352,10 @@ class Inflector
|
||||
|
||||
public static function monthize($days)
|
||||
{
|
||||
$now = new JDate();
|
||||
$end = new JDate();
|
||||
$now = new \DateTime();
|
||||
$end = new \DateTime();
|
||||
|
||||
$duration = new DateInterval("P{$days}D");
|
||||
$duration = new \DateInterval("P{$days}D");
|
||||
|
||||
$diff = $end->add($duration)->diff($now);
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Iterator as ArrayIterator;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Countable;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Serializable;
|
||||
|
||||
/**
|
||||
* Class Iterator
|
||||
@@ -9,25 +14,12 @@ use Symfony\Component\Yaml\Yaml;
|
||||
*/
|
||||
class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
{
|
||||
use Constructor, ArrayAccessWithGetters, ArrayIterator, Countable, Serializable, Export;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $items = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $unset = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $items Initial items inside the iterator.
|
||||
*/
|
||||
public function __construct(array $items = array())
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* Convert function calls for the existing keys into their values.
|
||||
@@ -41,49 +33,6 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
return (isset($this->items[$key])) ? $this->items[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array getter shorthand to get items.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return (isset($this->items[$key])) ? $this->items[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array setter shorthand to set the value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->items[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array isset shorthand to set the value.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($key)
|
||||
{
|
||||
return isset($this->items[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array unset shorthand to remove the key.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function __unset($key)
|
||||
{
|
||||
$this->offsetUnset($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the iterator.
|
||||
*/
|
||||
@@ -164,7 +113,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
shuffle($keys);
|
||||
|
||||
$new = array();
|
||||
foreach($keys as $key) {
|
||||
foreach ($keys as $key) {
|
||||
$new[$key] = $this->items[$key];
|
||||
}
|
||||
|
||||
@@ -215,180 +164,4 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Implements export functions to array, YAML and JSON.
|
||||
|
||||
/**
|
||||
* Return items as an array.
|
||||
*
|
||||
* @return array Array presentation of the iterator.
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return YAML encoded string of items.
|
||||
*
|
||||
* @return string YAML presentation of the iterator.
|
||||
*/
|
||||
public function toYaml()
|
||||
{
|
||||
return Yaml::dump($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON encoded string of items.
|
||||
*
|
||||
* @return string JSON presentation of the iterator.
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode($this->items);
|
||||
}
|
||||
|
||||
// Implements Iterator.
|
||||
|
||||
/**
|
||||
* Returns the current element.
|
||||
*
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return current($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key of the current element.
|
||||
*
|
||||
* @return mixed Returns scalar on success, or NULL on failure.
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return key($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the current position to the next element.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
if ($this->unset) {
|
||||
// If current item was unset, position is already in the next element (do nothing).
|
||||
$this->unset = false;
|
||||
} else {
|
||||
next($this->items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds back to the first element of the Iterator.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->unset = false;
|
||||
reset($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid.
|
||||
*
|
||||
* @return bool Returns TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return key($this->items) !== null;
|
||||
}
|
||||
|
||||
// Implements ArrayAccess
|
||||
|
||||
/**
|
||||
* Whether or not an offset exists.
|
||||
*
|
||||
* @param mixed $offset An offset to check for.
|
||||
* @return bool Returns TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->items[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return isset($this->items[$offset]) ? $this->items[$offset] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a value to the specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to assign the value to.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
$this->items[] = $value;
|
||||
} else {
|
||||
$this->items[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets an offset.
|
||||
*
|
||||
* @param mixed $offset The offset to unset.
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
if ($offset == key($this->items)) {
|
||||
$this->unset = true;
|
||||
}
|
||||
unset($this->items[$offset]);
|
||||
}
|
||||
|
||||
// Implements Countable
|
||||
|
||||
/**
|
||||
* This method is executed when using the count() function.
|
||||
*
|
||||
* @return int The count of items.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
// Implements Serializable
|
||||
|
||||
/**
|
||||
* Returns string representation of the object.
|
||||
*
|
||||
* @return string Returns the string representation of the object.
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during unserialization of the object.
|
||||
*
|
||||
* @param string $serialized The string representation of the object.
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
$this->items = unserialize($serialized);
|
||||
}
|
||||
}
|
||||
|
||||
13
system/src/Grav/Common/Markdown/Parsedown.php
Normal file
13
system/src/Grav/Common/Markdown/Parsedown.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
class Parsedown extends \Parsedown
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
public function __construct($page)
|
||||
{
|
||||
$this->init($page);
|
||||
}
|
||||
|
||||
}
|
||||
13
system/src/Grav/Common/Markdown/ParsedownExtra.php
Normal file
13
system/src/Grav/Common/Markdown/ParsedownExtra.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
class ParsedownExtra extends \ParsedownExtra
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
public function __construct($page)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->init($page);
|
||||
}
|
||||
}
|
||||
275
system/src/Grav/Common/Markdown/ParsedownGravTrait.php
Normal file
275
system/src/Grav/Common/Markdown/ParsedownGravTrait.php
Normal file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Page\Medium;
|
||||
use Grav\Common\Uri;
|
||||
|
||||
/**
|
||||
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
|
||||
*/
|
||||
trait ParsedownGravTrait
|
||||
{
|
||||
use GravTrait;
|
||||
protected $page;
|
||||
protected $pages;
|
||||
protected $base_url;
|
||||
protected $pages_dir;
|
||||
protected $special_chars;
|
||||
|
||||
protected $twig_link_regex = '/\!*\[(?:.*)\]\(([{{|{%|{#].*[#}|%}|}}])\)/';
|
||||
|
||||
/**
|
||||
* Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait
|
||||
*
|
||||
* @param $page
|
||||
*/
|
||||
protected function init($page)
|
||||
{
|
||||
$this->page = $page;
|
||||
$this->pages = self::$grav['pages'];
|
||||
$this->BlockTypes['{'] [] = "TwigTag";
|
||||
$this->base_url = rtrim(self::$grav['base_url'] . self::$grav['pages']->base(), '/');
|
||||
$this->pages_dir = self::$grav['locator']->findResource('page://');
|
||||
$this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for special chars
|
||||
*
|
||||
* @param $special_chars
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
function setSpecialChars($special_chars)
|
||||
{
|
||||
$this->special_chars = $special_chars;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure Twig tags are treated as block level items with no <p></p> tags
|
||||
*/
|
||||
protected function blockTwigTag($Line)
|
||||
{
|
||||
if (preg_match('/[{%|{{|{#].*[#}|}}|%}]/', $Line['body'], $matches)) {
|
||||
$Block = array(
|
||||
'markup' => $Line['body'],
|
||||
);
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
protected function inlineSpecialCharacter($Excerpt)
|
||||
{
|
||||
if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) {
|
||||
return array(
|
||||
'markup' => '&',
|
||||
'extent' => 1,
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($this->special_chars[$Excerpt['text'][0]])) {
|
||||
return array(
|
||||
'markup' => '&'.$this->special_chars[$Excerpt['text'][0]].';',
|
||||
'extent' => 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function inlineImage($excerpt)
|
||||
{
|
||||
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
|
||||
$excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
|
||||
$excerpt = parent::inlineImage($excerpt);
|
||||
$excerpt['element']['attributes']['src'] = $matches[1];
|
||||
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
|
||||
return $excerpt;
|
||||
} else {
|
||||
$excerpt = parent::inlineImage($excerpt);
|
||||
}
|
||||
|
||||
// Some stuff we will need
|
||||
$actions = array();
|
||||
$media = null;
|
||||
|
||||
// if this is an image
|
||||
if (isset($excerpt['element']['attributes']['src'])) {
|
||||
|
||||
$alt = $excerpt['element']['attributes']['alt'] ?: '';
|
||||
$title = $excerpt['element']['attributes']['title'] ?: '';
|
||||
|
||||
//get the url and parse it
|
||||
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['src']));
|
||||
|
||||
$path_parts = pathinfo($url['path']);
|
||||
|
||||
// if there is no host set but there is a path, the file is local
|
||||
if (!isset($url['host']) && isset($url['path'])) {
|
||||
|
||||
// get the local path to page media if possible
|
||||
if ($path_parts['dirname'] == $this->page->url()) {
|
||||
$url['path'] = ltrim(str_replace($this->page->url(), '', $url['path']), '/');
|
||||
// get the media objects for this page
|
||||
$media = $this->page->media();
|
||||
|
||||
} else {
|
||||
|
||||
// see if this is an external page to this one
|
||||
$page_route = str_replace($this->base_url, '', $path_parts['dirname']);
|
||||
|
||||
$ext_page = $this->pages->dispatch($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->media();
|
||||
$url['path'] = $path_parts['basename'];
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a media file that matches the path referenced..
|
||||
if ($media && isset($media->images()[$url['path']])) {
|
||||
// get the medium object
|
||||
$medium = $media->images()[$url['path']];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url['query'])) {
|
||||
parse_str($url['query'], $actions);
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action => $params) {
|
||||
// as long as it's a valid action
|
||||
if (in_array($action, Medium::$valid_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
}
|
||||
|
||||
$data = $medium->htmlRaw();
|
||||
|
||||
// set the src element with the new generated url
|
||||
if (!isset($actions['lightbox'])) {
|
||||
$excerpt['element']['attributes']['src'] = $data['img_src'];
|
||||
|
||||
if ($data['img_srcset']) {
|
||||
$excerpt['element']['attributes']['srcset'] = $data['img_srcset'];;
|
||||
$excerpt['element']['attributes']['sizes'] = '100vw';
|
||||
}
|
||||
|
||||
} else {
|
||||
// Create the custom lightbox element
|
||||
|
||||
$attributes = $data['a_attributes'];
|
||||
$attributes['href'] = $data['a_href'];
|
||||
|
||||
$img_attributes = [
|
||||
'src' => $data['img_src'],
|
||||
'alt' => $alt,
|
||||
'title' => $title
|
||||
];
|
||||
|
||||
if ($data['img_srcset']) {
|
||||
$img_attributes['srcset'] = $data['img_srcset'];
|
||||
$img_attributes['sizes'] = '100vw';
|
||||
}
|
||||
|
||||
$element = array(
|
||||
'name' => 'a',
|
||||
'attributes' => $attributes,
|
||||
'handler' => 'element',
|
||||
'text' => array(
|
||||
'name' => 'img',
|
||||
'attributes' => $img_attributes
|
||||
)
|
||||
);
|
||||
|
||||
// Set any custom classes on the lightbox element
|
||||
if (isset($excerpt['element']['attributes']['class'])) {
|
||||
$element['attributes']['class'] = $excerpt['element']['attributes']['class'];
|
||||
}
|
||||
|
||||
// Set the lightbox element on the Excerpt
|
||||
$excerpt['element'] = $element;
|
||||
}
|
||||
} else {
|
||||
// not a current page media file, see if it needs converting to relative
|
||||
$excerpt['element']['attributes']['src'] = Uri::build_url($url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
protected function inlineLink($excerpt)
|
||||
{
|
||||
// do some trickery to get around Parsedown requirement for valid URL if its Twig in there
|
||||
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
|
||||
$excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
|
||||
$excerpt = parent::inlineLink($excerpt);
|
||||
$excerpt['element']['attributes']['href'] = $matches[1];
|
||||
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
|
||||
return $excerpt;
|
||||
} else {
|
||||
$excerpt = parent::inlineLink($excerpt);
|
||||
}
|
||||
|
||||
// if this is a link
|
||||
if (isset($excerpt['element']['attributes']['href'])) {
|
||||
|
||||
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['href']));
|
||||
|
||||
// if there is no scheme, the file is local
|
||||
if (!isset($url['scheme'])) {
|
||||
|
||||
// convert the URl is required
|
||||
$excerpt['element']['attributes']['href'] = $this->convertUrl(Uri::build_url($url));
|
||||
}
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts links from absolute '/' or relative (../..) to a grav friendly format
|
||||
* @param string $markdown_url the URL as it was written in the markdown
|
||||
* @return string the more friendly formatted url
|
||||
*/
|
||||
protected function convertUrl($markdown_url)
|
||||
{
|
||||
// if absolute and starts with a base_url move on
|
||||
if ($this->base_url != '' && strpos($markdown_url, $this->base_url) === 0) {
|
||||
return $markdown_url;
|
||||
// if its absolute and starts with /
|
||||
} elseif (strpos($markdown_url, '/') === 0) {
|
||||
return $this->base_url . $markdown_url;
|
||||
} else {
|
||||
$relative_path = $this->base_url . $this->page->route();
|
||||
$real_path = $this->page->path() . '/' . parse_url($markdown_url, PHP_URL_PATH);
|
||||
|
||||
// strip numeric order from markdown path
|
||||
if (($real_path)) {
|
||||
$markdown_url = preg_replace('/^([\d]+\.)/', '', preg_replace('/\/([\d]+\.)/', '/', trim(preg_replace('/[^\/]+(\.md$)/', '', $markdown_url), '/')));
|
||||
}
|
||||
|
||||
// else its a relative path already
|
||||
$newpath = array();
|
||||
$paths = explode('/', $markdown_url);
|
||||
|
||||
// remove the updirectory references (..)
|
||||
foreach ($paths as $path) {
|
||||
if ($path == '..') {
|
||||
$relative_path = dirname($relative_path);
|
||||
} else {
|
||||
$newpath[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// build the new url
|
||||
$new_url = rtrim($relative_path, '/') . '/' . implode('/', $newpath);
|
||||
}
|
||||
|
||||
return $new_url;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Registry;
|
||||
|
||||
/**
|
||||
* Collection of Pages.
|
||||
@@ -21,11 +22,12 @@ class Collection extends Iterator
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
public function __construct($items = array(), array $params = array(), Pages $pages = null) {
|
||||
public function __construct($items = array(), array $params = array(), Pages $pages = null)
|
||||
{
|
||||
parent::__construct($items);
|
||||
|
||||
$this->params = $params;
|
||||
$this->pages = $pages ? $pages : Registry::get('Pages');
|
||||
$this->pages = $pages ? $pages : Grav::instance()->offsetGet('pages');
|
||||
}
|
||||
|
||||
public function params()
|
||||
@@ -33,6 +35,17 @@ class Collection extends Iterator
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a copy of this collection
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return new static($this->items, $this->params, $this->pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
@@ -114,9 +127,10 @@ class Collection extends Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the first in the collection
|
||||
* Check to see if this item is the first in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
* @return boolean True if item is first
|
||||
* @return boolean True if item is first.
|
||||
*/
|
||||
public function isFirst($path)
|
||||
{
|
||||
@@ -128,9 +142,10 @@ class Collection extends Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the last in the collection
|
||||
* Check to see if this item is the last in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
* @return boolean True if item is last
|
||||
* @return boolean True if item is last.
|
||||
*/
|
||||
public function isLast($path)
|
||||
{
|
||||
@@ -142,9 +157,10 @@ class Collection extends Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position
|
||||
* Gets the previous sibling based on current position.
|
||||
*
|
||||
* @return Object the previous item
|
||||
* @param string $path
|
||||
* @return Page The previous item.
|
||||
*/
|
||||
public function prevSibling($path)
|
||||
{
|
||||
@@ -152,9 +168,10 @@ class Collection extends Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @return Object the next item
|
||||
* @param string $path
|
||||
* @return Page The next item.
|
||||
*/
|
||||
public function nextSibling($path)
|
||||
{
|
||||
@@ -162,26 +179,214 @@ class Collection extends Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param string $path
|
||||
* @param integer $direction either -1 or +1
|
||||
* @return Object the sibling item
|
||||
* @return Page The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1)
|
||||
{
|
||||
|
||||
$values = array_keys($this->items);
|
||||
$keys = array_flip($values);
|
||||
$index = $keys[$path] - $direction;
|
||||
|
||||
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
|
||||
if (array_key_exists($path, $keys)) {
|
||||
$index = $keys[$path] - $direction;
|
||||
|
||||
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
|
||||
}
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item in the current position
|
||||
* @param String $path the path the item
|
||||
* @return Object item in the array the the current position
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @param string $path the path the item
|
||||
* @return Page Item in the array the the current position.
|
||||
*/
|
||||
public function currentPosition($path) {
|
||||
return array_search($path,array_keys($this->items));
|
||||
public function currentPosition($path)
|
||||
{
|
||||
return array_search($path, array_keys($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges where second value is optional
|
||||
* Dates can be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param $startDate
|
||||
* @param bool $endDate
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false)
|
||||
{
|
||||
$start = strtotime($startDate);
|
||||
$end = $endDate ? strtotime($endDate) : strtotime("now +1000 years");
|
||||
|
||||
$date_range = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->date() > $start && $page->date() < $end) {
|
||||
$date_range[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $date_range;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
* @return Collection The collection with only visible pages
|
||||
*/
|
||||
public function visible()
|
||||
{
|
||||
$visible = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->visible()) {
|
||||
$visible[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $visible;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return Collection The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible()
|
||||
{
|
||||
$visible = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->visible()) {
|
||||
$visible[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $visible;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return Collection The collection with only modular pages
|
||||
*/
|
||||
public function modular()
|
||||
{
|
||||
$modular = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->modular()) {
|
||||
$modular[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $modular;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return Collection The collection with only non-modular pages
|
||||
*/
|
||||
public function nonModular()
|
||||
{
|
||||
$modular = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->modular()) {
|
||||
$modular[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $modular;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return Collection The collection with only published pages
|
||||
*/
|
||||
public function published()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->published()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return Collection The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->published()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return Collection The collection with only routable pages
|
||||
*/
|
||||
public function routable()
|
||||
{
|
||||
$routable = [];
|
||||
|
||||
foreach (array_keys($this->items) as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->routable()) {
|
||||
$routable[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $routable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return Collection The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable()
|
||||
{
|
||||
$routable = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->routable()) {
|
||||
$routable[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $routable;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
11
system/src/Grav/Common/Page/Header.php
Normal file
11
system/src/Grav/Common/Page/Header.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess;
|
||||
|
||||
class Header implements \ArrayAccess
|
||||
{
|
||||
use NestedArrayAccess, Constructor;
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Getters;
|
||||
use Grav\Common\Registry;
|
||||
use Grav\Config;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\GravTrait;
|
||||
|
||||
/**
|
||||
* Media is a holder object that contains references to the media of page. This object is created and
|
||||
@@ -15,12 +15,15 @@ use Symfony\Component\Yaml\Yaml;
|
||||
*/
|
||||
class Media extends Getters
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected $gettersVariable = 'instances';
|
||||
protected $path;
|
||||
|
||||
protected $instances = array();
|
||||
protected $images = array();
|
||||
protected $videos = array();
|
||||
protected $audios = array();
|
||||
protected $files = array();
|
||||
|
||||
/**
|
||||
@@ -46,71 +49,81 @@ class Media extends Getters
|
||||
|
||||
// Find out the real filename, in case of we are at the metadata.
|
||||
$filename = $info->getFilename();
|
||||
list($basename, $ext, $meta) = $this->getFileParts($filename);
|
||||
list($basename, $ext, $meta, $alternative) = $this->getFileParts($filename);
|
||||
|
||||
// Get medium instance creating it if it didn't exist.
|
||||
$medium = $this->get("{$basename}.{$ext}", true);
|
||||
if (!$medium) {
|
||||
// Get medium instance if it already exists.
|
||||
$medium = $this->get("{$basename}.{$ext}");
|
||||
|
||||
if (!$alternative) {
|
||||
|
||||
$medium = $medium ? $medium : $this->createMedium($info->getPathname());
|
||||
|
||||
if (!$medium) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($meta) {
|
||||
$medium->addMetaFile($meta);
|
||||
} else {
|
||||
$medium->set('size', $info->getSize());
|
||||
}
|
||||
} else {
|
||||
|
||||
$altMedium = $this->createMedium($info->getPathname());
|
||||
|
||||
if (!$altMedium) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$altMedium->set('size', $info->getSize());
|
||||
|
||||
if (!$medium) {
|
||||
$medium = $this->createMedium("{$path}/${basename}.${ext}");
|
||||
|
||||
if ($medium) {
|
||||
$medium->set('size', filesize("{$path}/${basename}.${ext}"));
|
||||
}
|
||||
}
|
||||
|
||||
$medium = $medium ? $medium : $this->scaleMedium($altMedium, $alternative, 1);
|
||||
|
||||
$medium->addAlternative($this->parseRatio($alternative), $altMedium);
|
||||
}
|
||||
|
||||
$this->add("{$basename}.{$ext}", $medium);
|
||||
}
|
||||
|
||||
foreach ($this->images() as $medium) {
|
||||
|
||||
$alternatives = $medium->getAlternatives();
|
||||
|
||||
if (empty($alternatives)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Assign meta files to the medium.
|
||||
if ($meta) {
|
||||
$medium->addMetaFile($meta);
|
||||
$max = max(array_keys($alternatives));
|
||||
|
||||
for ($i=2; $i < $max; $i++) {
|
||||
|
||||
if (isset($alternatives[$i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$medium->addAlternative($i, $this->scaleMedium($alternatives[$max], $max, $i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get medium by basename and extension.
|
||||
* Get medium by filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param bool $create
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function get($filename, $create = false)
|
||||
public function get($filename)
|
||||
{
|
||||
if ($create && !isset($this->instances[$filename])) {
|
||||
$parts = explode('.', $filename);
|
||||
$ext = array_pop($parts);
|
||||
$basename = implode('.', $parts);
|
||||
|
||||
/** @var Config $config */
|
||||
$config = Registry::get('Config');
|
||||
|
||||
// Check if medium type has been configured.
|
||||
$params = $config->get("media.{$ext}");
|
||||
if (!$params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$filePath = $this->path . '/' . $filename;
|
||||
$params += array(
|
||||
'type' => 'file',
|
||||
'thumb' => 'media/thumb.png',
|
||||
'mime' => 'application/octet-stream',
|
||||
'name' => $filename,
|
||||
'filename' => $filename,
|
||||
'basename' => $basename,
|
||||
'extension' => $ext,
|
||||
'path' => $this->path,
|
||||
'modified' => filemtime($filePath),
|
||||
);
|
||||
|
||||
$lookup = array(
|
||||
USER_DIR . 'images/',
|
||||
SYSTEM_DIR . 'images/',
|
||||
);
|
||||
foreach ($lookup as $path) {
|
||||
if (is_file($path . $params['thumb'])) {
|
||||
$params['thumb'] = $path . $params['thumb'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->add(new Medium($params));
|
||||
}
|
||||
|
||||
return isset($this->instances[$filename]) ? $this->instances[$filename] : null;
|
||||
}
|
||||
|
||||
@@ -121,6 +134,7 @@ class Media extends Getters
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
@@ -131,6 +145,7 @@ class Media extends Getters
|
||||
*/
|
||||
public function images()
|
||||
{
|
||||
ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
@@ -141,9 +156,21 @@ class Media extends Getters
|
||||
*/
|
||||
public function videos()
|
||||
{
|
||||
ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->videos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all audio media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function audios()
|
||||
{
|
||||
ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->audios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all file media.
|
||||
*
|
||||
@@ -151,24 +178,116 @@ class Media extends Getters
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Medium object from a file
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return Medium|null
|
||||
*/
|
||||
protected function createMedium($file)
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = dirname($file);
|
||||
$filename = basename($file);
|
||||
$parts = explode('.', $filename);
|
||||
$ext = array_pop($parts);
|
||||
$basename = implode('.', $parts);
|
||||
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
|
||||
// Check if medium type has been configured.
|
||||
$params = $config->get("media.".strtolower($ext));
|
||||
if (!$params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add default settings for undefined variables.
|
||||
$params += $config->get('media.defaults');
|
||||
$params += array(
|
||||
'type' => 'file',
|
||||
'thumb' => 'media/thumb.png',
|
||||
'mime' => 'application/octet-stream',
|
||||
'name' => $filename,
|
||||
'filename' => $filename,
|
||||
'basename' => $basename,
|
||||
'extension' => $ext,
|
||||
'path' => $path,
|
||||
'modified' => filemtime($file),
|
||||
);
|
||||
|
||||
$locator = self::$grav['locator'];
|
||||
|
||||
$lookup = $locator->findResources('image://');
|
||||
foreach ($lookup as $lookupPath) {
|
||||
if (is_file($lookupPath . $params['thumb'])) {
|
||||
$params['thumb'] = $lookupPath . $params['thumb'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Medium($params);
|
||||
}
|
||||
|
||||
protected function scaleMedium($medium, $from, $to)
|
||||
{
|
||||
$from = $this->parseRatio($from);
|
||||
$to = $this->parseRatio($to);
|
||||
|
||||
if ($to > $from) {
|
||||
return $medium;
|
||||
}
|
||||
|
||||
$ratio = $to / $from;
|
||||
$width = (int) ($medium->get('width') * $ratio);
|
||||
$height = (int) ($medium->get('height') * $ratio);
|
||||
|
||||
$basename = $medium->get('basename');
|
||||
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $basename);
|
||||
|
||||
$debug = $medium->get('debug');
|
||||
$medium->set('debug', false);
|
||||
|
||||
$file = $medium->resize($width, $height)->setPrettyName($basename)->url();
|
||||
$file = preg_replace('|'. preg_quote(self::$grav['base_url_relative']) .'$|', '', GRAV_ROOT) . $file;
|
||||
|
||||
$medium->set('debug', $debug);
|
||||
|
||||
$size = filesize($file);
|
||||
|
||||
$medium = $this->createMedium($file);
|
||||
$medium->set('size', $size);
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function add($file)
|
||||
protected function add($name, $file)
|
||||
{
|
||||
$this->instances[$file->filename] = $file;
|
||||
$this->instances[$name] = $file;
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
$this->images[$file->filename] = $file;
|
||||
$this->images[$name] = $file;
|
||||
break;
|
||||
case 'video':
|
||||
$this->videos[$file->filename] = $file;
|
||||
$this->videos[$name] = $file;
|
||||
break;
|
||||
case 'audio':
|
||||
$this->audios[$name] = $file;
|
||||
break;
|
||||
default:
|
||||
$this->files[$file->filename] = $file;
|
||||
$this->files[$name] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +302,13 @@ class Media extends Getters
|
||||
$fileParts = explode('.', $filename);
|
||||
|
||||
$name = array_shift($fileParts);
|
||||
$alternative = false;
|
||||
|
||||
if (preg_match('/(.*)@(\d+x)$/', $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
$alternative = $matches[2];
|
||||
}
|
||||
|
||||
$extension = null;
|
||||
while (($part = array_shift($fileParts)) !== null) {
|
||||
if ($part != 'meta') {
|
||||
@@ -196,6 +322,15 @@ class Media extends Getters
|
||||
}
|
||||
$meta = implode('.', $fileParts);
|
||||
|
||||
return array($name, $extension, $meta);
|
||||
return array($name, $extension, $meta, $alternative);
|
||||
}
|
||||
|
||||
protected function parseRatio($ratio)
|
||||
{
|
||||
if (!is_numeric($ratio)) {
|
||||
$ratio = (float) trim($ratio, 'x');
|
||||
}
|
||||
|
||||
return $ratio;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Filesystem\File\Yaml;
|
||||
use Grav\Common\Registry;
|
||||
use Gregwar\Image\Image as ImageFile;
|
||||
|
||||
/**
|
||||
@@ -34,6 +35,8 @@ use Gregwar\Image\Image as ImageFile;
|
||||
*/
|
||||
class Medium extends Data
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -45,7 +48,25 @@ class Medium extends Data
|
||||
protected $image;
|
||||
|
||||
protected $type = 'guess';
|
||||
protected $quality = 80;
|
||||
protected $quality = 85;
|
||||
protected $debug_watermarked = false;
|
||||
|
||||
public static $valid_actions = [
|
||||
// Medium functions
|
||||
'format', 'lightbox', 'link', 'reset',
|
||||
|
||||
// Gregwar Image functions
|
||||
'resize', 'forceResize', 'cropResize', 'crop', 'cropZoom',
|
||||
'negate', 'brightness', 'contrast', 'grayscale', 'emboss', 'smooth', 'sharp', 'edge', 'colorize', 'sepia' ];
|
||||
|
||||
public static $size_param_actions = [
|
||||
'resize' => [ 0, 1 ],
|
||||
'forceResize' => [ 0, 1 ],
|
||||
'cropResize' => [ 0, 1 ],
|
||||
'crop' => [ 0, 1, 2, 3 ],
|
||||
'cropResize' => [ 0, 1 ],
|
||||
'zoomCrop' => [ 0, 1 ]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@@ -53,23 +74,44 @@ class Medium extends Data
|
||||
protected $meta = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var array
|
||||
*/
|
||||
protected $linkTarget;
|
||||
protected $alternatives = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $linkAttributes;
|
||||
protected $linkTarget;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $linkSrcset;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $linkAttributes = [];
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint $blueprint
|
||||
*/
|
||||
public function __construct($items = array(), Blueprint $blueprint = null)
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
$file_path = $this->get('path') . '/' . $this->get('filename');
|
||||
$file_parts = pathinfo($file_path);
|
||||
|
||||
$this->set('thumb', $file_path);
|
||||
$this->set('extension', $file_parts['extension']);
|
||||
$this->set('filename', $this->get('filename'));
|
||||
|
||||
if ($this->get('type') == 'image') {
|
||||
$filePath = $this->get('path') . '/' . $this->get('filename');
|
||||
$image_info = getimagesize($filePath);
|
||||
$this->set('thumb', $filePath);
|
||||
$image_info = getimagesize($file_path);
|
||||
$this->def('width', $image_info[0]);
|
||||
$this->def('height', $image_info[1]);
|
||||
$this->def('mime', $image_info['mime']);
|
||||
@@ -77,6 +119,17 @@ class Medium extends Data
|
||||
} else {
|
||||
$this->def('mime', 'application/octet-stream');
|
||||
}
|
||||
|
||||
$debug = self::$grav['config']->get('system.images.debug');
|
||||
// try to override with page setting if possible
|
||||
$page = self::$grav['page'];
|
||||
if (!is_null($page)) {
|
||||
if (isset($page->header()->images['debug'])) {
|
||||
$debug = $page->header()->images['debug'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->set('debug', $debug);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,39 +143,66 @@ class Medium extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL to file.
|
||||
* Return PATH to file.
|
||||
*
|
||||
* @return string
|
||||
* @return string path to file
|
||||
*/
|
||||
public function url()
|
||||
public function path()
|
||||
{
|
||||
$config = Registry::get('Config');
|
||||
|
||||
if ($this->image) {
|
||||
$output = $this->image->cacheFile($this->type, $this->quality);
|
||||
$output = $this->saveImage();
|
||||
$this->reset();
|
||||
$output = GRAV_ROOT . '/' . $output;
|
||||
} else {
|
||||
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
|
||||
$output = $relPath . '/' . $this->get('filename');
|
||||
$output = $this->get('path') . '/' . $this->get('filename');
|
||||
}
|
||||
|
||||
return $config->get('system.base_url_relative') . '/'. $output;
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets image output format.
|
||||
* Return URL to file.
|
||||
*
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function format($type = null, $quality = 80)
|
||||
public function url($reset = true)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
if ($this->image) {
|
||||
$output = '/' . $this->saveImage();
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
} else {
|
||||
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('path')) . '/' . $this->get('filename');
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
$this->quality = $quality;
|
||||
return self::$grav['base_url'] . $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return srcset string for this Medium and its alternatives.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function srcset($reset = true)
|
||||
{
|
||||
if (empty($this->alternatives)) {
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
$srcset = [ $this->url($reset) . ' ' . $this->get('width') . 'w' ];
|
||||
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
|
||||
}
|
||||
|
||||
return implode(', ', $srcset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,15 +212,16 @@ class Medium extends Data
|
||||
* @param string $class
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function img($title = null, $class = null, $type = null, $quality = 80)
|
||||
public function img($title = null, $class = null, $type = null, $quality = 80, $reset = true)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$output = $this->html($title, $class, $type, $quality);
|
||||
$output = $this->html($title, $class, $type, $quality, $reset);
|
||||
|
||||
return $output;
|
||||
}
|
||||
@@ -150,77 +231,136 @@ class Medium extends Data
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $class
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $class = null, $type = null, $quality = 80)
|
||||
public function html($title = null, $class = null, $reset = true)
|
||||
{
|
||||
$data = $this->htmlRaw($reset);
|
||||
|
||||
$title = $title ? $title : $this->get('title');
|
||||
$class = $class ? $class : '';
|
||||
|
||||
if ($this->image) {
|
||||
$type = $type ? $type : $this->type;
|
||||
$quality = $quality ? $quality : $this->quality;
|
||||
|
||||
$url = $this->url($type, $quality);
|
||||
$this->reset();
|
||||
|
||||
$output = '<img src="' . $url . '" class="'. $class . '" alt="' . $title . '" />';
|
||||
$attributes = $data['img_srcset'] ? ' srcset="' . $data['img_srcset'] . '" sizes="100vw"' : '';
|
||||
$output = '<img src="' . $data['img_src'] . '"' . $attributes . ' class="'. $class . '" alt="' . $title . '" />';
|
||||
} else {
|
||||
$output = $title;
|
||||
$output = $data['text'];
|
||||
}
|
||||
|
||||
if ($this->linkTarget) {
|
||||
$config = Registry::get('Config');
|
||||
if (isset($data['a_href'])) {
|
||||
$attributes = '';
|
||||
foreach ($data['a_attributes'] as $prop => $value) {
|
||||
$attributes .= " {$prop}=\"{$value}\"";
|
||||
}
|
||||
|
||||
$output = '<a href="' . $config->get('system.base_url_relative') . '/'. $this->linkTarget
|
||||
. '"' . $this->linkAttributes. ' class="'. $class . '">' . $output . '</a>';
|
||||
|
||||
$this->linkTarget = $this->linkAttributes = null;
|
||||
$output = '<a href="' . $data['a_href'] . '"' . $attributes . ' class="'. $class . '">' . $output . '</a>';
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return lightbox HTML for the medium.
|
||||
* Return HTML array from medium.
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
* @param bool $reset
|
||||
* @param string $title
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function lightbox($width = null, $height = null)
|
||||
public function htmlRaw($reset = true, $title = '')
|
||||
{
|
||||
$this->linkAttributes = ' rel="lightbox"';
|
||||
$output = [];
|
||||
|
||||
return $this->link($width, $height);
|
||||
if ($this->image) {
|
||||
$output['img_src'] = $this->url(false);
|
||||
$output['img_srcset'] = $this->srcset($reset);
|
||||
} else {
|
||||
$output['text'] = $title;
|
||||
}
|
||||
|
||||
if ($this->linkTarget) {
|
||||
$output['a_href'] = $this->linkTarget;
|
||||
$output['a_attributes'] = $this->linkAttributes;
|
||||
|
||||
$this->linkTarget = null;
|
||||
$this->linkAttributes = [];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return link HTML for the medium.
|
||||
* Sets the quality of the image
|
||||
* @param Int $quality 0-100 quality
|
||||
* @return Medium
|
||||
*/
|
||||
public function quality($quality)
|
||||
{
|
||||
$this->quality = $quality;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets image output format.
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @return $this
|
||||
*/
|
||||
public function format($type = null, $quality = 80)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
$this->quality = $quality;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable link for the medium object.
|
||||
*
|
||||
* @param null $width
|
||||
* @param null $height
|
||||
* @return $this
|
||||
*/
|
||||
public function link($width = null, $height = null)
|
||||
{
|
||||
if ($this->image) {
|
||||
$image = clone $this->image;
|
||||
if ($width && $height) {
|
||||
$image->cropResize($width, $height);
|
||||
$this->cropResize($width, $height);
|
||||
}
|
||||
|
||||
$this->linkTarget = $this->url(false);
|
||||
$srcset = $this->srcset();
|
||||
|
||||
if ($srcset) {
|
||||
$this->linkAttributes['data-srcset'] = $srcset;
|
||||
}
|
||||
$this->linkTarget = $image->cacheFile($this->type, $this->quality);
|
||||
} else {
|
||||
// TODO: we need to find out URI in a bit better way.
|
||||
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
|
||||
$this->linkTarget = $relPath. '/' . $this->get('filename');
|
||||
$this->linkTarget = self::$grav['base_url'] . preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('path')) . '/' . $this->get('filename');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable lightbox for the medium.
|
||||
*
|
||||
* @param null $width
|
||||
* @param null $height
|
||||
* @return Medium
|
||||
*/
|
||||
public function lightbox($width = null, $height = null)
|
||||
{
|
||||
$this->linkAttributes['rel'] = 'lightbox';
|
||||
|
||||
return $this->link($width, $height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset image.
|
||||
*
|
||||
@@ -236,6 +376,7 @@ class Medium extends Data
|
||||
}
|
||||
$this->type = 'guess';
|
||||
$this->quality = 80;
|
||||
$this->debug_watermarked = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -257,7 +398,26 @@ class Medium extends Data
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
$result = call_user_func_array(array($this->image, $method), $args);
|
||||
|
||||
try {
|
||||
$result = call_user_func_array(array($this->image, $method), $args);
|
||||
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$args_copy = $args;
|
||||
|
||||
if (isset(self::$size_param_actions[$method])) {
|
||||
foreach (self::$size_param_actions[$method] as $param) {
|
||||
if (isset($args_copy[$param])) {
|
||||
$args_copy[$param] = (int) $args_copy[$param] * $ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
call_user_func_array(array($medium, $method), $args_copy);
|
||||
}
|
||||
} catch (\BadFunctionCallException $e) {
|
||||
$result = null;
|
||||
}
|
||||
|
||||
// Returns either current object or result of the action.
|
||||
return $result instanceof ImageFile ? $this : $result;
|
||||
@@ -271,11 +431,13 @@ class Medium extends Data
|
||||
*/
|
||||
public function image($variable = 'thumb')
|
||||
{
|
||||
$locator = self::$grav['locator'];
|
||||
|
||||
// TODO: add default file
|
||||
$file = $this->get($variable);
|
||||
$this->image = ImageFile::open($file)
|
||||
->setCacheDir(basename(IMAGES_DIR))
|
||||
->setActualCacheDir(IMAGES_DIR)
|
||||
->setCacheDir($locator->findResource('cache://images', false))
|
||||
->setActualCacheDir($locator->findResource('cache://images', true))
|
||||
->setPrettyName(basename($this->get('basename')));
|
||||
|
||||
$this->filter();
|
||||
@@ -283,6 +445,31 @@ class Medium extends Data
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the image with cache.
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
protected function saveImage()
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
if ($this->get('debug') && !$this->debug_watermarked) {
|
||||
$ratio = $this->get('ratio');
|
||||
if (!$ratio) {
|
||||
$ratio = 1;
|
||||
}
|
||||
|
||||
$locator = self::$grav['locator'];
|
||||
$overlay = $locator->findResource("system://assets/responsive-overlays/{$ratio}x.png") ?: $locator->findResource('system://assets/responsive-overlays/unknown.png');
|
||||
$this->image->merge(ImageFile::open($overlay));
|
||||
}
|
||||
|
||||
return $this->image->cacheFile($this->type, $this->quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the medium.
|
||||
*
|
||||
@@ -295,7 +482,7 @@ class Medium extends Data
|
||||
|
||||
$path = $this->get('path') . '/' . $this->get('filename') . '.meta.' . $type;
|
||||
if ($type == 'yaml') {
|
||||
$this->merge(Yaml::instance($path)->content());
|
||||
$this->merge(CompiledYamlFile::instance($path)->content());
|
||||
} elseif (in_array($type, array('jpg', 'jpeg', 'png', 'gif'))) {
|
||||
$this->set('thumb', $path);
|
||||
}
|
||||
@@ -304,6 +491,28 @@ class Medium extends Data
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add alternative Medium to this Medium.
|
||||
*
|
||||
* @param $ratio
|
||||
* @param Medium $alternative
|
||||
*/
|
||||
public function addAlternative($ratio, Medium $alternative)
|
||||
{
|
||||
if (!is_numeric($ratio) || $ratio === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$alternative->set('ratio', $ratio);
|
||||
|
||||
$this->alternatives[(float) $ratio] = $alternative;
|
||||
}
|
||||
|
||||
public function getAlternatives()
|
||||
{
|
||||
return $this->alternatives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter image by using user defined filter parameters.
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,22 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use \Grav\Common\Filesystem\Folder;
|
||||
use \Grav\Common\Grav;
|
||||
use \Grav\Common\Config;
|
||||
use \Grav\Common\Data;
|
||||
use \Grav\Common\Registry;
|
||||
use \Grav\Common\Utils;
|
||||
use \Grav\Common\Cache;
|
||||
use \Grav\Common\Taxonomy;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Cache;
|
||||
use Grav\Common\Taxonomy;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* GravPages is the class that is the entry point into the hierarchy of pages
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Pages
|
||||
{
|
||||
@@ -20,11 +25,6 @@ class Pages
|
||||
*/
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var array|Page[]
|
||||
*/
|
||||
@@ -35,10 +35,15 @@ class Pages
|
||||
*/
|
||||
protected $children;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected $routes;
|
||||
protected $routes = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@@ -46,7 +51,7 @@ class Pages
|
||||
protected $sort;
|
||||
|
||||
/**
|
||||
* @var Data\Blueprints
|
||||
* @var Blueprints
|
||||
*/
|
||||
protected $blueprints;
|
||||
|
||||
@@ -55,14 +60,43 @@ class Pages
|
||||
*/
|
||||
protected $last_modified;
|
||||
|
||||
/**
|
||||
* @var Types
|
||||
*/
|
||||
static protected $types;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Grav $c
|
||||
*/
|
||||
public function __construct(Grav $c)
|
||||
{
|
||||
$this->grav = $c;
|
||||
$this->base = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set base path for the pages.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function base($path = null)
|
||||
{
|
||||
if ($path !== null) {
|
||||
$path = trim($path, '/');
|
||||
$this->base = $path ? '/' . $path : null;
|
||||
}
|
||||
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class initialization. Must be called before using this class.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->grav = Registry::get('Grav');
|
||||
$this->config = Registry::get('Config');
|
||||
|
||||
$this->buildPages();
|
||||
}
|
||||
|
||||
@@ -167,8 +201,11 @@ class Pages
|
||||
public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null)
|
||||
{
|
||||
$items = $collection->toArray();
|
||||
if (!$items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$lookup = md5(serialize($items));
|
||||
$lookup = md5(json_encode($items));
|
||||
if (!isset($this->sort[$lookup][$orderBy])) {
|
||||
$this->buildSort($lookup, $items, $orderBy, $orderManual);
|
||||
}
|
||||
@@ -188,10 +225,13 @@ class Pages
|
||||
*
|
||||
* @param string $path
|
||||
* @return Page
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function get($path)
|
||||
{
|
||||
if (!is_null($path) && !is_string($path)) throw new \Exception();
|
||||
if (!is_null($path) && !is_string($path)) {
|
||||
throw new \Exception();
|
||||
}
|
||||
return isset($this->instances[(string) $path]) ? $this->instances[(string) $path] : null;
|
||||
}
|
||||
|
||||
@@ -219,11 +259,33 @@ class Pages
|
||||
// Fetch page if there's a defined route to it.
|
||||
$page = isset($this->routes[$url]) ? $this->get($this->routes[$url]) : null;
|
||||
|
||||
// If the page cannot be reached, look into site wide routes.
|
||||
// If the page cannot be reached, look into site wide redirects, routes + wildcards
|
||||
if (!$all && (!$page || !$page->routable())) {
|
||||
$route = $this->config->get("site.routes.{$url}");
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// Try redirects
|
||||
$redirect = $config->get("site.redirects.{$url}");
|
||||
if ($redirect) {
|
||||
$this->grav->redirect($redirect);
|
||||
}
|
||||
|
||||
// See if route matches one in the site configuration
|
||||
$route = $config->get("site.routes.{$url}");
|
||||
if ($route) {
|
||||
$page = $this->dispatch($route, $all);
|
||||
} else {
|
||||
// Try looking for wildcards
|
||||
foreach ($config->get("site.routes") as $alias => $route) {
|
||||
$match = rtrim($alias, '*');
|
||||
if (strpos($alias, '*') !== false && strpos($url, $match) !== false) {
|
||||
$wildcard_url = str_replace('*', str_replace($match, '', $url), $route);
|
||||
$page = isset($this->routes[$wildcard_url]) ? $this->get($this->routes[$wildcard_url]) : null;
|
||||
if ($page) {
|
||||
return $page;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,19 +299,22 @@ class Pages
|
||||
*/
|
||||
public function root()
|
||||
{
|
||||
return $this->instances[rtrim(PAGES_DIR, DS)];
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
return $this->instances[rtrim($locator->findResource('page://'), DS)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a blueprint for a page type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return Data\Blueprint
|
||||
* @return Blueprint
|
||||
*/
|
||||
public function blueprints($type)
|
||||
{
|
||||
if (!isset($this->blueprints)) {
|
||||
$this->blueprints = new Data\Blueprints(THEMES_DIR . $this->config->get('system.pages.theme') . '/blueprints/');
|
||||
$this->blueprints = new Blueprints(self::getTypes());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -259,9 +324,7 @@ class Pages
|
||||
}
|
||||
|
||||
if (!$blueprint->initialized) {
|
||||
/** @var Grav $grav */
|
||||
$grav = Registry::get('Grav');
|
||||
$grav->fireEvent('onCreateBlueprint', $blueprint);
|
||||
$this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint]));
|
||||
$blueprint->initialized = true;
|
||||
}
|
||||
|
||||
@@ -291,25 +354,55 @@ class Pages
|
||||
$list[$current->route()] = str_repeat(' ', ($level-1)*2) . $current->title();
|
||||
}
|
||||
|
||||
foreach ($current as $next) {
|
||||
foreach ($current->children() as $next) {
|
||||
$list = array_merge($list, $this->getList($next, $level + 1));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available page types.
|
||||
*
|
||||
* @return Types
|
||||
*/
|
||||
public static function getTypes()
|
||||
{
|
||||
if (!self::$types) {
|
||||
self::$types = new Types();
|
||||
self::$types->scanBlueprints('theme://blueprints/');
|
||||
self::$types->scanTemplates('theme://templates/');
|
||||
|
||||
$event = new Event();
|
||||
$event->types = self::$types;
|
||||
Grav::instance()->fireEvent('onGetPageTemplates', $event);
|
||||
}
|
||||
|
||||
return self::$types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available page types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function types()
|
||||
public static function types()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = Registry::get('Config');
|
||||
$blueprints = new Data\Blueprints(THEMES_DIR . $config->get('system.pages.theme') . '/blueprints/');
|
||||
$types = self::getTypes();
|
||||
|
||||
return $blueprints->types();
|
||||
return $types->pageSelect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available page types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function modularTypes()
|
||||
{
|
||||
$types = self::getTypes();
|
||||
|
||||
return $types->modularSelect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,10 +410,13 @@ class Pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function parents()
|
||||
public static function parents()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Registry::get('Pages');
|
||||
$pages = $grav['pages'];
|
||||
|
||||
return $pages->getList();
|
||||
}
|
||||
|
||||
@@ -332,18 +428,39 @@ class Pages
|
||||
protected function buildPages()
|
||||
{
|
||||
$this->sort = array();
|
||||
if ($this->config->get('system.cache.enabled')) {
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
$pagesDir = $locator->findResource('page://');
|
||||
|
||||
if ($config->get('system.cache.enabled')) {
|
||||
/** @var Cache $cache */
|
||||
$cache = Registry::get('Cache');
|
||||
$cache = $this->grav['cache'];
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = Registry::get('Taxonomy');
|
||||
$last_modified = $this->config->get('system.cache.check.pages', true)
|
||||
? Folder::lastModified(PAGES_DIR) : 0;
|
||||
$page_cache_id = md5(USER_DIR.$last_modified);
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
|
||||
// how should we check for last modified? Default is by file
|
||||
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
|
||||
case 'none':
|
||||
case 'off':
|
||||
$last_modified = 0;
|
||||
break;
|
||||
case 'folder':
|
||||
$last_modified = Folder::lastModifiedFolder($pagesDir);
|
||||
break;
|
||||
default:
|
||||
$last_modified = Folder::lastModifiedFile($pagesDir);
|
||||
}
|
||||
|
||||
$page_cache_id = md5(USER_DIR.$last_modified.$config->checksum());
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
|
||||
if (!$this->instances) {
|
||||
$this->recurse();
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
$this->recurse($pagesDir);
|
||||
$this->buildRoutes();
|
||||
|
||||
// save pages, routes, taxonomy, and sort to cache
|
||||
@@ -353,10 +470,11 @@ class Pages
|
||||
);
|
||||
} else {
|
||||
// If pages was found in cache, set the taxonomy
|
||||
$this->grav['debugger']->addMessage('Page cache hit.');
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
}
|
||||
} else {
|
||||
$this->recurse();
|
||||
$this->recurse($pagesDir);
|
||||
$this->buildRoutes();
|
||||
}
|
||||
}
|
||||
@@ -365,21 +483,27 @@ class Pages
|
||||
* Recursive function to load & build page relationships.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param null $parent
|
||||
* @param Page|null $parent
|
||||
* @return Page
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function recurse($directory = PAGES_DIR, &$parent = null)
|
||||
protected function recurse($directory, Page &$parent = null)
|
||||
{
|
||||
$directory = rtrim($directory, DS);
|
||||
$iterator = new \DirectoryIterator($directory);
|
||||
$page = new Page;
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
$page->path($directory);
|
||||
$page->parent($parent);
|
||||
$page->orderDir($this->config->get('system.pages.order.dir'));
|
||||
$page->orderBy($this->config->get('system.pages.order.by'));
|
||||
if ($parent) {
|
||||
$page->parent($parent);
|
||||
}
|
||||
|
||||
$page->orderDir($config->get('system.pages.order.dir'));
|
||||
$page->orderBy($config->get('system.pages.order.by'));
|
||||
|
||||
// Add into instances
|
||||
if (!isset($this->instances[$page->path()])) {
|
||||
@@ -391,16 +515,24 @@ class Pages
|
||||
throw new \RuntimeException('Fatal error when creating page instances.');
|
||||
}
|
||||
|
||||
// set current modified of page
|
||||
$last_modified = $page->modified();
|
||||
|
||||
// flat for content availability
|
||||
$content_exists = false;
|
||||
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$name = $file->getFilename();
|
||||
$modified = $file->getMTime();
|
||||
|
||||
if ($file->isFile() && Utils::endsWith($name, CONTENT_EXT)) {
|
||||
|
||||
$page->init($file);
|
||||
$content_exists = true;
|
||||
|
||||
if ($this->config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onAfterPageProcessed', $page);
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
|
||||
} elseif ($file->isDir() && !$file->isDot()) {
|
||||
@@ -418,20 +550,26 @@ class Pages
|
||||
|
||||
$this->children[$page->path()][$child->path()] = array('slug' => $child->slug());
|
||||
|
||||
// set the modified time if not already set
|
||||
if (!$page->date()) {
|
||||
$page->date($file->getMTime());
|
||||
}
|
||||
|
||||
// set the last modified time on pages
|
||||
$this->lastModified($file->getMTime());
|
||||
|
||||
if ($this->config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onAfterFolderProcessed', $page);
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last modified if it's newer than already found
|
||||
if ($modified > $last_modified) {
|
||||
$last_modified = $modified;
|
||||
}
|
||||
}
|
||||
|
||||
// Set routability to false if no page found
|
||||
if (!$content_exists) {
|
||||
$page->routable(false);
|
||||
}
|
||||
|
||||
// Override the modified and ID so that it takes the latest change into account
|
||||
$page->modified($last_modified);
|
||||
$page->id($last_modified.md5($page->filePath()));
|
||||
|
||||
// Sort based on Defaults or Page Overridden sort order
|
||||
$this->children[$page->path()] = $this->sort($page);
|
||||
|
||||
@@ -444,7 +582,7 @@ class Pages
|
||||
protected function buildRoutes()
|
||||
{
|
||||
/** @var $taxonomy Taxonomy */
|
||||
$taxonomy = Registry::get('Taxonomy');
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
|
||||
// Build routes and taxonomy map.
|
||||
/** @var $page Page */
|
||||
@@ -465,8 +603,11 @@ class Pages
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// Alias and set default route to home page.
|
||||
$home = trim($this->config->get('system.home.alias'), '/');
|
||||
$home = trim($config->get('system.home.alias'), '/');
|
||||
if ($home && isset($this->routes['/' . $home])) {
|
||||
$this->routes['/'] = $this->routes['/' . $home];
|
||||
$this->get($this->routes['/' . $home])->route('/');
|
||||
@@ -484,12 +625,22 @@ class Pages
|
||||
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null)
|
||||
{
|
||||
$list = array();
|
||||
$header_default = null;
|
||||
$header_query = null;
|
||||
|
||||
// do this headery query work only once
|
||||
if (strpos($order_by, 'header.') === 0) {
|
||||
$header_query = explode('|', str_replace('header.', '', $order_by));
|
||||
if (isset($header_query[1])) {
|
||||
$header_default = $header_query[1];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pages as $key => $info) {
|
||||
|
||||
$child = isset($this->instances[$key]) ? $this->instances[$key] : null;
|
||||
if (!$child) {
|
||||
throw new \RuntimeException("Page does not exist: {$key}");
|
||||
throw new \RuntimeException("Page does not exist: {$key}");
|
||||
}
|
||||
|
||||
switch ($order_by) {
|
||||
@@ -503,11 +654,20 @@ class Pages
|
||||
$list[$key] = $child->modified();
|
||||
break;
|
||||
case 'slug':
|
||||
$list[$key] = $info['slug'];
|
||||
$list[$key] = $child->slug();
|
||||
break;
|
||||
case 'basename':
|
||||
$list[$key] = basename($key);
|
||||
break;
|
||||
case (is_string($header_query[0])):
|
||||
$child_header = new Header((array)$child->header());
|
||||
$header_value = $child_header->get($header_query[0]);
|
||||
if ($header_value) {
|
||||
$list[$key] = $header_value;
|
||||
} else {
|
||||
$list[$key] = $header_default ?: $key;
|
||||
}
|
||||
break;
|
||||
case 'manual':
|
||||
case 'default':
|
||||
default:
|
||||
@@ -515,8 +675,14 @@ class Pages
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by the new list.
|
||||
asort($list);
|
||||
// handle special case when order_by is random
|
||||
if ($order_by == 'random') {
|
||||
$list = $this->arrayShuffle($list);
|
||||
} else {
|
||||
// else just sort the list according to specified key
|
||||
asort($list);
|
||||
}
|
||||
|
||||
|
||||
// Move manually ordered items into the beginning of the list. Order of the unlisted items does not change.
|
||||
if (is_array($manual) && !empty($manual)) {
|
||||
@@ -544,4 +710,18 @@ class Pages
|
||||
$this->sort[$path][$order_by][$key] = $info;
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffles and associative array
|
||||
protected function arrayShuffle($list)
|
||||
{
|
||||
$keys = array_keys($list);
|
||||
shuffle($keys);
|
||||
|
||||
$new = array();
|
||||
foreach ($keys as $key) {
|
||||
$new[$key] = $list[$key];
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
}
|
||||
|
||||
86
system/src/Grav/Common/Page/Types.php
Normal file
86
system/src/Grav/Common/Page/Types.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ArrayAccess;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Countable;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Iterator;
|
||||
|
||||
class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
{
|
||||
use ArrayAccess, Constructor, Iterator, Countable, Export;
|
||||
|
||||
protected $items;
|
||||
|
||||
public function register($type, $blueprint = null)
|
||||
{
|
||||
if ($blueprint || empty($this->items[$type])) {
|
||||
$this->items[$type] = $blueprint;
|
||||
}
|
||||
}
|
||||
|
||||
public function scanBlueprints($path)
|
||||
{
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => [
|
||||
'key' => '|\.yaml$|'
|
||||
],
|
||||
'key' => 'SubPathName',
|
||||
'value' => 'PathName',
|
||||
];
|
||||
|
||||
$this->items = Folder::all($path, $options) + $this->items;
|
||||
}
|
||||
|
||||
public function scanTemplates($path)
|
||||
{
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.html\.twig$|',
|
||||
'filters' => [
|
||||
'value' => '|\.html\.twig$|'
|
||||
],
|
||||
'value' => 'Filename',
|
||||
'recursive' => false
|
||||
];
|
||||
|
||||
foreach (Folder::all($path, $options) as $type) {
|
||||
$this->register($type);
|
||||
}
|
||||
if (file_exists($path . 'modular/')) {
|
||||
foreach (Folder::all($path . 'modular/', $options) as $type) {
|
||||
$this->register('modular/' . $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function pageSelect()
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->items as $name => $file) {
|
||||
if (strpos($name, '/')) {
|
||||
continue;
|
||||
}
|
||||
$list[$name] = ucfirst(strtr($name, '_', ' '));
|
||||
}
|
||||
ksort($list);
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function modularSelect()
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->items as $name => $file) {
|
||||
if (strpos($name, 'modular/') !== 0) {
|
||||
continue;
|
||||
}
|
||||
$list[$name] = trim(ucfirst(strtr(basename($name), '_', ' ')));
|
||||
}
|
||||
ksort($list);
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user