mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
557 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb93dacba0 | ||
|
|
d350bd31cb | ||
|
|
89b9b4e9b7 | ||
|
|
9549e5aa4c | ||
|
|
1517d0e40b | ||
|
|
f13593aac7 | ||
|
|
b8023b2444 | ||
|
|
bb49098a05 | ||
|
|
c368fbcda9 | ||
|
|
1c1bf86e9a | ||
|
|
5f1b190ba9 | ||
|
|
0860f53d68 | ||
|
|
1ffd1cb6e7 | ||
|
|
030d230312 | ||
|
|
974f9d52a4 | ||
|
|
1a238ea1b1 | ||
|
|
a049441048 | ||
|
|
20e771f121 | ||
|
|
cff4e225e6 | ||
|
|
2c69f539ae | ||
|
|
548081471c | ||
|
|
3688cfa397 | ||
|
|
20b2856dee | ||
|
|
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 | ||
|
|
d983a908e4 | ||
|
|
260f200d6f | ||
|
|
2d95b3bb09 |
0
.gitignore
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
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)/(.*) 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
|
||||
|
||||
358
CHANGELOG.md
358
CHANGELOG.md
@@ -1,4 +1,352 @@
|
||||
# v0.9.3 beta
|
||||
# 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)
|
||||
@@ -26,7 +374,7 @@
|
||||
* Switched debugger to PRODUCTION mode by default
|
||||
* Various fixes in URI class for increased reliability
|
||||
|
||||
# v0.9.2 beta
|
||||
# v0.9.2
|
||||
## 09/15/2014
|
||||
|
||||
1. [](#new)
|
||||
@@ -59,7 +407,7 @@
|
||||
* Fix broken password validation
|
||||
* Back to proper PSR-4 Autoloader
|
||||
|
||||
# v0.9.1 beta
|
||||
# v0.9.1
|
||||
## 09/02/2014
|
||||
|
||||
1. [](#new)
|
||||
@@ -80,7 +428,7 @@
|
||||
* Fixed template inheritance
|
||||
* Moved Browser class to proper location
|
||||
|
||||
# v0.9.0 beta
|
||||
# v0.9.0
|
||||
## 08/25/2014
|
||||
|
||||
1. [](#new)
|
||||
@@ -108,7 +456,7 @@
|
||||
* Various minor bug fixes
|
||||
|
||||
|
||||
# v0.8.0 beta
|
||||
# v0.8.0
|
||||
## 08/13/2014
|
||||
|
||||
1. [](#new)
|
||||
|
||||
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. 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.
|
||||
[](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.
|
||||
|
||||
Binary file not shown.
3
bin/gpm
3
bin/gpm
@@ -28,14 +28,17 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
|
||||
$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(),
|
||||
));
|
||||
|
||||
@@ -8,29 +8,21 @@
|
||||
"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",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~2.6",
|
||||
"symfony/console": "~2.6",
|
||||
"symfony/event-dispatcher": "~2.6",
|
||||
"doctrine/cache": "~1.3",
|
||||
"tracy/tracy": "2.3.*@dev",
|
||||
"gregwar/image": "~2.0",
|
||||
"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",
|
||||
"rockettheme/toolbox": "dev-develop"
|
||||
"rockettheme/toolbox": "1.0.*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/rockettheme/toolbox"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/rhukster/minify"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\": "system/src/Grav"
|
||||
|
||||
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
|
||||
18
index.php
18
index.php
@@ -2,35 +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>bin/grav install</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(
|
||||
array(
|
||||
'loader' => $loader,
|
||||
'debugger' => new Debugger(Debugger::PRODUCTION)
|
||||
'loader' => $loader
|
||||
)
|
||||
);
|
||||
|
||||
// Process the page
|
||||
try {
|
||||
$grav['debugger']->init();
|
||||
$grav->process();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$grav->fireEvent('onFatalException');
|
||||
throw $e;
|
||||
|
||||
35
nginx.conf
Executable file → Normal file
35
nginx.conf
Executable file → Normal file
@@ -25,28 +25,32 @@ http {
|
||||
index index.php;
|
||||
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 /system {
|
||||
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
|
||||
}
|
||||
|
||||
location /vendor {
|
||||
rewrite ^/vendor/(.*)$ /error redirect;
|
||||
}
|
||||
location /vendor {
|
||||
rewrite ^/vendor/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
|
||||
}
|
||||
|
||||
# Remember to change 127.0.0.1:9000 to the Ip/port
|
||||
# you configured php-cgi.exe to run from
|
||||
@@ -60,7 +64,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;
|
||||
}
|
||||
@@ -9,7 +9,17 @@ form:
|
||||
|
||||
fields:
|
||||
username:
|
||||
type: unset
|
||||
type: text
|
||||
size: large
|
||||
label: Username
|
||||
readonly: true
|
||||
|
||||
email:
|
||||
type: text
|
||||
size: large
|
||||
label: Email
|
||||
validate:
|
||||
required: true
|
||||
|
||||
password:
|
||||
type: password
|
||||
@@ -18,12 +28,7 @@ form:
|
||||
validate:
|
||||
required: true
|
||||
|
||||
email:
|
||||
type: text
|
||||
size: large
|
||||
label: Email
|
||||
validate:
|
||||
required: true
|
||||
|
||||
|
||||
fullname:
|
||||
type: text
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,9 +6,13 @@ taxonomies: [category,tag] # Arbitrary list of taxonomy types
|
||||
blog:
|
||||
route: '/blog' # Route to blog
|
||||
metadata:
|
||||
description: 'My Grav Site' # Site description
|
||||
description: 'My Grav Site' # Site description
|
||||
summary:
|
||||
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
|
||||
/new/*: '/blog/*' # Wildcard any /new/my-page URL to /blog/my-page Route
|
||||
|
||||
@@ -8,6 +8,7 @@ schemes:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://images
|
||||
- system://images
|
||||
|
||||
page:
|
||||
type: ReadOnlyStream
|
||||
@@ -18,8 +19,3 @@ schemes:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://accounts
|
||||
|
||||
data:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user://data
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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"
|
||||
dir: asc # Default ordering direction, "asc" or "desc"
|
||||
@@ -12,12 +15,23 @@ pages:
|
||||
dateformat:
|
||||
short: 'jS M Y' # Short date format
|
||||
long: 'F jS \a\t g:ia' # Long date format
|
||||
publish_dates: true # automatically publish/unpublish based on dates
|
||||
process:
|
||||
markdown: true # Process Markdown
|
||||
twig: false # Process Twig
|
||||
events:
|
||||
page: 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)
|
||||
|
||||
cache:
|
||||
enabled: true # Set to true to enable caching
|
||||
@@ -25,12 +39,17 @@ cache:
|
||||
method: file # Method to check for updates in pages: file|folder|none
|
||||
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
|
||||
prefix: 'g' # Cache prefix string (prevents cache conflicts)
|
||||
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
|
||||
gzip: false # GZip compress the page output
|
||||
|
||||
|
||||
twig:
|
||||
cache: 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
|
||||
@@ -39,13 +58,26 @@ assets: # Configuration for Assets Manager (JS, C
|
||||
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
|
||||
twig: true # Enable debugging of Twig templates
|
||||
shutdown:
|
||||
close_connection: true # Close the connection before calling onShutdown(). disable for debugging
|
||||
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
|
||||
|
||||
security:
|
||||
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '0.9.3');
|
||||
define('GRAV_VERSION', '0.9.25');
|
||||
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/');
|
||||
@@ -17,14 +17,16 @@ define('ASSETS_DIR', ROOT_DIR . 'assets/');
|
||||
define('CACHE_DIR', ROOT_DIR . 'cache/');
|
||||
define('IMAGES_DIR', ROOT_DIR . 'images/');
|
||||
define('LOG_DIR', ROOT_DIR .'logs/');
|
||||
define('VENDOR_DIR', ROOT_DIR .'vendor/');
|
||||
define('LIB_DIR', SYSTEM_DIR .'src/');
|
||||
define('ACCOUNTS_DIR', USER_DIR .'accounts/');
|
||||
define('DATA_DIR', USER_DIR .'data/');
|
||||
define('PAGES_DIR', USER_DIR .'pages/');
|
||||
|
||||
// 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
@@ -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()
|
||||
|
||||
@@ -3,6 +3,7 @@ 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.
|
||||
@@ -25,6 +26,11 @@ class Cache extends Getters
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
protected $lifetime;
|
||||
protected $now;
|
||||
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var DoctrineCache
|
||||
*/
|
||||
@@ -37,6 +43,33 @@ class Cache extends Getters
|
||||
|
||||
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
|
||||
*
|
||||
@@ -57,6 +90,7 @@ class Cache extends Getters
|
||||
{
|
||||
/** @var Config $config */
|
||||
$this->config = $grav['config'];
|
||||
$this->now = time();
|
||||
|
||||
$this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
|
||||
|
||||
@@ -154,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);
|
||||
}
|
||||
}
|
||||
@@ -165,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RocketTheme\Toolbox\Blueprints\Blueprints as BaseBlueprints;
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
|
||||
@@ -3,10 +3,7 @@ namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RocketTheme\Toolbox\Blueprints\Blueprints;
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
@@ -21,6 +18,12 @@ class Config extends Data
|
||||
{
|
||||
protected $grav;
|
||||
protected $streams = [
|
||||
'system' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['system'],
|
||||
]
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
@@ -60,7 +63,8 @@ class Config extends Data
|
||||
'cache' => [
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
'' => ['cache']
|
||||
'' => ['cache'],
|
||||
'images' => ['images']
|
||||
]
|
||||
],
|
||||
'log' => [
|
||||
@@ -80,10 +84,16 @@ class Config extends Data
|
||||
protected $blueprintLookup;
|
||||
protected $pluginLookup;
|
||||
|
||||
protected $finder;
|
||||
protected $environment;
|
||||
protected $messages = [];
|
||||
|
||||
public function __construct(array $items = array(), Grav $grav = null)
|
||||
public function __construct(array $items = array(), Grav $grav = null, $environment = null)
|
||||
{
|
||||
$this->grav = $grav ?: Grav::instance();
|
||||
$this->finder = new ConfigFinder;
|
||||
$this->environment = $environment ?: 'localhost';
|
||||
$this->messages[] = 'Environment Name: ' . $this->environment;
|
||||
|
||||
if (isset($items['@class'])) {
|
||||
if ($items['@class'] != get_class($this)) {
|
||||
@@ -91,7 +101,7 @@ class Config extends Data
|
||||
}
|
||||
// Loading pre-compiled configuration.
|
||||
$this->timestamp = (int) $items['timestamp'];
|
||||
$this->checksum = (string) $items['checksum'];
|
||||
$this->checksum = $items['checksum'];
|
||||
$this->items = (array) $items['data'];
|
||||
} else {
|
||||
// Make sure that
|
||||
@@ -100,6 +110,9 @@ class Config extends Data
|
||||
}
|
||||
$items['streams']['schemes'] += $this->streams;
|
||||
|
||||
$items = $this->autoDetectEnvironmentConfig($items);
|
||||
$this->messages[] = $items['streams']['schemes']['config']['prefixes'][''];
|
||||
|
||||
parent::__construct($items);
|
||||
}
|
||||
$this->check();
|
||||
@@ -107,7 +120,7 @@ class Config extends Data
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->checksum;
|
||||
return $this->checksum();
|
||||
}
|
||||
|
||||
public function reload()
|
||||
@@ -132,6 +145,13 @@ class Config extends Data
|
||||
}
|
||||
}
|
||||
|
||||
public function debug()
|
||||
{
|
||||
foreach ($this->messages as $message) {
|
||||
$this->grav['debugger']->addMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
@@ -141,22 +161,19 @@ class Config extends Data
|
||||
$this->blueprintLookup = $locator->findResources('blueprints://config');
|
||||
$this->pluginLookup = $locator->findResources('plugins://');
|
||||
|
||||
$checksum = $this->checksum();
|
||||
if ($checksum == $this->checksum) {
|
||||
if (!isset($this->checksum)) {
|
||||
$this->messages[] = 'No cached configuration, compiling new configuration..';
|
||||
} elseif ($this->checksum() != $this->checksum) {
|
||||
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
|
||||
} else {
|
||||
$this->messages[] = 'Configuration checksum matches, using cached version.';
|
||||
return;
|
||||
}
|
||||
|
||||
$this->checksum = $checksum;
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
|
||||
// If not set, add manually current base url.
|
||||
$this->def('system.base_url_absolute', $uri->rootUrl(true));
|
||||
$this->def('system.base_url_relative', $uri->rootUrl(false));
|
||||
|
||||
$this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
|
||||
$this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
|
||||
|
||||
$this->initializeLocator($locator);
|
||||
}
|
||||
|
||||
public function checksum()
|
||||
@@ -166,45 +183,60 @@ class Config extends Data
|
||||
$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->detectFile($this->configLookup, 'system') : [];
|
||||
$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->getConfigFiles($this->configLookup, $this->pluginLookup);
|
||||
$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 = [];
|
||||
}
|
||||
$cb = $checkBlueprints ? $this->getBlueprintFiles($this->blueprintLookup, $this->pluginLookup) : [];
|
||||
|
||||
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 .'.php'
|
||||
: CACHE_DIR . 'compiled/blueprints/' . $checksum .'.php';
|
||||
? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php'
|
||||
: CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
|
||||
$file = PhpFile::instance($filename);
|
||||
|
||||
if ($file->exists()) {
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
} else {
|
||||
$cache = null;
|
||||
}
|
||||
|
||||
$blueprintFiles = $this->getBlueprintFiles($blueprints, $plugins);
|
||||
$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)
|
||||
|| empty($cache['checksum'])
|
||||
|| empty($cache['$class'])
|
||||
|| !isset($cache['checksum'])
|
||||
|| !isset($cache['@class'])
|
||||
|| $cache['checksum'] != $checksum
|
||||
|| $cache['@class'] != $class
|
||||
) {
|
||||
@@ -213,8 +245,8 @@ class Config extends Data
|
||||
|
||||
// Load blueprints.
|
||||
$this->blueprints = new Blueprints;
|
||||
foreach ($blueprintFiles as $key => $files) {
|
||||
$this->loadBlueprints($key);
|
||||
foreach ($blueprintFiles as $files) {
|
||||
$this->loadBlueprintFiles($files);
|
||||
}
|
||||
|
||||
$cache = [
|
||||
@@ -226,6 +258,7 @@ class Config extends Data
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -238,23 +271,19 @@ class Config extends Data
|
||||
{
|
||||
$checksum = md5(json_encode($configs));
|
||||
$filename = $filename
|
||||
? CACHE_DIR . 'compiled/config/' . $filename .'.php'
|
||||
: CACHE_DIR . 'compiled/config/' . $checksum .'.php';
|
||||
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
|
||||
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
|
||||
$file = PhpFile::instance($filename);
|
||||
|
||||
if ($file->exists()) {
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
} else {
|
||||
$cache = null;
|
||||
}
|
||||
|
||||
$configFiles = $this->getConfigFiles($configs, $plugins);
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
|
||||
$checksum .= ':'.md5(json_encode($configFiles));
|
||||
$class = get_class($this);
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (
|
||||
!is_array($cache)
|
||||
|| !isset($cache['checksum'])
|
||||
|| !isset($cache['@class'])
|
||||
|| $cache['checksum'] != $checksum
|
||||
|| $cache['@class'] != $class
|
||||
) {
|
||||
@@ -262,18 +291,19 @@ class Config extends Data
|
||||
$file->lock(false);
|
||||
|
||||
// Load configuration.
|
||||
foreach ($configFiles as $key => $files) {
|
||||
$this->loadConfig($key);
|
||||
foreach ($configFiles as $files) {
|
||||
$this->loadConfigFiles($files);
|
||||
}
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'timestamp' => time(),
|
||||
'checksum' => $this->checksum,
|
||||
'checksum' => $this->checksum(),
|
||||
'data' => $this->toArray()
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$this->messages[] = 'Saving compiled configuration.';
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
}
|
||||
@@ -283,16 +313,12 @@ class Config extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* Load global blueprints.
|
||||
* Load blueprints.
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $files
|
||||
* @param array $files
|
||||
*/
|
||||
public function loadBlueprints($key, array $files = null)
|
||||
public function loadBlueprintFiles(array $files)
|
||||
{
|
||||
if (is_null($files)) {
|
||||
$files = $this->blueprintFiles[$key];
|
||||
}
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->blueprints->embed($name, $file->content(), '/');
|
||||
@@ -300,16 +326,12 @@ class Config extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* Load global configuration.
|
||||
* Load configuration.
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $files
|
||||
* @param array $files
|
||||
*/
|
||||
public function loadConfig($key, array $files = null)
|
||||
public function loadConfigFiles(array $files)
|
||||
{
|
||||
if (is_null($files)) {
|
||||
$files = $this->configFiles[$key];
|
||||
}
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->join($name, $file->content(), '/');
|
||||
@@ -317,147 +339,45 @@ class Config extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all blueprint files (including plugins).
|
||||
* Initialize resource locator by using the configuration.
|
||||
*
|
||||
* @param array $blueprints
|
||||
* @param array $plugins
|
||||
* @return array
|
||||
* @param UniformResourceLocator $locator
|
||||
*/
|
||||
protected function getBlueprintFiles(array $blueprints, array $plugins)
|
||||
public function initializeLocator(UniformResourceLocator $locator)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
$locator->reset();
|
||||
|
||||
/**
|
||||
* Get all configuration files.
|
||||
*
|
||||
* @param array $configs
|
||||
* @param array $plugins
|
||||
* @return array
|
||||
*/
|
||||
protected function getConfigFiles(array $configs, array $plugins)
|
||||
{
|
||||
$list = [];
|
||||
foreach (array_reverse($plugins) as $folder) {
|
||||
$list += $this->detectPlugins($folder);
|
||||
}
|
||||
foreach (array_reverse($configs) as $folder) {
|
||||
$list += $this->detectConfig($folder);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
$schemes = (array) $this->get('streams.schemes', []);
|
||||
|
||||
/**
|
||||
* 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 (file_exists($filename)) {
|
||||
$list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->{$location}[$path] = $list;
|
||||
|
||||
return [$path => $list];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects all plugins with a configuration file and returns last modification time.
|
||||
* Get available streams and their types from the configuration.
|
||||
*
|
||||
* @param string $lookup Location to look up from.
|
||||
* @param bool $blueprints
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function detectConfig($lookup = SYSTEM_DIR, $blueprints = false)
|
||||
public function getStreams()
|
||||
{
|
||||
$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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects all instances of the file and returns last modification time.
|
||||
*
|
||||
* @param array $lookups Locations to look up from.
|
||||
* @param string $name
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function detectFile(array $lookups, $name)
|
||||
{
|
||||
$list = [];
|
||||
$filename = "{$name}.yaml";
|
||||
foreach ($lookups as $lookup) {
|
||||
$path = trim(Folder::getRelativePath($lookup), '/');
|
||||
|
||||
if (is_file("{$lookup}/{$filename}")) {
|
||||
$modified = filemtime("{$lookup}/{$filename}");
|
||||
} else {
|
||||
$modified = 0;
|
||||
$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;
|
||||
}
|
||||
$list[$path] = [$name => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
|
||||
|
||||
$schemes[$scheme] = $type;
|
||||
}
|
||||
|
||||
return $list;
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
*/
|
||||
class Blueprint
|
||||
{
|
||||
use Export;
|
||||
use Export, DataMutatorTrait;
|
||||
|
||||
public $name;
|
||||
|
||||
@@ -46,68 +46,6 @@ class Blueprint
|
||||
$this->filter = array_flip($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all form fields.
|
||||
*
|
||||
@@ -146,10 +84,9 @@ class Blueprint
|
||||
*
|
||||
* @param array $data1
|
||||
* @param array $data2
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function mergeData(array $data1, array $data2, $name = null)
|
||||
public function mergeData(array $data1, array $data2)
|
||||
{
|
||||
// Initialize data
|
||||
$this->fields();
|
||||
@@ -213,7 +150,7 @@ class Blueprint
|
||||
$bref = array_merge($bref, array($key => $head[$key]));
|
||||
}
|
||||
}
|
||||
} while(count($head_stack));
|
||||
} while (count($head_stack));
|
||||
|
||||
$this->items = $blueprints;
|
||||
}
|
||||
@@ -478,14 +415,15 @@ 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']}");
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ class Blueprints
|
||||
$file = CompiledYamlFile::instance($filename);
|
||||
$blueprints = $file->content();
|
||||
} else {
|
||||
// throw new \RuntimeException("Blueprints for '{$type}' cannot be found! {$this->search}{$type}");
|
||||
$blueprints = [];
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use RocketTheme\Toolbox\File\FileInterface;
|
||||
*/
|
||||
class Data implements DataInterface
|
||||
{
|
||||
use ArrayAccessWithGetters, Countable, Export;
|
||||
use ArrayAccessWithGetters, Countable, Export, DataMutatorTrait;
|
||||
|
||||
protected $gettersVariable = 'items';
|
||||
protected $items;
|
||||
@@ -56,67 +56,6 @@ class Data 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.
|
||||
*
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -375,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);
|
||||
}
|
||||
@@ -517,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,70 +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();
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$mode = $config->get('system.debugger.mode');
|
||||
|
||||
if (function_exists('ini_set')) {
|
||||
ini_set('display_errors', !($mode === 'production'));
|
||||
}
|
||||
|
||||
if ($mode === 'detect') {
|
||||
TracyDebugger::$productionMode = self::DETECT;
|
||||
} elseif ($mode === 'production') {
|
||||
TracyDebugger::$productionMode = self::PRODUCTION;
|
||||
} else {
|
||||
TracyDebugger::$productionMode = self::DEVELOPMENT;
|
||||
}
|
||||
$this->grav = Grav::instance();
|
||||
|
||||
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 static function barDump($var, $title = NULL, array $options = NULL)
|
||||
public function addCollector($collector)
|
||||
{
|
||||
TracyDebugger::barDump($var, $title, $options);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,11 @@ trait CompiledFile
|
||||
$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;
|
||||
|
||||
@@ -19,12 +19,13 @@ abstract class Folder
|
||||
{
|
||||
$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;
|
||||
}
|
||||
@@ -33,17 +34,6 @@ abstract class Folder
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
|
||||
public static function getRelativePath($to, $from = ROOT_DIR)
|
||||
{
|
||||
$from = preg_replace('![\\|/]+!', '/', $from);
|
||||
$to = preg_replace('![\\|/]+!', '/', $to);
|
||||
if (strpos($to, $from) === 0) {
|
||||
$to = substr($to, strlen($from));
|
||||
}
|
||||
|
||||
return $to;
|
||||
}
|
||||
/**
|
||||
* Recursively find the last modified time under given path by file.
|
||||
*
|
||||
@@ -52,14 +42,16 @@ abstract class Folder
|
||||
*/
|
||||
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) {
|
||||
foreach ($itr as $filepath => $file) {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
@@ -69,6 +61,43 @@ abstract class Folder
|
||||
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.
|
||||
*
|
||||
@@ -208,8 +237,9 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively delete directory from filesystem.
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($target)
|
||||
{
|
||||
@@ -270,20 +300,3 @@ abstract class Folder
|
||||
return @rmdir($folder);
|
||||
}
|
||||
}
|
||||
|
||||
class GravRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
public static $FILTERS = array(
|
||||
'..', '.DS_Store'
|
||||
);
|
||||
|
||||
public function accept()
|
||||
{
|
||||
return !in_array(
|
||||
$this->current()->getFilename(),
|
||||
self::$FILTERS,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
public function accept()
|
||||
{
|
||||
// only accept directories
|
||||
return $this->current()->isDir();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Collection extends Iterator
|
||||
{
|
||||
abstract class AbstractCollection extends Iterator {
|
||||
|
||||
use GravTrait;
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
@@ -23,8 +23,8 @@ class Collection extends Iterator
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
<?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 Packages
|
||||
* @var Local\Packages
|
||||
*/
|
||||
private $installed;
|
||||
|
||||
/**
|
||||
* Remote available Packages
|
||||
* @var Packages
|
||||
* @var Remote\Packages
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
@@ -24,10 +26,12 @@ class GPM extends Iterator
|
||||
|
||||
/**
|
||||
* Internal cache
|
||||
* @var Iterator
|
||||
* @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
|
||||
@@ -63,7 +67,7 @@ class GPM extends Iterator
|
||||
/**
|
||||
* Return the instance of a specific Plugin
|
||||
* @param string $slug The slug of the Plugin
|
||||
* @return Package The instance of the Plugin
|
||||
* @return Local\Package The instance of the Plugin
|
||||
*/
|
||||
public function getInstalledPlugin($slug)
|
||||
{
|
||||
@@ -92,7 +96,7 @@ class GPM extends Iterator
|
||||
/**
|
||||
* Return the instance of a specific Theme
|
||||
* @param string $slug The slug of the Theme
|
||||
* @return Package The instance of the Theme
|
||||
* @return Local\Package The instance of the Theme
|
||||
*/
|
||||
public function getInstalledTheme($slug)
|
||||
{
|
||||
@@ -204,7 +208,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function isPluginUpdatable($plugin)
|
||||
{
|
||||
return array_key_exists($plugin, $this->getUpdatablePlugins());
|
||||
return array_key_exists($plugin, (array) $this->getUpdatablePlugins());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,7 +253,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function isThemeUpdatable($theme)
|
||||
{
|
||||
return array_key_exists($theme, $this->getUpdatableThemes());
|
||||
return array_key_exists($theme, (array) $this->getUpdatableThemes());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,7 +307,7 @@ class GPM extends Iterator
|
||||
/**
|
||||
* Searches for a Package in the repository
|
||||
* @param string $search Can be either the slug or the name
|
||||
* @return Package Package if found, FALSE if not
|
||||
* @return Remote\Package Package if found, FALSE if not
|
||||
*/
|
||||
public function findPackage($search)
|
||||
{
|
||||
@@ -347,7 +351,20 @@ class GPM extends Iterator
|
||||
$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] = [];
|
||||
}
|
||||
@@ -355,7 +372,20 @@ class GPM extends Iterator
|
||||
$packages[$found->package_type][$found->slug] = $found;
|
||||
$packages['total']++;
|
||||
} else {
|
||||
$packages['not_found'][] = $search;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,15 +61,12 @@ class Installer
|
||||
$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'])
|
||||
) {
|
||||
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']
|
||||
) {
|
||||
if (self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
|
||||
self::lastErrorCode() == self::EXISTS && !$options['overwrite']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,7 +109,7 @@ class Installer
|
||||
|
||||
}
|
||||
|
||||
public static function nonSophisticatedInstall($zip, $install_path, $tmp)
|
||||
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)) {
|
||||
@@ -124,7 +121,7 @@ class Installer
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function sophisticatedInstall($zip, $install_path, $tmp)
|
||||
public static function sophisticatedInstall(\ZipArchive $zip, $install_path, $tmp)
|
||||
{
|
||||
for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
|
||||
$filename = $zip->getNameIndex($i);
|
||||
@@ -150,10 +147,8 @@ class Installer
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_file($path)) {
|
||||
@unlink($path);
|
||||
@copy($tmp . DS . $filename, $path);
|
||||
}
|
||||
@unlink($path);
|
||||
@copy($tmp . DS . $filename, $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,6 +156,26 @@ class Installer
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unnstalls one or more given package
|
||||
*
|
||||
* @param string $package The slug of the package(s)
|
||||
* @param array $options Options to use for uninstalling
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function uninstall($path, $options = [])
|
||||
{
|
||||
$options = array_merge(self::$options, $options);
|
||||
if (!self::isValidDestination($path, $options['exclude_checks'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Folder::delete($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a set of checks on the destination and sets the Error if any
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,38 +2,24 @@
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
|
||||
/**
|
||||
* Class Package
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Package
|
||||
class Package extends BasePackage
|
||||
{
|
||||
/**
|
||||
* @var Data
|
||||
*/
|
||||
protected $data;
|
||||
/**
|
||||
* @var \Grav\Common\Data\Blueprint
|
||||
*/
|
||||
protected $blueprints;
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @param Data $package
|
||||
* @param bool $package_type
|
||||
*/
|
||||
public function __construct(Data $package, $package_type = false)
|
||||
public function __construct(Data $package, $package_type = null)
|
||||
{
|
||||
$this->data = $package;
|
||||
$this->blueprints = $this->data->blueprints();
|
||||
$data = new Data($package->blueprints()->toArray());
|
||||
parent::__construct($data, $package_type);
|
||||
|
||||
if ($package_type) {
|
||||
$html_description = \Parsedown::instance()->line($this->blueprints->get('description'));
|
||||
$this->blueprints->set('package_type', $package_type);
|
||||
$this->blueprints->set('description_html', $html_description);
|
||||
$this->blueprints->set('description_plain', strip_tags($html_description));
|
||||
$this->blueprints->set('symlink', is_link(USER_DIR . $package_type . DS . $this->blueprints->name));
|
||||
}
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,47 +27,6 @@ class Package
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->data['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->blueprints->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return $this->blueprints->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->blueprints->toArray();
|
||||
return $this->settings['enabled'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\GPM\Common\CachedCollection;
|
||||
|
||||
class Packages extends Iterator
|
||||
class Packages extends CachedCollection
|
||||
{
|
||||
private $plugins;
|
||||
private $themes;
|
||||
protected static $cache;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[__METHOD__])) {
|
||||
self::$cache[__METHOD__] = [
|
||||
'plugins' => new Plugins(),
|
||||
'themes' => new Themes()
|
||||
];
|
||||
}
|
||||
$items = [
|
||||
'plugins' => new Plugins(),
|
||||
'themes' => new Themes()
|
||||
];
|
||||
|
||||
$this->plugins = self::$cache[__METHOD__]['plugins'];
|
||||
$this->themes = self::$cache[__METHOD__]['themes'];
|
||||
|
||||
$this->append(['plugins' => $this->plugins]);
|
||||
$this->append(['themes' => $this->themes]);
|
||||
parent::__construct($items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,18 @@ namespace Grav\Common\GPM\Local;
|
||||
* Class Plugins
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Plugins extends Collection
|
||||
class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type = 'plugins';
|
||||
protected $type = 'plugins';
|
||||
|
||||
/**
|
||||
* Local Plugins Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$grav = self::$grav;
|
||||
|
||||
foreach ($grav['plugins']->all() as $name => $data) {
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
parent::__construct(self::getGrav()['plugins']->all());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
class Themes extends Collection
|
||||
/**
|
||||
* Class Themes
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Themes extends AbstractPackageCollection
|
||||
{
|
||||
private $type = 'themes';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'themes';
|
||||
|
||||
/**
|
||||
* Local Themes Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$grav = self::$grav;
|
||||
|
||||
foreach ($grav['themes']->all() as $name => $data) {
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
use \Doctrine\Common\Cache\Cache as DoctrineCache;
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
|
||||
class Collection extends Iterator {
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* The cached data previously fetched
|
||||
* @var string
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* The lifetime to store the entry in seconds
|
||||
* @var integer
|
||||
*/
|
||||
private $lifetime = 86400;
|
||||
private $repository;
|
||||
private $cache;
|
||||
|
||||
private $plugins, $themes;
|
||||
|
||||
public function __construct($repository = null) {
|
||||
if ($repository == null) {
|
||||
throw new \RuntimeException("A repository is required for storing the cache");
|
||||
}
|
||||
|
||||
$cache_dir = self::$grav['locator']->findResource('cache://gpm', true, true);
|
||||
$this->cache = new FilesystemCache($cache_dir);
|
||||
|
||||
$this->repository = $repository;
|
||||
$this->raw = $this->cache->fetch(md5($this->repository));
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function fetch($refresh = false, $callback = null) {
|
||||
if (!$this->raw || $refresh) {
|
||||
$response = Response::get($this->repository, [], $callback);
|
||||
$this->raw = $response;
|
||||
$this->cache->save(md5($this->repository), $this->raw, $this->lifetime);
|
||||
}
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Grav extends Collection
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
|
||||
class Grav extends AbstractPackageCollection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/grav.json';
|
||||
protected $repository = 'http://getgrav.org/downloads/grav.json';
|
||||
private $data;
|
||||
|
||||
private $version;
|
||||
@@ -15,15 +17,17 @@ class Grav extends Collection
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository);
|
||||
$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);
|
||||
|
||||
$this->version = @$this->data->version ?: '-';
|
||||
$this->date = @$this->data->date ?: '-';
|
||||
$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) {
|
||||
foreach ($this->data['assets'] as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +38,7 @@ class Grav extends Collection
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->data->assets;
|
||||
return $this->data['assets'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,11 +50,11 @@ class Grav extends Collection
|
||||
public function getChangelog($diff = null)
|
||||
{
|
||||
if (!$diff) {
|
||||
return $this->data->changelog;
|
||||
return $this->data['changelog'];
|
||||
}
|
||||
|
||||
$diffLog = [];
|
||||
foreach ($this->data->changelog as $version => $changelog) {
|
||||
foreach ($this->data['changelog'] as $version => $changelog) {
|
||||
preg_match("/[\d\.]+/", $version, $cleanVersion);
|
||||
|
||||
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; }
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Package {
|
||||
public function __construct($package, $package_type = false) {
|
||||
$this->data = $package;
|
||||
if ($package_type) {
|
||||
$this->data->package_type = $package_type;
|
||||
}
|
||||
}
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
class Package extends BasePackage {
|
||||
public function __construct($package, $package_type = null) {
|
||||
$data = new Data($package);
|
||||
parent::__construct($data, $package_type);
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
return $this->data->$key;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
return json_encode($this->data);
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\GPM\Common\CachedCollection;
|
||||
|
||||
class Packages extends Iterator
|
||||
class Packages extends CachedCollection
|
||||
{
|
||||
private $plugins;
|
||||
private $themes;
|
||||
protected static $cache;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[__METHOD__])) {
|
||||
self::$cache[__METHOD__] = [
|
||||
'plugins' => new Plugins($refresh, $callback),
|
||||
'themes' => new Themes($refresh, $callback)
|
||||
];
|
||||
}
|
||||
$items = [
|
||||
'plugins' => new Plugins($refresh, $callback),
|
||||
'themes' => new Themes($refresh, $callback)
|
||||
];
|
||||
|
||||
$this->plugins = self::$cache[__METHOD__]['plugins']->toArray();
|
||||
$this->themes = self::$cache[__METHOD__]['themes']->toArray();
|
||||
|
||||
$this->append(['plugins' => $this->plugins]);
|
||||
$this->append(['themes' => $this->themes]);
|
||||
parent::__construct($items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Plugins extends Collection
|
||||
/**
|
||||
* Class Plugins
|
||||
* @package Grav\Common\GPM\Remote
|
||||
*/
|
||||
class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/plugins.json';
|
||||
private $type = 'plugins';
|
||||
private $data;
|
||||
/**
|
||||
* @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);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
foreach ($this->data as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
parent::__construct($this->repository, $refresh, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Themes extends Collection
|
||||
/**
|
||||
* Class Themes
|
||||
* @package Grav\Common\GPM\Remote
|
||||
*/
|
||||
class Themes extends AbstractPackageCollection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/themes.json';
|
||||
private $type = 'themes';
|
||||
private $data;
|
||||
/**
|
||||
* @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);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
foreach ($this->data as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
parent::__construct($this->repository, $refresh, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ class Response
|
||||
$method = 'get' . ucfirst(strtolower(self::$method));
|
||||
|
||||
self::$callback = $callback;
|
||||
|
||||
return static::$method($uri, $options, $callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\Installer;
|
||||
|
||||
class Upgrader
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
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 RocketTheme\Toolbox\DI\Container;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use Grav\Common\Page\Medium;
|
||||
|
||||
/**
|
||||
* Grav
|
||||
@@ -15,9 +18,7 @@ use Grav\Common\Page\Medium;
|
||||
* @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
|
||||
@@ -25,7 +26,7 @@ class Grav extends Container
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $output;
|
||||
public $output;
|
||||
|
||||
/**
|
||||
* @var static
|
||||
@@ -55,6 +56,13 @@ class Grav extends Container
|
||||
|
||||
$container['grav'] = $container;
|
||||
|
||||
$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);
|
||||
};
|
||||
@@ -90,30 +98,35 @@ class Grav extends Container
|
||||
$container['page'] = function ($c) {
|
||||
/** @var Pages $pages */
|
||||
$pages = $c['pages'];
|
||||
$page = $pages->dispatch($c['uri']->route());
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $c['uri'];
|
||||
|
||||
$path = $uri->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();
|
||||
|
||||
// special case where a media file is requested
|
||||
if (!$page) {
|
||||
$path_parts = pathinfo($c['uri']->route());
|
||||
$page = $c['pages']->dispatch($path_parts['dirname']);
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
$media_file = urldecode($path_parts['basename']);
|
||||
if (isset($media[$media_file])) {
|
||||
$medium = $media[$media_file];
|
||||
$parsed_url = parse_url(urldecode($uri->basename()));
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($c['uri']->query(null,true) as $action => $params) {
|
||||
if (in_array($action, Medium::$valid_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
$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));
|
||||
}
|
||||
header('Content-type: '. $mime = $medium->get('mime'));
|
||||
echo file_get_contents($medium->path());
|
||||
die;
|
||||
}
|
||||
Utils::download($medium->path(), false);
|
||||
} else {
|
||||
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,9 +148,21 @@ 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;
|
||||
}
|
||||
|
||||
@@ -145,17 +170,40 @@ class Grav extends Container
|
||||
{
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_start('ob_gzhandler');
|
||||
}
|
||||
|
||||
/** @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');
|
||||
|
||||
// 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) {
|
||||
@@ -163,25 +211,31 @@ class Grav extends Container
|
||||
}
|
||||
|
||||
$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;
|
||||
$debugger->render();
|
||||
|
||||
$this->fireEvent('onOutputRendered');
|
||||
|
||||
@@ -203,7 +257,13 @@ class Grav extends Container
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
header("Location: " . rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
|
||||
if ($this['uri']->isExternal($route)) {
|
||||
$url = $route;
|
||||
} else {
|
||||
$url = rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/');
|
||||
}
|
||||
|
||||
header("Location: {$url}", true, $code);
|
||||
exit();
|
||||
}
|
||||
|
||||
@@ -235,9 +295,38 @@ 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
|
||||
$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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,20 +349,31 @@ class Grav extends Container
|
||||
*/
|
||||
public function shutdown()
|
||||
{
|
||||
if($this['config']->get('system.debugger.shutdown.close_connection')) {
|
||||
set_time_limit(0);
|
||||
ignore_user_abort(true);
|
||||
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
|
||||
|
||||
if (function_exists('ignore_user_abort')) {
|
||||
@ignore_user_abort(true);
|
||||
}
|
||||
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
header('Content-length: ' . ob_get_length());
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_end_flush(); // gzhandler buffer
|
||||
}
|
||||
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
header("Connection: close\r\n");
|
||||
|
||||
ob_end_flush();
|
||||
ob_flush();
|
||||
ob_end_flush(); // regular buffer
|
||||
@ob_flush();
|
||||
flush();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,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,4 +197,23 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 filter(callable $callback = null)
|
||||
{
|
||||
foreach ($this->items as $key => $value) {
|
||||
if (
|
||||
($callback && !call_user_func($callback, $value, $key)) ||
|
||||
(!$callback && !(bool) $value)
|
||||
) {
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
class Markdown extends \Parsedown
|
||||
{
|
||||
use MarkdownGravLinkTrait;
|
||||
|
||||
function __construct($page)
|
||||
{
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
class MarkdownExtra extends \ParsedownExtra
|
||||
{
|
||||
use MarkdownGravLinkTrait;
|
||||
|
||||
function __construct($page)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->page = $page;
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Page\Medium;
|
||||
use Grav\Common\Uri;
|
||||
|
||||
/**
|
||||
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
|
||||
*/
|
||||
trait MarkdownGravLinkTrait
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected function identifyLink($Excerpt)
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
|
||||
// Run the parent method to get the actual results
|
||||
$Excerpt = parent::identifyLink($Excerpt);
|
||||
$actions = array();
|
||||
$this->base_url = trim($config->get('system.base_url_relative'));
|
||||
|
||||
// if this is a link
|
||||
if (isset($Excerpt['element']['attributes']['href'])) {
|
||||
|
||||
$url = parse_url(htmlspecialchars_decode($Excerpt['element']['attributes']['href']));
|
||||
|
||||
// if there is no host set but there is a path, the file is local
|
||||
if (!isset($url['host']) && isset($url['path'])) {
|
||||
|
||||
// convert the URl is required
|
||||
$Excerpt['element']['attributes']['href'] = $this->convertUrl($url['path']);
|
||||
}
|
||||
}
|
||||
|
||||
// if this is an image
|
||||
if (isset($Excerpt['element']['attributes']['src'])) {
|
||||
|
||||
$alt = isset($Excerpt['element']['attributes']['alt']) ? $Excerpt['element']['attributes']['alt'] : '';
|
||||
$title = isset($Excerpt['element']['attributes']['title']) ? $Excerpt['element']['attributes']['title'] : '';
|
||||
|
||||
//get the url and parse it
|
||||
$url = parse_url(htmlspecialchars_decode($Excerpt['element']['attributes']['src']));
|
||||
|
||||
// if there is no host set but there is a path, the file is local
|
||||
if (!isset($url['host']) && isset($url['path'])) {
|
||||
// get the media objects for this page
|
||||
$media = $this->page->media();
|
||||
|
||||
// if there is a media file that matches the path referenced..
|
||||
if (isset($media->images()[$url['path']])) {
|
||||
// get the medium object
|
||||
$medium = $media->images()[$url['path']];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url['query'])) {
|
||||
parse_str($url['query'], $actions);
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action => $params) {
|
||||
// as long as it's a valid action
|
||||
if (in_array($action, Medium::$valid_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the URL for regular images, or an array of bits needed to put together
|
||||
// the lightbox HTML
|
||||
if (!isset($actions['lightbox'])) {
|
||||
$src = $medium->url();
|
||||
} else {
|
||||
$src = $medium->lightboxRaw();
|
||||
}
|
||||
|
||||
// set the src element with the new generated url
|
||||
if (!isset($actions['lightbox']) && !is_array($src)) {
|
||||
$Excerpt['element']['attributes']['src'] = $src;
|
||||
} else {
|
||||
|
||||
// Create the custom lightbox element
|
||||
$Element = array(
|
||||
'name' => 'a',
|
||||
'attributes' => array('rel' => $src['a_rel'], 'href' => $src['a_url']),
|
||||
'handler' => 'element',
|
||||
'text' => array(
|
||||
'name' => 'img',
|
||||
'attributes' => array('src' => $src['img_url'], 'alt' => $alt, 'title' => $title)
|
||||
),
|
||||
);
|
||||
|
||||
// Set the lightbox element on the Excerpt
|
||||
$Excerpt['element'] = $Element;
|
||||
}
|
||||
} else {
|
||||
// not a current page media file, see if it needs converting to relative
|
||||
$Excerpt['element']['attributes']['src'] = $this->convertUrl(Uri::build_url($url));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $Excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts links from absolute '/' or relative (../..) to a grav friendly format
|
||||
* @param string $markdown_url the URL as it was written in the markdown
|
||||
* @return string the more friendly formatted url
|
||||
*/
|
||||
protected function convertUrl($markdown_url)
|
||||
{
|
||||
// if absolue and starts with a base_url move on
|
||||
if ($this->base_url == '' || strpos($markdown_url, $this->base_url) === 0) {
|
||||
$new_url = $markdown_url;
|
||||
// if its absolute with /
|
||||
} elseif (strpos($markdown_url, '/') === 0) {
|
||||
$new_url = rtrim($this->base_url, '/') . $markdown_url;
|
||||
} else {
|
||||
$relative_path = rtrim($this->base_url, '/') . $this->page->route();
|
||||
|
||||
// If this is a 'real' filepath clean it up
|
||||
if (file_exists($this->page->path().'/'.$markdown_url)) {
|
||||
$relative_path = rtrim($this->base_url, '/') .
|
||||
preg_replace('/\/([\d]+.)/', '/',
|
||||
str_replace(PAGES_DIR, '/', $this->page->path()));
|
||||
$markdown_url = preg_replace('/^([\d]+.)/', '',
|
||||
preg_replace('/\/([\d]+.)/', '/',
|
||||
trim(preg_replace('/[^\/]+(\.md$)/', '', $markdown_url), '/')));
|
||||
}
|
||||
|
||||
// else its a relative path already
|
||||
$newpath = array();
|
||||
$paths = explode('/', $markdown_url);
|
||||
|
||||
// remove the updirectory references (..)
|
||||
foreach ($paths as $path) {
|
||||
if ($path == '..') {
|
||||
$relative_path = dirname($relative_path);
|
||||
} else {
|
||||
$newpath[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// build the new url
|
||||
$new_url = $relative_path . '/' . implode('/', $newpath);
|
||||
}
|
||||
|
||||
return $new_url;
|
||||
}
|
||||
}
|
||||
13
system/src/Grav/Common/Markdown/Parsedown.php
Normal file
13
system/src/Grav/Common/Markdown/Parsedown.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
class Parsedown extends \Parsedown
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
public function __construct($page, $defaults)
|
||||
{
|
||||
$this->init($page, $defaults);
|
||||
}
|
||||
|
||||
}
|
||||
13
system/src/Grav/Common/Markdown/ParsedownExtra.php
Normal file
13
system/src/Grav/Common/Markdown/ParsedownExtra.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
class ParsedownExtra extends \ParsedownExtra
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
public function __construct($page, $defaults)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->init($page, $defaults);
|
||||
}
|
||||
}
|
||||
277
system/src/Grav/Common/Markdown/ParsedownGravTrait.php
Normal file
277
system/src/Grav/Common/Markdown/ParsedownGravTrait.php
Normal file
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
|
||||
*/
|
||||
trait ParsedownGravTrait
|
||||
{
|
||||
use GravTrait;
|
||||
protected $page;
|
||||
protected $pages;
|
||||
protected $base_url;
|
||||
protected $pages_dir;
|
||||
protected $special_chars;
|
||||
|
||||
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
|
||||
|
||||
/**
|
||||
* Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait
|
||||
*
|
||||
* @param $page
|
||||
* @param $defaults
|
||||
*/
|
||||
protected function init($page, $defaults)
|
||||
{
|
||||
$this->page = $page;
|
||||
$this->pages = self::getGrav()['pages'];
|
||||
$this->BlockTypes['{'] [] = "TwigTag";
|
||||
$this->base_url = rtrim(self::getGrav()['base_url'] . self::getGrav()['pages']->base(), '/');
|
||||
$this->pages_dir = self::getGrav()['locator']->findResource('page://');
|
||||
$this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
|
||||
|
||||
if ($defaults == null) {
|
||||
$defaults = self::getGrav()['config']->get('system.pages.markdown');
|
||||
}
|
||||
|
||||
$this->setBreaksEnabled($defaults['auto_line_breaks']);
|
||||
$this->setUrlsLinked($defaults['auto_url_links']);
|
||||
$this->setMarkupEscaped($defaults['escape_markup']);
|
||||
$this->setSpecialChars($defaults['special_chars']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the element function publicly accessible, Medium uses this to render from Twig
|
||||
*
|
||||
* @param array $Element
|
||||
* @return string markup
|
||||
*/
|
||||
public function elementToHtml(array $Element)
|
||||
{
|
||||
return $this->element($Element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for special chars
|
||||
*
|
||||
* @param $special_chars
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
function setSpecialChars($special_chars)
|
||||
{
|
||||
$this->special_chars = $special_chars;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure Twig tags are treated as block level items with no <p></p> tags
|
||||
*/
|
||||
protected function blockTwigTag($Line)
|
||||
{
|
||||
if (preg_match('/[{%|{{|{#].*[#}|}}|%}]/', $Line['body'], $matches)) {
|
||||
$Block = array(
|
||||
'markup' => $Line['body'],
|
||||
);
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
protected function inlineSpecialCharacter($Excerpt)
|
||||
{
|
||||
if ($Excerpt['text'][0] === '&' && ! preg_match('/^&#?\w+;/', $Excerpt['text'])) {
|
||||
return array(
|
||||
'markup' => '&',
|
||||
'extent' => 1,
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($this->special_chars[$Excerpt['text'][0]])) {
|
||||
return array(
|
||||
'markup' => '&'.$this->special_chars[$Excerpt['text'][0]].';',
|
||||
'extent' => 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function inlineImage($excerpt)
|
||||
{
|
||||
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
|
||||
$excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
|
||||
$excerpt = parent::inlineImage($excerpt);
|
||||
$excerpt['element']['attributes']['src'] = $matches[1];
|
||||
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
|
||||
return $excerpt;
|
||||
} else {
|
||||
$excerpt = parent::inlineImage($excerpt);
|
||||
}
|
||||
|
||||
// Some stuff we will need
|
||||
$actions = array();
|
||||
$media = null;
|
||||
|
||||
// if this is an image
|
||||
if (isset($excerpt['element']['attributes']['src'])) {
|
||||
$alt = $excerpt['element']['attributes']['alt'] ?: '';
|
||||
$title = $excerpt['element']['attributes']['title'] ?: '';
|
||||
$class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : '';
|
||||
|
||||
//get the url and parse it
|
||||
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['src']));
|
||||
|
||||
// if there is no host set but there is a path, the file is local
|
||||
if (!isset($url['host']) && isset($url['path'])) {
|
||||
$path_parts = pathinfo($url['path']);
|
||||
|
||||
// get the local path to page media if possible
|
||||
if ($path_parts['dirname'] == $this->page->url()) {
|
||||
$url['path'] = ltrim(str_replace($this->page->url(), '', $url['path']), '/');
|
||||
// get the media objects for this page
|
||||
$media = $this->page->media();
|
||||
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$page_route = str_replace($this->base_url, '', $path_parts['dirname']);
|
||||
|
||||
$ext_page = $this->pages->dispatch($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->media();
|
||||
$url['path'] = $path_parts['basename'];
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a media file that matches the path referenced..
|
||||
if ($media && isset($media->all()[$url['path']])) {
|
||||
// get the medium object
|
||||
$medium = $media->all()[$url['path']];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url['query'])) {
|
||||
$actions = array_reduce(explode('&', $url['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? $parts[1] : null;
|
||||
$carry[] = [ 'method' => $parts[0], 'params' => $value ];
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
$medium = call_user_func_array(array($medium, $action['method']), explode(',', $action['params']));
|
||||
}
|
||||
|
||||
if (isset($url['fragment'])) {
|
||||
$medium->urlHash($url['fragment']);
|
||||
}
|
||||
|
||||
$excerpt['element'] = $medium->parseDownElement($title, $alt, $class);
|
||||
|
||||
} else {
|
||||
// not a current page media file, see if it needs converting to relative
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
protected function inlineLink($excerpt)
|
||||
{
|
||||
// do some trickery to get around Parsedown requirement for valid URL if its Twig in there
|
||||
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
|
||||
$excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
|
||||
$excerpt = parent::inlineLink($excerpt);
|
||||
$excerpt['element']['attributes']['href'] = $matches[1];
|
||||
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
|
||||
return $excerpt;
|
||||
} else {
|
||||
$excerpt = parent::inlineLink($excerpt);
|
||||
}
|
||||
|
||||
// if this is a link
|
||||
if (isset($excerpt['element']['attributes']['href'])) {
|
||||
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['href']));
|
||||
|
||||
// if there is no scheme, the file is local
|
||||
if (!isset($url['scheme']) && (count($url) > 0)) {
|
||||
// convert the URl is required
|
||||
$excerpt['element']['attributes']['href'] = $this->convertUrl(Uri::buildUrl($url));
|
||||
}
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts links from absolute '/' or relative (../..) to a grav friendly format
|
||||
* @param string $markdown_url the URL as it was written in the markdown
|
||||
* @return string the more friendly formatted url
|
||||
*/
|
||||
protected function convertUrl($markdown_url)
|
||||
{
|
||||
// if absolute and starts with a base_url move on
|
||||
if ($this->base_url != '' && Utils::startsWith($markdown_url, $this->base_url)) {
|
||||
return $markdown_url;
|
||||
// if contains only a fragment
|
||||
} elseif (Utils::startsWith($markdown_url, '#')) {
|
||||
return $markdown_url;
|
||||
} else {
|
||||
$target = null;
|
||||
// see if page is relative to this or absolute
|
||||
if (Utils::startsWith($markdown_url, '/')) {
|
||||
$normalized_path = Utils::normalizePath($this->pages_dir . $markdown_url);
|
||||
$normalized_url = Utils::normalizePath($this->base_url . $markdown_url);
|
||||
} else {
|
||||
// contains path, so need to normalize it
|
||||
if (Utils::contains($markdown_url, '/')) {
|
||||
$normalized_path = Utils::normalizePath($this->page->path() . '/' . $markdown_url);
|
||||
} else {
|
||||
$normalized_path = false;
|
||||
}
|
||||
$normalized_url = $this->base_url . Utils::normalizePath($this->page->route() . '/' . $markdown_url);
|
||||
}
|
||||
|
||||
// if this file exits, get the page and work with that
|
||||
if ($normalized_path) {
|
||||
$url_bits = parse_url($normalized_path);
|
||||
$full_path = $url_bits['path'];
|
||||
|
||||
if ($full_path && file_exists($full_path)) {
|
||||
$path_info = pathinfo($full_path);
|
||||
$page_path = $path_info['dirname'];
|
||||
$filename = '';
|
||||
|
||||
// save the filename if a file is part of the path
|
||||
$filename_regex = "/([\w\d-_]+\.([a-zA-Z]{2,4}))$/";
|
||||
if (preg_match($filename_regex, $full_path, $matches)) {
|
||||
if ($matches[2] != 'md') {
|
||||
$filename = '/' . $matches[1];
|
||||
}
|
||||
} else {
|
||||
$page_path = $full_path;
|
||||
}
|
||||
|
||||
// get page instances and try to find one that fits
|
||||
$instances = $this->pages->instances();
|
||||
if (isset($instances[$page_path])) {
|
||||
$target = $instances[$page_path];
|
||||
$url_bits['path'] = $this->base_url . $target->route() . $filename;
|
||||
return Uri::buildUrl($url_bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ class Collection extends Iterator
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
public function __construct($items = array(), array $params = array(), Pages $pages = null) {
|
||||
public function __construct($items = array(), array $params = array(), Pages $pages = null)
|
||||
{
|
||||
parent::__construct($items);
|
||||
|
||||
$this->params = $params;
|
||||
@@ -34,6 +35,17 @@ class Collection extends Iterator
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a copy of this collection
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return new static($this->items, $this->params, $this->pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
@@ -175,12 +187,16 @@ class Collection extends Iterator
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1)
|
||||
{
|
||||
|
||||
$values = array_keys($this->items);
|
||||
$keys = array_flip($values);
|
||||
$index = $keys[$path] - $direction;
|
||||
|
||||
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
|
||||
if (array_key_exists($path, $keys)) {
|
||||
$index = $keys[$path] - $direction;
|
||||
|
||||
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
|
||||
}
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,10 +205,39 @@ class Collection extends Iterator
|
||||
* @param string $path the path the item
|
||||
* @return Page Item in the array the the current position.
|
||||
*/
|
||||
public function currentPosition($path) {
|
||||
public function currentPosition($path)
|
||||
{
|
||||
return array_search($path, array_keys($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges where second value is optional
|
||||
* Dates can be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param $startDate
|
||||
* @param bool $endDate
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false)
|
||||
{
|
||||
$start = strtotime($startDate);
|
||||
$end = $endDate ? strtotime($endDate) : strtotime("now +1000 years");
|
||||
|
||||
$date_range = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->date() > $start && $page->date() < $end) {
|
||||
$date_range[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $date_range;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
@@ -205,10 +250,106 @@ class Collection extends Iterator
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->visible()) {
|
||||
$visible[$path] = $slug;
|
||||
$visible[$path] = $slug;
|
||||
}
|
||||
}
|
||||
return new static($visible, $this->params, $this->pages);
|
||||
$this->items = $visible;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return Collection The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible()
|
||||
{
|
||||
$visible = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->visible()) {
|
||||
$visible[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $visible;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return Collection The collection with only modular pages
|
||||
*/
|
||||
public function modular()
|
||||
{
|
||||
$modular = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->modular()) {
|
||||
$modular[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $modular;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return Collection The collection with only non-modular pages
|
||||
*/
|
||||
public function nonModular()
|
||||
{
|
||||
$modular = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->modular()) {
|
||||
$modular[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $modular;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return Collection The collection with only published pages
|
||||
*/
|
||||
public function published()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->published()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return Collection The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished()
|
||||
{
|
||||
$published = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->published()) {
|
||||
$published[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $published;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,12 +361,33 @@ class Collection extends Iterator
|
||||
{
|
||||
$routable = [];
|
||||
|
||||
foreach (array_keys($this->items) as $path => $slug) {
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if ($page->routable()) {
|
||||
$routable[$path] = $slug;
|
||||
}
|
||||
}
|
||||
return new static($routable, $this->params, $this->pages);
|
||||
|
||||
$this->items = $routable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return Collection The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable()
|
||||
{
|
||||
$routable = [];
|
||||
|
||||
foreach ($this->items as $path => $slug) {
|
||||
$page = $this->pages->get($path);
|
||||
if (!$page->routable()) {
|
||||
$routable[$path] = $slug;
|
||||
}
|
||||
}
|
||||
$this->items = $routable;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
11
system/src/Grav/Common/Page/Header.php
Normal file
11
system/src/Grav/Common/Page/Header.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess;
|
||||
|
||||
class Header implements \ArrayAccess
|
||||
{
|
||||
use NestedArrayAccess, Constructor;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ use Grav\Common\Getters;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Medium\MediumFactory;
|
||||
|
||||
/**
|
||||
* Media is a holder object that contains references to the media of page. This object is created and
|
||||
@@ -23,6 +25,7 @@ class Media extends Getters
|
||||
protected $instances = array();
|
||||
protected $images = array();
|
||||
protected $videos = array();
|
||||
protected $audios = array();
|
||||
protected $files = array();
|
||||
|
||||
/**
|
||||
@@ -37,88 +40,93 @@ class Media extends Getters
|
||||
|
||||
$this->path = $path;
|
||||
|
||||
$iterator = new \DirectoryIterator($path);
|
||||
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
|
||||
|
||||
$media = [];
|
||||
|
||||
/** @var \DirectoryIterator $info */
|
||||
foreach ($iterator as $info) {
|
||||
foreach ($iterator as $path => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
if ($info->isDot() || !$info->isFile() || $info->getExtension() == 'md') {
|
||||
if (!$info->isFile() || $info->getExtension() == 'md') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find out the real filename, in case of we are at the metadata.
|
||||
$filename = $info->getFilename();
|
||||
list($basename, $ext, $meta) = $this->getFileParts($filename);
|
||||
// Find out what type we're dealing with
|
||||
list($basename, $ext, $type, $extra) = $this->getFileParts($info->getFilename());
|
||||
|
||||
$media["{$basename}.{$ext}"] = isset($media["{$basename}.{$ext}"]) ? $media["{$basename}.{$ext}"] : [];
|
||||
|
||||
if ($type === 'alternative') {
|
||||
$media["{$basename}.{$ext}"][$type] = isset($media["{$basename}.{$ext}"][$type]) ? $media["{$basename}.{$ext}"][$type] : [];
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = $path;
|
||||
} else {
|
||||
$media["{$basename}.{$ext}"][$type] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($media as $name => $types) {
|
||||
// First prepare the alternatives in case there is no base medium
|
||||
if (!empty($types['alternative'])) {
|
||||
foreach ($types['alternative'] as $ratio => &$file) {
|
||||
$file = MediumFactory::fromFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the base medium
|
||||
if (!empty($types['base'])) {
|
||||
$medium = MediumFactory::fromFile($types['base']);
|
||||
} else if (!empty($types['alternative'])) {
|
||||
$altMedium = reset($types['alternative']);
|
||||
$ratio = key($types['alternative']);
|
||||
|
||||
$medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1);
|
||||
}
|
||||
|
||||
// Get medium instance creating it if it didn't exist.
|
||||
$medium = $this->get("{$basename}.{$ext}", true);
|
||||
if (!$medium) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//set file size
|
||||
$medium->set('size',$info->getSize());
|
||||
|
||||
// Assign meta files to the medium.
|
||||
if ($meta) {
|
||||
$medium->addMetaFile($meta);
|
||||
if (!empty($types['meta'])) {
|
||||
$medium->addMetaFile($types['meta']);
|
||||
}
|
||||
|
||||
if (!empty($types['thumb'])) {
|
||||
// We will not turn it into medium yet because user might never request the thumbnail
|
||||
// not wasting any resources on that, maybe we should do this for medium in general?
|
||||
$medium->set('thumbnails.page', $types['thumb']);
|
||||
}
|
||||
|
||||
// Build missing alternatives
|
||||
if (!empty($types['alternative'])) {
|
||||
$alternatives = $types['alternative'];
|
||||
|
||||
$max = max(array_keys($alternatives));
|
||||
|
||||
for ($i=2; $i < $max; $i++) {
|
||||
if (isset($alternatives[$i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max], $max, $i);
|
||||
}
|
||||
|
||||
foreach ($types['alternative'] as $ratio => $altMedium) {
|
||||
$medium->addAlternative($ratio, $altMedium);
|
||||
}
|
||||
}
|
||||
|
||||
$this->add($name, $medium);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get medium by basename and extension.
|
||||
* Get medium by filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param bool $create
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function get($filename, $create = false)
|
||||
public function get($filename)
|
||||
{
|
||||
if ($create && !isset($this->instances[$filename])) {
|
||||
$parts = explode('.', $filename);
|
||||
$ext = array_pop($parts);
|
||||
$basename = implode('.', $parts);
|
||||
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
|
||||
// Check if medium type has been configured.
|
||||
$params = $config->get("media.{$ext}");
|
||||
if (!$params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$filePath = $this->path . '/' . $filename;
|
||||
|
||||
// Add default settings for undefined variables.
|
||||
$params += $config->get('media.defaults');
|
||||
$params += array(
|
||||
'type' => 'file',
|
||||
'thumb' => 'media/thumb.png',
|
||||
'mime' => 'application/octet-stream',
|
||||
'name' => $filename,
|
||||
'filename' => $filename,
|
||||
'basename' => $basename,
|
||||
'extension' => $ext,
|
||||
'path' => $this->path,
|
||||
'modified' => filemtime($filePath),
|
||||
);
|
||||
|
||||
$lookup = array(
|
||||
USER_DIR . 'images/',
|
||||
SYSTEM_DIR . 'images/',
|
||||
);
|
||||
foreach ($lookup as $path) {
|
||||
if (is_file($path . $params['thumb'])) {
|
||||
$params['thumb'] = $path . $params['thumb'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->add(new Medium($params));
|
||||
}
|
||||
|
||||
return isset($this->instances[$filename]) ? $this->instances[$filename] : null;
|
||||
}
|
||||
|
||||
@@ -129,6 +137,7 @@ class Media extends Getters
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
@@ -139,6 +148,7 @@ class Media extends Getters
|
||||
*/
|
||||
public function images()
|
||||
{
|
||||
ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
@@ -149,9 +159,21 @@ class Media extends Getters
|
||||
*/
|
||||
public function videos()
|
||||
{
|
||||
ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->videos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all audio media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function audios()
|
||||
{
|
||||
ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->audios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all file media.
|
||||
*
|
||||
@@ -159,24 +181,28 @@ class Media extends Getters
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function add($file)
|
||||
protected function add($name, $file)
|
||||
{
|
||||
$this->instances[$file->filename] = $file;
|
||||
$this->instances[$name] = $file;
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
$this->images[$file->filename] = $file;
|
||||
$this->images[$name] = $file;
|
||||
break;
|
||||
case 'video':
|
||||
$this->videos[$file->filename] = $file;
|
||||
$this->videos[$name] = $file;
|
||||
break;
|
||||
case 'audio':
|
||||
$this->audios[$name] = $file;
|
||||
break;
|
||||
default:
|
||||
$this->files[$file->filename] = $file;
|
||||
$this->files[$name] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,19 +217,35 @@ class Media extends Getters
|
||||
$fileParts = explode('.', $filename);
|
||||
|
||||
$name = array_shift($fileParts);
|
||||
$extension = null;
|
||||
while (($part = array_shift($fileParts)) !== null) {
|
||||
if ($part != 'meta') {
|
||||
if (isset($extension)) {
|
||||
$name .= '.' . $extension;
|
||||
$type = 'base';
|
||||
$extra = null;
|
||||
|
||||
if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) {
|
||||
$name = $matches[1];
|
||||
$extension = $matches[3];
|
||||
$extra = (int) $matches[2];
|
||||
$type = 'alternative';
|
||||
|
||||
if ($extra === 1) {
|
||||
$type = 'base';
|
||||
$extra = null;
|
||||
}
|
||||
} else {
|
||||
$extension = null;
|
||||
while (($part = array_shift($fileParts)) !== null) {
|
||||
if ($part != 'meta' && $part != 'thumb') {
|
||||
if (isset($extension)) {
|
||||
$name .= '.' . $extension;
|
||||
}
|
||||
$extension = $part;
|
||||
} else {
|
||||
$type = $part;
|
||||
$extra = '.' . $part . '.' . implode('.', $fileParts);
|
||||
break;
|
||||
}
|
||||
$extension = $part;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$meta = implode('.', $fileParts);
|
||||
|
||||
return array($name, $extension, $meta);
|
||||
return array($name, $extension, $type, $extra);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Data;
|
||||
use Gregwar\Image\Image as ImageFile;
|
||||
|
||||
/**
|
||||
* The Image medium holds information related to an individual image. These are then stored in the Media object.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*
|
||||
* @property string $file_name
|
||||
* @property string $type
|
||||
* @property string $name Alias of file_name
|
||||
* @property string $description
|
||||
* @property string $url
|
||||
* @property string $path
|
||||
* @property string $thumb
|
||||
* @property int $width
|
||||
* @property int $height
|
||||
* @property string $mime
|
||||
* @property int $modified
|
||||
*
|
||||
* Medium can have up to 3 files:
|
||||
* - video.mov Medium file itself.
|
||||
* - video.mov.meta.yaml Metadata for the medium.
|
||||
* - video.mov.thumb.jpg Thumbnail image for the medium.
|
||||
*
|
||||
*/
|
||||
class Medium extends Data
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var ImageFile
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
protected $type = 'guess';
|
||||
protected $quality = 80;
|
||||
|
||||
public static $valid_actions = ['resize', 'forceResize', 'cropResize', 'crop', 'cropZoom', 'negate', 'brightness', 'contrast', 'grayscale', 'emboss', 'smooth', 'sharp', 'edge', 'colorize', 'sepia' ];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $meta = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $linkTarget;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $linkAttributes;
|
||||
|
||||
public function __construct($items = array(), Blueprint $blueprint = null)
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
if ($this->get('type') == 'image') {
|
||||
$filePath = $this->get('path') . '/' . $this->get('filename');
|
||||
$image_info = getimagesize($filePath);
|
||||
$this->set('thumb', $filePath);
|
||||
$this->def('width', $image_info[0]);
|
||||
$this->def('height', $image_info[1]);
|
||||
$this->def('mime', $image_info['mime']);
|
||||
$this->reset();
|
||||
} else {
|
||||
$this->def('mime', 'application/octet-stream');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string representation of the object (html or url).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->linkImage ? $this->html() : $this->url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PATH to file.
|
||||
*
|
||||
* @return string path to file
|
||||
*/
|
||||
public function path()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
|
||||
if ($this->image) {
|
||||
$output = $this->image->cacheFile($this->type, $this->quality);
|
||||
$this->reset();
|
||||
$output = ROOT_DIR . $output;
|
||||
} else {
|
||||
$output = $this->get('path') . '/' . $this->get('filename');
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL to file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
|
||||
if ($this->image) {
|
||||
$output = $this->image->cacheFile($this->type, $this->quality);
|
||||
$this->reset();
|
||||
} else {
|
||||
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
|
||||
$output = $relPath . '/' . $this->get('filename');
|
||||
}
|
||||
|
||||
return $config->get('system.base_url_relative') . '/'. $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets image output format.
|
||||
*
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
*/
|
||||
public function format($type = null, $quality = 80)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
$this->quality = $quality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <img> tag from the medium.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $class
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @return string
|
||||
*/
|
||||
public function img($title = null, $class = null, $type = null, $quality = 80)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$output = $this->html($title, $class, $type, $quality);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTML markup from the medium.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $class
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $class = null, $type = null, $quality = 80)
|
||||
{
|
||||
$title = $title ? $title : $this->get('title');
|
||||
$class = $class ? $class : '';
|
||||
|
||||
if ($this->image) {
|
||||
$type = $type ? $type : $this->type;
|
||||
$quality = $quality ? $quality : $this->quality;
|
||||
|
||||
$url = $this->url($type, $quality);
|
||||
$this->reset();
|
||||
|
||||
$output = '<img src="' . $url . '" class="'. $class . '" alt="' . $title . '" />';
|
||||
} else {
|
||||
$output = $title;
|
||||
}
|
||||
|
||||
if ($this->linkTarget) {
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
|
||||
$output = '<a href="' . $config->get('system.base_url_relative') . '/'. $this->linkTarget
|
||||
. '"' . $this->linkAttributes. ' class="'. $class . '">' . $output . '</a>';
|
||||
|
||||
$this->linkTarget = $this->linkAttributes = null;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return lightbox HTML for the medium.
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
*/
|
||||
public function lightbox($width = null, $height = null)
|
||||
{
|
||||
$this->linkAttributes = ' rel="lightbox"';
|
||||
|
||||
return $this->link($width, $height);
|
||||
}
|
||||
|
||||
public function lightboxRaw($width = null, $height = null)
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = self::$grav['config'];
|
||||
$url = $this->url();
|
||||
$this->link($width, $height);
|
||||
$lightbox_url = $config->get('system.base_url_relative') . '/'. $this->linkTarget;
|
||||
|
||||
return array('a_url' => $lightbox_url, 'a_rel' => 'lightbox', 'img_url' => $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return link HTML for the medium.
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
*/
|
||||
public function link($width = null, $height = null)
|
||||
{
|
||||
if ($this->image) {
|
||||
$image = clone $this->image;
|
||||
if ($width && $height) {
|
||||
$image->cropResize($width, $height);
|
||||
}
|
||||
$this->linkTarget = $image->cacheFile($this->type, $this->quality);
|
||||
} else {
|
||||
// TODO: we need to find out URI in a bit better way.
|
||||
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
|
||||
$this->linkTarget = $relPath. '/' . $this->get('filename');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->image = null;
|
||||
|
||||
if ($this->get('type') == 'image') {
|
||||
$this->image();
|
||||
$this->filter();
|
||||
}
|
||||
$this->type = 'guess';
|
||||
$this->quality = 80;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the call to the image processing method.
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return $this|mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($method == 'cropZoom') {
|
||||
$method = 'zoomCrop';
|
||||
}
|
||||
|
||||
// Always initialize image.
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
$result = call_user_func_array(array($this->image, $method), $args);
|
||||
|
||||
// Returns either current object or result of the action.
|
||||
return $result instanceof ImageFile ? $this : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets medium image, resets image manipulation operations.
|
||||
*
|
||||
* @param string $variable
|
||||
* @return $this
|
||||
*/
|
||||
public function image($variable = 'thumb')
|
||||
{
|
||||
// TODO: add default file
|
||||
$file = $this->get($variable);
|
||||
$this->image = ImageFile::open($file)
|
||||
->setCacheDir(basename(IMAGES_DIR))
|
||||
->setActualCacheDir(IMAGES_DIR)
|
||||
->setPrettyName(basename($this->get('basename')));
|
||||
|
||||
$this->filter();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the medium.
|
||||
*
|
||||
* @param $type
|
||||
* @return $this
|
||||
*/
|
||||
public function addMetaFile($type)
|
||||
{
|
||||
$this->meta[$type] = $type;
|
||||
|
||||
$path = $this->get('path') . '/' . $this->get('filename') . '.meta.' . $type;
|
||||
if ($type == 'yaml') {
|
||||
$this->merge(CompiledYamlFile::instance($path)->content());
|
||||
} elseif (in_array($type, array('jpg', 'jpeg', 'png', 'gif'))) {
|
||||
$this->set('thumb', $path);
|
||||
}
|
||||
$this->reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter image by using user defined filter parameters.
|
||||
*
|
||||
* @param string $filter Filter to be used.
|
||||
*/
|
||||
public function filter($filter = 'image.filters.default')
|
||||
{
|
||||
$filters = (array) $this->get($filter, array());
|
||||
foreach ($filters as $params) {
|
||||
$params = (array) $params;
|
||||
$method = array_shift($params);
|
||||
$this->__call($method, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
397
system/src/Grav/Common/Page/Medium/ImageMedium.php
Normal file
397
system/src/Grav/Common/Page/Medium/ImageMedium.php
Normal file
@@ -0,0 +1,397 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Gregwar\Image\Image as ImageFile;
|
||||
|
||||
class ImageMedium extends Medium
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $thumbnailTypes = [ 'page', 'media', 'default' ];
|
||||
|
||||
/**
|
||||
* @var ImageFile
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $format = 'guess';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $quality;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $default_quality;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $debug_watermarked = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $magic_actions = [
|
||||
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
|
||||
'negate', 'brightness', 'contrast', 'grayscale', 'emboss',
|
||||
'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $magic_resize_actions = [
|
||||
'resize' => [ 0, 1 ],
|
||||
'forceResize' => [ 0, 1 ],
|
||||
'cropResize' => [ 0, 1 ],
|
||||
'crop' => [ 0, 1, 2, 3 ],
|
||||
'cropResize' => [ 0, 1 ],
|
||||
'zoomCrop' => [ 0, 1 ]
|
||||
];
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint $blueprint
|
||||
*/
|
||||
public function __construct($items = [], Blueprint $blueprint = null)
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
$image_info = getimagesize($this->get('filepath'));
|
||||
$this->def('width', $image_info[0]);
|
||||
$this->def('height', $image_info[1]);
|
||||
$this->def('mime', $image_info['mime']);
|
||||
$this->def('debug', self::$grav['config']->get('system.images.debug'));
|
||||
|
||||
$this->set('thumbnails.media', $this->get('filepath'));
|
||||
|
||||
$this->default_quality = self::$grav['config']->get('system.images.default_image_quality', 85);
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the medium.
|
||||
*
|
||||
* @param $filepath
|
||||
* @return $this
|
||||
*/
|
||||
public function addMetaFile($filepath)
|
||||
{
|
||||
parent::addMetaFile($filepath);
|
||||
|
||||
// Apply filters in meta file
|
||||
$this->reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PATH to image.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string path to image
|
||||
*/
|
||||
public function path($reset = true)
|
||||
{
|
||||
$output = $this->saveImage();
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL to image.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function url($reset = true)
|
||||
{
|
||||
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->saveImage());
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return srcset string for this Medium and its alternatives.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function srcset($reset = true)
|
||||
{
|
||||
if (empty($this->alternatives)) {
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
$srcset = [ $this->url($reset) . ' ' . $this->get('width') . 'w' ];
|
||||
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
|
||||
}
|
||||
|
||||
return implode(', ', $srcset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsedown element for source display mode
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
public function sourceParsedownElement(array $attributes, $reset = true)
|
||||
{
|
||||
empty($attributes['src']) && $attributes['src'] = $this->url(false);
|
||||
|
||||
$srcset = $this->srcset($reset);
|
||||
if ($srcset) {
|
||||
empty($attributes['srcset']) && $attributes['srcset'] = $srcset;
|
||||
empty($attributes['sizes']) && $attributes['sizes'] = $this->sizes();
|
||||
}
|
||||
|
||||
return [ 'name' => 'img', 'attributes' => $attributes ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
|
||||
if ($this->image) {
|
||||
$this->image();
|
||||
$this->filter();
|
||||
}
|
||||
|
||||
$this->format = 'guess';
|
||||
$this->quality = $this->default_quality;
|
||||
|
||||
$this->debug_watermarked = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium into a Link
|
||||
*
|
||||
* @param boolean $reset
|
||||
* @param array $attributes
|
||||
* @return Link
|
||||
*/
|
||||
public function link($reset = true, array $attributes = [])
|
||||
{
|
||||
$attributes['href'] = $this->url(false);
|
||||
$srcset = $this->srcset(false);
|
||||
if ($srcset) {
|
||||
$attributes['data-srcset'] = $srcset;
|
||||
}
|
||||
|
||||
return parent::link($reset, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium inta a Link with lightbox enabled
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param boolean $reset
|
||||
* @return Link
|
||||
*/
|
||||
public function lightbox($width = null, $height = null, $reset = true)
|
||||
{
|
||||
if ($this->mode !== 'source') {
|
||||
$this->display('source');
|
||||
}
|
||||
|
||||
if ($width && $height) {
|
||||
$this->cropResize($width, $height);
|
||||
}
|
||||
|
||||
return parent::lightbox($width, $height, $reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quality of the image
|
||||
*
|
||||
* @param int $quality 0-100 quality
|
||||
* @return Medium
|
||||
*/
|
||||
public function quality($quality)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$this->quality = $quality;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets image output format.
|
||||
*
|
||||
* @param string $format
|
||||
* @return $this
|
||||
*/
|
||||
public function format($format)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$this->format = $format;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or get sizes parameter for srcset media action
|
||||
*
|
||||
* @param string $sizes
|
||||
* @return $this
|
||||
*/
|
||||
public function sizes($sizes = null)
|
||||
{
|
||||
|
||||
if ($sizes) {
|
||||
$this->attributes['sizes'] = $sizes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
return empty($this->attributes['sizes']) ? '100vw' : $this->attributes['sizes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the call to the image processing method.
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return $this|mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($method == 'cropZoom') {
|
||||
$method = 'zoomCrop';
|
||||
}
|
||||
|
||||
if (!in_array($method, self::$magic_actions)) {
|
||||
return parent::__call($method, $args);
|
||||
}
|
||||
|
||||
// Always initialize image.
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
try {
|
||||
$result = call_user_func_array([$this->image, $method], $args);
|
||||
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$args_copy = $args;
|
||||
|
||||
// regular image: resize 400x400 -> 200x200
|
||||
// --> @2x: resize 800x800->400x400
|
||||
if (isset(self::$magic_resize_actions[$method])) {
|
||||
foreach (self::$magic_resize_actions[$method] as $param) {
|
||||
if (isset($args_copy[$param])) {
|
||||
$args_copy[$param] = (int) $args_copy[$param] * $ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
call_user_func_array([$medium, $method], $args_copy);
|
||||
}
|
||||
} catch (\BadFunctionCallException $e) {
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets medium image, resets image manipulation operations.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function image()
|
||||
{
|
||||
$locator = self::$grav['locator'];
|
||||
|
||||
$file = $this->get('filepath');
|
||||
$cacheDir = $locator->findResource('cache://images', true);
|
||||
|
||||
$this->image = ImageFile::open($file)
|
||||
->setCacheDir($cacheDir)
|
||||
->setActualCacheDir($cacheDir)
|
||||
->setPrettyName(basename($this->get('basename')));
|
||||
|
||||
$this->filter();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the image with cache.
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
protected function saveImage()
|
||||
{
|
||||
if (!$this->image) {
|
||||
return parent::path(false);
|
||||
}
|
||||
|
||||
if ($this->get('debug') && !$this->debug_watermarked) {
|
||||
$ratio = $this->get('ratio');
|
||||
if (!$ratio) {
|
||||
$ratio = 1;
|
||||
}
|
||||
|
||||
$locator = self::$grav['locator'];
|
||||
$overlay = $locator->findResource("system://assets/responsive-overlays/{$ratio}x.png") ?: $locator->findResource('system://assets/responsive-overlays/unknown.png');
|
||||
$this->image->merge(ImageFile::open($overlay));
|
||||
}
|
||||
|
||||
$result = $this->image->cacheFile($this->format, $this->quality);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter image by using user defined filter parameters.
|
||||
*
|
||||
* @param string $filter Filter to be used.
|
||||
*/
|
||||
public function filter($filter = 'image.filters.default')
|
||||
{
|
||||
$filters = (array) $this->get($filter, []);
|
||||
foreach ($filters as $params) {
|
||||
$params = (array) $params;
|
||||
$method = array_shift($params);
|
||||
$this->__call($method, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
system/src/Grav/Common/Page/Medium/Link.php
Normal file
65
system/src/Grav/Common/Page/Medium/Link.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
|
||||
class Link implements RenderableInterface
|
||||
{
|
||||
use GravTrait;
|
||||
use ParsedownHtmlTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [];
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
* @param array $attributes
|
||||
* @param Medium $medium
|
||||
*/
|
||||
public function __construct(array $attributes, Medium $medium)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
$this->source = $medium->reset()->thumbnail('auto')->display('thumbnail');
|
||||
$this->source->linked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element (is array) that can be rendered by the Parsedown engine
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
|
||||
{
|
||||
$innerElement = $this->source->parsedownElement($title, $alt, $class, $reset);
|
||||
|
||||
return [
|
||||
'name' => 'a',
|
||||
'attributes' => $this->attributes,
|
||||
'handler' => is_string($innerElement) ? 'line' : 'element',
|
||||
'text' => $innerElement
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the call to the source element
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return $this|mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$this->source = call_user_func_array(array($this->source, $method), $args);
|
||||
|
||||
// Don't start nesting links, if user has multiple link calls in his
|
||||
// actions, we will drop the previous links.
|
||||
return $this->source instanceof LinkMedium ? $this->source : $this;
|
||||
}
|
||||
}
|
||||
415
system/src/Grav/Common/Page/Medium/Medium.php
Normal file
415
system/src/Grav/Common/Page/Medium/Medium.php
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
|
||||
/**
|
||||
* The Medium is a general class for multimedia objects in Grav pages, specific implementations will derive from
|
||||
*
|
||||
* @author Grav
|
||||
* @license MIT
|
||||
*
|
||||
*/
|
||||
class Medium extends Data implements RenderableInterface
|
||||
{
|
||||
use GravTrait;
|
||||
use ParsedownHtmlTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $mode = 'source';
|
||||
|
||||
/**
|
||||
* @var Medium
|
||||
*/
|
||||
protected $_thumbnail = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $thumbnailTypes = [ 'page', 'default' ];
|
||||
|
||||
protected $thumbnailType = null;
|
||||
|
||||
/**
|
||||
* @var Medium[]
|
||||
*/
|
||||
protected $alternatives = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $styleAttributes = [];
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint $blueprint
|
||||
*/
|
||||
public function __construct($items = [], Blueprint $blueprint = null)
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
if (self::getGrav()['config']->get('media.enable_media_timestamp', true)) {
|
||||
$this->querystring('&' . self::getGrav()['cache']->getKey());
|
||||
}
|
||||
|
||||
$this->def('mime', 'application/octet-stream');
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the medium.
|
||||
*
|
||||
* @param $filepath
|
||||
*/
|
||||
public function addMetaFile($filepath)
|
||||
{
|
||||
$this->merge(CompiledYamlFile::instance($filepath)->content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add alternative Medium to this Medium.
|
||||
*
|
||||
* @param $ratio
|
||||
* @param Medium $alternative
|
||||
*/
|
||||
public function addAlternative($ratio, Medium $alternative)
|
||||
{
|
||||
if (!is_numeric($ratio) || $ratio === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$alternative->set('ratio', $ratio);
|
||||
$this->alternatives[(float) $ratio] = $alternative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string representation of the object (html).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->html();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PATH to file.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string path to file
|
||||
*/
|
||||
public function path($reset = true)
|
||||
{
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return $this->get('filepath');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL to file.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function url($reset = true)
|
||||
{
|
||||
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('filepath'));
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set querystring for the file's url
|
||||
*
|
||||
* @param string $hash
|
||||
* @param boolean $withHash
|
||||
* @return string
|
||||
*/
|
||||
public function querystring($querystring = null, $withQuestionmark = true)
|
||||
{
|
||||
if ($querystring) {
|
||||
$this->set('querystring', ltrim($querystring, '?&'));
|
||||
|
||||
foreach ($this->alternatives as $alt) {
|
||||
$alt->querystring($querystring, $withQuestionmark);
|
||||
}
|
||||
}
|
||||
|
||||
$querystring = $this->get('querystring', '');
|
||||
|
||||
if ($withQuestionmark && !empty($querystring)) {
|
||||
return '?' . $querystring;
|
||||
} else {
|
||||
return $querystring;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set hash for the file's url
|
||||
*
|
||||
* @param string $hash
|
||||
* @param boolean $withHash
|
||||
* @return string
|
||||
*/
|
||||
public function urlHash($hash = null, $withHash = true)
|
||||
{
|
||||
if ($hash) {
|
||||
$this->set('urlHash', ltrim($hash, '#'));
|
||||
}
|
||||
|
||||
$hash = $this->get('urlHash', '');
|
||||
|
||||
if ($withHash && !empty($hash)) {
|
||||
return '#' . $hash;
|
||||
} else {
|
||||
return $hash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element (is array) that can be rendered by the Parsedown engine
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
|
||||
{
|
||||
$attributes = $this->attributes;
|
||||
|
||||
$style = '';
|
||||
foreach ($this->styleAttributes as $key => $value) {
|
||||
$style .= $key . ': ' . $value . ';';
|
||||
}
|
||||
$attributes['style'] = $style;
|
||||
|
||||
!empty($title) && empty($attributes['title']) && $attributes['title'] = $title;
|
||||
!empty($alt) && empty($attributes['alt']) && $attributes['alt'] = $alt;
|
||||
!empty($class) && empty($attributes['class']) && $attributes['class'] = $class;
|
||||
|
||||
switch ($this->mode) {
|
||||
case 'text':
|
||||
$element = $this->textParsedownElement($attributes, false);
|
||||
break;
|
||||
case 'thumbnail':
|
||||
$element = $this->getThumbnail()->sourceParsedownElement($attributes, false);
|
||||
break;
|
||||
case 'source':
|
||||
$element = $this->sourceParsedownElement($attributes, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
$this->display('source');
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsedown element for source display mode
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
protected function sourceParsedownElement(array $attributes, $reset = true)
|
||||
{
|
||||
return $this->textParsedownElement($attributes, $reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsedown element for text display mode
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
protected function textParsedownElement(array $attributes, $reset = true)
|
||||
{
|
||||
$text = empty($attributes['title']) ? empty($attributes['alt']) ? $this->get('filename') : $attributes['alt'] : $attributes['title'];
|
||||
|
||||
$element = [
|
||||
'name' => 'p',
|
||||
'attributes' => $attributes,
|
||||
'text' => $text
|
||||
];
|
||||
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset medium.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->attributes = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch display mode.
|
||||
*
|
||||
* @param string $mode
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function display($mode = 'source')
|
||||
{
|
||||
if ($this->mode === $mode) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
$this->mode = $mode;
|
||||
|
||||
return $mode === 'thumbnail' ? $this->getThumbnail()->reset() : $this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch thumbnail.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function thumbnail($type = 'auto')
|
||||
{
|
||||
if ($type !== 'auto' && !in_array($type, $this->thumbnailTypes)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->thumbnailType !== $type) {
|
||||
$this->_thumbnail = null;
|
||||
}
|
||||
|
||||
$this->thumbnailType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium into a Link
|
||||
*
|
||||
* @param boolean $reset
|
||||
* @param array $attributes
|
||||
* @return Link
|
||||
*/
|
||||
public function link($reset = true, array $attributes = [])
|
||||
{
|
||||
if ($this->mode !== 'source') {
|
||||
$this->display('source');
|
||||
}
|
||||
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $value;
|
||||
}
|
||||
|
||||
empty($attributes['href']) && $attributes['href'] = $this->url();
|
||||
|
||||
return new Link($attributes, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium inta a Link with lightbox enabled
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param boolean $reset
|
||||
* @return Link
|
||||
*/
|
||||
public function lightbox($width = null, $height = null, $reset = true)
|
||||
{
|
||||
$attributes = ['rel' => 'lightbox'];
|
||||
|
||||
if ($width && $height) {
|
||||
$attributes['data-width'] = $width;
|
||||
$attributes['data-height'] = $height;
|
||||
}
|
||||
|
||||
return $this->link($reset, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow any action to be called on this medium from twig or markdown
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return $this
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$qs = $method;
|
||||
if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) {
|
||||
$qs .= '=' . implode(',', array_map(function ($a) { return urlencode($a); }, $args));
|
||||
}
|
||||
|
||||
if (!empty($qs)) {
|
||||
$this->querystring($this->querystring(null, false) . '&' . $qs);
|
||||
}
|
||||
|
||||
self::$grav['debugger']->addMessage($this->querystring());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thumbnail Medium object
|
||||
*
|
||||
* @return ThumbnailImageMedium
|
||||
*/
|
||||
protected function getThumbnail()
|
||||
{
|
||||
if (!$this->_thumbnail) {
|
||||
$types = $this->thumbnailTypes;
|
||||
|
||||
if ($this->thumbnailType !== 'auto') {
|
||||
array_unshift($types, $this->thumbnailType);
|
||||
}
|
||||
|
||||
foreach ($types as $type) {
|
||||
$thumb = $this->get('thumbnails.' . $type, false);
|
||||
|
||||
if ($thumb) {
|
||||
$thumb = $thumb instanceof ThumbnailMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
|
||||
$thumb->parent = $this;
|
||||
}
|
||||
|
||||
if ($thumb) {
|
||||
$this->_thumbnail = $thumb;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_thumbnail;
|
||||
}
|
||||
}
|
||||
145
system/src/Grav/Common/Page/Medium/MediumFactory.php
Normal file
145
system/src/Grav/Common/Page/Medium/MediumFactory.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
|
||||
/**
|
||||
* MediumFactory can be used to more easily create various Medium objects from files or arrays, it should
|
||||
* contain most logic for instantiating a Medium object.
|
||||
*
|
||||
* @author Grav
|
||||
* @license MIT
|
||||
*
|
||||
*/
|
||||
class MediumFactory
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* Create Medium from a file
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $params
|
||||
* @return Medium
|
||||
*/
|
||||
public static function fromFile($file, array $params = [])
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = dirname($file);
|
||||
$filename = basename($file);
|
||||
$parts = explode('.', $filename);
|
||||
$ext = array_pop($parts);
|
||||
$basename = implode('.', $parts);
|
||||
|
||||
$config = self::getGrav()['config'];
|
||||
|
||||
$media_params = $config->get("media.".strtolower($ext));
|
||||
if (!$media_params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$params += $media_params;
|
||||
|
||||
// Add default settings for undefined variables.
|
||||
$params += $config->get('media.defaults');
|
||||
$params += [
|
||||
'type' => 'file',
|
||||
'thumb' => 'media/thumb.png',
|
||||
'mime' => 'application/octet-stream',
|
||||
'filepath' => $file,
|
||||
'filename' => $filename,
|
||||
'basename' => $basename,
|
||||
'extension' => $ext,
|
||||
'path' => $path,
|
||||
'modified' => filemtime($file),
|
||||
'thumbnails' => []
|
||||
];
|
||||
|
||||
$locator = self::getGrav()['locator'];
|
||||
|
||||
$lookup = $locator->findResources('image://');
|
||||
foreach ($lookup as $lookupPath) {
|
||||
if (is_file($lookupPath . '/' . $params['thumb'])) {
|
||||
$params['thumbnails']['default'] = $lookupPath . '/' . $params['thumb'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return static::fromArray($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Medium from array of parameters
|
||||
*
|
||||
* @param array $items
|
||||
* @param Blueprint|null $blueprint
|
||||
* @return Medium
|
||||
*/
|
||||
public static function fromArray(array $items = [], Blueprint $blueprint = null)
|
||||
{
|
||||
$type = isset($items['type']) ? $items['type'] : null;
|
||||
|
||||
switch ($type) {
|
||||
case 'image':
|
||||
return new ImageMedium($items, $blueprint);
|
||||
break;
|
||||
case 'thumbnail':
|
||||
return new ThumbnailImageMedium($items, $blueprint);
|
||||
break;
|
||||
case 'animated':
|
||||
case 'vector':
|
||||
return new StaticImageMedium($items, $blueprint);
|
||||
break;
|
||||
case 'video':
|
||||
return new VideoMedium($items, $blueprint);
|
||||
break;
|
||||
default:
|
||||
return new Medium($items, $blueprint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ImageMedium by scaling another ImageMedium object.
|
||||
*
|
||||
* @param ImageMedium $medium
|
||||
* @param int $from
|
||||
* @param int $to
|
||||
* @return Medium
|
||||
*/
|
||||
public static function scaledFromMedium($medium, $from, $to)
|
||||
{
|
||||
if (! $medium instanceof ImageMedium) {
|
||||
return $medium;
|
||||
}
|
||||
|
||||
if ($to > $from) {
|
||||
return $medium;
|
||||
}
|
||||
|
||||
$ratio = $to / $from;
|
||||
$width = (int) ($medium->get('width') * $ratio);
|
||||
$height = (int) ($medium->get('height') * $ratio);
|
||||
|
||||
$basename = $medium->get('basename');
|
||||
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $basename);
|
||||
|
||||
$debug = $medium->get('debug');
|
||||
$medium->set('debug', false);
|
||||
|
||||
$file = $medium->resize($width, $height)->path();
|
||||
|
||||
$medium->set('debug', $debug);
|
||||
|
||||
$size = filesize($file);
|
||||
|
||||
$medium = self::fromFile($file);
|
||||
$medium->set('size', $size);
|
||||
|
||||
return $medium;
|
||||
}
|
||||
}
|
||||
31
system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
Normal file
31
system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
|
||||
trait ParsedownHtmlTrait
|
||||
{
|
||||
/**
|
||||
* @var \Grav\Common\Markdown\Parsedown
|
||||
*/
|
||||
protected $parsedown = null;
|
||||
|
||||
/**
|
||||
* Return HTML markup from the medium.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $class
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $alt = null, $class = null, $reset = true)
|
||||
{
|
||||
$element = $this->parsedownElement($title, $alt, $class, $reset);
|
||||
|
||||
if (!$this->parsedown) {
|
||||
$this->parsedown = new Parsedown(null, null);
|
||||
}
|
||||
|
||||
return $this->parsedown->elementToHtml($element);
|
||||
}
|
||||
}
|
||||
34
system/src/Grav/Common/Page/Medium/RenderableInterface.php
Normal file
34
system/src/Grav/Common/Page/Medium/RenderableInterface.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
/**
|
||||
* Renderable Medium objects can be rendered to HTML markup and Parsedown objects
|
||||
*
|
||||
* @author Grav
|
||||
* @license MIT
|
||||
*
|
||||
*/
|
||||
interface RenderableInterface
|
||||
{
|
||||
/**
|
||||
* Return HTML markup from the medium.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $alt = null, $class = null, $reset = true);
|
||||
|
||||
/**
|
||||
* Return Parsedown Element from the medium.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true);
|
||||
}
|
||||
28
system/src/Grav/Common/Page/Medium/StaticImageMedium.php
Normal file
28
system/src/Grav/Common/Page/Medium/StaticImageMedium.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
/**
|
||||
* The Image medium holds information related to an individual image. These are then stored in the Media object.
|
||||
*
|
||||
* @author Grav
|
||||
* @license MIT
|
||||
*
|
||||
*/
|
||||
class StaticImageMedium extends Medium
|
||||
{
|
||||
use StaticResizeTrait;
|
||||
|
||||
/**
|
||||
* Parsedown element for source display mode
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
protected function sourceParsedownElement(array $attributes, $reset = true)
|
||||
{
|
||||
empty($attributes['src']) && $attributes['src'] = $this->url($reset);
|
||||
|
||||
return [ 'name' => 'image', 'attributes' => $attributes ];
|
||||
}
|
||||
}
|
||||
20
system/src/Grav/Common/Page/Medium/StaticResizeTrait.php
Normal file
20
system/src/Grav/Common/Page/Medium/StaticResizeTrait.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
trait StaticResizeTrait
|
||||
{
|
||||
/**
|
||||
* Resize media by setting attributes
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return Medium
|
||||
*/
|
||||
public function resize($width = null, $height = null)
|
||||
{
|
||||
$this->styleAttributes['width'] = $width . 'px';
|
||||
$this->styleAttributes['height'] = $height . 'px';
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
121
system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php
Normal file
121
system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
class ThumbnailImageMedium extends ImageMedium
|
||||
{
|
||||
/**
|
||||
* @var Medium
|
||||
*/
|
||||
public $parent = null;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $linked = false;
|
||||
|
||||
/**
|
||||
* Return srcset string for this Medium and its alternatives.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function srcset($reset = true)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element (is array) that can be rendered by the Parsedown engine
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
|
||||
{
|
||||
return $this->bubble('parsedownElement', [$title, $alt, $class, $reset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTML markup from the medium.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $alt = null, $class = null, $reset = true)
|
||||
{
|
||||
return $this->bubble('html', [$title, $alt, $class, $reset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch display mode.
|
||||
*
|
||||
* @param string $mode
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function display($mode = 'source')
|
||||
{
|
||||
return $this->bubble('display', [$mode], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch thumbnail.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function thumbnail($type = 'auto')
|
||||
{
|
||||
$this->bubble('thumbnail', [$type], false);
|
||||
return $this->bubble('getThumbnail', [], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium into a Link
|
||||
*
|
||||
* @param boolean $reset
|
||||
* @param array $attributes
|
||||
* @return Link
|
||||
*/
|
||||
public function link($reset = true, array $attributes = [])
|
||||
{
|
||||
return $this->bubble('link', [$reset, $attributes], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium inta a Link with lightbox enabled
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param boolean $reset
|
||||
* @return Link
|
||||
*/
|
||||
public function lightbox($width = null, $height = null, $reset = true)
|
||||
{
|
||||
return $this->bubble('lightbox', [$width, $height, $reset], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble a function call up to either the superclass function or the parent Medium instance
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @param boolean $testLinked
|
||||
* @return Medium
|
||||
*/
|
||||
protected function bubble($method, array $arguments = [], $testLinked = true)
|
||||
{
|
||||
if (!$testLinked || $this->linked) {
|
||||
return $this->parent ? call_user_func_array(array($this->parent, $method), $arguments) : $this;
|
||||
} else {
|
||||
return call_user_func_array(array($this, 'parent::' . $method), $arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
system/src/Grav/Common/Page/Medium/VideoMedium.php
Normal file
46
system/src/Grav/Common/Page/Medium/VideoMedium.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Data;
|
||||
use Gregwar\Image\Image as ImageFile;
|
||||
|
||||
class VideoMedium extends Medium
|
||||
{
|
||||
use StaticResizeTrait;
|
||||
|
||||
/**
|
||||
* Parsedown element for source display mode
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
protected function sourceParsedownElement(array $attributes, $reset = true)
|
||||
{
|
||||
$location = $this->url($reset);
|
||||
|
||||
return [
|
||||
'name' => 'video',
|
||||
'text' => '<source src="' . $location . '">Your browser does not support the video tag.',
|
||||
'attributes' => $attributes
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset medium.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
|
||||
$this->attributes['controls'] = true;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* GravPages is the class that is the entry point into the hierarchy of pages
|
||||
@@ -34,6 +35,11 @@ class Pages
|
||||
*/
|
||||
protected $children;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
@@ -62,11 +68,28 @@ class Pages
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @params Grav $c
|
||||
* @param Grav $c
|
||||
*/
|
||||
public function __construct(Grav $c)
|
||||
{
|
||||
$this->grav = $c;
|
||||
$this->base = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set base path for the pages.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function base($path = null)
|
||||
{
|
||||
if ($path !== null) {
|
||||
$path = trim($path, '/');
|
||||
$this->base = $path ? '/' . $path : null;
|
||||
}
|
||||
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +229,9 @@ class Pages
|
||||
*/
|
||||
public function get($path)
|
||||
{
|
||||
if (!is_null($path) && !is_string($path)) throw new \Exception();
|
||||
if (!is_null($path) && !is_string($path)) {
|
||||
throw new \Exception();
|
||||
}
|
||||
return isset($this->instances[(string) $path]) ? $this->instances[(string) $path] : null;
|
||||
}
|
||||
|
||||
@@ -234,14 +259,33 @@ class Pages
|
||||
// Fetch page if there's a defined route to it.
|
||||
$page = isset($this->routes[$url]) ? $this->get($this->routes[$url]) : null;
|
||||
|
||||
// If the page cannot be reached, look into site wide routes.
|
||||
// If the page cannot be reached, look into site wide redirects, routes + wildcards
|
||||
if (!$all && (!$page || !$page->routable())) {
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// Try redirects
|
||||
$redirect = $config->get("site.redirects.{$url}");
|
||||
if ($redirect) {
|
||||
$this->grav->redirect($redirect);
|
||||
}
|
||||
|
||||
// See if route matches one in the site configuration
|
||||
$route = $config->get("site.routes.{$url}");
|
||||
if ($route) {
|
||||
$page = $this->dispatch($route, $all);
|
||||
} else {
|
||||
// Try looking for wildcards
|
||||
foreach ($config->get("site.routes") as $alias => $route) {
|
||||
$match = rtrim($alias, '*');
|
||||
if (strpos($alias, '*') !== false && strpos($url, $match) !== false) {
|
||||
$wildcard_url = str_replace('*', str_replace($match, '', $url), $route);
|
||||
$page = isset($this->routes[$wildcard_url]) ? $this->get($this->routes[$wildcard_url]) : null;
|
||||
if ($page) {
|
||||
return $page;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +299,10 @@ class Pages
|
||||
*/
|
||||
public function root()
|
||||
{
|
||||
return $this->instances[rtrim(PAGES_DIR, DS)];
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
return $this->instances[rtrim($locator->findResource('page://'), DS)];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,6 +331,28 @@ class Pages
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pages
|
||||
*
|
||||
* @param \Grav\Common\Page\Page $current
|
||||
* @return \Grav\Common\Page\Collection
|
||||
*/
|
||||
public function all(Page $current = null)
|
||||
{
|
||||
$all = new Collection();
|
||||
$current = $current ?: $this->root();
|
||||
|
||||
if ($current->routable()) {
|
||||
$all[$current->path()] = [ 'slug' => $current->slug() ];
|
||||
}
|
||||
|
||||
foreach ($current->children() as $next) {
|
||||
$all->append($this->all($next));
|
||||
}
|
||||
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of route/title of all pages.
|
||||
*
|
||||
@@ -319,7 +388,7 @@ class Pages
|
||||
*
|
||||
* @return Types
|
||||
*/
|
||||
static public function getTypes()
|
||||
public static function getTypes()
|
||||
{
|
||||
if (!self::$types) {
|
||||
self::$types = new Types();
|
||||
@@ -339,7 +408,7 @@ class Pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function types()
|
||||
public static function types()
|
||||
{
|
||||
$types = self::getTypes();
|
||||
|
||||
@@ -351,7 +420,7 @@ class Pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function modularTypes()
|
||||
public static function modularTypes()
|
||||
{
|
||||
$types = self::getTypes();
|
||||
|
||||
@@ -363,7 +432,7 @@ class Pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function parents()
|
||||
public static function parents()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
@@ -385,14 +454,16 @@ class Pages
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
$pagesDir = $locator->findResource('page://');
|
||||
|
||||
if ($config->get('system.cache.enabled')) {
|
||||
/** @var Cache $cache */
|
||||
$cache = $this->grav['cache'];
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
// how should we check for last modified? Default is by file
|
||||
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
|
||||
case 'none':
|
||||
@@ -400,17 +471,18 @@ class Pages
|
||||
$last_modified = 0;
|
||||
break;
|
||||
case 'folder':
|
||||
$last_modified = Folder::lastModifiedFolder(PAGES_DIR);
|
||||
$last_modified = Folder::lastModifiedFolder($pagesDir);
|
||||
break;
|
||||
default:
|
||||
$last_modified = Folder::lastModifiedFile(PAGES_DIR);
|
||||
$last_modified = Folder::lastModifiedFile($pagesDir);
|
||||
}
|
||||
|
||||
$page_cache_id = md5(USER_DIR.$last_modified);
|
||||
$page_cache_id = md5(USER_DIR.$last_modified.$config->checksum());
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
|
||||
if (!$this->instances) {
|
||||
$this->recurse();
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
$this->recurse($pagesDir);
|
||||
$this->buildRoutes();
|
||||
|
||||
// save pages, routes, taxonomy, and sort to cache
|
||||
@@ -420,10 +492,11 @@ class Pages
|
||||
);
|
||||
} else {
|
||||
// If pages was found in cache, set the taxonomy
|
||||
$this->grav['debugger']->addMessage('Page cache hit.');
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
}
|
||||
} else {
|
||||
$this->recurse();
|
||||
$this->recurse($pagesDir);
|
||||
$this->buildRoutes();
|
||||
}
|
||||
}
|
||||
@@ -432,12 +505,12 @@ class Pages
|
||||
* Recursive function to load & build page relationships.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param null $parent
|
||||
* @param Page|null $parent
|
||||
* @return Page
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function recurse($directory = PAGES_DIR, Page &$parent = null)
|
||||
protected function recurse($directory, Page &$parent = null)
|
||||
{
|
||||
$directory = rtrim($directory, DS);
|
||||
$iterator = new \DirectoryIterator($directory);
|
||||
@@ -447,7 +520,9 @@ class Pages
|
||||
$config = $this->grav['config'];
|
||||
|
||||
$page->path($directory);
|
||||
if ($parent) $page->parent($parent);
|
||||
if ($parent) {
|
||||
$page->parent($parent);
|
||||
}
|
||||
|
||||
$page->orderDir($config->get('system.pages.order.dir'));
|
||||
$page->orderBy($config->get('system.pages.order.by'));
|
||||
@@ -470,20 +545,27 @@ class Pages
|
||||
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $file->getFilename();
|
||||
$modified = $file->getMTime();
|
||||
|
||||
if ($file->isFile() && Utils::endsWith($name, CONTENT_EXT)) {
|
||||
|
||||
$page->init($file);
|
||||
$content_exists = true;
|
||||
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
if ($file->isFile()) {
|
||||
// Update the last modified if it's newer than already found
|
||||
if ($file->getBasename() !== '.DS_Store' && ($modified = $file->getMTime()) > $last_modified) {
|
||||
$last_modified = $modified;
|
||||
}
|
||||
|
||||
} elseif ($file->isDir() && !$file->isDot()) {
|
||||
if (Utils::endsWith($name, CONTENT_EXT)) {
|
||||
$page->init($file);
|
||||
$content_exists = true;
|
||||
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
}
|
||||
} elseif ($file->isDir()) {
|
||||
if (!$page->path()) {
|
||||
$page->path($file->getPath());
|
||||
}
|
||||
@@ -497,20 +579,10 @@ class Pages
|
||||
|
||||
$this->children[$page->path()][$child->path()] = array('slug' => $child->slug());
|
||||
|
||||
// set the modified time if not already set
|
||||
if (!$page->date()) {
|
||||
$page->date($modified);
|
||||
}
|
||||
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last modified if it's newer than already found
|
||||
if ($modified > $last_modified) {
|
||||
$last_modified = $modified;
|
||||
}
|
||||
}
|
||||
|
||||
// Set routability to false if no page found
|
||||
@@ -539,7 +611,6 @@ class Pages
|
||||
// Build routes and taxonomy map.
|
||||
/** @var $page Page */
|
||||
foreach ($this->instances as $page) {
|
||||
|
||||
$parent = $page->parent();
|
||||
|
||||
if ($parent) {
|
||||
@@ -577,9 +648,18 @@ class Pages
|
||||
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null)
|
||||
{
|
||||
$list = array();
|
||||
$header_default = null;
|
||||
$header_query = null;
|
||||
|
||||
// do this headery query work only once
|
||||
if (strpos($order_by, 'header.') === 0) {
|
||||
$header_query = explode('|', str_replace('header.', '', $order_by));
|
||||
if (isset($header_query[1])) {
|
||||
$header_default = $header_query[1];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pages as $key => $info) {
|
||||
|
||||
$child = isset($this->instances[$key]) ? $this->instances[$key] : null;
|
||||
if (!$child) {
|
||||
throw new \RuntimeException("Page does not exist: {$key}");
|
||||
@@ -596,11 +676,20 @@ class Pages
|
||||
$list[$key] = $child->modified();
|
||||
break;
|
||||
case 'slug':
|
||||
$list[$key] = $info['slug'];
|
||||
$list[$key] = $child->slug();
|
||||
break;
|
||||
case 'basename':
|
||||
$list[$key] = basename($key);
|
||||
break;
|
||||
case (is_string($header_query[0])):
|
||||
$child_header = new Header((array)$child->header());
|
||||
$header_value = $child_header->get($header_query[0]);
|
||||
if ($header_value) {
|
||||
$list[$key] = $header_value;
|
||||
} else {
|
||||
$list[$key] = $header_default ?: $key;
|
||||
}
|
||||
break;
|
||||
case 'manual':
|
||||
case 'default':
|
||||
default:
|
||||
@@ -610,7 +699,7 @@ class Pages
|
||||
|
||||
// handle special case when order_by is random
|
||||
if ($order_by == 'random') {
|
||||
$list = $this->array_shuffle($list);
|
||||
$list = $this->arrayShuffle($list);
|
||||
} else {
|
||||
// else just sort the list according to specified key
|
||||
asort($list);
|
||||
@@ -645,12 +734,13 @@ class Pages
|
||||
}
|
||||
|
||||
// Shuffles and associative array
|
||||
protected function array_shuffle($list) {
|
||||
protected function arrayShuffle($list)
|
||||
{
|
||||
$keys = array_keys($list);
|
||||
shuffle($keys);
|
||||
|
||||
$new = array();
|
||||
foreach($keys as $key) {
|
||||
foreach ($keys as $key) {
|
||||
$new[$key] = $list[$key];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Config\Config;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use RocketTheme\Toolbox\Event\EventSubscriberInterface;
|
||||
@@ -23,12 +25,19 @@ class Plugin implements EventSubscriberInterface
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
protected $active = true;
|
||||
/**
|
||||
* @var \Grav\Common\string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* By default assign all methods as listeners using the default priority.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
$methods = get_class_methods(get_called_class());
|
||||
|
||||
$list = array();
|
||||
@@ -44,13 +53,23 @@ class Plugin implements EventSubscriberInterface
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Grav $grav
|
||||
* @param Config $config
|
||||
* @param string $name
|
||||
* @param Grav $grav
|
||||
* @param Config $config
|
||||
*/
|
||||
public function __construct(Grav $grav, Config $config)
|
||||
public function __construct($name, Grav $grav, Config $config)
|
||||
{
|
||||
$this->grav = $grav;
|
||||
$this->config = $config;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function isAdmin()
|
||||
{
|
||||
if (isset($this->grav['admin'])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,4 +92,70 @@ class Plugin implements EventSubscriberInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $events
|
||||
*/
|
||||
protected function disable(array $events)
|
||||
{
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = $this->grav['events'];
|
||||
|
||||
foreach ($events as $eventName => $params) {
|
||||
if (is_string($params)) {
|
||||
$dispatcher->removeListener($eventName, array($this, $params));
|
||||
} elseif (is_string($params[0])) {
|
||||
$dispatcher->removeListener($eventName, array($this, $params[0]));
|
||||
} else {
|
||||
foreach ($params as $listener) {
|
||||
$dispatcher->removeListener($eventName, array($this, $listener[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge global and page configurations.
|
||||
*
|
||||
* @param Page $page The page to merge the configurations with the
|
||||
* plugin settings.
|
||||
*
|
||||
* @param bool $deep Should you use deep or shallow merging
|
||||
*
|
||||
* @return \Grav\Common\Data\Data
|
||||
*/
|
||||
protected function mergeConfig(Page $page, $deep = false)
|
||||
{
|
||||
$class_name = $this->name;
|
||||
$class_name_merged = $class_name . '.merged';
|
||||
$defaults = $this->config->get('plugins.' . $class_name, array());
|
||||
$header = array();
|
||||
|
||||
if (isset($page->header()->$class_name_merged)) {
|
||||
$merged = $page->header()->$class_name_merged;
|
||||
if (count($merged) > 0) {
|
||||
return $merged;
|
||||
} else {
|
||||
return new Data($defaults);
|
||||
}
|
||||
}
|
||||
|
||||
// Get default plugin configurations and retrieve page header configuration
|
||||
if (isset($page->header()->$class_name)) {
|
||||
if ($deep) {
|
||||
$header = array_replace_recursive($defaults, $page->header()->$class_name);
|
||||
} else {
|
||||
$header = array_merge($defaults, $page->header()->$class_name);
|
||||
}
|
||||
} else {
|
||||
$header = $defaults;
|
||||
}
|
||||
|
||||
// Create new config object and set it on the page object so it's cached for next time
|
||||
$config = new Data($header);
|
||||
$page->modifyHeader($class_name_merged, $config);
|
||||
|
||||
// Return configurations as a new data config class
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ class Plugins extends Iterator
|
||||
{
|
||||
protected $grav;
|
||||
|
||||
public function __construct(Grav $grav) {
|
||||
public function __construct(Grav $grav)
|
||||
{
|
||||
$this->grav = $grav;
|
||||
}
|
||||
|
||||
@@ -46,18 +47,30 @@ class Plugins extends Iterator
|
||||
|
||||
$filePath = $this->grav['locator']('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT);
|
||||
if (!is_file($filePath)) {
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found!", $filePath, $plugin));
|
||||
$this->grav['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
|
||||
continue;
|
||||
}
|
||||
|
||||
require_once $filePath;
|
||||
|
||||
$pluginClass = 'Grav\\Plugin\\'.ucfirst($plugin).'Plugin';
|
||||
$pluginClassFormat = [
|
||||
'Grav\\Plugin\\'.ucfirst($plugin).'Plugin',
|
||||
'Grav\\Plugin\\'.Inflector::camelize($plugin).'Plugin'
|
||||
];
|
||||
$pluginClassName = false;
|
||||
|
||||
if (!class_exists($pluginClass)) {
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' class not found!", $plugin));
|
||||
foreach ($pluginClassFormat as $pluginClass) {
|
||||
if (class_exists($pluginClass)) {
|
||||
$pluginClassName = $pluginClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$instance = new $pluginClass($this->grav, $config);
|
||||
if (false === $pluginClassName) {
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", $plugin));
|
||||
}
|
||||
|
||||
$instance = new $pluginClassName($plugin, $this->grav, $config);
|
||||
if ($instance instanceof EventSubscriberInterface) {
|
||||
$events->addSubscriber($instance);
|
||||
}
|
||||
@@ -78,10 +91,11 @@ class Plugins extends Iterator
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function all()
|
||||
public static function all()
|
||||
{
|
||||
$list = array();
|
||||
$iterator = new \DirectoryIterator('plugins://');
|
||||
$locator = Grav::instance()['locator'];
|
||||
$iterator = new \DirectoryIterator($locator->findResource('plugins://', false));
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
@@ -98,7 +112,7 @@ class Plugins extends Iterator
|
||||
return $list;
|
||||
}
|
||||
|
||||
static public function get($name)
|
||||
public static function get($name)
|
||||
{
|
||||
$blueprints = new Blueprints("plugins://{$name}");
|
||||
$blueprint = $blueprints->get('blueprints');
|
||||
@@ -117,4 +131,5 @@ class Plugins extends Iterator
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\Blueprints\Blueprints;
|
||||
@@ -16,10 +14,18 @@ use RocketTheme\Toolbox\Blueprints\Blueprints;
|
||||
*/
|
||||
class ConfigServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
private $environment;
|
||||
private $setup;
|
||||
|
||||
public function register(Container $container)
|
||||
{
|
||||
$self = $this;
|
||||
|
||||
// Pre-load setup.php as it contains our initial configuration.
|
||||
$file = GRAV_ROOT . '/setup.php';
|
||||
$this->setup = is_file($file) ? (array) include $file : [];
|
||||
$this->environment = isset($this->setup['environment']) ? $this->setup['environment'] : null;
|
||||
|
||||
$container['blueprints'] = function ($c) use ($self) {
|
||||
return $self->loadMasterBlueprints($c);
|
||||
};
|
||||
@@ -31,19 +37,18 @@ class ConfigServiceProvider implements ServiceProviderInterface
|
||||
|
||||
public function loadMasterConfig(Container $container)
|
||||
{
|
||||
$file = CACHE_DIR . 'compiled/config/master.php';
|
||||
$environment = $this->getEnvironment($container);
|
||||
$file = CACHE_DIR . 'compiled/config/master-'.$environment.'.php';
|
||||
$data = is_file($file) ? (array) include $file : [];
|
||||
if ($data) {
|
||||
try {
|
||||
$config = new Config($data, $container);
|
||||
$config = new Config($data, $container, $environment);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($config)) {
|
||||
$file = GRAV_ROOT . '/setup.php';
|
||||
$data = is_file($file) ? (array) include $file : [];
|
||||
$config = new Config($data, $container);
|
||||
$config = new Config($this->setup, $container, $environment);
|
||||
}
|
||||
|
||||
return $config;
|
||||
@@ -51,9 +56,19 @@ class ConfigServiceProvider implements ServiceProviderInterface
|
||||
|
||||
public function loadMasterBlueprints(Container $container)
|
||||
{
|
||||
$file = CACHE_DIR . 'compiled/blueprints/master.php';
|
||||
$environment = $this->getEnvironment($container);
|
||||
$file = CACHE_DIR . 'compiled/blueprints/master-'.$environment.'.php';
|
||||
$data = is_file($file) ? (array) include $file : [];
|
||||
|
||||
return new Blueprints($data, $container);
|
||||
}
|
||||
|
||||
public function getEnvironment(Container $container)
|
||||
{
|
||||
if (!isset($this->environment)) {
|
||||
$this->environment = $container['uri']->environment();
|
||||
}
|
||||
|
||||
return $this->environment;
|
||||
}
|
||||
}
|
||||
|
||||
40
system/src/Grav/Common/Service/ErrorServiceProvider.php
Normal file
40
system/src/Grav/Common/Service/ErrorServiceProvider.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Errors\Errors;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use Whoops\Handler\JsonResponseHandler;
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Handler\PlainTextHandler;
|
||||
|
||||
class ErrorServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
// Setup Whoops-based error handler
|
||||
$errors = new Errors;
|
||||
|
||||
$error_page = new PrettyPageHandler;
|
||||
$error_page->setPageTitle('Crikey! There was an error...');
|
||||
$error_page->setEditor('sublime');
|
||||
$error_page->addResourcePath(GRAV_ROOT . '/system/assets');
|
||||
$error_page->addCustomCss('whoops.css');
|
||||
|
||||
$json_page = new JsonResponseHandler;
|
||||
$json_page->onlyForAjaxRequests(true);
|
||||
|
||||
$errors->pushHandler($error_page, 'pretty');
|
||||
$errors->pushHandler(new PlainTextHandler, 'text');
|
||||
$errors->pushHandler($json_page, 'json');
|
||||
|
||||
$logger = $container['log'];
|
||||
$errors->pushHandler(function (\Exception $exception, $inspector, $run) use ($logger) {
|
||||
$logger->addCritical($exception->getMessage(). ' - Trace: '. $exception->getTraceAsString());
|
||||
}, 'log');
|
||||
|
||||
$errors->register();
|
||||
|
||||
$container['errors'] = $errors;
|
||||
}
|
||||
}
|
||||
20
system/src/Grav/Common/Service/LoggerServiceProvider.php
Normal file
20
system/src/Grav/Common/Service/LoggerServiceProvider.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use \Monolog\Logger;
|
||||
use \Monolog\Handler\StreamHandler;
|
||||
|
||||
class LoggerServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
$log = new Logger('grav');
|
||||
$log_file = LOG_DIR.'grav.log';
|
||||
|
||||
$log->pushHandler(new StreamHandler($log_file, Logger::WARNING));
|
||||
|
||||
$container['log'] = $log;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use RocketTheme\Toolbox\DI\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RocketTheme\Toolbox\StreamWrapper\ReadOnlyStream;
|
||||
use RocketTheme\Toolbox\StreamWrapper\Stream;
|
||||
use RocketTheme\Toolbox\StreamWrapper\StreamBuilder;
|
||||
|
||||
class StreamsServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
@@ -16,49 +17,26 @@ class StreamsServiceProvider implements ServiceProviderInterface
|
||||
|
||||
$container['locator'] = function($c) use ($self) {
|
||||
$locator = new UniformResourceLocator(ROOT_DIR);
|
||||
$self->init($c, $locator);
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $c['config'];
|
||||
$config->initializeLocator($locator);
|
||||
|
||||
return $locator;
|
||||
};
|
||||
}
|
||||
|
||||
protected function init(Container $container, UniformResourceLocator $locator)
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $container['config'];
|
||||
$schemes = $config->get('streams.schemes');
|
||||
$container['streams'] = function($c) use ($self) {
|
||||
/** @var Config $config */
|
||||
$config = $c['config'];
|
||||
|
||||
if (!$schemes) {
|
||||
return;
|
||||
}
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $c['locator'];
|
||||
|
||||
// Set locator to both streams.
|
||||
Stream::setLocator($locator);
|
||||
ReadOnlyStream::setLocator($locator);
|
||||
// Set locator to both streams.
|
||||
Stream::setLocator($locator);
|
||||
ReadOnlyStream::setLocator($locator);
|
||||
|
||||
$registered = stream_get_wrappers();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($scheme, $registered)) {
|
||||
stream_wrapper_unregister($scheme);
|
||||
}
|
||||
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
|
||||
if ($type[0] != '\\') {
|
||||
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
|
||||
}
|
||||
|
||||
if (!stream_wrapper_register($scheme, $type)) {
|
||||
throw new \InvalidArgumentException("Stream '{$type}' could not be initialized.");
|
||||
}
|
||||
}
|
||||
return new StreamBuilder($config->getStreams($c));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ class Taxonomy
|
||||
$page_taxonomy = $page->taxonomy();
|
||||
}
|
||||
|
||||
if (!$page->published()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
if ($config->get('site.taxonomies') && count($page_taxonomy) > 0) {
|
||||
@@ -71,20 +75,33 @@ class Taxonomy
|
||||
* particular taxonomy.
|
||||
*
|
||||
* @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']]
|
||||
* @return Page page object with sub-pages set to contain matches found in the taxonomy map
|
||||
* @param string $operator can be 'or' or 'and' (defaults to 'or')
|
||||
* @return Colleciton Collection object set to contain matches found in the taxonomy map
|
||||
*/
|
||||
public function findTaxonomy($taxonomies)
|
||||
public function findTaxonomy($taxonomies, $operator = 'and')
|
||||
{
|
||||
$results = array();
|
||||
$matches = [];
|
||||
$results = [];
|
||||
|
||||
foreach ((array)$taxonomies as $taxonomy => $items) {
|
||||
foreach ((array) $items as $item) {
|
||||
if (isset($this->taxonomy_map[$taxonomy][$item])) {
|
||||
$results = array_merge($results, $this->taxonomy_map[$taxonomy][$item]);
|
||||
$matches[] = $this->taxonomy_map[$taxonomy][$item];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strtolower($operator) == 'or') {
|
||||
foreach ($matches as $match) {
|
||||
$results = array_merge($results, $match);
|
||||
}
|
||||
} else {
|
||||
$results = $matches ? array_pop($matches) : [];
|
||||
foreach ($matches as $match) {
|
||||
$results = array_intersect_key($results, $match);
|
||||
}
|
||||
}
|
||||
|
||||
return new Collection($results, ['taxonomies' => $taxonomies]);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,6 @@ class Theme extends Plugin
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
parent::__construct($grav, $config);
|
||||
parent::__construct($name, $grav, $config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,12 @@ class Themes extends Iterator
|
||||
/** @var Themes $themes */
|
||||
$themes = $this->grav['themes'];
|
||||
$themes->configure();
|
||||
$instance = $themes->load();
|
||||
|
||||
try {
|
||||
$instance = $themes->load();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new \RuntimeException($this->current(). ' theme could not be found');
|
||||
}
|
||||
|
||||
if ($instance instanceof EventSubscriberInterface) {
|
||||
$events->addSubscriber($instance);
|
||||
@@ -54,7 +59,8 @@ class Themes extends Iterator
|
||||
public function all()
|
||||
{
|
||||
$list = array();
|
||||
$iterator = new \DirectoryIterator('themes://');
|
||||
$locator = Grav::instance()['locator'];
|
||||
$iterator = new \DirectoryIterator($locator->findResource('themes://', false));
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
@@ -92,7 +98,7 @@ class Themes extends Iterator
|
||||
$thumb = "themes://{$name}/thumbnail.jpg";
|
||||
|
||||
if (file_exists($thumb)) {
|
||||
$blueprint->set('thumbnail', $this->config->get('system.base_url_relative') . "/user/themes/{$name}/thumbnail.jpg");
|
||||
$blueprint->set('thumbnail', $this->grav['base_url'] . "/user/themes/{$name}/thumbnail.jpg");
|
||||
}
|
||||
|
||||
// Load default configuration.
|
||||
@@ -140,10 +146,19 @@ class Themes extends Iterator
|
||||
$class = include $file;
|
||||
|
||||
if (!is_object($class)) {
|
||||
$className = '\\Grav\\Theme\\' . ucfirst($name);
|
||||
|
||||
if (class_exists($className)) {
|
||||
$class = new $className($grav, $config, $name);
|
||||
$themeClassFormat = [
|
||||
'Grav\\Theme\\'.ucfirst($name),
|
||||
'Grav\\Theme\\'.Inflector::camelize($name)
|
||||
];
|
||||
$themeClassName = false;
|
||||
|
||||
foreach ($themeClassFormat as $themeClass) {
|
||||
if (class_exists($themeClass)) {
|
||||
$themeClassName = $themeClass;
|
||||
$class = new $themeClassName($grav, $config, $name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (!$locator('theme://') && !defined('GRAV_CLI')) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user