mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
988 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b07fec1f | ||
|
|
9601061f25 | ||
|
|
b1317e56ec | ||
|
|
353832c386 | ||
|
|
8bf04d4593 | ||
|
|
c9f0500c6f | ||
|
|
0dc465bb41 | ||
|
|
34e50aab21 | ||
|
|
5a2411a0e6 | ||
|
|
178f66c940 | ||
|
|
6d3a7a3989 | ||
|
|
2400eaf04e | ||
|
|
752a3ca5bd | ||
|
|
6dbb6eb432 | ||
|
|
e580bb9998 | ||
|
|
d309491f06 | ||
|
|
0e35048143 | ||
|
|
c919ed36b4 | ||
|
|
6d2a7c53dc | ||
|
|
a8582fc131 | ||
|
|
76b7bd855d | ||
|
|
5656bb3caf | ||
|
|
57bd4d8f22 | ||
|
|
e71cd5a7ad | ||
|
|
4132388dda | ||
|
|
1edabe3b00 | ||
|
|
019fdd65e9 | ||
|
|
fa432cd32f | ||
|
|
4935679659 | ||
|
|
835c64c173 | ||
|
|
4d6ecbe618 | ||
|
|
63456aad85 | ||
|
|
bec2ee91b5 | ||
|
|
027c9cdd04 | ||
|
|
8ecea3a8c1 | ||
|
|
69e6d57346 | ||
|
|
8127d9cf31 | ||
|
|
32810efcd9 | ||
|
|
76b463792e | ||
|
|
f779fc57df | ||
|
|
7afef9073c | ||
|
|
1147516dcc | ||
|
|
3f1661965b | ||
|
|
8afad07146 | ||
|
|
81bce07a6e | ||
|
|
e883b57ac6 | ||
|
|
ae2f95b1ae | ||
|
|
d8df9ffb53 | ||
|
|
3c51c0acd4 | ||
|
|
c085540143 | ||
|
|
d239dd56d5 | ||
|
|
8f9eb3b48b | ||
|
|
1f906e6a50 | ||
|
|
e0a4efe181 | ||
|
|
9382dc9c10 | ||
|
|
aa85f20aa9 | ||
|
|
73654a99f9 | ||
|
|
ea8add59b1 | ||
|
|
5078ae62c0 | ||
|
|
e026ba32f4 | ||
|
|
e9ebe3b533 | ||
|
|
cea130700d | ||
|
|
0ae7ebac68 | ||
|
|
cabec818e2 | ||
|
|
718d443d52 | ||
|
|
57c5885216 | ||
|
|
14767a3e11 | ||
|
|
fb31caefef | ||
|
|
7ceb0dd065 | ||
|
|
b2a78d587c | ||
|
|
16c3a3690b | ||
|
|
7f8e8f67a5 | ||
|
|
9792c9a84e | ||
|
|
13e9e6f5e1 | ||
|
|
e62133233c | ||
|
|
3ec855e28f | ||
|
|
4b56a05f57 | ||
|
|
845da953e1 | ||
|
|
565a76c317 | ||
|
|
e01a116173 | ||
|
|
4e07c294c5 | ||
|
|
58bb5a6993 | ||
|
|
2c51dd5fe1 | ||
|
|
ce8513d3ff | ||
|
|
08c4fd02d2 | ||
|
|
2e680cd35a | ||
|
|
f12ef84a98 | ||
|
|
0563b2b6e5 | ||
|
|
c68c39df27 | ||
|
|
41c00d7fbe | ||
|
|
750dfb60dc | ||
|
|
4305bbabd5 | ||
|
|
ecc12be531 | ||
|
|
b65280f3c9 | ||
|
|
e077b3d04c | ||
|
|
91a963f580 | ||
|
|
8404ba7a09 | ||
|
|
86b907c86c | ||
|
|
ca899072d4 | ||
|
|
bbfc63e943 | ||
|
|
9bce9ce026 | ||
|
|
3a25f028df | ||
|
|
491c6d6a1f | ||
|
|
64bb6ea2ad | ||
|
|
084e59dc90 | ||
|
|
f7ea2e95e4 | ||
|
|
7acdf231a4 | ||
|
|
5d38e0fa14 | ||
|
|
e364616730 | ||
|
|
5ccefee288 | ||
|
|
93f4ad6e5a | ||
|
|
c481acbb71 | ||
|
|
bbdb0189f1 | ||
|
|
e1d655a3ac | ||
|
|
d849f8a03e | ||
|
|
67c3d64275 | ||
|
|
1546783371 | ||
|
|
21cc9f86f3 | ||
|
|
2936d26f8d | ||
|
|
ae453dbc71 | ||
|
|
d25699397f | ||
|
|
d461fac089 | ||
|
|
4bda629a6a | ||
|
|
8a2817a305 | ||
|
|
8887f8862d | ||
|
|
4883a408c7 | ||
|
|
7b75171f79 | ||
|
|
dc42288fa0 | ||
|
|
f576f914ae | ||
|
|
88faa5040c | ||
|
|
1e5365ba51 | ||
|
|
eb93dacba0 | ||
|
|
1e79cbc945 | ||
|
|
d350bd31cb | ||
|
|
89b9b4e9b7 | ||
|
|
9549e5aa4c | ||
|
|
1517d0e40b | ||
|
|
f13593aac7 | ||
|
|
b8023b2444 | ||
|
|
bb49098a05 | ||
|
|
c368fbcda9 | ||
|
|
1c1bf86e9a | ||
|
|
5f1b190ba9 | ||
|
|
0860f53d68 | ||
|
|
1ffd1cb6e7 | ||
|
|
030d230312 | ||
|
|
974f9d52a4 | ||
|
|
1a238ea1b1 | ||
|
|
a049441048 | ||
|
|
20e771f121 | ||
|
|
cff4e225e6 | ||
|
|
2c69f539ae | ||
|
|
548081471c | ||
|
|
3688cfa397 | ||
|
|
20b2856dee | ||
|
|
7e94e46459 | ||
|
|
f3b4efb661 | ||
|
|
7947ba2442 | ||
|
|
ef63e993d2 | ||
|
|
29ae5b7aae | ||
|
|
6baf7e0b35 | ||
|
|
2e7ece17d7 | ||
|
|
2a56f21d13 | ||
|
|
d253c3c6c5 | ||
|
|
289a838ba1 | ||
|
|
f6f3e96106 | ||
|
|
7d22305678 | ||
|
|
7571d1d562 | ||
|
|
b4c06f537d | ||
|
|
830c723bae | ||
|
|
b83ab07374 | ||
|
|
8200cb9336 | ||
|
|
b1a38306af | ||
|
|
6ec0f4782f | ||
|
|
d25f9bcf1b | ||
|
|
cd3fd5a7b7 | ||
|
|
1ab1378259 | ||
|
|
21f87ade2d | ||
|
|
8f54e5739f | ||
|
|
21a6594573 | ||
|
|
01ce80fb1a | ||
|
|
174672c411 | ||
|
|
4785103081 | ||
|
|
7030422b11 | ||
|
|
2a06dc9bea | ||
|
|
66927043de | ||
|
|
7d16bafd52 | ||
|
|
ee340e2d6f | ||
|
|
66d9fd1a5e | ||
|
|
7c28de6ae5 | ||
|
|
c95f602ea2 | ||
|
|
6361280d99 | ||
|
|
b3a9d7cd41 | ||
|
|
071989c554 | ||
|
|
f956d7113f | ||
|
|
f0472fdd76 | ||
|
|
01899676a4 | ||
|
|
b13d572ca8 | ||
|
|
a588e08405 | ||
|
|
6c93483220 | ||
|
|
733c13102b | ||
|
|
688d6fe17a | ||
|
|
25ff1f230f | ||
|
|
094b58130a | ||
|
|
a18ec00962 | ||
|
|
d9188e76ed | ||
|
|
a04a79af3e | ||
|
|
0c84392d0f | ||
|
|
be12f350cb | ||
|
|
01c7eadc92 | ||
|
|
4b3abc282a | ||
|
|
81358e9984 | ||
|
|
8179d8eac1 | ||
|
|
c22a2579a9 | ||
|
|
0a26772e35 | ||
|
|
364fc7afa8 | ||
|
|
82bd54eb0b | ||
|
|
d92d9bafc6 | ||
|
|
9c541ee20b | ||
|
|
105dc34b2a | ||
|
|
0d8b46a157 | ||
|
|
6142a12f48 | ||
|
|
1bda3eb1e3 | ||
|
|
96b2b327b2 | ||
|
|
e014b12626 | ||
|
|
d473f72edd | ||
|
|
7139425812 | ||
|
|
f5722d7baa | ||
|
|
467eb00f0f | ||
|
|
0ce3646977 | ||
|
|
53dd6c0860 | ||
|
|
e4a130c919 | ||
|
|
0d9ddb92d5 | ||
|
|
68a561d4fb | ||
|
|
5e651dd0e5 | ||
|
|
1bb2965916 | ||
|
|
4ea650fc6d | ||
|
|
501b38c4ba | ||
|
|
ede958bd61 | ||
|
|
f3d099e655 | ||
|
|
db5c3ea400 | ||
|
|
504f57930a | ||
|
|
02b1b2cf9d | ||
|
|
99dbb3225b | ||
|
|
08a8d69be2 | ||
|
|
a029f89ad2 | ||
|
|
ed02fed866 | ||
|
|
06de6c8e17 | ||
|
|
9ddfcd2154 | ||
|
|
217e5e5a2c | ||
|
|
272bf357d4 | ||
|
|
9a5d14aa13 | ||
|
|
2b70b2ad4f | ||
|
|
9323385b25 | ||
|
|
fe90204772 | ||
|
|
1b66e3d2a1 | ||
|
|
a2e1b9e100 | ||
|
|
74ab81b524 | ||
|
|
01be2df935 | ||
|
|
62e5ea3bbd | ||
|
|
b8c274b7b8 | ||
|
|
e154b13b6e | ||
|
|
0f312a3c43 | ||
|
|
d8823a6b3a | ||
|
|
73d5f9da90 | ||
|
|
7707b042e5 | ||
|
|
ff95a116c2 | ||
|
|
2fe6dda365 | ||
|
|
f30a3c137e | ||
|
|
a5d31e7187 | ||
|
|
73c42313fa | ||
|
|
686ba8a3f6 | ||
|
|
3397d5d2b7 | ||
|
|
9ab3524fc5 | ||
|
|
3f10fa1b4c | ||
|
|
6811fbea3d | ||
|
|
3e245ef686 | ||
|
|
28cff4e1da | ||
|
|
2bf67e482d | ||
|
|
3976e4ce23 | ||
|
|
a1ab94ffdd | ||
|
|
d6bed5441d | ||
|
|
e505c409ac | ||
|
|
409742c078 | ||
|
|
6bd76028ce | ||
|
|
87dc53912b | ||
|
|
83d606c34a | ||
|
|
1f906326e7 | ||
|
|
ec67bf4c5b | ||
|
|
127fe7fa2a | ||
|
|
e0de6f8b5f | ||
|
|
e81e35b7c2 | ||
|
|
9bc365fe9e | ||
|
|
21772c5481 | ||
|
|
7b32cbe2e1 | ||
|
|
04a2f618bd | ||
|
|
73a104000a | ||
|
|
b8cb788324 | ||
|
|
690bb8a8be | ||
|
|
26b0b02de2 | ||
|
|
e5348ec8f1 | ||
|
|
5e22eee1a8 | ||
|
|
534bb57d3f | ||
|
|
f8c203c033 | ||
|
|
300155e82e | ||
|
|
3117a214d4 | ||
|
|
9959868022 | ||
|
|
9248c5b709 | ||
|
|
85fb5bccf3 | ||
|
|
bd595ca9f6 | ||
|
|
ace8823bd6 | ||
|
|
5051b15145 | ||
|
|
1c1f2c268a | ||
|
|
545c76088c | ||
|
|
b418a7fd1e | ||
|
|
2938848df1 | ||
|
|
17172f8920 | ||
|
|
aeb237633e | ||
|
|
d8a29dd639 | ||
|
|
5c139e4b3c | ||
|
|
d69a0a9b06 | ||
|
|
07ce9c83ac | ||
|
|
1d3559f0db | ||
|
|
be92a1da97 | ||
|
|
97b430a888 | ||
|
|
b696e0d790 | ||
|
|
5b08a8605e | ||
|
|
ab358dd820 | ||
|
|
573b3227c7 | ||
|
|
50785c2434 | ||
|
|
fb9705809d | ||
|
|
1aeac01284 | ||
|
|
0cc35b56e5 | ||
|
|
1423375312 | ||
|
|
640ba16f8b | ||
|
|
d4e17442b2 | ||
|
|
eb4eafd915 | ||
|
|
dc65475723 | ||
|
|
3f33e97f0c | ||
|
|
02508933d7 | ||
|
|
89ebf2b011 | ||
|
|
c747c4baf7 | ||
|
|
a3c848e4e2 | ||
|
|
345d61f04f | ||
|
|
f0585ddb4e | ||
|
|
a6790cace3 | ||
|
|
f244fc93c8 | ||
|
|
846a0baed8 | ||
|
|
26ebb8fa6d | ||
|
|
cb8ea7780f | ||
|
|
5400bd3951 | ||
|
|
03521cd3f6 | ||
|
|
1eb0e37214 | ||
|
|
3e769618ec | ||
|
|
9319d5c0aa | ||
|
|
eb94940df6 | ||
|
|
a4c8c53939 | ||
|
|
567dd0d2c6 | ||
|
|
6f06e0c424 | ||
|
|
8dfb9d08c4 | ||
|
|
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 | ||
|
|
15db488abc | ||
|
|
2c4a724fc0 | ||
|
|
3568c25ef1 | ||
|
|
3948aa77a8 | ||
|
|
0d0c22c940 | ||
|
|
1ac4014d64 | ||
|
|
627646bf35 | ||
|
|
67a325e8e9 | ||
|
|
69e345f64f | ||
|
|
37980d15e4 | ||
|
|
3fc6c47b5e | ||
|
|
dee24787ba | ||
|
|
7d9142fa03 | ||
|
|
51cca81bc8 | ||
|
|
8de6231116 | ||
|
|
fbc7ca4ef2 | ||
|
|
b507815eef | ||
|
|
c56efb9a10 | ||
|
|
eba9002400 | ||
|
|
7d5426144d |
@@ -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,10 +1,14 @@
|
||||
# Composer
|
||||
composer.lock
|
||||
.composer
|
||||
vendor/
|
||||
|
||||
# Sass
|
||||
.sass-cache
|
||||
|
||||
# Grav Specific
|
||||
backup/*
|
||||
!backup/.*
|
||||
cache/*
|
||||
!cache/.*
|
||||
assets/*
|
||||
|
||||
55
.htaccess
Executable file → Normal file
55
.htaccess
Executable file → Normal file
@@ -2,7 +2,7 @@
|
||||
|
||||
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
|
||||
@@ -11,29 +11,46 @@ RewriteEngine On
|
||||
|
||||
# RewriteBase /
|
||||
|
||||
# Access site
|
||||
## 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
|
||||
|
||||
# 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|backup)/(.*) 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
|
||||
|
||||
78
.travis.yml
Normal file
78
.travis.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
language: php
|
||||
php: 5.6
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- build_test
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: always
|
||||
hipchat:
|
||||
# hipchat_api@grav
|
||||
rooms:
|
||||
- secure: "bqO0wM1B7bJnQw2fuhquSXEqI9gw6WmFytIh9sEWXzbYTzTUP5t0PcKOd3FT2BNMRaDxPJLVl+vG/oqmqDUBkEmOGcG504IQjeNzZqnMz0tXQMIcCc22Las9tFfc4Jf6RVi/qGomFtHGE9Wgii+TAN4zqZaufbNjwd8SyjO0+W8="
|
||||
template:
|
||||
- '%{repository}#%{build_number} (%{branch}): Grav Core and Skeletons packages have been uploaded. (<a href="%{build_url}">Details</a>)'
|
||||
format: html
|
||||
env:
|
||||
global:
|
||||
# Colors!
|
||||
- TEXTRESET=$(tput sgr0) # reset the foreground colour
|
||||
- RED=$(tput setaf 1)
|
||||
- GREEN=$(tput setaf 2)
|
||||
- YELLOW=$(tput setaf 3)
|
||||
- BLUE=$(tput setaf 4)
|
||||
- BOLD=$(tput bold)
|
||||
# User
|
||||
- GH_USER="getgrav"
|
||||
# Paths
|
||||
- RT_DEVTOOLS=$HOME/devtools
|
||||
- GOPATH="$HOME/go"
|
||||
- PATH="$GOPATH/bin:$PATH"
|
||||
# GH_TOKEN [API Key]
|
||||
- secure: "jS+c+g2v33vypG4VtqiSDW2qQ4dGJZlrUKBRCztoy1yrOrYRPvc5Vzi/AS3fDmZ4yizukEwmUNNzyZQcgFvLPpmCCml46Dovp8R9OXhbNe8OnULmaSn2Zkr71oblMYu6ZP+RpYvNq0BIdSB3u2TiFriHMiTIkX9UwZNaUCOX1ig="
|
||||
# BB_TOKEN value => "user:pass@"
|
||||
- secure: "einUtSEkUWy2IrqLXyVjwUU+mwaaoiOXRRVdLBpA3Zye6bZx8cm5h/5AplkPWhM/NmCJoW/MwNZHHkFhlr3mDRov5iOxVmTTYfnXB+I5lxYTSgduOLLErS7mU8hfADpVDU8bHNU44fNGD3UEiG1PD4qQBX4DMlqIFmR20mjs81k="
|
||||
# GH_API_USER [for curl]
|
||||
- secure: "Xbk/V9aIys0NxccJGR3Zrm2GRxDnA0RuazBs1puIboTYDhbi0Z7JTL+mOx3xp5Kfoniad/xAuijQESTM9MMrKqq/qCzhAMaC1+vcL4pCHZH4NSG6DBxB9BPkKVFq+1llu5FTEf8bkxHzwGR0l1ARW6TVRcgTHr5B58bCEIwEOrI="
|
||||
# Latest Release version
|
||||
- TRAVIS_TAG=$(curl --fail --user ${GH_API_USER} -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4)
|
||||
|
||||
before_install:
|
||||
- composer self-update
|
||||
- go get github.com/aktau/github-release
|
||||
- git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
|
||||
- if [ ! -z "$TRAVIS_TAG" ]; then
|
||||
cd $RT_DEVTOOLS;
|
||||
./build-grav.sh skeletons.txt;
|
||||
fi
|
||||
script:
|
||||
- if [ ! -z "$TRAVIS_TAG" ]; then
|
||||
FILES="$RT_DEVTOOLS/grav-dist/*.zip";
|
||||
for file in ${FILES[@]}; do
|
||||
NAME=${file##*/};
|
||||
REPO="$(echo ${NAME} | rev | cut -f 2- -d "-" | rev)";
|
||||
if [[ $REPO == 'grav' || $REPO == 'grav-update' ]]; then
|
||||
REPO="grav";
|
||||
fi;
|
||||
API="$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/${GH_USER}/${REPO}/releases/latest)";
|
||||
ASSETS="$(echo "${API}" | node gh-assets.js)";
|
||||
TAG="$(echo "${API}" | grep tag_name | head -n 1 | cut -d '"' -f 4)";
|
||||
if [ $REPO == "grav" ]; then
|
||||
TAG=$TRAVIS_TAG;
|
||||
fi;
|
||||
if [ ! -z "$ASSETS" ]; then
|
||||
for asset in ${ASSETS[@]}; do
|
||||
asset_id=$(echo ${asset} | cut -d ':' -f 1);
|
||||
asset_name=$(echo ${asset} | cut -d ':' -f 2);
|
||||
if [ "${NAME}" == "${asset_name}" ]; then
|
||||
echo -e "\nAsset ${BOLD}${BLUE}${NAME}${TEXTRESET} already exists in ${YELLOW}${REPO}${TEXTRESET}@${BOLD}${YELLOW}${TAG}${TEXTRESET}... deleting id ${BOLD}${RED}${asset_id}${TEXTRESET}...";
|
||||
curl -X DELETE --fail --user "${GH_API_USER}" "https://api.github.com/repos/${GH_USER}/${REPO}/releases/assets/${asset_id}";
|
||||
fi;
|
||||
done;
|
||||
fi;
|
||||
echo "Uploading package ${BOLD}${BLUE}${NAME}${TEXTRESET} to ${YELLOW}${REPO}${TEXTRESET}@${YELLOW}${TAG}${TEXTRESET}";
|
||||
github-release upload --security-token $GH_TOKEN --user ${GH_USER} --repo $REPO --tag $TAG --name "$NAME" --file "$file";
|
||||
done;
|
||||
fi
|
||||
538
CHANGELOG.md
Normal file
538
CHANGELOG.md
Normal file
@@ -0,0 +1,538 @@
|
||||
# v0.9.29
|
||||
## 06/22/2015
|
||||
|
||||
1. [](#new)
|
||||
* New and improved Regex-powered redirect and route alias logic
|
||||
* Added new `onBuildPagesInitialized` event for memory critical or time-consuming plugins
|
||||
* Added a `setSummary()` method for pages
|
||||
2. [](#improved)
|
||||
* Improved `MergeConfig()` logic for more control
|
||||
* Travis skeleton build trigger implemented
|
||||
* Set composer.json versions to stable versions where possible
|
||||
* Disabled `last_modified` and `etag` page headers by default (causing too much page caching)
|
||||
3. [](#bugfix)
|
||||
* Preload classes during `bin/gpm selfupgrade` to avoid issues with updated classes
|
||||
* Fix for directory relative _down_ links
|
||||
|
||||
# v0.9.28
|
||||
## 06/16/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added method to set raw markdown on a page
|
||||
* Added ability to enabled system and page level `etag` and `last_modified` headers
|
||||
2. [](#improved)
|
||||
* Improved image path processing
|
||||
* Improved query string handling
|
||||
* Optimization to image handling supporting URL encoded filenames
|
||||
* Use global `composer` when available rather than Grv provided one
|
||||
* Use `PHP_BINARY` contant rather than `php` executable
|
||||
* Updated Doctrine Cache library
|
||||
* Updated Symfony libraries
|
||||
* Moved `convertUrl()` method to Uri object
|
||||
3. [](#bugfix)
|
||||
* Fix incorrect slug causing problems with CLI `uninstall`
|
||||
* Fix Twig runtime error with assets pipeline in sufolder installations
|
||||
* Fix for `+` in image filenames
|
||||
* Fix for dot files causing issues with page processing
|
||||
* Fix for Uri path detection on Windows platform
|
||||
* Fix for atlernative media resolutions
|
||||
* Fix for modularTypes key properties
|
||||
|
||||
# v0.9.27
|
||||
## 05/09/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added new composer CLI command
|
||||
* Added page-level summary header overrides
|
||||
* Added `size` back for Media objects
|
||||
* Refactored Backup command in preparation for admin plugin
|
||||
* Added a new `parseLinks` method to Plugins class
|
||||
* Added `starts_with` and `ends_with` Twig filters
|
||||
2. [](#improved)
|
||||
* Opitmized install of vendor libraries for speed improvement
|
||||
* Improved configuration handling in preparation for admin plugin
|
||||
* Cache optimization: Don't cache Twig templates when you pass dynamic params
|
||||
* Moved `Utils::rcopy` to `Folder::rcopy`
|
||||
* Improved `Folder::doDelete`
|
||||
* Added check for required Curl in GPM
|
||||
* Updated included composer.phar to latest version
|
||||
* Various blueprint fixes for admin plugin
|
||||
* Various PSR and code cleanup tasks
|
||||
3. [](#bugfix)
|
||||
* Fix issue with Gzip not working with `onShutDown()` event
|
||||
* Fix for URLs with trailing slashes
|
||||
* Handle condition where certain errors resulted in blank page
|
||||
* Fix for issue with theme name equal to base_url and asset pipeline
|
||||
* Fix to properly nomralize font rewrite path
|
||||
* Fix for absolute URLs below the current page
|
||||
* Fix for `..` page references
|
||||
|
||||
# v0.9.26
|
||||
## 04/24/2015
|
||||
|
||||
3. [](#bugfix)
|
||||
* Fixed issue with homepage routes failing with 'dirname' error
|
||||
|
||||
# v0.9.25
|
||||
## 04/24/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added support for E-Tag, Last-Modified, Cache-Control and Page-based expires headers
|
||||
2. [](#improved)
|
||||
* Refactored media image handling to make it more flexible and support absolute paths
|
||||
* Refactored page modification check process to make it faster
|
||||
* User account improvements in preparation for Admin plugin
|
||||
* Protect against timing attacks
|
||||
* Reset default system expires time to 0 seconds (can override if you need to)
|
||||
3. [](#bugfix)
|
||||
* Fix issues with spaces in webroot when using `bin/grav install`
|
||||
* Fix for spaces in relative directory
|
||||
* Bug fix in collection filtering
|
||||
|
||||
# v0.9.24
|
||||
## 04/15/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added support for chunked downloads of Assets
|
||||
* Added new `onBeforeDownload()` event
|
||||
* Added new `download()` and `getMimeType()` methods to Utils class
|
||||
* Added configuration option for supported page types
|
||||
* Added assets and media timestamp options (off by default)
|
||||
* Added page expires configuration option
|
||||
2. [](#bugfix)
|
||||
* Fixed issue with Nginx/Gzip and `ob_flush()` throwing error
|
||||
* Fixed assets actions on 'direct media' URLs
|
||||
* Fix for 'direct assets` with any parameters
|
||||
|
||||
# v0.9.23
|
||||
## 04/09/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for broken GPM `selfupgrade` (Grav 0.9.21 and 0.9.22 will need to manually upgrade to this version)
|
||||
|
||||
# v0.9.22
|
||||
## 04/08/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix to normalize GRAV_ROOT path for Windows
|
||||
* Fix to normalize Media image paths for Windows
|
||||
* Fix for GPM `selfupgrade` when you are on latest version
|
||||
|
||||
# v0.9.21
|
||||
## 04/07/2015
|
||||
|
||||
1. [](#new)
|
||||
* Major Media functionality enhancements: SVG, Animated GIF, Video support!
|
||||
* Added ability to configure default image quality in system configuration
|
||||
* Added `sizes` attributes for custom retina image breakpoints
|
||||
2. [](#improved)
|
||||
* Don't scale @1x retina images
|
||||
* Add filter to Iterator class
|
||||
* Updated various composer packages
|
||||
* Various PSR fixes
|
||||
|
||||
# v0.9.20
|
||||
## 03/24/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added `addAsyncJs()` and `addDeferJs()` to Assets manager
|
||||
* Added support for extranal URL redirects
|
||||
2. [](#improved)
|
||||
* Fix unpredictable asset ordering when set from plugin/system
|
||||
* Updated `nginx.conf` to ensure system assets are accessible
|
||||
* Ensure images are served as static files in Nginx
|
||||
* Updated vendor libraries to latest versions
|
||||
* Updated included composer.phar to latest version
|
||||
3. [](#bugfix)
|
||||
* Fixed issue with markdown links to `#` breaking HTML
|
||||
|
||||
# v0.9.19
|
||||
## 02/28/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added named assets capability and bundled jQuery into Grav core
|
||||
* Added `first()` and `last()` to `Iterator` class
|
||||
2. [](#improved)
|
||||
* Improved page modification routine to skip _dot files_
|
||||
* Only use files to calculate page modification dates
|
||||
* Broke out Folder iterators into their own classes
|
||||
* Various Sensiolabs Insight fixes
|
||||
3. [](#bugfix)
|
||||
* Fixed `Iterator.nth()` method
|
||||
|
||||
# v0.9.18
|
||||
## 02/19/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added ability for GPM `install` to automatically install `_demo` content if found (w/backup)
|
||||
* Added ability for themes and plugins to have dependencies required to install via GPM
|
||||
* Added ability to override the system timezone rather than relying on server setting only
|
||||
* Added new Twig filter `random_string` for generating random id values
|
||||
* Added new Twig filter `markdown` for on-the-fly markdown processing
|
||||
* Added new Twig filter `absoluteUrl` to convert relative to absolute URLs
|
||||
* Added new `processTemplate()` method to Twig object for on-the-fly processing of twig template
|
||||
* Added `rcopy()` and `contains()` helper methods in Utils
|
||||
2. [](#improved)
|
||||
* Provided new `param_sep` variable to better support Apache on Windows
|
||||
* Moved parsedown configuration into the trait
|
||||
* Added optional **deep-copy** option to `mergeConfig()` for plugins
|
||||
* Updated bundled `composer.phar` package
|
||||
* Various Sensiolabs Insight fixes - Silver level now!
|
||||
* Various PSR Fixes
|
||||
3. [](#bugfix)
|
||||
* Fix for windows platforms not displaying installed themes/plugins via GPM
|
||||
* Fix page IDs not picking up folder-only pages
|
||||
|
||||
# v0.9.17
|
||||
## 02/05/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added **full HHVM support!** Get your speed on with Facebook's crazy fast PHP JIT compiler
|
||||
2. [](#improved)
|
||||
* More flexible page summary control
|
||||
* Support **CamelCase** plugin and theme class names. Replaces dashes and underscores
|
||||
* Moved summary delimiter into `site.yaml` so it can be configurable
|
||||
* Various PSR fixes
|
||||
3. [](#bugfix)
|
||||
* Fix for `mergeConfig()` not falling back to defaults
|
||||
* Fix for `addInlineCss()` and `addInlineJs()` Assets not working between Twig tags
|
||||
* Fix for Markdown adding HTML tags into inline CSS and JS
|
||||
|
||||
# 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
|
||||
34
README.md
34
README.md
@@ -1,8 +1,10 @@
|
||||
#  Grav
|
||||
|
||||
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.
|
||||
[](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
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:
|
||||
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 principles 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 is designed to use well-established and _best-in-class_ technologies, 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
|
||||
@@ -38,6 +40,34 @@ 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.
|
||||
|
||||
0
backup/.gitkeep
Normal file
0
backup/.gitkeep
Normal file
BIN
bin/composer.phar
Executable file
BIN
bin/composer.phar
Executable file
Binary file not shown.
56
bin/gpm
Executable file
56
bin/gpm
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/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')){
|
||||
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
|
||||
}
|
||||
|
||||
use Grav\Common\Composer;
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
$composer = Composer::getComposerExecutor();
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o 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!');
|
||||
}
|
||||
|
||||
if (!function_exists('curl_version')) {
|
||||
exit('FATAL: GPM requires PHP Curl module to be installed');
|
||||
}
|
||||
|
||||
$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();
|
||||
30
bin/grav
30
bin/grav
@@ -1,13 +1,28 @@
|
||||
#!/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')){
|
||||
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
|
||||
}
|
||||
|
||||
use Grav\Common\Composer;
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
$composer = Composer::getComposerExecutor();
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install');
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
@@ -19,11 +34,12 @@ 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\ComposerCommand(),
|
||||
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();
|
||||
|
||||
@@ -8,31 +8,28 @@
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"twig/twig": "~1.16",
|
||||
"erusev/parsedown-extra": "dev-master",
|
||||
"symfony/yaml": "~2.5",
|
||||
"symfony/console": "~2.5",
|
||||
"symfony/event-dispatcher": "~2.5",
|
||||
"doctrine/cache": "~1.3",
|
||||
"tracy/tracy": "~2.2",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "2.7.*",
|
||||
"symfony/console": "2.7.*",
|
||||
"symfony/event-dispatcher": "2.7.*",
|
||||
"doctrine/cache": "~1.4",
|
||||
"maximebf/debugbar": "dev-master",
|
||||
"filp/whoops": "1.2.*@dev",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "~2.0",
|
||||
"ircmaxell/password-compat": "1.0.*",
|
||||
"mrclay/minify": "dev-master",
|
||||
"donatj/phpuseragentparser": "dev-master",
|
||||
"pimple/pimple": "~3.0"
|
||||
"mrclay/minify": "~2.2",
|
||||
"donatj/phpuseragentparser": "~0.3",
|
||||
"pimple/pimple": "~3.0",
|
||||
"rockettheme/toolbox": "1.0.*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Grav\\": "system/src/"
|
||||
"psr-4": {
|
||||
"Grav\\": "system/src/Grav"
|
||||
},
|
||||
"files": ["system/defines.php"]
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["VERSION"]
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/rhukster/minify"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
23
index.php
23
index.php
@@ -2,34 +2,33 @@
|
||||
namespace Grav;
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
exit(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
throw new \RuntimeException(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
}
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
exit('Please run: <i>composer install -o</i>');
|
||||
throw new \RuntimeException("Please run: <i>bin/grav install</i>");
|
||||
}
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Debugger;
|
||||
|
||||
// Register the auto-loader.
|
||||
$loader = require_once $autoload;
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
// Get the Grav instance
|
||||
$grav = Grav::instance(
|
||||
[
|
||||
'loader' => $loader,
|
||||
'debugger' => new Debugger(Debugger::PRODUCTION)
|
||||
]
|
||||
array(
|
||||
'loader' => $loader
|
||||
)
|
||||
);
|
||||
|
||||
// Process the page
|
||||
try {
|
||||
$grav['debugger']->init();
|
||||
$grav->process();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$grav->fireEvent('onFatalException');
|
||||
throw $e;
|
||||
|
||||
39
nginx.conf
Executable file → Normal file
39
nginx.conf
Executable file → Normal file
@@ -26,27 +26,35 @@ http {
|
||||
if (!-e $request_filename){ rewrite ^(.*)$ /index.php last; }
|
||||
}
|
||||
|
||||
location /images/ {
|
||||
# Serve images as static
|
||||
}
|
||||
|
||||
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 {
|
||||
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 /backup {
|
||||
rewrite ^/backup/(.*) /error redirect;
|
||||
}
|
||||
|
||||
location /vendor {
|
||||
rewrite ^/vendor/(.*)$ /error redirect;
|
||||
}
|
||||
location /system {
|
||||
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /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
|
||||
@@ -60,7 +68,6 @@ http {
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 |
4
system/assets/jquery/jquery-2.1.3.min.js
vendored
Normal file
4
system/assets/jquery/jquery-2.1.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
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,5 +1,5 @@
|
||||
title: Media
|
||||
validation: loose
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
72
system/blueprints/config/site.yaml
Normal file
72
system/blueprints/config/site.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
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
|
||||
validate:
|
||||
type: 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
|
||||
486
system/blueprints/config/system.yaml
Normal file
486
system/blueprints/config/system.yaml
Normal file
@@ -0,0 +1,486 @@
|
||||
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
|
||||
pages.publish_dates:
|
||||
type: toggle
|
||||
label: Date-based publishing
|
||||
help: Automatically (un)publish posts based on their date
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
|
||||
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)"
|
||||
|
||||
cache.gzip:
|
||||
type: toggle
|
||||
label: GZIP compression
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.enable_asset_timestamp:
|
||||
type: toggle
|
||||
label: Enable timestamps on assets
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
media.enable_media_timestamp:
|
||||
type: toggle
|
||||
label: Enable timestamps on media
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
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
|
||||
|
||||
errors:
|
||||
type: section
|
||||
title: Error handler
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
errors.display:
|
||||
type: toggle
|
||||
label: Display errors
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
errors.log:
|
||||
type: toggle
|
||||
label: Log errors
|
||||
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
|
||||
|
||||
media:
|
||||
type: section
|
||||
title: Media
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
images.default_image_quality:
|
||||
type: text
|
||||
label: Default image quality
|
||||
classes: x-small
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
max: 100
|
||||
|
||||
images.debug:
|
||||
type: toggle
|
||||
label: Image debug watermark
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
media.upload_limit:
|
||||
type: text
|
||||
label: File upload limit
|
||||
classes: small
|
||||
validate:
|
||||
type: number
|
||||
|
||||
system:
|
||||
type: section
|
||||
title: System
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
timezone:
|
||||
type: select
|
||||
label: Timezone
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Utils::timezones'
|
||||
default: ''
|
||||
options:
|
||||
'': '- None -'
|
||||
|
||||
param_sep:
|
||||
type: select
|
||||
label: Parameter separator
|
||||
classes: fancy
|
||||
default: ''
|
||||
options:
|
||||
':': ': (default)'
|
||||
';': '; (use this for apache on Windows)'
|
||||
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:
|
||||
'': '- Select -'
|
||||
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
|
||||
41
system/blueprints/user/account.yaml
Normal file
41
system/blueprints/user/account.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
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
|
||||
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
|
||||
@@ -20,10 +20,15 @@ png:
|
||||
thumb: media/thumb-png.png
|
||||
mime: image/png
|
||||
gif:
|
||||
type: image
|
||||
type: animated
|
||||
thumb: media/thumb-gif.png
|
||||
mime: image/gif
|
||||
|
||||
svg:
|
||||
type: vector
|
||||
thumb: media/thumb-gif.png
|
||||
mime: image/svg+xml
|
||||
|
||||
mp4:
|
||||
type: video
|
||||
thumb: media/thumb-mp4.png
|
||||
@@ -40,6 +45,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,31 @@
|
||||
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 (deprecated)
|
||||
|
||||
metadata:
|
||||
description: 'My Grav Site' # Site description
|
||||
|
||||
summary:
|
||||
size: 300 # Maximum length of summary (characters)
|
||||
enabled: true # enable or disable summary of page
|
||||
format: short # long = summary delimiter will be ignored; short = use the first occurence of delimter or size
|
||||
size: 300 # Maximum length of summary (characters)
|
||||
delimiter: === # The summary delimiter
|
||||
|
||||
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
|
||||
|
||||
#menu: # Sample Menu Example
|
||||
# - text: Source
|
||||
# icon: github
|
||||
# url: https://github.com/getgrav/grav
|
||||
# - icon: twitter
|
||||
# url: http://twitter.com/getgrav
|
||||
|
||||
@@ -1,47 +1,21 @@
|
||||
schemes:
|
||||
plugin:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/plugins
|
||||
- system/plugins
|
||||
|
||||
asset:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- assets
|
||||
|
||||
cache:
|
||||
image:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- cache
|
||||
|
||||
log:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- logs
|
||||
|
||||
user:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user
|
||||
- user://images
|
||||
- system://images
|
||||
|
||||
page:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/pages
|
||||
- user://pages
|
||||
|
||||
account:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/accounts
|
||||
|
||||
data:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/data
|
||||
|
||||
theme:
|
||||
type: ReadOnlyStream
|
||||
prefixes:
|
||||
'/':
|
||||
- user/themes
|
||||
- user://accounts
|
||||
|
||||
@@ -1,49 +1,86 @@
|
||||
absolute_urls: false # Absolute or relative URLs for `base_url`
|
||||
timezone: '' # Valid values: http://php.net/manual/en/timezones.php
|
||||
param_sep: ':' # Parameter separator, use ';' for Apache on windows
|
||||
|
||||
home:
|
||||
alias: '/home' # Default path for home, ie /
|
||||
|
||||
pages:
|
||||
theme: antimatter # Default theme (defaults to "antimatter" theme)
|
||||
markdown_extra: false # Enable support for Markdown Extra support (GFM by default)
|
||||
order:
|
||||
by: defaults # Order pages by "default", "alpha" or "date"
|
||||
by: default # Order pages by "default", "alpha" or "date"
|
||||
dir: asc # Default ordering direction, "asc" or "desc"
|
||||
list:
|
||||
count: 20 # Default item count per page
|
||||
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: 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'
|
||||
types: 'txt|xml|html|json|rss|atom' # Pipe separated list of valid page types
|
||||
expires: 0 # Page expires time in seconds (604800 seconds = 7 days)
|
||||
last_modified: false # Set the last modified date header based on file modifcation timestamp
|
||||
etag: false # Set the etag header tag
|
||||
|
||||
cache:
|
||||
enabled: true # Set to true to enable caching
|
||||
check:
|
||||
method: file # Method to check for updates in pages: file|folder|none
|
||||
driver: auto # One of: auto|file|apc|xcache|memcache|memcached|wincache
|
||||
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: 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
|
||||
enable_asset_timestamp: false # Enable asset timetsamps
|
||||
collections:
|
||||
jquery: system://assets/jquery/jquery-2.1.3.min.js
|
||||
|
||||
errors:
|
||||
display: true # Display full backtrace-style error page
|
||||
log: true # Log errors to /logs folder
|
||||
|
||||
debugger:
|
||||
enabled: false # Enable Grav debugger and following settings
|
||||
mode: detect # Mode tracy Debugger should be set to when enabled: detect|development|production
|
||||
strict: false # Throw fatal error also on PHP warnings and notices
|
||||
max_depth: 10 # How many nested levels to display for objects or arrays
|
||||
log:
|
||||
enabled: true # Enable logging
|
||||
timing: false # Enable timing logging
|
||||
twig: true # Enable debugging of Twig templates
|
||||
shutdown:
|
||||
close_connection: true # Close the connection before calling onShutdown(). false for debugging
|
||||
|
||||
images:
|
||||
default_image_quality: 85 # Default image quality to use when resampling images (85%)
|
||||
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
|
||||
|
||||
media:
|
||||
enable_media_timestamp: false # Enable media timetsamps
|
||||
upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
|
||||
|
||||
security:
|
||||
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK
|
||||
|
||||
@@ -2,29 +2,31 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '0.9.1');
|
||||
define('GRAV_VERSION', '0.9.29');
|
||||
define('DS', '/');
|
||||
|
||||
// Directories and Paths
|
||||
if (!defined('GRAV_ROOT')) {
|
||||
define('GRAV_ROOT', getcwd());
|
||||
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, DS, getcwd()));
|
||||
}
|
||||
define('ROOT_DIR', GRAV_ROOT . '/');
|
||||
define('USER_PATH', 'user/');
|
||||
define('USER_DIR', ROOT_DIR . USER_PATH);
|
||||
define('SYSTEM_DIR', ROOT_DIR .'system/');
|
||||
define('ASSETS_DIR', ROOT_DIR . 'assets/');
|
||||
define('CACHE_DIR', ROOT_DIR .'cache/');
|
||||
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/');
|
||||
|
||||
// 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');
|
||||
@@ -38,6 +40,3 @@ define('RAW_CONTENT', 1);
|
||||
define('TWIG_CONTENT', 2);
|
||||
define('TWIG_CONTENT_LIST', 3);
|
||||
define('TWIG_TEMPLATES', 4);
|
||||
|
||||
// Misc Defines
|
||||
define('SUMMARY_DELIMITER', '===');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
121
system/src/Grav/Common/Backup/ZipBackup.php
Normal file
121
system/src/Grav/Common/Backup/ZipBackup.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace Grav\Common\Backup;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
/**
|
||||
* The ZipBackup class lets you create simple zip-backups of a grav site
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipBackup
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected static $ignorePaths = [
|
||||
'backup',
|
||||
'cache',
|
||||
'images',
|
||||
'logs'
|
||||
];
|
||||
|
||||
protected static $ignoreFolders = [
|
||||
'.git'
|
||||
];
|
||||
|
||||
public static function backup($destination = null, callable $messager = null)
|
||||
{
|
||||
if (!$destination) {
|
||||
$destination = self::getGrav()['locator']->findResource('backup://', true);
|
||||
|
||||
if (!$destination)
|
||||
throw new \RuntimeException('The backup folder is missing.');
|
||||
|
||||
Folder::mkdir($destination);
|
||||
}
|
||||
|
||||
$name = self::getGrav()['config']->get('site.title', basename(GRAV_ROOT));
|
||||
|
||||
if (is_dir($destination)) {
|
||||
$date = date('YmdHis', time());
|
||||
$filename = $name . '-' . $date . '.zip';
|
||||
$destination = rtrim($destination, DS) . DS . $filename;
|
||||
}
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => 'Creating new Backup "' . $destination . '"'
|
||||
]);
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => ''
|
||||
]);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip->open($destination, \ZipArchive::CREATE);
|
||||
|
||||
static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'progress',
|
||||
'percentage' => false,
|
||||
'complete' => true
|
||||
]);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => ''
|
||||
]);
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => 'Saving and compressing archive...'
|
||||
]);
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $folder
|
||||
* @param $zipFile
|
||||
* @param $exclusiveLength
|
||||
* @param $messager
|
||||
*/
|
||||
private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, callable $messager = null)
|
||||
{
|
||||
$handle = opendir($folder);
|
||||
while (false !== $f = readdir($handle)) {
|
||||
if ($f != '.' && $f != '..') {
|
||||
$filePath = "$folder/$f";
|
||||
// Remove prefix from file path before add to zip.
|
||||
$localPath = substr($filePath, $exclusiveLength);
|
||||
|
||||
if (in_array($f, static::$ignoreFolders) || in_array($localPath, static::$ignorePaths)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_file($filePath)) {
|
||||
$zipFile->addFile($filePath, $localPath);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'progress',
|
||||
'percentage' => false,
|
||||
'complete' => false
|
||||
]);
|
||||
} elseif (is_dir($filePath)) {
|
||||
// Add sub-directory.
|
||||
$zipFile->addEmptyDir($localPath);
|
||||
static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,18 @@ namespace Grav\Common;
|
||||
/**
|
||||
* Simple wrapper for the very simple parse_user_agent() function
|
||||
*/
|
||||
class Browser {
|
||||
class Browser
|
||||
{
|
||||
|
||||
protected $useragent;
|
||||
protected $useragent = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->useragent = parse_user_agent();
|
||||
try {
|
||||
$this->useragent = parse_user_agent();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)");
|
||||
}
|
||||
}
|
||||
|
||||
public function getBrowser()
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
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.
|
||||
@@ -24,6 +26,11 @@ class Cache extends Getters
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
protected $lifetime;
|
||||
protected $now;
|
||||
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var DoctrineCache
|
||||
*/
|
||||
@@ -34,6 +41,35 @@ 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
|
||||
*
|
||||
@@ -54,6 +90,9 @@ class Cache extends Getters
|
||||
{
|
||||
/** @var Config $config */
|
||||
$this->config = $grav['config'];
|
||||
$this->now = time();
|
||||
|
||||
$this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $grav['uri'];
|
||||
@@ -63,7 +102,7 @@ class Cache extends Getters
|
||||
$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) . $this->config->key . GRAV_VERSION), 2, 8);
|
||||
$this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
|
||||
|
||||
$this->driver = $this->getCacheDriver();
|
||||
|
||||
@@ -90,8 +129,6 @@ class Cache extends Getters
|
||||
$driver_name = 'wincache';
|
||||
} elseif (extension_loaded('xcache')) {
|
||||
$driver_name = 'xcache';
|
||||
} elseif (extension_loaded('memcache')) {
|
||||
$driver_name = 'memcache';
|
||||
}
|
||||
} else {
|
||||
$driver_name = $setting;
|
||||
@@ -111,15 +148,15 @@ 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;
|
||||
}
|
||||
|
||||
@@ -151,6 +188,9 @@ class Cache extends Getters
|
||||
public function save($id, $data, $lifetime = null)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
if ($lifetime === null) {
|
||||
$lifetime = $this->getLifetime();
|
||||
}
|
||||
$this->driver->save($id, $data, $lifetime);
|
||||
}
|
||||
}
|
||||
@@ -162,4 +202,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;
|
||||
}
|
||||
}
|
||||
|
||||
55
system/src/Grav/Common/Composer.php
Normal file
55
system/src/Grav/Common/Composer.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* Offers composer helper methods.
|
||||
*
|
||||
* @author eschmar
|
||||
* @license MIT
|
||||
*/
|
||||
class Composer
|
||||
{
|
||||
/** @const Default composer location */
|
||||
const DEFAULT_PATH = "bin/composer.phar";
|
||||
|
||||
/**
|
||||
* Returns the location of composer.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getComposerLocation()
|
||||
{
|
||||
if (!function_exists('shell_exec') || strtolower(substr(PHP_OS, 0, 3)) === 'win') {
|
||||
return self::DEFAULT_PATH;
|
||||
}
|
||||
|
||||
// check for global composer install
|
||||
$path = trim(shell_exec("command -v composer"));
|
||||
|
||||
// fall back to grav bundled composer
|
||||
if (!$path || !preg_match('/(composer|composer\.phar)$/', $path)) {
|
||||
$path = self::DEFAULT_PATH;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public static function getComposerExecutor()
|
||||
{
|
||||
$executor = PHP_BINARY . ' ';
|
||||
$composer = static::getComposerLocation();
|
||||
|
||||
if ($composer !== static::DEFAULT_PATH && is_executable($composer)) {
|
||||
$file = fopen($composer, 'r');
|
||||
$firstLine = fgets($file);
|
||||
fclose($file);
|
||||
|
||||
if (!preg_match('/^#!.+php/i', $firstLine)) {
|
||||
$executor = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $executor . $composer;
|
||||
}
|
||||
}
|
||||
@@ -1,259 +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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration.
|
||||
*
|
||||
* @param Grav $grav
|
||||
* @return \Grav\Common\Config
|
||||
*/
|
||||
public static function instance(Grav $grav)
|
||||
{
|
||||
$filename = $grav['config_path'];
|
||||
|
||||
// Load cached version if available..
|
||||
if (file_exists($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'] = $grav['uri']->rootUrl(true);
|
||||
}
|
||||
|
||||
if (empty($instance->items['system']['base_url_relative'])) {
|
||||
$instance->items['system']['base_url_relative'] = $grav['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);
|
||||
}
|
||||
}
|
||||
207
system/src/Grav/Common/Config/Blueprints.php
Normal file
207
system/src/Grav/Common/Config/Blueprints.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
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];
|
||||
}
|
||||
}
|
||||
383
system/src/Grav/Common/Config/Config.php
Normal file
383
system/src/Grav/Common/Config/Config.php
Normal file
@@ -0,0 +1,383 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Data\Data;
|
||||
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']
|
||||
]
|
||||
],
|
||||
'backup' => [
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
'' => ['backup']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
protected $setup = [];
|
||||
|
||||
protected $blueprintFiles = [];
|
||||
protected $configFiles = [];
|
||||
protected $checksum;
|
||||
protected $timestamp;
|
||||
|
||||
protected $configLookup;
|
||||
protected $blueprintLookup;
|
||||
protected $pluginLookup;
|
||||
|
||||
protected $finder;
|
||||
protected $environment;
|
||||
protected $messages = [];
|
||||
|
||||
public function __construct(array $setup = 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;
|
||||
|
||||
// Make sure that
|
||||
if (!isset($setup['streams']['schemes'])) {
|
||||
$setup['streams']['schemes'] = [];
|
||||
}
|
||||
$setup['streams']['schemes'] += $this->streams;
|
||||
|
||||
$setup = $this->autoDetectEnvironmentConfig($setup);
|
||||
$this->messages[] = $setup['streams']['schemes']['config']['prefixes'][''];
|
||||
|
||||
$this->setup = $setup;
|
||||
parent::__construct($setup);
|
||||
|
||||
$this->check();
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->checksum();
|
||||
}
|
||||
|
||||
public function reload()
|
||||
{
|
||||
$this->items = $this->setup;
|
||||
$this->check();
|
||||
$this->init();
|
||||
$this->debug();
|
||||
|
||||
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);
|
||||
}
|
||||
$this->messages = [];
|
||||
}
|
||||
|
||||
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://');
|
||||
|
||||
$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)
|
||||
{
|
||||
$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;
|
||||
$class = get_class($this);
|
||||
$checksum = $this->checksum();
|
||||
|
||||
if (
|
||||
!is_array($cache)
|
||||
|| !isset($cache['checksum'])
|
||||
|| !isset($cache['@class'])
|
||||
|| $cache['@class'] != $class
|
||||
) {
|
||||
$this->messages[] = 'No cached configuration, compiling new configuration..';
|
||||
} else if ($cache['checksum'] !== $checksum) {
|
||||
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
|
||||
} else {
|
||||
$this->messages[] = 'Configuration checksum matches, using cached version.';
|
||||
|
||||
$this->items = $cache['data'];
|
||||
return;
|
||||
}
|
||||
|
||||
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
|
||||
|
||||
// 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' => $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,7 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
|
||||
/**
|
||||
* Blueprint handles the inside logic of blueprints.
|
||||
@@ -11,86 +11,39 @@ use \Symfony\Component\Yaml\Yaml;
|
||||
*/
|
||||
class Blueprint
|
||||
{
|
||||
use Export, DataMutatorTrait;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
* Set filter for inherited properties.
|
||||
*
|
||||
* @example $value = $data->get('this.is.my.nested.variable');
|
||||
*
|
||||
* @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.
|
||||
* @param array $filter List of field names to be inherited.
|
||||
*/
|
||||
public function get($name, $default = null, $separator = '.')
|
||||
public function setFilter(array $filter)
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = $this->blueprints;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current) && isset($current->{$field})) {
|
||||
$current = $current->{$field};
|
||||
} elseif (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sey value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $data->set('this.is.my.nested.variable', true);
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value New value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
*/
|
||||
public function set($name, $value, $separator = '.')
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = &$this->blueprints;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current)) {
|
||||
// Handle objects.
|
||||
if (!isset($current->{$field})) {
|
||||
$current->{$field} = array();
|
||||
}
|
||||
$current = &$current->{$field};
|
||||
} else {
|
||||
// Handle arrays and scalars.
|
||||
if (!is_array($current)) {
|
||||
$current = array($field => array());
|
||||
} elseif (!isset($current[$field])) {
|
||||
$current[$field] = array();
|
||||
}
|
||||
$current = &$current[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$current = $value;
|
||||
$this->filter = array_flip($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,8 +54,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;
|
||||
@@ -167,6 +120,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
|
||||
@@ -187,7 +205,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));
|
||||
}
|
||||
@@ -213,7 +231,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;
|
||||
}
|
||||
|
||||
@@ -281,26 +299,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();
|
||||
@@ -379,8 +403,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();
|
||||
}
|
||||
@@ -391,82 +415,18 @@ class Blueprint
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function checkRequired(array $data, array $fields) {
|
||||
protected function checkRequired(array $data, array $fields)
|
||||
{
|
||||
foreach ($fields as $name => $field) {
|
||||
if (!is_string($field)) {
|
||||
continue;
|
||||
}
|
||||
$field = $this->rules[$field];
|
||||
if (isset($field['validate']['required'])
|
||||
&& $field['validate']['required'] == true
|
||||
&& $field['validate']['required'] === true
|
||||
&& empty($data[$name])) {
|
||||
throw new \RuntimeException("Missing required field: {$field['name']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,17 @@ 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 {
|
||||
// throw new \RuntimeException("Blueprints for '{$type}' cannot be found! {$this->search}{$type}");
|
||||
$blueprints = array();
|
||||
$filename = isset($this->search[$type]) ? $this->search[$type] : '';
|
||||
}
|
||||
|
||||
if ($filename && is_file($filename)) {
|
||||
$file = CompiledYamlFile::instance($filename);
|
||||
$blueprints = $file->content();
|
||||
} else {
|
||||
$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, DataMutatorTrait;
|
||||
|
||||
protected $gettersVariable = 'items';
|
||||
protected $items;
|
||||
|
||||
@@ -22,7 +26,7 @@ class Data extends Getters implements DataInterface
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @var File\General
|
||||
* @var File
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
@@ -52,67 +56,6 @@ class Data extends Getters implements DataInterface
|
||||
return $this->get($name, $default, $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $data->get('this.is.my.nested.variable');
|
||||
*
|
||||
* @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 get($name, $default = null, $separator = '.')
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = $this->items;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current) && isset($current->{$field})) {
|
||||
$current = $current->{$field};
|
||||
} elseif (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sey value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $data->set('this.is.my.nested.variable', true);
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value New value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
*/
|
||||
public function set($name, $value, $separator = '.')
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = &$this->items;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current)) {
|
||||
// Handle objects.
|
||||
if (!isset($current->{$field})) {
|
||||
$current->{$field} = array();
|
||||
}
|
||||
$current = &$current->{$field};
|
||||
} else {
|
||||
// Handle arrays and scalars.
|
||||
if (!is_array($current)) {
|
||||
$current = array($field => array());
|
||||
} elseif (!isset($current[$field])) {
|
||||
$current[$field] = array();
|
||||
}
|
||||
$current = &$current[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$current = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
@@ -121,17 +64,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 +132,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
|
||||
|
||||
68
system/src/Grav/Common/Data/DataMutatorTrait.php
Normal file
68
system/src/Grav/Common/Data/DataMutatorTrait.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
trait DataMutatorTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $data->get('this.is.my.nested.variable');
|
||||
*
|
||||
* @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 get($name, $default = null, $separator = '.')
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = $this->items;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current) && isset($current->{$field})) {
|
||||
$current = $current->{$field};
|
||||
} elseif (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sey value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $data->set('this.is.my.nested.variable', true);
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value New value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
*/
|
||||
public function set($name, $value, $separator = '.')
|
||||
{
|
||||
$path = explode($separator, $name);
|
||||
$current = &$this->items;
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current)) {
|
||||
// Handle objects.
|
||||
if (!isset($current->{$field})) {
|
||||
$current->{$field} = array();
|
||||
}
|
||||
$current = &$current->{$field};
|
||||
} else {
|
||||
// Handle arrays and scalars.
|
||||
if (!is_array($current)) {
|
||||
$current = array($field => array());
|
||||
} elseif (!isset($current[$field])) {
|
||||
$current[$field] = array();
|
||||
}
|
||||
$current = &$current[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$current = $value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -381,7 +375,7 @@ class Validation
|
||||
* @param array $field Blueprint for the field.
|
||||
* @return bool True if validation succeeded.
|
||||
*/
|
||||
public static function typeDatetime_local($value, array $params, array $field)
|
||||
public static function typeDatetimeLocal($value, array $params, array $field)
|
||||
{
|
||||
return self::typeDatetime($value, $params, $field);
|
||||
}
|
||||
@@ -523,7 +517,7 @@ class Validation
|
||||
|
||||
public static function validateRequired($value, $params)
|
||||
{
|
||||
return (bool) $params != true || !empty($value);
|
||||
return (bool) $params !== true || !empty($value);
|
||||
}
|
||||
|
||||
public static function validatePattern($value, $params)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use \Tracy\Debugger as TracyDebugger;
|
||||
use DebugBar\JavascriptRenderer;
|
||||
use DebugBar\StandardDebugBar;
|
||||
|
||||
/**
|
||||
* Class Debugger
|
||||
@@ -9,65 +10,112 @@ use \Tracy\Debugger as TracyDebugger;
|
||||
*/
|
||||
class Debugger
|
||||
{
|
||||
const PRODUCTION = TracyDebugger::PRODUCTION;
|
||||
const DEVELOPMENT = TracyDebugger::DEVELOPMENT;
|
||||
const DETECT = TracyDebugger::DETECT;
|
||||
protected $grav;
|
||||
protected $debugbar;
|
||||
protected $renderer;
|
||||
protected $enabled;
|
||||
|
||||
public function __construct($mode = self::PRODUCTION)
|
||||
public function __construct()
|
||||
{
|
||||
// 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.
|
||||
TracyDebugger::timer();
|
||||
TracyDebugger::enable($mode, is_dir(LOG_DIR) ? LOG_DIR : null);
|
||||
$this->debugbar = new StandardDebugBar();
|
||||
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$this->grav = Grav::instance();
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
$mode = $config->get('system.debugger.mode');
|
||||
TracyDebugger::$logDirectory = $config->get('system.debugger.log.enabled') ? LOG_DIR : null;
|
||||
TracyDebugger::$maxDepth = $config->get('system.debugger.max_depth');
|
||||
|
||||
// Switch debugger into development mode if configured
|
||||
if ($config->get('system.debugger.enabled')) {
|
||||
if ($config->get('system.debugger.strict')) {
|
||||
TracyDebugger::$strictMode = true;
|
||||
}
|
||||
|
||||
if (function_exists('ini_set')) {
|
||||
ini_set('display_errors', true);
|
||||
}
|
||||
|
||||
if ($mode == strtolower('detect')) {
|
||||
TracyDebugger::$productionMode = self::DETECT;
|
||||
} elseif ($mode == strtolower('production')) {
|
||||
TracyDebugger::$productionMode = self::PRODUCTION;
|
||||
} else {
|
||||
TracyDebugger::$productionMode = self::DEVELOPMENT;
|
||||
}
|
||||
|
||||
|
||||
if ($this->enabled()) {
|
||||
$this->debugbar->addCollector(new \DebugBar\DataCollector\ConfigCollector((array)$this->grav['config']->get('system')));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message.
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
public function log($message)
|
||||
public function enabled($state = null)
|
||||
{
|
||||
if (TracyDebugger::$logDirectory) {
|
||||
TracyDebugger::log(sprintf($message, TracyDebugger::timer() * 1000));
|
||||
if (isset($state)) {
|
||||
$this->enabled = $state;
|
||||
} else {
|
||||
if (!isset($this->enabled)) {
|
||||
$this->enabled = $this->grav['config']->get('system.debugger.enabled');
|
||||
}
|
||||
}
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public static function dump($var)
|
||||
public function addAssets()
|
||||
{
|
||||
TracyDebugger::dump($var);
|
||||
if ($this->enabled()) {
|
||||
$assets = $this->grav['assets'];
|
||||
|
||||
// Add jquery library
|
||||
$assets->add('jquery', 101);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
52
system/src/Grav/Common/Errors/Errors.php
Normal file
52
system/src/Grav/Common/Errors/Errors.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Whoops\Handler\CallbackHandler;
|
||||
use Whoops\Handler\HandlerInterface;
|
||||
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>
|
||||
94
system/src/Grav/Common/Errors/SimplePageHandler.php
Normal file
94
system/src/Grav/Common/Errors/SimplePageHandler.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?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()
|
||||
{
|
||||
$inspector = $this->getInspector();
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $resource
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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 = $this->handle ? flock($this->handle, $lock) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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", $var, $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,79 +12,120 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively find the last modified time under given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFolder($path)
|
||||
{
|
||||
$last_modified = 0;
|
||||
|
||||
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$filterItr = new RecursiveFolderFilterIterator($dirItr);
|
||||
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$dir_modified = $file->getMTime();
|
||||
foreach ($itr as $dir) {
|
||||
$dir_modified = $dir->getMTime();
|
||||
if ($dir_modified > $last_modified) {
|
||||
$last_modified = $dir_modified;
|
||||
}
|
||||
}
|
||||
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find the last modified time under given path by file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFile($path)
|
||||
{
|
||||
// pipe separated list of extensions to search for changes with
|
||||
$extensions = 'md|yaml';
|
||||
$last_modified = 0;
|
||||
|
||||
$dirItr = new \RecursiveDirectoryIterator($path);
|
||||
$filterItr = new GravRecursiveFilterIterator($dirItr);
|
||||
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i');
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($itr as $file) {
|
||||
if (!$file->isDir()) {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
}
|
||||
foreach ($itr as $filepath => $file) {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
@@ -100,20 +141,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)
|
||||
@@ -157,8 +208,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)
|
||||
@@ -188,6 +239,7 @@ abstract class Folder
|
||||
*
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($target)
|
||||
{
|
||||
@@ -204,6 +256,66 @@ 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']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive copy of one directory to another
|
||||
*
|
||||
* @param $src
|
||||
* @param $dest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function rcopy($src, $dest)
|
||||
{
|
||||
|
||||
// If the src is not a directory do a simple file copy
|
||||
if (!is_dir($src)) {
|
||||
copy($src, $dest);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the destination directory does not exist create it
|
||||
if (!is_dir($dest)) {
|
||||
if (!mkdir($dest)) {
|
||||
// If the destination directory could not be created stop processing
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the source directory to read in files
|
||||
$i = new \DirectoryIterator($src);
|
||||
/** @var \DirectoryIterator $f */
|
||||
foreach ($i as $f) {
|
||||
if ($f->isFile()) {
|
||||
copy($f->getRealPath(), "$dest/" . $f->getFilename());
|
||||
} else {
|
||||
if (!$f->isDot() && $f->isDir()) {
|
||||
static::rcopy($f->getRealPath(), "$dest/$f");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,48 +330,24 @@ abstract class Folder
|
||||
return @unlink($folder);
|
||||
}
|
||||
|
||||
// Go through all items in filesystem and recursively remove everything.
|
||||
$files = array_diff(scandir($folder), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
$path = "{$folder}/{$file}";
|
||||
(is_dir($path)) ? self::doDelete($path) : @unlink($path);
|
||||
}
|
||||
|
||||
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
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
}
|
||||
|
||||
/** @var \DirectoryIterator $fileinfo */
|
||||
foreach ($files as $fileinfo) {
|
||||
if ($fileinfo->isDir()) {
|
||||
if (false === rmdir($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (false === unlink($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($folder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
public function accept()
|
||||
{
|
||||
// only accept directories
|
||||
return $this->current()->isDir();
|
||||
}
|
||||
}
|
||||
32
system/src/Grav/Common/GPM/AbstractCollection.php
Normal file
32
system/src/Grav/Common/GPM/AbstractCollection.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
abstract class AbstractCollection extends Iterator {
|
||||
|
||||
use GravTrait;
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
abstract class AbstractPackageCollection extends Iterator {
|
||||
|
||||
use GravTrait;
|
||||
|
||||
protected $type;
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
21
system/src/Grav/Common/GPM/Common/CachedCollection.php
Normal file
21
system/src/Grav/Common/GPM/Common/CachedCollection.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class CachedCollection extends Iterator {
|
||||
|
||||
protected static $cache;
|
||||
|
||||
public function __construct($items)
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[get_called_class().__METHOD__])) {
|
||||
self::$cache[get_called_class().__METHOD__] = $items;
|
||||
}
|
||||
|
||||
foreach (self::$cache[get_called_class().__METHOD__] as $name => $item) {
|
||||
$this->append([$name => $item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
system/src/Grav/Common/GPM/Common/Package.php
Normal file
42
system/src/Grav/Common/GPM/Common/Package.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
class Package {
|
||||
|
||||
protected $data;
|
||||
|
||||
public function __construct(Data $package, $type = null) {
|
||||
$this->data = $package;
|
||||
|
||||
if ($type) {
|
||||
$this->data->set('package_type', $type);
|
||||
}
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
return $this->data->get($key);
|
||||
}
|
||||
|
||||
public function __isset($key) {
|
||||
return isset($this->data->$key);
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
return $this->data->toJson();
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
return $this->data->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
394
system/src/Grav/Common/GPM/GPM.php
Normal file
394
system/src/Grav/Common/GPM/GPM.php
Normal file
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
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;
|
||||
|
||||
protected $install_paths = ['plugins' => 'user/plugins/%name%', 'themes' => 'user/themes/%name%', 'skeletons' => 'user/'];
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$repository = '';
|
||||
// if this is an object, get the search data from the key
|
||||
if (is_object($search)) {
|
||||
$search = (array) $search;
|
||||
$key = key($search);
|
||||
$repository = $search[$key];
|
||||
$search = $key;
|
||||
}
|
||||
|
||||
if ($found = $this->findPackage($search)) {
|
||||
// set override respository if provided
|
||||
if ($repository) {
|
||||
$found->override_repository = $repository;
|
||||
}
|
||||
if (!isset($packages[$found->package_type])) {
|
||||
$packages[$found->package_type] = [];
|
||||
}
|
||||
|
||||
$packages[$found->package_type][$found->slug] = $found;
|
||||
$packages['total']++;
|
||||
} else {
|
||||
// make a best guess at the type based on the repo URL
|
||||
if (Utils::contains($repository, '-theme')) {
|
||||
$type = 'themes';
|
||||
} else {
|
||||
$type = 'plugins';
|
||||
}
|
||||
|
||||
$not_found = new \stdClass();
|
||||
$not_found->name = Inflector::camelize($search);
|
||||
$not_found->slug = $search;
|
||||
$not_found->package_type = $type;
|
||||
$not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]);
|
||||
$not_found->override_repository = $repository;
|
||||
$packages['not_found'][$search] = $not_found;
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
}
|
||||
290
system/src/Grav/Common/GPM/Installer.php
Normal file
290
system/src/Grav/Common/GPM/Installer.php
Normal file
@@ -0,0 +1,290 @@
|
||||
<?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(\ZipArchive $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(\ZipArchive $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 $path 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
|
||||
use Grav\Common\GPM\Local\Package;
|
||||
|
||||
abstract class AbstractPackageCollection extends BaseCollection {
|
||||
|
||||
public function __construct($items)
|
||||
{
|
||||
foreach ($items as $name => $data) {
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
system/src/Grav/Common/GPM/Local/Package.php
Normal file
32
system/src/Grav/Common/GPM/Local/Package.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
|
||||
class Package extends BasePackage
|
||||
{
|
||||
protected $settings;
|
||||
|
||||
public function __construct(Data $package, $package_type = null)
|
||||
{
|
||||
$data = new Data($package->blueprints()->toArray());
|
||||
parent::__construct($data, $package_type);
|
||||
|
||||
$this->settings = $package->toArray();
|
||||
|
||||
$html_description = \Parsedown::instance()->line($this->description);
|
||||
$this->data->set('slug', $this->name);
|
||||
$this->data->set('description_html', $html_description);
|
||||
$this->data->set('description_plain', strip_tags($html_description));
|
||||
$this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->settings['enabled'];
|
||||
}
|
||||
}
|
||||
17
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
17
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GPM\Common\CachedCollection;
|
||||
|
||||
class Packages extends CachedCollection
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$items = [
|
||||
'plugins' => new Plugins(),
|
||||
'themes' => new Themes()
|
||||
];
|
||||
|
||||
parent::__construct($items);
|
||||
}
|
||||
}
|
||||
22
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
22
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
/**
|
||||
* Class Plugins
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'plugins';
|
||||
|
||||
/**
|
||||
* Local Plugins Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(self::getGrav()['plugins']->all());
|
||||
}
|
||||
}
|
||||
22
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
22
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
/**
|
||||
* Class Themes
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Themes extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'themes';
|
||||
|
||||
/**
|
||||
* Local Themes Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(self::getGrav()['themes']->all());
|
||||
}
|
||||
}
|
||||
58
system/src/Grav/Common/GPM/PackageInterface.php
Normal file
58
system/src/Grav/Common/GPM/PackageInterface.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
/**
|
||||
* Interface Package
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function isEnabled();
|
||||
|
||||
/**
|
||||
* @return Data
|
||||
*/
|
||||
public function getData();
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toJson();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
|
||||
use Grav\Common\GPM\Response;
|
||||
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
|
||||
class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* The cached data previously fetched
|
||||
* @var string
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* The lifetime to store the entry in seconds
|
||||
* @var integer
|
||||
*/
|
||||
private $lifetime = 86400;
|
||||
|
||||
protected $repository;
|
||||
|
||||
protected $cache;
|
||||
|
||||
public function __construct($repository = null, $refresh = false, $callback = null)
|
||||
{
|
||||
if ($repository === null) {
|
||||
throw new \RuntimeException("A repository is required to indicate the origin of the remote collection");
|
||||
}
|
||||
|
||||
$cache_dir = self::getGrav()['locator']->findResource('cache://gpm', true, true);
|
||||
$this->cache = new FilesystemCache($cache_dir);
|
||||
|
||||
$this->repository = $repository;
|
||||
$this->raw = $this->cache->fetch(md5($this->repository));
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
foreach (json_decode($this->raw, true) as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
90
system/src/Grav/Common/GPM/Remote/Grav.php
Normal file
90
system/src/Grav/Common/GPM/Remote/Grav.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
|
||||
class Grav extends AbstractPackageCollection
|
||||
{
|
||||
protected $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)
|
||||
{
|
||||
$cache_dir = self::getGrav()['locator']->findResource('cache://gpm', true, true);
|
||||
$this->cache = new FilesystemCache($cache_dir);
|
||||
$this->raw = $this->cache->fetch(md5($this->repository));
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
|
||||
$this->data = json_decode($this->raw, true);
|
||||
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
|
||||
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
|
||||
|
||||
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(), '<');
|
||||
}
|
||||
}
|
||||
12
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
12
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
|
||||
class Package extends BasePackage {
|
||||
public function __construct($package, $package_type = null) {
|
||||
$data = new Data($package);
|
||||
parent::__construct($data, $package_type);
|
||||
}
|
||||
}
|
||||
17
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
17
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\GPM\Common\CachedCollection;
|
||||
|
||||
class Packages extends CachedCollection
|
||||
{
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$items = [
|
||||
'plugins' => new Plugins($refresh, $callback),
|
||||
'themes' => new Themes($refresh, $callback)
|
||||
];
|
||||
|
||||
parent::__construct($items);
|
||||
}
|
||||
}
|
||||
24
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
24
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
/**
|
||||
* Class Plugins
|
||||
* @package Grav\Common\GPM\Remote
|
||||
*/
|
||||
class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'plugins';
|
||||
|
||||
protected $repository = 'http://getgrav.org/downloads/plugins.json';
|
||||
|
||||
/**
|
||||
* Local Plugins Constructor
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository, $refresh, $callback);
|
||||
}
|
||||
}
|
||||
24
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
24
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
/**
|
||||
* Class Themes
|
||||
* @package Grav\Common\GPM\Remote
|
||||
*/
|
||||
class Themes extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'themes';
|
||||
|
||||
protected $repository = 'http://getgrav.org/downloads/themes.json';
|
||||
|
||||
/**
|
||||
* Local Themes Constructor
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository, $refresh, $callback);
|
||||
}
|
||||
}
|
||||
219
system/src/Grav/Common/GPM/Response.php
Normal file
219
system/src/Grav/Common/GPM/Response.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
83
system/src/Grav/Common/GPM/Upgrader.php
Normal file
83
system/src/Grav/Common/GPM/Upgrader.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
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(), "<");
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
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 Grav\Component\DI\Container;
|
||||
use Grav\Component\EventDispatcher\Event;
|
||||
use Grav\Component\EventDispatcher\EventDispatcher;
|
||||
use RocketTheme\Toolbox\DI\Container;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
|
||||
/**
|
||||
* Grav
|
||||
@@ -12,9 +17,7 @@ use Grav\Component\EventDispatcher\EventDispatcher;
|
||||
* @author Andy Miller
|
||||
* @link http://www.rockettheme.com
|
||||
* @license http://opensource.org/licenses/MIT
|
||||
* @version 0.8.0
|
||||
*
|
||||
* Originally based on Pico by Gilbert Pellegrom - http://pico.dev7studios.com
|
||||
* Influenced by Pico, Stacey, Kirby, PieCrust and other great platforms...
|
||||
*/
|
||||
class Grav extends Container
|
||||
@@ -22,7 +25,7 @@ class Grav extends Container
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $output;
|
||||
public $output;
|
||||
|
||||
/**
|
||||
* @var static
|
||||
@@ -50,24 +53,31 @@ class Grav extends Container
|
||||
{
|
||||
$container = new static($values);
|
||||
|
||||
$container['config_path'] = CACHE_DIR . 'config.php';
|
||||
|
||||
$container['grav'] = $container;
|
||||
|
||||
$container['events'] = function ($c) {
|
||||
return new EventDispatcher;
|
||||
};
|
||||
$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['config'] = function ($c) {
|
||||
return Config::instance($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);
|
||||
return new Plugins();
|
||||
};
|
||||
$container['themes'] = function ($c) {
|
||||
return new Themes($c);
|
||||
@@ -85,9 +95,42 @@ class Grav extends Container
|
||||
return new Assets();
|
||||
};
|
||||
$container['page'] = function ($c) {
|
||||
$page = $c['pages']->dispatch($c['uri']->route());
|
||||
/** @var Pages $pages */
|
||||
$pages = $c['pages'];
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $c['uri'];
|
||||
|
||||
$path = rtrim($uri->path(), '/');
|
||||
$path = $path ?: '/';
|
||||
|
||||
$page = $pages->dispatch($path);
|
||||
|
||||
if (!$page || !$page->routable()) {
|
||||
$path_parts = pathinfo($path);
|
||||
$page = $c['pages']->dispatch($path_parts['dirname'], true);
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
|
||||
$parsed_url = parse_url(urldecode($uri->basename()));
|
||||
|
||||
$media_file = $parsed_url['path'];
|
||||
|
||||
// if this is a media object, try actions first
|
||||
if (isset($media[$media_file])) {
|
||||
$medium = $media[$media_file];
|
||||
foreach ($uri->query(null, true) as $action => $params) {
|
||||
if (in_array($action, ImageMedium::$magic_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
}
|
||||
Utils::download($medium->path(), false);
|
||||
} else {
|
||||
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// If no page found, fire event
|
||||
$event = $c->fireEvent('onPageNotFound');
|
||||
|
||||
if (isset($event->page)) {
|
||||
@@ -105,48 +148,98 @@ class Grav extends Container
|
||||
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()
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
|
||||
// Initialize configuration.
|
||||
$debugger->startTimer('_config', 'Configuration');
|
||||
$this['config']->init();
|
||||
$this['uri']->init();
|
||||
$this['errors']->resetHandlers();
|
||||
$debugger->init();
|
||||
$this['config']->debug();
|
||||
$debugger->stopTimer('_config');
|
||||
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_start('ob_gzhandler');
|
||||
}
|
||||
|
||||
// Initialize stream wrappers.
|
||||
$this['locator'];
|
||||
// Initialize the timezone
|
||||
if ($this['config']->get('system.timezone')) {
|
||||
date_default_timezone_set($this['config']->get('system.timezone'));
|
||||
}
|
||||
|
||||
$debugger->startTimer('streams', 'Streams');
|
||||
$this['streams'];
|
||||
$debugger->stopTimer('streams');
|
||||
|
||||
$debugger->startTimer('plugins', 'Plugins');
|
||||
$this['plugins']->init();
|
||||
|
||||
$this->fireEvent('onPluginsInitialized');
|
||||
$debugger->stopTimer('plugins');
|
||||
|
||||
$debugger->startTimer('themes', 'Themes');
|
||||
$this['themes']->init();
|
||||
$this->fireEvent('onThemeInitialized');
|
||||
$debugger->stopTimer('themes');
|
||||
|
||||
$task = $this['task'];
|
||||
if ($task) {
|
||||
$this->fireEvent('onTask.' . $task);
|
||||
}
|
||||
|
||||
$this['assets']->init();
|
||||
|
||||
$this->fireEvent('onAssetsInitialized');
|
||||
|
||||
$debugger->startTimer('twig', 'Twig');
|
||||
$this['twig']->init();
|
||||
$this['pages']->init();
|
||||
$debugger->stopTimer('twig');
|
||||
|
||||
$debugger->startTimer('pages', 'Pages');
|
||||
$this['pages']->init();
|
||||
$this->fireEvent('onPagesInitialized');
|
||||
$debugger->stopTimer('pages');
|
||||
|
||||
$this->fireEvent('onPageInitialized');
|
||||
|
||||
// Process whole page as required
|
||||
$this->output = $this['output'];
|
||||
$debugger->addAssets();
|
||||
|
||||
// Process whole page as required
|
||||
$debugger->startTimer('render', 'Render');
|
||||
$this->output = $this['output'];
|
||||
$this->fireEvent('onOutputGenerated');
|
||||
$debugger->stopTimer('render');
|
||||
|
||||
// Set the header type
|
||||
$this->header();
|
||||
|
||||
echo $this->output;
|
||||
|
||||
ob_end_flush();
|
||||
flush();
|
||||
$debugger->render();
|
||||
|
||||
$this->fireEvent('onOutputRendered');
|
||||
|
||||
register_shutdown_function([$this, 'shutdown']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +252,18 @@ class Grav extends Container
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
header("Location: " . rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
|
||||
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
if ($this['uri']->isExternal($route)) {
|
||||
$url = $route;
|
||||
} else {
|
||||
$url = rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/');
|
||||
}
|
||||
|
||||
header("Location: {$url}", true, $code);
|
||||
exit();
|
||||
}
|
||||
|
||||
@@ -191,9 +295,42 @@ class Grav extends Container
|
||||
*/
|
||||
public function header()
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
header('Content-type: ' . $this->mime($uri->extension()));
|
||||
$extension = $this['uri']->extension();
|
||||
|
||||
/** @var Page $page */
|
||||
$page = $this['page'];
|
||||
|
||||
header('Content-type: ' . $this->mime($extension));
|
||||
|
||||
// Calculate Expires Headers if set to > 0
|
||||
$expires = $page->expires();
|
||||
|
||||
if ($expires > 0) {
|
||||
$expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT';
|
||||
header('Cache-Control: max-age=' . $expires_date);
|
||||
header('Expires: '. $expires_date);
|
||||
}
|
||||
|
||||
// Set the last modified time
|
||||
if ($page->lastModified()) {
|
||||
$last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT';
|
||||
header('Last-Modified: ' . $last_modified_date);
|
||||
}
|
||||
|
||||
// Calculate a Hash based on the raw file
|
||||
if ($page->eTag()) {
|
||||
header('ETag: ' . md5($page->raw() . $page->modified()));
|
||||
}
|
||||
|
||||
// Set debugger data in headers
|
||||
if (!($extension === null || $extension == 'html')) {
|
||||
$this['debugger']->enabled(false);
|
||||
}
|
||||
|
||||
// Set HTTP response code
|
||||
if (isset($this['page']->header()->http_response_code)) {
|
||||
http_response_code($this['page']->header()->http_response_code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,4 +346,45 @@ class Grav extends Container
|
||||
$events = $this['events'];
|
||||
return $events->dispatch($eventName, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the final content length for the page and flush the buffer
|
||||
*
|
||||
*/
|
||||
public function shutdown()
|
||||
{
|
||||
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
|
||||
//stop user abort
|
||||
if (function_exists('ignore_user_abort')) {
|
||||
@ignore_user_abort(true);
|
||||
}
|
||||
|
||||
// close the session
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
// flush buffer if gzip buffer was started
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_end_flush(); // gzhandler buffer
|
||||
}
|
||||
|
||||
// get lengh and close the connection
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
header("Connection: close");
|
||||
|
||||
// flush the regular buffer
|
||||
ob_end_flush();
|
||||
@ob_flush();
|
||||
flush();
|
||||
|
||||
// fix for fastcgi close connection issue
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
@fastcgi_finish_request();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->fireEvent('onShutdown');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ trait GravTrait
|
||||
/**
|
||||
* @return Grav
|
||||
*/
|
||||
public function getGrav()
|
||||
public static function getGrav()
|
||||
{
|
||||
if (!self::$grav) {
|
||||
self::$grav = Grav::instance();
|
||||
}
|
||||
return self::$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.
|
||||
*/
|
||||
@@ -135,10 +84,43 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
*/
|
||||
public function nth($key)
|
||||
{
|
||||
$items = array_values($this->items);
|
||||
$items = array_keys($this->items);
|
||||
return (isset($items[$key])) ? $this->offsetGet($items[$key]) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first item
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
$items = array_keys($this->items);
|
||||
return $this->offsetGet(array_shift($items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last item
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function last()
|
||||
{
|
||||
$items = array_keys($this->items);
|
||||
return $this->offsetGet(array_pop($items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the Iterator
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
$this->items = array_reverse($this->items);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $needle Searched value.
|
||||
* @return string|bool Key if found, otherwise false.
|
||||
@@ -164,7 +146,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];
|
||||
}
|
||||
|
||||
@@ -216,179 +198,22 @@ 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.
|
||||
* Filter elements from the list
|
||||
* @param callable|null $callback A function the receives ($value, $key) and must return a boolean to indicate filter status
|
||||
* @return $this
|
||||
*/
|
||||
public function toArray()
|
||||
public function filter(callable $callback = null)
|
||||
{
|
||||
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);
|
||||
foreach ($this->items as $key => $value) {
|
||||
if (
|
||||
($callback && !call_user_func($callback, $value, $key)) ||
|
||||
(!$callback && !(bool) $value)
|
||||
) {
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
class Markdown extends \Parsedown
|
||||
{
|
||||
use MarkdownGravLinkTrait;
|
||||
|
||||
function __construct($page)
|
||||
{
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user