mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
428 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11869ad4ec | ||
|
|
11fc34cfed | ||
|
|
b5717c2cbb | ||
|
|
0c085a5aab | ||
|
|
5cee23cbfa | ||
|
|
1e168b3100 | ||
|
|
018f7a6dec | ||
|
|
a62e88f22b | ||
|
|
dc5ba9eff4 | ||
|
|
4bebdfe0c7 | ||
|
|
f8fd065192 | ||
|
|
9d38d0818b | ||
|
|
a1ad9b7f4d | ||
|
|
29cb55e91c | ||
|
|
0687d2ff78 | ||
|
|
cd04572b78 | ||
|
|
6bb47124a9 | ||
|
|
1a21186ba1 | ||
|
|
82d1193090 | ||
|
|
ea76ac024a | ||
|
|
ff52d61322 | ||
|
|
83e970731e | ||
|
|
c6ea5eb5e9 | ||
|
|
aa7a4111e6 | ||
|
|
2b01f832bd | ||
|
|
1401102396 | ||
|
|
8963d024a6 | ||
|
|
bb5e7b508f | ||
|
|
bfc9efea92 | ||
|
|
75feea3e75 | ||
|
|
e142f5ee61 | ||
|
|
9746c2db5d | ||
|
|
311598e3a6 | ||
|
|
109d19f02e | ||
|
|
bc5ea13821 | ||
|
|
e9cc34f481 | ||
|
|
9f254b6c84 | ||
|
|
4f442d1edc | ||
|
|
a1a10ab23d | ||
|
|
21beefd387 | ||
|
|
a7e03c9d5c | ||
|
|
49781f9717 | ||
|
|
ca12c69741 | ||
|
|
333814eab0 | ||
|
|
c18311eeb2 | ||
|
|
3caa0d1ef5 | ||
|
|
dc56f85881 | ||
|
|
532e035724 | ||
|
|
c3431e1ead | ||
|
|
3016e77897 | ||
|
|
cf69ba0d66 | ||
|
|
4e82891779 | ||
|
|
7fa3e7bf28 | ||
|
|
656c23a891 | ||
|
|
00f758dd98 | ||
|
|
8962d2b1e4 | ||
|
|
eefb761e98 | ||
|
|
9363da137f | ||
|
|
c4daae2f95 | ||
|
|
b201b21e46 | ||
|
|
c959fd9c48 | ||
|
|
0845b5e28a | ||
|
|
b9f9570033 | ||
|
|
baa0e73703 | ||
|
|
e97aaccfee | ||
|
|
f58cfd8571 | ||
|
|
1a43d31c50 | ||
|
|
d244442e70 | ||
|
|
a009c56271 | ||
|
|
f30af37faf | ||
|
|
bb4eea7999 | ||
|
|
e26b80d873 | ||
|
|
b23aa1bc4c | ||
|
|
e22655b440 | ||
|
|
8e57839271 | ||
|
|
16f779c8f5 | ||
|
|
bd08d787f2 | ||
|
|
df99fe0073 | ||
|
|
7a2fceaee9 | ||
|
|
bb69656bd7 | ||
|
|
5be0618b4d | ||
|
|
ad00252a4e | ||
|
|
84eee30d1f | ||
|
|
8975c2936c | ||
|
|
b64bfc9ab0 | ||
|
|
cc9f5ed096 | ||
|
|
2161daa398 | ||
|
|
f1a41394ab | ||
|
|
6d5bb6f887 | ||
|
|
9bcf9a7dcd | ||
|
|
6d69f03111 | ||
|
|
0b709ddb1a | ||
|
|
6ba780ba3b | ||
|
|
f0f24142b2 | ||
|
|
a716da14f7 | ||
|
|
d8a80479c2 | ||
|
|
8d4a55a3a4 | ||
|
|
0c0aa94ded | ||
|
|
0fd4991dd4 | ||
|
|
5f32ea64eb | ||
|
|
dc6705c74a | ||
|
|
4479f251a6 | ||
|
|
ac076fb892 | ||
|
|
503b38c17f | ||
|
|
7b6680cde2 | ||
|
|
7029edfd58 | ||
|
|
3b3ac68f47 | ||
|
|
5e28d04aa6 | ||
|
|
102140b917 | ||
|
|
17d7e19e84 | ||
|
|
d7b810e87e | ||
|
|
3cf44b4c62 | ||
|
|
96a9962895 | ||
|
|
b2cd4db395 | ||
|
|
015ef2c6ce | ||
|
|
0ba1af244d | ||
|
|
547a049140 | ||
|
|
05e0013990 | ||
|
|
b9d6f75923 | ||
|
|
07f4cb0892 | ||
|
|
e48be33c6c | ||
|
|
b3ba54894e | ||
|
|
14753d499d | ||
|
|
bb348fff94 | ||
|
|
c097907098 | ||
|
|
3719bac746 | ||
|
|
85add10bef | ||
|
|
8a7a9d4a45 | ||
|
|
875885f647 | ||
|
|
3af85ed14f | ||
|
|
34f6020e7f | ||
|
|
2a6b96937c | ||
|
|
8861918827 | ||
|
|
6212aef8b1 | ||
|
|
f13c4e916a | ||
|
|
60e183a8c0 | ||
|
|
939449c1b0 | ||
|
|
5970e5bf5c | ||
|
|
aee5a38313 | ||
|
|
19b2de7040 | ||
|
|
20d7ed5421 | ||
|
|
0dd53aea02 | ||
|
|
801d5d361a | ||
|
|
dc7362c70b | ||
|
|
fc3c0e7fa5 | ||
|
|
e172f5c82d | ||
|
|
3a7426ed04 | ||
|
|
3ab1feb7b1 | ||
|
|
1c90ee6db3 | ||
|
|
046136bc37 | ||
|
|
1d0234550e | ||
|
|
cca3a039ad | ||
|
|
e73d101c97 | ||
|
|
389e53207e | ||
|
|
5bdce4fb28 | ||
|
|
b06e3b3281 | ||
|
|
a4d39ce424 | ||
|
|
79f5aaa032 | ||
|
|
740ea2e86d | ||
|
|
364f95ec45 | ||
|
|
6668a7b8ab | ||
|
|
571f27d518 | ||
|
|
0fa70b5b35 | ||
|
|
be8d424404 | ||
|
|
08e239fcf4 | ||
|
|
947cb79558 | ||
|
|
1f1df9afc7 | ||
|
|
ef40bb25fd | ||
|
|
e2db025aa0 | ||
|
|
63369247ab | ||
|
|
8c695df9da | ||
|
|
1c9d41ad58 | ||
|
|
e7721d21a2 | ||
|
|
547f72919f | ||
|
|
2b6740dc7b | ||
|
|
1a91cf7033 | ||
|
|
1e9418b87b | ||
|
|
bedf1555af | ||
|
|
5d070f4d9e | ||
|
|
7eff48bd4f | ||
|
|
dfccfb3cf3 | ||
|
|
aeebc13d83 | ||
|
|
0a42ace4ba | ||
|
|
7b4f0a29ac | ||
|
|
b529456745 | ||
|
|
f1a929624f | ||
|
|
65285ad11e | ||
|
|
eb421bc95e | ||
|
|
917a5c3082 | ||
|
|
ea491fbde5 | ||
|
|
a98d01ba65 | ||
|
|
7e2fa8295a | ||
|
|
adf758a569 | ||
|
|
49941105dc | ||
|
|
c07afab981 | ||
|
|
c2b07fec1f | ||
|
|
7f8baf326e | ||
|
|
9601061f25 | ||
|
|
b1317e56ec | ||
|
|
353832c386 | ||
|
|
8bf04d4593 | ||
|
|
c9f0500c6f | ||
|
|
0dc465bb41 | ||
|
|
34e50aab21 | ||
|
|
5a2411a0e6 | ||
|
|
178f66c940 | ||
|
|
6d3a7a3989 | ||
|
|
2400eaf04e | ||
|
|
752a3ca5bd | ||
|
|
6dbb6eb432 | ||
|
|
e580bb9998 | ||
|
|
d309491f06 | ||
|
|
0e35048143 | ||
|
|
c919ed36b4 | ||
|
|
6d2a7c53dc | ||
|
|
a8582fc131 | ||
|
|
76b7bd855d | ||
|
|
5656bb3caf | ||
|
|
57bd4d8f22 | ||
|
|
e71cd5a7ad | ||
|
|
4132388dda | ||
|
|
1edabe3b00 | ||
|
|
019fdd65e9 | ||
|
|
fa432cd32f | ||
|
|
4935679659 | ||
|
|
835c64c173 | ||
|
|
4d6ecbe618 | ||
|
|
63456aad85 | ||
|
|
bec2ee91b5 | ||
|
|
027c9cdd04 | ||
|
|
8ecea3a8c1 | ||
|
|
69e6d57346 | ||
|
|
8127d9cf31 | ||
|
|
bc1a9b31fa | ||
|
|
ff5658e803 | ||
|
|
3d986cdd91 | ||
|
|
32810efcd9 | ||
|
|
76b463792e | ||
|
|
b4a0a31539 | ||
|
|
acbc7efdc8 | ||
|
|
8475e0803a | ||
|
|
f779fc57df | ||
|
|
7afef9073c | ||
|
|
370b5db34e | ||
|
|
6adabb5f71 | ||
|
|
df9a0eeab2 | ||
|
|
e6d58b780e | ||
|
|
e4b65d5d7f | ||
|
|
bf61a123cc | ||
|
|
71f0757015 | ||
|
|
f1e57e0e9c | ||
|
|
1147516dcc | ||
|
|
3f1661965b | ||
|
|
adb0b3ab18 | ||
|
|
8afad07146 | ||
|
|
81bce07a6e | ||
|
|
e883b57ac6 | ||
|
|
921685ff88 | ||
|
|
ae2f95b1ae | ||
|
|
4f77ef26b5 | ||
|
|
d8df9ffb53 | ||
|
|
3c51c0acd4 | ||
|
|
c085540143 | ||
|
|
d239dd56d5 | ||
|
|
8f9eb3b48b | ||
|
|
1f906e6a50 | ||
|
|
e0a4efe181 | ||
|
|
78e9c8fa1a | ||
|
|
9382dc9c10 | ||
|
|
aa85f20aa9 | ||
|
|
4ae01d48ae | ||
|
|
fe3082c6c9 | ||
|
|
ab6c257ba6 | ||
|
|
628ae561d5 | ||
|
|
56b5a65b24 | ||
|
|
73654a99f9 | ||
|
|
ea8add59b1 | ||
|
|
5078ae62c0 | ||
|
|
e026ba32f4 | ||
|
|
e9ebe3b533 | ||
|
|
cea130700d | ||
|
|
0ae7ebac68 | ||
|
|
326f1bc890 | ||
|
|
cabec818e2 | ||
|
|
ff04b33efd | ||
|
|
4f1a71b145 | ||
|
|
f7f8aa108a | ||
|
|
718d443d52 | ||
|
|
57c5885216 | ||
|
|
14767a3e11 | ||
|
|
fb31caefef | ||
|
|
7ceb0dd065 | ||
|
|
b2a78d587c | ||
|
|
16c3a3690b | ||
|
|
7f8e8f67a5 | ||
|
|
9792c9a84e | ||
|
|
13e9e6f5e1 | ||
|
|
e62133233c | ||
|
|
3ec855e28f | ||
|
|
4b56a05f57 | ||
|
|
845da953e1 | ||
|
|
565a76c317 | ||
|
|
e01a116173 | ||
|
|
4e07c294c5 | ||
|
|
58bb5a6993 | ||
|
|
2c51dd5fe1 | ||
|
|
ce8513d3ff | ||
|
|
08c4fd02d2 | ||
|
|
2e680cd35a | ||
|
|
f12ef84a98 | ||
|
|
0563b2b6e5 | ||
|
|
c68c39df27 | ||
|
|
41c00d7fbe | ||
|
|
750dfb60dc | ||
|
|
4305bbabd5 | ||
|
|
ecc12be531 | ||
|
|
b65280f3c9 | ||
|
|
e077b3d04c | ||
|
|
91a963f580 | ||
|
|
8404ba7a09 | ||
|
|
86b907c86c | ||
|
|
ca899072d4 | ||
|
|
bbfc63e943 | ||
|
|
9bce9ce026 | ||
|
|
3a25f028df | ||
|
|
491c6d6a1f | ||
|
|
64bb6ea2ad | ||
|
|
084e59dc90 | ||
|
|
f7ea2e95e4 | ||
|
|
7acdf231a4 | ||
|
|
5d38e0fa14 | ||
|
|
e364616730 | ||
|
|
5ccefee288 | ||
|
|
93f4ad6e5a | ||
|
|
c481acbb71 | ||
|
|
bbdb0189f1 | ||
|
|
e1d655a3ac | ||
|
|
d849f8a03e | ||
|
|
67c3d64275 | ||
|
|
1546783371 | ||
|
|
21cc9f86f3 | ||
|
|
2936d26f8d | ||
|
|
ae453dbc71 | ||
|
|
d25699397f | ||
|
|
d461fac089 | ||
|
|
4bda629a6a | ||
|
|
8a2817a305 | ||
|
|
8887f8862d | ||
|
|
4883a408c7 | ||
|
|
7b75171f79 | ||
|
|
dc42288fa0 | ||
|
|
f576f914ae | ||
|
|
88faa5040c | ||
|
|
1e5365ba51 | ||
|
|
eb93dacba0 | ||
|
|
1e79cbc945 | ||
|
|
d350bd31cb | ||
|
|
89b9b4e9b7 | ||
|
|
9549e5aa4c | ||
|
|
1517d0e40b | ||
|
|
f13593aac7 | ||
|
|
b8023b2444 | ||
|
|
bb49098a05 | ||
|
|
c368fbcda9 | ||
|
|
1c1bf86e9a | ||
|
|
5f1b190ba9 | ||
|
|
0860f53d68 | ||
|
|
1ffd1cb6e7 | ||
|
|
030d230312 | ||
|
|
974f9d52a4 | ||
|
|
1a238ea1b1 | ||
|
|
a049441048 | ||
|
|
20e771f121 | ||
|
|
cff4e225e6 | ||
|
|
2c69f539ae | ||
|
|
548081471c | ||
|
|
3688cfa397 | ||
|
|
20b2856dee | ||
|
|
7e94e46459 | ||
|
|
f3b4efb661 | ||
|
|
7947ba2442 | ||
|
|
ef63e993d2 | ||
|
|
29ae5b7aae | ||
|
|
6baf7e0b35 | ||
|
|
2e7ece17d7 | ||
|
|
2a56f21d13 | ||
|
|
d253c3c6c5 | ||
|
|
289a838ba1 | ||
|
|
f6f3e96106 | ||
|
|
7d22305678 | ||
|
|
7571d1d562 | ||
|
|
b4c06f537d | ||
|
|
830c723bae | ||
|
|
b83ab07374 | ||
|
|
8200cb9336 | ||
|
|
b1a38306af | ||
|
|
6ec0f4782f | ||
|
|
d25f9bcf1b | ||
|
|
cd3fd5a7b7 | ||
|
|
1ab1378259 | ||
|
|
21f87ade2d | ||
|
|
8f54e5739f | ||
|
|
21a6594573 | ||
|
|
01ce80fb1a | ||
|
|
174672c411 | ||
|
|
4785103081 | ||
|
|
7030422b11 | ||
|
|
2a06dc9bea | ||
|
|
66927043de | ||
|
|
7d16bafd52 | ||
|
|
ee340e2d6f | ||
|
|
66d9fd1a5e | ||
|
|
7c28de6ae5 | ||
|
|
c95f602ea2 | ||
|
|
6361280d99 | ||
|
|
b3a9d7cd41 | ||
|
|
071989c554 | ||
|
|
f956d7113f | ||
|
|
f0472fdd76 | ||
|
|
01899676a4 | ||
|
|
b13d572ca8 | ||
|
|
a588e08405 | ||
|
|
6c93483220 | ||
|
|
733c13102b | ||
|
|
688d6fe17a | ||
|
|
25ff1f230f | ||
|
|
094b58130a | ||
|
|
d9188e76ed |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,6 +7,8 @@ vendor/
|
||||
.sass-cache
|
||||
|
||||
# Grav Specific
|
||||
backup/*
|
||||
!backup/.*
|
||||
cache/*
|
||||
!cache/.*
|
||||
assets/*
|
||||
|
||||
@@ -44,7 +44,7 @@ RewriteRule .* index.php [L]
|
||||
|
||||
## Begin - Security
|
||||
# Block all direct access for these folders
|
||||
RewriteRule ^(cache|bin|logs)/(.*) error [L]
|
||||
RewriteRule ^(cache|bin|logs|backup)/(.*) error [L]
|
||||
# Block access to specific file types for these folders
|
||||
RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L]
|
||||
## End - Security
|
||||
|
||||
78
.travis.yml
Normal file
78
.travis.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
language: php
|
||||
php: 5.6
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- build_test
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: always
|
||||
hipchat:
|
||||
# hipchat_api@grav
|
||||
rooms:
|
||||
- secure: "bqO0wM1B7bJnQw2fuhquSXEqI9gw6WmFytIh9sEWXzbYTzTUP5t0PcKOd3FT2BNMRaDxPJLVl+vG/oqmqDUBkEmOGcG504IQjeNzZqnMz0tXQMIcCc22Las9tFfc4Jf6RVi/qGomFtHGE9Wgii+TAN4zqZaufbNjwd8SyjO0+W8="
|
||||
template:
|
||||
- '%{repository}#%{build_number} (%{branch}): Grav Core and Skeletons packages have been uploaded. (<a href="%{build_url}">Details</a>)'
|
||||
format: html
|
||||
env:
|
||||
global:
|
||||
# Colors!
|
||||
- TEXTRESET=$(tput sgr0) # reset the foreground colour
|
||||
- RED=$(tput setaf 1)
|
||||
- GREEN=$(tput setaf 2)
|
||||
- YELLOW=$(tput setaf 3)
|
||||
- BLUE=$(tput setaf 4)
|
||||
- BOLD=$(tput bold)
|
||||
# User
|
||||
- GH_USER="getgrav"
|
||||
# Paths
|
||||
- RT_DEVTOOLS=$HOME/devtools
|
||||
- GOPATH="$HOME/go"
|
||||
- PATH="$GOPATH/bin:$PATH"
|
||||
# GH_TOKEN [API Key]
|
||||
- secure: "jS+c+g2v33vypG4VtqiSDW2qQ4dGJZlrUKBRCztoy1yrOrYRPvc5Vzi/AS3fDmZ4yizukEwmUNNzyZQcgFvLPpmCCml46Dovp8R9OXhbNe8OnULmaSn2Zkr71oblMYu6ZP+RpYvNq0BIdSB3u2TiFriHMiTIkX9UwZNaUCOX1ig="
|
||||
# BB_TOKEN value => "user:pass@"
|
||||
- secure: "einUtSEkUWy2IrqLXyVjwUU+mwaaoiOXRRVdLBpA3Zye6bZx8cm5h/5AplkPWhM/NmCJoW/MwNZHHkFhlr3mDRov5iOxVmTTYfnXB+I5lxYTSgduOLLErS7mU8hfADpVDU8bHNU44fNGD3UEiG1PD4qQBX4DMlqIFmR20mjs81k="
|
||||
# GH_API_USER [for curl]
|
||||
- secure: "Xbk/V9aIys0NxccJGR3Zrm2GRxDnA0RuazBs1puIboTYDhbi0Z7JTL+mOx3xp5Kfoniad/xAuijQESTM9MMrKqq/qCzhAMaC1+vcL4pCHZH4NSG6DBxB9BPkKVFq+1llu5FTEf8bkxHzwGR0l1ARW6TVRcgTHr5B58bCEIwEOrI="
|
||||
# Latest Release version
|
||||
- TRAVIS_TAG=$(curl --fail --user ${GH_API_USER} -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4)
|
||||
|
||||
before_install:
|
||||
- composer self-update
|
||||
- go get github.com/aktau/github-release
|
||||
- git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
|
||||
- if [ ! -z "$TRAVIS_TAG" ]; then
|
||||
cd "${RT_DEVTOOLS}";
|
||||
./build-grav.sh skeletons.txt;
|
||||
fi
|
||||
script:
|
||||
- if [ ! -z "$TRAVIS_TAG" ]; then
|
||||
FILES="$RT_DEVTOOLS/grav-dist/*.zip";
|
||||
for file in ${FILES[@]}; do
|
||||
NAME=${file##*/};
|
||||
REPO="$(echo ${NAME} | rev | cut -f 2- -d "-" | rev)";
|
||||
if [[ $REPO == 'grav' || $REPO == 'grav-update' ]]; then
|
||||
REPO="grav";
|
||||
fi;
|
||||
API="$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/${GH_USER}/${REPO}/releases/latest)";
|
||||
ASSETS="$(echo "${API}" | node gh-assets.js)";
|
||||
TAG="$(echo "${API}" | grep tag_name | head -n 1 | cut -d '"' -f 4)";
|
||||
if [ $REPO == "grav" ]; then
|
||||
TAG=$TRAVIS_TAG;
|
||||
fi;
|
||||
if [ ! -z "$ASSETS" ]; then
|
||||
for asset in ${ASSETS[@]}; do
|
||||
asset_id=$(echo ${asset} | cut -d ':' -f 1);
|
||||
asset_name=$(echo ${asset} | cut -d ':' -f 2);
|
||||
if [ "${NAME}" == "${asset_name}" ]; then
|
||||
echo -e "\nAsset ${BOLD}${BLUE}${NAME}${TEXTRESET} already exists in ${YELLOW}${REPO}${TEXTRESET}@${BOLD}${YELLOW}${TAG}${TEXTRESET}... deleting id ${BOLD}${RED}${asset_id}${TEXTRESET}...";
|
||||
curl -X DELETE --fail --user "${GH_API_USER}" "https://api.github.com/repos/${GH_USER}/${REPO}/releases/assets/${asset_id}";
|
||||
fi;
|
||||
done;
|
||||
fi;
|
||||
echo "Uploading package ${BOLD}${BLUE}${NAME}${TEXTRESET} to ${YELLOW}${REPO}${TEXTRESET}@${YELLOW}${TAG}${TEXTRESET}";
|
||||
github-release upload --security-token $GH_TOKEN --user ${GH_USER} --repo $REPO --tag $TAG --name "$NAME" --file "$file";
|
||||
done;
|
||||
fi
|
||||
235
CHANGELOG.md
235
CHANGELOG.md
@@ -1,3 +1,230 @@
|
||||
# v0.9.35
|
||||
## 08/06/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added `body_classes` field
|
||||
* Added `visiblity` toggle and help tooltips on new page form
|
||||
* Added new `Page.unsetRoute()` method to allow admin to regenerate the route
|
||||
1. [](#improved)
|
||||
* User save no longer stores username each time
|
||||
* Page list form field now shows all pages except root
|
||||
* Removed required option from page title
|
||||
* Added configuration settings for running Nginx in sub directory
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with GPM and cURL throwing `Undefined offset: 1` error
|
||||
* Fixed deep translation merging
|
||||
* Fixed broken **metadata** merging with site defaults
|
||||
* Fixed broken **summary** field
|
||||
* Fixed broken robots field
|
||||
* Fixed GPM issue when using cURL, throwing an `Undefined offset: 1` exception
|
||||
* Removed duplicate hidden page `type` field
|
||||
|
||||
# v0.9.34
|
||||
## 08/04/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added new `cache_all` system setting + media `cache()` method
|
||||
* Added base languages configuration
|
||||
* Added property language to page to help plugins identify page language
|
||||
* New `Utils::arrayFilterRecursive()` method
|
||||
2. [](#improved)
|
||||
* Improved Session handling to support site and admin independently
|
||||
* Allow Twig variables to be modified in other events
|
||||
* Blueprint updates in preparation for Admin plugin
|
||||
* Changed `Inflector` from static to object and added multi-language support
|
||||
* Support for admin override of a page's blueprints
|
||||
3. [](#bugfix)
|
||||
* Removed unused `use` in `VideoMedium` that was causing error
|
||||
* Array fix in `User.authorise()` method
|
||||
* Fix for typo in `translations_fallback`
|
||||
* Fixed moving page to the root
|
||||
|
||||
# v0.9.33
|
||||
## 07/21/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added new `onImageMediumSaved()` event (useful for post-image processing)
|
||||
* Added `Vary: Accept-Encoding` option
|
||||
2. [](#improved)
|
||||
* Multilang-safe delimeter position
|
||||
* Refactored Twig classes and added optional umask setting
|
||||
* Removed `pageinit()` timing
|
||||
* `Page->routable()` now takes `published()` state into account
|
||||
* Improved how page extension is set
|
||||
* Support `Language->translate()` method taking string and array
|
||||
3. [](#bugfix)
|
||||
* Fixed `backup` command to include empty folders
|
||||
|
||||
# v0.9.32
|
||||
## 07/14/2015
|
||||
|
||||
1. [](#new)
|
||||
* Detect users preferred language via `http_accept_language` setting
|
||||
* Added new `translateArray()` language method
|
||||
2. [](#improved)
|
||||
* Support `en` translations by default for plugins & themes
|
||||
* Improved default generator tag
|
||||
* Minor language tweaks and fixes
|
||||
3. [](#bugfix)
|
||||
* Fix for session active language and homepage redirects
|
||||
* Ignore root-level page rather than throwing error
|
||||
|
||||
# v0.9.31
|
||||
## 07/09/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added xml, json, css and js to valid media file types
|
||||
2. [](#improved)
|
||||
* Better handling of unsupported media type downloads
|
||||
* Improved `bin/grav backup` command to mimic admin plugin location/name
|
||||
3. [](#bugfix)
|
||||
* Critical fix for broken language translations
|
||||
* Fix for Twig markdown filter error
|
||||
* Safety check for download extension
|
||||
|
||||
# v0.9.30
|
||||
## 07/08/2015
|
||||
|
||||
1. [](#new)
|
||||
* BIG NEWS! Extensive Multi-Language support is all new in 0.9.30!
|
||||
* Translation support via Twig filter/function and PHP method
|
||||
* Page specific default route
|
||||
* Page specific route aliases
|
||||
* Canonical URL route support
|
||||
* Added built-in session support
|
||||
* New `Page.rawRoute()` to get a consistent folder-based route to a page
|
||||
* Added option to always redirect to default page on alias URL
|
||||
* Added language safe redirect function for use in core and plugins
|
||||
2. [](#improved)
|
||||
* Improved `Page.active()` and `Page.activeChild()` methods to support route aliases
|
||||
* Various spelling corrections in `.php` comments, `.md` and `.yaml` files
|
||||
* `Utils::startsWith()` and `Utils::endsWith()` now support needle arrays
|
||||
* Added a new timer around `pageInitialized` event
|
||||
* Updated jQuery library to v2.1.4
|
||||
3. [](#bugfix)
|
||||
* In-page CSS and JS files are now handled properly
|
||||
* Fix for `enable_media_timestamp` not working properly
|
||||
|
||||
# v0.9.29
|
||||
## 06/22/2015
|
||||
|
||||
1. [](#new)
|
||||
* New and improved Regex-powered redirect and route alias logic
|
||||
* Added new `onBuildPagesInitialized` event for memory critical or time-consuming plugins
|
||||
* Added a `setSummary()` method for pages
|
||||
2. [](#improved)
|
||||
* Improved `MergeConfig()` logic for more control
|
||||
* Travis skeleton build trigger implemented
|
||||
* Set composer.json versions to stable versions where possible
|
||||
* Disabled `last_modified` and `etag` page headers by default (causing too much page caching)
|
||||
3. [](#bugfix)
|
||||
* Preload classes during `bin/gpm selfupgrade` to avoid issues with updated classes
|
||||
* Fix for directory relative _down_ links
|
||||
|
||||
# v0.9.28
|
||||
## 06/16/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added method to set raw markdown on a page
|
||||
* Added ability to enabled system and page level `etag` and `last_modified` headers
|
||||
2. [](#improved)
|
||||
* Improved image path processing
|
||||
* Improved query string handling
|
||||
* Optimization to image handling supporting URL encoded filenames
|
||||
* Use global `composer` when available rather than Grv provided one
|
||||
* Use `PHP_BINARY` contant rather than `php` executable
|
||||
* Updated Doctrine Cache library
|
||||
* Updated Symfony libraries
|
||||
* Moved `convertUrl()` method to Uri object
|
||||
3. [](#bugfix)
|
||||
* Fix incorrect slug causing problems with CLI `uninstall`
|
||||
* Fix Twig runtime error with assets pipeline in sufolder installations
|
||||
* Fix for `+` in image filenames
|
||||
* Fix for dot files causing issues with page processing
|
||||
* Fix for Uri path detection on Windows platform
|
||||
* Fix for alternative media resolutions
|
||||
* Fix for modularTypes key properties
|
||||
|
||||
# v0.9.27
|
||||
## 05/09/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added new composer CLI command
|
||||
* Added page-level summary header overrides
|
||||
* Added `size` back for Media objects
|
||||
* Refactored Backup command in preparation for admin plugin
|
||||
* Added a new `parseLinks` method to Plugins class
|
||||
* Added `starts_with` and `ends_with` Twig filters
|
||||
2. [](#improved)
|
||||
* Optimized install of vendor libraries for speed improvement
|
||||
* Improved configuration handling in preparation for admin plugin
|
||||
* Cache optimization: Don't cache Twig templates when you pass dynamic params
|
||||
* Moved `Utils::rcopy` to `Folder::rcopy`
|
||||
* Improved `Folder::doDelete`
|
||||
* Added check for required Curl in GPM
|
||||
* Updated included composer.phar to latest version
|
||||
* Various blueprint fixes for admin plugin
|
||||
* Various PSR and code cleanup tasks
|
||||
3. [](#bugfix)
|
||||
* Fix issue with Gzip not working with `onShutDown()` event
|
||||
* Fix for URLs with trailing slashes
|
||||
* Handle condition where certain errors resulted in blank page
|
||||
* Fix for issue with theme name equal to base_url and asset pipeline
|
||||
* Fix to properly normalize font rewrite path
|
||||
* Fix for absolute URLs below the current page
|
||||
* Fix for `..` page references
|
||||
|
||||
# v0.9.26
|
||||
## 04/24/2015
|
||||
|
||||
3. [](#bugfix)
|
||||
* Fixed issue with homepage routes failing with 'dirname' error
|
||||
|
||||
# v0.9.25
|
||||
## 04/24/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added support for E-Tag, Last-Modified, Cache-Control and Page-based expires headers
|
||||
2. [](#improved)
|
||||
* Refactored media image handling to make it more flexible and support absolute paths
|
||||
* Refactored page modification check process to make it faster
|
||||
* User account improvements in preparation for admin plugin
|
||||
* Protect against timing attacks
|
||||
* Reset default system expires time to 0 seconds (can override if you need to)
|
||||
3. [](#bugfix)
|
||||
* Fix issues with spaces in webroot when using `bin/grav install`
|
||||
* Fix for spaces in relative directory
|
||||
* Bug fix in collection filtering
|
||||
|
||||
# v0.9.24
|
||||
## 04/15/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added support for chunked downloads of Assets
|
||||
* Added new `onBeforeDownload()` event
|
||||
* Added new `download()` and `getMimeType()` methods to Utils class
|
||||
* Added configuration option for supported page types
|
||||
* Added assets and media timestamp options (off by default)
|
||||
* Added page expires configuration option
|
||||
2. [](#bugfix)
|
||||
* Fixed issue with Nginx/Gzip and `ob_flush()` throwing error
|
||||
* Fixed assets actions on 'direct media' URLs
|
||||
* Fix for 'direct assets` with any parameters
|
||||
|
||||
# v0.9.23
|
||||
## 04/09/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for broken GPM `selfupgrade` (Grav 0.9.21 and 0.9.22 will need to manually upgrade to this version)
|
||||
|
||||
# v0.9.22
|
||||
## 04/08/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix to normalize GRAV_ROOT path for Windows
|
||||
* Fix to normalize Media image paths for Windows
|
||||
* Fix for GPM `selfupgrade` when you are on latest version
|
||||
|
||||
# v0.9.21
|
||||
## 04/07/2015
|
||||
|
||||
@@ -89,7 +316,7 @@
|
||||
* 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
|
||||
* Added a new `disable()` method to Plugin class to programmatically disable a plugin
|
||||
* Updated Parsedown and Parsedown Extra to address bugs
|
||||
* Various PSR fixes
|
||||
3. [](#bugfix)
|
||||
@@ -142,7 +369,7 @@
|
||||
* 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 dynamically control Cache lifetime programmatically
|
||||
* 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)
|
||||
@@ -317,7 +544,7 @@
|
||||
* Broke cache types out into multiple directories in the cache folder
|
||||
* Removed vendor libs from github repository
|
||||
* Various PSR cleanup of code
|
||||
* Various Blueprint updates to support upcoming Admin plugin
|
||||
* Various Blueprint updates to support upcoming admin plugin
|
||||
* Added ability to filter page children for normal/modular/all
|
||||
* Added `sort_by_key` twig filter
|
||||
* Added `visible()` and `routable()` filters to page collections
|
||||
@@ -390,7 +617,7 @@
|
||||
* Addition of Dependency Injection Container
|
||||
* Refactored plugins to use Symfony Event Dispatcher
|
||||
* New Asset Manager to provide unified management of JavaScript and CSS
|
||||
* Asset Pipelining to provide unification, minify, and optimazation of JavaScript and CSS
|
||||
* Asset Pipelining to provide unification, minify, and optimization of JavaScript and CSS
|
||||
* Grav Media support directly in Markdown syntax
|
||||
* Additional Grav Generator meta tag in default themes
|
||||
* Added support for PHP Stream Wrapper for resource location
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
[](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)
|
||||
|
||||
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.
|
||||
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 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:
|
||||
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
|
||||
|
||||
0
backup/.gitkeep
Normal file
0
backup/.gitkeep
Normal file
Binary file not shown.
13
bin/gpm
13
bin/gpm
@@ -6,10 +6,17 @@ if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
|
||||
}
|
||||
|
||||
use Grav\Common\Composer;
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
$composer = Composer::getComposerExecutor();
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
|
||||
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install');
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
@@ -26,6 +33,10 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
|
||||
if (!function_exists('curl_version')) {
|
||||
exit('FATAL: GPM requires PHP Curl module to be installed');
|
||||
}
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav['config']->init();
|
||||
$grav['streams'];
|
||||
|
||||
10
bin/grav
10
bin/grav
@@ -6,10 +6,17 @@ if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
|
||||
}
|
||||
|
||||
use Grav\Common\Composer;
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
$composer = Composer::getComposerExecutor();
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
|
||||
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install');
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
@@ -28,6 +35,7 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
$app = new Application('Grav CLI Application', '0.1.0');
|
||||
$app->addCommands(array(
|
||||
new Grav\Console\Cli\InstallCommand(),
|
||||
new Grav\Console\Cli\ComposerCommand(),
|
||||
new Grav\Console\Cli\SandboxCommand(),
|
||||
new Grav\Console\Cli\CleanCommand(),
|
||||
new Grav\Console\Cli\ClearCacheCommand(),
|
||||
|
||||
@@ -9,27 +9,20 @@
|
||||
"php": ">=5.4.0",
|
||||
"twig/twig": "~1.16",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~2.6",
|
||||
"symfony/console": "~2.6",
|
||||
"symfony/event-dispatcher": "~2.6",
|
||||
"doctrine/cache": "~1.3",
|
||||
"symfony/yaml": "2.7.3",
|
||||
"symfony/console": "2.7.3",
|
||||
"symfony/event-dispatcher": "2.7.3",
|
||||
"doctrine/cache": "~1.4",
|
||||
"maximebf/debugbar": "dev-master",
|
||||
"filp/whoops": "1.2.*@dev",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "~2.0",
|
||||
"gregwar/image": "~2.0",
|
||||
"ircmaxell/password-compat": "1.0.*",
|
||||
"mrclay/minify": "dev-master",
|
||||
"donatj/phpuseragentparser": "dev-master",
|
||||
"mrclay/minify": "~2.2",
|
||||
"donatj/phpuseragentparser": "~0.3",
|
||||
"pimple/pimple": "~3.0",
|
||||
"rockettheme/toolbox": "dev-develop"
|
||||
"rockettheme/toolbox": "1.0.*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"no-api": true,
|
||||
"url": "https://github.com/rockettheme/toolbox"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\": "system/src/Grav"
|
||||
|
||||
21
nginx.conf
21
nginx.conf
@@ -25,7 +25,22 @@ http {
|
||||
index index.php;
|
||||
if (!-e $request_filename){ rewrite ^(.*)$ /index.php last; }
|
||||
}
|
||||
|
||||
|
||||
# if you want grav in a sub-directory of your main site
|
||||
# (for example, example.com/mygrav) then you need this rewrite:
|
||||
location /mygrav {
|
||||
index index.php;
|
||||
if (!-e $request_filename){ rewrite ^(.*)$ /mygrav/$2 last; }
|
||||
try_files $uri $uri/ /index.php?$args;
|
||||
}
|
||||
|
||||
# if using grav in a sub-directory of your site,
|
||||
# prepend the actual path to each location
|
||||
# for example: /mygrav/images
|
||||
# and: /mygrav/user
|
||||
# and: /mygrav/cache
|
||||
# and so on
|
||||
|
||||
location /images/ {
|
||||
# Serve images as static
|
||||
}
|
||||
@@ -44,6 +59,10 @@ http {
|
||||
rewrite ^/bin/(.*)$ /error redirect;
|
||||
}
|
||||
|
||||
location /backup {
|
||||
rewrite ^/backup/(.*) /error redirect;
|
||||
}
|
||||
|
||||
location /system {
|
||||
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
|
||||
}
|
||||
|
||||
4
system/assets/jquery/jquery-2.1.3.min.js
vendored
4
system/assets/jquery/jquery-2.1.3.min.js
vendored
File diff suppressed because one or more lines are too long
4
system/assets/jquery/jquery-2.1.4.min.js
vendored
Normal file
4
system/assets/jquery/jquery-2.1.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -13,58 +13,101 @@ form:
|
||||
label: Site Title
|
||||
size: large
|
||||
placeholder: "Site wide title"
|
||||
help: Default title for your site
|
||||
help: "Default title for your site, often used in themes"
|
||||
|
||||
author.name:
|
||||
type: text
|
||||
size: large
|
||||
label: Default Author
|
||||
help: "A default author name, often used in themes or page content"
|
||||
|
||||
author.email:
|
||||
type: text
|
||||
size: large
|
||||
label: Default Email
|
||||
help: "A default email to reference in themes or pages"
|
||||
validate:
|
||||
type: email
|
||||
|
||||
taxonomies:
|
||||
type: text
|
||||
type: selectize
|
||||
size: large
|
||||
label: Taxonomy Types
|
||||
classes: fancy
|
||||
help: "Taxonomy types must be defined here if you wish to use them in pages"
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
metadata:
|
||||
type: array
|
||||
label: Metadata
|
||||
placeholder_key: Name
|
||||
placeholder_value: Content
|
||||
|
||||
blog:
|
||||
summary:
|
||||
type: section
|
||||
title: Blog
|
||||
title: Page Summary
|
||||
|
||||
fields:
|
||||
blog.route:
|
||||
type: text
|
||||
size: large
|
||||
label: Blog URL
|
||||
summary.enabled:
|
||||
type: toggle
|
||||
label: Enabled
|
||||
highlight: 1
|
||||
help: "Enable page summary (the summary returns the same as the page content)"
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
summary.size:
|
||||
type: text
|
||||
size: x-small
|
||||
label: Summary Size
|
||||
help: "The amount of characters of a page to use as a content summary"
|
||||
validate:
|
||||
type: int
|
||||
min: 0
|
||||
max: 65536
|
||||
|
||||
routes:
|
||||
summary.format:
|
||||
type: toggle
|
||||
label: Format
|
||||
classes: fancy
|
||||
help: "short = use the first occurrence of delimiter or size; long = summary delimiter will be ignored"
|
||||
highlight: short
|
||||
options:
|
||||
'short': 'Short'
|
||||
'long': 'Long'
|
||||
|
||||
summary.delimiter:
|
||||
type: text
|
||||
size: x-small
|
||||
label: Delimiter
|
||||
help: "The summary delimiter (default '===')"
|
||||
|
||||
metadata:
|
||||
type: section
|
||||
title: Routes
|
||||
title: Metadata
|
||||
|
||||
fields:
|
||||
metadata:
|
||||
type: array
|
||||
label: Metadata
|
||||
help: "Default metadata values that will be displayed on every page unless overridden by the page"
|
||||
placeholder_key: Name
|
||||
placeholder_value: Content
|
||||
|
||||
|
||||
routes:
|
||||
type: section
|
||||
title: Redirects & Routes
|
||||
|
||||
fields:
|
||||
redirects:
|
||||
type: array
|
||||
label: Custom Redirects
|
||||
help: "routes to redirect to other pages. Standard Regex replacement is valid"
|
||||
placeholder_key: /your/alias
|
||||
placeholder_value: /your/redirect
|
||||
|
||||
routes:
|
||||
type: array
|
||||
label: Custom
|
||||
label: Custom Routes
|
||||
help: "routes to alias to other pages. Standard Regex replacement is valid"
|
||||
placeholder_key: /your/alias
|
||||
placeholder_value: /your/route
|
||||
|
||||
@@ -14,7 +14,7 @@ form:
|
||||
type: pages
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Home Page
|
||||
label: Home page
|
||||
show_all: false
|
||||
show_modular: false
|
||||
show_root: false
|
||||
@@ -23,61 +23,65 @@ form:
|
||||
pages.theme:
|
||||
type: themeselect
|
||||
classes: fancy
|
||||
selectize: true
|
||||
size: medium
|
||||
label: Default Theme
|
||||
help: "Set the theme (defaults to 'default')"
|
||||
|
||||
pages.markdown_extra:
|
||||
type: toggle
|
||||
label: Markdown Extra
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
label: Default theme
|
||||
help: "Set the default theme for Grav to use (default is Antimatter)"
|
||||
|
||||
pages.process:
|
||||
type: checkboxes
|
||||
label: Process
|
||||
help: "Control how pages are processed. Can be set per-page rather than globally"
|
||||
default: [markdown: true, twig: true]
|
||||
options:
|
||||
markdown: Markdown
|
||||
twig: Twig
|
||||
use: keys
|
||||
|
||||
timezone:
|
||||
type: select
|
||||
label: Timezone
|
||||
size: medium
|
||||
classes: fancy
|
||||
help: "Override the default timezone the server"
|
||||
@data-options: '\Grav\Common\Utils::timezones'
|
||||
default: ''
|
||||
options:
|
||||
'': 'Default (Server Timezone)'
|
||||
|
||||
pages.dateformat.short:
|
||||
type: select
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Short Date Format
|
||||
help: "Set the short date format"
|
||||
default: 'jS M Y'
|
||||
label: Short date format
|
||||
help: "Set the short date format that can be used by themes"
|
||||
default: "jS M Y"
|
||||
options:
|
||||
'F jS \\a\\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
"F jS \\a\\t g:ia": "January 1st at 11:59pm"
|
||||
"l jS of F g:i A": "Monday 1st of January at 11:59 PM"
|
||||
"D, m M Y G:i:s": "Mon, 01 Jan 2014 23:59:00"
|
||||
"d-m-y G:i": "01-01-14 23:59"
|
||||
"jS M Y": "10th Feb 2014"
|
||||
|
||||
pages.dateformat.long:
|
||||
type: select
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Long Date Format
|
||||
help: "Set the long date format"
|
||||
label: Long date format
|
||||
help: "Set the long date format that can be used by themes"
|
||||
options:
|
||||
'F jS \a\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
"F jS \\a\\t g:ia": "January 1st at 11:59pm"
|
||||
"l jS of F g:i A": "Monday 1st of January at 11:59 PM"
|
||||
"D, m M Y G:i:s": "Mon, 01 Jan 2014 23:59:00"
|
||||
"d-m-y G:i": "01-01-14 23:59"
|
||||
"jS M Y": "10th Feb 2014"
|
||||
|
||||
pages.order.by:
|
||||
type: select
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: Default Ordering
|
||||
label: Default ordering
|
||||
help: "Pages in a list will render using this order unless it is overridden"
|
||||
options:
|
||||
default: Default - based on folder name
|
||||
folder: Folder - based on prefix-less folder name
|
||||
@@ -86,9 +90,10 @@ form:
|
||||
|
||||
pages.order.dir:
|
||||
type: toggle
|
||||
label: Default Order Direction
|
||||
label: Default order direction
|
||||
highlight: asc
|
||||
default: desc
|
||||
help: "The direction of pages in a list"
|
||||
options:
|
||||
asc: Ascending
|
||||
desc: Descending
|
||||
@@ -96,23 +101,16 @@ form:
|
||||
pages.list.count:
|
||||
type: text
|
||||
size: x-small
|
||||
label: Default Item Count
|
||||
help: "Default max pages count"
|
||||
label: Default page count
|
||||
help: "Default maximum pages count in a list"
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
|
||||
|
||||
|
||||
events:
|
||||
type: section
|
||||
title: Events
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
pages.events.page:
|
||||
pages.publish_dates:
|
||||
type: toggle
|
||||
label: Page events
|
||||
label: Date-based publishing
|
||||
help: "Automatically (un)publish posts based on their date"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -120,9 +118,47 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.events.twig:
|
||||
pages.events:
|
||||
type: checkboxes
|
||||
label: Events
|
||||
help: "Enable or Disable specific events. Disabling these can break plugins"
|
||||
default: [page: true, twig: true]
|
||||
options:
|
||||
page: Page Events
|
||||
twig: Twig Events
|
||||
use: keys
|
||||
|
||||
pages.redirect_default_route:
|
||||
type: toggle
|
||||
label: Twig events
|
||||
label: Redirect default route
|
||||
help: "Automatically redirect to a page's default route"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
languages:
|
||||
type: section
|
||||
title: Languages
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
|
||||
languages.supported:
|
||||
type: selectize
|
||||
size: large
|
||||
label: Supported
|
||||
help: "Comma separated list of 2 letter language codes (for example 'en,fr,de')"
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
languages.translations:
|
||||
type: toggle
|
||||
label: Translations enabled
|
||||
help: "Support translations in Grav, plugins and extensions"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -130,6 +166,142 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
languages.translations_fallback:
|
||||
type: toggle
|
||||
label: Translations fallback
|
||||
help: "Fallback through supported translations if active language doesn't exist"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
languages.session_store_active:
|
||||
type: toggle
|
||||
label: Active language in session
|
||||
help: "Support translations in Grav, plugins and extensions"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
languages.home_redirect.include_lang:
|
||||
type: toggle
|
||||
label: Home redirect include language
|
||||
help: "Include language in home redirect (/en)"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
languages.home_redirect.include_route:
|
||||
type: toggle
|
||||
label: Home redirect include route
|
||||
help: "Include route in home redirect (/blog)"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
http_headers:
|
||||
type: section
|
||||
title: HTTP Headers
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
pages.expires:
|
||||
type: text
|
||||
size: small
|
||||
label: Expires
|
||||
help: "Sets the expires header. The value is in seconds."
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
pages.last_modified:
|
||||
type: toggle
|
||||
label: Last modified
|
||||
help: "Sets the last modified header that can help optimize proxy and browser caching"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
pages.etag:
|
||||
type: toggle
|
||||
label: ETag
|
||||
help: "Sets the etag header to help identify when a page has been modified"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
pages.vary_accept_encoding:
|
||||
type: toggle
|
||||
label: Vary accept encoding
|
||||
help: "Sets the `Vary: Accept Encoding` header to help with proxy and CDN caching"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
markdown:
|
||||
type: section
|
||||
title: Markdown
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
pages.markdown.extra:
|
||||
type: toggle
|
||||
label: Markdown extra
|
||||
help: "Enable default support for Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
pages.markdown.auto_line_breaks:
|
||||
type: toggle
|
||||
label: Auto line breaks
|
||||
help: "Enable support for automatic line breaks in markdown"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
pages.markdown.auto_url_links:
|
||||
type: toggle
|
||||
label: Auto URL links
|
||||
help: "Enable automatic conversion of URLs into HTML hyperlinks"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
pages.markdown.escape_markup:
|
||||
type: toggle
|
||||
label: Escape markup
|
||||
help: "Escape markup tags into HTML entities"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
caching:
|
||||
type: section
|
||||
title: Caching
|
||||
@@ -139,6 +311,7 @@ form:
|
||||
cache.enabled:
|
||||
type: toggle
|
||||
label: Caching
|
||||
help: "Global ON/OFF switch to enable/disable Grav caching"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -150,7 +323,8 @@ form:
|
||||
type: select
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Cache Check Method
|
||||
label: Cache check method
|
||||
help: "Select the method that Grav uses to check if page files have been modified."
|
||||
options:
|
||||
file: File
|
||||
folder: Folder
|
||||
@@ -161,6 +335,7 @@ form:
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Cache driver
|
||||
help: "Choose which cache driver Grav should use. 'Auto Detect' attempts to find the best for you"
|
||||
options:
|
||||
auto: Auto detect
|
||||
file: File
|
||||
@@ -172,9 +347,30 @@ form:
|
||||
cache.prefix:
|
||||
type: text
|
||||
size: x-small
|
||||
label: Cache Prefix
|
||||
label: Cache prefix
|
||||
help: "An identifier for part of the Grav key. Don't change unless you know what your doing."
|
||||
placeholder: "Derived from base URL (override by entering random string)"
|
||||
|
||||
cache.lifetime:
|
||||
type: text
|
||||
size: small
|
||||
label: Lifetime
|
||||
help: "Sets the cache lifetime in seconds. 0 = infinite"
|
||||
validate:
|
||||
type: number
|
||||
|
||||
cache.gzip:
|
||||
type: toggle
|
||||
label: Gzip compression
|
||||
help: "Enable GZip compression of the Grav page for increased performance."
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
twig:
|
||||
type: section
|
||||
title: Twig Templating
|
||||
@@ -184,6 +380,7 @@ form:
|
||||
twig.cache:
|
||||
type: toggle
|
||||
label: Twig caching
|
||||
help: "Control the Twig caching mechanism. Leave this enabled for best performance."
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -194,7 +391,8 @@ form:
|
||||
twig.debug:
|
||||
type: toggle
|
||||
label: Twig debug
|
||||
highlight: 1
|
||||
help: "Allows the option of not loading the Twig Debugger extension"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
@@ -204,6 +402,7 @@ form:
|
||||
twig.auto_reload:
|
||||
type: toggle
|
||||
label: Detect changes
|
||||
help: "Twig will automatically recompile the Twig cache if it detects any changes in Twig templates"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -214,7 +413,8 @@ form:
|
||||
twig.autoescape:
|
||||
type: toggle
|
||||
label: Autoescape variables
|
||||
highlight: 1
|
||||
help: "Autoescapes all variables. This will break your site most likely"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
@@ -229,8 +429,9 @@ form:
|
||||
fields:
|
||||
assets.css_pipeline:
|
||||
type: toggle
|
||||
label: CSS Pipeline
|
||||
highlight: 1
|
||||
label: CSS pipeline
|
||||
help: "The CSS pipeline is the unification of multiple CSS resources into one file"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
@@ -239,7 +440,8 @@ form:
|
||||
|
||||
assets.css_minify:
|
||||
type: toggle
|
||||
label: CSS Minify
|
||||
label: CSS minify
|
||||
help: "Minify the CSS during pipelining"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -249,8 +451,9 @@ form:
|
||||
|
||||
assets.css_minify_windows:
|
||||
type: toggle
|
||||
label: CSS Minify Windows Override
|
||||
highlight: 1
|
||||
label: CSS minify Windows override
|
||||
help: "Minify Override for Windows platforms. False by default due to ThreadStackSize"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
@@ -259,7 +462,8 @@ form:
|
||||
|
||||
assets.css_rewrite:
|
||||
type: toggle
|
||||
label: CSS Rewrite
|
||||
label: CSS rewrite
|
||||
help: "Rewrite any CSS relative URLs during pipelining"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -269,8 +473,9 @@ form:
|
||||
|
||||
assets.js_pipeline:
|
||||
type: toggle
|
||||
label: JavaScript Pipeline
|
||||
highlight: 01
|
||||
label: JavaScript pipeline
|
||||
help: "The JS pipeline is the unification of multiple JS resources into one file"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
@@ -279,7 +484,53 @@ form:
|
||||
|
||||
assets.js_minify:
|
||||
type: toggle
|
||||
label: JavaScript Minify
|
||||
label: JavaScript minify
|
||||
help: "Minify the JS during pipelining"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.enable_asset_timestamp:
|
||||
type: toggle
|
||||
label: Enable timestamps on assets
|
||||
help: "Enable asset timestamps"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.collections:
|
||||
type: array
|
||||
label: Collections
|
||||
placeholder_key: collection_name
|
||||
placeholder_value: collection_path
|
||||
|
||||
errors:
|
||||
type: section
|
||||
title: Error handler
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
errors.display:
|
||||
type: toggle
|
||||
label: Display errors
|
||||
help: "Display full backtrace-style error page"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
errors.log:
|
||||
type: toggle
|
||||
label: Log errors
|
||||
help: "Log errors to /logs folder"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -296,56 +547,18 @@ form:
|
||||
debugger.enabled:
|
||||
type: toggle
|
||||
label: Debugger
|
||||
highlight: 1
|
||||
help: "Enable Grav debugger and following settings"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.mode:
|
||||
type: select
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Mode
|
||||
options:
|
||||
detect: Auto-Detect
|
||||
development: Development
|
||||
production: Production
|
||||
|
||||
debugger.strict:
|
||||
debugger.twig:
|
||||
type: toggle
|
||||
label: Strict
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.max_depth:
|
||||
type: select
|
||||
size: small
|
||||
classes: fancy
|
||||
label: Detail Level
|
||||
placeholder: "How many nested levels to display for objects or arrays"
|
||||
options:
|
||||
1: 1 level
|
||||
2: 2 levels
|
||||
3: 3 levels
|
||||
4: 4 levels
|
||||
5: 5 levels
|
||||
6: 6 levels
|
||||
7: 7 levels
|
||||
8: 8 levels
|
||||
9: 9 levels
|
||||
10: 10 levels
|
||||
validate:
|
||||
type: number
|
||||
|
||||
debugger.log.enabled:
|
||||
type: toggle
|
||||
label: Logging
|
||||
label: Debug Twig
|
||||
help: "Enable debugging of Twig templates"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
@@ -355,10 +568,130 @@ form:
|
||||
|
||||
debugger.shutdown.close_connection:
|
||||
type: toggle
|
||||
label: Shutdown Close Connection
|
||||
label: Shutdown close connection
|
||||
help: "Close the connection before calling onShutdown(). false for debugging"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
media:
|
||||
type: section
|
||||
title: Media
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
images.default_image_quality:
|
||||
type: text
|
||||
label: Default image quality
|
||||
help: "Default image quality to use when resampling or caching images (85%)"
|
||||
classes: x-small
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
max: 100
|
||||
|
||||
images.cache_all:
|
||||
type: toggle
|
||||
label: Cache all images
|
||||
help: "Run all images through Grav's cache system even if they have no media manipulations"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
images.debug:
|
||||
type: toggle
|
||||
label: Image debug watermark
|
||||
help: "Show an overlay over images indicating the pixel depth of the image when working with retina for example"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
media.upload_limit:
|
||||
type: text
|
||||
label: File upload limit
|
||||
help: "Set maximum upload size in bytes (0 is unlimited)"
|
||||
classes: small
|
||||
validate:
|
||||
type: number
|
||||
|
||||
media.enable_media_timestamp:
|
||||
type: toggle
|
||||
label: Enable timestamps on media
|
||||
help: "Appends a timestamp based on last modified date to each media item"
|
||||
highlight: 0
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
session:
|
||||
type: section
|
||||
title: Session
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
session.enabled:
|
||||
type: toggle
|
||||
label: Enabled
|
||||
help: "Enable session support within Grav"
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
session.timeout:
|
||||
type: text
|
||||
size: small
|
||||
label: Timeout
|
||||
help: "Sets the session timeout in seconds"
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
|
||||
session.name:
|
||||
type: text
|
||||
size: small
|
||||
label: Name
|
||||
help: "An identifier used to form the name of the session cookie"
|
||||
|
||||
|
||||
advanced:
|
||||
type: section
|
||||
title: Advanced
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
absolute_urls:
|
||||
type: toggle
|
||||
label: Absolute URLs
|
||||
highlight: 0
|
||||
help: "Absolute or relative URLs for `base_url`"
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
|
||||
param_sep:
|
||||
type: select
|
||||
label: Parameter separator
|
||||
classes: fancy
|
||||
help: "Separater for passed parameters that can be changed for Apache on Windows"
|
||||
default: ''
|
||||
options:
|
||||
':': ': (default)'
|
||||
';': '; (for Apache running on Windows)'
|
||||
|
||||
282
system/blueprints/pages/default.yaml
Normal file
282
system/blueprints/pages/default.yaml
Normal file
@@ -0,0 +1,282 @@
|
||||
title: Default
|
||||
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
|
||||
fields:
|
||||
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
content:
|
||||
type: tab
|
||||
title: Content
|
||||
|
||||
fields:
|
||||
header.title:
|
||||
type: text
|
||||
style: vertical
|
||||
label: Title
|
||||
|
||||
content:
|
||||
type: markdown
|
||||
label: Content
|
||||
validate:
|
||||
type: textarea
|
||||
|
||||
uploads:
|
||||
type: uploads
|
||||
label: Page Media
|
||||
|
||||
options:
|
||||
type: tab
|
||||
title: Options
|
||||
|
||||
fields:
|
||||
|
||||
publishing:
|
||||
type: section
|
||||
title: Publishing
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
header.published:
|
||||
type: toggle
|
||||
label: Published
|
||||
help: "By default, a page is published unless you explicitly set published: false or via a publish_date being in the future, or unpublish_date in the past"
|
||||
highlight: 1
|
||||
size: medium
|
||||
options:
|
||||
'': Global
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.date:
|
||||
type: datetime
|
||||
label: Date
|
||||
toggleable: true
|
||||
help: "The date variable allows you to specifically set a date associated with this page."
|
||||
|
||||
|
||||
|
||||
header.published_date:
|
||||
type: datetime
|
||||
label: Published Date
|
||||
toggleable: true
|
||||
help: "Can provide a date to automatically trigger publication."
|
||||
|
||||
header.unpublished_date:
|
||||
type: datetime
|
||||
label: Unublished Date
|
||||
toggleable: true
|
||||
help: "can provide a date to automatically trigger un-publication."
|
||||
|
||||
|
||||
|
||||
meta:
|
||||
type: section
|
||||
title: Metadata
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
header.metadata.description:
|
||||
type: textarea
|
||||
toggleable: true
|
||||
label: Description
|
||||
default:
|
||||
validate:
|
||||
max: 155
|
||||
|
||||
header.metadata.keywords:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: Keywords
|
||||
validate:
|
||||
max: 120
|
||||
|
||||
header.metadata.author:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: Author
|
||||
validate:
|
||||
max: 120
|
||||
|
||||
header.metadata.robots:
|
||||
type: text
|
||||
toggleable: true
|
||||
label: Robots
|
||||
validate:
|
||||
max: 120
|
||||
|
||||
taxonomies:
|
||||
type: section
|
||||
title: Taxonomies
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
header.taxonomy:
|
||||
type: taxonomy
|
||||
label: Taxonomy
|
||||
multiple: true
|
||||
validate:
|
||||
type: array
|
||||
|
||||
advanced:
|
||||
type: tab
|
||||
title: Advanced
|
||||
|
||||
fields:
|
||||
columns:
|
||||
type: columns
|
||||
fields:
|
||||
column1:
|
||||
type: column
|
||||
fields:
|
||||
|
||||
settings:
|
||||
type: section
|
||||
title: Settings
|
||||
underline: true
|
||||
|
||||
header.body_classes:
|
||||
type: text
|
||||
label: Body Classes
|
||||
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder Name
|
||||
validate:
|
||||
type: slug
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
|
||||
type:
|
||||
type: templates
|
||||
classes: fancy
|
||||
label: Display Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
|
||||
|
||||
column2:
|
||||
type: column
|
||||
|
||||
fields:
|
||||
order:
|
||||
type: order
|
||||
label: Ordering
|
||||
sitemap:
|
||||
|
||||
overrides:
|
||||
type: section
|
||||
title: Overrides
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
|
||||
header.menu:
|
||||
type: text
|
||||
label: Menu
|
||||
toggleable: true
|
||||
help: "The string to be used in a menu. If not set, <b>Title</b> will be used."
|
||||
|
||||
header.slug:
|
||||
type: text
|
||||
label: Slug
|
||||
toggleable: true
|
||||
help: "The slug variable allows you to specifically set the page's portion of the URL"
|
||||
validate:
|
||||
message: A slug must contain only lowercase alphanumeric characters and dashes
|
||||
rule: slug
|
||||
|
||||
|
||||
|
||||
header.process:
|
||||
type: checkboxes
|
||||
label: Process
|
||||
toggleable: true
|
||||
@config-default: system.pages.process
|
||||
default:
|
||||
markdown: true
|
||||
twig: false
|
||||
options:
|
||||
markdown: Markdown
|
||||
twig: Twig
|
||||
use: keys
|
||||
|
||||
header.child_type:
|
||||
type: select
|
||||
toggleable: true
|
||||
label: Default Child Type
|
||||
default: default
|
||||
placeholder: Use Global
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
|
||||
header.visible:
|
||||
type: toggle
|
||||
label: Visible
|
||||
help: "Determines if a page is visible in the navigation."
|
||||
highlight: 1
|
||||
options:
|
||||
'': Global
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.routable:
|
||||
type: toggle
|
||||
label: Routable
|
||||
help: If this page is reachable by a URL
|
||||
highlight: 1
|
||||
default: ''
|
||||
options:
|
||||
'': Global
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.cache_enable:
|
||||
type: toggle
|
||||
label: Caching
|
||||
highlight: 1
|
||||
options:
|
||||
'': Global
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
header.order_by:
|
||||
type: hidden
|
||||
|
||||
header.order_manual:
|
||||
type: hidden
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
blueprint:
|
||||
type: blueprint
|
||||
47
system/blueprints/pages/modular.yaml
Normal file
47
system/blueprints/pages/modular.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
title: Modular
|
||||
@extends:
|
||||
type: default
|
||||
context: blueprints://pages
|
||||
|
||||
form:
|
||||
fields:
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
content:
|
||||
fields:
|
||||
|
||||
header.content.items:
|
||||
type: select
|
||||
label: Items
|
||||
default: @self.modular
|
||||
options:
|
||||
@self.modular: Children
|
||||
|
||||
header.content.order.by:
|
||||
type: select
|
||||
label: Order By
|
||||
default: date
|
||||
options:
|
||||
folder: Folder
|
||||
title: Title
|
||||
date: Date
|
||||
default: Default
|
||||
|
||||
header.content.order.dir:
|
||||
type: select
|
||||
label: Order
|
||||
default: desc
|
||||
options:
|
||||
asc: Ascending
|
||||
desc: Descending
|
||||
|
||||
header.process:
|
||||
type: ignore
|
||||
content:
|
||||
type: ignore
|
||||
uploads:
|
||||
type: ignore
|
||||
|
||||
@@ -52,3 +52,7 @@ form:
|
||||
default: 1
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
blueprint:
|
||||
type: blueprint
|
||||
|
||||
@@ -61,7 +61,7 @@ form:
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
'': '- Select -'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
@@ -82,3 +82,5 @@ form:
|
||||
type: order
|
||||
label: Ordering
|
||||
|
||||
blueprint:
|
||||
type: blueprint
|
||||
|
||||
17
system/blueprints/pages/move.yaml
Normal file
17
system/blueprints/pages/move.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
@@ -15,18 +15,18 @@ form:
|
||||
title:
|
||||
type: text
|
||||
label: Page Title
|
||||
help: "The title of the page"
|
||||
validate:
|
||||
required: true
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder Name
|
||||
help: "The folder name that will be stored in the filesystem for this page"
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Parent Page
|
||||
@@ -40,9 +40,28 @@ form:
|
||||
|
||||
type:
|
||||
type: select
|
||||
help: "The page type that translates into which twig template renders the page"
|
||||
classes: fancy
|
||||
label: Display Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
visible:
|
||||
type: toggle
|
||||
label: Visible
|
||||
help: "Determines if a page is visible in the navigation."
|
||||
highlight: ''
|
||||
default: ''
|
||||
options:
|
||||
'': Auto
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
required: true
|
||||
|
||||
|
||||
blueprint:
|
||||
type: blueprint
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
title:
|
||||
type: text
|
||||
label: Title
|
||||
validate:
|
||||
required: true
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
classes: fancy
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'/': '- Root -'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
type:
|
||||
type: select
|
||||
classes: fancy
|
||||
label: Display Template
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
validate:
|
||||
required: true
|
||||
@@ -82,3 +82,5 @@ form:
|
||||
type: order
|
||||
label: Ordering
|
||||
|
||||
blueprint:
|
||||
type: blueprint
|
||||
|
||||
@@ -12,13 +12,16 @@ form:
|
||||
type: text
|
||||
size: large
|
||||
label: Username
|
||||
disabled: true
|
||||
readonly: true
|
||||
|
||||
email:
|
||||
type: text
|
||||
type: email
|
||||
size: large
|
||||
label: Email
|
||||
validate:
|
||||
type: email
|
||||
message: Must be a valid email address
|
||||
required: true
|
||||
|
||||
password:
|
||||
@@ -27,8 +30,8 @@ form:
|
||||
label: Password
|
||||
validate:
|
||||
required: true
|
||||
|
||||
|
||||
message: Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters
|
||||
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
|
||||
|
||||
fullname:
|
||||
type: text
|
||||
@@ -41,46 +44,3 @@ form:
|
||||
type: text
|
||||
size: large
|
||||
label: Title
|
||||
|
||||
admin:
|
||||
type: section
|
||||
title: Admin Access
|
||||
|
||||
fields:
|
||||
access.admin.super:
|
||||
type: toggle
|
||||
label: Super user
|
||||
default: 0
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
access.admin.login:
|
||||
type: toggle
|
||||
label: Admin login
|
||||
default: 0
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
site:
|
||||
type: section
|
||||
title: Site Access
|
||||
|
||||
fields:
|
||||
access.site.login:
|
||||
type: toggle
|
||||
label: Site login
|
||||
default: 1
|
||||
highlight: 1
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
@@ -75,6 +75,10 @@ txt:
|
||||
type: file
|
||||
thumb: media/thumb-txt.png
|
||||
mime: text/plain
|
||||
xml:
|
||||
type: file
|
||||
thumb: media/thumb-xml.png
|
||||
mime: application/xml
|
||||
doc:
|
||||
type: file
|
||||
thumb: media/thumb-doc.png
|
||||
@@ -95,3 +99,16 @@ gz:
|
||||
type: file
|
||||
thumb: media/thumb-gz.png
|
||||
mime: application/gzip
|
||||
css:
|
||||
type: file
|
||||
thumb: media/thumb-css.png
|
||||
mime: text/css
|
||||
js:
|
||||
type: file
|
||||
thumb: media/thumb-js.png
|
||||
mime: application/javascript
|
||||
json:
|
||||
type: file
|
||||
thumb: media/thumb-json.png
|
||||
mime: application/json
|
||||
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
title: Grav # Name of the site
|
||||
|
||||
author:
|
||||
name: John Appleseed # Default author name
|
||||
email: 'john@email.com' # Default author email
|
||||
|
||||
taxonomies: [category,tag] # Arbitrary list of taxonomy types
|
||||
blog:
|
||||
route: '/blog' # Route to blog
|
||||
|
||||
metadata:
|
||||
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
|
||||
format: short # long = summary delimiter will be ignored; short = use the first occurrence of delimiter or size
|
||||
size: 300 # Maximum length of summary (characters)
|
||||
delimiter: === # The summary delimiter
|
||||
|
||||
redirects:
|
||||
/redirect-test: / # Redirect test goes to home page
|
||||
/old/(.*): /new/$1 # Would redirect /old/my-page to /new/my-page
|
||||
|
||||
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
|
||||
/new/(.*): '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route
|
||||
|
||||
blog:
|
||||
route: '/blog' # Custom value added (accessible via system.blog.route)
|
||||
|
||||
#menu: # Sample Menu Example
|
||||
# - text: Source
|
||||
# icon: github
|
||||
# url: https://github.com/getgrav/grav
|
||||
# - icon: twitter
|
||||
# url: http://twitter.com/getgrav
|
||||
|
||||
@@ -2,13 +2,23 @@ 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
|
||||
|
||||
languages:
|
||||
supported: [] # List of languages supported. eg: [en, fr, de]
|
||||
translations: true # Enable translations by default
|
||||
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
|
||||
session_store_active: false # Store active language in session
|
||||
home_redirect:
|
||||
include_lang: true # Include language in home redirect (/en)
|
||||
include_route: false # Include route in home redirect (/blog)
|
||||
|
||||
|
||||
home:
|
||||
alias: '/home' # Default path for home, ie /
|
||||
|
||||
pages:
|
||||
theme: antimatter # Default theme (defaults to "antimatter" theme)
|
||||
order:
|
||||
by: defaults # Order pages by "default", "alpha" or "date"
|
||||
by: default # Order pages by "default", "alpha" or "date"
|
||||
dir: asc # Default ordering direction, "asc" or "desc"
|
||||
list:
|
||||
count: 20 # Default item count per page
|
||||
@@ -30,6 +40,12 @@ pages:
|
||||
special_chars: # List of special characters to automatically convert to entities
|
||||
'>': 'gt'
|
||||
'<': 'lt'
|
||||
types: [txt,xml,html,json,rss,atom] # list of valid page types
|
||||
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
|
||||
last_modified: false # Set the last modified date header based on file modifcation timestamp
|
||||
etag: false # Set the etag header tag
|
||||
vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
|
||||
redirect_default_route: false # Automatically redirect to a page's default route
|
||||
|
||||
cache:
|
||||
enabled: true # Set to true to enable caching
|
||||
@@ -55,8 +71,9 @@ 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 timestamps
|
||||
collections:
|
||||
jquery: system://assets/jquery/jquery-2.1.3.min.js
|
||||
jquery: system://assets/jquery/jquery-2.1.4.min.js
|
||||
|
||||
errors:
|
||||
display: true # Display full backtrace-style error page
|
||||
@@ -70,4 +87,18 @@ debugger:
|
||||
|
||||
images:
|
||||
default_image_quality: 85 # Default image quality to use when resampling images (85%)
|
||||
cache_all: false # Cache all image by default
|
||||
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
|
||||
|
||||
media:
|
||||
enable_media_timestamp: false # Enable media timetsamps
|
||||
upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
|
||||
unsupported_inline_types: [] # Array of unsupported media file types to try to display inline
|
||||
|
||||
session:
|
||||
enabled: true # Enable Session support
|
||||
timeout: 1800 # Timeout in seconds
|
||||
name: grav-site # Name prefix of the session cookie
|
||||
|
||||
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.21');
|
||||
define('GRAV_VERSION', '0.9.35');
|
||||
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/');
|
||||
|
||||
58
system/languages/en.yaml
Normal file
58
system/languages/en.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
INFLECTOR_PLURALS:
|
||||
'/(quiz)$/i': '\1zes'
|
||||
'/^(ox)$/i': '\1en'
|
||||
'/([m|l])ouse$/i': '\1ice'
|
||||
'/(matr|vert|ind)ix|ex$/i': '\1ices'
|
||||
'/(x|ch|ss|sh)$/i': '\1es'
|
||||
'/([^aeiouy]|qu)ies$/i': '\1y'
|
||||
'/([^aeiouy]|qu)y$/i': '\1ies'
|
||||
'/(hive)$/i': '\1s'
|
||||
'/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
|
||||
'/sis$/i': 'ses'
|
||||
'/([ti])um$/i': '\1a'
|
||||
'/(buffal|tomat)o$/i': '\1oes'
|
||||
'/(bu)s$/i': '\1ses'
|
||||
'/(alias|status)/i': '\1es'
|
||||
'/(octop|vir)us$/i': '\1i'
|
||||
'/(ax|test)is$/i': '\1es'
|
||||
'/s$/i': 's'
|
||||
'/$/': 's'
|
||||
INFLECTOR_SINGULAR:
|
||||
'/(quiz)zes$/i': '\1'
|
||||
'/(matr)ices$/i': '\1ix'
|
||||
'/(vert|ind)ices$/i': '\1ex'
|
||||
'/^(ox)en/i': '\1'
|
||||
'/(alias|status)es$/i': '\1'
|
||||
'/([octop|vir])i$/i': '\1us'
|
||||
'/(cris|ax|test)es$/i': '\1is'
|
||||
'/(shoe)s$/i': '\1'
|
||||
'/(o)es$/i': '\1'
|
||||
'/(bus)es$/i': '\1'
|
||||
'/([m|l])ice$/i': '\1ouse'
|
||||
'/(x|ch|ss|sh)es$/i': '\1'
|
||||
'/(m)ovies$/i': '\1ovie'
|
||||
'/(s)eries$/i': '\1eries'
|
||||
'/([^aeiouy]|qu)ies$/i': '\1y'
|
||||
'/([lr])ves$/i': '\1f'
|
||||
'/(tive)s$/i': '\1'
|
||||
'/(hive)s$/i': '\1'
|
||||
'/([^f])ves$/i': '\1fe'
|
||||
'/(^analy)ses$/i': '\1sis'
|
||||
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
|
||||
'/([ti])a$/i': '\1um'
|
||||
'/(n)ews$/i': '\1ews'
|
||||
'/s$/i': ''
|
||||
INFLECTOR_UNCOUNTABLE: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep']
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': 'people'
|
||||
'man': 'men'
|
||||
'child': 'children'
|
||||
'sex': 'sexes'
|
||||
'move': 'moves'
|
||||
INFLECTOR_ORDINALS:
|
||||
'default': 'th'
|
||||
'first': 'st'
|
||||
'second': 'nd'
|
||||
'third': 'rd'
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class Assets
|
||||
* Closure used by the pipeline to fetch assets.
|
||||
*
|
||||
* Useful when file_get_contents() function is not available in your PHP
|
||||
* instalation or when you want to apply any kind of preprocessing to
|
||||
* installation or when you want to apply any kind of preprocessing to
|
||||
* your assets before they get pipelined.
|
||||
*
|
||||
* The closure will receive as the only parameter a string with the path/URL of the asset and
|
||||
@@ -71,6 +71,7 @@ class Assets
|
||||
// Some configuration variables
|
||||
protected $config;
|
||||
protected $base_url;
|
||||
protected $timestamp = '';
|
||||
|
||||
// Default values for pipeline settings
|
||||
protected $css_minify = true;
|
||||
@@ -82,7 +83,6 @@ class Assets
|
||||
protected $css_no_pipeline = array();
|
||||
protected $js_no_pipeline = array();
|
||||
|
||||
|
||||
public function __construct(array $options = array())
|
||||
{
|
||||
// Forward config options
|
||||
@@ -154,6 +154,12 @@ class Assets
|
||||
}
|
||||
}
|
||||
|
||||
// Set timestamp
|
||||
if (isset($config['enable_asset_timestamp']) && $config['enable_asset_timestamp'] === true) {
|
||||
$this->timestamp = '?' . self::getGrav()['cache']->getKey();
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -422,11 +428,11 @@ class Assets
|
||||
$output .= '<link href="' . $this->pipeline(CSS_ASSET) . '"' . $attributes . ' />' . "\n";
|
||||
|
||||
foreach ($this->css_no_pipeline as $file) {
|
||||
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
|
||||
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
|
||||
}
|
||||
} else {
|
||||
foreach ($this->css as $file) {
|
||||
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
|
||||
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,11 +486,11 @@ class Assets
|
||||
if ($this->js_pipeline) {
|
||||
$output .= '<script src="' . $this->pipeline(JS_ASSET) . '"' . $attributes . ' ></script>' . "\n";
|
||||
foreach ($this->js_no_pipeline as $file) {
|
||||
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ' . $file['loading']. '></script>' . "\n";
|
||||
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading']. '></script>' . "\n";
|
||||
}
|
||||
} else {
|
||||
foreach ($this->js as $file) {
|
||||
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ' . $file['loading'].'></script>' . "\n";
|
||||
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading'].'></script>' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +509,7 @@ class Assets
|
||||
|
||||
|
||||
/**
|
||||
* Minifiy and concatenate CSS / JS files.
|
||||
* Minify and concatenate CSS / JS files.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -735,7 +741,7 @@ class Assets
|
||||
/**
|
||||
* Determine whether a link is local or remote.
|
||||
*
|
||||
* Undestands both "http://" and "https://" as well as protocol agnostic links "//"
|
||||
* Understands both "http://" and "https://" as well as protocol agnostic links "//"
|
||||
*
|
||||
* @param string $link
|
||||
*
|
||||
@@ -819,7 +825,8 @@ class Assets
|
||||
} else {
|
||||
// Fix to remove relative dir if grav is in one
|
||||
if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) {
|
||||
$relative_path = str_replace($this->base_url, '/', $link);
|
||||
$base_url = '#' . preg_quote($this->base_url, '#') . '#';
|
||||
$relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
|
||||
}
|
||||
|
||||
$relative_dir = dirname($relative_path);
|
||||
@@ -875,18 +882,7 @@ class Assets
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$newpath = array();
|
||||
$paths = explode('/', $old_url);
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if ($path == '..') {
|
||||
$relative_path = dirname($relative_path);
|
||||
} else {
|
||||
$newpath[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
$new_url = rtrim($this->base_url, '/') . $relative_path . '/' . implode('/', $newpath);
|
||||
$new_url = $this->base_url . ltrim(Utils::normalizePath($relative_path . '/' . $old_url), '/');
|
||||
|
||||
return str_replace($old_url, $new_url, $matches[0]);
|
||||
},
|
||||
@@ -924,7 +920,7 @@ class Assets
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $pattern (regex)
|
||||
* @param string $ltrim Will be trimed from the left of the file path
|
||||
* @param string $ltrim Will be trimmed from the left of the file path
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
125
system/src/Grav/Common/Backup/ZipBackup.php
Normal file
125
system/src/Grav/Common/Backup/ZipBackup.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
namespace Grav\Common\Backup;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
/**
|
||||
* The ZipBackup class lets you create simple zip-backups of a grav site
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipBackup
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected static $ignorePaths = [
|
||||
'backup',
|
||||
'cache',
|
||||
'images',
|
||||
'logs'
|
||||
];
|
||||
|
||||
protected static $ignoreFolders = [
|
||||
'.git',
|
||||
'.idea'
|
||||
];
|
||||
|
||||
public static function backup($destination = null, callable $messager = null)
|
||||
{
|
||||
if (!$destination) {
|
||||
$destination = self::getGrav()['locator']->findResource('backup://', true);
|
||||
|
||||
if (!$destination)
|
||||
throw new \RuntimeException('The backup folder is missing.');
|
||||
|
||||
Folder::mkdir($destination);
|
||||
}
|
||||
|
||||
$name = self::getGrav()['config']->get('site.title', basename(GRAV_ROOT));
|
||||
|
||||
if (is_dir($destination)) {
|
||||
$date = date('YmdHis', time());
|
||||
$filename = $name . '-' . $date . '.zip';
|
||||
$destination = rtrim($destination, DS) . DS . $filename;
|
||||
}
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => 'Creating new Backup "' . $destination . '"'
|
||||
]);
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => ''
|
||||
]);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip->open($destination, \ZipArchive::CREATE);
|
||||
|
||||
static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'progress',
|
||||
'percentage' => false,
|
||||
'complete' => true
|
||||
]);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => ''
|
||||
]);
|
||||
$messager && $messager([
|
||||
'type' => 'message',
|
||||
'level' => 'info',
|
||||
'message' => 'Saving and compressing archive...'
|
||||
]);
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $folder
|
||||
* @param $zipFile
|
||||
* @param $exclusiveLength
|
||||
* @param $messager
|
||||
*/
|
||||
private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, callable $messager = null)
|
||||
{
|
||||
$handle = opendir($folder);
|
||||
while (false !== $f = readdir($handle)) {
|
||||
if ($f != '.' && $f != '..') {
|
||||
$filePath = "$folder/$f";
|
||||
// Remove prefix from file path before add to zip.
|
||||
$localPath = substr($filePath, $exclusiveLength);
|
||||
|
||||
if (in_array($f, static::$ignoreFolders)) {
|
||||
continue;
|
||||
} elseif (in_array($localPath, static::$ignorePaths)) {
|
||||
$zipFile->addEmptyDir($f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_file($filePath)) {
|
||||
$zipFile->addFile($filePath, $localPath);
|
||||
|
||||
$messager && $messager([
|
||||
'type' => 'progress',
|
||||
'percentage' => false,
|
||||
'complete' => false
|
||||
]);
|
||||
} elseif (is_dir($filePath)) {
|
||||
// Add sub-directory.
|
||||
$zipFile->addEmptyDir($localPath);
|
||||
static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
}
|
||||
@@ -271,7 +271,7 @@ class Cache extends Getters
|
||||
|
||||
|
||||
/**
|
||||
* Set the cache lifetime programatically
|
||||
* Set the cache lifetime programmatically
|
||||
*
|
||||
* @param int $future timestamp
|
||||
*/
|
||||
|
||||
55
system/src/Grav/Common/Composer.php
Normal file
55
system/src/Grav/Common/Composer.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* Offers composer helper methods.
|
||||
*
|
||||
* @author eschmar
|
||||
* @license MIT
|
||||
*/
|
||||
class Composer
|
||||
{
|
||||
/** @const Default composer location */
|
||||
const DEFAULT_PATH = "bin/composer.phar";
|
||||
|
||||
/**
|
||||
* Returns the location of composer.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getComposerLocation()
|
||||
{
|
||||
if (!function_exists('shell_exec') || strtolower(substr(PHP_OS, 0, 3)) === 'win') {
|
||||
return self::DEFAULT_PATH;
|
||||
}
|
||||
|
||||
// check for global composer install
|
||||
$path = trim(shell_exec("command -v composer"));
|
||||
|
||||
// fall back to grav bundled composer
|
||||
if (!$path || !preg_match('/(composer|composer\.phar)$/', $path)) {
|
||||
$path = self::DEFAULT_PATH;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public static function getComposerExecutor()
|
||||
{
|
||||
$executor = PHP_BINARY . ' ';
|
||||
$composer = static::getComposerLocation();
|
||||
|
||||
if ($composer !== static::DEFAULT_PATH && is_executable($composer)) {
|
||||
$file = fopen($composer, 'r');
|
||||
$firstLine = fgets($file);
|
||||
fclose($file);
|
||||
|
||||
if (!preg_match('/^#!.+php/i', $firstLine)) {
|
||||
$executor = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $executor . $composer;
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,12 @@ class Config extends Data
|
||||
'' => ['user://themes'],
|
||||
]
|
||||
],
|
||||
'languages' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user://languages', 'system/languages'],
|
||||
]
|
||||
],
|
||||
'cache' => [
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
@@ -72,49 +78,52 @@ class Config extends Data
|
||||
'prefixes' => [
|
||||
'' => ['logs']
|
||||
]
|
||||
],
|
||||
'backup' => [
|
||||
'type' => 'Stream',
|
||||
'prefixes' => [
|
||||
'' => ['backup']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
protected $setup = [];
|
||||
|
||||
protected $blueprintFiles = [];
|
||||
protected $configFiles = [];
|
||||
protected $languageFiles = [];
|
||||
protected $checksum;
|
||||
protected $timestamp;
|
||||
|
||||
protected $configLookup;
|
||||
protected $blueprintLookup;
|
||||
protected $pluginLookup;
|
||||
protected $languagesLookup;
|
||||
|
||||
protected $finder;
|
||||
protected $environment;
|
||||
protected $messages = [];
|
||||
|
||||
public function __construct(array $items = array(), Grav $grav = null, $environment = null)
|
||||
protected $languages;
|
||||
|
||||
public function __construct(array $setup = array(), Grav $grav = null, $environment = null)
|
||||
{
|
||||
$this->grav = $grav ?: Grav::instance();
|
||||
$this->finder = new ConfigFinder;
|
||||
$this->environment = $environment ?: 'localhost';
|
||||
$this->messages[] = 'Environment Name: ' . $this->environment;
|
||||
|
||||
if (isset($items['@class'])) {
|
||||
if ($items['@class'] != get_class($this)) {
|
||||
throw new \InvalidArgumentException('Unrecognized config cache file!');
|
||||
}
|
||||
// Loading pre-compiled configuration.
|
||||
$this->timestamp = (int) $items['timestamp'];
|
||||
$this->checksum = $items['checksum'];
|
||||
$this->items = (array) $items['data'];
|
||||
} else {
|
||||
// Make sure that
|
||||
if (!isset($items['streams']['schemes'])) {
|
||||
$items['streams']['schemes'] = [];
|
||||
}
|
||||
$items['streams']['schemes'] += $this->streams;
|
||||
|
||||
$items = $this->autoDetectEnvironmentConfig($items);
|
||||
$this->messages[] = $items['streams']['schemes']['config']['prefixes'][''];
|
||||
|
||||
parent::__construct($items);
|
||||
// Make sure that
|
||||
if (!isset($setup['streams']['schemes'])) {
|
||||
$setup['streams']['schemes'] = [];
|
||||
}
|
||||
$setup['streams']['schemes'] += $this->streams;
|
||||
|
||||
$setup = $this->autoDetectEnvironmentConfig($setup);
|
||||
|
||||
$this->setup = $setup;
|
||||
parent::__construct($setup);
|
||||
|
||||
$this->check();
|
||||
}
|
||||
|
||||
@@ -125,8 +134,10 @@ class Config extends Data
|
||||
|
||||
public function reload()
|
||||
{
|
||||
$this->items = $this->setup;
|
||||
$this->check();
|
||||
$this->init();
|
||||
$this->debug();
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -150,6 +161,7 @@ class Config extends Data
|
||||
foreach ($this->messages as $message) {
|
||||
$this->grav['debugger']->addMessage($message);
|
||||
}
|
||||
$this->messages = [];
|
||||
}
|
||||
|
||||
public function init()
|
||||
@@ -161,51 +173,57 @@ class Config extends Data
|
||||
$this->blueprintLookup = $locator->findResources('blueprints://config');
|
||||
$this->pluginLookup = $locator->findResources('plugins://');
|
||||
|
||||
if (!isset($this->checksum)) {
|
||||
$this->messages[] = 'No cached configuration, compiling new configuration..';
|
||||
} elseif ($this->checksum() != $this->checksum) {
|
||||
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
|
||||
} else {
|
||||
$this->messages[] = 'Configuration checksum matches, using cached version.';
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
|
||||
$this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
|
||||
|
||||
// process languages if supported
|
||||
if ($this->get('system.languages.translations', true)) {
|
||||
$this->languagesLookup = $locator->findResources('languages://');
|
||||
$this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master');
|
||||
}
|
||||
|
||||
$this->initializeLocator($locator);
|
||||
}
|
||||
|
||||
public function checksum()
|
||||
{
|
||||
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
|
||||
$checkConfig = $this->get('system.cache.check.config', true);
|
||||
$checkSystem = $this->get('system.cache.check.system', true);
|
||||
if (empty($this->checksum)) {
|
||||
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
|
||||
$checkLanguages = $this->get('system.cache.check.languages', false);
|
||||
$checkConfig = $this->get('system.cache.check.config', true);
|
||||
$checkSystem = $this->get('system.cache.check.system', true);
|
||||
|
||||
if (!$checkBlueprints && !$checkConfig && !$checkSystem) {
|
||||
$this->messages[] = 'Skip configuration timestamp check.';
|
||||
return false;
|
||||
if (!$checkBlueprints && !!$checkLanguages && $checkConfig && !$checkSystem) {
|
||||
$this->messages[] = 'Skip configuration timestamp check.';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate checksum according to the configuration settings.
|
||||
if (!$checkConfig) {
|
||||
// Just check changes in system.yaml files and ignore all the other files.
|
||||
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
|
||||
} else {
|
||||
// Check changes in all configuration files.
|
||||
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
|
||||
}
|
||||
|
||||
if ($checkBlueprints) {
|
||||
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
|
||||
} else {
|
||||
$cb = [];
|
||||
}
|
||||
|
||||
if ($checkLanguages) {
|
||||
$cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup);
|
||||
} else {
|
||||
$cl = [];
|
||||
}
|
||||
|
||||
$this->checksum = md5(json_encode([$cc, $cb, $cl]));
|
||||
}
|
||||
|
||||
// Generate checksum according to the configuration settings.
|
||||
if (!$checkConfig) {
|
||||
$this->messages[] = 'Check configuration timestamps from system.yaml files.';
|
||||
// Just check changes in system.yaml files and ignore all the other files.
|
||||
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
|
||||
} else {
|
||||
$this->messages[] = 'Check configuration timestamps from all configuration files.';
|
||||
// Check changes in all configuration files.
|
||||
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
|
||||
}
|
||||
|
||||
if ($checkBlueprints) {
|
||||
$this->messages[] = 'Check blueprint timestamps from all blueprint files.';
|
||||
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
|
||||
} else {
|
||||
$cb = [];
|
||||
}
|
||||
|
||||
return md5(json_encode([$cc, $cb]));
|
||||
return $this->checksum;
|
||||
}
|
||||
|
||||
protected function autoDetectEnvironmentConfig($items)
|
||||
@@ -255,7 +273,6 @@ class Config extends Data
|
||||
'files' => $blueprintFiles,
|
||||
'data' => $this->blueprints->toArray()
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$this->messages[] = 'Saving compiled blueprints.';
|
||||
@@ -269,14 +286,71 @@ class Config extends Data
|
||||
|
||||
protected function loadCompiledConfig($configs, $plugins, $filename = null)
|
||||
{
|
||||
$checksum = md5(json_encode($configs));
|
||||
$filename = $filename
|
||||
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
|
||||
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
|
||||
$file = PhpFile::instance($filename);
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
$class = get_class($this);
|
||||
$checksum = $this->checksum();
|
||||
|
||||
if (
|
||||
!is_array($cache)
|
||||
|| !isset($cache['checksum'])
|
||||
|| !isset($cache['@class'])
|
||||
|| $cache['@class'] != $class
|
||||
) {
|
||||
$this->messages[] = 'No cached configuration, compiling new configuration..';
|
||||
} else if ($cache['checksum'] !== $checksum) {
|
||||
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
|
||||
} else {
|
||||
$this->messages[] = 'Configuration checksum matches, using cached version.';
|
||||
|
||||
$this->items = $cache['data'];
|
||||
return;
|
||||
}
|
||||
|
||||
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
|
||||
$checksum .= ':'.md5(json_encode($configFiles));
|
||||
|
||||
// Attempt to lock the file for writing.
|
||||
$file->lock(false);
|
||||
|
||||
// Load configuration.
|
||||
foreach ($configFiles as $files) {
|
||||
$this->loadConfigFiles($files);
|
||||
}
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'timestamp' => time(),
|
||||
'checksum' => $checksum,
|
||||
'data' => $this->toArray()
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$this->messages[] = 'Saving compiled configuration.';
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
}
|
||||
|
||||
$this->items = $cache['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $languages
|
||||
* @param $plugins
|
||||
* @param null $filename
|
||||
*/
|
||||
protected function loadCompiledLanguages($languages, $plugins, $filename = null)
|
||||
{
|
||||
$checksum = md5(json_encode($languages));
|
||||
$filename = $filename
|
||||
? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php'
|
||||
: CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php';
|
||||
$file = PhpFile::instance($filename);
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
$languageFiles = $this->finder->locateLanguageFiles($languages, $plugins);
|
||||
$checksum .= ':' . md5(json_encode($languageFiles));
|
||||
$class = get_class($this);
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
@@ -290,26 +364,40 @@ class Config extends Data
|
||||
// Attempt to lock the file for writing.
|
||||
$file->lock(false);
|
||||
|
||||
// Load configuration.
|
||||
foreach ($configFiles as $files) {
|
||||
$this->loadConfigFiles($files);
|
||||
}
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'timestamp' => time(),
|
||||
'checksum' => $this->checksum(),
|
||||
'data' => $this->toArray()
|
||||
];
|
||||
// Load languages.
|
||||
$this->languages = new Languages;
|
||||
|
||||
if (isset($languageFiles['user/plugins'])) {
|
||||
foreach ((array) $languageFiles['user/plugins'] as $plugin => $item) {
|
||||
$lang_file = CompiledYamlFile::instance($item['file']);
|
||||
$content = $lang_file->content();
|
||||
$this->languages->mergeRecursive($content);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($languageFiles['system/languages'])) {
|
||||
foreach ((array) $languageFiles['system/languages'] as $lang => $item) {
|
||||
$lang_file = CompiledYamlFile::instance($item['file']);
|
||||
$content = $lang_file->content();
|
||||
$this->languages->join($lang, $content, '/');
|
||||
}
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'checksum' => $checksum,
|
||||
'files' => $languageFiles,
|
||||
'data' => $this->languages->toArray()
|
||||
];
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$this->messages[] = 'Saving compiled configuration.';
|
||||
$this->messages[] = 'Saving compiled languages.';
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
}
|
||||
} else {
|
||||
$this->languages = new Languages($cache['data']);
|
||||
}
|
||||
|
||||
$this->items = $cache['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,4 +468,9 @@ class Config extends Data
|
||||
|
||||
return $schemes;
|
||||
}
|
||||
|
||||
public function getLanguages()
|
||||
{
|
||||
return $this->languages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,18 @@ class ConfigFinder
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function locateLanguageFiles(array $languages, array $plugins)
|
||||
{
|
||||
$list = [];
|
||||
foreach (array_reverse($plugins) as $folder) {
|
||||
$list += $this->detectLanguagesInFolder($folder, 'languages');
|
||||
}
|
||||
foreach (array_reverse($languages) as $folder) {
|
||||
$list += $this->detectRecursive($folder);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all locations for a single configuration file.
|
||||
*
|
||||
@@ -90,11 +102,11 @@ class ConfigFinder
|
||||
$list = [];
|
||||
|
||||
if (is_dir($folder)) {
|
||||
$iterator = new \DirectoryIterator($folder);
|
||||
$iterator = new \FilesystemIterator($folder);
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
if (!$directory->isDir() || $directory->isDot()) {
|
||||
if (!$directory->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -111,6 +123,34 @@ class ConfigFinder
|
||||
return [$path => $list];
|
||||
}
|
||||
|
||||
protected function detectLanguagesInFolder($folder, $lookup = null)
|
||||
{
|
||||
$path = trim(Folder::getRelativePath($folder), '/');
|
||||
|
||||
$list = [];
|
||||
|
||||
if (is_dir($folder)) {
|
||||
$iterator = new \FilesystemIterator($folder);
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
if (!$directory->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $directory->getBasename();
|
||||
$find = ($lookup ?: $name) . '.yaml';
|
||||
$filename = "{$path}/{$name}/$find";
|
||||
|
||||
if (file_exists($filename)) {
|
||||
$list[$name] = ['file' => $filename, 'modified' => filemtime($filename)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$path => $list];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects all plugins with a configuration file and returns them with last modification time.
|
||||
*
|
||||
|
||||
27
system/src/Grav/Common/Config/Languages.php
Normal file
27
system/src/Grav/Common/Config/Languages.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
/**
|
||||
* The Languages class contains configuration rules.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Languages extends Data
|
||||
{
|
||||
|
||||
public function reformat()
|
||||
{
|
||||
if (isset($this->items['plugins'])) {
|
||||
$this->items = array_merge_recursive($this->items, $this->items['plugins']);
|
||||
unset($this->items['plugins']);
|
||||
}
|
||||
}
|
||||
|
||||
public function mergeRecursive(array $data)
|
||||
{
|
||||
$this->items = array_merge_recursive($this->items, $data);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
|
||||
/**
|
||||
@@ -11,7 +12,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
*/
|
||||
class Blueprint
|
||||
{
|
||||
use Export, DataMutatorTrait;
|
||||
use Export, DataMutatorTrait, GravTrait;
|
||||
|
||||
public $name;
|
||||
|
||||
@@ -75,7 +76,7 @@ class Blueprint
|
||||
try {
|
||||
$this->validateArray($data, $this->nested);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \RuntimeException(sprintf('Page validation failed: %s', $e->getMessage()));
|
||||
throw new \RuntimeException(sprintf('<b>Validation failed:</b> %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +118,17 @@ class Blueprint
|
||||
{
|
||||
// Initialize data
|
||||
$this->fields();
|
||||
return $this->extraArray($data, $this->nested, $prefix);
|
||||
$rules = $this->nested;
|
||||
|
||||
// Drill down to prefix level
|
||||
if (!empty($prefix)) {
|
||||
$parts = explode('.', trim($prefix, '.'));
|
||||
foreach ($parts as $part) {
|
||||
$rules = isset($rules[$part]) ? $rules[$part] : [];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->extraArray($data, $rules, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +297,7 @@ class Blueprint
|
||||
// Item has been defined in blueprints.
|
||||
} elseif (is_array($field) && is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$array += $this->ExtraArray($field, $val, $prefix);
|
||||
$array += $this->ExtraArray($field, $val, $prefix . $key . '.');
|
||||
} else {
|
||||
// Undefined/extra item.
|
||||
$array[$prefix.$key] = $field;
|
||||
@@ -313,11 +324,11 @@ class Blueprint
|
||||
$field['name'] = $prefix . $key;
|
||||
$field += $params;
|
||||
|
||||
if (isset($field['fields'])) {
|
||||
if (isset($field['fields']) && $field['type'] !== 'list') {
|
||||
// Recursively get all the nested fields.
|
||||
$newParams = array_intersect_key($this->filter, $field);
|
||||
$this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
|
||||
} else {
|
||||
} else if ($field['type'] !== 'ignore') {
|
||||
// Add rule.
|
||||
$this->rules[$prefix . $key] = &$field;
|
||||
$this->addProperty($prefix . $key);
|
||||
@@ -362,10 +373,20 @@ class Blueprint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elseif (substr($name, 0, 8) == '@config-') {
|
||||
$property = substr($name, 8);
|
||||
$default = isset($field[$property]) ? $field[$property] : null;
|
||||
$config = self::getGrav()['config']->get($value, $default);
|
||||
|
||||
if (!is_null($config)) {
|
||||
$field[$property] = $config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize predefined validation rule.
|
||||
if (isset($field['validate']['rule'])) {
|
||||
if (isset($field['validate']['rule']) && $field['type'] !== 'ignore') {
|
||||
$field['validate'] += $this->getRule($field['validate']['rule']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\GravTrait;
|
||||
|
||||
/**
|
||||
* Blueprints class keeps track on blueprint instances.
|
||||
@@ -11,6 +12,8 @@ use Grav\Common\File\CompiledYamlFile;
|
||||
*/
|
||||
class Blueprints
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected $search;
|
||||
protected $types;
|
||||
protected $instances = array();
|
||||
@@ -55,8 +58,20 @@ class Blueprints
|
||||
if (isset($blueprints['@extends'])) {
|
||||
// Extend blueprint by other blueprints.
|
||||
$extends = (array) $blueprints['@extends'];
|
||||
foreach ($extends as $extendType) {
|
||||
$blueprint->extend($this->get($extendType));
|
||||
|
||||
if (is_string(key($extends))) {
|
||||
$extends = [ $extends ];
|
||||
}
|
||||
|
||||
foreach ($extends as $extendConfig) {
|
||||
$extendType = !is_string($extendConfig) ? empty($extendConfig['type']) ? false : $extendConfig['type'] : $extendConfig;
|
||||
|
||||
if (!$extendType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context = is_string($extendConfig) || empty($extendConfig['context']) ? $this : new self(self::getGrav()['locator']->findResource($extendConfig['context']));
|
||||
$blueprint->extend($context->get($extendType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ trait DataMutatorTrait
|
||||
}
|
||||
|
||||
/**
|
||||
* Sey value by using dot notation for nested arrays/objects.
|
||||
* Set value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $data->set('this.is.my.nested.variable', true);
|
||||
*
|
||||
|
||||
@@ -28,14 +28,16 @@ class Validation
|
||||
// Validate type with fallback type text.
|
||||
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
|
||||
$method = 'type'.strtr($type, '-', '_');
|
||||
$name = ucfirst($field['label'] ? $field['label'] : $field['name']);
|
||||
$message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : 'Invalid input in ' . $name;
|
||||
|
||||
if (method_exists(__CLASS__, $method)) {
|
||||
$success = self::$method($value, $validate, $field);
|
||||
} else {
|
||||
$success = self::typeText($value, $validate, $field);
|
||||
}
|
||||
if (!$success) {
|
||||
$name = $field['label'] ? $field['label'] : $field['name'];
|
||||
throw new \RuntimeException("invalid input in {$name}");
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
|
||||
// Check individual rules
|
||||
@@ -43,8 +45,9 @@ class Validation
|
||||
$method = 'validate'.strtr($rule, '-', '_');
|
||||
if (method_exists(__CLASS__, $method)) {
|
||||
$success = self::$method($value, $params);
|
||||
|
||||
if (!$success) {
|
||||
throw new \RuntimeException('Failed');
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,6 +492,7 @@ class Validation
|
||||
{
|
||||
$values = (array) $value;
|
||||
$options = isset($field['options']) ? array_keys($field['options']) : array();
|
||||
$multi = isset($field['multiple']) ? $field['multiple'] : false;
|
||||
|
||||
if ($options) {
|
||||
$useKey = isset($field['use']) && $field['use'] == 'keys';
|
||||
@@ -497,9 +501,39 @@ class Validation
|
||||
}
|
||||
}
|
||||
|
||||
if ($multi) {
|
||||
foreach ($values as $key => $value) {
|
||||
$values[$key] = explode(',', $value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public static function typeList($value, array $params, array $field)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($field['fields'])) {
|
||||
foreach ($value as $key => $item) {
|
||||
foreach ($field['fields'] as $subKey => $subField) {
|
||||
$subKey = trim($subKey, '.');
|
||||
$subValue = isset($item[$subKey]) ? $item[$subKey] : null;
|
||||
self::validate($subValue, $subField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static function filterList($value, array $params, array $field)
|
||||
{
|
||||
return (array) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom input: ignore (will not validate)
|
||||
*
|
||||
@@ -513,6 +547,11 @@ class Validation
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function filterIgnore($value, array $params, array $field)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
// HTML5 attributes (min, max and range are handled inside the types)
|
||||
|
||||
public static function validateRequired($value, $params)
|
||||
|
||||
@@ -25,7 +25,7 @@ trait CompiledFile
|
||||
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
||||
if ($var === null && $this->raw === null && $this->content === null) {
|
||||
$key = md5($this->filename);
|
||||
$file = PhpFile::instance(CACHE_DIR . "/compiled/files/{$key}{$this->extension}.php");
|
||||
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
|
||||
$modified = $this->modified();
|
||||
|
||||
if (!$modified) {
|
||||
|
||||
@@ -38,21 +38,20 @@ abstract class Folder
|
||||
* Recursively find the last modified time under given path by file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $extensions
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFile($path)
|
||||
public static function lastModifiedFile($path, $extensions = 'md|yaml')
|
||||
{
|
||||
$last_modified = 0;
|
||||
|
||||
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$filterItr = new RecursiveFileFilterIterator($dirItr);
|
||||
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i');
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($itr as $file) {
|
||||
if ($file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
foreach ($itr as $filepath => $file) {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
@@ -72,8 +71,8 @@ abstract class Folder
|
||||
public static function getRelativePath($path, $base = GRAV_ROOT)
|
||||
{
|
||||
if ($base) {
|
||||
$base = preg_replace('![\\|/]+!', '/', $base);
|
||||
$path = preg_replace('![\\|/]+!', '/', $path);
|
||||
$base = preg_replace('![\\\/]+!', '/', $base);
|
||||
$path = preg_replace('![\\\/]+!', '/', $path);
|
||||
if (strpos($path, $base) === 0) {
|
||||
$path = ltrim(substr($path, strlen($base)), '/');
|
||||
}
|
||||
@@ -279,6 +278,46 @@ abstract class Folder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive copy of one directory to another
|
||||
*
|
||||
* @param $src
|
||||
* @param $dest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function rcopy($src, $dest)
|
||||
{
|
||||
|
||||
// If the src is not a directory do a simple file copy
|
||||
if (!is_dir($src)) {
|
||||
copy($src, $dest);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the destination directory does not exist create it
|
||||
if (!is_dir($dest)) {
|
||||
if (!mkdir($dest)) {
|
||||
// If the destination directory could not be created stop processing
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the source directory to read in files
|
||||
$i = new \DirectoryIterator($src);
|
||||
/** @var \DirectoryIterator $f */
|
||||
foreach ($i as $f) {
|
||||
if ($f->isFile()) {
|
||||
copy($f->getRealPath(), "$dest/" . $f->getFilename());
|
||||
} else {
|
||||
if (!$f->isDot() && $f->isDir()) {
|
||||
static::rcopy($f->getRealPath(), "$dest/$f");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @return bool
|
||||
@@ -291,13 +330,24 @@ abstract class Folder
|
||||
return @unlink($folder);
|
||||
}
|
||||
|
||||
// Go through all items in filesystem and recursively remove everything.
|
||||
$files = array_diff(scandir($folder), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
$path = "{$folder}/{$file}";
|
||||
(is_dir($path)) ? self::doDelete($path) : @unlink($path);
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
/** @var \DirectoryIterator $fileinfo */
|
||||
foreach ($files as $fileinfo) {
|
||||
if ($fileinfo->isDir()) {
|
||||
if (false === rmdir($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (false === unlink($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @rmdir($folder);
|
||||
return rmdir($folder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
class RecursiveFileFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
public static $FILTERS = ['.DS_Store'];
|
||||
|
||||
public function accept()
|
||||
{
|
||||
// Ensure any filtered file names are skipped
|
||||
return !in_array($this->current()->getFilename(), self::$FILTERS, true);
|
||||
}
|
||||
}
|
||||
@@ -349,6 +349,7 @@ class GPM extends Iterator
|
||||
public function findPackages($searches = [])
|
||||
{
|
||||
$packages = ['total' => 0, 'not_found' => []];
|
||||
$inflector = new Inflector();
|
||||
|
||||
foreach ($searches as $search) {
|
||||
$repository = '';
|
||||
@@ -361,7 +362,7 @@ class GPM extends Iterator
|
||||
}
|
||||
|
||||
if ($found = $this->findPackage($search)) {
|
||||
// set override respository if provided
|
||||
// set override repository if provided
|
||||
if ($repository) {
|
||||
$found->override_repository = $repository;
|
||||
}
|
||||
@@ -380,7 +381,7 @@ class GPM extends Iterator
|
||||
}
|
||||
|
||||
$not_found = new \stdClass();
|
||||
$not_found->name = Inflector::camelize($search);
|
||||
$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]);
|
||||
|
||||
@@ -158,9 +158,9 @@ class Installer
|
||||
|
||||
|
||||
/**
|
||||
* Unnstalls one or more given package
|
||||
* Uninstalls one or more given package
|
||||
*
|
||||
* @param string $package The slug of the package(s)
|
||||
* @param string $path The slug of the package(s)
|
||||
* @param array $options Options to use for uninstalling
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
|
||||
@@ -24,8 +24,8 @@ class Grav extends AbstractPackageCollection
|
||||
$this->fetch($refresh, $callback);
|
||||
|
||||
$this->data = json_decode($this->raw, true);
|
||||
$this->version = @$this->data['version'] ?: '-';
|
||||
$this->date = @$this->data['date'] ?: '-';
|
||||
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
|
||||
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
|
||||
|
||||
foreach ($this->data['assets'] as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data);
|
||||
@@ -38,7 +38,7 @@ class Grav extends AbstractPackageCollection
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->data->assets;
|
||||
return $this->data['assets'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,11 +50,11 @@ class Grav extends AbstractPackageCollection
|
||||
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; }
|
||||
|
||||
@@ -160,6 +160,8 @@ class Response
|
||||
private static function getCurl()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$args = count($args) > 1 ? $args : array_shift($args);
|
||||
|
||||
$uri = $args[0];
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
@@ -65,7 +65,7 @@ class Upgrader
|
||||
* Returns the changelog list for each version of Grav
|
||||
* @param string $diff the version number to start the diff from
|
||||
*
|
||||
* @return array return the chagenlog list for each version
|
||||
* @return array return the changelog list for each version
|
||||
*/
|
||||
public function getChangelog($diff = null)
|
||||
{
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Service\ConfigServiceProvider;
|
||||
use Grav\Common\Service\ErrorServiceProvider;
|
||||
use Grav\Common\Service\LoggerServiceProvider;
|
||||
use Grav\Common\Service\StreamsServiceProvider;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use RocketTheme\Toolbox\DI\Container;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
|
||||
/**
|
||||
* Grav
|
||||
@@ -56,6 +57,8 @@ class Grav extends Container
|
||||
|
||||
$container['grav'] = $container;
|
||||
|
||||
|
||||
|
||||
$container['debugger'] = new Debugger();
|
||||
$container['debugger']->startTimer('_init', 'Initialize');
|
||||
|
||||
@@ -77,8 +80,11 @@ class Grav extends Container
|
||||
$container['cache'] = function ($c) {
|
||||
return new Cache($c);
|
||||
};
|
||||
$container['session'] = function ($c) {
|
||||
return new Session($c);
|
||||
};
|
||||
$container['plugins'] = function ($c) {
|
||||
return new Plugins($c);
|
||||
return new Plugins();
|
||||
};
|
||||
$container['themes'] = function ($c) {
|
||||
return new Themes($c);
|
||||
@@ -89,44 +95,38 @@ class Grav extends Container
|
||||
$container['taxonomy'] = function ($c) {
|
||||
return new Taxonomy($c);
|
||||
};
|
||||
$container['language'] = function ($c) {
|
||||
return new Language($c);
|
||||
};
|
||||
|
||||
$container['pages'] = function ($c) {
|
||||
return new Page\Pages($c);
|
||||
};
|
||||
$container['assets'] = function ($c) {
|
||||
return new Assets();
|
||||
};
|
||||
|
||||
$container['assets'] = new Assets();
|
||||
|
||||
$container['page'] = function ($c) {
|
||||
/** @var Pages $pages */
|
||||
$pages = $c['pages'];
|
||||
|
||||
// If base URI is set, we want to remove it from the URL.
|
||||
$path = '/' . ltrim(Folder::getRelativePath($c['uri']->route(), $pages->base()), '/');
|
||||
/** @var Uri $uri */
|
||||
$uri = $c['uri'];
|
||||
|
||||
$path = rtrim($uri->path(), '/');
|
||||
$path = $path ?: '/';
|
||||
|
||||
$page = $pages->dispatch($path);
|
||||
|
||||
// handle redirect if not 'default route' configuration
|
||||
if ($page && $c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) {
|
||||
$c->redirectLangSafe($page->route());
|
||||
}
|
||||
|
||||
// if page is not found, try some fallback stuff
|
||||
if (!$page || !$page->routable()) {
|
||||
|
||||
// special case where a media file is requested
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
$page = $c['pages']->dispatch($path_parts['dirname'], true);
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
$media_file = urldecode($path_parts['basename']);
|
||||
if (isset($media[$media_file])) {
|
||||
$medium = $media[$media_file];
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($c['uri']->query(null, true) as $action => $params) {
|
||||
if (in_array($action, Medium::$valid_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
}
|
||||
header('Content-type: '. $medium->get('mime'));
|
||||
echo file_get_contents($medium->path());
|
||||
die;
|
||||
}
|
||||
}
|
||||
// Try fallback URL stuff...
|
||||
$c->fallbackUrl($page, $path);
|
||||
|
||||
// If no page found, fire event
|
||||
$event = $c->fireEvent('onPageNotFound');
|
||||
@@ -159,6 +159,8 @@ class Grav extends Container
|
||||
$container->register(new StreamsServiceProvider);
|
||||
$container->register(new ConfigServiceProvider);
|
||||
|
||||
$container['inflector'] = new Inflector();
|
||||
|
||||
$container['debugger']->stopTimer('_init');
|
||||
|
||||
return $container;
|
||||
@@ -166,12 +168,6 @@ class Grav extends Container
|
||||
|
||||
public function process()
|
||||
{
|
||||
// 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'];
|
||||
|
||||
@@ -179,11 +175,18 @@ class Grav extends Container
|
||||
$debugger->startTimer('_config', 'Configuration');
|
||||
$this['config']->init();
|
||||
$this['uri']->init();
|
||||
$this['session']->init();
|
||||
$this['errors']->resetHandlers();
|
||||
$debugger->init();
|
||||
$this['config']->debug();
|
||||
$debugger->stopTimer('_config');
|
||||
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_start('ob_gzhandler');
|
||||
}
|
||||
|
||||
// Initialize the timezone
|
||||
if ($this['config']->get('system.timezone')) {
|
||||
date_default_timezone_set($this['config']->get('system.timezone'));
|
||||
@@ -219,7 +222,6 @@ class Grav extends Container
|
||||
$this['pages']->init();
|
||||
$this->fireEvent('onPagesInitialized');
|
||||
$debugger->stopTimer('pages');
|
||||
|
||||
$this->fireEvent('onPageInitialized');
|
||||
|
||||
$debugger->addAssets();
|
||||
@@ -265,6 +267,25 @@ class Grav extends Container
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect browser to another location taking language into account (preferred)
|
||||
*
|
||||
* @param string $route Internal route.
|
||||
* @param int $code Redirection code (30x)
|
||||
*/
|
||||
public function redirectLangSafe($route, $code = 303)
|
||||
{
|
||||
/** @var Language $language */
|
||||
$language = $this['language'];
|
||||
$config = $this['config'];
|
||||
|
||||
if ($language->enabled()) {
|
||||
return $this->redirect($language->getLanguage() . $route, $code);
|
||||
} else {
|
||||
return $this->redirect($route);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns mime type for the file format.
|
||||
*
|
||||
@@ -294,8 +315,32 @@ class Grav extends Container
|
||||
public function header()
|
||||
{
|
||||
$extension = $this['uri']->extension();
|
||||
|
||||
/** @var Page $page */
|
||||
$page = $this['page'];
|
||||
|
||||
header('Content-type: ' . $this->mime($extension));
|
||||
|
||||
// Calculate Expires Headers if set to > 0
|
||||
$expires = $page->expires();
|
||||
|
||||
if ($expires > 0) {
|
||||
$expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT';
|
||||
header('Cache-Control: max-age=' . $expires_date);
|
||||
header('Expires: '. $expires_date);
|
||||
}
|
||||
|
||||
// Set the last modified time
|
||||
if ($page->lastModified()) {
|
||||
$last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT';
|
||||
header('Last-Modified: ' . $last_modified_date);
|
||||
}
|
||||
|
||||
// Calculate a Hash based on the raw file
|
||||
if ($page->eTag()) {
|
||||
header('ETag: ' . md5($page->raw() . $page->modified()));
|
||||
}
|
||||
|
||||
// Set debugger data in headers
|
||||
if (!($extension === null || $extension == 'html')) {
|
||||
$this['debugger']->enabled(false);
|
||||
@@ -305,6 +350,11 @@ class Grav extends Container
|
||||
if (isset($this['page']->header()->http_response_code)) {
|
||||
http_response_code($this['page']->header()->http_response_code);
|
||||
}
|
||||
|
||||
// Vary: Accept-Encoding
|
||||
if ($this['config']->get('system.pages.vary_accept_encoding', false)) {
|
||||
header('Vary: Accept-Encoding');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,26 +378,31 @@ class Grav extends Container
|
||||
public function shutdown()
|
||||
{
|
||||
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
|
||||
|
||||
//stop user abort
|
||||
if (function_exists('ignore_user_abort')) {
|
||||
@ignore_user_abort(true);
|
||||
}
|
||||
|
||||
// close the session
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
// flush buffer if gzip buffer was started
|
||||
if ($this['config']->get('system.cache.gzip')) {
|
||||
ob_end_flush(); // gzhandler buffer
|
||||
}
|
||||
|
||||
// get lengh and close the connection
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
header("Connection: close\r\n");
|
||||
header("Connection: close");
|
||||
|
||||
ob_end_flush(); // regular buffer
|
||||
ob_flush();
|
||||
// flush the regular buffer
|
||||
ob_end_flush();
|
||||
@ob_flush();
|
||||
flush();
|
||||
|
||||
// fix for fastcgi close connection issue
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
@fastcgi_finish_request();
|
||||
}
|
||||
@@ -356,4 +411,56 @@ class Grav extends Container
|
||||
|
||||
$this->fireEvent('onShutdown');
|
||||
}
|
||||
|
||||
/**
|
||||
* This attempts to fine media, other files, and download them
|
||||
* @param $page
|
||||
* @param $path
|
||||
*/
|
||||
protected function fallbackUrl($page, $path)
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
|
||||
$path_parts = pathinfo($path);
|
||||
$page = $this['pages']->dispatch($path_parts['dirname'], true);
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
|
||||
$parsed_url = parse_url(urldecode($uri->basename()));
|
||||
|
||||
$media_file = $parsed_url['path'];
|
||||
|
||||
// if this is a media object, try actions first
|
||||
if (isset($media[$media_file])) {
|
||||
$medium = $media[$media_file];
|
||||
foreach ($uri->query(null, true) as $action => $params) {
|
||||
if (in_array($action, ImageMedium::$magic_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
}
|
||||
Utils::download($medium->path(), false);
|
||||
}
|
||||
|
||||
// unsupported media type, try to download it...
|
||||
$uri_extension = $uri->extension();
|
||||
if ($uri_extension) {
|
||||
$extension = $uri_extension;
|
||||
} else {
|
||||
if (isset($path_parts['extension'])) {
|
||||
$extension = $path_parts['extension'];
|
||||
} else {
|
||||
$extension = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($extension) {
|
||||
$download = true;
|
||||
if (in_array(ltrim($extension, '.'), $this['config']->get('system.media.unsupported_inline_types'))) {
|
||||
$download = false;
|
||||
}
|
||||
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Grav\Common;
|
||||
/**
|
||||
* This file was originally part of the Akelos Framework
|
||||
*/
|
||||
use Grav\Common\Language\Language;
|
||||
|
||||
/**
|
||||
* Inflector for pluralize and singularize English nouns.
|
||||
@@ -20,65 +21,55 @@ namespace Grav\Common;
|
||||
|
||||
class Inflector
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected $plural;
|
||||
protected $singular;
|
||||
protected $uncountable;
|
||||
protected $irregular;
|
||||
protected $ordinals;
|
||||
|
||||
public function init()
|
||||
{
|
||||
if (empty($this->plural)) {
|
||||
$language = self::getGrav()['language'];
|
||||
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true);
|
||||
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true);
|
||||
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true);
|
||||
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true);
|
||||
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pluralizes English nouns.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word English noun to pluralize
|
||||
* @return string Plural noun
|
||||
*/
|
||||
public static function pluralize($word, $count = 2)
|
||||
public function pluralize($word, $count = 2)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
if ($count == 1) {
|
||||
return $word;
|
||||
}
|
||||
|
||||
$plural = array(
|
||||
'/(quiz)$/i' => '\1zes',
|
||||
'/^(ox)$/i' => '\1en',
|
||||
'/([m|l])ouse$/i' => '\1ice',
|
||||
'/(matr|vert|ind)ix|ex$/i' => '\1ices',
|
||||
'/(x|ch|ss|sh)$/i' => '\1es',
|
||||
'/([^aeiouy]|qu)ies$/i' => '\1y',
|
||||
'/([^aeiouy]|qu)y$/i' => '\1ies',
|
||||
'/(hive)$/i' => '\1s',
|
||||
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
|
||||
'/sis$/i' => 'ses',
|
||||
'/([ti])um$/i' => '\1a',
|
||||
'/(buffal|tomat)o$/i' => '\1oes',
|
||||
'/(bu)s$/i' => '\1ses',
|
||||
'/(alias|status)/i'=> '\1es',
|
||||
'/(octop|vir)us$/i'=> '\1i',
|
||||
'/(ax|test)is$/i'=> '\1es',
|
||||
'/s$/i'=> 's',
|
||||
'/$/'=> 's');
|
||||
|
||||
$uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep');
|
||||
|
||||
$irregular = array(
|
||||
'person' => 'people',
|
||||
'man' => 'men',
|
||||
'child' => 'children',
|
||||
'sex' => 'sexes',
|
||||
'move' => 'moves');
|
||||
|
||||
$lowercased_word = strtolower($word);
|
||||
|
||||
foreach ($uncountable as $_uncountable) {
|
||||
foreach ($this->uncountable as $_uncountable) {
|
||||
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
|
||||
return $word;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($irregular as $_plural => $_singular) {
|
||||
foreach ($this->irregular as $_plural => $_singular) {
|
||||
if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
|
||||
return preg_replace('/('.$_plural.')$/i', substr($arr[0], 0, 1).substr($_singular, 1), $word);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($plural as $rule => $replacement) {
|
||||
foreach ($this->plural as $rule => $replacement) {
|
||||
if (preg_match($rule, $word)) {
|
||||
return preg_replace($rule, $replacement, $word);
|
||||
}
|
||||
@@ -94,62 +85,28 @@ class Inflector
|
||||
* @param int $count
|
||||
* @return string Singular noun.
|
||||
*/
|
||||
public static function singularize($word, $count = 1)
|
||||
public function singularize($word, $count = 1)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
if ($count != 1) {
|
||||
return $word;
|
||||
}
|
||||
|
||||
$singular = array (
|
||||
'/(quiz)zes$/i' => '\1',
|
||||
'/(matr)ices$/i' => '\1ix',
|
||||
'/(vert|ind)ices$/i' => '\1ex',
|
||||
'/^(ox)en/i' => '\1',
|
||||
'/(alias|status)es$/i' => '\1',
|
||||
'/([octop|vir])i$/i' => '\1us',
|
||||
'/(cris|ax|test)es$/i' => '\1is',
|
||||
'/(shoe)s$/i' => '\1',
|
||||
'/(o)es$/i' => '\1',
|
||||
'/(bus)es$/i' => '\1',
|
||||
'/([m|l])ice$/i' => '\1ouse',
|
||||
'/(x|ch|ss|sh)es$/i' => '\1',
|
||||
'/(m)ovies$/i' => '\1ovie',
|
||||
'/(s)eries$/i' => '\1eries',
|
||||
'/([^aeiouy]|qu)ies$/i' => '\1y',
|
||||
'/([lr])ves$/i' => '\1f',
|
||||
'/(tive)s$/i' => '\1',
|
||||
'/(hive)s$/i' => '\1',
|
||||
'/([^f])ves$/i' => '\1fe',
|
||||
'/(^analy)ses$/i' => '\1sis',
|
||||
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
|
||||
'/([ti])a$/i' => '\1um',
|
||||
'/(n)ews$/i' => '\1ews',
|
||||
'/s$/i' => '',
|
||||
);
|
||||
|
||||
$uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep');
|
||||
|
||||
$irregular = array(
|
||||
'person' => 'people',
|
||||
'man' => 'men',
|
||||
'child' => 'children',
|
||||
'sex' => 'sexes',
|
||||
'move' => 'moves');
|
||||
|
||||
$lowercased_word = strtolower($word);
|
||||
foreach ($uncountable as $_uncountable) {
|
||||
foreach ($this->uncountable as $_uncountable) {
|
||||
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
|
||||
return $word;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($irregular as $_plural => $_singular) {
|
||||
foreach ($this->irregular as $_plural => $_singular) {
|
||||
if (preg_match('/('.$_singular.')$/i', $word, $arr)) {
|
||||
return preg_replace('/('.$_singular.')$/i', substr($arr[0], 0, 1).substr($_plural, 1), $word);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($singular as $rule => $replacement) {
|
||||
foreach ($this->singular as $rule => $replacement) {
|
||||
if (preg_match($rule, $word)) {
|
||||
return preg_replace($rule, $replacement, $word);
|
||||
}
|
||||
@@ -162,24 +119,22 @@ class Inflector
|
||||
* Converts an underscored or CamelCase word into a English
|
||||
* sentence.
|
||||
*
|
||||
* The titleize static public function converts text like "WelcomePage",
|
||||
* The titleize public function converts text like "WelcomePage",
|
||||
* "welcome_page" or "welcome page" to this "Welcome
|
||||
* Page".
|
||||
* If second parameter is set to 'first' it will only
|
||||
* capitalize the first character of the title.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word Word to format as tile
|
||||
* @param string $uppercase If set to 'first' it will only uppercase the
|
||||
* first character. Otherwise it will uppercase all
|
||||
* the words in the title.
|
||||
* @return string Text formatted as title
|
||||
*/
|
||||
public static function titleize($word, $uppercase = '')
|
||||
public function titleize($word, $uppercase = '')
|
||||
{
|
||||
$uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords';
|
||||
return $uppercase(static::humanize(static::underscorize($word)));
|
||||
return $uppercase($this->humanize($this->underscorize($word)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,13 +144,11 @@ class Inflector
|
||||
* will remove non alphanumeric character from the word, so
|
||||
* "who's online" will be converted to "WhoSOnline"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see variablize
|
||||
* @param string $word Word to convert to camel case
|
||||
* @return string UpperCamelCasedWord
|
||||
*/
|
||||
public static function camelize($word)
|
||||
public function camelize($word)
|
||||
{
|
||||
return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
|
||||
}
|
||||
@@ -208,12 +161,10 @@ class Inflector
|
||||
*
|
||||
* This can be really useful for creating friendly URLs.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word Word to underscore
|
||||
* @return string Underscored word
|
||||
*/
|
||||
public static function underscorize($word)
|
||||
public function underscorize($word)
|
||||
{
|
||||
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', $word);
|
||||
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1_\2', $regex1);
|
||||
@@ -229,12 +180,10 @@ class Inflector
|
||||
*
|
||||
* This can be really useful for creating friendly URLs.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word Word to hyphenate
|
||||
* @return string hyphenized word
|
||||
*/
|
||||
public static function hyphenize($word)
|
||||
public function hyphenize($word)
|
||||
{
|
||||
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
|
||||
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1);
|
||||
@@ -252,14 +201,12 @@ class Inflector
|
||||
* If you need to uppercase all the words you just have to
|
||||
* pass 'all' as a second parameter.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word String to "humanize"
|
||||
* @param string $uppercase If set to 'all' it will uppercase all the words
|
||||
* instead of just the first one.
|
||||
* @return string Human-readable word
|
||||
*/
|
||||
public static function humanize($word, $uppercase = '')
|
||||
public function humanize($word, $uppercase = '')
|
||||
{
|
||||
$uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst';
|
||||
return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
|
||||
@@ -272,15 +219,13 @@ class Inflector
|
||||
* will remove non alphanumeric character from the word, so
|
||||
* "who's online" will be converted to "whoSOnline"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see camelize
|
||||
* @param string $word Word to lowerCamelCase
|
||||
* @return string Returns a lowerCamelCasedWord
|
||||
*/
|
||||
public static function variablize($word)
|
||||
public function variablize($word)
|
||||
{
|
||||
$word = static::camelize($word);
|
||||
$word = $this->camelize($word);
|
||||
return strtolower($word[0]).substr($word, 1);
|
||||
}
|
||||
|
||||
@@ -290,15 +235,13 @@ class Inflector
|
||||
*
|
||||
* Converts "Person" to "people"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see classify
|
||||
* @param string $class_name Class name for getting related table_name.
|
||||
* @return string plural_table_name
|
||||
*/
|
||||
public static function tableize($class_name)
|
||||
public function tableize($class_name)
|
||||
{
|
||||
return static::pluralize(static::underscore($class_name));
|
||||
return $this->pluralize($this->underscore($class_name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,15 +250,13 @@ class Inflector
|
||||
*
|
||||
* Converts "people" to "Person"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see tableize
|
||||
* @param string $table_name Table name for getting related ClassName.
|
||||
* @return string SingularClassName
|
||||
*/
|
||||
public static function classify($table_name)
|
||||
public function classify($table_name)
|
||||
{
|
||||
return static::camelize(static::singularize($table_name));
|
||||
return $this->camelize($this->singularize($table_name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,34 +264,34 @@ class Inflector
|
||||
*
|
||||
* This method converts 13 to 13th, 2 to 2nd ...
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param integer $number Number to get its ordinal value
|
||||
* @return string Ordinal representation of given string.
|
||||
*/
|
||||
public static function ordinalize($number)
|
||||
public function ordinalize($number)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
if (in_array(($number % 100), range(11, 13))) {
|
||||
return $number.'th';
|
||||
return $number.$this->ordinals['default'];
|
||||
} else {
|
||||
switch (($number % 10)) {
|
||||
case 1:
|
||||
return $number.'st';
|
||||
return $number.$this->ordinals['first'];
|
||||
break;
|
||||
case 2:
|
||||
return $number.'nd';
|
||||
return $number.$this->ordinals['second'];
|
||||
break;
|
||||
case 3:
|
||||
return $number.'rd';
|
||||
return $number.$this->ordinals['third'];
|
||||
break;
|
||||
default:
|
||||
return $number.'th';
|
||||
return $number.$this->ordinals['default'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function monthize($days)
|
||||
public function monthize($days)
|
||||
{
|
||||
$now = new \DateTime();
|
||||
$end = new \DateTime();
|
||||
|
||||
418
system/src/Grav/Common/Language/Language.php
Normal file
418
system/src/Grav/Common/Language/Language.php
Normal file
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
namespace Grav\Common\Language;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
/**
|
||||
* Language and translation functionality for Grav
|
||||
*/
|
||||
class Language
|
||||
{
|
||||
protected $grav;
|
||||
protected $enabled = true;
|
||||
protected $languages = [];
|
||||
protected $page_extensions = [];
|
||||
protected $fallback_languages = [];
|
||||
protected $default;
|
||||
protected $active = null;
|
||||
protected $config;
|
||||
protected $http_accept_language;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Grav\Common\Grav $grav
|
||||
*/
|
||||
public function __construct(Grav $grav)
|
||||
{
|
||||
$this->grav = $grav;
|
||||
$this->config = $grav['config'];
|
||||
$this->languages = $this->config->get('system.languages.supported', []);
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the default and enabled languages
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->default = reset($this->languages);
|
||||
|
||||
if (empty($this->languages)) {
|
||||
$this->enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that languages are enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of supported languages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLanguages()
|
||||
{
|
||||
return $this->languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current supported languages manually
|
||||
*
|
||||
* @param $langs
|
||||
*/
|
||||
public function setLanguages($langs)
|
||||
{
|
||||
$this->languages = $langs;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pipe-separated string of available languages
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAvailable()
|
||||
{
|
||||
return implode('|', $this->languages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets language, active if set, else default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->active ? $this->active : $this->default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current default language
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDefault()
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default language manually
|
||||
*
|
||||
* @param $lang
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setDefault($lang)
|
||||
{
|
||||
if ($this->validate($lang)) {
|
||||
$this->default = $lang;
|
||||
|
||||
return $lang;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current active language
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getActive()
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets active language manually
|
||||
*
|
||||
* @param $lang
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setActive($lang)
|
||||
{
|
||||
if ($this->validate($lang)) {
|
||||
$this->active = $lang;
|
||||
|
||||
return $lang;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active language based on the first part of the URL
|
||||
*
|
||||
* @param $uri
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setActiveFromUri($uri)
|
||||
{
|
||||
$regex = '/(\/(' . $this->getAvailable() . ')).*/';
|
||||
|
||||
// if languages set
|
||||
if ($this->enabled()) {
|
||||
// try setting from prefix of URL (/en/blah/blah)
|
||||
if (preg_match($regex, $uri, $matches)) {
|
||||
$this->active = $matches[2];
|
||||
$uri = preg_replace("/\\" . $matches[1] . "/", '', $matches[0], 1);
|
||||
|
||||
// store in session if different
|
||||
if ($this->config->get('system.session.enabled', false)
|
||||
&& $this->config->get('system.languages.session_store_active', true)
|
||||
&& $this->grav['session']->active_language != $this->active
|
||||
) {
|
||||
$this->grav['session']->active_language = $this->active;
|
||||
}
|
||||
} else {
|
||||
// try getting from session, else no active
|
||||
if ($this->config->get('system.session.enabled', false) &&
|
||||
$this->config->get('system.languages.session_store_active', true)) {
|
||||
$this->active = $this->grav['session']->active_language ?: null;
|
||||
}
|
||||
// if still null, try from http_accept_language header
|
||||
if ($this->active === null && $this->config->get('system.languages.http_accept_language')) {
|
||||
$preferred = $this->getBrowserLanguages();
|
||||
foreach ($preferred as $lang) {
|
||||
if ($this->validate($lang)) {
|
||||
$this->active = $lang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets an array of valid extensions with active first, then fallback extensions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFallbackPageExtensions($file_ext = null)
|
||||
{
|
||||
if (empty($this->page_extensions)) {
|
||||
if (empty($file_ext)) {
|
||||
$file_ext = CONTENT_EXT;
|
||||
}
|
||||
|
||||
if ($this->enabled()) {
|
||||
$valid_lang_extensions = [];
|
||||
foreach ($this->languages as $lang) {
|
||||
$valid_lang_extensions[] = '.' . $lang . $file_ext;
|
||||
}
|
||||
|
||||
if ($this->active) {
|
||||
$active_extension = '.' . $this->active . $file_ext;
|
||||
$key = array_search($active_extension, $valid_lang_extensions);
|
||||
unset($valid_lang_extensions[$key]);
|
||||
array_unshift($valid_lang_extensions, $active_extension);
|
||||
}
|
||||
|
||||
$this->page_extensions = array_merge($valid_lang_extensions, (array)$file_ext);
|
||||
} else {
|
||||
$this->page_extensions = (array)$file_ext;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->page_extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of languages with active first, then fallback languages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFallbackLanguages()
|
||||
{
|
||||
if (empty($this->fallback_languages)) {
|
||||
if ($this->enabled()) {
|
||||
$fallback_languages = $this->languages;
|
||||
|
||||
if ($this->active) {
|
||||
$active_extension = $this->active;
|
||||
$key = array_search($active_extension, $fallback_languages);
|
||||
unset($fallback_languages[$key]);
|
||||
array_unshift($fallback_languages, $active_extension);
|
||||
}
|
||||
$this->fallback_languages = $fallback_languages;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fallback_languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the language is valid and supported
|
||||
*
|
||||
* @param $lang
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($lang)
|
||||
{
|
||||
if (in_array($lang, $this->languages)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a key and possibly arguments into a string using current lang and fallbacks
|
||||
*
|
||||
* @param $args first argument is the lookup key value
|
||||
* other arguments can be passed and replaced in the translation with sprintf syntax
|
||||
* @param Array $languages
|
||||
* @param bool $array_support
|
||||
* @param bool $html_out
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function translate($args, Array $languages = null, $array_support = false, $html_out = false)
|
||||
{
|
||||
if (is_array($args)) {
|
||||
$lookup = array_shift($args);
|
||||
} else {
|
||||
$lookup = $args;
|
||||
$args = [];
|
||||
}
|
||||
|
||||
|
||||
if ($this->config->get('system.languages.translations', true)) {
|
||||
|
||||
if (isset($this->grav['admin'])) {
|
||||
$languages = ['en'];
|
||||
} elseif ($this->enabled() && $lookup) {
|
||||
if (empty($languages)) {
|
||||
if ($this->config->get('system.languages.translations_fallback', true)) {
|
||||
$languages = $this->getFallbackLanguages();
|
||||
} else {
|
||||
$languages = (array)$this->getDefault();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$languages = ['en'];
|
||||
}
|
||||
|
||||
foreach ((array)$languages as $lang) {
|
||||
$translation = $this->getTranslation($lang, $lookup, $array_support);
|
||||
|
||||
if ($translation) {
|
||||
if (count($args) >= 1) {
|
||||
return vsprintf($translation, $args);
|
||||
} else {
|
||||
return $translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($html_out) {
|
||||
return '<span class="untranslated">' . $lookup . '</span>';
|
||||
} else {
|
||||
return $lookup;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate Array
|
||||
*
|
||||
* @param $key
|
||||
* @param $index
|
||||
* @param null $languages
|
||||
* @param bool $html_out
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function translateArray($key, $index, $languages = null, $html_out = false)
|
||||
{
|
||||
if ($this->config->get('system.languages.translations', true)) {
|
||||
if ($this->enabled() && $key) {
|
||||
if (empty($languages)) {
|
||||
if ($this->config->get('system.languages.translations_fallback', true)) {
|
||||
$languages = $this->getFallbackLanguages();
|
||||
} else {
|
||||
$languages = (array)$this->getDefault();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$languages = ['en'];
|
||||
}
|
||||
|
||||
foreach ((array)$languages as $lang) {
|
||||
$translation_array = (array)$this->config->getLanguages()->get($lang . '.' . $key, null);
|
||||
if ($translation_array && array_key_exists($index, $translation_array)) {
|
||||
return $translation_array[$index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($html_out) {
|
||||
return '<span class="untranslated">' . $key . '[' . $index . ']</span>';
|
||||
} else {
|
||||
return $key . '[' . $index . ']';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the translation text for a given lang and key
|
||||
*
|
||||
* @param $lang lang code
|
||||
* @param $key key to lookup with
|
||||
* @param bool $array_support
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTranslation($lang, $key, $array_support = false)
|
||||
{
|
||||
$translation = $this->config->getLanguages()->get($lang . '.' . $key, null);
|
||||
if (!$array_support && is_array($translation)) {
|
||||
return (string)array_shift($translation);
|
||||
}
|
||||
|
||||
return $translation;
|
||||
}
|
||||
|
||||
public function getBrowserLanguages($accept_langs = [])
|
||||
{
|
||||
if (empty($this->http_accept_language)) {
|
||||
if (empty($accept_langs) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
$accept_langs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
||||
} else {
|
||||
return $accept_langs;
|
||||
}
|
||||
|
||||
foreach (explode(',', $accept_langs) as $k => $pref) {
|
||||
// split $pref again by ';q='
|
||||
// and decorate the language entries by inverted position
|
||||
if (false !== ($i = strpos($pref, ';q='))) {
|
||||
$langs[substr($pref, 0, $i)] = array((float)substr($pref, $i + 3), -$k);
|
||||
} else {
|
||||
$langs[$pref] = array(1, -$k);
|
||||
}
|
||||
}
|
||||
arsort($langs);
|
||||
|
||||
// no need to undecorate, because we're only interested in the keys
|
||||
$this->http_accept_language = array_keys($langs);
|
||||
}
|
||||
return $this->http_accept_language;
|
||||
}
|
||||
|
||||
}
|
||||
770
system/src/Grav/Common/Language/LanguageCodes.php
Normal file
770
system/src/Grav/Common/Language/LanguageCodes.php
Normal file
@@ -0,0 +1,770 @@
|
||||
<?php
|
||||
namespace Grav\Common\Language;
|
||||
|
||||
/**
|
||||
* Language and translation functionality for Grav
|
||||
*/
|
||||
class LanguageCodes
|
||||
{
|
||||
protected static $codes = [
|
||||
"ab" => [
|
||||
"name" => "Abkhaz",
|
||||
"nativeName" => "аҧсуа"
|
||||
],
|
||||
"aa" => [
|
||||
"name" => "Afar",
|
||||
"nativeName" => "Afaraf"
|
||||
],
|
||||
"af" => [
|
||||
"name" => "Afrikaans",
|
||||
"nativeName" => "Afrikaans"
|
||||
],
|
||||
"ak" => [
|
||||
"name" => "Akan",
|
||||
"nativeName" => "Akan"
|
||||
],
|
||||
"sq" => [
|
||||
"name" => "Albanian",
|
||||
"nativeName" => "Shqip"
|
||||
],
|
||||
"am" => [
|
||||
"name" => "Amharic",
|
||||
"nativeName" => "አማርኛ"
|
||||
],
|
||||
"ar" => [
|
||||
"name" => "Arabic",
|
||||
"nativeName" => "العربية"
|
||||
],
|
||||
"an" => [
|
||||
"name" => "Aragonese",
|
||||
"nativeName" => "Aragonés"
|
||||
],
|
||||
"hy" => [
|
||||
"name" => "Armenian",
|
||||
"nativeName" => "Հայերեն"
|
||||
],
|
||||
"as" => [
|
||||
"name" => "Assamese",
|
||||
"nativeName" => "অসমীয়া"
|
||||
],
|
||||
"av" => [
|
||||
"name" => "Avaric",
|
||||
"nativeName" => "авар мацӀ"
|
||||
],
|
||||
"ae" => [
|
||||
"name" => "Avestan",
|
||||
"nativeName" => "avesta"
|
||||
],
|
||||
"ay" => [
|
||||
"name" => "Aymara",
|
||||
"nativeName" => "aymar aru"
|
||||
],
|
||||
"az" => [
|
||||
"name" => "Azerbaijani",
|
||||
"nativeName" => "azərbaycan dili"
|
||||
],
|
||||
"bm" => [
|
||||
"name" => "Bambara",
|
||||
"nativeName" => "bamanankan"
|
||||
],
|
||||
"ba" => [
|
||||
"name" => "Bashkir",
|
||||
"nativeName" => "башҡорт теле"
|
||||
],
|
||||
"eu" => [
|
||||
"name" => "Basque",
|
||||
"nativeName" => "euskara"
|
||||
],
|
||||
"be" => [
|
||||
"name" => "Belarusian",
|
||||
"nativeName" => "Беларуская"
|
||||
],
|
||||
"bn" => [
|
||||
"name" => "Bengali",
|
||||
"nativeName" => "বাংলা"
|
||||
],
|
||||
"bh" => [
|
||||
"name" => "Bihari",
|
||||
"nativeName" => "भोजपुरी"
|
||||
],
|
||||
"bi" => [
|
||||
"name" => "Bislama",
|
||||
"nativeName" => "Bislama"
|
||||
],
|
||||
"bs" => [
|
||||
"name" => "Bosnian",
|
||||
"nativeName" => "bosanski jezik"
|
||||
],
|
||||
"br" => [
|
||||
"name" => "Breton",
|
||||
"nativeName" => "brezhoneg"
|
||||
],
|
||||
"bg" => [
|
||||
"name" => "Bulgarian",
|
||||
"nativeName" => "български език"
|
||||
],
|
||||
"my" => [
|
||||
"name" => "Burmese",
|
||||
"nativeName" => "ဗမာစာ"
|
||||
],
|
||||
"ca" => [
|
||||
"name" => "Catalan",
|
||||
"nativeName" => "Català"
|
||||
],
|
||||
"ch" => [
|
||||
"name" => "Chamorro",
|
||||
"nativeName" => "Chamoru"
|
||||
],
|
||||
"ce" => [
|
||||
"name" => "Chechen",
|
||||
"nativeName" => "нохчийн мотт"
|
||||
],
|
||||
"ny" => [
|
||||
"name" => "Chichewa",
|
||||
"nativeName" => "chiCheŵa"
|
||||
],
|
||||
"zh" => [
|
||||
"name" => "Chinese",
|
||||
"nativeName" => "中文"
|
||||
],
|
||||
"cv" => [
|
||||
"name" => "Chuvash",
|
||||
"nativeName" => "чӑваш чӗлхи"
|
||||
],
|
||||
"kw" => [
|
||||
"name" => "Cornish",
|
||||
"nativeName" => "Kernewek"
|
||||
],
|
||||
"co" => [
|
||||
"name" => "Corsican",
|
||||
"nativeName" => "corsu"
|
||||
],
|
||||
"cr" => [
|
||||
"name" => "Cree",
|
||||
"nativeName" => "ᓀᐦᐃᔭᐍᐏᐣ"
|
||||
],
|
||||
"hr" => [
|
||||
"name" => "Croatian",
|
||||
"nativeName" => "hrvatski"
|
||||
],
|
||||
"cs" => [
|
||||
"name" => "Czech",
|
||||
"nativeName" => "česky"
|
||||
],
|
||||
"da" => [
|
||||
"name" => "Danish",
|
||||
"nativeName" => "dansk"
|
||||
],
|
||||
"dv" => [
|
||||
"name" => "Divehi",
|
||||
"nativeName" => "ދިވެހި"
|
||||
],
|
||||
"nl" => [
|
||||
"name" => "Dutch",
|
||||
"nativeName" => "Nederlands"
|
||||
],
|
||||
"en" => [
|
||||
"name" => "English",
|
||||
"nativeName" => "English"
|
||||
],
|
||||
"eo" => [
|
||||
"name" => "Esperanto",
|
||||
"nativeName" => "Esperanto"
|
||||
],
|
||||
"et" => [
|
||||
"name" => "Estonian",
|
||||
"nativeName" => "eesti"
|
||||
],
|
||||
"ee" => [
|
||||
"name" => "Ewe",
|
||||
"nativeName" => "Eʋegbe"
|
||||
],
|
||||
"fo" => [
|
||||
"name" => "Faroese",
|
||||
"nativeName" => "føroyskt"
|
||||
],
|
||||
"fj" => [
|
||||
"name" => "Fijian",
|
||||
"nativeName" => "vosa Vakaviti"
|
||||
],
|
||||
"fi" => [
|
||||
"name" => "Finnish",
|
||||
"nativeName" => "suomi"
|
||||
],
|
||||
"fr" => [
|
||||
"name" => "French",
|
||||
"nativeName" => "français"
|
||||
],
|
||||
"ff" => [
|
||||
"name" => "Fula",
|
||||
"nativeName" => "Fulfulde"
|
||||
],
|
||||
"gl" => [
|
||||
"name" => "Galician",
|
||||
"nativeName" => "Galego"
|
||||
],
|
||||
"ka" => [
|
||||
"name" => "Georgian",
|
||||
"nativeName" => "ქართული"
|
||||
],
|
||||
"de" => [
|
||||
"name" => "German",
|
||||
"nativeName" => "Deutsch"
|
||||
],
|
||||
"el" => [
|
||||
"name" => "Greek",
|
||||
"nativeName" => "Ελληνικά"
|
||||
],
|
||||
"gn" => [
|
||||
"name" => "Guaraní",
|
||||
"nativeName" => "Avañeẽ"
|
||||
],
|
||||
"gu" => [
|
||||
"name" => "Gujarati",
|
||||
"nativeName" => "ગુજરાતી"
|
||||
],
|
||||
"ht" => [
|
||||
"name" => "Haitian",
|
||||
"nativeName" => "Kreyòl ayisyen"
|
||||
],
|
||||
"ha" => [
|
||||
"name" => "Hausa",
|
||||
"nativeName" => "هَوُسَ"
|
||||
],
|
||||
"he" => [
|
||||
"name" => "Hebrew",
|
||||
"nativeName" => "עברית"
|
||||
],
|
||||
"hz" => [
|
||||
"name" => "Herero",
|
||||
"nativeName" => "Otjiherero"
|
||||
],
|
||||
"hi" => [
|
||||
"name" => "Hindi",
|
||||
"nativeName" => "हिन्दी"
|
||||
],
|
||||
"ho" => [
|
||||
"name" => "Hiri Motu",
|
||||
"nativeName" => "Hiri Motu"
|
||||
],
|
||||
"hu" => [
|
||||
"name" => "Hungarian",
|
||||
"nativeName" => "Magyar"
|
||||
],
|
||||
"ia" => [
|
||||
"name" => "Interlingua",
|
||||
"nativeName" => "Interlingua"
|
||||
],
|
||||
"id" => [
|
||||
"name" => "Indonesian",
|
||||
"nativeName" => "Bahasa Indonesia"
|
||||
],
|
||||
"ie" => [
|
||||
"name" => "Interlingue",
|
||||
"nativeName" => "Interlingue"
|
||||
],
|
||||
"ga" => [
|
||||
"name" => "Irish",
|
||||
"nativeName" => "Gaeilge"
|
||||
],
|
||||
"ig" => [
|
||||
"name" => "Igbo",
|
||||
"nativeName" => "Asụsụ Igbo"
|
||||
],
|
||||
"ik" => [
|
||||
"name" => "Inupiaq",
|
||||
"nativeName" => "Iñupiaq"
|
||||
],
|
||||
"io" => [
|
||||
"name" => "Ido",
|
||||
"nativeName" => "Ido"
|
||||
],
|
||||
"is" => [
|
||||
"name" => "Icelandic",
|
||||
"nativeName" => "Íslenska"
|
||||
],
|
||||
"it" => [
|
||||
"name" => "Italian",
|
||||
"nativeName" => "Italiano"
|
||||
],
|
||||
"iu" => [
|
||||
"name" => "Inuktitut",
|
||||
"nativeName" => "ᐃᓄᒃᑎᑐᑦ"
|
||||
],
|
||||
"ja" => [
|
||||
"name" => "Japanese",
|
||||
"nativeName" => "日本語"
|
||||
],
|
||||
"jv" => [
|
||||
"name" => "Javanese",
|
||||
"nativeName" => "basa Jawa"
|
||||
],
|
||||
"kl" => [
|
||||
"name" => "Kalaallisut",
|
||||
"nativeName" => "kalaallisut"
|
||||
],
|
||||
"kn" => [
|
||||
"name" => "Kannada",
|
||||
"nativeName" => "ಕನ್ನಡ"
|
||||
],
|
||||
"kr" => [
|
||||
"name" => "Kanuri",
|
||||
"nativeName" => "Kanuri"
|
||||
],
|
||||
"ks" => [
|
||||
"name" => "Kashmiri",
|
||||
"nativeName" => "कश्मीरी"
|
||||
],
|
||||
"kk" => [
|
||||
"name" => "Kazakh",
|
||||
"nativeName" => "Қазақ тілі"
|
||||
],
|
||||
"km" => [
|
||||
"name" => "Khmer",
|
||||
"nativeName" => "ភាសាខ្មែរ"
|
||||
],
|
||||
"ki" => [
|
||||
"name" => "Kikuyu",
|
||||
"nativeName" => "Gĩkũyũ"
|
||||
],
|
||||
"rw" => [
|
||||
"name" => "Kinyarwanda",
|
||||
"nativeName" => "Ikinyarwanda"
|
||||
],
|
||||
"ky" => [
|
||||
"name" => "Kirghiz",
|
||||
"nativeName" => "кыргыз тили"
|
||||
],
|
||||
"kv" => [
|
||||
"name" => "Komi",
|
||||
"nativeName" => "коми кыв"
|
||||
],
|
||||
"kg" => [
|
||||
"name" => "Kongo",
|
||||
"nativeName" => "KiKongo"
|
||||
],
|
||||
"ko" => [
|
||||
"name" => "Korean",
|
||||
"nativeName" => "한국어"
|
||||
],
|
||||
"ku" => [
|
||||
"name" => "Kurdish",
|
||||
"nativeName" => "كوردی"
|
||||
],
|
||||
"kj" => [
|
||||
"name" => "Kwanyama",
|
||||
"nativeName" => "Kuanyama"
|
||||
],
|
||||
"la" => [
|
||||
"name" => "Latin",
|
||||
"nativeName" => "latine"
|
||||
],
|
||||
"lb" => [
|
||||
"name" => "Luxembourgish",
|
||||
"nativeName" => "Lëtzebuergesch"
|
||||
],
|
||||
"lg" => [
|
||||
"name" => "Luganda",
|
||||
"nativeName" => "Luganda"
|
||||
],
|
||||
"li" => [
|
||||
"name" => "Limburgish",
|
||||
"nativeName" => "Limburgs"
|
||||
],
|
||||
"ln" => [
|
||||
"name" => "Lingala",
|
||||
"nativeName" => "Lingála"
|
||||
],
|
||||
"lo" => [
|
||||
"name" => "Lao",
|
||||
"nativeName" => "ພາສາລາວ"
|
||||
],
|
||||
"lt" => [
|
||||
"name" => "Lithuanian",
|
||||
"nativeName" => "lietuvių kalba"
|
||||
],
|
||||
"lu" => [
|
||||
"name" => "Luba-Katanga",
|
||||
"nativeName" => "Luba-Katanga"
|
||||
],
|
||||
"lv" => [
|
||||
"name" => "Latvian",
|
||||
"nativeName" => "latviešu valoda"
|
||||
],
|
||||
"gv" => [
|
||||
"name" => "Manx",
|
||||
"nativeName" => "Gaelg"
|
||||
],
|
||||
"mk" => [
|
||||
"name" => "Macedonian",
|
||||
"nativeName" => "македонски јазик"
|
||||
],
|
||||
"mg" => [
|
||||
"name" => "Malagasy",
|
||||
"nativeName" => "Malagasy fiteny"
|
||||
],
|
||||
"ms" => [
|
||||
"name" => "Malay",
|
||||
"nativeName" => "بهاس ملايو"
|
||||
],
|
||||
"ml" => [
|
||||
"name" => "Malayalam",
|
||||
"nativeName" => "മലയാളം"
|
||||
],
|
||||
"mt" => [
|
||||
"name" => "Maltese",
|
||||
"nativeName" => "Malti"
|
||||
],
|
||||
"mi" => [
|
||||
"name" => "Māori",
|
||||
"nativeName" => "te reo Māori"
|
||||
],
|
||||
"mr" => [
|
||||
"name" => "Marathi",
|
||||
"nativeName" => "मराठी"
|
||||
],
|
||||
"mh" => [
|
||||
"name" => "Marshallese",
|
||||
"nativeName" => "Kajin M̧ajeļ"
|
||||
],
|
||||
"mn" => [
|
||||
"name" => "Mongolian",
|
||||
"nativeName" => "монгол"
|
||||
],
|
||||
"na" => [
|
||||
"name" => "Nauru",
|
||||
"nativeName" => "Ekakairũ Naoero"
|
||||
],
|
||||
"nv" => [
|
||||
"name" => "Navajo",
|
||||
"nativeName" => "Diné bizaad"
|
||||
],
|
||||
"nb" => [
|
||||
"name" => "Norwegian Bokmål",
|
||||
"nativeName" => "Norsk bokmål"
|
||||
],
|
||||
"nd" => [
|
||||
"name" => "North Ndebele",
|
||||
"nativeName" => "isiNdebele"
|
||||
],
|
||||
"ne" => [
|
||||
"name" => "Nepali",
|
||||
"nativeName" => "नेपाली"
|
||||
],
|
||||
"ng" => [
|
||||
"name" => "Ndonga",
|
||||
"nativeName" => "Owambo"
|
||||
],
|
||||
"nn" => [
|
||||
"name" => "Norwegian Nynorsk",
|
||||
"nativeName" => "Norsk nynorsk"
|
||||
],
|
||||
"no" => [
|
||||
"name" => "Norwegian",
|
||||
"nativeName" => "Norsk"
|
||||
],
|
||||
"ii" => [
|
||||
"name" => "Nuosu",
|
||||
"nativeName" => "ꆈꌠ꒿ Nuosuhxop"
|
||||
],
|
||||
"nr" => [
|
||||
"name" => "South Ndebele",
|
||||
"nativeName" => "isiNdebele"
|
||||
],
|
||||
"oc" => [
|
||||
"name" => "Occitan",
|
||||
"nativeName" => "Occitan"
|
||||
],
|
||||
"oj" => [
|
||||
"name" => "Ojibwe, Ojibwa",
|
||||
"nativeName" => "ᐊᓂᔑᓈᐯᒧᐎᓐ"
|
||||
],
|
||||
"cu" => [
|
||||
"name" => "Church Slavic",
|
||||
"nativeName" => "ѩзыкъ словѣньскъ"
|
||||
],
|
||||
"om" => [
|
||||
"name" => "Oromo",
|
||||
"nativeName" => "Afaan Oromoo"
|
||||
],
|
||||
"or" => [
|
||||
"name" => "Oriya",
|
||||
"nativeName" => "ଓଡ଼ିଆ"
|
||||
],
|
||||
"os" => [
|
||||
"name" => "Ossetian",
|
||||
"nativeName" => "ирон æвзаг"
|
||||
],
|
||||
"pa" => [
|
||||
"name" => "Panjabi",
|
||||
"nativeName" => "ਪੰਜਾਬੀ"
|
||||
],
|
||||
"pi" => [
|
||||
"name" => "Pāli",
|
||||
"nativeName" => "पाऴि"
|
||||
],
|
||||
"fa" => [
|
||||
"name" => "Persian",
|
||||
"nativeName" => "فارسی"
|
||||
],
|
||||
"pl" => [
|
||||
"name" => "Polish",
|
||||
"nativeName" => "polski"
|
||||
],
|
||||
"ps" => [
|
||||
"name" => "Pashto",
|
||||
"nativeName" => "پښتو"
|
||||
],
|
||||
"pt" => [
|
||||
"name" => "Portuguese",
|
||||
"nativeName" => "Português"
|
||||
],
|
||||
"qu" => [
|
||||
"name" => "Quechua",
|
||||
"nativeName" => "Runa Simi"
|
||||
],
|
||||
"rm" => [
|
||||
"name" => "Romansh",
|
||||
"nativeName" => "rumantsch grischun"
|
||||
],
|
||||
"rn" => [
|
||||
"name" => "Kirundi",
|
||||
"nativeName" => "kiRundi"
|
||||
],
|
||||
"ro" => [
|
||||
"name" => "Romanian",
|
||||
"nativeName" => "română"
|
||||
],
|
||||
"ru" => [
|
||||
"name" => "Russian",
|
||||
"nativeName" => "русский язык"
|
||||
],
|
||||
"sa" => [
|
||||
"name" => "Sanskrit",
|
||||
"nativeName" => "संस्कृतम्"
|
||||
],
|
||||
"sc" => [
|
||||
"name" => "Sardinian",
|
||||
"nativeName" => "sardu"
|
||||
],
|
||||
"sd" => [
|
||||
"name" => "Sindhi",
|
||||
"nativeName" => "सिन्धी"
|
||||
],
|
||||
"se" => [
|
||||
"name" => "Northern Sami",
|
||||
"nativeName" => "Davvisámegiella"
|
||||
],
|
||||
"sm" => [
|
||||
"name" => "Samoan",
|
||||
"nativeName" => "gagana faa Samoa"
|
||||
],
|
||||
"sg" => [
|
||||
"name" => "Sango",
|
||||
"nativeName" => "yângâ tî sängö"
|
||||
],
|
||||
"sr" => [
|
||||
"name" => "Serbian",
|
||||
"nativeName" => "српски језик"
|
||||
],
|
||||
"gd" => [
|
||||
"name" => "Scottish Gaelic",
|
||||
"nativeName" => "Gàidhlig"
|
||||
],
|
||||
"sn" => [
|
||||
"name" => "Shona",
|
||||
"nativeName" => "chiShona"
|
||||
],
|
||||
"si" => [
|
||||
"name" => "Sinhala",
|
||||
"nativeName" => "සිංහල"
|
||||
],
|
||||
"sk" => [
|
||||
"name" => "Slovak",
|
||||
"nativeName" => "slovenčina"
|
||||
],
|
||||
"sl" => [
|
||||
"name" => "Slovene",
|
||||
"nativeName" => "slovenščina"
|
||||
],
|
||||
"so" => [
|
||||
"name" => "Somali",
|
||||
"nativeName" => "Soomaaliga"
|
||||
],
|
||||
"st" => [
|
||||
"name" => "Southern Sotho",
|
||||
"nativeName" => "Sesotho"
|
||||
],
|
||||
"es" => [
|
||||
"name" => "Spanish",
|
||||
"nativeName" => "español"
|
||||
],
|
||||
"su" => [
|
||||
"name" => "Sundanese",
|
||||
"nativeName" => "Basa Sunda"
|
||||
],
|
||||
"sw" => [
|
||||
"name" => "Swahili",
|
||||
"nativeName" => "Kiswahili"
|
||||
],
|
||||
"ss" => [
|
||||
"name" => "Swati",
|
||||
"nativeName" => "SiSwati"
|
||||
],
|
||||
"sv" => [
|
||||
"name" => "Swedish",
|
||||
"nativeName" => "svenska"
|
||||
],
|
||||
"ta" => [
|
||||
"name" => "Tamil",
|
||||
"nativeName" => "தமிழ்"
|
||||
],
|
||||
"te" => [
|
||||
"name" => "Telugu",
|
||||
"nativeName" => "తెలుగు"
|
||||
],
|
||||
"tg" => [
|
||||
"name" => "Tajik",
|
||||
"nativeName" => "тоҷикӣ"
|
||||
],
|
||||
"th" => [
|
||||
"name" => "Thai",
|
||||
"nativeName" => "ไทย"
|
||||
],
|
||||
"ti" => [
|
||||
"name" => "Tigrinya",
|
||||
"nativeName" => "ትግርኛ"
|
||||
],
|
||||
"bo" => [
|
||||
"name" => "Tibetan",
|
||||
"nativeName" => "བོད་ཡིག"
|
||||
],
|
||||
"tk" => [
|
||||
"name" => "Turkmen",
|
||||
"nativeName" => "Türkmen"
|
||||
],
|
||||
"tl" => [
|
||||
"name" => "Tagalog",
|
||||
"nativeName" => "Wikang Tagalog"
|
||||
],
|
||||
"tn" => [
|
||||
"name" => "Tswana",
|
||||
"nativeName" => "Setswana"
|
||||
],
|
||||
"to" => [
|
||||
"name" => "Tonga",
|
||||
"nativeName" => "faka Tonga"
|
||||
],
|
||||
"tr" => [
|
||||
"name" => "Turkish",
|
||||
"nativeName" => "Türkçe"
|
||||
],
|
||||
"ts" => [
|
||||
"name" => "Tsonga",
|
||||
"nativeName" => "Xitsonga"
|
||||
],
|
||||
"tt" => [
|
||||
"name" => "Tatar",
|
||||
"nativeName" => "татарча"
|
||||
],
|
||||
"tw" => [
|
||||
"name" => "Twi",
|
||||
"nativeName" => "Twi"
|
||||
],
|
||||
"ty" => [
|
||||
"name" => "Tahitian",
|
||||
"nativeName" => "Reo Tahiti"
|
||||
],
|
||||
"ug" => [
|
||||
"name" => "Uighur",
|
||||
"nativeName" => "Uyƣurqə"
|
||||
],
|
||||
"uk" => [
|
||||
"name" => "Ukrainian",
|
||||
"nativeName" => "українська"
|
||||
],
|
||||
"ur" => [
|
||||
"name" => "Urdu",
|
||||
"nativeName" => "اردو"
|
||||
],
|
||||
"uz" => [
|
||||
"name" => "Uzbek",
|
||||
"nativeName" => "zbek"
|
||||
],
|
||||
"ve" => [
|
||||
"name" => "Venda",
|
||||
"nativeName" => "Tshivenḓa"
|
||||
],
|
||||
"vi" => [
|
||||
"name" => "Vietnamese",
|
||||
"nativeName" => "Tiếng Việt"
|
||||
],
|
||||
"vo" => [
|
||||
"name" => "Volapük",
|
||||
"nativeName" => "Volapük"
|
||||
],
|
||||
"wa" => [
|
||||
"name" => "Walloon",
|
||||
"nativeName" => "Walon"
|
||||
],
|
||||
"cy" => [
|
||||
"name" => "Welsh",
|
||||
"nativeName" => "Cymraeg"
|
||||
],
|
||||
"wo" => [
|
||||
"name" => "Wolof",
|
||||
"nativeName" => "Wollof"
|
||||
],
|
||||
"fy" => [
|
||||
"name" => "Western Frisian",
|
||||
"nativeName" => "Frysk"
|
||||
],
|
||||
"xh" => [
|
||||
"name" => "Xhosa",
|
||||
"nativeName" => "isiXhosa"
|
||||
],
|
||||
"yi" => [
|
||||
"name" => "Yiddish",
|
||||
"nativeName" => "ייִדיש"
|
||||
],
|
||||
"yo" => [
|
||||
"name" => "Yoruba",
|
||||
"nativeName" => "Yorùbá"
|
||||
],
|
||||
"za" => [
|
||||
"name" => "Zhuang",
|
||||
"nativeName" => "Saɯ cueŋƅ"
|
||||
]
|
||||
];
|
||||
|
||||
public static function getName($code)
|
||||
{
|
||||
return static::get($code, 'name');
|
||||
}
|
||||
|
||||
public static function getNativeName($code)
|
||||
{
|
||||
return static::get($code, 'nativeName');
|
||||
}
|
||||
|
||||
public static function getNames(array $keys)
|
||||
{
|
||||
$results = [];
|
||||
foreach ($keys as $key) {
|
||||
if (isset(static::$codes[$key])) {
|
||||
$results[$key] = static::$codes[$key];
|
||||
}
|
||||
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected static function get($code, $type)
|
||||
{
|
||||
if (isset(static::$codes[$code][$type])) {
|
||||
return static::$codes[$code][$type];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ class Parsedown extends \Parsedown
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
public function __construct($page)
|
||||
public function __construct($page, $defaults)
|
||||
{
|
||||
$this->init($page);
|
||||
$this->init($page, $defaults);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ class ParsedownExtra extends \ParsedownExtra
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
public function __construct($page)
|
||||
public function __construct($page, $defaults)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->init($page);
|
||||
$this->init($page, $defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
@@ -22,11 +23,12 @@ trait ParsedownGravTrait
|
||||
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
|
||||
|
||||
/**
|
||||
* Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait
|
||||
* Initialization function to setup key variables needed by the MarkdownGravLinkTrait
|
||||
*
|
||||
* @param $page
|
||||
* @param $defaults
|
||||
*/
|
||||
protected function init($page)
|
||||
protected function init($page, $defaults)
|
||||
{
|
||||
$this->page = $page;
|
||||
$this->pages = self::getGrav()['pages'];
|
||||
@@ -35,7 +37,9 @@ trait ParsedownGravTrait
|
||||
$this->pages_dir = self::getGrav()['locator']->findResource('page://');
|
||||
$this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
|
||||
|
||||
$defaults = self::getGrav()['config']->get('system.pages.markdown');
|
||||
if ($defaults === null) {
|
||||
$defaults = self::getGrav()['config']->get('system.pages.markdown');
|
||||
}
|
||||
|
||||
$this->setBreaksEnabled($defaults['auto_line_breaks']);
|
||||
$this->setUrlsLinked($defaults['auto_url_links']);
|
||||
@@ -116,7 +120,6 @@ trait ParsedownGravTrait
|
||||
|
||||
// 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'] : '';
|
||||
@@ -124,19 +127,16 @@ trait ParsedownGravTrait
|
||||
//get the url and parse it
|
||||
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['src']));
|
||||
|
||||
$path_parts = pathinfo($url['path']);
|
||||
|
||||
// if there is no host set but there is a path, the file is local
|
||||
if (!isset($url['host']) && isset($url['path'])) {
|
||||
$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']), '/');
|
||||
$url['path'] = $path_parts['basename'];
|
||||
// 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']);
|
||||
|
||||
@@ -204,52 +204,10 @@ trait ParsedownGravTrait
|
||||
// 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));
|
||||
$excerpt['element']['attributes']['href'] = Uri::convertUrl($this->page, 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 != '' && strpos($markdown_url, $this->base_url) === 0) {
|
||||
return $markdown_url;
|
||||
// if its absolute and starts with /
|
||||
} elseif (strpos($markdown_url, '/') === 0) {
|
||||
return $this->base_url . $markdown_url;
|
||||
} else {
|
||||
$relative_path = $this->base_url . $this->page->route();
|
||||
$real_path = $this->page->path() . '/' . parse_url($markdown_url, PHP_URL_PATH);
|
||||
|
||||
// strip numeric order from markdown path
|
||||
if (($real_path)) {
|
||||
$markdown_url = preg_replace('/^([\d]+\.)/', '', preg_replace('/\/([\d]+\.)/', '/', trim(preg_replace('/[^\/]+(\.md$)/', '', $markdown_url), '/')));
|
||||
}
|
||||
|
||||
// else its a relative path already
|
||||
$newpath = array();
|
||||
$paths = explode('/', $markdown_url);
|
||||
|
||||
// remove the updirectory references (..)
|
||||
foreach ($paths as $path) {
|
||||
if ($path == '..') {
|
||||
$relative_path = dirname($relative_path);
|
||||
} else {
|
||||
$newpath[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// build the new url
|
||||
$new_url = rtrim($relative_path, '/') . '/' . implode('/', $newpath);
|
||||
}
|
||||
|
||||
return $new_url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,12 +361,13 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $routable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ 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' || $info->getBasename() === '.DS_Store') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -58,27 +58,36 @@ class Media extends Getters
|
||||
|
||||
if ($type === 'alternative') {
|
||||
$media["{$basename}.{$ext}"][$type] = isset($media["{$basename}.{$ext}"][$type]) ? $media["{$basename}.{$ext}"][$type] : [];
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = $info->getPathname();
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = [ 'file' => $path, 'size' => $info->getSize() ];
|
||||
} else {
|
||||
$media["{$basename}.{$ext}"][$type] = $info->getPathname();
|
||||
$media["{$basename}.{$ext}"][$type] = [ 'file' => $path, 'size' => $info->getSize() ];
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
foreach ($types['alternative'] as $ratio => &$alt) {
|
||||
$alt['file'] = MediumFactory::fromFile($alt['file']);
|
||||
|
||||
if (!$alt['file']) {
|
||||
unset($types['alternative'][$ratio]);
|
||||
} else {
|
||||
$alt['file']->set('size', $alt['size']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the base medium
|
||||
if (!empty($types['base'])) {
|
||||
$medium = MediumFactory::fromFile($types['base']);
|
||||
$medium = MediumFactory::fromFile($types['base']['file']);
|
||||
$medium && $medium->set('size', $types['base']['size']);
|
||||
} else if (!empty($types['alternative'])) {
|
||||
$altMedium = reset($types['alternative']);
|
||||
$ratio = key($types['alternative']);
|
||||
|
||||
$altMedium = $altMedium['file'];
|
||||
|
||||
$medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1);
|
||||
}
|
||||
|
||||
@@ -87,13 +96,13 @@ class Media extends Getters
|
||||
}
|
||||
|
||||
if (!empty($types['meta'])) {
|
||||
$medium->addMetaFile($types['meta']);
|
||||
$medium->addMetaFile($types['meta']['file']);
|
||||
}
|
||||
|
||||
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']);
|
||||
$medium->set('thumbnails.page', $types['thumb']['file']);
|
||||
}
|
||||
|
||||
// Build missing alternatives
|
||||
@@ -111,7 +120,7 @@ class Media extends Getters
|
||||
}
|
||||
|
||||
foreach ($types['alternative'] as $ratio => $altMedium) {
|
||||
$medium->addAlternative($ratio, $altMedium);
|
||||
$medium->addAlternative($ratio, $altMedium['file']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
84
system/src/Grav/Common/Page/Medium/ImageFile.php
Normal file
84
system/src/Grav/Common/Page/Medium/ImageFile.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Gregwar\Image\Exceptions\GenerationError;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class ImageFile extends \Gregwar\Image\Image
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file
|
||||
*
|
||||
* @param string $type the image type
|
||||
* @param int $quality the quality (for JPEG)
|
||||
* @param bool $actual
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
|
||||
{
|
||||
if ($type == 'guess') {
|
||||
$type = $this->guessType();
|
||||
}
|
||||
|
||||
if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) {
|
||||
return $this->getFilename($this->getFilePath());
|
||||
}
|
||||
|
||||
// Computes the hash
|
||||
$this->hash = $this->getHash($type, $quality);
|
||||
|
||||
// Generates the cache file
|
||||
$cacheFile = '';
|
||||
|
||||
if (!$this->prettyName || $this->prettyPrefix) {
|
||||
$cacheFile .= $this->hash;
|
||||
}
|
||||
|
||||
if ($this->prettyPrefix) {
|
||||
$cacheFile .= '-';
|
||||
}
|
||||
|
||||
if ($this->prettyName) {
|
||||
$cacheFile .= $this->prettyName;
|
||||
}
|
||||
|
||||
$cacheFile .= '.'.$type;
|
||||
|
||||
// If the files does not exists, save it
|
||||
$image = $this;
|
||||
|
||||
// Target file should be younger than all the current image
|
||||
// dependencies
|
||||
$conditions = array(
|
||||
'younger-than' => $this->getDependencies()
|
||||
);
|
||||
|
||||
// The generating function
|
||||
$generate = function ($target) use ($image, $type, $quality) {
|
||||
$result = $image->save($target, $type, $quality);
|
||||
|
||||
if ($result != $target) {
|
||||
throw new GenerationError($result);
|
||||
}
|
||||
|
||||
self::getGrav()->fireEvent('onImageMediumSaved', new Event(['image' => $target]));
|
||||
};
|
||||
|
||||
// Asking the cache for the cacheFile
|
||||
try {
|
||||
$file = $this->cache->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
|
||||
} catch (GenerationError $e) {
|
||||
$file = $e->getNewFile();
|
||||
}
|
||||
|
||||
if ($actual) {
|
||||
return $file;
|
||||
} else {
|
||||
return $this->getFilename($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Gregwar\Image\Image as ImageFile;
|
||||
|
||||
class ImageMedium extends Medium
|
||||
{
|
||||
@@ -42,7 +41,7 @@ class ImageMedium extends Medium
|
||||
public static $magic_actions = [
|
||||
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
|
||||
'negate', 'brightness', 'contrast', 'grayscale', 'emboss',
|
||||
'smooth', 'sharp', 'edge', 'colorize', 'sepia'
|
||||
'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -67,17 +66,23 @@ class ImageMedium extends Medium
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
$config = self::$grav['config'];
|
||||
|
||||
$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->def('debug', $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->default_quality = $config->get('system.images.default_image_quality', 85);
|
||||
|
||||
$this->reset();
|
||||
|
||||
if ($config->get('system.images.cache_all', false)) {
|
||||
$this->cache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +132,20 @@ class ImageMedium extends Medium
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return self::$grav['base_url'] . $output . $this->urlHash();
|
||||
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply processes with no extra methods. Useful for triggering events.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cache()
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -216,7 +234,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium inta a Link with lightbox enabled
|
||||
* Turn the current Medium into a Link with lightbox enabled
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
@@ -299,7 +317,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
if (!in_array($method, self::$magic_actions)) {
|
||||
return $this;
|
||||
return parent::__call($method, $args);
|
||||
}
|
||||
|
||||
// Always initialize image.
|
||||
@@ -308,7 +326,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
try {
|
||||
$result = call_user_func_array([$this->image, $method], $args);
|
||||
call_user_func_array([$this->image, $method], $args);
|
||||
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$args_copy = $args;
|
||||
@@ -364,6 +382,10 @@ class ImageMedium extends Medium
|
||||
return parent::path(false);
|
||||
}
|
||||
|
||||
if (isset($this->result)) {
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
if ($this->get('debug') && !$this->debug_watermarked) {
|
||||
$ratio = $this->get('ratio');
|
||||
if (!$ratio) {
|
||||
@@ -375,9 +397,7 @@ class ImageMedium extends Medium
|
||||
$this->image->merge(ImageFile::open($overlay));
|
||||
}
|
||||
|
||||
$result = $this->image->cacheFile($this->format, $this->quality);
|
||||
|
||||
return $result;
|
||||
return $this->image->cacheFile($this->format, $this->quality);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,6 +60,6 @@ class Link implements RenderableInterface
|
||||
|
||||
// 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;
|
||||
return $this->source instanceof Link ? $this->source : $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ class Medium extends Data implements RenderableInterface
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
if (self::getGrav()['config']->get('system.media.enable_media_timestamp', true)) {
|
||||
$this->querystring('&' . self::getGrav()['cache']->getKey());
|
||||
}
|
||||
|
||||
$this->def('mime', 'application/octet-stream');
|
||||
$this->reset();
|
||||
}
|
||||
@@ -129,7 +133,33 @@ class Medium extends Data implements RenderableInterface
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return self::$grav['base_url'] . $output . $this->urlHash();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,7 +339,7 @@ class Medium extends Data implements RenderableInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium inta a Link with lightbox enabled
|
||||
* Turn the current Medium into a Link with lightbox enabled
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
@@ -337,6 +367,17 @@ class Medium extends Data implements RenderableInterface
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -358,7 +399,7 @@ class Medium extends Data implements RenderableInterface
|
||||
$thumb = $this->get('thumbnails.' . $type, false);
|
||||
|
||||
if ($thumb) {
|
||||
$thumb = $thumb instanceof ThumbnailMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
|
||||
$thumb = $thumb instanceof ThumbnailImageMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
|
||||
$thumb->parent = $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,8 +131,7 @@ class MediumFactory
|
||||
$debug = $medium->get('debug');
|
||||
$medium->set('debug', false);
|
||||
|
||||
$file = $medium->resize($width, $height)->setPrettyName($basename)->url();
|
||||
$file = preg_replace('|'. preg_quote(self::getGrav()['base_url_relative']) .'$|', '', GRAV_ROOT) . $file;
|
||||
$file = $medium->resize($width, $height)->path();
|
||||
|
||||
$medium->set('debug', $debug);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ trait ParsedownHtmlTrait
|
||||
$element = $this->parsedownElement($title, $alt, $class, $reset);
|
||||
|
||||
if (!$this->parsedown) {
|
||||
$this->parsedown = new Parsedown(null);
|
||||
$this->parsedown = new Parsedown(null, null);
|
||||
}
|
||||
|
||||
return $this->parsedown->elementToHtml($element);
|
||||
|
||||
@@ -90,7 +90,7 @@ class ThumbnailImageMedium extends ImageMedium
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the current Medium inta a Link with lightbox enabled
|
||||
* Turn the current Medium into a Link with lightbox enabled
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
|
||||
@@ -7,7 +7,6 @@ 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
|
||||
{
|
||||
|
||||
@@ -34,32 +34,31 @@ class Page
|
||||
* @var string Filename. Leave as null if page is folder.
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string Folder name.
|
||||
*/
|
||||
protected $folder;
|
||||
|
||||
/**
|
||||
* @var string Path to the folder. Add $this->folder to get full path.
|
||||
*/
|
||||
protected $path;
|
||||
protected $extension;
|
||||
|
||||
protected $parent;
|
||||
protected $template;
|
||||
protected $expires;
|
||||
protected $visible;
|
||||
protected $published;
|
||||
protected $publish_date;
|
||||
protected $unpublish_date;
|
||||
protected $slug;
|
||||
protected $route;
|
||||
protected $raw_route;
|
||||
protected $url;
|
||||
protected $routes;
|
||||
protected $routable;
|
||||
protected $modified;
|
||||
protected $id;
|
||||
protected $items;
|
||||
protected $header;
|
||||
protected $frontmatter;
|
||||
protected $language;
|
||||
protected $content;
|
||||
protected $summary;
|
||||
protected $raw_content;
|
||||
protected $pagination;
|
||||
protected $media;
|
||||
@@ -77,6 +76,8 @@ class Page
|
||||
protected $process;
|
||||
protected $summary_size;
|
||||
protected $markdown_extra;
|
||||
protected $etag;
|
||||
protected $last_modified;
|
||||
|
||||
/**
|
||||
* @var Page Unmodified (original) version of the page. Used for copying and moving the page.
|
||||
@@ -106,9 +107,9 @@ class Page
|
||||
* Initializes the page instance variables based on a file
|
||||
*
|
||||
* @param \SplFileInfo $file The file information for the .md file that the page represents
|
||||
* @return void
|
||||
* @param string $extension
|
||||
*/
|
||||
public function init(\SplFileInfo $file)
|
||||
public function init(\SplFileInfo $file, $extension = null)
|
||||
{
|
||||
$this->filePath($file->getPathName());
|
||||
$this->modified($file->getMTime());
|
||||
@@ -116,28 +117,21 @@ class Page
|
||||
$this->header();
|
||||
$this->date();
|
||||
$this->metadata();
|
||||
$this->slug();
|
||||
$this->url();
|
||||
$this->visible();
|
||||
$this->modularTwig($this->slug[0] == '_');
|
||||
|
||||
// Handle publishing dates if no explict published option set
|
||||
if (self::getGrav()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
|
||||
// unpublish if required, if not clear cache right before page should be unpublished
|
||||
if ($this->unpublishDate()) {
|
||||
if ($this->unpublishDate() < time()) {
|
||||
$this->published(false);
|
||||
} else {
|
||||
$this->published();
|
||||
self::getGrav()['cache']->setLifeTime($this->unpublishDate());
|
||||
}
|
||||
}
|
||||
// publish if required, if not clear cache right before page is published
|
||||
if ($this->publishDate() != $this->modified() && $this->publishDate() > time()) {
|
||||
$this->published(false);
|
||||
self::getGrav()['cache']->setLifeTime($this->publishDate());
|
||||
}
|
||||
}
|
||||
$this->setPublishState();
|
||||
$this->published();
|
||||
|
||||
if (empty($extension)) {
|
||||
$this->extension('.'.$file->getExtension());
|
||||
} else {
|
||||
$this->extension($extension);
|
||||
}
|
||||
|
||||
// Exract page language from page extension
|
||||
$language = trim(basename($this->extension(), 'md'), '.') ?: null;
|
||||
$this->language($language);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,6 +155,7 @@ class Page
|
||||
$this->id($this->modified().md5($this->filePath()));
|
||||
$this->header = null;
|
||||
$this->content = null;
|
||||
$this->summary = null;
|
||||
}
|
||||
return $file ? $file->raw() : '';
|
||||
}
|
||||
@@ -221,9 +216,15 @@ class Page
|
||||
if (isset($this->header->slug)) {
|
||||
$this->slug = trim($this->header->slug);
|
||||
}
|
||||
if (isset($this->header->routes)) {
|
||||
$this->routes = (array)($this->header->routes);
|
||||
}
|
||||
if (isset($this->header->title)) {
|
||||
$this->title = trim($this->header->title);
|
||||
}
|
||||
if (isset($this->header->language)) {
|
||||
$this->language = trim($this->header->language);
|
||||
}
|
||||
if (isset($this->header->template)) {
|
||||
$this->template = trim($this->header->template);
|
||||
}
|
||||
@@ -273,11 +274,37 @@ class Page
|
||||
if (isset($this->header->unpublish_date)) {
|
||||
$this->unpublish_date = strtotime($this->header->unpublish_date);
|
||||
}
|
||||
if (isset($this->header->expires)) {
|
||||
$this->expires = intval($this->header->expires);
|
||||
}
|
||||
if (isset($this->header->etag)) {
|
||||
$this->etag = (bool)$this->header->etag;
|
||||
}
|
||||
if (isset($this->header->last_modified)) {
|
||||
$this->last_modified = (bool)$this->header->last_modified;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page language
|
||||
*
|
||||
* @param $var
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function language($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->language = $var;
|
||||
}
|
||||
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a header value directly
|
||||
*
|
||||
@@ -292,32 +319,43 @@ class Page
|
||||
/**
|
||||
* Get the summary.
|
||||
*
|
||||
* @param int $size Max summary size.
|
||||
* @param int $size Max summary size.
|
||||
* @return string
|
||||
*/
|
||||
public function summary($size = null)
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = self::getGrav()['config'];
|
||||
$content = $this->content();
|
||||
$config = self::getGrav()['config']->get('site.summary');
|
||||
if (isset($this->header->summary)) {
|
||||
$config = array_merge($config, $this->header->summary);
|
||||
}
|
||||
|
||||
// Return summary based on settings in site config file
|
||||
if (!$config->get('site.summary.enabled', true)) {
|
||||
if (!$config['enabled']) {
|
||||
return $this->content();
|
||||
}
|
||||
|
||||
// Set up variables to process summary from page or from custom summary
|
||||
if ($this->summary === null) {
|
||||
$content = $this->content();
|
||||
$summary_size = $this->summary_size;
|
||||
} else {
|
||||
$content = $this->summary;
|
||||
$summary_size = mb_strlen($this->summary);
|
||||
}
|
||||
|
||||
// Return calculated summary based on summary divider's position
|
||||
$format = $config['format'];
|
||||
// Return entire page content on wrong/ unknown format
|
||||
if (!in_array($format, array('short', 'long'))) {
|
||||
return $content;
|
||||
} elseif (($format === 'short') && isset($summary_size)) {
|
||||
return mb_substr($content, 0, $summary_size);
|
||||
}
|
||||
|
||||
// Get summary size from site config's file
|
||||
if (is_null($size)) {
|
||||
$size = $config->get('site.summary.size', null);
|
||||
}
|
||||
|
||||
// Return calculated summary based on summary divider's position
|
||||
$format = $config->get('site.summary.format', 'short');
|
||||
// Return entire page content on wrong/ unknown format
|
||||
if (!in_array($format, array('short', 'long'))) {
|
||||
return $content;
|
||||
} elseif (($format === 'short') && isset($this->summary_size)) {
|
||||
return substr($content, 0, $this->summary_size);
|
||||
$size = $config['size'];
|
||||
}
|
||||
|
||||
// If the size is zero, return the entire page content
|
||||
@@ -331,6 +369,16 @@ class Page
|
||||
return Utils::truncateHTML($content, $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the summary of the page
|
||||
*
|
||||
* @param string $var Summary
|
||||
*/
|
||||
public function setSummary($summary)
|
||||
{
|
||||
$this->summary = $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and Sets the content based on content portion of the .md file
|
||||
*
|
||||
@@ -352,7 +400,6 @@ class Page
|
||||
$this->id(time().md5($this->filePath()));
|
||||
$this->content = null;
|
||||
}
|
||||
|
||||
// If no content, process it
|
||||
if ($this->content === null) {
|
||||
// Get media
|
||||
@@ -411,7 +458,7 @@ class Page
|
||||
|
||||
// Handle summary divider
|
||||
$delimiter = self::getGrav()['config']->get('site.summary.delimiter', '===');
|
||||
$divider_pos = strpos($this->content, "<p>{$delimiter}</p>");
|
||||
$divider_pos = mb_strpos($this->content, "<p>{$delimiter}</p>");
|
||||
if ($divider_pos !== false) {
|
||||
$this->summary_size = $divider_pos;
|
||||
$this->content = str_replace("<p>{$delimiter}</p>", '', $this->content);
|
||||
@@ -442,9 +489,9 @@ class Page
|
||||
|
||||
// Initialize the preferred variant of Parsedown
|
||||
if ($defaults['extra']) {
|
||||
$parsedown = new ParsedownExtra($this);
|
||||
$parsedown = new ParsedownExtra($this, $defaults);
|
||||
} else {
|
||||
$parsedown = new Parsedown($this);
|
||||
$parsedown = new Parsedown($this, $defaults);
|
||||
}
|
||||
|
||||
$this->content = $parsedown->text($this->content);
|
||||
@@ -516,7 +563,7 @@ class Page
|
||||
return preg_replace($regex, '', $this->folder);
|
||||
}
|
||||
if ($name == 'type') {
|
||||
return basename($this->name(), '.md');
|
||||
return $this->template();
|
||||
}
|
||||
if ($name == 'media') {
|
||||
return $this->media()->all();
|
||||
@@ -559,6 +606,15 @@ class Page
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function rawMarkdown($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->raw_content = $var;
|
||||
}
|
||||
|
||||
return $this->raw_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file object to the page.
|
||||
*
|
||||
@@ -610,9 +666,12 @@ class Page
|
||||
if ($parent->path()) {
|
||||
$clone->path($parent->path() . '/' . $clone->folder());
|
||||
}
|
||||
|
||||
// TODO: make sure we always have the route.
|
||||
if ($parent->route()) {
|
||||
$clone->route($parent->route() . '/'. $clone->slug());
|
||||
} else {
|
||||
$clone->route(self::getGrav()['pages']->root()->route() . '/'. $clone->slug());
|
||||
}
|
||||
|
||||
return $clone;
|
||||
@@ -645,7 +704,33 @@ class Page
|
||||
/** @var Pages $pages */
|
||||
$pages = self::getGrav()['pages'];
|
||||
|
||||
return $pages->blueprints($this->template());
|
||||
$blueprint = $pages->blueprints($this->blueprintName());
|
||||
|
||||
$fields = $blueprint->fields();
|
||||
|
||||
// override if you only want 'normal' mode
|
||||
if (empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'normal') {
|
||||
$blueprint = $pages->blueprints('default');
|
||||
}
|
||||
|
||||
// override if you only want 'expert' mode
|
||||
if (!empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'expert') {
|
||||
$blueprint = $pages->blueprints('');
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blueprint name for this page. Use the blueprint form field if set
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function blueprintName()
|
||||
{
|
||||
$blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template();
|
||||
|
||||
return $blueprint_name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -677,7 +762,7 @@ class Page
|
||||
public function extra()
|
||||
{
|
||||
$blueprints = $this->blueprints();
|
||||
return $blueprints->extra($this->toArray(), 'header.');
|
||||
return $blueprints->extra($this->toArray()['header'], 'header.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -776,11 +861,42 @@ class Page
|
||||
$this->template = $var;
|
||||
}
|
||||
if (empty($this->template)) {
|
||||
$this->template = ($this->modular() ? 'modular/' : '') . str_replace(CONTENT_EXT, '', $this->name());
|
||||
$this->template = ($this->modular() ? 'modular/' : '') . str_replace($this->extension(), '', $this->name());
|
||||
}
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the extension field.
|
||||
*
|
||||
* @param null $var
|
||||
* @return null|string
|
||||
*/
|
||||
public function extension($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->extension = $var;
|
||||
}
|
||||
if (empty($this->extension)) {
|
||||
$this->extension = '.' . pathinfo($this->name(), PATHINFO_EXTENSION);
|
||||
}
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the expires field. If not set will return the default
|
||||
*
|
||||
* @param string $var The name of this page.
|
||||
* @return string The name of this page.
|
||||
*/
|
||||
public function expires($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->expires = $var;
|
||||
}
|
||||
return empty($this->expires) ? self::getGrav()['config']->get('system.pages.expires') : $this->expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name
|
||||
*
|
||||
@@ -897,7 +1013,8 @@ class Page
|
||||
|
||||
/**
|
||||
* Gets and Sets whether or not this Page is routable, ie you can reach it
|
||||
* via a URL
|
||||
* via a URL.
|
||||
* The page must be *routable* and *published*
|
||||
*
|
||||
* @param bool $var true if the page is routable
|
||||
* @return bool true if the page is routable
|
||||
@@ -907,7 +1024,7 @@ class Page
|
||||
if ($var !== null) {
|
||||
$this->routable = (bool) $var;
|
||||
}
|
||||
return $this->routable;
|
||||
return $this->routable && $this->published();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -941,38 +1058,34 @@ class Page
|
||||
// if not metadata yet, process it.
|
||||
if (null === $this->metadata) {
|
||||
$header_tag_http_equivs = ['content-type', 'default-style', 'refresh'];
|
||||
$this->metadata = array();
|
||||
$page_header = $this->header;
|
||||
|
||||
$this->metadata = [];
|
||||
|
||||
// Set the Generator tag
|
||||
$this->metadata['generator'] = array('name'=>'generator', 'content'=>'Grav ' . GRAV_VERSION);
|
||||
$this->metadata['generator'] = array('name'=>'generator', 'content'=>'GravCMS ' . GRAV_VERSION);
|
||||
|
||||
// Safety check to ensure we have a header
|
||||
if ($page_header) {
|
||||
// Get initial metadata for the page
|
||||
$metadata = self::getGrav()['config']->get('site.metadata');
|
||||
|
||||
if (isset($this->header->metadata)) {
|
||||
// Merge any site.metadata settings in with page metadata
|
||||
$defaults = (array) self::getGrav()['config']->get('site.metadata');
|
||||
$metadata = array_merge($metadata, $this->header->metadata);
|
||||
}
|
||||
|
||||
if (isset($page_header->metadata)) {
|
||||
$page_header->metadata = array_merge($defaults, $page_header->metadata);
|
||||
// Build an array of meta objects..
|
||||
foreach ((array)$metadata as $key => $value) {
|
||||
// If this is a property type metadata: "og", "twitter", "facebook" etc
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $property => $prop_value) {
|
||||
$prop_key = $key.":".$property;
|
||||
$this->metadata[$prop_key] = array('property'=>$prop_key, 'content'=>htmlspecialchars($prop_value, ENT_QUOTES));
|
||||
}
|
||||
// If it this is a standard meta data type
|
||||
} else {
|
||||
$page_header->metadata = $defaults;
|
||||
}
|
||||
|
||||
// Build an array of meta objects..
|
||||
foreach ((array)$page_header->metadata as $key => $value) {
|
||||
// If this is a property type metadata: "og", "twitter", "facebook" etc
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $property => $prop_value) {
|
||||
$prop_key = $key.":".$property;
|
||||
$this->metadata[$prop_key] = array('property'=>$prop_key, 'content'=>htmlspecialchars($prop_value, ENT_QUOTES));
|
||||
}
|
||||
// If it this is a standard meta data type
|
||||
if (in_array($key, $header_tag_http_equivs)) {
|
||||
$this->metadata[$key] = array('http_equiv'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
|
||||
} else {
|
||||
if (in_array($key, $header_tag_http_equivs)) {
|
||||
$this->metadata[$key] = array('http_equiv'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
|
||||
} else {
|
||||
$this->metadata[$key] = array('name'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
|
||||
}
|
||||
$this->metadata[$key] = array('name'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -992,16 +1105,13 @@ class Page
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->slug = $var;
|
||||
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
|
||||
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null;
|
||||
}
|
||||
|
||||
if (empty($this->slug)) {
|
||||
$regex = '/^[0-9]+\./u';
|
||||
$this->slug = preg_replace($regex, '', $this->folder);
|
||||
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
|
||||
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null;
|
||||
}
|
||||
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
@@ -1046,19 +1156,36 @@ class Page
|
||||
/**
|
||||
* Gets the url for the Page.
|
||||
*
|
||||
* @param bool $include_host Defaults false, but true would include http://yourhost.com
|
||||
* @return string The url.
|
||||
* @param bool $include_host Defaults false, but true would include http://yourhost.com
|
||||
* @param bool $canonical true to return the canonical URL
|
||||
*
|
||||
* @return string The url.
|
||||
*/
|
||||
public function url($include_host = false)
|
||||
public function url($include_host = false, $canonical = false)
|
||||
{
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = self::getGrav()['pages'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = self::getGrav()['language'];
|
||||
|
||||
// get pre-route
|
||||
$pre_route = $language->enabled() && $language->getActive() ? '/'.$language->getActive() : '';
|
||||
|
||||
// get canonical route if requested
|
||||
if ($canonical) {
|
||||
$route = $pre_route . $this->routeCanonical();
|
||||
} else {
|
||||
$route = $pre_route . $this->route();
|
||||
}
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = self::getGrav()['uri'];
|
||||
|
||||
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
|
||||
$url = $rootUrl.'/'.trim($this->route(), '/');
|
||||
|
||||
$url = $rootUrl.'/'.trim($route, '/');
|
||||
|
||||
// trim trailing / if not root
|
||||
if ($url !== '/') {
|
||||
@@ -1069,7 +1196,8 @@ class Page
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route for the page based on the parents route and the current Page's slug.
|
||||
* Gets the route for the page based on the route headers if available, else from
|
||||
* the parents route and the current Page's slug.
|
||||
*
|
||||
* @param string $var Set new default route.
|
||||
*
|
||||
@@ -1080,9 +1208,90 @@ class Page
|
||||
if ($var !== null) {
|
||||
$this->route = $var;
|
||||
}
|
||||
|
||||
if (empty($this->route)) {
|
||||
// calculate route based on parent slugs
|
||||
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
|
||||
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug() : null;
|
||||
|
||||
if (!empty($this->routes) && isset($this->routes['default'])) {
|
||||
$this->routes['aliases'][] = $this->route;
|
||||
$this->route = $this->routes['default'];
|
||||
return $this->route;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to clear the route out so it regenerates next time you use it
|
||||
*/
|
||||
public function unsetRoute()
|
||||
{
|
||||
unset($this->route);
|
||||
|
||||
}
|
||||
|
||||
public function rawRoute($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->raw_route = $var;
|
||||
}
|
||||
|
||||
if (empty($this->raw_route)) {
|
||||
$baseRoute = $this->parent ? (string) $this->parent()->rawRoute() : null;
|
||||
|
||||
$regex = '/^[0-9]+\./u';
|
||||
$slug = preg_replace($regex, '', $this->folder);
|
||||
|
||||
$this->raw_route = isset($baseRoute) ? $baseRoute . '/'. $slug : null;
|
||||
}
|
||||
|
||||
return $this->raw_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route aliases for the page based on page headers.
|
||||
*
|
||||
* @param array $var list of route aliases
|
||||
*
|
||||
* @return array The route aliases for the Page.
|
||||
*/
|
||||
public function routeAliases($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->routes['aliases'] = (array) $var;
|
||||
}
|
||||
|
||||
if (!empty($this->routes) && isset($this->routes['aliases'])) {
|
||||
return $this->routes['aliases'];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the canonical route for this page if its set. If provided it will use
|
||||
* that value, else if it's `true` it will use the default route.
|
||||
*
|
||||
* @param null $var
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function routeCanonical($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->routes['canonical'] = (array)$var;
|
||||
}
|
||||
|
||||
if (!empty($this->routes) && isset($this->routes['canonical'])) {
|
||||
return $this->routes['canonical'];
|
||||
}
|
||||
|
||||
return $this->route();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the identifier for this Page object.
|
||||
*
|
||||
@@ -1111,6 +1320,40 @@ class Page
|
||||
return $this->modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the option to show the etag header for the page.
|
||||
*
|
||||
* @param boolean $var show etag header
|
||||
* @return boolean show etag header
|
||||
*/
|
||||
public function eTag($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->etag = $var;
|
||||
}
|
||||
if (!isset($this->etag)) {
|
||||
$this->etag = (bool) self::getGrav()['config']->get('system.pages.etag');
|
||||
}
|
||||
return $this->etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the option to show the last_modified header for the page.
|
||||
*
|
||||
* @param boolean $var show last_modified header
|
||||
* @return boolean show last_modified header
|
||||
*/
|
||||
public function lastModified($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->last_modified = $var;
|
||||
}
|
||||
if (!isset($this->last_modified)) {
|
||||
$this->last_modified = (bool) self::getGrav()['config']->get('system.pages.last_modified');
|
||||
}
|
||||
return $this->last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the path to the .md file for this Page object.
|
||||
*
|
||||
@@ -1408,10 +1651,14 @@ class Page
|
||||
*/
|
||||
public function active()
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = self::getGrav()['uri'];
|
||||
if ($this->url() == $uri->url()) {
|
||||
return true;
|
||||
$uri_path = self::getGrav()['uri']->path();
|
||||
$routes = self::getGrav()['pages']->routes();
|
||||
|
||||
if (isset($routes[$uri_path])) {
|
||||
if ($routes[$uri_path] == $this->path()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1424,20 +1671,18 @@ class Page
|
||||
*/
|
||||
public function activeChild()
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = self::getGrav()['uri'];
|
||||
$config = self::getGrav()['config'];
|
||||
$pages = self::getGrav()['pages'];
|
||||
$uri_path = $uri->path();
|
||||
$routes = self::getGrav()['pages']->routes();
|
||||
|
||||
// Special check when item is home
|
||||
if ($this->home()) {
|
||||
$paths = $uri->paths();
|
||||
$home = ltrim($config->get('system.home.alias'), '/');
|
||||
if (isset($paths[0]) && $paths[0] == $home) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (strpos($uri->url(), $this->url()) === 0) {
|
||||
return true;
|
||||
if (isset($routes[$uri_path])) {
|
||||
$child_page = $pages->dispatch($uri->route())->parent();
|
||||
while (!$child_page->root()) {
|
||||
if ($this->path() == $child_page->path()) {
|
||||
return true;
|
||||
}
|
||||
$child_page = $child_page->parent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1642,7 +1887,6 @@ class Page
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not this Page object has a .md file associated with it or if its just a directory.
|
||||
*
|
||||
@@ -1699,7 +1943,7 @@ class Page
|
||||
*/
|
||||
protected function doRelocation($reorder)
|
||||
{
|
||||
if (empty($this->_original)) {
|
||||
if (empty($this->_original) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1766,5 +2010,24 @@ class Page
|
||||
$this->_original = null;
|
||||
}
|
||||
|
||||
|
||||
protected function setPublishState()
|
||||
{
|
||||
// Handle publishing dates if no explict published option set
|
||||
if (self::getGrav()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
|
||||
// unpublish if required, if not clear cache right before page should be unpublished
|
||||
if ($this->unpublishDate()) {
|
||||
if ($this->unpublishDate() < time()) {
|
||||
$this->published(false);
|
||||
} else {
|
||||
$this->published();
|
||||
self::getGrav()['cache']->setLifeTime($this->unpublishDate());
|
||||
}
|
||||
}
|
||||
// publish if required, if not clear cache right before page is published
|
||||
if ($this->publishDate() != $this->modified() && $this->publishDate() > time()) {
|
||||
$this->published(false);
|
||||
self::getGrav()['cache']->setLifeTime($this->publishDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ use Grav\Common\Config\Config;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Cache;
|
||||
use Grav\Common\Taxonomy;
|
||||
use Grav\Common\Language;
|
||||
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;
|
||||
use Whoops\Exception\ErrorException;
|
||||
|
||||
/**
|
||||
* GravPages is the class that is the entry point into the hierarchy of pages
|
||||
@@ -65,6 +67,8 @@ class Pages
|
||||
*/
|
||||
static protected $types;
|
||||
|
||||
static protected $home_route;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -264,26 +268,34 @@ class Pages
|
||||
/** @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;
|
||||
// Try Regex style redirects
|
||||
foreach ((array)$config->get("site.redirects") as $pattern => $replace) {
|
||||
$pattern = '#' . $pattern . '#';
|
||||
try {
|
||||
$found = preg_replace($pattern, $replace, $url);
|
||||
if ($found != $url) {
|
||||
$this->grav->redirectLangSafe($found);
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
$this->grav['log']->error('site.redirects: '. $pattern . '-> ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Try Regex style routes
|
||||
foreach ((array)$config->get("site.routes") as $pattern => $replace) {
|
||||
$pattern = '#' . $pattern . '#';
|
||||
try {
|
||||
$found = preg_replace($pattern, $replace, $url);
|
||||
if ($found != $url) {
|
||||
$page = $this->dispatch($found, $all);
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
$this->grav['log']->error('site.routes: '. $pattern . '-> ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,6 +343,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.
|
||||
*
|
||||
@@ -350,7 +384,8 @@ class Pages
|
||||
}
|
||||
|
||||
$list = array();
|
||||
if ($current->routable()) {
|
||||
|
||||
if (!$current->root()) {
|
||||
$list[$current->route()] = str_repeat(' ', ($level-1)*2) . $current->title();
|
||||
}
|
||||
|
||||
@@ -370,8 +405,8 @@ class Pages
|
||||
{
|
||||
if (!self::$types) {
|
||||
self::$types = new Types();
|
||||
self::$types->scanBlueprints('theme://blueprints/');
|
||||
self::$types->scanTemplates('theme://templates/');
|
||||
file_exists('theme://blueprints/') && self::$types->scanBlueprints('theme://blueprints/');
|
||||
file_exists('theme://templates/') && self::$types->scanTemplates('theme://templates/');
|
||||
|
||||
$event = new Event();
|
||||
$event->types = self::$types;
|
||||
@@ -420,6 +455,48 @@ class Pages
|
||||
return $pages->getList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the home route
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getHomeRoute()
|
||||
{
|
||||
if (empty(self::$home)) {
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$home = $config->get('system.home.alias');
|
||||
|
||||
if ($language->enabled()) {
|
||||
$home_aliases = $config->get('system.home.aliases');
|
||||
if ($home_aliases) {
|
||||
$active = $language->getActive();
|
||||
$default = $language->getDefault();
|
||||
|
||||
try {
|
||||
if ($active) {
|
||||
$home = $home_aliases[$active];
|
||||
} else {
|
||||
$home = $home_aliases[$default];
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
$home = $home_aliases[$default];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
self::$home_route = trim($home, '/');
|
||||
}
|
||||
return self::$home_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds pages.
|
||||
*
|
||||
@@ -432,6 +509,9 @@ class Pages
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
$pagesDir = $locator->findResource('page://');
|
||||
@@ -455,11 +535,12 @@ class Pages
|
||||
$last_modified = Folder::lastModifiedFile($pagesDir);
|
||||
}
|
||||
|
||||
$page_cache_id = md5(USER_DIR.$last_modified.$config->checksum());
|
||||
$page_cache_id = md5(USER_DIR.$last_modified.$language->getActive().$config->checksum());
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
|
||||
if (!$this->instances) {
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
|
||||
$this->recurse($pagesDir);
|
||||
$this->buildRoutes();
|
||||
|
||||
@@ -491,12 +572,23 @@ class Pages
|
||||
protected function recurse($directory, Page &$parent = null)
|
||||
{
|
||||
$directory = rtrim($directory, DS);
|
||||
$iterator = new \DirectoryIterator($directory);
|
||||
$page = new Page;
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
// stuff to do at root page
|
||||
if ($parent === null) {
|
||||
|
||||
// Fire event for memory and time consuming plugins...
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onBuildPagesInitialized');
|
||||
}
|
||||
}
|
||||
|
||||
$page->path($directory);
|
||||
if ($parent) {
|
||||
$page->parent($parent);
|
||||
@@ -515,18 +607,38 @@ class Pages
|
||||
throw new \RuntimeException('Fatal error when creating page instances.');
|
||||
}
|
||||
|
||||
$content_exists = false;
|
||||
$pages_found = glob($directory.'/*'.CONTENT_EXT);
|
||||
$page_extensions = $language->getFallbackPageExtensions();
|
||||
|
||||
if ($pages_found) {
|
||||
foreach ($page_extensions as $extension) {
|
||||
foreach ($pages_found as $found) {
|
||||
if (preg_match('/^.*\/[0-9A-Za-z\-\_]+('.$extension.')$/', $found)) {
|
||||
$page_found = $found;
|
||||
$page_extension = $extension;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent && !empty($page_found)) {
|
||||
$file = new \SplFileInfo($page_found);
|
||||
$page->init($file, $page_extension);
|
||||
|
||||
$content_exists = true;
|
||||
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
}
|
||||
|
||||
// set current modified of page
|
||||
$last_modified = $page->modified();
|
||||
|
||||
// flat for content availability
|
||||
$content_exists = false;
|
||||
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (new \FilesystemIterator($directory) as $file) {
|
||||
$name = $file->getFilename();
|
||||
|
||||
if ($file->isFile()) {
|
||||
@@ -534,15 +646,6 @@ class Pages
|
||||
if ($file->getBasename() !== '.DS_Store' && ($modified = $file->getMTime()) > $last_modified) {
|
||||
$last_modified = $modified;
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -586,29 +689,45 @@ class Pages
|
||||
/** @var $taxonomy Taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
|
||||
// Get the home route
|
||||
$home = self::getHomeRoute();
|
||||
|
||||
// Build routes and taxonomy map.
|
||||
/** @var $page Page */
|
||||
foreach ($this->instances as $page) {
|
||||
$parent = $page->parent();
|
||||
|
||||
if ($parent) {
|
||||
$route = rtrim($parent->route(), '/') . '/' . $page->slug();
|
||||
$this->routes[$route] = $page->path();
|
||||
$page->route($route);
|
||||
}
|
||||
|
||||
if (!empty($route)) {
|
||||
if (!$page->root()) {
|
||||
// process taxonomy
|
||||
$taxonomy->addTaxonomy($page);
|
||||
} else {
|
||||
$page->routable(false);
|
||||
|
||||
$route = $page->route();
|
||||
$raw_route = $page->rawRoute();
|
||||
$page_path = $page->path();
|
||||
|
||||
// add regular route
|
||||
$this->routes[$route] = $page_path;
|
||||
|
||||
// add raw route
|
||||
if ($raw_route != $route) {
|
||||
$this->routes[$raw_route] = $page_path;
|
||||
}
|
||||
|
||||
// add canonical route
|
||||
$route_canonical = $page->routeCanonical();
|
||||
if ($route_canonical && ($route !== $route_canonical)) {
|
||||
$this->routes[$route_canonical] = $page_path;
|
||||
}
|
||||
|
||||
// add aliases to routes list if they are provided
|
||||
$route_aliases = $page->routeAliases();
|
||||
if ($route_aliases) {
|
||||
foreach ($route_aliases as $alias) {
|
||||
$this->routes[$alias] = $page_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// Alias and set default route to home page.
|
||||
$home = trim($config->get('system.home.alias'), '/');
|
||||
if ($home && isset($this->routes['/' . $home])) {
|
||||
$this->routes['/'] = $this->routes['/' . $home];
|
||||
$this->get($this->routes['/' . $home])->route('/');
|
||||
|
||||
@@ -13,27 +13,24 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
use ArrayAccess, Constructor, Iterator, Countable, Export;
|
||||
|
||||
protected $items;
|
||||
protected $systemBlueprints;
|
||||
|
||||
public function register($type, $blueprint = null)
|
||||
{
|
||||
if (!$blueprint && $this->systemBlueprints && isset($this->systemBlueprints[$type])) {
|
||||
$useBlueprint = $this->systemBlueprints[$type];
|
||||
} else {
|
||||
$useBlueprint = $blueprint;
|
||||
}
|
||||
|
||||
if ($blueprint || empty($this->items[$type])) {
|
||||
$this->items[$type] = $blueprint;
|
||||
$this->items[$type] = $useBlueprint;
|
||||
}
|
||||
}
|
||||
|
||||
public function scanBlueprints($path)
|
||||
{
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => [
|
||||
'key' => '|\.yaml$|'
|
||||
],
|
||||
'key' => 'SubPathName',
|
||||
'value' => 'PathName',
|
||||
];
|
||||
|
||||
$this->items = Folder::all($path, $options) + $this->items;
|
||||
$this->items = $this->findBlueprints($path) + $this->items;
|
||||
}
|
||||
|
||||
public function scanTemplates($path)
|
||||
@@ -48,6 +45,10 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
'recursive' => false
|
||||
];
|
||||
|
||||
if (!$this->systemBlueprints) {
|
||||
$this->systemBlueprints = $this->findBlueprints('blueprints://pages');
|
||||
}
|
||||
|
||||
foreach (Folder::all($path, $options) as $type) {
|
||||
$this->register($type);
|
||||
}
|
||||
@@ -83,4 +84,19 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
ksort($list);
|
||||
return $list;
|
||||
}
|
||||
|
||||
private function findBlueprints($path)
|
||||
{
|
||||
$options = [
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => [
|
||||
'key' => '|\.yaml$|'
|
||||
],
|
||||
'key' => 'SubPathName',
|
||||
'value' => 'PathName',
|
||||
];
|
||||
|
||||
return Folder::all($path, $options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,48 +114,72 @@ class Plugin implements EventSubscriberInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will search a string for markdown links in a specific format. The link value can be
|
||||
* optionally compared against via the $internal_regex and operated on by the callback $function
|
||||
* provided.
|
||||
*
|
||||
* format: [plugin:myplugin_name](function_data)
|
||||
*
|
||||
* @param $content The string to perform operations upon
|
||||
* @param $function The anonymous callback function
|
||||
* @param string $internal_regex Optional internal regex to extra data from
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLinks($content, $function, $internal_regex = '(.*)')
|
||||
{
|
||||
$regex = '/\[plugin:(?:'.$this->name.')\]\('.$internal_regex.'\)/i';
|
||||
return preg_replace_callback($regex, $function, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param Page $page The page to merge the configurations with the
|
||||
* plugin settings.
|
||||
* @param bool $deep Should you use deep or shallow merging
|
||||
* @param array $params Array of additional configuration options to
|
||||
* merge with the plugin settings.
|
||||
*
|
||||
* @return \Grav\Common\Data\Data
|
||||
*/
|
||||
protected function mergeConfig(Page $page, $deep = false)
|
||||
protected function mergeConfig(Page $page, $deep = false, $params = [])
|
||||
{
|
||||
$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);
|
||||
$defaults = $this->config->get('plugins.'. $class_name, []);
|
||||
$page_header = $page->header();
|
||||
$header = [];
|
||||
if (!isset($page_header->$class_name_merged) && isset($page_header->$class_name)) {
|
||||
// Get default plugin configurations and retrieve page header configuration
|
||||
$config = $page_header->$class_name;
|
||||
if (is_bool($config)) {
|
||||
// Overwrite enabled option with boolean value in page header
|
||||
$config = ['enabled' => $config];
|
||||
}
|
||||
}
|
||||
|
||||
// Get default plugin configurations and retrieve page header configuration
|
||||
if (isset($page->header()->$class_name)) {
|
||||
// Merge page header settings using deep or shallow merging technique
|
||||
if ($deep) {
|
||||
$header = array_replace_recursive($defaults, $page->header()->$class_name);
|
||||
$header = array_replace_recursive($defaults, $config);
|
||||
} else {
|
||||
$header = array_merge($defaults, $page->header()->$class_name);
|
||||
$header = array_merge($defaults, $config);
|
||||
}
|
||||
} else {
|
||||
// Create new config object and set it on the page object so it's cached for next time
|
||||
$page->modifyHeader($class_name_merged, new Data($header));
|
||||
} else if (isset($page_header->$class_name_merged)) {
|
||||
$merged = $page_header->$class_name_merged;
|
||||
$header = $merged->toArray();
|
||||
}
|
||||
if (empty($header)) {
|
||||
$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);
|
||||
|
||||
// Merge additional parameter with configuration options
|
||||
if ($deep) {
|
||||
$header = array_replace_recursive($header, $params);
|
||||
} else {
|
||||
$header = array_merge($header, $params);
|
||||
}
|
||||
// Return configurations as a new data config class
|
||||
return $config;
|
||||
return new Data($header);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Grav\Common;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use RocketTheme\Toolbox\Event\EventSubscriberInterface;
|
||||
@@ -17,12 +18,7 @@ use RocketTheme\Toolbox\Event\EventSubscriberInterface;
|
||||
*/
|
||||
class Plugins extends Iterator
|
||||
{
|
||||
protected $grav;
|
||||
|
||||
public function __construct(Grav $grav)
|
||||
{
|
||||
$this->grav = $grav;
|
||||
}
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* Recurses through the plugins directory creating Plugin objects for each plugin it finds.
|
||||
@@ -33,11 +29,13 @@ class Plugins extends Iterator
|
||||
public function init()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
$config = self::getGrav()['config'];
|
||||
$plugins = (array) $config->get('plugins');
|
||||
|
||||
$inflector = self::getGrav()['inflector'];
|
||||
|
||||
/** @var EventDispatcher $events */
|
||||
$events = $this->grav['events'];
|
||||
$events = self::getGrav()['events'];
|
||||
|
||||
foreach ($plugins as $plugin => $data) {
|
||||
if (empty($data['enabled'])) {
|
||||
@@ -45,9 +43,10 @@ class Plugins extends Iterator
|
||||
continue;
|
||||
}
|
||||
|
||||
$filePath = $this->grav['locator']('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT);
|
||||
$locator = self::getGrav()['locator'];
|
||||
$filePath = $locator->findResource('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT);
|
||||
if (!is_file($filePath)) {
|
||||
$this->grav['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
|
||||
self::getGrav()['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ class Plugins extends Iterator
|
||||
|
||||
$pluginClassFormat = [
|
||||
'Grav\\Plugin\\'.ucfirst($plugin).'Plugin',
|
||||
'Grav\\Plugin\\'.Inflector::camelize($plugin).'Plugin'
|
||||
'Grav\\Plugin\\'.$inflector->camelize($plugin).'Plugin'
|
||||
];
|
||||
$pluginClassName = false;
|
||||
|
||||
@@ -70,7 +69,7 @@ class Plugins extends Iterator
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", $plugin));
|
||||
}
|
||||
|
||||
$instance = new $pluginClassName($plugin, $this->grav, $config);
|
||||
$instance = new $pluginClassName($plugin, self::getGrav(), $config);
|
||||
if ($instance instanceof EventSubscriberInterface) {
|
||||
$events->addSubscriber($instance);
|
||||
}
|
||||
@@ -123,10 +122,10 @@ class Plugins extends Iterator
|
||||
$obj = new Data($file->content(), $blueprint);
|
||||
|
||||
// Override with user configuration.
|
||||
$file = CompiledYamlFile::instance("user://config/plugins/{$name}.yaml");
|
||||
$obj->merge($file->content());
|
||||
$obj->merge(self::getGrav()['config']->get('plugins.' . $name) ?: []);
|
||||
|
||||
// Save configuration always to user/config.
|
||||
$file = CompiledYamlFile::instance("config://plugins/{$name}.yaml");
|
||||
$obj->file($file);
|
||||
|
||||
return $obj;
|
||||
|
||||
@@ -38,18 +38,8 @@ class ConfigServiceProvider implements ServiceProviderInterface
|
||||
public function loadMasterConfig(Container $container)
|
||||
{
|
||||
$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, $environment);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($config)) {
|
||||
$config = new Config($this->setup, $container, $environment);
|
||||
}
|
||||
$config = new Config($this->setup, $container, $environment);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ class ErrorServiceProvider implements ServiceProviderInterface
|
||||
|
||||
$logger = $container['log'];
|
||||
$errors->pushHandler(function (\Exception $exception, $inspector, $run) use ($logger) {
|
||||
$logger->addCritical($exception->getMessage(). ' - Trace: '. $exception->getTraceAsString());
|
||||
try {
|
||||
$logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString());
|
||||
} catch (\Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
}, 'log');
|
||||
|
||||
$errors->register();
|
||||
|
||||
47
system/src/Grav/Common/Session.php
Normal file
47
system/src/Grav/Common/Session.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* Wrapper for Session
|
||||
*/
|
||||
class Session extends \RocketTheme\Toolbox\Session\Session
|
||||
{
|
||||
protected $grav;
|
||||
protected $session;
|
||||
|
||||
public function __construct(Grav $grav)
|
||||
{
|
||||
$this->grav = $grav;
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
$config = $this->grav['config'];
|
||||
|
||||
if ($config->get('system.session.enabled')) {
|
||||
// Only activate admin if we're inside the admin path.
|
||||
$is_admin = false;
|
||||
$route = $config->get('plugins.admin.route');
|
||||
$base = '/' . trim($route, '/');
|
||||
if (substr($uri->route(), 0, strlen($base)) == $base) {
|
||||
$is_admin = true;
|
||||
}
|
||||
|
||||
$session_timeout = $config->get('system.session.timeout', 1800);
|
||||
$session_path = $config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/'));
|
||||
|
||||
// Define session service.
|
||||
parent::__construct(
|
||||
$session_timeout,
|
||||
$session_path
|
||||
);
|
||||
|
||||
$site_identifier = $config->get('site.title', 'unknown');
|
||||
$this->setName($config->get('system.session.name', 'grav_site') . '_' . substr(md5($site_identifier), 0, 7) . ($is_admin ? '_admin' : ''));
|
||||
$this->start();
|
||||
setcookie(session_name(), session_id(), time() + $session_timeout, $session_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,13 +52,13 @@ class Taxonomy
|
||||
$page_taxonomy = $page->taxonomy();
|
||||
}
|
||||
|
||||
if (!$page->published()) {
|
||||
if (!$page->published() || empty($page_taxonomy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
if ($config->get('site.taxonomies') && count($page_taxonomy) > 0) {
|
||||
if ($config->get('site.taxonomies')) {
|
||||
foreach ((array) $config->get('site.taxonomies') as $taxonomy) {
|
||||
if (isset($page_taxonomy[$taxonomy])) {
|
||||
foreach ((array) $page_taxonomy[$taxonomy] as $item) {
|
||||
@@ -76,7 +76,7 @@ class Taxonomy
|
||||
*
|
||||
* @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']]
|
||||
* @param string $operator can be 'or' or 'and' (defaults to 'or')
|
||||
* @return Colleciton Collection object set to contain matches found in the taxonomy map
|
||||
* @return Collection Collection object set to contain matches found in the taxonomy map
|
||||
*/
|
||||
public function findTaxonomy($taxonomies, $operator = 'and')
|
||||
{
|
||||
|
||||
@@ -106,10 +106,10 @@ class Themes extends Iterator
|
||||
$obj = new Data($file->content(), $blueprint);
|
||||
|
||||
// Override with user configuration.
|
||||
$file = CompiledYamlFile::instance("user://config/themes/{$name}" . YAML_EXT);
|
||||
$obj->merge($file->content());
|
||||
$obj->merge($this->grav['config']->get('themes.' . $name) ?: []);
|
||||
|
||||
// Save configuration always to user/config.
|
||||
$file = CompiledYamlFile::instance("config://themes/{$name}" . YAML_EXT);
|
||||
$obj->file($file);
|
||||
|
||||
return $obj;
|
||||
@@ -141,15 +141,16 @@ class Themes extends Iterator
|
||||
$locator = $grav['locator'];
|
||||
$file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php");
|
||||
|
||||
$inflector = $grav['inflector'];
|
||||
|
||||
if ($file) {
|
||||
// Local variables available in the file: $grav, $config, $name, $file
|
||||
$class = include $file;
|
||||
|
||||
if (!is_object($class)) {
|
||||
|
||||
$themeClassFormat = [
|
||||
'Grav\\Theme\\'.ucfirst($name),
|
||||
'Grav\\Theme\\'.Inflector::camelize($name)
|
||||
'Grav\\Theme\\'.$inflector->camelize($name)
|
||||
];
|
||||
$themeClassName = false;
|
||||
|
||||
@@ -221,7 +222,13 @@ class Themes extends Iterator
|
||||
protected function loadConfiguration($name, Config $config)
|
||||
{
|
||||
$themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content();
|
||||
|
||||
$config->joinDefaults("themes.{$name}", $themeConfig);
|
||||
|
||||
if ($this->config->get('system.languages.translations', true)) {
|
||||
$languages = CompiledYamlFile::instance("themes://{$name}/languages". YAML_EXT)->content();
|
||||
if ($languages) {
|
||||
$config->getLanguages()->mergeRecursive($languages);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
system/src/Grav/Common/Twig/TraceableTwigEnvironment.php
Normal file
16
system/src/Grav/Common/Twig/TraceableTwigEnvironment.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
|
||||
/**
|
||||
* The Twig Environment class is a wrapper that handles configurable permissions
|
||||
* for the Twig cache files
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class TraceableTwigEnvironment extends \DebugBar\Bridge\Twig\TraceableTwigEnvironment
|
||||
{
|
||||
use WriteCacheFileTrait;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Page\Page;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
@@ -57,6 +58,7 @@ class Twig
|
||||
public function __construct(Grav $grav)
|
||||
{
|
||||
$this->grav = $grav;
|
||||
$this->twig_paths = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +74,23 @@ class Twig
|
||||
$locator = $this->grav['locator'];
|
||||
$debugger = $this->grav['debugger'];
|
||||
|
||||
$this->twig_paths = $locator->findResources('theme://templates');
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
$active_language = $language->getActive();
|
||||
|
||||
$language_append = $active_language ? '/'.$active_language : '';
|
||||
|
||||
// handle language templates if available
|
||||
if ($language->enabled()) {
|
||||
$lang_templates = $locator->findResource('theme://templates/'.$active_language);
|
||||
if ($lang_templates) {
|
||||
$this->twig_paths[] = $lang_templates;
|
||||
}
|
||||
}
|
||||
|
||||
$this->twig_paths = array_merge($this->twig_paths, $locator->findResources('theme://templates'));
|
||||
|
||||
$this->grav->fireEvent('onTwigTemplatePaths');
|
||||
|
||||
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
|
||||
@@ -84,9 +102,9 @@ class Twig
|
||||
$params['cache'] = $locator->findResource('cache://twig', true, true);
|
||||
}
|
||||
|
||||
$this->twig = new \Twig_Environment($loader_chain, $params);
|
||||
$this->twig = new TwigEnvironment($loader_chain, $params);
|
||||
if ($debugger->enabled() && $config->get('system.debugger.twig')) {
|
||||
$this->twig = new \DebugBar\Bridge\Twig\TraceableTwigEnvironment($this->twig);
|
||||
$this->twig = new TraceableTwigEnvironment($this->twig);
|
||||
$collector = new \DebugBar\Bridge\Twig\TwigCollector($this->twig);
|
||||
$debugger->addCollector($collector);
|
||||
}
|
||||
@@ -131,9 +149,9 @@ class Twig
|
||||
'config' => $config,
|
||||
'uri' => $this->grav['uri'],
|
||||
'base_dir' => rtrim(ROOT_DIR, '/'),
|
||||
'base_url' => $this->grav['base_url'],
|
||||
'base_url_absolute' => $this->grav['base_url_absolute'],
|
||||
'base_url_relative' => $this->grav['base_url_relative'],
|
||||
'base_url' => $this->grav['base_url'] . $language_append,
|
||||
'base_url_absolute' => $this->grav['base_url_absolute'] . $language_append,
|
||||
'base_url_relative' => $this->grav['base_url_relative'] . $language_append,
|
||||
'theme_dir' => $locator->findResource('theme://'),
|
||||
'theme_url' => $this->grav['base_url'] .'/'. $locator->findResource('theme://', false),
|
||||
'site' => $config->get('site'),
|
||||
@@ -283,15 +301,22 @@ class Twig
|
||||
$this->grav->fireEvent('onTwigSiteVariables');
|
||||
$pages = $this->grav['pages'];
|
||||
$page = $this->grav['page'];
|
||||
$content = $page->content();
|
||||
|
||||
$twig_vars = $this->twig_vars;
|
||||
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
$twig_vars['content'] = $page->content();
|
||||
$twig_vars['content'] = $content;
|
||||
$ext = '.' . ($format ? $format : 'html') . TWIG_EXT;
|
||||
|
||||
// determine if params are set, if so disable twig cache
|
||||
$params = $this->grav['uri']->params(null, true);
|
||||
if (!empty($params)) {
|
||||
$this->twig->setCache(false);
|
||||
}
|
||||
|
||||
// Get Twig template layout
|
||||
$template = $this->template($page->template() . $ext);
|
||||
|
||||
16
system/src/Grav/Common/Twig/TwigEnvironment.php
Normal file
16
system/src/Grav/Common/Twig/TwigEnvironment.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
|
||||
/**
|
||||
* The Twig Environment class is a wrapper that handles configurable permissions
|
||||
* for the Twig cache files
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class TwigEnvironment extends \Twig_Environment
|
||||
{
|
||||
use WriteCacheFileTrait;
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
|
||||
/**
|
||||
* The Twig extension adds some filters and functions that are useful for Grav
|
||||
*
|
||||
@@ -16,11 +18,13 @@ class TwigExtension extends \Twig_Extension
|
||||
{
|
||||
protected $grav;
|
||||
protected $debugger;
|
||||
protected $config;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->grav = Grav::instance();
|
||||
$this->debugger = isset($this->grav['debugger']) ? $this->grav['debugger'] : null;
|
||||
$this->config = $this->grav['config'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +56,11 @@ class TwigExtension extends \Twig_Extension
|
||||
new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
|
||||
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFilter']),
|
||||
new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
|
||||
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter'])
|
||||
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter']),
|
||||
new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
|
||||
new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
|
||||
new \Twig_SimpleFilter('t', [$this, 'translate']),
|
||||
new \Twig_SimpleFilter('ta', [$this, 'translateArray'])
|
||||
];
|
||||
}
|
||||
|
||||
@@ -70,6 +78,9 @@ class TwigExtension extends \Twig_Extension
|
||||
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
|
||||
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
|
||||
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
|
||||
new \Twig_SimpleFunction('array', [$this, 'arrayFunc']),
|
||||
new \Twig_simpleFunction('t', [$this, 'translate']),
|
||||
new \Twig_simpleFunction('ta', [$this, 'translateArray'])
|
||||
];
|
||||
}
|
||||
|
||||
@@ -106,7 +117,7 @@ class TwigExtension extends \Twig_Extension
|
||||
* Truncate content by a limit.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $limit Nax number of characters.
|
||||
* @param int $limit Max number of characters.
|
||||
* @param string $break Break point.
|
||||
* @param string $pad Appended padding to the end of the string.
|
||||
* @return string
|
||||
@@ -184,16 +195,18 @@ class TwigExtension extends \Twig_Extension
|
||||
// TODO: check this and fix the docblock if needed.
|
||||
$action = $action.'ize';
|
||||
|
||||
$inflector = $this->grav['inflector'];
|
||||
|
||||
if (in_array(
|
||||
$action,
|
||||
['titleize','camelize','underscorize','hyphenize', 'humanize','ordinalize','monthize']
|
||||
)) {
|
||||
return Inflector::$action($data);
|
||||
return $inflector->$action($data);
|
||||
} elseif (in_array($action, ['pluralize','singularize'])) {
|
||||
if ($count) {
|
||||
return Inflector::$action($data, $count);
|
||||
return $inflector->$action($data, $count);
|
||||
} else {
|
||||
return Inflector::$action($data);
|
||||
return $inflector->$action($data);
|
||||
}
|
||||
} else {
|
||||
return $data;
|
||||
@@ -333,13 +346,13 @@ class TwigExtension extends \Twig_Extension
|
||||
public function markdownFilter($string)
|
||||
{
|
||||
$page = $this->grav['page'];
|
||||
$defaults = $this->grav['config']->get('system.pages.markdown');
|
||||
$defaults = $this->config->get('system.pages.markdown');
|
||||
|
||||
// Initialize the preferred variant of Parsedown
|
||||
if ($defaults['extra']) {
|
||||
$parsedown = new ParsedownExtra($page);
|
||||
$parsedown = new ParsedownExtra($page, $defaults);
|
||||
} else {
|
||||
$parsedown = new Parsedown($page);
|
||||
$parsedown = new Parsedown($page, $defaults);
|
||||
}
|
||||
|
||||
$string = $parsedown->text($string);
|
||||
@@ -347,6 +360,27 @@ class TwigExtension extends \Twig_Extension
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function startsWithFilter($haystack, $needle)
|
||||
{
|
||||
return Utils::startsWith($haystack, $needle);
|
||||
}
|
||||
|
||||
public function endsWithFilter($haystack, $needle)
|
||||
{
|
||||
return Utils::endsWith($haystack, $needle);
|
||||
}
|
||||
|
||||
public function translate()
|
||||
{
|
||||
return $this->grav['language']->translate(func_get_args());
|
||||
}
|
||||
|
||||
public function translateArray($key, $index, $lang = null)
|
||||
{
|
||||
return $this->grav['language']->translateArray($key, $index, $lang);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Repeat given string x times.
|
||||
*
|
||||
@@ -447,4 +481,14 @@ class TwigExtension extends \Twig_Extension
|
||||
{
|
||||
return Utils::generateRandomString($count);
|
||||
}
|
||||
|
||||
public function arrayFunc($value)
|
||||
{
|
||||
return (array) $value;
|
||||
}
|
||||
|
||||
public function translateFunc()
|
||||
{
|
||||
return $this->grav['language']->translate(func_get_args());
|
||||
}
|
||||
}
|
||||
39
system/src/Grav/Common/Twig/WriteCacheFileTrait.php
Normal file
39
system/src/Grav/Common/Twig/WriteCacheFileTrait.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
|
||||
/**
|
||||
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
|
||||
*/
|
||||
trait WriteCacheFileTrait
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected static $umask;
|
||||
/**
|
||||
* This exists so template cache files use the same
|
||||
* group between apache and cli
|
||||
*
|
||||
* @param $file
|
||||
* @param $content
|
||||
*/
|
||||
protected function writeCacheFile($file, $content)
|
||||
{
|
||||
if (!isset(self::$umask)) {
|
||||
self::$umask = self::getGrav()['config']->get('system.twig.umask_fix', false);
|
||||
}
|
||||
|
||||
if (self::$umask) {
|
||||
if (!is_dir(dirname($file))) {
|
||||
$old = umask(0002);
|
||||
mkdir(dirname($file), 0777, true);
|
||||
umask($old);
|
||||
}
|
||||
parent::writeCacheFile($file, $content);
|
||||
chmod($file, 0775);
|
||||
} else {
|
||||
parent::writeCacheFile($file, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
|
||||
/**
|
||||
* The URI object provides information about the current URL
|
||||
*
|
||||
@@ -11,6 +14,7 @@ class Uri
|
||||
{
|
||||
public $url;
|
||||
|
||||
protected $basename;
|
||||
protected $base;
|
||||
protected $root;
|
||||
protected $bits;
|
||||
@@ -33,8 +37,7 @@ class Uri
|
||||
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
|
||||
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
|
||||
|
||||
$root_path = rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/');
|
||||
|
||||
$root_path = str_replace(' ', '%20', rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/'));
|
||||
|
||||
if (isset($_SERVER['HTTPS'])) {
|
||||
$base = (@$_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
|
||||
@@ -64,6 +67,7 @@ class Uri
|
||||
$this->base = $base;
|
||||
$this->root = $base . $root_path;
|
||||
$this->url = $base . $uri;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,37 +75,61 @@ class Uri
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$config = Grav::instance()['config'];
|
||||
$grav = Grav::instance();
|
||||
|
||||
$config = $grav['config'];
|
||||
$language = $grav['language'];
|
||||
|
||||
// resets
|
||||
$this->paths = [];
|
||||
$this->params = [];
|
||||
$this->query = [];
|
||||
|
||||
|
||||
// get any params and remove them
|
||||
$uri = str_replace($this->root, '', $this->url);
|
||||
|
||||
// reset params
|
||||
$this->params = [];
|
||||
|
||||
// process params
|
||||
$uri = $this->processParams($uri, $config->get('system.param_sep'));
|
||||
|
||||
// set active language
|
||||
$uri = $language->setActiveFromUri($uri);
|
||||
|
||||
// redirect to language specific homepage if configured to do so
|
||||
if ($uri == '/' && $language->enabled()) {
|
||||
if ($config->get('system.languages.home_redirect.include_route', true)) {
|
||||
$prefix = $config->get('system.languages.home_redirect.include_lang', true) ? $language->getLanguage() . '/' : '';
|
||||
$grav->redirect($prefix . Pages::getHomeRoute());
|
||||
} elseif ($config->get('system.languages.home_redirect.include_lang', true)) {
|
||||
$grav->redirect($language->getLanguage() . '/');
|
||||
}
|
||||
}
|
||||
|
||||
// split the URL and params
|
||||
$bits = parse_url($uri);
|
||||
|
||||
// process query string
|
||||
if (isset($bits['query'])) {
|
||||
parse_str($bits['query'], $this->query);
|
||||
$uri = $bits['path'];
|
||||
}
|
||||
|
||||
// remove the extension if there is one set
|
||||
$parts = pathinfo($uri);
|
||||
if (preg_match("/\.(txt|xml|html|json|rss|atom)$/", $parts['basename'])) {
|
||||
$uri = rtrim($parts['dirname'], '/').'/'.$parts['filename'];
|
||||
|
||||
// set the original basename
|
||||
$this->basename = $parts['basename'];
|
||||
|
||||
$valid_page_types = implode('|', $config->get('system.pages.types'));
|
||||
|
||||
if (preg_match("/\.(".$valid_page_types.")$/", $parts['basename'])) {
|
||||
$uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS). '/' .$parts['filename'];
|
||||
$this->extension = $parts['extension'];
|
||||
}
|
||||
|
||||
// set the new url
|
||||
$this->url = $this->root . $uri;
|
||||
|
||||
// split into bits
|
||||
$this->bits = parse_url($uri);
|
||||
|
||||
$this->query = array();
|
||||
if (isset($this->bits['query'])) {
|
||||
parse_str($this->bits['query'], $this->query);
|
||||
}
|
||||
|
||||
$this->paths = array();
|
||||
$this->path = $this->bits['path'];
|
||||
$this->path = $uri;
|
||||
$this->content_path = trim(str_replace($this->base, '', $this->path), '/');
|
||||
if ($this->content_path != '') {
|
||||
$this->paths = explode('/', $this->content_path);
|
||||
@@ -131,7 +159,7 @@ class Uri
|
||||
$path[] = $bit;
|
||||
}
|
||||
}
|
||||
$uri = implode('/', $path);
|
||||
$uri = '/' . ltrim(implode('/', $path), '/');
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
@@ -186,20 +214,27 @@ class Uri
|
||||
* Return all or a single query parameter as a URI compatible string.
|
||||
*
|
||||
* @param string $id Optional parameter name.
|
||||
* @param boolean $array return the array format or not
|
||||
* @return null|string
|
||||
*/
|
||||
public function params($id = null)
|
||||
public function params($id = null, $array = false)
|
||||
{
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$params = null;
|
||||
if ($id === null) {
|
||||
if ($array) {
|
||||
return $this->params;
|
||||
}
|
||||
$output = array();
|
||||
foreach ($this->params as $key => $value) {
|
||||
$output[] = $key . $config->get('system.param_sep') . $value;
|
||||
$params = '/'.implode('/', $output);
|
||||
}
|
||||
} elseif (isset($this->params[$id])) {
|
||||
if ($array) {
|
||||
return $this->params[$id];
|
||||
}
|
||||
$params = "/{$id}". $config->get('system.param_sep') . $this->params[$id];
|
||||
}
|
||||
|
||||
@@ -244,7 +279,11 @@ class Uri
|
||||
*/
|
||||
public function path()
|
||||
{
|
||||
return $this->path;
|
||||
$path = $this->path;
|
||||
if ($path === '') {
|
||||
$path = '/';
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,6 +321,17 @@ class Uri
|
||||
return $this->host();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the basename of the URI
|
||||
*
|
||||
* @return String The basename of the URI
|
||||
*/
|
||||
public function basename()
|
||||
{
|
||||
return $this->basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base of the URI
|
||||
*
|
||||
@@ -355,7 +405,7 @@ class Uri
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrun the IP address of the current user
|
||||
* Return the IP address of the current user
|
||||
*
|
||||
* @return string ip address
|
||||
*/
|
||||
@@ -378,6 +428,7 @@ class Uri
|
||||
return $ipaddress;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an external URL? if it starts with `http` then yes, else false
|
||||
*
|
||||
@@ -412,4 +463,86 @@ class Uri
|
||||
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||||
return "$scheme$user$pass$host$port$path$query$fragment";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts links from absolute '/' or relative (../..) to a grav friendly format
|
||||
*
|
||||
* @param $page the current page to use as reference
|
||||
* @param string $markdown_url the URL as it was written in the markdown
|
||||
*
|
||||
* @return string the more friendly formatted url
|
||||
*/
|
||||
public static function convertUrl(Page $page, $markdown_url)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$pages_dir = $grav['locator']->findResource('page://');
|
||||
$base_url = rtrim($grav['base_url'] . $grav['pages']->base(), '/');
|
||||
|
||||
// if absolute and starts with a base_url move on
|
||||
if (pathinfo($markdown_url, PATHINFO_DIRNAME) == '.' && $page->url() == '/') {
|
||||
return '/' . $markdown_url;
|
||||
// no path to convert
|
||||
} elseif ($base_url != '' && Utils::startsWith($markdown_url, $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_url = Utils::normalizePath($base_url . $markdown_url);
|
||||
$normalized_path = Utils::normalizePath($pages_dir . $markdown_url);
|
||||
} else {
|
||||
$normalized_url = $base_url . Utils::normalizePath($page->route() . '/' . $markdown_url);
|
||||
$normalized_path = Utils::normalizePath($page->path() . '/' . $markdown_url);
|
||||
}
|
||||
|
||||
// special check to see if path checking is required.
|
||||
$just_path = str_replace($normalized_url, '', $normalized_path);
|
||||
if ($just_path == $page->path()) {
|
||||
return $normalized_url;
|
||||
}
|
||||
|
||||
$url_bits = parse_url($normalized_path);
|
||||
$full_path = ($url_bits['path']);
|
||||
|
||||
if (file_exists($full_path)) {
|
||||
// do nothing
|
||||
} elseif (file_exists(urldecode($full_path))) {
|
||||
$full_path = urldecode($full_path);
|
||||
} else {
|
||||
return $normalized_url;
|
||||
}
|
||||
|
||||
$path_info = pathinfo($full_path);
|
||||
$page_path = $path_info['dirname'];
|
||||
$filename = '';
|
||||
|
||||
|
||||
if ($markdown_url == '..') {
|
||||
$page_path = $full_path;
|
||||
} else {
|
||||
// save the filename if a file is part of the path
|
||||
if (is_file($full_path)) {
|
||||
if ($path_info['extension'] != 'md') {
|
||||
$filename = '/' . $path_info['basename'];
|
||||
}
|
||||
} else {
|
||||
$page_path = $full_path;
|
||||
}
|
||||
}
|
||||
|
||||
// get page instances and try to find one that fits
|
||||
$instances = $grav['pages']->instances();
|
||||
if (isset($instances[$page_path])) {
|
||||
$target = $instances[$page_path];
|
||||
$url_bits['path'] = $base_url . $target->route() . $filename;
|
||||
return Uri::buildUrl($url_bits);
|
||||
}
|
||||
|
||||
return $normalized_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,22 @@ abstract class Authentication
|
||||
* Create password hash from plaintext password.
|
||||
*
|
||||
* @param string $password Plaintext password.
|
||||
* @throws \RuntimeException
|
||||
* @return string|bool
|
||||
*/
|
||||
public static function create($password)
|
||||
{
|
||||
return password_hash($password, PASSWORD_DEFAULT);
|
||||
if (!$password) {
|
||||
throw new \RuntimeException('Password hashing failed: no password provided.');
|
||||
}
|
||||
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
if (!$hash) {
|
||||
throw new \RuntimeException('Password hashing failed: internal error.');
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,13 +40,8 @@ abstract class Authentication
|
||||
*/
|
||||
public static function verify($password, $hash)
|
||||
{
|
||||
// Always accept plaintext passwords (needs an update).
|
||||
if ($password && $password == $hash) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Fail if hash doesn't match.
|
||||
if (!$password || !password_verify($password, $hash)) {
|
||||
// Fail if hash doesn't match
|
||||
if (!$password || !$hash || !password_verify($password, $hash)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ use Grav\Common\GravTrait;
|
||||
/**
|
||||
* User object
|
||||
*
|
||||
* @property mixed authenticated
|
||||
* @property mixed password
|
||||
* @property bool|string hashed_password
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -53,11 +56,35 @@ class User extends Data
|
||||
*/
|
||||
public function authenticate($password)
|
||||
{
|
||||
$result = Authentication::verify($password, $this->password);
|
||||
$save = false;
|
||||
|
||||
// Plain-text is still stored
|
||||
if ($this->password) {
|
||||
if ($password !== $this->password) {
|
||||
// Plain-text passwords do not match, we know we should fail but execute
|
||||
// verify to protect us from timing attacks and return false regardless of
|
||||
// the result
|
||||
Authentication::verify($password, self::getGrav()['config']->get('system.security.default_hash'));
|
||||
return false;
|
||||
} else {
|
||||
// Plain-text does match, we can update the hash and proceed
|
||||
$save = true;
|
||||
|
||||
$this->hashed_password = Authentication::create($this->password);
|
||||
unset($this->password);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$result = Authentication::verify($password, $this->hashed_password);
|
||||
|
||||
// Password needs to be updated, save the file.
|
||||
if ($result == 2) {
|
||||
$this->password = Authentication::create($password);
|
||||
$save = true;
|
||||
$this->hashed_password = Authentication::create($password);
|
||||
}
|
||||
|
||||
if ($save) {
|
||||
$this->save();
|
||||
}
|
||||
|
||||
@@ -65,13 +92,31 @@ class User extends Data
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user authorisation to the action.
|
||||
* Save user without the username
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$file = $this->file();
|
||||
if ($file) {
|
||||
$username = $this->get('username');
|
||||
unset($this->username);
|
||||
$file->save($this->items);
|
||||
$this->set('username', $username);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user authorization to the action.
|
||||
*
|
||||
* @param string $action
|
||||
* @return bool
|
||||
*/
|
||||
public function authorise($action)
|
||||
{
|
||||
if (empty($this->items)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->get("access.{$action}") === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Misc utilities.
|
||||
*
|
||||
@@ -8,29 +10,58 @@ namespace Grav\Common;
|
||||
*/
|
||||
abstract class Utils
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
if (is_array($needle)) {
|
||||
$status = false;
|
||||
foreach ($needle as $each_needle) {
|
||||
$status = $status || ($each_needle === '' || strpos($haystack, $each_needle) === 0);
|
||||
if ($status) {
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
return $needle === '' || strpos($haystack, $needle) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
if (is_array($needle)) {
|
||||
$status = false;
|
||||
foreach ($needle as $each_needle) {
|
||||
$status = $status || ($each_needle === '' || substr($haystack, -strlen($each_needle)) === $each_needle);
|
||||
if ($status) {
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function contains($haystack, $needle)
|
||||
@@ -43,80 +74,12 @@ abstract class Utils
|
||||
*
|
||||
* @param object $obj1
|
||||
* @param object $obj2
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function mergeObjects($obj1, $obj2)
|
||||
{
|
||||
return (object) array_merge((array) $obj1, (array) $obj2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive remove a directory - DANGEROUS! USE WITH CARE!!!!
|
||||
*
|
||||
* @param $dir
|
||||
* @return bool
|
||||
*/
|
||||
public static function rrmdir($dir)
|
||||
{
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
/** @var \DirectoryIterator $fileinfo */
|
||||
foreach ($files as $fileinfo) {
|
||||
if ($fileinfo->isDir()) {
|
||||
if (false === rmdir($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (false === unlink($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive copy of one directory to another
|
||||
*
|
||||
* @param $src
|
||||
* @param $dest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function rcopy($src, $dest)
|
||||
{
|
||||
|
||||
// If the src is not a directory do a simple file copy
|
||||
if (!is_dir($src)) {
|
||||
copy($src, $dest);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the destination directory does not exist create it
|
||||
if (!is_dir($dest)) {
|
||||
if (!mkdir($dest)) {
|
||||
// If the destination directory could not be created stop processing
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the source directory to read in files
|
||||
$i = new \DirectoryIterator($src);
|
||||
/** @var \DirectoryIterator $f */
|
||||
foreach ($i as $f) {
|
||||
if ($f->isFile()) {
|
||||
copy($f->getRealPath(), "$dest/" . $f->getFilename());
|
||||
} else {
|
||||
if (!$f->isDot() && $f->isDir()) {
|
||||
static::rcopy($f->getRealPath(), "$dest/$f");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return (object)array_merge((array)$obj1, (array)$obj2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,6 +90,7 @@ abstract class Utils
|
||||
* @param string $ending
|
||||
* @param bool $exact
|
||||
* @param bool $considerHtml
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true)
|
||||
@@ -137,7 +101,7 @@ abstract class Utils
|
||||
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
|
||||
return $text;
|
||||
}
|
||||
// splits all html-tags to scanable lines
|
||||
// splits all html-tags to scannable lines
|
||||
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
|
||||
$total_length = strlen($ending);
|
||||
$truncate = '';
|
||||
@@ -145,34 +109,41 @@ abstract class Utils
|
||||
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
|
||||
if (!empty($line_matchings[1])) {
|
||||
// if it's an "empty element" with or without xhtml-conform closing slash
|
||||
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
|
||||
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is',
|
||||
$line_matchings[1])) {
|
||||
// do nothing
|
||||
// if tag is a closing tag
|
||||
} else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
|
||||
// delete tag from $open_tags list
|
||||
$pos = array_search($tag_matchings[1], $open_tags);
|
||||
if ($pos !== false) {
|
||||
unset($open_tags[$pos]);
|
||||
// if tag is a closing tag
|
||||
} else {
|
||||
if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
|
||||
// delete tag from $open_tags list
|
||||
$pos = array_search($tag_matchings[1], $open_tags);
|
||||
if ($pos !== false) {
|
||||
unset($open_tags[$pos]);
|
||||
}
|
||||
// if tag is an opening tag
|
||||
} else {
|
||||
if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
|
||||
// add tag to the beginning of $open_tags list
|
||||
array_unshift($open_tags, strtolower($tag_matchings[1]));
|
||||
}
|
||||
}
|
||||
// if tag is an opening tag
|
||||
} else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
|
||||
// add tag to the beginning of $open_tags list
|
||||
array_unshift($open_tags, strtolower($tag_matchings[1]));
|
||||
}
|
||||
// add html-tag to $truncate'd text
|
||||
$truncate .= $line_matchings[1];
|
||||
}
|
||||
// calculate the length of the plain text part of the line; handle entities as one character
|
||||
$content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
|
||||
if ($total_length+$content_length> $length) {
|
||||
$content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ',
|
||||
$line_matchings[2]));
|
||||
if ($total_length + $content_length > $length) {
|
||||
// the number of characters which are left
|
||||
$left = $length - $total_length;
|
||||
$entities_length = 0;
|
||||
// search for html entities
|
||||
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
|
||||
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities,
|
||||
PREG_OFFSET_CAPTURE)) {
|
||||
// calculate the real length of all entities in the legal range
|
||||
foreach ($entities[0] as $entity) {
|
||||
if ($entity[1]+1-$entities_length <= $left) {
|
||||
if ($entity[1] + 1 - $entities_length <= $left) {
|
||||
$left--;
|
||||
$entities_length += strlen($entity[0]);
|
||||
} else {
|
||||
@@ -181,7 +152,7 @@ abstract class Utils
|
||||
}
|
||||
}
|
||||
}
|
||||
$truncate .= substr($line_matchings[2], 0, $left+$entities_length);
|
||||
$truncate .= substr($line_matchings[2], 0, $left + $entities_length);
|
||||
// maximum length is reached, so get off the loop
|
||||
break;
|
||||
} else {
|
||||
@@ -202,7 +173,7 @@ abstract class Utils
|
||||
}
|
||||
// if the words shouldn't be cut in the middle...
|
||||
if (!$exact) {
|
||||
// ...search the last occurance of a space...
|
||||
// ...search the last occurrence of a space...
|
||||
$spacepos = strrpos($truncate, ' ');
|
||||
if (isset($spacepos)) {
|
||||
// ...and cut the text in this position
|
||||
@@ -217,6 +188,7 @@ abstract class Utils
|
||||
$truncate .= '</' . $tag . '>';
|
||||
}
|
||||
}
|
||||
|
||||
return $truncate;
|
||||
}
|
||||
|
||||
@@ -231,4 +203,234 @@ abstract class Utils
|
||||
{
|
||||
return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ability to download a file to the browser
|
||||
*
|
||||
* @param $file the full path to the file to be downloaded
|
||||
* @param bool $force_download as opposed to letting browser choose if to download or render
|
||||
*/
|
||||
public static function download($file, $force_download = true)
|
||||
{
|
||||
if (file_exists($file)) {
|
||||
// fire download event
|
||||
self::getGrav()->fireEvent('onBeforeDownload', new Event(['file' => $file]));
|
||||
|
||||
$file_parts = pathinfo($file);
|
||||
$filesize = filesize($file);
|
||||
|
||||
set_time_limit(0);
|
||||
ignore_user_abort(false);
|
||||
|
||||
if ($force_download) {
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename=' . $file_parts['basename']);
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
||||
header('Pragma: public');
|
||||
} else {
|
||||
header("Content-Type: " . Utils::getMimeType($file_parts['extension']));
|
||||
}
|
||||
header('Content-Length: ' . $filesize);
|
||||
|
||||
// 8kb chunks for now
|
||||
$chunk = 8 * 1024;
|
||||
|
||||
$fh = fopen($file, "rb");
|
||||
|
||||
if ($fh === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Repeat reading until EOF
|
||||
while (!feof($fh)) {
|
||||
echo fread($fh, $chunk);
|
||||
|
||||
ob_flush(); // flush output
|
||||
flush();
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename
|
||||
*
|
||||
* @param $extension Extension of file (eg .txt)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMimeType($extension)
|
||||
{
|
||||
$extension = strtolower($extension);
|
||||
|
||||
switch ($extension) {
|
||||
case "js":
|
||||
return "application/x-javascript";
|
||||
|
||||
case "json":
|
||||
return "application/json";
|
||||
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "jpe":
|
||||
return "image/jpg";
|
||||
|
||||
case "png":
|
||||
case "gif":
|
||||
case "bmp":
|
||||
case "tiff":
|
||||
return "image/" . $extension;
|
||||
|
||||
case "css":
|
||||
return "text/css";
|
||||
|
||||
case "xml":
|
||||
return "application/xml";
|
||||
|
||||
case "doc":
|
||||
case "docx":
|
||||
return "application/msword";
|
||||
|
||||
case "xls":
|
||||
case "xlt":
|
||||
case "xlm":
|
||||
case "xld":
|
||||
case "xla":
|
||||
case "xlc":
|
||||
case "xlw":
|
||||
case "xll":
|
||||
return "application/vnd.ms-excel";
|
||||
|
||||
case "ppt":
|
||||
case "pps":
|
||||
return "application/vnd.ms-powerpoint";
|
||||
|
||||
case "rtf":
|
||||
return "application/rtf";
|
||||
|
||||
case "pdf":
|
||||
return "application/pdf";
|
||||
|
||||
case "html":
|
||||
case "htm":
|
||||
case "php":
|
||||
return "text/html";
|
||||
|
||||
case "txt":
|
||||
return "text/plain";
|
||||
|
||||
case "mpeg":
|
||||
case "mpg":
|
||||
case "mpe":
|
||||
return "video/mpeg";
|
||||
|
||||
case "mp3":
|
||||
return "audio/mpeg3";
|
||||
|
||||
case "wav":
|
||||
return "audio/wav";
|
||||
|
||||
case "aiff":
|
||||
case "aif":
|
||||
return "audio/aiff";
|
||||
|
||||
case "avi":
|
||||
return "video/msvideo";
|
||||
|
||||
case "wmv":
|
||||
return "video/x-ms-wmv";
|
||||
|
||||
case "mov":
|
||||
return "video/quicktime";
|
||||
|
||||
case "zip":
|
||||
return "application/zip";
|
||||
|
||||
case "tar":
|
||||
return "application/x-tar";
|
||||
|
||||
case "swf":
|
||||
return "application/x-shockwave-flash";
|
||||
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path by processing relative `.` and `..` syntax and merging path
|
||||
*
|
||||
* @param $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function normalizePath($path)
|
||||
{
|
||||
$root = ($path[0] === '/') ? '/' : '';
|
||||
|
||||
$segments = explode('/', trim($path, '/'));
|
||||
$ret = array();
|
||||
foreach ($segments as $segment) {
|
||||
if (($segment == '.') || empty($segment)) {
|
||||
continue;
|
||||
}
|
||||
if ($segment == '..') {
|
||||
array_pop($ret);
|
||||
} else {
|
||||
array_push($ret, $segment);
|
||||
}
|
||||
}
|
||||
|
||||
return $root . implode('/', $ret);
|
||||
}
|
||||
|
||||
public static function timezones()
|
||||
{
|
||||
$timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL);
|
||||
$offsets = [];
|
||||
$testDate = new \DateTime;
|
||||
|
||||
foreach ($timezones as $zone) {
|
||||
$tz = new \DateTimeZone($zone);
|
||||
$offsets[$zone] = $tz->getOffset($testDate);
|
||||
}
|
||||
|
||||
asort($offsets);
|
||||
|
||||
$timezone_list = array();
|
||||
foreach ($offsets as $timezone => $offset) {
|
||||
$offset_prefix = $offset < 0 ? '-' : '+';
|
||||
$offset_formatted = gmdate('H:i', abs($offset));
|
||||
|
||||
$pretty_offset = "UTC${offset_prefix}${offset_formatted}";
|
||||
|
||||
$timezone_list[$timezone] = "(${pretty_offset}) $timezone";
|
||||
}
|
||||
|
||||
return $timezone_list;
|
||||
|
||||
}
|
||||
|
||||
public static function arrayFilterRecursive(Array $source, $fn)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($source as $key => $value)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
$result[$key] = static::arrayFilterRecursive($value, $fn);
|
||||
continue;
|
||||
}
|
||||
if ($fn($key, $value))
|
||||
{
|
||||
$result[$key] = $value; // KEEP
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Backup\ZipBackup;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use RocketTheme\Toolbox\File\JsonFile;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
@@ -14,14 +17,9 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
*/
|
||||
class BackupCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $source;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $progress;
|
||||
|
||||
/**
|
||||
@@ -34,13 +32,12 @@ class BackupCommand extends Command
|
||||
->addArgument(
|
||||
'destination',
|
||||
InputArgument::OPTIONAL,
|
||||
'Where to store the backup'
|
||||
'Where to store the backup (/backup is default)'
|
||||
|
||||
)
|
||||
->setDescription("Creates a backup of the Grav instance")
|
||||
->setHelp('The <info>backup</info> creates a zipped backup. Optionally can be saved in a different destination.');
|
||||
|
||||
|
||||
$this->source = getcwd();
|
||||
}
|
||||
|
||||
@@ -52,33 +49,23 @@ class BackupCommand extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
|
||||
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
|
||||
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
|
||||
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
|
||||
$this->setupConsole($input, $output);
|
||||
|
||||
$this->progress = new ProgressBar($output);
|
||||
$this->progress->setFormat('Archiving <cyan>%current%</cyan> files [<green>%bar%</green>] %elapsed:6s% %memory:6s%');
|
||||
|
||||
$name = basename($this->source);
|
||||
$dir = dirname($this->source);
|
||||
$date = date('YmdHis', time());
|
||||
$filename = $name . '-' . $date . '.zip';
|
||||
self::getGrav()['config']->init();
|
||||
|
||||
$destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : ROOT_DIR;
|
||||
$destination = rtrim($destination, DS) . DS . $filename;
|
||||
$destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : null;
|
||||
$log = JsonFile::instance(self::getGrav()['locator']->findResource("log://backup.log", true, true));
|
||||
$backup = ZipBackup::backup($destination, [$this, 'output']);
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('Creating new Backup "' . $destination . '"');
|
||||
$this->progress->start();
|
||||
$log->content([
|
||||
'time' => time(),
|
||||
'location' => $backup
|
||||
]);
|
||||
$log->save();
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip->open($destination, \ZipArchive::CREATE);
|
||||
$zip->addEmptyDir($name);
|
||||
|
||||
$this->folderToZip($this->source, $zip, strlen($dir . DS), $this->progress);
|
||||
$zip->close();
|
||||
$this->progress->finish();
|
||||
$output->writeln('');
|
||||
$output->writeln('');
|
||||
|
||||
@@ -90,25 +77,21 @@ class BackupCommand extends Command
|
||||
* @param $exclusiveLength
|
||||
* @param $progress
|
||||
*/
|
||||
private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, ProgressBar $progress)
|
||||
public function output($args)
|
||||
{
|
||||
$handle = opendir($folder);
|
||||
while (false !== $f = readdir($handle)) {
|
||||
if ($f != '.' && $f != '..') {
|
||||
$filePath = "$folder/$f";
|
||||
// Remove prefix from file path before add to zip.
|
||||
$localPath = substr($filePath, $exclusiveLength);
|
||||
if (is_file($filePath)) {
|
||||
$zipFile->addFile($filePath, $localPath);
|
||||
$progress->advance();
|
||||
} elseif (is_dir($filePath)) {
|
||||
// Add sub-directory.
|
||||
$zipFile->addEmptyDir($localPath);
|
||||
self::folderToZip($filePath, $zipFile, $exclusiveLength, $progress);
|
||||
switch ($args['type']) {
|
||||
case 'message':
|
||||
$this->output->writeln($args['message']);
|
||||
break;
|
||||
case 'progress':
|
||||
if ($args['complete']) {
|
||||
$this->progress->finish();
|
||||
} else {
|
||||
$this->progress->advance();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@@ -13,6 +14,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
*/
|
||||
class CleanCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@@ -66,7 +68,6 @@ class CleanCommand extends Command
|
||||
'vendor/filp/whoops/.scrutinizer.yml',
|
||||
'vendor/filp/whoops/.travis.yml',
|
||||
'vendor/filp/whoops/phpunit.xml.dist',
|
||||
'vendor/filp/whoops/src/deprecated',
|
||||
'vendor/gregwar/image/Gregwar/Image/composer.json',
|
||||
'vendor/gregwar/image/Gregwar/Image/phpunit.xml',
|
||||
'vendor/gregwar/image/Gregwar/Image/.gitignore',
|
||||
@@ -178,27 +179,14 @@ class CleanCommand extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
|
||||
|
||||
// Create a red output option
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
|
||||
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
|
||||
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
|
||||
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
|
||||
|
||||
$this->cleanPaths($output);
|
||||
|
||||
|
||||
$this->setupConsole($input, $output);
|
||||
$this->cleanPaths();
|
||||
}
|
||||
|
||||
// loops over the array of paths and deletes the files/folders
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
private function cleanPaths(OutputInterface $output)
|
||||
private function cleanPaths()
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<red>DELETING</red>');
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<red>DELETING</red>');
|
||||
|
||||
$anything = false;
|
||||
|
||||
@@ -207,16 +195,16 @@ class CleanCommand extends Command
|
||||
|
||||
if (is_dir($path) && @Folder::delete($path)) {
|
||||
$anything = true;
|
||||
$output->writeln('<red>dir: </red>' . $path);
|
||||
$this->output->writeln('<red>dir: </red>' . $path);
|
||||
} elseif (is_file($path) && @unlink($path)) {
|
||||
$anything = true;
|
||||
$output->writeln('<red>file: </red>' . $path);
|
||||
$this->output->writeln('<red>file: </red>' . $path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$anything) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<green>Nothing to clean...</green>');
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<green>Nothing to clean...</green>');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Cache;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@@ -14,6 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
*/
|
||||
class ClearCacheCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -38,40 +40,33 @@ class ClearCacheCommand extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// Create a red output option
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
|
||||
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
|
||||
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
|
||||
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
|
||||
|
||||
$this->cleanPaths($input, $output);
|
||||
$this->setupConsole($input, $output);
|
||||
$this->cleanPaths();
|
||||
}
|
||||
|
||||
// loops over the array of paths and deletes the files/folders
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* loops over the array of paths and deletes the files/folders
|
||||
*/
|
||||
private function cleanPaths(InputInterface $input, OutputInterface $output)
|
||||
private function cleanPaths()
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<magenta>Clearing cache</magenta>');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<magenta>Clearing cache</magenta>');
|
||||
$this->output->writeln('');
|
||||
|
||||
if ($input->getOption('all')) {
|
||||
if ($this->input->getOption('all')) {
|
||||
$remove = 'all';
|
||||
} elseif ($input->getOption('assets-only')) {
|
||||
} elseif ($this->input->getOption('assets-only')) {
|
||||
$remove = 'assets-only';
|
||||
} elseif ($input->getOption('images-only')) {
|
||||
} elseif ($this->input->getOption('images-only')) {
|
||||
$remove = 'images-only';
|
||||
} elseif ($input->getOption('cache-only')) {
|
||||
} elseif ($this->input->getOption('cache-only')) {
|
||||
$remove = 'cache-only';
|
||||
} else {
|
||||
$remove = 'standard';
|
||||
}
|
||||
|
||||
foreach (Cache::clearCache($remove) as $result) {
|
||||
$output->writeln($result);
|
||||
$this->output->writeln($result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
system/src/Grav/Console/Cli/ComposerCommand.php
Normal file
82
system/src/Grav/Console/Cli/ComposerCommand.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Class ComposerCommand
|
||||
* @package Grav\Console\Cli
|
||||
*/
|
||||
class ComposerCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $config;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $local_config;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $destination;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $user_path;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("composer")
|
||||
->addOption(
|
||||
'install',
|
||||
'i',
|
||||
InputOption::VALUE_NONE,
|
||||
'install the dependencies'
|
||||
)
|
||||
->addOption(
|
||||
'update',
|
||||
'u',
|
||||
InputOption::VALUE_NONE,
|
||||
'update the dependencies'
|
||||
)
|
||||
->setDescription("Updates the composer vendordependencies needed by Grav.")
|
||||
->setHelp('The <info>composer</info> command updates the composer vendordependencies needed by Grav');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
|
||||
$action = 'update';
|
||||
|
||||
if ($input->getOption('install')) {
|
||||
$action = 'install';
|
||||
}
|
||||
|
||||
// Updates composer first
|
||||
$output->writeln("\nInstalling vendor dependencies");
|
||||
$output->writeln($this->composerUpdate(GRAV_ROOT, $action));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -15,7 +16,7 @@ use Symfony\Component\Yaml\Yaml;
|
||||
*/
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
|
||||
use ConsoleTrait;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
@@ -64,6 +65,7 @@ class InstallCommand extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
|
||||
$dependencies_file = '.dependencies';
|
||||
$local_config_file = exec('eval echo ~/.grav/config');
|
||||
@@ -73,12 +75,6 @@ class InstallCommand extends Command
|
||||
$this->destination = rtrim($this->destination, DS) . DS;
|
||||
$this->user_path = $this->destination . USER_PATH;
|
||||
|
||||
// Create a red output option
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
|
||||
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
|
||||
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
|
||||
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
|
||||
|
||||
if (file_exists($local_config_file)) {
|
||||
$this->local_config = Yaml::parse($local_config_file);
|
||||
$output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
|
||||
@@ -98,11 +94,11 @@ class InstallCommand extends Command
|
||||
if (!$input->getOption('symlink')) {
|
||||
// Updates composer first
|
||||
$output->writeln("\nInstalling vendor dependencies");
|
||||
$output->writeln(system('php bin/composer.phar --working-dir="'.$this->destination.'" --no-interaction update'));
|
||||
$output->writeln($this->composerUpdate(GRAV_ROOT, 'install'));
|
||||
|
||||
$this->gitclone($output);
|
||||
$this->gitclone();
|
||||
} else {
|
||||
$this->symlink($output);
|
||||
$this->symlink();
|
||||
}
|
||||
} else {
|
||||
$output->writeln('<red>ERROR</red> invalid YAML in ' . $dependencies_file);
|
||||
@@ -111,45 +107,43 @@ class InstallCommand extends Command
|
||||
|
||||
}
|
||||
|
||||
// loops over the array of paths and deletes the files/folders
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
* Clones from Git
|
||||
*/
|
||||
private function gitclone(OutputInterface $output)
|
||||
private function gitclone()
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<green>Cloning Bits</green>');
|
||||
$output->writeln('============');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<green>Cloning Bits</green>');
|
||||
$this->output->writeln('============');
|
||||
$this->output->writeln('');
|
||||
|
||||
foreach ($this->config['git'] as $repo => $data) {
|
||||
$path = $this->destination . DS . $data['path'];
|
||||
if (!file_exists($path)) {
|
||||
exec('cd ' . $this->destination . ' && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']);
|
||||
$output->writeln('<green>SUCCESS</green> cloned <magenta>' . $data['url'] . '</magenta> -> <cyan>' . $path . '</cyan>');
|
||||
$output->writeln('');
|
||||
exec('cd "' . $this->destination . '" && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']);
|
||||
$this->output->writeln('<green>SUCCESS</green> cloned <magenta>' . $data['url'] . '</magenta> -> <cyan>' . $path . '</cyan>');
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$output->writeln('<red>' . $path . ' already exists, skipping...</red>');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('<red>' . $path . ' already exists, skipping...</red>');
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// loops over the array of paths and deletes the files/folders
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
* Symlinks
|
||||
*/
|
||||
private function symlink(OutputInterface $output)
|
||||
private function symlink()
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<green>Symlinking Bits</green>');
|
||||
$output->writeln('===============');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<green>Symlinking Bits</green>');
|
||||
$this->output->writeln('===============');
|
||||
$this->output->writeln('');
|
||||
|
||||
if (!$this->local_config) {
|
||||
$output->writeln('<red>No local configuration available, aborting...</red>');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('<red>No local configuration available, aborting...</red>');
|
||||
$this->output->writeln('');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,15 +155,15 @@ class InstallCommand extends Command
|
||||
if (file_exists($from)) {
|
||||
if (!file_exists($to)) {
|
||||
symlink($from, $to);
|
||||
$output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
|
||||
$this->output->writeln('');
|
||||
}
|
||||
} else {
|
||||
$output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
|
||||
$output->writeln('');
|
||||
$this->output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -14,6 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
*/
|
||||
class NewProjectCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -45,6 +47,7 @@ class NewProjectCommand extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
|
||||
$sandboxCommand = $this->getApplication()->find('sandbox');
|
||||
$installCommand = $this->getApplication()->find('install');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -16,10 +16,13 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
*/
|
||||
class SandboxCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $directories = array(
|
||||
'/backup',
|
||||
'/cache',
|
||||
'/logs',
|
||||
'/images',
|
||||
@@ -66,22 +69,8 @@ class SandboxCommand extends Command
|
||||
|
||||
protected $default_file = "---\ntitle: HomePage\n---\n# HomePage\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porttitor eu felis sed ornare. Sed a mauris venenatis, pulvinar velit vel, dictum enim. Phasellus ac rutrum velit. Nunc lorem purus, hendrerit sit amet augue aliquet, iaculis ultricies nisl. Suspendisse tincidunt euismod risus, quis feugiat arcu tincidunt eget. Nulla eros mi, commodo vel ipsum vel, aliquet congue odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque velit orci, laoreet at adipiscing eu, interdum quis nibh. Nunc a accumsan purus.";
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $source;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $destination;
|
||||
/**
|
||||
* @var InputInterface $input
|
||||
*/
|
||||
protected $input;
|
||||
/**
|
||||
* @var OutputInterface $output
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -114,14 +103,8 @@ class SandboxCommand extends Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
$this->destination = $input->getArgument('destination');
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
|
||||
// Create a red output option
|
||||
$this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
|
||||
$this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
|
||||
$this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
|
||||
|
||||
// Symlink the Core Stuff
|
||||
if ($input->getOption('symlink')) {
|
||||
@@ -189,7 +172,7 @@ class SandboxCommand extends Command
|
||||
$to = $this->destination . $target;
|
||||
|
||||
$this->output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
|
||||
Utils::rcopy($from, $to);
|
||||
Folder::rcopy($from, $to);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +252,7 @@ class SandboxCommand extends Command
|
||||
|
||||
if (count($pages_files) == 0) {
|
||||
$destination = $this->source . '/user/pages';
|
||||
Utils::rcopy($destination, $pages_dir);
|
||||
Folder::rcopy($destination, $pages_dir);
|
||||
$this->output->writeln(' <cyan>' . $destination . '</cyan> <comment>-></comment> Created');
|
||||
|
||||
}
|
||||
@@ -281,7 +264,7 @@ class SandboxCommand extends Command
|
||||
private function perms()
|
||||
{
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<comment>Permisions Initializing</comment>');
|
||||
$this->output->writeln('<comment>Permissions Initializing</comment>');
|
||||
|
||||
$dir_perms = 0755;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Grav\Console;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Console\Cli\ClearCacheCommand;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
@@ -83,6 +84,13 @@ trait ConsoleTrait
|
||||
}
|
||||
}
|
||||
|
||||
public function composerUpdate($path, $action = 'install')
|
||||
{
|
||||
$composer = Composer::getComposerExecutor();
|
||||
|
||||
return system($composer . ' --working-dir="'.$path.'" --no-interaction --no-dev --prefer-dist -o '. $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $all
|
||||
*
|
||||
|
||||
@@ -255,7 +255,7 @@ class InstallCommand extends Command
|
||||
|
||||
// Confirmation received, copy over the data
|
||||
$this->output->writeln(" |- Installing demo content... <green>ok</green> ");
|
||||
Utils::rcopy($demo_dir, $dest_dir);
|
||||
Folder::rcopy($demo_dir, $dest_dir);
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
@@ -359,8 +359,6 @@ class InstallCommand extends Command
|
||||
{
|
||||
$matches = $this->getGitRegexMatches($package);
|
||||
|
||||
$to = $this->destination . DS . $package->install_path;
|
||||
|
||||
$this->output->writeln("Preparing to Git clone <cyan>" . $package->name . "</cyan> from " . $matches[0]);
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
|
||||
@@ -6,6 +6,7 @@ use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Common\GPM\Upgrader;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -84,9 +85,10 @@ class SelfupgradeCommand extends Command
|
||||
$this->setupConsole($input, $output);
|
||||
$this->upgrader = new Upgrader($this->input->getOption('force'));
|
||||
|
||||
$update = $this->upgrader->getAssets()['grav-update'];
|
||||
|
||||
$local = $this->upgrader->getLocalVersion();
|
||||
$remote = $this->upgrader->getRemoteVersion();
|
||||
$update = $this->upgrader->getAssets()->{'grav-update'};
|
||||
$release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
|
||||
|
||||
if (!$this->upgrader->isUpgradable()) {
|
||||
@@ -94,6 +96,9 @@ class SelfupgradeCommand extends Command
|
||||
exit;
|
||||
}
|
||||
|
||||
// not used but preloaded just in case!
|
||||
new ArrayInput([]);
|
||||
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$skipPrompt = $this->input->getOption('all-yes');
|
||||
|
||||
@@ -110,10 +115,10 @@ class SelfupgradeCommand extends Command
|
||||
|
||||
$this->output->writeln("");
|
||||
foreach ($changelog as $version => $log) {
|
||||
$title = $version . ' [' . $log->date . ']';
|
||||
$title = $version . ' [' . $log['date'] . ']';
|
||||
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
|
||||
return "\n" . ucfirst($match[1]) . ":";
|
||||
}, $log->content);
|
||||
}, $log['content']);
|
||||
|
||||
$this->output->writeln($title);
|
||||
$this->output->writeln(str_repeat('-', strlen($title)));
|
||||
@@ -138,7 +143,7 @@ class SelfupgradeCommand extends Command
|
||||
$this->output->writeln("");
|
||||
$this->output->writeln("Preparing to upgrade to v<cyan>$remote</cyan>..");
|
||||
|
||||
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update->size) . "]... 0%");
|
||||
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update['size']) . "]... 0%");
|
||||
$this->file = $this->download($update);
|
||||
|
||||
$this->output->write(" |- Installing upgrade... ");
|
||||
@@ -164,17 +169,17 @@ class SelfupgradeCommand extends Command
|
||||
private function download($package)
|
||||
{
|
||||
$this->tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid();
|
||||
$output = Response::get($package->download, [], [$this, 'progress']);
|
||||
$output = Response::get($package['download'], [], [$this, 'progress']);
|
||||
|
||||
Folder::mkdir($this->tmp);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package->size) . "]... 100%");
|
||||
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package['size']) . "]... 100%");
|
||||
$this->output->writeln('');
|
||||
|
||||
file_put_contents($this->tmp . DS . $package->name, $output);
|
||||
file_put_contents($this->tmp . DS . $package['name'], $output);
|
||||
|
||||
return $this->tmp . DS . $package->name;
|
||||
return $this->tmp . DS . $package['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -110,14 +110,14 @@ class UninstallCommand extends Command
|
||||
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
$checks = $this->checkDestination($package);
|
||||
$checks = $this->checkDestination($slug, $package);
|
||||
|
||||
if (!$checks) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->write(" |- Uninstalling package... ");
|
||||
$uninstall = $this->uninstallPackage($package);
|
||||
$uninstall = $this->uninstallPackage($slug, $package);
|
||||
|
||||
if (!$uninstall) {
|
||||
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
|
||||
@@ -135,12 +135,14 @@ class UninstallCommand extends Command
|
||||
|
||||
|
||||
/**
|
||||
* @param $slug
|
||||
* @param $package
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function uninstallPackage($package)
|
||||
private function uninstallPackage($slug, $package)
|
||||
{
|
||||
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug);
|
||||
$path = self::getGrav()['locator']->findResource($package->package_type . '://' .$slug);
|
||||
Installer::uninstall($path);
|
||||
$errorCode = Installer::lastErrorCode();
|
||||
|
||||
@@ -159,15 +161,17 @@ class UninstallCommand extends Command
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $slug
|
||||
* @param $package
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
private function checkDestination($package)
|
||||
private function checkDestination($slug, $package)
|
||||
{
|
||||
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug);
|
||||
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $slug);
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$skipPrompt = $this->input->getOption('all-yes');
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ class UpdateCommand extends Command
|
||||
$commandExec = $installCommand->run($args, $this->output);
|
||||
|
||||
if ($commandExec != 0) {
|
||||
$this->output->writeln("<red>Error:</red> An error occured while trying to install the extensions");
|
||||
$this->output->writeln("<red>Error:</red> An error occurred while trying to install the extensions");
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
106
web.config
106
web.config
@@ -1,51 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<defaultDocument>
|
||||
<files>
|
||||
<remove value="index.php" />
|
||||
<add value="index.php" />
|
||||
</files>
|
||||
</defaultDocument>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="request_filename" stopProcessing="true">
|
||||
<match url="." ignoreCase="false" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="index.php" />
|
||||
</rule>
|
||||
<rule name="user_accounts" stopProcessing="true">
|
||||
<match url="^user/accounts/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="user_config" stopProcessing="true">
|
||||
<match url="^user/config/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="user_error_redirect" stopProcessing="true">
|
||||
<match url="^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="cache" stopProcessing="true">
|
||||
<match url="^cache/(.*)" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="bin" stopProcessing="true">
|
||||
<match url="^bin/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="system" stopProcessing="true">
|
||||
<match url="^system/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="vendor" stopProcessing="true">
|
||||
<match url="^vendor/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<defaultDocument>
|
||||
<files>
|
||||
<remove value="index.php" />
|
||||
<add value="index.php" />
|
||||
</files>
|
||||
</defaultDocument>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="request_filename" stopProcessing="true">
|
||||
<match url="." ignoreCase="false" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="index.php" />
|
||||
</rule>
|
||||
<rule name="user_accounts" stopProcessing="true">
|
||||
<match url="^user/accounts/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="user_config" stopProcessing="true">
|
||||
<match url="^user/config/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="user_error_redirect" stopProcessing="true">
|
||||
<match url="^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="cache" stopProcessing="true">
|
||||
<match url="^cache/(.*)" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="bin" stopProcessing="true">
|
||||
<match url="^bin/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="backup" stopProcessing="true">
|
||||
<match url="^backup/(.*)" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="system" stopProcessing="true">
|
||||
<match url="^system/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
<rule name="vendor" stopProcessing="true">
|
||||
<match url="^vendor/(.*)$" ignoreCase="false" />
|
||||
<action type="Redirect" url="error" redirectType="Permanent" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
|
||||
Reference in New Issue
Block a user