mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 23:39:58 +01:00
Compare commits
458 Commits
1.6.7
...
1.7.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dafa2a207 | ||
|
|
5253aa6aef | ||
|
|
1b34530a4a | ||
|
|
ae41b6f5ff | ||
|
|
4d09a345a4 | ||
|
|
8ac3451fbc | ||
|
|
15f1f2a03d | ||
|
|
ffa9ef6a7e | ||
|
|
a75c0cbe62 | ||
|
|
c795ead402 | ||
|
|
91270c9c66 | ||
|
|
9259c2f660 | ||
|
|
138d1e93e3 | ||
|
|
5db9f16174 | ||
|
|
342eac1047 | ||
|
|
88c859fde2 | ||
|
|
8b5bce0b6d | ||
|
|
09c4cf66f5 | ||
|
|
46b859b8df | ||
|
|
a7c23c58e5 | ||
|
|
d206711354 | ||
|
|
0007100a97 | ||
|
|
3d999501a0 | ||
|
|
52f83775e3 | ||
|
|
a35cf5a830 | ||
|
|
39837d0826 | ||
|
|
66bd6a4046 | ||
|
|
4e9d3395e0 | ||
|
|
248c7764f0 | ||
|
|
f72eb1b002 | ||
|
|
25caa5138a | ||
|
|
dffb227df6 | ||
|
|
7c161d5cbc | ||
|
|
7b2313ef0b | ||
|
|
1ad2a3f212 | ||
|
|
1f2363b623 | ||
|
|
d1c069c2ee | ||
|
|
67e772a5ce | ||
|
|
59bb167bd4 | ||
|
|
5c9eb1cdb8 | ||
|
|
94a54d2a82 | ||
|
|
f201d48112 | ||
|
|
df51b64b35 | ||
|
|
93ff915737 | ||
|
|
e30ab9a043 | ||
|
|
8e59a08f9e | ||
|
|
5a874006b4 | ||
|
|
5d639cc633 | ||
|
|
ab7038b49e | ||
|
|
651b354d3e | ||
|
|
90c2079529 | ||
|
|
fa064301a2 | ||
|
|
5424047d02 | ||
|
|
ce2b80aeb9 | ||
|
|
e91ae5542d | ||
|
|
922f263005 | ||
|
|
01af81a0a6 | ||
|
|
bd8c274f38 | ||
|
|
05fb69daa7 | ||
|
|
21f95eba53 | ||
|
|
4ef56054ee | ||
|
|
bec9292a5c | ||
|
|
0fa6328816 | ||
|
|
b30240c340 | ||
|
|
59eb3b4cb2 | ||
|
|
5fa047c1b7 | ||
|
|
30481da51d | ||
|
|
beb8f09a9d | ||
|
|
603bb6c878 | ||
|
|
dac3e57fd4 | ||
|
|
b601d2c8fd | ||
|
|
ab75201f11 | ||
|
|
45b1b0a2ef | ||
|
|
ca2f657c98 | ||
|
|
5c33882f5b | ||
|
|
0f85214693 | ||
|
|
4570c00041 | ||
|
|
4a0c8846e2 | ||
|
|
41e51cd86d | ||
|
|
e2ed3098a3 | ||
|
|
4e9ca82a0f | ||
|
|
f032f310b5 | ||
|
|
6b665b112c | ||
|
|
d9dbe5520d | ||
|
|
5b8674122a | ||
|
|
2e245cd36f | ||
|
|
e36a2ea1b0 | ||
|
|
53b7c95b0d | ||
|
|
46975cca22 | ||
|
|
a418acc32b | ||
|
|
eeb35fc521 | ||
|
|
90ca9f9d49 | ||
|
|
ba267a389e | ||
|
|
0ab99806db | ||
|
|
c300a3b8f8 | ||
|
|
8480fb68ac | ||
|
|
8e82056afa | ||
|
|
3e34d54b9a | ||
|
|
9ad7b208ba | ||
|
|
1fa62d2bdc | ||
|
|
b659c56aec | ||
|
|
3bd02b95fe | ||
|
|
9e5ad84a48 | ||
|
|
40a6c4bf72 | ||
|
|
22acffac5c | ||
|
|
ede749821d | ||
|
|
f26e518c03 | ||
|
|
0f6a517589 | ||
|
|
3530f4fdef | ||
|
|
b92476d40d | ||
|
|
0d0bb2c229 | ||
|
|
de59bad0f8 | ||
|
|
72ad49610c | ||
|
|
dcd1f3b10d | ||
|
|
52cf554ea2 | ||
|
|
f30f6485ba | ||
|
|
dd8b503aa0 | ||
|
|
dab30673e0 | ||
|
|
13689c2065 | ||
|
|
6e23627f26 | ||
|
|
7db85cc79c | ||
|
|
9b6f218f33 | ||
|
|
829da9ee3a | ||
|
|
033b54104e | ||
|
|
e5cedd074b | ||
|
|
a6741cb761 | ||
|
|
8cbc2a27cd | ||
|
|
5f1639dc63 | ||
|
|
ed87faad92 | ||
|
|
8d8b803e66 | ||
|
|
e4ed00d84a | ||
|
|
239f34d40c | ||
|
|
20b9ca56fa | ||
|
|
647ae0fda3 | ||
|
|
806dbd9ee5 | ||
|
|
1ab8442630 | ||
|
|
040c34d693 | ||
|
|
505661404b | ||
|
|
a2ea6faf4d | ||
|
|
ce51491b4d | ||
|
|
8942aa8afc | ||
|
|
d241223aa3 | ||
|
|
256cbe3f12 | ||
|
|
8b31ee173e | ||
|
|
182970eb78 | ||
|
|
9ed3da3df2 | ||
|
|
14eaa4d00a | ||
|
|
e134e3dbd9 | ||
|
|
5bfb168cd7 | ||
|
|
5aef09a410 | ||
|
|
76732ab671 | ||
|
|
d16f83fdd8 | ||
|
|
02e10ff8fe | ||
|
|
6a44d8f286 | ||
|
|
4b614d871f | ||
|
|
f54d9af758 | ||
|
|
f883191d99 | ||
|
|
de5ead78d1 | ||
|
|
44bbdf7e39 | ||
|
|
4b4eedf467 | ||
|
|
bb477fd3b1 | ||
|
|
758e316a65 | ||
|
|
2c38e24d00 | ||
|
|
3286d70092 | ||
|
|
9fc37e46fa | ||
|
|
f304f429c5 | ||
|
|
ca24e63d22 | ||
|
|
65c73f639f | ||
|
|
d2833a1997 | ||
|
|
aa8f764436 | ||
|
|
f1909d80db | ||
|
|
7718dd7e98 | ||
|
|
cc66070e85 | ||
|
|
bbdc54b406 | ||
|
|
c013f63b26 | ||
|
|
aa007badb5 | ||
|
|
618a59921a | ||
|
|
bb2e7a720b | ||
|
|
039f71dc61 | ||
|
|
c36e6abd66 | ||
|
|
c2b1142b7a | ||
|
|
e03fb200a6 | ||
|
|
27b8db4c10 | ||
|
|
a964a34a6f | ||
|
|
1c28fd4c4c | ||
|
|
e8529e7d0b | ||
|
|
a6032af594 | ||
|
|
ea49415e14 | ||
|
|
7b26022f9f | ||
|
|
443fecfeb6 | ||
|
|
c3324e3702 | ||
|
|
9e60408769 | ||
|
|
3737bc9371 | ||
|
|
3d2360c995 | ||
|
|
08b8505b6d | ||
|
|
afd53d76c2 | ||
|
|
9607a99a7d | ||
|
|
7e63935001 | ||
|
|
3d767a4d25 | ||
|
|
ee53e1be6e | ||
|
|
57c65ad881 | ||
|
|
a372ae90c2 | ||
|
|
676924cce5 | ||
|
|
c8739c40a5 | ||
|
|
36a3a95ed9 | ||
|
|
95c58c8361 | ||
|
|
66c17a8f53 | ||
|
|
00ff9ac42d | ||
|
|
7172da8ed6 | ||
|
|
c55ea919ef | ||
|
|
53216631a6 | ||
|
|
69d6b52a0e | ||
|
|
e13a8304e6 | ||
|
|
3c2b17853c | ||
|
|
ea1e0a76c1 | ||
|
|
833fe8b729 | ||
|
|
23d508b390 | ||
|
|
288b2a1953 | ||
|
|
e8b24479b9 | ||
|
|
1485c23aba | ||
|
|
d43357f366 | ||
|
|
86b1f1fbac | ||
|
|
c8e5aa05f9 | ||
|
|
04ccce1f67 | ||
|
|
5826821895 | ||
|
|
3ed341304b | ||
|
|
025e73affd | ||
|
|
5635ba2bb7 | ||
|
|
b5e26133a7 | ||
|
|
95637a243c | ||
|
|
cd417a1509 | ||
|
|
631ae3d3d5 | ||
|
|
7c34224304 | ||
|
|
6062e47377 | ||
|
|
c97faa0238 | ||
|
|
a94abb4fb2 | ||
|
|
8ab317b49a | ||
|
|
44dda3d607 | ||
|
|
75ed986437 | ||
|
|
3ac785b9ce | ||
|
|
de367e1558 | ||
|
|
4fead303d1 | ||
|
|
41898af46f | ||
|
|
fe8833876c | ||
|
|
e116998914 | ||
|
|
053f96dec1 | ||
|
|
94494c3c96 | ||
|
|
69b39b4b21 | ||
|
|
5afae3c3f2 | ||
|
|
eed3d84a10 | ||
|
|
e7b996104f | ||
|
|
02f544f813 | ||
|
|
69b5a779e4 | ||
|
|
3f176c1924 | ||
|
|
91d20d8840 | ||
|
|
6998505e3c | ||
|
|
ffa2e0a6f6 | ||
|
|
b54e4fb71b | ||
|
|
8f391d327a | ||
|
|
e0e29f468b | ||
|
|
89b9cc5367 | ||
|
|
fa5c1e495d | ||
|
|
7fdb2c10cb | ||
|
|
e422eebd3c | ||
|
|
3dca7e3539 | ||
|
|
75210b102e | ||
|
|
f0e97a7277 | ||
|
|
f6c30cbeae | ||
|
|
cca7b6b1d4 | ||
|
|
e16c81516e | ||
|
|
c765787102 | ||
|
|
dff3872b43 | ||
|
|
3c91cea232 | ||
|
|
a2e9c013ad | ||
|
|
dd82ab45bc | ||
|
|
46b710b435 | ||
|
|
a6d3e1ee8e | ||
|
|
140c9a941f | ||
|
|
2b29b17044 | ||
|
|
5316f0f28c | ||
|
|
eb58fe9e97 | ||
|
|
5d4ea87402 | ||
|
|
9f1d7240a9 | ||
|
|
ac4d6cc8d0 | ||
|
|
84894274f0 | ||
|
|
ea09002012 | ||
|
|
601ec5cb7a | ||
|
|
565947e074 | ||
|
|
25d1767e6c | ||
|
|
c079f9b95b | ||
|
|
85ec2ee3a0 | ||
|
|
20cfb45c14 | ||
|
|
5314558a8e | ||
|
|
965f69f680 | ||
|
|
02b93d510a | ||
|
|
13a56dd4da | ||
|
|
4cf1b8c400 | ||
|
|
9c805a4317 | ||
|
|
b7b1182e14 | ||
|
|
8dfb0ca993 | ||
|
|
f695aaaea4 | ||
|
|
b398d04d96 | ||
|
|
574df5cf80 | ||
|
|
ac9ef4da76 | ||
|
|
a10893eaad | ||
|
|
34dcd8c346 | ||
|
|
5079077e1d | ||
|
|
0e0d5b23be | ||
|
|
e6c8b30882 | ||
|
|
a222e353ba | ||
|
|
1f120a0127 | ||
|
|
fe05c9b87b | ||
|
|
f795a634b5 | ||
|
|
888b93926c | ||
|
|
687f29f912 | ||
|
|
273bc9d970 | ||
|
|
d593d5a392 | ||
|
|
18fb688cde | ||
|
|
d8fccb0edd | ||
|
|
5843347c46 | ||
|
|
30cfe3bdfa | ||
|
|
fedb0625b8 | ||
|
|
72d012e401 | ||
|
|
8d77b50055 | ||
|
|
42eefbd34c | ||
|
|
e5e5bf1bd8 | ||
|
|
01ec334127 | ||
|
|
916469a903 | ||
|
|
55f205c801 | ||
|
|
8a3fc57cd8 | ||
|
|
6cd1cca4fc | ||
|
|
1c9926ff38 | ||
|
|
638477b06d | ||
|
|
7e3ca73b0e | ||
|
|
57a3a20868 | ||
|
|
4e45c5be95 | ||
|
|
ca89156c4f | ||
|
|
dc5390b3dc | ||
|
|
0e8c7eae99 | ||
|
|
115bdb7e10 | ||
|
|
9738c55633 | ||
|
|
23921c1a35 | ||
|
|
1f3547b15b | ||
|
|
6974a24669 | ||
|
|
2cf35ec2c7 | ||
|
|
d21bb6b8c7 | ||
|
|
9b8b480c8c | ||
|
|
ac654d56d0 | ||
|
|
db9e1a197e | ||
|
|
19bb9a6966 | ||
|
|
b7a1c7b72a | ||
|
|
042486bc73 | ||
|
|
42cc1c6b01 | ||
|
|
407d2c8323 | ||
|
|
8ab14c0639 | ||
|
|
48c281024f | ||
|
|
f7e1bec0cf | ||
|
|
41c7973fc7 | ||
|
|
bc93e70d11 | ||
|
|
320ab41435 | ||
|
|
7213c393fd | ||
|
|
6caadc8396 | ||
|
|
e0d1385061 | ||
|
|
e1eed973a2 | ||
|
|
f4645fc77e | ||
|
|
136ec07450 | ||
|
|
e01605de55 | ||
|
|
5cc48c4e01 | ||
|
|
5e2545c606 | ||
|
|
d227a82056 | ||
|
|
0fd2627cea | ||
|
|
cfcd955cc2 | ||
|
|
7d59b69709 | ||
|
|
e8eb1d9a44 | ||
|
|
2117748f18 | ||
|
|
e76733e8c3 | ||
|
|
2da5f90e9c | ||
|
|
81b100cd3b | ||
|
|
19be8f5104 | ||
|
|
9825daa79b | ||
|
|
10d405112c | ||
|
|
ad64a9d857 | ||
|
|
c4c70d082c | ||
|
|
23a928c5b7 | ||
|
|
8a7f624558 | ||
|
|
331f416e36 | ||
|
|
3a48c79b21 | ||
|
|
0c66de25c4 | ||
|
|
92e8dbf4ea | ||
|
|
aca04cee6b | ||
|
|
0bd227c22d | ||
|
|
7bc45bd9e0 | ||
|
|
9e5363b4df | ||
|
|
37f69d89cc | ||
|
|
30e96aa4ff | ||
|
|
7e1d3b260e | ||
|
|
be558ccac9 | ||
|
|
e991056106 | ||
|
|
b795155345 | ||
|
|
99d0c7cb3e | ||
|
|
01b85f19bc | ||
|
|
65ba214494 | ||
|
|
ccff51144e | ||
|
|
c8f6e7b0e1 | ||
|
|
7cccf8e237 | ||
|
|
e1f646c308 | ||
|
|
2a49a2ba7c | ||
|
|
e9bbd1e0b0 | ||
|
|
84e259f0f1 | ||
|
|
b3f4461f34 | ||
|
|
1df6b76e25 | ||
|
|
2153e20bc5 | ||
|
|
0cbc98ab53 | ||
|
|
0915a0413d | ||
|
|
f4d3d302f4 | ||
|
|
5d5e2264c0 | ||
|
|
b39fc72bd2 | ||
|
|
d6d50c4b66 | ||
|
|
791ef8ad88 | ||
|
|
e0861e5505 | ||
|
|
48c9176d90 | ||
|
|
33cb20561e | ||
|
|
0acb38f586 | ||
|
|
1a41e00a4f | ||
|
|
48ef93e495 | ||
|
|
3f7da86711 | ||
|
|
11aa2314d5 | ||
|
|
179c5065ca | ||
|
|
7c60f73942 | ||
|
|
7095c665b7 | ||
|
|
c6d94885e0 | ||
|
|
d9ddab8239 | ||
|
|
a118d45177 | ||
|
|
416c400367 | ||
|
|
e6839530d8 | ||
|
|
fea9e53be3 | ||
|
|
c5b3792a60 | ||
|
|
4f83b5da5b | ||
|
|
504c8faf4c | ||
|
|
4d6db5b334 | ||
|
|
ec1fc1f1e3 | ||
|
|
563cf8900c | ||
|
|
0850c2f362 | ||
|
|
f30334d80f | ||
|
|
9057a804a2 | ||
|
|
4c5c26033a | ||
|
|
a4f679adcf | ||
|
|
e9e12392ac | ||
|
|
6b4663c2ff | ||
|
|
0411c3a98b | ||
|
|
972a758ac9 | ||
|
|
05d72306c6 | ||
|
|
18b7c0955d | ||
|
|
71cbbf4e1e | ||
|
|
0606e12872 | ||
|
|
afc7cac5ab | ||
|
|
471e3d8954 | ||
|
|
74988f1254 |
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: grav
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: # Replace with a single custom sponsorship URL
|
||||
@@ -12,13 +12,6 @@ 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}): Travis Job Finished [%{duration}] (<a href="%{build_url}">Details</a>)'
|
||||
format: html
|
||||
slack:
|
||||
secure: dowksPsxxCxGKT6nis5hUgkp6+ZDAhoqzQHF9rJnx4hx0iEygPhVBs7pKl9yL2jubYJoLs+EXwE7z1dYgDAEJh4BnfrCokCMLpFGcxVxQC/HeAUdSQ2/RtdBYR5PRT75ScaFpqM/SfXXZVtnwVXAw9Z+JC6BjQ9vmn23m51Jw4k=
|
||||
env:
|
||||
|
||||
291
CHANGELOG.md
291
CHANGELOG.md
@@ -1,3 +1,276 @@
|
||||
# v1.7.0-beta.10
|
||||
## 10/03/2019
|
||||
|
||||
1. [](#new)
|
||||
1. [](#improved)
|
||||
* Flex: Removed extra exists check when creating object (messes up "non-existing" pages)
|
||||
* Support customizable null character replacement in `CSVFormatter::decode()`
|
||||
1. [](#bugfix)
|
||||
* Fixed wrong Grav param separator when using `Route` class
|
||||
* Fixed Flex User Avatar not fully backwards compatible with old user
|
||||
* Grav 1.7: Fixed prev/next page missing pages if pagination was turned on in page header
|
||||
* Grav 1.7: Reverted setting language for every page during initialization
|
||||
* Grav 1.7: Fixed numeric language inconsistencies
|
||||
|
||||
# v1.7.0-beta.9
|
||||
## 09/26/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `{% cache %}` Twig tag eliminating need for `twigcache` extension.
|
||||
1. [](#improved)
|
||||
* Improved blueprint initialization in Flex Objects (fixes content aware fields)
|
||||
* Improved Flex FolderStorage class to better hide storage specific logic
|
||||
* Exception will output a badly formatted line in `CsvFormatter::decode()`
|
||||
1. [](#bugfix)
|
||||
* Fixed error when activating Flex Accounts in GRAV system configuration (PHP 7.1)
|
||||
* Fixed Grav parameter handling in `RouteFactory::createFromString()`
|
||||
|
||||
# v1.7.0-beta.8
|
||||
## 09/19/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added new `Security::sanitizeSVG()` function
|
||||
* Backwards compatibility break: `FlexStorageInterface::getStoragePath()` and `getMediaPath()` can now return null
|
||||
1. [](#improved)
|
||||
* Several FlexObject loading improvements
|
||||
* Added `bin/grav page-system-validator [-r|--record] [-c|--check]` to test Flex Pages
|
||||
* Improved language support for `Route` class
|
||||
1. [](#bugfix)
|
||||
* Regression: Fixed language fallback
|
||||
* Regression: Fixed translations when language code is used for non-language purposes
|
||||
* Regression: Allow SVG avatar images for users
|
||||
* Fixed error in `Session::getFlashObject()` if Flex Form is being used
|
||||
* Fixed broken Twig `dump()`
|
||||
* Fixed `Page::modular()` and `Page::modularTwig()` returning `null` for folders and other non-initialized pages
|
||||
* Fixed 404 error when you click to non-routable menu item with children: redirect to the first child instead
|
||||
* Fixed wrong `Pages::dispatch()` calls (with redirect) when we really meant to call `Pages::find()`
|
||||
* Fixed avatars not being displayed with flex users [#2431](https://github.com/getgrav/grav/issues/2431)
|
||||
* Fixed initial Flex Object state when creating a new objects in a form
|
||||
|
||||
# v1.7.0-beta.7
|
||||
## 08/30/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Improved language support
|
||||
1. [](#bugfix)
|
||||
* `FlexForm`: Fixed some compatibility issues with Form plugin
|
||||
|
||||
# v1.7.0-beta.6
|
||||
## 08/29/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added experimental support for `Flex Pages` (**Flex Objects** plugin required)
|
||||
1. [](#improved)
|
||||
* Improved `bin/grav yamllinter` CLI command by adding an option to find YAML Linting issues from the whole site or custom folder
|
||||
* Added support for not instantiating pages, useful to speed up tasks
|
||||
* Greatly improved speed of loading Flex collections
|
||||
1. [](#bugfix)
|
||||
* Fixed `$page->summary()` always striping HTML tags if the summary was set by `$page->setSummary()`
|
||||
* Fixed `Flex->getObject()` when using Flex Key
|
||||
* Grav 1.7: Fixed enabling PHP Debug Bar causes fatal error in Gantry [#2634](https://github.com/getgrav/grav/issues/2634)
|
||||
* Grav 1.7: Fixed broken taxonomies [#2633](https://github.com/getgrav/grav/issues/2633)
|
||||
* Grav 1.7: Fixed unpublished blog posts being displayed on the front-end [#2650](https://github.com/getgrav/grav/issues/2650)
|
||||
|
||||
# v1.7.0-beta.5
|
||||
## 08/11/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `bin/grav server` CLI command to easily run Symfony or PHP built-in webservers
|
||||
* Added `hasFlexFeature()` method to test if `FlexObject` or `FlexCollection` implements a given feature
|
||||
* Added `getFlexFeatures()` method to return all features that `FlexObject` or `FlexCollection` implements
|
||||
* Deprecated `FlexDirectory::update()` and `FlexDirectory::remove()`
|
||||
* Added `FlexStorage::getMetaData()` to get updated object meta information for listed keys
|
||||
* Added `Language::getPageExtensions()` to get full list of supported page language extensions
|
||||
* Added `$grav->close()` method to properly terminate the request with a response
|
||||
* Added `Pages::getCollection()` method
|
||||
1. [](#improved)
|
||||
* Better support for Symfony local server `symfony server:start`
|
||||
* Make `Route` objects immutable
|
||||
* `FlexDirectory::getObject()` can now be called without any parameters to create a new object
|
||||
* Flex objects no longer return temporary key if they do not have one; empty key is returned instead
|
||||
* Updated vendor libraries
|
||||
* Moved `collection()` and `evaluate()` logic from `Page` class into `Pages` class
|
||||
1. [](#bugfix)
|
||||
* Fixed `Form` not to use deleted flash object until the end of the request fixing issues with reset
|
||||
* Fixed `FlexForm` to allow multiple form instances with non-existing objects
|
||||
* Fixed `FlexObject` search by using `key`
|
||||
* Grav 1.7: Fixed clockwork messages with arrays and objects
|
||||
|
||||
# v1.7.0-beta.4
|
||||
## 07/01/2019
|
||||
|
||||
1. [](#new)
|
||||
* Updated with Grav 1.6.12 features, improvements & fixes
|
||||
* Added new configuration option `system.debugger.censored` to hide potentially sensitive information
|
||||
* Added new configuration option `system.languages.include_default_lang_file_extension` to keep default language in `.md` files if set to `false`
|
||||
* Added configuration option to set fallback content languages individually for every language
|
||||
1. [](#improved)
|
||||
* Updated Vendor libraries
|
||||
1. [](#bugfix)
|
||||
* Fixed `.md` page to be assigned to the default language and to be listed in translated/untranslated page list
|
||||
* Fixed `Language::getFallbackPageExtensions()` to fall back only to default language instead of going through all languages
|
||||
* Fixed `Language::getFallbackPageExtensions()` returning wrong file extensions when passing custom page extension
|
||||
|
||||
# v1.7.0-beta.3
|
||||
## 06/24/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed Clockwork on Windows machines
|
||||
* Fixed parent field issues on Windows machines
|
||||
* Fixed unreliable Clockwork calls in sub-folders
|
||||
|
||||
# v1.7.0-beta.2
|
||||
## 06/21/2019
|
||||
|
||||
1. [](#new)
|
||||
* Updated with Grav 1.6.11 fixes
|
||||
1. [](#improved)
|
||||
* Updated the Clockwork text
|
||||
|
||||
# v1.7.0-beta.1
|
||||
## 06/14/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added support for [Clockwork](https://underground.works/clockwork) developer tools (now default debugger)
|
||||
* Added support for [Tideways XHProf](https://github.com/tideways/php-xhprof-extension) PHP Extension for profiling method calls
|
||||
* Added Twig profiling for Clockwork debugger
|
||||
* Added support for Twig 2.11 (compatible with Twig 1.40+)
|
||||
* Optimization: Initialize debugbar only after the configuration has been loaded
|
||||
* Optimization: Combine some early Grav processors into a single one
|
||||
|
||||
# v1.6.17
|
||||
## mm/dd/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Safer file handling + customizable null char replacment in `CsvFormatter::decode()`
|
||||
|
||||
|
||||
# v1.6.16
|
||||
## 09/19/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed Flex user creation if file storage is being used [#2444](https://github.com/getgrav/grav/issues/2444)
|
||||
* Fixed `Badly encoded JSON data` warning when uploading files [#2663](https://github.com/getgrav/grav/issues/2663)
|
||||
|
||||
# v1.6.15
|
||||
## 08/20/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Improved robots.txt [#2632](https://github.com/getgrav/grav/issues/2632)
|
||||
1. [](#bugfix)
|
||||
* Fixed broken markdown Twig tag [#2635](https://github.com/getgrav/grav/issues/2635)
|
||||
* Force Symfony 4.2 in Grav 1.6 to remove a bunch of deprecated messages
|
||||
|
||||
# v1.6.14
|
||||
## 08/18/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Actually include fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
|
||||
|
||||
# v1.6.13
|
||||
## 08/16/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Regression fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
|
||||
|
||||
# v1.6.12
|
||||
## 08/14/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added support for custom `FormFlash` save locations
|
||||
* Added a new `Utils::arrayLower()` method for lowercasing arrays
|
||||
* Support new GRAV_BASEDIR environment variable [#2541](https://github.com/getgrav/grav/pull/2541)
|
||||
* Allow users to override plugin handler priorities [#2165](https://github.com/getgrav/grav/pull/2165)
|
||||
1. [](#improved)
|
||||
* Use new `Utils::getSupportedPageTypes()` to enforce `html,htm` at the front of the list [#2531](https://github.com/getgrav/grav/issues/2531)
|
||||
* Updated vendor libraries
|
||||
* Markdown filter is now page-aware so that it works with modular references [admin#1731](https://github.com/getgrav/grav-plugin-admin/issues/1731)
|
||||
* Check of `GRAV_USER_INSTANCE` constant is already defined [#2621](https://github.com/getgrav/grav/pull/2621)
|
||||
1. [](#bugfix)
|
||||
* Fixed some potential issues when `$grav['user']` is not set
|
||||
* Fixed error when calling `Media::add($name, null)`
|
||||
* Fixed `url()` returning wrong path if using stream with grav root path in it, eg: `user-data://shop` when Grav is in `/shop`
|
||||
* Fixed `url()` not returning a path to non-existing file (`user-data://shop` => `/user/data/shop`) if it is set to fail gracefully
|
||||
* Fixed `url()` returning false on unknown streams, such as `ftp://domain.com`, they should be treated as external URL
|
||||
* Fixed Flex User to have permissions to save and delete his own user
|
||||
* Fixed new Flex User creation not being possible because of username could not be given
|
||||
* Fixed fatal error 'Expiration date must be an integer, a DateInterval or null, "double" given' [#2529](https://github.com/getgrav/grav/issues/2529)
|
||||
* Fixed non-existing Flex object having a bad media folder
|
||||
* Fixed collections using `page@.self:` should allow modular pages if requested
|
||||
* Fixed an error when trying to delete a file from non-existing Flex Object
|
||||
* Fixed `FlexObject::exists()` failing sometimes just after the object has been saved
|
||||
* Fixed CSV formatter not encoding strings with `"` and `,` properly
|
||||
* Fixed var order in `Validation.php` [#2610](https://github.com/getgrav/grav/issues/2610)
|
||||
|
||||
# v1.6.11
|
||||
## 06/21/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `FormTrait::getAllFlashes()` method to get all the available form flash objects for the form
|
||||
* Added creation and update timestamps to `FormFlash` objects
|
||||
1. [](#improved)
|
||||
* Added `FormFlashInterface`, changed constructor to take `$config` array
|
||||
1. [](#bugfix)
|
||||
* Fixed error in `ImageMedium::url()` if the image cache folder does not exist
|
||||
* Fixed empty form flash name after file upload or form state update
|
||||
* Fixed a bug in `Route::withParam()` method
|
||||
* Fixed issue with `FormFlash` objects when there is no session initialized
|
||||
|
||||
# v1.6.10
|
||||
## 06/14/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Added **page blueprints** to `YamlLinter` CLI and Admin reports
|
||||
* Removed `Gitter` and `Slack` [#2502](https://github.com/getgrav/grav/issues/2502)
|
||||
* Optimizations for Plugin/Theme loading
|
||||
* Generalized markdown classes so they can be used outside of `Page` scope with a custom `Excerpts` class instance
|
||||
* Change minimal port number to 0 (unix socket) [#2452](https://github.com/getgrav/grav/issues/2452)
|
||||
1. [](#bugfix)
|
||||
* Force question to install demo content in theme update [#2493](https://github.com/getgrav/grav/issues/2493)
|
||||
* Fixed GPM errors from blueprints not being logged [#2505](https://github.com/getgrav/grav/issues/2505)
|
||||
* Don't error when IP is invalid [#2507](https://github.com/getgrav/grav/issues/2507)
|
||||
* Fixed regression with `bin/plugin` not listing the plugins available (1c725c0)
|
||||
* Fixed bitwise operator in `TwigExtension::exifFunc()` [#2518](https://github.com/getgrav/grav/issues/2518)
|
||||
* Fixed issue with lang prefix incorrectly identifying as admin [#2511](https://github.com/getgrav/grav/issues/2511)
|
||||
* Fixed issue with `U0ils::pathPrefixedBYLanguageCode()` and trailing slash [#2510](https://github.com/getgrav/grav/issues/2511)
|
||||
* Fixed regresssion issue of `Utils::Url()` not returning `false` on failure. Added new optional `fail_gracefully` 3rd attribute to return string that caused failure [#2524](https://github.com/getgrav/grav/issues/2524)
|
||||
|
||||
# v1.6.9
|
||||
## 05/09/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `Route::withoutParams()` methods
|
||||
* Added `Pages::setCheckMethod()` method to override page configuration in Admin Plugin
|
||||
* Added `Cache::clearCache('invalidate')` parameter for just invalidating the cache without deleting any cached files
|
||||
* Made `UserCollectionInderface` to extend `Countable` to get the count of existing users
|
||||
1. [](#improved)
|
||||
* Flex admin: added default search options for flex objects
|
||||
* Flex collection and object now fall back to the default template if template file doesn't exist
|
||||
* Updated Vendor libraries including Twig 1.40.1
|
||||
* Updated language files from `https://crowdin.com/project/grav-core`
|
||||
1. [](#bugfix)
|
||||
* Fixed `$grav['route']` from being modified when the route instance gets modified
|
||||
* Fixed Assets options array mixed with standalone priority [#2477](https://github.com/getgrav/grav/issues/2477)
|
||||
* Fix for `avatar_url` provided by 3rd party providers
|
||||
* Fixed non standard `lang` code lengths in `Utils` and `Session` detection
|
||||
* Fixed saving a new object in Flex `SimpleStorage`
|
||||
* Fixed exception in `Flex::getDirectories()` if the first parameter is set
|
||||
* Output correct "Last Updated" in `bin/gpm info` command
|
||||
* Checkbox getting interpreted as string, so created new `Validation::filterCheckbox()`
|
||||
* Fixed backwards compatibility to `select` field with `selectize.create` set to true [git-sync#141](https://github.com/trilbymedia/grav-plugin-git-sync/issues/141)
|
||||
* Fixed `YamlFormatter::decode()` to always return array [#2494](https://github.com/getgrav/grav/pull/2494)
|
||||
* Fixed empty `$grav['request']->getAttribute('route')->getExtension()`
|
||||
|
||||
# v1.6.8
|
||||
## 04/23/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `FlexCollection::filterBy()` method
|
||||
1. [](#bugfix)
|
||||
* Revert `Use Null Coalesce Operator` [#2466](https://github.com/getgrav/grav/pull/2466)
|
||||
* Fixed `FormTrait::render()` not providing config variable
|
||||
* Updated `bin/grav clean` to clear `cache/compiled` and `user/config/security.yaml`
|
||||
|
||||
# v1.6.7
|
||||
## 04/22/2019
|
||||
|
||||
@@ -15,18 +288,18 @@
|
||||
## 04/17/2019
|
||||
|
||||
1. [](#new)
|
||||
* `FormInterface` now implements `RenderInterface`
|
||||
* Added new `FormInterface::getTask()` method which reads the task from `form.task` in the blueprint
|
||||
* `FormInterface` now implements `RenderInterface`
|
||||
* Added new `FormInterface::getTask()` method which reads the task from `form.task` in the blueprint
|
||||
1. [](#improved)
|
||||
* Updated vendor libraries to latest
|
||||
1. [](#bugfix)
|
||||
* Rollback `redirect_default_route` logic as it has issues with multi-lang [#2459](https://github.com/getgrav/grav/issues/2459)
|
||||
* Fix potential issue with `|contains` Twig filter on PHP 7.3
|
||||
* Fix potential issue with `|contains` Twig filter on PHP 7.3
|
||||
* Fixed bug in text field filtering: return empty string if value isn't a string or number [#2460](https://github.com/getgrav/grav/issues/2460)
|
||||
* Force Asset `priority` to be an integer and not throw error if invalid string passed [#2461](https://github.com/getgrav/grav/issues/2461)
|
||||
* Fixed bug in text field filtering: return empty string if value isn't a string or number
|
||||
* Fixed `FlexForm` missing getter methods for defining form variables
|
||||
|
||||
|
||||
# v1.6.5
|
||||
## 04/15/2019
|
||||
|
||||
@@ -87,7 +360,7 @@
|
||||
* Added `Grav\Framework\Object\ObjectIndex` class
|
||||
* Added `Grav\Framework\Flex` classes
|
||||
* Added support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar`, `scope@: object` or `scope-ignore@: object` to any field
|
||||
* New experimental **FlexObjects** powered `Users` for increased performance and capability (**disabled** by default)
|
||||
* New experimental **FlexObjects** powered `Users` for increased performance and capability (**disabled** by default)
|
||||
* Added PSR-7 and PSR-15 classes
|
||||
* Added `Grav\Framework\DI\Container` class
|
||||
* Added `Grav\Framework\RequestHandler\RequestHandler` class
|
||||
@@ -163,7 +436,7 @@
|
||||
* Added ability to reset `Page::metadata` to allow rebuilding from automatically generated values
|
||||
* Added back missing `page.types` field in system content configuration [admin#1612](https://github.com/getgrav/grav-plugin-admin/issues/1612)
|
||||
* Console commands: add method for invalidating cache
|
||||
* Updated languages
|
||||
* Updated languages
|
||||
* Improved `$page->forms()` call, added `$page->addForms()`
|
||||
* Updated languages from crowdin
|
||||
* Fixed `ImageMedium` constructor warning when file does not exist
|
||||
@@ -184,7 +457,7 @@
|
||||
* Added apcu autoloader optimization
|
||||
* Additional helper methods in `Language`, `Languages`, and `LanguageCodes` classes
|
||||
* Call `onFatalException` event also on internal PHP errors
|
||||
* Built-in PHP Webserver: log requests before handling them
|
||||
* Built-in PHP Webserver: log requests before handling them
|
||||
* Added support for syslog and syslog facility logging (default: 'file')
|
||||
* Improved usability of `System` configuration blueprint with side-tabs
|
||||
1. [](#bugfix)
|
||||
@@ -209,7 +482,7 @@
|
||||
* Fixed failed login if user attempts to log in with upper case non-english letters
|
||||
* Removed extra authenticated/authorized fields when saving existing user from a form
|
||||
* Fixed `Grav\Framework\Route::__toString()` returning relative URL, not relative route
|
||||
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
|
||||
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
|
||||
* Fixed a broken language string [#2261](https://github.com/getgrav/grav/issues/2261)
|
||||
* Fixed clearing cache having no effect on Doctrine cache
|
||||
* Fixed `Medium::relativePath()` for streams
|
||||
@@ -262,7 +535,7 @@
|
||||
* Updated vendor libraries
|
||||
1. [](#bugfix)
|
||||
* Support spaces with filenames in responsive images [#2300](https://github.com/getgrav/grav/pull/2300)
|
||||
|
||||
|
||||
# v1.5.6
|
||||
## 12/14/2018
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ The issue tracker is the preferred channel for [bug reports](#bugs),
|
||||
requests](#pull-requests), but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for support requests. Use
|
||||
[the Forum](http://getgrav.org/forum) or [the Gitter chat](https://gitter.im/getgrav/grav).
|
||||
[the Forum](http://getgrav.org/forum) or [the Chat](https://chat.getgrav.org/).
|
||||
|
||||
|
||||
<a name="bugs"></a>
|
||||
@@ -110,7 +110,8 @@ Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**Please ask first** in [Slack](https://getgrav.org/slack) or in the Forum before embarking on any significant pull request (e.g.
|
||||
**Please ask first** in [the Forum](http://getgrav.org/forum) or [the Chat](https://chat.getgrav.org/)
|
||||
before embarking on any significant pull request (e.g.
|
||||
implementing features, refactoring code..),
|
||||
otherwise you risk spending a lot of time working on something that the
|
||||
project's developers might not want to merge into the project.
|
||||
|
||||
26
bin/grav
26
bin/grav
@@ -3,7 +3,7 @@
|
||||
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Common\Grav;
|
||||
use League\CLImate\CLImate;
|
||||
use Grav\Console\Cli;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
\define('GRAV_CLI', true);
|
||||
@@ -52,16 +52,18 @@ if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
|
||||
$app = new Application('Grav CLI Application', GRAV_VERSION);
|
||||
$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(),
|
||||
new \Grav\Console\Cli\BackupCommand(),
|
||||
new \Grav\Console\Cli\NewProjectCommand(),
|
||||
new \Grav\Console\Cli\SchedulerCommand(),
|
||||
new \Grav\Console\Cli\SecurityCommand(),
|
||||
new \Grav\Console\Cli\LogViewerCommand(),
|
||||
new \Grav\Console\Cli\YamlLinterCommand(),
|
||||
new Cli\InstallCommand(),
|
||||
new Cli\ComposerCommand(),
|
||||
new Cli\SandboxCommand(),
|
||||
new Cli\CleanCommand(),
|
||||
new Cli\ClearCacheCommand(),
|
||||
new Cli\BackupCommand(),
|
||||
new Cli\NewProjectCommand(),
|
||||
new Cli\SchedulerCommand(),
|
||||
new Cli\SecurityCommand(),
|
||||
new Cli\LogViewerCommand(),
|
||||
new Cli\YamlLinterCommand(),
|
||||
new Cli\ServerCommand(),
|
||||
new Cli\PageSystemValidatorCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
25
bin/plugin
25
bin/plugin
@@ -79,19 +79,6 @@ $output = new ConsoleOutput();
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
|
||||
$output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
|
||||
|
||||
if (is_null($plugin)) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name plugin not found</red>");
|
||||
die;
|
||||
}
|
||||
|
||||
if (!$plugin->enabled) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name not enabled</red>");
|
||||
die;
|
||||
}
|
||||
|
||||
|
||||
if (!$name) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<red>Usage:</red>');
|
||||
@@ -123,6 +110,18 @@ if (!$name) {
|
||||
}
|
||||
|
||||
exit;
|
||||
} else {
|
||||
if (is_null($plugin)) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name plugin not found</red>");
|
||||
die;
|
||||
}
|
||||
|
||||
if (!$plugin->enabled) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name not enabled</red>");
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
if ($plugin === null) {
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
"kodus/psr7-server": "*",
|
||||
"nyholm/psr7": "^1.0",
|
||||
|
||||
"twig/twig": "~1.35",
|
||||
"twig/twig": "~1.0",
|
||||
"erusev/parsedown": "1.6.4",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~4.2",
|
||||
"symfony/console": "~4.2",
|
||||
"symfony/event-dispatcher": "~4.2",
|
||||
"symfony/var-dumper": "~4.2",
|
||||
"symfony/process": "~4.2",
|
||||
"symfony/console": "~4.2.0",
|
||||
"symfony/event-dispatcher": "~4.2.0",
|
||||
"symfony/var-dumper": "~4.2.0",
|
||||
"symfony/process": "~4.2.0",
|
||||
"doctrine/cache": "^1.8",
|
||||
"doctrine/collections": "^1.5",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
@@ -50,7 +50,9 @@
|
||||
"composer/ca-bundle": "^1.0",
|
||||
"dragonmantank/cron-expression": "^1.2",
|
||||
"phive/twig-extensions-deferred": "^1.0",
|
||||
"willdurand/negotiation": "^2.3"
|
||||
"willdurand/negotiation": "^2.3",
|
||||
"itsgoingd/clockwork": "~4.0",
|
||||
"enshrined/svg-sanitize": "^0.10.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.4",
|
||||
@@ -77,6 +79,10 @@
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/itsgoingd/clockwork"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
@@ -89,6 +95,8 @@
|
||||
"exclude": ["VERSION"]
|
||||
},
|
||||
"scripts": {
|
||||
"api-16": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.16.md",
|
||||
"api-15": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.md",
|
||||
"post-create-project-cmd": "bin/grav install",
|
||||
"phpstan": "vendor/bin/phpstan analyse -l 2 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=256M",
|
||||
"phpstan-framework": "vendor/bin/phpstan analyse -l 5 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=256M",
|
||||
|
||||
906
composer.lock
generated
906
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,11 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
die(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli-server' && !isset($_SERVER['PHP_CLI_ROUTER'])) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
if (PHP_SAPI === 'cli-server') {
|
||||
$symfony_server = strpos(getenv('_'), 'symfony') !== false;
|
||||
if (!isset($_SERVER['PHP_CLI_ROUTER']) && !$symfony_server) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
|
||||
4
now.json
Normal file
4
now.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "*.php", "use": "@now/php" }]
|
||||
}
|
||||
@@ -10,3 +10,4 @@ Disallow: /user/
|
||||
Allow: /user/pages/
|
||||
Allow: /user/themes/
|
||||
Allow: /user/images/
|
||||
Allow: /
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
div.phpdebugbar {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.phpdebugbar pre {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div > * {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
|
||||
background-image: url(grav.png);
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-restore-btn {
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
|
||||
background: #3DB9EC;
|
||||
color: #fff;
|
||||
margin-top: -1px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar {
|
||||
border-top: 1px solid #ddd;
|
||||
padding-left: 5px;
|
||||
padding-right: 2px;
|
||||
padding-top: 2px;
|
||||
background-color: #fafafa !important;
|
||||
width: auto !important;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar input {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar .phpdebugbar-widgets-filter {
|
||||
|
||||
}
|
||||
|
||||
|
||||
.phpdebugbar input[type=text] {
|
||||
padding: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar pre, .phpdebugbar code {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
2
system/assets/debugger/clockwork.css
Normal file
2
system/assets/debugger/clockwork.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/** Clockwork Debugger CSS **/
|
||||
.clockwork-badge{position:fixed;z-index:10;bottom:0;left:0;padding:2px 4px;background-color:#eee;border:1px solid #ccc;border-bottom:0;border-left:0;display:flex;align-items:center}.clockwork-badge:hover{width:auto}.clockwork-badge:hover:after{content:'Grav Clockwork debugger enabled. Install Clockwork Browser extension (Chrome or Firefox), open your Developer tools and then select the Clockwork tab.'}.clockwork-badge:after{margin-left:10px;font-family:Monaco,Consolas,"Lucida Console",monospace;font-size:12px;line-height:1.5;color:#666}.clockwork-badge i{display:block;float:left;height:22px;width:22px;min-width:22px;background-size:contain;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAA/1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeHh4AAAD///8EBAT7+/sLCwv29vYVFRUvLy/t7e3m5ubCwsKxsbE/Pz+mpqZMTEwcHBzy8vLp6emfn5+AgIA2Njbi4uLf39+rq6tzc3NWVlYhISHa2trW1tbS0tLMzMy7u7uZmZmUlJSMjIxvb29kZGRHR0c7Ozt5eXkqKiq1tbWQkJBqampbW1tSUlLHx8eHh4ckJCRDQ0M3wD42AAAAI3RSTlMA/PibTbQ0x76TVAlw4LhZLOuEYCAN9Hjx0a2ppGZEGYw97djhXHwAAATZSURBVFjDlVcHW+MwDO1eFCjj2McNOzvdpXTTXVbL/P+/5SQ7QSSX5Di1X1onfi/Sk+Q4sTDbKqWK+YuznZ2zi3wxVdqK/Zf92M1nT9gnO8rmd398GX6Z3xaoOFoiAQcx3E5efgmeSuN8F6Xg1x3G06l/wjNpMR1B0uif4EhnIuFb+0diIoFXk3IVfokisR+h52GO4JKgyjmfaMhAFNlSaPR7DpwI+lzn/E4QKIqmKIJirxCMP4izBPPZPXhgXwMBYgULw0nfg/BF5scDbslb7QeJ08yqqTEmGYoB95d4H8ETL8+n9wBqrLu6ao3bBsMwAnxISf/9BHcqxNB8Y7cWl3Zz7TAUfPrvAT6AoNEFFXvsjutL01yOuMrtBxnFXsmT/1wQHmdWAFNnI3uI48Yj0FUcHbKf62GfUfr8eeQt7Uk3mQZpZNoVRPEui5vtEz5zFEpgWnyqVBZMc6oaGNriH2hGVZ0OxEvInPeMaZWJBA7vmPbCr5jjws5HBnAUxvDMH40aCIf4G5BjRQSs8E8HFFYf8bGxgDvD55bzGhwWkoBcuIyHR/AMdaCagxXDhtL6tSqoWpd4BMnlIR+Or+rYTK/a3EAGcc6e4AWHISnWv20iCCojsHoVlQdjrMexFF2C7UMg2A2WEGWbQhXN6l3eXC6XGp4b9qxbuEB2EBGBwtocrK90cVG5mbRXm6vmx/0phq1sIAGKDgLOBiN1MrO5a9aDl+D0W6x0Ar9BCTRuIIANa90Y7LrLVRXzwVtDInCqMRWcf2bUOEAsa4wJqFowQALL9EiAtVRk8QC4OW+1pOM9jIaVASwYagyNXDj+W0NcfuZNzjtXOiL0Zzg30Llj+ptfxQs4+vBPNiL5PawFCBkgXpUaVtqGl+A8dgZHL34BcBUQrwPptToW+o37Ku+UH9eYByJIx3YkAeFnMFuGO7S5gEp7YhXxa5OOAM39RXDPXb0qmpROsswZe+twXdU55oUIZAiEv3bD1UFwIYKkmGqytPCDCwKFQCKK0yL7qtSAPX54UAbtsLuBHkb9zyLmPQSNjsSgmQwKUOIfEY8F8t4B34DvndJY9BA8tNBJq1Nev9axmaStFcQLhgYoCTo0salkIaW8OUDdWjMTR2sHPhrAFZqx6cqcKE4pl2BJJ4K6hfwvqNgAnXfKX/HU6X3Zrhnu0k7tLNZtTBRv1hkwTDBY1NzFU6doDYjJbWdQkQhWwuU7/LvhTh3SDoco4ECL4i5dwURbc8NdDZz2IwKicE8d0KIqWetLE3+lL4hvUuGSeRfVWNLfj/gpOw4smBJBkKQHCzlHGwvAj4woB1gq5NGGLSXtORBPnUQPV5/MPVkDMxbpwG7w4x0xL6Ltxka0A/4NBvV09UVk4DoSn/jl2+JQS9q9KYawisAD4CfhsZ4TH3htylsdEHARIQBusqCKyUpymycgbbkkXEXjT3z7/oKQFTFVuZD2FMJHZIDsO5x2d4aAr2jR+GLwZhtAb028/0yJ9J8dE87jQyKObcjtTXT8dH+fDuKF4/eiPwzH44wTf/yUi6wrpRIOZ9lM1EtXAifFI+CJn9+iX/t2xMQwOMth/UZbASi8btAwR9FHWSpJr75g9Oqbin3VDg+SpwlP6k6TB4ex/7JvmcJx8jydy6XPk8eFTKhyfwCgX71MSvaBHgAAAABJRU5ErkJggg==)}
|
||||
2
system/assets/debugger/clockwork.js
Normal file
2
system/assets/debugger/clockwork.js
Normal file
@@ -0,0 +1,2 @@
|
||||
/** Clockwork Debugger JS **/
|
||||
document.addEventListener("DOMContentLoaded",function(){var e=document.createElement("div");e.appendChild(document.createElement("i")),e.className="clockwork-badge",document.body.appendChild(e)});
|
||||
70
system/assets/debugger/phpdebugbar.css
Normal file
70
system/assets/debugger/phpdebugbar.css
Normal file
@@ -0,0 +1,70 @@
|
||||
div.phpdebugbar {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.phpdebugbar pre {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div > * {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAA/1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeHh4AAAD///8EBAT7+/sLCwv29vYVFRUvLy/t7e3m5ubCwsKxsbE/Pz+mpqZMTEwcHBzy8vLp6emfn5+AgIA2Njbi4uLf39+rq6tzc3NWVlYhISHa2trW1tbS0tLMzMy7u7uZmZmUlJSMjIxvb29kZGRHR0c7Ozt5eXkqKiq1tbWQkJBqampbW1tSUlLHx8eHh4ckJCRDQ0M3wD42AAAAI3RSTlMA/PibTbQ0x76TVAlw4LhZLOuEYCAN9Hjx0a2ppGZEGYw97djhXHwAAATZSURBVFjDlVcHW+MwDO1eFCjj2McNOzvdpXTTXVbL/P+/5SQ7QSSX5Di1X1onfi/Sk+Q4sTDbKqWK+YuznZ2zi3wxVdqK/Zf92M1nT9gnO8rmd398GX6Z3xaoOFoiAQcx3E5efgmeSuN8F6Xg1x3G06l/wjNpMR1B0uif4EhnIuFb+0diIoFXk3IVfokisR+h52GO4JKgyjmfaMhAFNlSaPR7DpwI+lzn/E4QKIqmKIJirxCMP4izBPPZPXhgXwMBYgULw0nfg/BF5scDbslb7QeJ08yqqTEmGYoB95d4H8ETL8+n9wBqrLu6ao3bBsMwAnxISf/9BHcqxNB8Y7cWl3Zz7TAUfPrvAT6AoNEFFXvsjutL01yOuMrtBxnFXsmT/1wQHmdWAFNnI3uI48Yj0FUcHbKf62GfUfr8eeQt7Uk3mQZpZNoVRPEui5vtEz5zFEpgWnyqVBZMc6oaGNriH2hGVZ0OxEvInPeMaZWJBA7vmPbCr5jjws5HBnAUxvDMH40aCIf4G5BjRQSs8E8HFFYf8bGxgDvD55bzGhwWkoBcuIyHR/AMdaCagxXDhtL6tSqoWpd4BMnlIR+Or+rYTK/a3EAGcc6e4AWHISnWv20iCCojsHoVlQdjrMexFF2C7UMg2A2WEGWbQhXN6l3eXC6XGp4b9qxbuEB2EBGBwtocrK90cVG5mbRXm6vmx/0phq1sIAGKDgLOBiN1MrO5a9aDl+D0W6x0Ar9BCTRuIIANa90Y7LrLVRXzwVtDInCqMRWcf2bUOEAsa4wJqFowQALL9EiAtVRk8QC4OW+1pOM9jIaVASwYagyNXDj+W0NcfuZNzjtXOiL0Zzg30Llj+ptfxQs4+vBPNiL5PawFCBkgXpUaVtqGl+A8dgZHL34BcBUQrwPptToW+o37Ku+UH9eYByJIx3YkAeFnMFuGO7S5gEp7YhXxa5OOAM39RXDPXb0qmpROsswZe+twXdU55oUIZAiEv3bD1UFwIYKkmGqytPCDCwKFQCKK0yL7qtSAPX54UAbtsLuBHkb9zyLmPQSNjsSgmQwKUOIfEY8F8t4B34DvndJY9BA8tNBJq1Nev9axmaStFcQLhgYoCTo0salkIaW8OUDdWjMTR2sHPhrAFZqx6cqcKE4pl2BJJ4K6hfwvqNgAnXfKX/HU6X3Zrhnu0k7tLNZtTBRv1hkwTDBY1NzFU6doDYjJbWdQkQhWwuU7/LvhTh3SDoco4ECL4i5dwURbc8NdDZz2IwKicE8d0KIqWetLE3+lL4hvUuGSeRfVWNLfj/gpOw4smBJBkKQHCzlHGwvAj4woB1gq5NGGLSXtORBPnUQPV5/MPVkDMxbpwG7w4x0xL6Ltxka0A/4NBvV09UVk4DoSn/jl2+JQS9q9KYawisAD4CfhsZ4TH3htylsdEHARIQBusqCKyUpymycgbbkkXEXjT3z7/oKQFTFVuZD2FMJHZIDsO5x2d4aAr2jR+GLwZhtAb028/0yJ9J8dE87jQyKObcjtTXT8dH+fDuKF4/eiPwzH44wTf/yUi6wrpRIOZ9lM1EtXAifFI+CJn9+iX/t2xMQwOMth/UZbASi8btAwR9FHWSpJr75g9Oqbin3VDg+SpwlP6k6TB4ex/7JvmcJx8jydy6XPk8eFTKhyfwCgX71MSvaBHgAAAABJRU5ErkJggg==);
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-restore-btn {
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
|
||||
background: #3DB9EC;
|
||||
color: #fff;
|
||||
margin-top: -1px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar {
|
||||
border-top: 1px solid #ddd;
|
||||
padding-left: 5px;
|
||||
padding-right: 2px;
|
||||
padding-top: 2px;
|
||||
background-color: #fafafa !important;
|
||||
width: auto !important;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar input {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar .phpdebugbar-widgets-filter {
|
||||
|
||||
}
|
||||
|
||||
|
||||
.phpdebugbar input[type=text] {
|
||||
padding: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar pre, .phpdebugbar code {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -105,3 +105,15 @@ form:
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
|
||||
sanitize_svg:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.SANITIZE_SVG
|
||||
help: PLUGIN_ADMIN.SANITIZE_SVG_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
@@ -65,7 +65,7 @@ form:
|
||||
|
||||
summary.size:
|
||||
type: text
|
||||
size: x-small
|
||||
size: small
|
||||
append: PLUGIN_ADMIN.CHARACTERS
|
||||
label: PLUGIN_ADMIN.SUMMARY_SIZE
|
||||
help: PLUGIN_ADMIN.SUMMARY_SIZE_HELP
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,5 +12,7 @@ form:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.USERNAME
|
||||
help: PLUGIN_ADMIN.USERNAME_HELP
|
||||
unset-disabled@: true
|
||||
unset-readonly@: true
|
||||
validate:
|
||||
required: true
|
||||
|
||||
@@ -27,3 +27,14 @@ config:
|
||||
title: Accounts
|
||||
icon: fa-users
|
||||
authorize: ['admin.users', 'admin.accounts', 'admin.super']
|
||||
priority: 6
|
||||
|
||||
form:
|
||||
fields:
|
||||
username:
|
||||
flex-disabled@: exists
|
||||
disabled: false
|
||||
flex-readonly@: exists
|
||||
readonly: false
|
||||
validate:
|
||||
required: true
|
||||
|
||||
@@ -36,3 +36,4 @@ uploads_dangerous_extensions:
|
||||
- htm
|
||||
- js
|
||||
- exe
|
||||
sanitize_svg: true
|
||||
|
||||
@@ -27,6 +27,7 @@ home:
|
||||
hide_in_urls: false # Hide the home route in URLs
|
||||
|
||||
pages:
|
||||
type: page # EXPERIMENTAL: Page type: page or flex
|
||||
theme: quark # Default theme (defaults to "quark" theme)
|
||||
order:
|
||||
by: default # Order pages by "default", "alpha" or "date"
|
||||
@@ -125,6 +126,8 @@ log:
|
||||
|
||||
debugger:
|
||||
enabled: false # Enable Grav debugger and following settings
|
||||
provider: clockwork # Debugger provider: debugbar | clockwork
|
||||
censored: false # Censor potentially sensitive information (POST parameters, cookies, files, configuration and most array/object data in log messages)
|
||||
shutdown:
|
||||
close_connection: true # Close the connection before calling onShutdown(). false for debugging
|
||||
|
||||
@@ -154,15 +157,15 @@ session:
|
||||
path:
|
||||
|
||||
gpm:
|
||||
releases: stable # Set to either 'stable' or 'testing'
|
||||
releases: testing # Set to either 'stable' or 'testing'
|
||||
proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
|
||||
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
|
||||
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
|
||||
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
|
||||
|
||||
accounts:
|
||||
type: data # Account type: data or flex
|
||||
storage: file # Flex storage type: file or folder
|
||||
type: data # EXPERIMENTAL: Account type: data or flex
|
||||
storage: file # EXPERIMENTAL: Flex storage type: file or folder
|
||||
|
||||
strict_mode:
|
||||
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.6.7');
|
||||
define('GRAV_TESTING', false);
|
||||
define('GRAV_VERSION', '1.7.0-beta.10');
|
||||
define('GRAV_TESTING', true);
|
||||
define('DS', '/');
|
||||
|
||||
if (!defined('GRAV_PHP_MIN')) {
|
||||
|
||||
@@ -23,7 +23,7 @@ GRAV:
|
||||
BAD_DATE: Fecha errónea
|
||||
AGO: antes
|
||||
FROM_NOW: desde ahora
|
||||
JUST_NOW: justo ahora
|
||||
JUST_NOW: hace un momento
|
||||
SECOND: segundo
|
||||
MINUTE: minuto
|
||||
HOUR: hora
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\npealkiri: %1$s\n---\n\n# Viga: vigane Frontmatter'i\n\nasukoht: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'equipment'
|
||||
- 'informatsioon'
|
||||
- 'rice'
|
||||
- 'money'
|
||||
- 'species'
|
||||
- 'series'
|
||||
- 'kala'
|
||||
- 'lammas'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': 'inimesed'
|
||||
'man': 'mees'
|
||||
'child': 'lapsed'
|
||||
INFLECTOR_ORDINALS:
|
||||
'default': '.'
|
||||
'first': '.'
|
||||
'second': '.'
|
||||
'third': '.'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Kuupäev määramata
|
||||
BAD_DATE: Vigane kuupäev
|
||||
AGO: tagasi
|
||||
FROM_NOW: praegusest
|
||||
JUST_NOW: just nüüd
|
||||
SECOND: sekund
|
||||
MINUTE: minut
|
||||
HOUR: tundi
|
||||
@@ -60,3 +79,7 @@ GRAV:
|
||||
- 'reede'
|
||||
- 'laupäev'
|
||||
- 'pühapäev'
|
||||
CRON:
|
||||
EVERY: iga
|
||||
EVERY_MONTH: iga kuu
|
||||
TEXT_PERIOD: Iga <b />
|
||||
|
||||
@@ -14,6 +14,8 @@ GRAV:
|
||||
'/sis$/i': 'ses'
|
||||
'/([ti])um$/i': '\1a'
|
||||
'/(buffal|tomat)o$/i': '\1es'
|
||||
'/(bu)s$/i': 'Bus'
|
||||
'/(alias|status)/i': 'alias|status'
|
||||
'/(ax|test)is$/i': '\1s'
|
||||
'/s$/i': 's'
|
||||
'/$/': 's'
|
||||
|
||||
@@ -11,6 +11,8 @@ GRAV:
|
||||
- 'fish'
|
||||
- 'sheep'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Engin dagsetning gefin
|
||||
BAD_DATE: Röng dagsetning
|
||||
AGO: síðan
|
||||
JUST_NOW: í þessu
|
||||
SECOND: sekúndu
|
||||
@@ -45,6 +47,7 @@ GRAV:
|
||||
DEC_PLURAL: árat
|
||||
FORM:
|
||||
VALIDATION_FAIL: <b>Sannvottun mistókst:</b>
|
||||
INVALID_INPUT: Ógilt inntak í
|
||||
MISSING_REQUIRED_FIELD: 'Vantar nauðsynlegan reit:'
|
||||
MONTHS_OF_THE_YEAR:
|
||||
- 'janúar'
|
||||
@@ -67,3 +70,11 @@ GRAV:
|
||||
- 'Föstudagur'
|
||||
- 'Laugardagur'
|
||||
- 'Sunnudagur'
|
||||
CRON:
|
||||
TEXT_TIME: ' á <b />:<b />'
|
||||
TEXT_DOW: ' á <b />'
|
||||
TEXT_MONTH: ' af <b />'
|
||||
TEXT_DOM: ' á <b />'
|
||||
ERROR1: Merkið %s er ekki stutt!
|
||||
ERROR3: Það ætti að setja jquery_element inn í stillingar jqCron
|
||||
ERROR4: Óþekkt segð
|
||||
|
||||
@@ -35,3 +35,12 @@ GRAV:
|
||||
- 'Outubro'
|
||||
- 'Novembro'
|
||||
- 'Dezembro'
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'equipment'
|
||||
- 'information'
|
||||
- 'arroz'
|
||||
- 'money'
|
||||
- 'species'
|
||||
- 'series'
|
||||
- 'fish'
|
||||
- 'sheep'
|
||||
|
||||
@@ -1,10 +1,75 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Chyba: Chybný frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
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'
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'vybavenie'
|
||||
- 'informácie'
|
||||
- 'ryža'
|
||||
- 'peniaze'
|
||||
- 'druhy'
|
||||
- 'séria'
|
||||
- 'ryba'
|
||||
- 'ovce'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': 'ľudia'
|
||||
'man': 'muži'
|
||||
'child': 'deti'
|
||||
'sex': 'pohlavia'
|
||||
'move': 'pohyby'
|
||||
INFLECTOR_ORDINALS:
|
||||
'default': '.'
|
||||
'first': '.'
|
||||
'second': '.'
|
||||
'third': '.'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Neposkytnutý žiaden dátum
|
||||
BAD_DATE: Nesprávny dátum
|
||||
AGO: pred
|
||||
FROM_NOW: odteraz
|
||||
JUST_NOW: práve teraz
|
||||
SECOND: sekunda
|
||||
MINUTE: minúta
|
||||
HOUR: hodina
|
||||
@@ -14,10 +79,12 @@ GRAV:
|
||||
YEAR: rok
|
||||
DECADE: desaťročie
|
||||
SEC: sek
|
||||
MIN: min
|
||||
HR: hod
|
||||
WK: t
|
||||
MO: m
|
||||
YR: r
|
||||
DEC: dec
|
||||
SECOND_PLURAL: sekúnd
|
||||
MINUTE_PLURAL: minút
|
||||
HOUR_PLURAL: hodín
|
||||
@@ -58,3 +125,20 @@ GRAV:
|
||||
- 'Piatok'
|
||||
- 'Sobota'
|
||||
- 'Nedeľa'
|
||||
CRON:
|
||||
EVERY: každý
|
||||
EVERY_HOUR: každú hodinu
|
||||
EVERY_MINUTE: každú minútu
|
||||
EVERY_DAY_OF_WEEK: každý deň v týždni
|
||||
EVERY_DAY_OF_MONTH: každý deň v mesiaci
|
||||
EVERY_MONTH: každý mesiac
|
||||
TEXT_PERIOD: Každý <b />
|
||||
TEXT_MINS: ' at <b /> minute(s) past the hour'
|
||||
TEXT_TIME: ' at <b />:<b />'
|
||||
TEXT_DOW: ' on <b />'
|
||||
TEXT_MONTH: ' of <b />'
|
||||
TEXT_DOM: ' on <b />'
|
||||
ERROR1: Tag %s nieje podporovaný!
|
||||
ERROR2: Chybný počet položiek
|
||||
ERROR3: jquery_element musí byť nastavený v nastaveniach pre jqCron
|
||||
ERROR4: Neznámy výraz
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "--- titel: %1$s --- # Fel: Ogiltig Frontmatter-sökväg: `%2$s` **%3$s** ``` %4$s ```"
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'utrustning'
|
||||
- 'information'
|
||||
- 'ris'
|
||||
- 'pengar'
|
||||
- 'arter'
|
||||
- 'serier'
|
||||
- 'fisk'
|
||||
- 'får'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': 'personer'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Inget datum har angivits
|
||||
BAD_DATE: Ogiltigt datum
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 錯誤: 不正確的 Frontmatter\n\n路徑: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: 沒有提供日期
|
||||
BAD_DATE: 錯誤日期
|
||||
AGO: 之前
|
||||
FROM_NOW: 之後
|
||||
JUST_NOW: 剛剛
|
||||
SECOND: 秒
|
||||
MINUTE: 分
|
||||
HOUR: 小時
|
||||
DAY: 天
|
||||
WEEK: 週
|
||||
MONTH: 月
|
||||
YEAR: 年
|
||||
DECADE: 十年
|
||||
SEC: 秒
|
||||
MIN: 分
|
||||
HR: 小時
|
||||
WK: 週
|
||||
MO: 月
|
||||
YR: 年
|
||||
DEC: 十年
|
||||
SECOND_PLURAL: 秒
|
||||
MINUTE_PLURAL: 分
|
||||
HOUR_PLURAL: 時
|
||||
DAY_PLURAL: 日
|
||||
WEEK_PLURAL: 周
|
||||
HOUR_PLURAL: 小時
|
||||
DAY_PLURAL: 天
|
||||
WEEK_PLURAL: 週
|
||||
MONTH_PLURAL: 月
|
||||
YEAR_PLURAL: 年
|
||||
DECADE_PLURAL: 十年
|
||||
SEC_PLURAL: 秒
|
||||
MIN_PLURAL: 分
|
||||
HR_PLURAL: 時
|
||||
WK_PLURAL: 周
|
||||
WK_PLURAL: 週
|
||||
MO_PLURAL: 月
|
||||
YR_PLURAL: 年
|
||||
DEC_PLURAL: 十年
|
||||
FORM:
|
||||
MISSING_REQUIRED_FIELD: 遺漏必填欄位:
|
||||
MONTHS_OF_THE_YEAR:
|
||||
- '一月'
|
||||
- '二月'
|
||||
|
||||
@@ -1,57 +1,122 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 錯誤: 不正確的 Frontmatter\n\n路徑: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
FRONTMATTER_ERROR_PAGE: "---\n标题: %1$s\n---\n\n# 错误:无效参数\n\n位置: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
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'
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- '装备'
|
||||
- '信息'
|
||||
- '大米'
|
||||
- '钱'
|
||||
- '物种'
|
||||
- '系列'
|
||||
- '鱼'
|
||||
- '羊'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': '人员'
|
||||
'man': '男人'
|
||||
'child': '儿童'
|
||||
'sex': '性别'
|
||||
'move': '移动'
|
||||
INFLECTOR_ORDINALS:
|
||||
'default': 'th'
|
||||
'first': 'st'
|
||||
'second': 'md'
|
||||
'third': 'rd'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: 沒有提供日期
|
||||
BAD_DATE: 錯誤日期
|
||||
AGO: 之前
|
||||
FROM_NOW: 之後
|
||||
JUST_NOW: 剛剛
|
||||
NO_DATE_PROVIDED: 无日期信息
|
||||
BAD_DATE: 无效日期
|
||||
AGO: 前
|
||||
FROM_NOW: 距今
|
||||
JUST_NOW: 刚刚
|
||||
SECOND: 秒
|
||||
MINUTE: 分
|
||||
HOUR: 小時
|
||||
MINUTE: 分钟
|
||||
HOUR: 小时
|
||||
DAY: 天
|
||||
WEEK: 週
|
||||
WEEK: 周
|
||||
MONTH: 月
|
||||
YEAR: 年
|
||||
DECADE: 十年
|
||||
SEC: 秒
|
||||
MIN: 分
|
||||
HR: 小時
|
||||
WK: 週
|
||||
MIN: 分钟
|
||||
HR: 小时
|
||||
WK: 周
|
||||
MO: 月
|
||||
YR: 年
|
||||
DEC: 十年
|
||||
DEC: 年代
|
||||
SECOND_PLURAL: 秒
|
||||
MINUTE_PLURAL: 分
|
||||
HOUR_PLURAL: 小時
|
||||
HOUR_PLURAL: 小时
|
||||
DAY_PLURAL: 天
|
||||
WEEK_PLURAL: 週
|
||||
WEEK_PLURAL: 周
|
||||
MONTH_PLURAL: 月
|
||||
YEAR_PLURAL: 年
|
||||
DECADE_PLURAL: 十年
|
||||
SEC_PLURAL: 秒
|
||||
MIN_PLURAL: 分
|
||||
HR_PLURAL: 時
|
||||
WK_PLURAL: 週
|
||||
HR_PLURAL: 时
|
||||
WK_PLURAL: 周
|
||||
MO_PLURAL: 月
|
||||
YR_PLURAL: 年
|
||||
DEC_PLURAL: 十年
|
||||
DEC_PLURAL: 年代
|
||||
FORM:
|
||||
MISSING_REQUIRED_FIELD: 遺漏必填欄位:
|
||||
VALIDATION_FAIL: <b>验证失败:</b>
|
||||
INVALID_INPUT: 无效输入
|
||||
MISSING_REQUIRED_FIELD: 必填字段缺失:
|
||||
MONTHS_OF_THE_YEAR:
|
||||
- '一月'
|
||||
- '二月'
|
||||
- '三月'
|
||||
- '四月'
|
||||
- '五月'
|
||||
- '六月'
|
||||
- '七月'
|
||||
- '八月'
|
||||
- '九月'
|
||||
- '十月'
|
||||
- '十一月'
|
||||
- '十二月'
|
||||
- '1月'
|
||||
- '2月'
|
||||
- '3月'
|
||||
- '4月'
|
||||
- '5月'
|
||||
- '6月'
|
||||
- '7月'
|
||||
- '8月'
|
||||
- '9月'
|
||||
- '10月'
|
||||
- '11月'
|
||||
- '12月'
|
||||
DAYS_OF_THE_WEEK:
|
||||
- '星期一'
|
||||
- '星期二'
|
||||
@@ -60,4 +125,20 @@ GRAV:
|
||||
- '星期五'
|
||||
- '星期六'
|
||||
- '星期日'
|
||||
|
||||
CRON:
|
||||
EVERY: 每隔
|
||||
EVERY_HOUR: 每小时
|
||||
EVERY_MINUTE: 每分钟
|
||||
EVERY_DAY_OF_WEEK: 一周中的每一天
|
||||
EVERY_DAY_OF_MONTH: 月份中的每一天
|
||||
EVERY_MONTH: 每月
|
||||
TEXT_PERIOD: 所有 <b />
|
||||
TEXT_MINS: ' 在 <b /> 小时过后的分钟'
|
||||
TEXT_TIME: ' 在 <b />:<b />'
|
||||
TEXT_DOW: ' on <b />'
|
||||
TEXT_MONTH: ' of <b />'
|
||||
TEXT_DOM: ' on <b />'
|
||||
ERROR1: 不支持分享类型 %s
|
||||
ERROR2: 无效数字
|
||||
ERROR3: 请在 jqCron 设置中设定 jquery_element
|
||||
ERROR4: 无法识别表达式
|
||||
|
||||
@@ -17,11 +17,22 @@ if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_N
|
||||
return false;
|
||||
}
|
||||
|
||||
$grav_index = 'index.php';
|
||||
|
||||
/* Check the GRAV_BASEDIR environment variable and use if set */
|
||||
|
||||
$grav_basedir = getenv('GRAV_BASEDIR') ?: '';
|
||||
if ($grav_basedir) {
|
||||
$grav_index = ltrim($grav_basedir, '/') . DIRECTORY_SEPARATOR . $grav_index;
|
||||
$grav_basedir = DIRECTORY_SEPARATOR . trim($grav_basedir, DIRECTORY_SEPARATOR);
|
||||
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . $grav_basedir);
|
||||
}
|
||||
|
||||
$_SERVER = array_merge($_SERVER, $_ENV);
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . $grav_basedir .DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_NAME'] = $grav_basedir . DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['PHP_SELF'] = $grav_basedir . DIRECTORY_SEPARATOR . 'index.php';
|
||||
|
||||
error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4);
|
||||
|
||||
require 'index.php';
|
||||
require $grav_index;
|
||||
|
||||
@@ -24,19 +24,21 @@ trait LegacyAssetsTrait
|
||||
// First argument is always the asset
|
||||
array_shift($args);
|
||||
|
||||
if (\count($args) === 0) {
|
||||
if (count($args) === 0) {
|
||||
return [];
|
||||
}
|
||||
if (\count($args) === 1 && \is_array($args[0])) {
|
||||
// New options array format
|
||||
if (count($args) === 1 && is_array($args[0])) {
|
||||
return $args[0];
|
||||
}
|
||||
// Handle obscure case where options array is mixed with a priority
|
||||
if (count($args) === 2 && is_array($args[0]) && is_int($args[1])) {
|
||||
$arguments = $args[0];
|
||||
$arguments['priority'] = $args[1];
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case(Assets::INLINE_CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
case(Assets::JS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'loading' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
@@ -55,6 +57,11 @@ trait LegacyAssetsTrait
|
||||
|
||||
break;
|
||||
|
||||
case(Assets::INLINE_CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
default:
|
||||
case(Assets::CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null];
|
||||
|
||||
@@ -334,7 +334,7 @@ class Cache extends Getters
|
||||
* Stores a new cached entry.
|
||||
*
|
||||
* @param string $id the id of the cached entry
|
||||
* @param array|object $data the data for the cached entry to store
|
||||
* @param array|object|int $data the data for the cached entry to store
|
||||
* @param int $lifetime the lifetime to store the entry in seconds
|
||||
*/
|
||||
public function save($id, $data, $lifetime = null)
|
||||
@@ -437,6 +437,9 @@ class Cache extends Getters
|
||||
case 'tmp-only':
|
||||
$remove_paths = self::$tmp_remove;
|
||||
break;
|
||||
case 'invalidate':
|
||||
$remove_paths = [];
|
||||
break;
|
||||
default:
|
||||
if (Grav::instance()['config']->get('system.cache.clear_images_by_default')) {
|
||||
$remove_paths = self::$standard_remove;
|
||||
@@ -528,7 +531,6 @@ class Cache extends Getters
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the cache lifetime programmatically
|
||||
*
|
||||
@@ -540,7 +542,7 @@ class Cache extends Getters
|
||||
return;
|
||||
}
|
||||
|
||||
$interval = $future - $this->now;
|
||||
$interval = (int)($future - $this->now);
|
||||
if ($interval > 0 && $interval < $this->getLifetime()) {
|
||||
$this->lifetime = $interval;
|
||||
}
|
||||
@@ -555,7 +557,7 @@ class Cache extends Getters
|
||||
public function getLifetime()
|
||||
{
|
||||
if ($this->lifetime === null) {
|
||||
$this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default
|
||||
$this->lifetime = (int)($this->config->get('system.cache.lifetime') ?: 604800); // 1 week default
|
||||
}
|
||||
|
||||
return $this->lifetime;
|
||||
|
||||
@@ -25,6 +25,9 @@ class Blueprint extends BlueprintForm
|
||||
/** @var BlueprintSchema */
|
||||
protected $blueprintSchema;
|
||||
|
||||
/** @var object */
|
||||
protected $object;
|
||||
|
||||
/** @var array */
|
||||
protected $defaults;
|
||||
|
||||
@@ -42,6 +45,11 @@ class Blueprint extends BlueprintForm
|
||||
$this->scope = $scope;
|
||||
}
|
||||
|
||||
public function setObject($object)
|
||||
{
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values for field types.
|
||||
*
|
||||
@@ -111,6 +119,7 @@ class Blueprint extends BlueprintForm
|
||||
foreach ($data as $property => $call) {
|
||||
$action = $call['action'];
|
||||
$method = 'dynamic' . ucfirst($action);
|
||||
$call['object'] = $this->object;
|
||||
|
||||
if (isset($this->handlers[$action])) {
|
||||
$callable = $this->handlers[$action];
|
||||
@@ -378,14 +387,12 @@ class Blueprint extends BlueprintForm
|
||||
$grav = Grav::instance();
|
||||
$actions = (array)$call['params'];
|
||||
|
||||
/** @var UserInterface $user */
|
||||
if (isset($grav['user'])) {
|
||||
$user = Grav::instance()['user'];
|
||||
foreach ($actions as $action) {
|
||||
if (!$user->authorize($action)) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
}
|
||||
/** @var UserInterface|null $user */
|
||||
$user = $grav['user'] ?? null;
|
||||
foreach ($actions as $action) {
|
||||
if (!$user || !$user->authorize($action)) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,8 +244,8 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
|| !empty($field['disabled'])
|
||||
// Field validation is set to be ignored
|
||||
|| !empty($field['validate']['ignore'])
|
||||
// Field is toggleable and the toggle is turned off
|
||||
|| (!empty($field['toggleable']) && empty($toggles[$key]))
|
||||
// Field is overridable and the toggle is turned off
|
||||
|| (!empty($field['overridable']) && empty($toggles[$key]))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -279,6 +279,12 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip overridable fields without value.
|
||||
// TODO: We need better overridable support, which is not just ignoring required values but also looking if defaults are good.
|
||||
if (!empty($field['overridable']) && !isset($data[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if required.
|
||||
if (isset($field['validate']['required'])
|
||||
&& $field['validate']['required'] === true) {
|
||||
|
||||
@@ -39,7 +39,8 @@ class Blueprints
|
||||
public function get($type)
|
||||
{
|
||||
if (!isset($this->instances[$type])) {
|
||||
$this->instances[$type] = $this->loadFile($type);
|
||||
$blueprint = $this->loadFile($type);
|
||||
$this->instances[$type] = $blueprint;
|
||||
}
|
||||
|
||||
return $this->instances[$type];
|
||||
@@ -99,6 +100,15 @@ class Blueprints
|
||||
$blueprint->setContext($this->search);
|
||||
}
|
||||
|
||||
return $blueprint->load()->init();
|
||||
try {
|
||||
$blueprint->load()->init();
|
||||
} catch (\RuntimeException $e) {
|
||||
$log = Grav::instance()['log'];
|
||||
$log->error(sprintf('Blueprint %s cannot be loaded: %s', $name, $e->getMessage()));
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ class Validation
|
||||
if (!isset($field['type'])) {
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
$type = $validate['type'] ?? $field['type'];
|
||||
|
||||
$validate = (array)($field['validate'] ?? null);
|
||||
$type = $validate['type'] ?? $field['type'];
|
||||
$required = $validate['required'] ?? false;
|
||||
|
||||
// If value isn't required, we will stop validation if empty value is given.
|
||||
@@ -165,6 +166,11 @@ class Validation
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
protected static function filterCheckbox($value, array $params, array $field)
|
||||
{
|
||||
return (bool) $value;
|
||||
}
|
||||
|
||||
protected static function filterCommaList($value, array $params, array $field)
|
||||
{
|
||||
return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
@@ -571,6 +577,11 @@ class Validation
|
||||
}
|
||||
}
|
||||
|
||||
// If creating new values is allowed, no further checks are needed.
|
||||
if (!empty($field['selectize']['create'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$options = $field['options'] ?? [];
|
||||
$use = $field['use'] ?? 'values';
|
||||
|
||||
|
||||
@@ -9,6 +9,14 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\DataSource\MonologDataSource;
|
||||
use Clockwork\DataSource\PhpDataSource;
|
||||
use Clockwork\DataSource\PsrMessageDataSource;
|
||||
use Clockwork\DataSource\XdebugDataSource;
|
||||
use Clockwork\Helpers\ServerTiming;
|
||||
use Clockwork\Request\UserData;
|
||||
use Clockwork\Storage\FileStorage;
|
||||
use DebugBar\DataCollector\ConfigCollector;
|
||||
use DebugBar\DataCollector\DataCollectorInterface;
|
||||
use DebugBar\DataCollector\ExceptionsCollector;
|
||||
@@ -22,11 +30,23 @@ use DebugBar\JavascriptRenderer;
|
||||
use DebugBar\StandardDebugBar;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Processors\ProcessorInterface;
|
||||
use Grav\Common\Twig\TwigClockworkDataSource;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Monolog\Logger;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
class Debugger
|
||||
{
|
||||
/** @var static */
|
||||
protected static $instance;
|
||||
|
||||
/** @var Grav $grav */
|
||||
protected $grav;
|
||||
|
||||
@@ -39,8 +59,11 @@ class Debugger
|
||||
/** @var StandardDebugBar $debugbar */
|
||||
protected $debugbar;
|
||||
|
||||
/** @var Clockwork */
|
||||
protected $clockwork;
|
||||
|
||||
/** @var bool */
|
||||
protected $enabled;
|
||||
protected $enabled = false;
|
||||
|
||||
protected $initialized = false;
|
||||
|
||||
@@ -53,36 +76,38 @@ class Debugger
|
||||
/** @var callable */
|
||||
protected $errorHandler;
|
||||
|
||||
protected $requestTime;
|
||||
protected $currentTime;
|
||||
|
||||
/** @var int */
|
||||
protected $profiling = 0;
|
||||
|
||||
protected $censored = false;
|
||||
|
||||
/**
|
||||
* Debugger constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$currentTime = microtime(true);
|
||||
static::$instance = $this;
|
||||
|
||||
$this->currentTime = microtime(true);
|
||||
|
||||
if (!\defined('GRAV_REQUEST_TIME')) {
|
||||
\define('GRAV_REQUEST_TIME', $currentTime);
|
||||
\define('GRAV_REQUEST_TIME', $this->currentTime);
|
||||
}
|
||||
|
||||
// Enable debugger until $this->init() gets called.
|
||||
$this->enabled = true;
|
||||
|
||||
$debugbar = new DebugBar();
|
||||
$debugbar->addCollector(new PhpInfoCollector());
|
||||
$debugbar->addCollector(new MessagesCollector());
|
||||
$debugbar->addCollector(new RequestDataCollector());
|
||||
$debugbar->addCollector(new TimeDataCollector($_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME));
|
||||
|
||||
$debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
|
||||
$debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $currentTime);
|
||||
$debugbar['time']->addMeasure('Debugger', $currentTime, microtime(true));
|
||||
|
||||
$this->debugbar = $debugbar;
|
||||
$this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
|
||||
|
||||
// Set deprecation collector.
|
||||
$this->setErrorHandler();
|
||||
}
|
||||
|
||||
public function getClockwork(): ?Clockwork
|
||||
{
|
||||
return $this->enabled ? $this->clockwork : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the debugger
|
||||
*
|
||||
@@ -100,25 +125,234 @@ class Debugger
|
||||
|
||||
// Enable/disable debugger based on configuration.
|
||||
$this->enabled = (bool)$this->config->get('system.debugger.enabled');
|
||||
$this->censored = (bool)$this->config->get('system.debugger.censored', false);
|
||||
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled) {
|
||||
$this->initialized = true;
|
||||
|
||||
$plugins_config = (array)$this->config->get('plugins');
|
||||
$clockwork = $debugbar = null;
|
||||
|
||||
switch ($this->config->get('system.debugger.provider', 'debugbar')) {
|
||||
case 'clockwork':
|
||||
$this->clockwork = $clockwork = new Clockwork();
|
||||
break;
|
||||
default:
|
||||
$this->debugbar = $debugbar = new DebugBar();
|
||||
}
|
||||
|
||||
$plugins_config = (array)$this->config->get('plugins');
|
||||
ksort($plugins_config);
|
||||
|
||||
$debugbar = $this->debugbar;
|
||||
$debugbar->addCollector(new MemoryCollector());
|
||||
$debugbar->addCollector(new ExceptionsCollector());
|
||||
$debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
|
||||
$debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
|
||||
$this->addMessage('Grav v' . GRAV_VERSION);
|
||||
if ($clockwork) {
|
||||
$log = $this->grav['log'];
|
||||
$clockwork->setStorage(new FileStorage('cache://clockwork'));
|
||||
if (extension_loaded('xdebug')) {
|
||||
$clockwork->addDataSource(new XdebugDataSource());
|
||||
}
|
||||
if ($log instanceof Logger) {
|
||||
$clockwork->addDataSource(new MonologDataSource($log));
|
||||
}
|
||||
|
||||
$clockwork->addDataSource(new TwigClockworkDataSource());
|
||||
|
||||
$timeLine = $clockwork->getTimeline();
|
||||
if ($this->requestTime !== GRAV_REQUEST_TIME) {
|
||||
$timeLine->addEvent('server', 'Server', $this->requestTime, GRAV_REQUEST_TIME);
|
||||
}
|
||||
if ($this->currentTime !== GRAV_REQUEST_TIME) {
|
||||
$timeLine->addEvent('loading', 'Loading', GRAV_REQUEST_TIME, $this->currentTime);
|
||||
}
|
||||
$timeLine->addEvent('setup', 'Site Setup', $this->currentTime, microtime(true));
|
||||
}
|
||||
|
||||
if ($this->censored) {
|
||||
$censored = ['CENSORED' => true];
|
||||
}
|
||||
|
||||
if ($debugbar) {
|
||||
$debugbar->addCollector(new PhpInfoCollector());
|
||||
$debugbar->addCollector(new MessagesCollector());
|
||||
if (!$this->censored) {
|
||||
$debugbar->addCollector(new RequestDataCollector());
|
||||
}
|
||||
$debugbar->addCollector(new TimeDataCollector($this->requestTime));
|
||||
$debugbar->addCollector(new MemoryCollector());
|
||||
$debugbar->addCollector(new ExceptionsCollector());
|
||||
$debugbar->addCollector(new ConfigCollector($censored ?? (array)$this->config->get('system'), 'Config'));
|
||||
$debugbar->addCollector(new ConfigCollector($censored ?? $plugins_config, 'Plugins'));
|
||||
$debugbar->addCollector(new ConfigCollector($this->config->get('streams.schemes'), 'Streams'));
|
||||
|
||||
if ($this->requestTime !== GRAV_REQUEST_TIME) {
|
||||
$debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
|
||||
}
|
||||
if ($this->currentTime !== GRAV_REQUEST_TIME) {
|
||||
$debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $this->currentTime);
|
||||
}
|
||||
$debugbar['time']->addMeasure('Site Setup', $this->currentTime, microtime(true));
|
||||
}
|
||||
|
||||
$this->addMessage('Grav v' . GRAV_VERSION . ' - PHP ' . PHP_VERSION);
|
||||
$this->config->debug();
|
||||
|
||||
if ($clockwork) {
|
||||
$clockwork->info('System Configuration', $censored ?? $this->config->get('system'));
|
||||
$clockwork->info('Plugins Configuration', $censored ?? $plugins_config);
|
||||
$clockwork->info('Streams', $this->config->get('streams.schemes'));
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function finalize(): void
|
||||
{
|
||||
if ($this->clockwork && $this->enabled) {
|
||||
$this->stopProfiling('Profiler Analysis');
|
||||
$this->addMeasures();
|
||||
|
||||
$deprecations = $this->getDeprecations();
|
||||
$count = count($deprecations);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var UserData $userData */
|
||||
$userData = $this->clockwork->userData('Deprecated');
|
||||
$userData->counters([
|
||||
'Deprecated' => count($deprecations)
|
||||
]);
|
||||
foreach ($deprecations as &$deprecation) {
|
||||
if (0) {
|
||||
$d = $deprecation;
|
||||
unset($d['message']);
|
||||
$this->clockwork->log('deprecated', $deprecation['message'], $d);
|
||||
}
|
||||
}
|
||||
unset($deprecation);
|
||||
|
||||
$userData->table('Your site is using following deprecated features', $deprecations);
|
||||
}
|
||||
}
|
||||
|
||||
public function logRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
if (!$this->enabled || !$this->clockwork) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$clockwork = $this->clockwork;
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$clockwork->getTimeline()->finalize($request->getAttribute('request_time'));
|
||||
|
||||
if ($this->censored) {
|
||||
$censored = 'CENSORED';
|
||||
$request = $request
|
||||
->withCookieParams([$censored => ''])
|
||||
->withUploadedFiles([])
|
||||
->withHeader('cookie', $censored);
|
||||
if ($request->getBody()) {
|
||||
$request = $request->withParsedBody([$censored => '']);
|
||||
}
|
||||
}
|
||||
|
||||
$clockwork->addDataSource(new PsrMessageDataSource($request, $response));
|
||||
|
||||
$clockwork->resolveRequest();
|
||||
$clockwork->storeRequest();
|
||||
|
||||
$clockworkRequest = $clockwork->getRequest();
|
||||
|
||||
$response = $response
|
||||
->withHeader('X-Clockwork-Id', $clockworkRequest->id)
|
||||
->withHeader('X-Clockwork-Version', $clockwork::VERSION);
|
||||
|
||||
$basePath = Grav::instance()['uri']->rootUrl();
|
||||
if ($basePath) {
|
||||
$response = $response->withHeader('X-Clockwork-Path', $basePath . '/__clockwork/');
|
||||
}
|
||||
|
||||
return $response->withHeader('Server-Timing', ServerTiming::fromRequest($clockworkRequest)->value());
|
||||
}
|
||||
|
||||
|
||||
public function debuggerRequest(RequestInterface $request): Response
|
||||
{
|
||||
$clockwork = $this->clockwork;
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Grav-Internal-SkipShutdown' => 1
|
||||
];
|
||||
|
||||
$path = $request->getUri()->getPath();
|
||||
$clockworkDataUri = '#/__clockwork(?:/(?<id>[0-9-]+))?(?:/(?<direction>(?:previous|next)))?(?:/(?<count>\d+))?#';
|
||||
if (preg_match($clockworkDataUri, $path, $matches) === false) {
|
||||
$response = ['message' => 'Bad Input'];
|
||||
|
||||
return new Response(400, $headers, json_encode($response));
|
||||
}
|
||||
|
||||
$id = $matches['id'] ?? null;
|
||||
$direction = $matches['direction'] ?? null;
|
||||
$count = $matches['count'] ?? null;
|
||||
|
||||
$storage = $clockwork->getStorage();
|
||||
|
||||
if ($direction === 'previous') {
|
||||
$data = $storage->previous($id, $count);
|
||||
} elseif ($direction === 'next') {
|
||||
$data = $storage->next($id, $count);
|
||||
} elseif ($id === 'latest') {
|
||||
$data = $storage->latest();
|
||||
} else {
|
||||
$data = $storage->find($id);
|
||||
}
|
||||
|
||||
if (preg_match('#(?<id>[0-9-]+|latest)/extended#', $path)) {
|
||||
$clockwork->extendRequest($data);
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
$response = ['message' => 'Not Found'];
|
||||
|
||||
return new Response(404, $headers, json_encode($response));
|
||||
}
|
||||
|
||||
$data = is_array($data) ? array_map(function ($item) { return $item->toArray(); }, $data) : $data->toArray();
|
||||
|
||||
return new Response(200, $headers, json_encode($data));
|
||||
}
|
||||
|
||||
protected function addMeasures()
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nowTime = microtime(true);
|
||||
$clkTimeLine = $this->clockwork ? $this->clockwork->getTimeline() : null;
|
||||
$debTimeLine = $this->debugbar ? $this->debugbar['time'] : null;
|
||||
foreach ($this->timers as $name => $data) {
|
||||
$description = $data[0];
|
||||
$startTime = $data[1] ?? null;
|
||||
$endTime = $data[2] ?? $nowTime;
|
||||
if ($endTime - $startTime < 0.001) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($clkTimeLine) {
|
||||
$clkTimeLine->addEvent($name, $description ?? $name, $startTime, $endTime);
|
||||
}
|
||||
|
||||
if ($debTimeLine) {
|
||||
$debTimeLine->addMeasure($description ?? $name, $startTime, $endTime);
|
||||
}
|
||||
}
|
||||
$this->timers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/get the enabled state of the debugger
|
||||
*
|
||||
@@ -142,7 +376,8 @@ class Debugger
|
||||
*/
|
||||
public function addAssets()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled) {
|
||||
|
||||
|
||||
// Only add assets if Page is HTML
|
||||
$page = $this->grav['page'];
|
||||
@@ -153,23 +388,35 @@ class Debugger
|
||||
/** @var Assets $assets */
|
||||
$assets = $this->grav['assets'];
|
||||
|
||||
// Add jquery library
|
||||
$assets->add('jquery', 101);
|
||||
|
||||
$this->renderer = $this->debugbar->getJavascriptRenderer();
|
||||
$this->renderer->setIncludeVendors(false);
|
||||
|
||||
// Get the required CSS files
|
||||
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
|
||||
foreach ((array)$css_files as $css) {
|
||||
$assets->addCss($css);
|
||||
// Clockwork specific assets
|
||||
if ($this->clockwork) {
|
||||
$assets->addCss('/system/assets/debugger/clockwork.css', ['loading' => 'inline']);
|
||||
$assets->addJs('/system/assets/debugger/clockwork.js', ['loading' => 'inline']);
|
||||
}
|
||||
|
||||
$assets->addCss('/system/assets/debugger.css');
|
||||
|
||||
foreach ((array)$js_files as $js) {
|
||||
$assets->addJs($js);
|
||||
// Debugbar specific assets
|
||||
if ($this->debugbar) {
|
||||
|
||||
// Add jquery library
|
||||
$assets->add('jquery', 101);
|
||||
|
||||
$this->renderer = $this->debugbar->getJavascriptRenderer();
|
||||
$this->renderer->setIncludeVendors(false);
|
||||
|
||||
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
|
||||
|
||||
foreach ((array)$css_files as $css) {
|
||||
$assets->addCss($css);
|
||||
}
|
||||
|
||||
$assets->addCss('/system/assets/debugger/phpdebugbar.css', ['loading' => 'inline']);
|
||||
|
||||
foreach ((array)$js_files as $js) {
|
||||
$assets->addJs($js);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -192,7 +439,9 @@ class Debugger
|
||||
*/
|
||||
public function addCollector($collector)
|
||||
{
|
||||
$this->debugbar->addCollector($collector);
|
||||
if ($this->debugbar && !$this->debugbar->hasCollector($collector->getName())) {
|
||||
$this->debugbar->addCollector($collector);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -200,14 +449,18 @@ class Debugger
|
||||
/**
|
||||
* Returns a data collector
|
||||
*
|
||||
* @param DataCollectorInterface $collector
|
||||
* @param string $name
|
||||
*
|
||||
* @return DataCollectorInterface
|
||||
* @throws \DebugBar\DebugBarException
|
||||
*/
|
||||
public function getCollector($collector)
|
||||
public function getCollector($name)
|
||||
{
|
||||
return $this->debugbar->getCollector($collector);
|
||||
if ($this->debugbar && $this->debugbar->hasCollector($name)) {
|
||||
return $this->debugbar->getCollector($name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,13 +470,14 @@ class Debugger
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled && $this->debugbar) {
|
||||
// Only add assets if Page is HTML
|
||||
$page = $this->grav['page'];
|
||||
if (!$this->renderer || $page->templateFormat() !== 'html') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->addMeasures();
|
||||
$this->addDeprecations();
|
||||
|
||||
echo $this->renderer->render();
|
||||
@@ -239,7 +493,8 @@ class Debugger
|
||||
*/
|
||||
public function sendDataInHeaders()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled && $this->debugbar) {
|
||||
$this->addMeasures();
|
||||
$this->addDeprecations();
|
||||
$this->debugbar->sendDataInHeaders();
|
||||
}
|
||||
@@ -254,16 +509,146 @@ class Debugger
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
if (!$this->enabled || !$this->debugbar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->addMeasures();
|
||||
$this->addDeprecations();
|
||||
$this->timers = [];
|
||||
|
||||
return $this->debugbar->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hierarchical Profiler support.
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param string $message
|
||||
* @return mixed
|
||||
*/
|
||||
public function profile(callable $callable, string $message = null)
|
||||
{
|
||||
$this->startProfiling();
|
||||
$response = $callable();
|
||||
$this->stopProfiling($message);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start profiling code.
|
||||
*/
|
||||
public function startProfiling(): void
|
||||
{
|
||||
if ($this->enabled && extension_loaded('tideways_xhprof')) {
|
||||
$this->profiling++;
|
||||
if ($this->profiling === 1) {
|
||||
\tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_NO_BUILTINS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop profiling code. Returns profiling array or null if profiling couldn't be done.
|
||||
*
|
||||
* @param string $message
|
||||
* @return array|null
|
||||
*/
|
||||
public function stopProfiling(string $message = null): ?array
|
||||
{
|
||||
$timings = null;
|
||||
if ($this->enabled && extension_loaded('tideways_xhprof')) {
|
||||
$profiling = $this->profiling - 1;
|
||||
if ($profiling === 0) {
|
||||
$timings = \tideways_xhprof_disable();
|
||||
$timings = $this->buildProfilerTimings($timings);
|
||||
|
||||
if ($this->clockwork) {
|
||||
/** @var UserData $userData */
|
||||
$userData = $this->clockwork->userData('Profiler');
|
||||
$userData->counters([
|
||||
'Calls' => count($timings)
|
||||
]);
|
||||
$userData->table('Profiler', $timings);
|
||||
} else {
|
||||
$this->addMessage($message ?? 'Profiler Analysis', 'debug', $timings);
|
||||
}
|
||||
}
|
||||
$this->profiling = max(0, $profiling);
|
||||
}
|
||||
|
||||
return $timings;
|
||||
}
|
||||
|
||||
protected function buildProfilerTimings(array $timings): array
|
||||
{
|
||||
// Filter method calls which take almost no time.
|
||||
$timings = array_filter($timings, function ($value) {
|
||||
return $value['wt'] > 50;
|
||||
});
|
||||
|
||||
uasort($timings, function (array $a, array $b) {
|
||||
return $b['wt'] <=> $a['wt'];
|
||||
});
|
||||
|
||||
$table = [];
|
||||
foreach ($timings as $key => $timing) {
|
||||
$parts = explode('==>', $key);
|
||||
$method = $this->parseProfilerCall(array_pop($parts));
|
||||
$context = $this->parseProfilerCall(array_pop($parts));
|
||||
|
||||
// Skip redundant method calls.
|
||||
if ($context === 'Grav\Framework\RequestHandler\RequestHandler::handle()') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not profile library calls.
|
||||
if (strpos($context, 'Grav\\') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$table[] = [
|
||||
'Context' => $context,
|
||||
'Method' => $method,
|
||||
'Calls' => $timing['ct'],
|
||||
'Time (ms)' => $timing['wt'] / 1000,
|
||||
];
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
protected function parseProfilerCall(?string $call)
|
||||
{
|
||||
if (null === $call) {
|
||||
return '';
|
||||
}
|
||||
if (strpos($call, '@')) {
|
||||
[$call,] = explode('@', $call);
|
||||
}
|
||||
if (strpos($call, '::')) {
|
||||
[$class, $call] = explode('::', $call);
|
||||
}
|
||||
|
||||
if (!isset($class)) {
|
||||
return $call;
|
||||
}
|
||||
|
||||
// It is also possible to display twig files, but they are being logged in views.
|
||||
/*
|
||||
if (strpos($class, '__TwigTemplate_') === 0 && class_exists($class)) {
|
||||
$env = new Environment();
|
||||
/ ** @var Template $template * /
|
||||
$template = new $class($env);
|
||||
|
||||
return $template->getTemplateName();
|
||||
}
|
||||
*/
|
||||
|
||||
return "{$class}::{$call}()";
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a timer with an associated name and description
|
||||
*
|
||||
@@ -274,10 +659,7 @@ class Debugger
|
||||
*/
|
||||
public function startTimer($name, $description = null)
|
||||
{
|
||||
if (strpos($name, '_') === 0 || $this->enabled()) {
|
||||
$this->debugbar['time']->startMeasure($name, $description);
|
||||
$this->timers[] = $name;
|
||||
}
|
||||
$this->timers[$name] = [$description, microtime(true)];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -291,8 +673,9 @@ class Debugger
|
||||
*/
|
||||
public function stopTimer($name)
|
||||
{
|
||||
if (\in_array($name, $this->timers, true) && (strpos($name, '_') === 0 || $this->enabled())) {
|
||||
$this->debugbar['time']->stopMeasure($name);
|
||||
if (isset($this->timers[$name])) {
|
||||
$endTime = microtime(true);
|
||||
$this->timers[$name][] = $endTime;
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -303,14 +686,60 @@ class Debugger
|
||||
*
|
||||
* @param mixed $message
|
||||
* @param string $label
|
||||
* @param bool $isString
|
||||
* @param mixed|bool $isString
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addMessage($message, $label = 'info', $isString = true)
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
$this->debugbar['messages']->addMessage($message, $label, $isString);
|
||||
if ($this->enabled) {
|
||||
if ($this->censored) {
|
||||
if (!is_scalar($message)) {
|
||||
$message = 'CENSORED';
|
||||
}
|
||||
if (!is_scalar($isString)) {
|
||||
$isString = ['CENSORED'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->debugbar) {
|
||||
if (is_array($isString)) {
|
||||
$message = $isString;
|
||||
$isString = false;
|
||||
} elseif (is_string($isString)) {
|
||||
$message = $isString;
|
||||
$isString = true;
|
||||
}
|
||||
$this->debugbar['messages']->addMessage($message, $label, $isString);
|
||||
}
|
||||
|
||||
if ($this->clockwork) {
|
||||
if (!is_scalar($message)) {
|
||||
$isString = $message;
|
||||
$message = '';
|
||||
} elseif (is_bool($isString)) {
|
||||
$isString = [];
|
||||
}
|
||||
if (!is_array($isString)) {
|
||||
$isString = [gettype($isString) => $isString];
|
||||
}
|
||||
$this->clockwork->log($label, $message, $isString);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addEvent(string $name, ?Event $event, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
if ($this->clockwork) {
|
||||
$listeners = [];
|
||||
foreach ($dispatcher->getListeners($name) as $listener) {
|
||||
$listeners[] = $this->resolveCallable($listener);
|
||||
}
|
||||
$this->clockwork->addEvent($name, null, microtime(true), ['listeners' => $listeners]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -319,13 +748,23 @@ class Debugger
|
||||
/**
|
||||
* Dump exception into the Messages tab of the Debug Bar
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Throwable $e
|
||||
* @return Debugger
|
||||
*/
|
||||
public function addException(\Exception $e)
|
||||
public function addException(\Throwable $e)
|
||||
{
|
||||
if ($this->initialized && $this->enabled()) {
|
||||
$this->debugbar['exceptions']->addException($e);
|
||||
if ($this->initialized && $this->enabled) {
|
||||
if ($this->debugbar) {
|
||||
$this->debugbar['exceptions']->addException($e);
|
||||
}
|
||||
|
||||
if ($this->clockwork) {
|
||||
/** @var UserData $exceptions */
|
||||
$exceptions = $this->clockwork->userData('Exceptions');
|
||||
$exceptions->data(['message' => $e->getMessage()]);
|
||||
|
||||
$this->clockwork->alert($e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -355,7 +794,7 @@ class Debugger
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->enabled()) {
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -540,6 +979,21 @@ class Debugger
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getDeprecations(): array
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$list = [];
|
||||
/** @var array $deprecated */
|
||||
foreach ($this->deprecations as $deprecated) {
|
||||
$list[] = $this->getDepracatedMessage($deprecated)[0];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
protected function addDeprecations()
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
@@ -603,4 +1057,13 @@ class Debugger
|
||||
|
||||
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
|
||||
}
|
||||
|
||||
protected function resolveCallable(callable $callable)
|
||||
{
|
||||
if (is_array($callable)) {
|
||||
return get_class($callable[0]) . '->' . $callable[1] . '()';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,54 +10,10 @@
|
||||
namespace Grav\Common\Form;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Grav\Framework\Form\FormFlash as FrameworkFormFlash;
|
||||
|
||||
class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
class FormFlash extends FrameworkFormFlash
|
||||
{
|
||||
/**
|
||||
* @param string $sessionId
|
||||
*/
|
||||
public static function clearSession(string $sessionId): void
|
||||
{
|
||||
$folder = static::getSessionTmpDir($sessionId);
|
||||
if (is_dir($folder)) {
|
||||
Folder::delete($folder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
* @return string
|
||||
*/
|
||||
public static function getSessionTmpDir(string $sessionId): string
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
return $locator->findResource("tmp://forms/{$sessionId}", true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface|null $user
|
||||
* @return $this
|
||||
*/
|
||||
public function setUser(UserInterface $user = null)
|
||||
{
|
||||
if ($user && $user->username) {
|
||||
$this->user = [
|
||||
'username' => $user->username,
|
||||
'email' => $user->email ?? ''
|
||||
];
|
||||
} else {
|
||||
$this->user = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @deprecated 1.6 For backwards compatibility only, do not use
|
||||
@@ -89,8 +45,11 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
*/
|
||||
public function uploadFile(string $field, string $filename, array $upload): bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
if (!$this->uniqueId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
@@ -118,8 +77,11 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
*/
|
||||
public function cropFile(string $field, string $filename, array $upload, array $crop): bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
if (!$this->uniqueId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
@@ -136,32 +98,4 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return YamlFile
|
||||
*/
|
||||
protected function getTmpIndex(): YamlFile
|
||||
{
|
||||
// Do not use CompiledYamlFile as the file can change multiple times per second.
|
||||
return YamlFile::instance($this->getTmpDir() . '/index.yaml');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
protected function removeTmpFile(string $name): void
|
||||
{
|
||||
$filename = $this->getTmpDir() . '/' . $name;
|
||||
if ($name && is_file($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeTmpDir(): void
|
||||
{
|
||||
$tmpDir = $this->getTmpDir();
|
||||
if (file_exists($tmpDir)) {
|
||||
Folder::delete($tmpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,22 @@ namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class CachedCollection extends Iterator {
|
||||
|
||||
class CachedCollection extends Iterator
|
||||
{
|
||||
protected static $cache;
|
||||
|
||||
public function __construct($items)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$method = static::class . __METHOD__;
|
||||
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[get_called_class() . __METHOD__])) {
|
||||
self::$cache[get_called_class() . __METHOD__] = $items;
|
||||
if (!isset(self::$cache[$method])) {
|
||||
self::$cache[$method] = $items;
|
||||
}
|
||||
|
||||
foreach (self::$cache[get_called_class() . __METHOD__] as $name => $item) {
|
||||
foreach (self::$cache[$method] as $name => $item) {
|
||||
$this->append([$name => $item]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
class Package {
|
||||
|
||||
/**
|
||||
* @property string $name
|
||||
*/
|
||||
class Package
|
||||
{
|
||||
/**
|
||||
* @var Data
|
||||
*/
|
||||
|
||||
@@ -770,7 +770,7 @@ class GPM extends Iterator
|
||||
* @param array $ignore_packages_list
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function checkNoOtherPackageNeedsThisDependencyInALowerVersion(
|
||||
$slug,
|
||||
@@ -793,8 +793,8 @@ class GPM extends Iterator
|
||||
$compatible = $this->checkNextSignificantReleasesAreCompatible($version,
|
||||
$other_dependency_version);
|
||||
if (!$compatible) {
|
||||
if (!in_array($dependent_package, $ignore_packages_list)) {
|
||||
throw new \Exception("Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
|
||||
if (!in_array($dependent_package, $ignore_packages_list, true)) {
|
||||
throw new \RuntimeException("Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
|
||||
2);
|
||||
}
|
||||
}
|
||||
@@ -850,10 +850,10 @@ class GPM extends Iterator
|
||||
) {
|
||||
//Needs a Grav update first
|
||||
throw new \RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
|
||||
} else {
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
|
||||
@@ -863,10 +863,10 @@ class GPM extends Iterator
|
||||
) {
|
||||
//Needs a Grav update first
|
||||
throw new \RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
|
||||
} else {
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isPluginInstalled($dependency_slug)) {
|
||||
@@ -1092,6 +1092,7 @@ class GPM extends Iterator
|
||||
if ($this->versionFormatIsEqualOrHigher($version)) {
|
||||
return trim(substr($version, 2));
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
@@ -1104,7 +1105,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function versionFormatIsNextSignificantRelease($version)
|
||||
public function versionFormatIsNextSignificantRelease($version): bool
|
||||
{
|
||||
return strpos($version, '~') === 0;
|
||||
}
|
||||
@@ -1118,7 +1119,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function versionFormatIsEqualOrHigher($version)
|
||||
public function versionFormatIsEqualOrHigher($version): bool
|
||||
{
|
||||
return strpos($version, '>=') === 0;
|
||||
}
|
||||
@@ -1136,7 +1137,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkNextSignificantReleasesAreCompatible($version1, $version2)
|
||||
public function checkNextSignificantReleasesAreCompatible($version1, $version2): bool
|
||||
{
|
||||
$version1array = explode('.', $version1);
|
||||
$version2array = explode('.', $version2);
|
||||
|
||||
@@ -103,7 +103,7 @@ class Licenses
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the License File object
|
||||
* Get the License File object
|
||||
*
|
||||
* @return \RocketTheme\Toolbox\File\FileInterface
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ abstract class AbstractPackageCollection extends BaseCollection
|
||||
public function __construct($items)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
foreach ($items as $name => $data) {
|
||||
$data->set('slug', $name);
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
|
||||
@@ -25,6 +25,7 @@ class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/** @var \Grav\Common\Plugins $plugins */
|
||||
$plugins = Grav::instance()['plugins'];
|
||||
|
||||
parent::__construct($plugins->all());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
parent::__construct();
|
||||
if ($repository === null) {
|
||||
throw new \RuntimeException("A repository is required to indicate the origin of the remote collection");
|
||||
throw new \RuntimeException('A repository is required to indicate the origin of the remote collection');
|
||||
}
|
||||
|
||||
$channel = Grav::instance()['config']->get('system.gpm.releases', 'stable');
|
||||
|
||||
@@ -37,9 +37,9 @@ class GravCore extends AbstractPackageCollection
|
||||
$this->fetch($refresh, $callback);
|
||||
|
||||
$this->data = json_decode($this->raw, true);
|
||||
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
|
||||
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
|
||||
$this->min_php = isset($this->data['min_php']) ? $this->data['min_php'] : null;
|
||||
$this->version = $this->data['version'] ?? '-';
|
||||
$this->date = $this->data['date'] ?? '-';
|
||||
$this->min_php = $this->data['min_php'] ?? null;
|
||||
|
||||
if (isset($this->data['assets'])) {
|
||||
foreach ((array)$this->data['assets'] as $slug => $data) {
|
||||
|
||||
@@ -16,12 +16,8 @@ use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Processors\AssetsProcessor;
|
||||
use Grav\Common\Processors\BackupsProcessor;
|
||||
use Grav\Common\Processors\ConfigurationProcessor;
|
||||
use Grav\Common\Processors\DebuggerAssetsProcessor;
|
||||
use Grav\Common\Processors\DebuggerProcessor;
|
||||
use Grav\Common\Processors\ErrorsProcessor;
|
||||
use Grav\Common\Processors\InitializeProcessor;
|
||||
use Grav\Common\Processors\LoggerProcessor;
|
||||
use Grav\Common\Processors\PagesProcessor;
|
||||
use Grav\Common\Processors\PluginsProcessor;
|
||||
use Grav\Common\Processors\RenderProcessor;
|
||||
@@ -90,10 +86,6 @@ class Grav extends Container
|
||||
* @var array All middleware processors that are processed in $this->process()
|
||||
*/
|
||||
protected $middleware = [
|
||||
'configurationProcessor',
|
||||
'loggerProcessor',
|
||||
'errorsProcessor',
|
||||
'debuggerProcessor',
|
||||
'initializeProcessor',
|
||||
'pluginsProcessor',
|
||||
'themesProcessor',
|
||||
@@ -157,15 +149,13 @@ class Grav extends Container
|
||||
|
||||
$this->initialized['setup'] = true;
|
||||
|
||||
$this->measureTime('_setup', 'Site Setup', function () use ($environment) {
|
||||
// Force environment if passed to the method.
|
||||
if ($environment) {
|
||||
Setup::$environment = $environment;
|
||||
}
|
||||
// Force environment if passed to the method.
|
||||
if ($environment) {
|
||||
Setup::$environment = $environment;
|
||||
}
|
||||
|
||||
$this['setup'];
|
||||
$this['streams'];
|
||||
});
|
||||
$this['setup'];
|
||||
$this['streams'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -186,18 +176,6 @@ class Grav extends Container
|
||||
|
||||
$container = new Container(
|
||||
[
|
||||
'configurationProcessor' => function () {
|
||||
return new ConfigurationProcessor($this);
|
||||
},
|
||||
'loggerProcessor' => function () {
|
||||
return new LoggerProcessor($this);
|
||||
},
|
||||
'errorsProcessor' => function () {
|
||||
return new ErrorsProcessor($this);
|
||||
},
|
||||
'debuggerProcessor' => function () {
|
||||
return new DebuggerProcessor($this);
|
||||
},
|
||||
'initializeProcessor' => function () {
|
||||
return new InitializeProcessor($this);
|
||||
},
|
||||
@@ -237,13 +215,10 @@ class Grav extends Container
|
||||
]
|
||||
);
|
||||
|
||||
$default = function (ServerRequestInterface $request) {
|
||||
$default = static function () {
|
||||
return new Response(404);
|
||||
};
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
|
||||
$collection = new RequestHandler($this->middleware, $default, $container);
|
||||
|
||||
$response = $collection->handle($this['request']);
|
||||
@@ -251,32 +226,65 @@ class Grav extends Container
|
||||
$this->header($response);
|
||||
echo $response->getBody();
|
||||
|
||||
$debugger->render();
|
||||
$this['debugger']->render();
|
||||
|
||||
register_shutdown_function([$this, 'shutdown']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the system locale based on the language and configuration
|
||||
*/
|
||||
public function setLocale()
|
||||
{
|
||||
// Initialize Locale if set and configured.
|
||||
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
|
||||
$language = $this['language']->getLanguage();
|
||||
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
|
||||
} elseif ($this['config']->get('system.default_locale')) {
|
||||
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
|
||||
// Response object can turn off all shutdown processing. This can be used for example to speed up AJAX responses.
|
||||
// Note that using this feature will also turn off response compression.
|
||||
if ($response->getHeaderLine('Grav-Internal-SkipShutdown') !== '1') {
|
||||
register_shutdown_function([$this, 'shutdown']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect browser to another location.
|
||||
* Terminates Grav request with a response.
|
||||
*
|
||||
* Please use this method instead of calling `die();` or `exit();`. Note that you need to create a response object.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function close(ResponseInterface $response): void
|
||||
{
|
||||
// Make sure nothing extra gets written to the response.
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Close the session.
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = $this['request'];
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
$response = $debugger->logRequest($request, $response);
|
||||
|
||||
// Send the response and terminate.
|
||||
$this->header($response);
|
||||
echo $response->getBody();
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @deprecated 1.7 Do not use
|
||||
*/
|
||||
public function exit(ResponseInterface $response): void
|
||||
{
|
||||
$this->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates Grav request and redirects browser to another location.
|
||||
*
|
||||
* Please use this method instead of calling `header("Location: {$url}", true, 302); exit();`.
|
||||
*
|
||||
* @param string $route Internal route.
|
||||
* @param int $code Redirection code (30x)
|
||||
*/
|
||||
public function redirect($route, $code = null)
|
||||
public function redirect($route, $code = null): void
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
@@ -293,11 +301,7 @@ class Grav extends Container
|
||||
$code = $this['config']->get('system.pages.redirect_default_code', 302);
|
||||
}
|
||||
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
if ($uri->isExternal($route)) {
|
||||
if ($uri::isExternal($route)) {
|
||||
$url = $route;
|
||||
} else {
|
||||
$url = rtrim($uri->rootUrl(), '/') . '/';
|
||||
@@ -309,8 +313,9 @@ class Grav extends Container
|
||||
}
|
||||
}
|
||||
|
||||
header("Location: {$url}", true, $code);
|
||||
exit();
|
||||
$response = new Response($code, ['Location' => $url]);
|
||||
|
||||
$this->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,12 +348,30 @@ class Grav extends Container
|
||||
|
||||
header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
|
||||
foreach ($response->getHeaders() as $key => $values) {
|
||||
// Skip internal Grav headers.
|
||||
if (strpos($key, 'Grav-Internal-') === 0) {
|
||||
continue;
|
||||
}
|
||||
foreach ($values as $i => $value) {
|
||||
header($key . ': ' . $value, $i === 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the system locale based on the language and configuration
|
||||
*/
|
||||
public function setLocale()
|
||||
{
|
||||
// Initialize Locale if set and configured.
|
||||
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
|
||||
$language = $this['language']->getLanguage();
|
||||
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
|
||||
} elseif ($this['config']->get('system.default_locale')) {
|
||||
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires an event with optional parameters.
|
||||
*
|
||||
@@ -362,6 +385,10 @@ class Grav extends Container
|
||||
/** @var EventDispatcher $events */
|
||||
$events = $this['events'];
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
$debugger->addEvent($eventName, $event, $events);
|
||||
|
||||
return $events->dispatch($eventName, $event);
|
||||
}
|
||||
|
||||
@@ -470,9 +497,7 @@ class Grav extends Container
|
||||
return $container;
|
||||
};
|
||||
|
||||
$container->measureTime('_services', 'Services', function () use ($container) {
|
||||
$container->registerServices();
|
||||
});
|
||||
$container->registerServices();
|
||||
|
||||
return $container;
|
||||
}
|
||||
@@ -528,7 +553,7 @@ class Grav extends Container
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $this['pages']->dispatch($path_parts['dirname'], true);
|
||||
$page = $this['pages']->find($path_parts['dirname'], true);
|
||||
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
|
||||
@@ -9,24 +9,20 @@
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Page\Markdown\Excerpts as ExcerptsObject;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Excerpts
|
||||
{
|
||||
/**
|
||||
* Process Grav image media URL from HTML tag
|
||||
*
|
||||
* @param string $html HTML tag e.g. `<img src="image.jpg" />`
|
||||
* @param PageInterface $page The current page object
|
||||
* @return string Returns final HTML string
|
||||
* @param string $html HTML tag e.g. `<img src="image.jpg" />`
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @return string Returns final HTML string
|
||||
*/
|
||||
public static function processImageHtml($html, PageInterface $page)
|
||||
public static function processImageHtml($html, PageInterface $page = null)
|
||||
{
|
||||
$excerpt = static::getExcerptFromHtml($html, 'img');
|
||||
|
||||
@@ -112,157 +108,29 @@ class Excerpts
|
||||
* Process a Link excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @param PageInterface $page
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
public static function processLinkExcerpt($excerpt, PageInterface $page, $type = 'link')
|
||||
public static function processLinkExcerpt($excerpt, PageInterface $page = null, $type = 'link')
|
||||
{
|
||||
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
|
||||
$excerpts = new ExcerptsObject($page);
|
||||
|
||||
$url_parts = static::parseUrl($url);
|
||||
|
||||
// If there is a query, then parse it and build action calls.
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
|
||||
$carry[$parts[0]] = $value;
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// Valid attributes supported.
|
||||
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
|
||||
|
||||
// Unless told to not process, go through actions.
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
unset($actions['noprocess']);
|
||||
} else {
|
||||
// Loop through actions for the image and call them.
|
||||
foreach ($actions as $attrib => $value) {
|
||||
$key = $attrib;
|
||||
|
||||
if (in_array($attrib, $valid_attributes, true)) {
|
||||
// support both class and classes.
|
||||
if ($attrib === 'classes') {
|
||||
$attrib = 'class';
|
||||
}
|
||||
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
|
||||
unset($actions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
// If no query elements left, unset query.
|
||||
if (empty($url_parts['query'])) {
|
||||
unset ($url_parts['query']);
|
||||
}
|
||||
|
||||
// Set path to / if not set.
|
||||
if (empty($url_parts['path'])) {
|
||||
$url_parts['path'] = '';
|
||||
}
|
||||
|
||||
// If scheme isn't http(s)..
|
||||
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
|
||||
// Handle custom streams.
|
||||
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
|
||||
$url_parts['path'] = Grav::instance()['base_url_relative'] . '/' . static::resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
|
||||
unset($url_parts['stream'], $url_parts['scheme']);
|
||||
}
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// Handle paths and such.
|
||||
$url_parts = Uri::convertUrl($page, $url_parts, $type);
|
||||
|
||||
// Build the URL from the component parts and set it on the element.
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
return $excerpts->processLinkExcerpt($excerpt, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an image excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @param PageInterface $page
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @return array
|
||||
*/
|
||||
public static function processImageExcerpt(array $excerpt, PageInterface $page)
|
||||
public static function processImageExcerpt(array $excerpt, PageInterface $page = null)
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
|
||||
$url_parts = static::parseUrl($url);
|
||||
$excerpts = new ExcerptsObject($page);
|
||||
|
||||
$media = null;
|
||||
$filename = null;
|
||||
|
||||
if (!empty($url_parts['stream'])) {
|
||||
$filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
|
||||
|
||||
$media = $page->getMedia();
|
||||
|
||||
} else {
|
||||
$grav = Grav::instance();
|
||||
|
||||
// File is also local if scheme is http(s) and host matches.
|
||||
$local_file = isset($url_parts['path'])
|
||||
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
|
||||
&& (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host());
|
||||
|
||||
if ($local_file) {
|
||||
$filename = basename($url_parts['path']);
|
||||
$folder = dirname($url_parts['path']);
|
||||
|
||||
// Get the local path to page media if possible.
|
||||
if ($folder === $page->url(false, false, false)) {
|
||||
// Get the media objects for this page.
|
||||
$media = $page->getMedia();
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$base_url = rtrim($grav['base_url_relative'] . $grav['pages']->base(), '/');
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
|
||||
|
||||
/** @var PageInterface $ext_page */
|
||||
$ext_page = $grav['pages']->dispatch($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->getMedia();
|
||||
} else {
|
||||
$grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a media file that matches the path referenced..
|
||||
if ($media && $filename && isset($media[$filename])) {
|
||||
// Get the medium object.
|
||||
/** @var Medium $medium */
|
||||
$medium = $media[$filename];
|
||||
|
||||
// Process operations
|
||||
$medium = static::processMediaActions($medium, $url_parts);
|
||||
$element_excerpt = $excerpt['element']['attributes'];
|
||||
|
||||
$alt = $element_excerpt['alt'] ?? '';
|
||||
$title = $element_excerpt['title'] ?? '';
|
||||
$class = $element_excerpt['class'] ?? '';
|
||||
$id = $element_excerpt['id'] ?? '';
|
||||
|
||||
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
|
||||
|
||||
} else {
|
||||
// Not a current page media file, see if it needs converting to relative.
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
return $excerpts->processImageExcerpt($excerpt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,104 +138,13 @@ class Excerpts
|
||||
*
|
||||
* @param Medium $medium
|
||||
* @param string|array $url
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @return Medium
|
||||
*/
|
||||
public static function processMediaActions($medium, $url)
|
||||
public static function processMediaActions($medium, $url, PageInterface $page = null)
|
||||
{
|
||||
if (!is_array($url)) {
|
||||
$url_parts = parse_url($url);
|
||||
} else {
|
||||
$url_parts = $url;
|
||||
}
|
||||
$excerpts = new ExcerptsObject($page);
|
||||
|
||||
$actions = [];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = $parts[1] ?? null;
|
||||
$carry[] = ['method' => $parts[0], 'params' => $value];
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
}
|
||||
|
||||
if (Grav::instance()['config']->get('system.images.auto_fix_orientation')) {
|
||||
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
|
||||
}
|
||||
$defaults = Grav::instance()['config']->get('system.images.defaults');
|
||||
if (is_array($defaults) && count($defaults)) {
|
||||
foreach ($defaults as $method => $params) {
|
||||
$actions[] = [
|
||||
'method' => $method,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
|
||||
$args = [explode(',', $matches[1])];
|
||||
} else {
|
||||
$args = explode(',', $action['params']);
|
||||
}
|
||||
|
||||
$medium = call_user_func_array([$medium, $action['method']], $args);
|
||||
}
|
||||
|
||||
if (isset($url_parts['fragment'])) {
|
||||
$medium->urlHash($url_parts['fragment']);
|
||||
}
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variation of parse_url() which works also with local streams.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array|bool
|
||||
*/
|
||||
protected static function parseUrl($url)
|
||||
{
|
||||
$url_parts = Utils::multibyteParseUrl($url);
|
||||
|
||||
if (isset($url_parts['scheme'])) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
// Special handling for the streams.
|
||||
if ($locator->schemeExists($url_parts['scheme'])) {
|
||||
if (isset($url_parts['host'])) {
|
||||
// Merge host and path into a path.
|
||||
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
|
||||
unset($url_parts['host']);
|
||||
}
|
||||
|
||||
$url_parts['stream'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $url_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return bool|string
|
||||
*/
|
||||
protected static function resolveStream($url)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($locator->isStream($url)) {
|
||||
return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
|
||||
}
|
||||
|
||||
return $url;
|
||||
return $excerpts->processMediaActions($medium, $url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ class Truncator {
|
||||
* Clean extra code
|
||||
*
|
||||
* @param DOMDocument $doc
|
||||
* @param $container
|
||||
* @param DOMDocument $container
|
||||
* @return string
|
||||
*/
|
||||
private static function getCleanedHTML(DOMDocument $doc, $container)
|
||||
@@ -203,8 +203,7 @@ class Truncator {
|
||||
$doc->appendChild($container->firstChild);
|
||||
}
|
||||
|
||||
$html = trim($doc->saveHTML());
|
||||
return $html;
|
||||
return trim($doc->saveHTML());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,7 +233,7 @@ class Truncator {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
*/
|
||||
public function truncate(
|
||||
$text,
|
||||
@@ -242,17 +241,20 @@ class Truncator {
|
||||
$ending = '...',
|
||||
$exact = false,
|
||||
$considerHtml = true
|
||||
) {
|
||||
)
|
||||
{
|
||||
if ($considerHtml) {
|
||||
// if the plain text is shorter than the maximum length, return the whole text
|
||||
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
// splits all html-tags to scanable lines
|
||||
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
|
||||
$total_length = strlen($ending);
|
||||
$open_tags = array();
|
||||
$truncate = '';
|
||||
$open_tags = [];
|
||||
|
||||
foreach ($lines as $line_matchings) {
|
||||
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
|
||||
if (!empty($line_matchings[1])) {
|
||||
@@ -308,22 +310,22 @@ class Truncator {
|
||||
} else {
|
||||
if (strlen($text) <= $length) {
|
||||
return $text;
|
||||
} else {
|
||||
$truncate = substr($text, 0, $length - strlen($ending));
|
||||
}
|
||||
|
||||
$truncate = substr($text, 0, $length - strlen($ending));
|
||||
}
|
||||
// if the words shouldn't be cut in the middle...
|
||||
if (!$exact) {
|
||||
// ...search the last occurance of a space...
|
||||
$spacepos = strrpos($truncate, ' ');
|
||||
if (isset($spacepos)) {
|
||||
if (false !== $spacepos) {
|
||||
// ...and cut the text in this position
|
||||
$truncate = substr($truncate, 0, $spacepos);
|
||||
}
|
||||
}
|
||||
// add the defined ending to the text
|
||||
$truncate .= $ending;
|
||||
if($considerHtml) {
|
||||
if (isset($open_tags)) {
|
||||
// close all unclosed html-tags
|
||||
foreach ($open_tags as $tag) {
|
||||
$truncate .= '</' . $tag . '>';
|
||||
|
||||
@@ -16,12 +16,15 @@ use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class YamlLinter
|
||||
{
|
||||
public static function lint()
|
||||
public static function lint(string $folder = null)
|
||||
{
|
||||
$errors = static::lintConfig();
|
||||
$errors = $errors + static::lintPages();
|
||||
if (null !== $folder) {
|
||||
$folder = $folder ?: GRAV_ROOT;
|
||||
|
||||
return $errors;
|
||||
return static::recurseFolder($folder);
|
||||
}
|
||||
|
||||
return array_merge(static::lintConfig(), static::lintPages(), static::lintBlueprints());
|
||||
}
|
||||
|
||||
public static function lintPages()
|
||||
@@ -34,7 +37,19 @@ class YamlLinter
|
||||
return static::recurseFolder('config://');
|
||||
}
|
||||
|
||||
public static function recurseFolder($path, $extensions = 'md|yaml')
|
||||
public static function lintBlueprints()
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
$current_theme = Grav::instance()['config']->get('system.pages.theme');
|
||||
$theme_path = 'themes://' . $current_theme . '/blueprints';
|
||||
|
||||
$locator->addPath('blueprints', '', [$theme_path]);
|
||||
return static::recurseFolder('blueprints://');
|
||||
}
|
||||
|
||||
public static function recurseFolder($path, $extensions = '(md|yaml)')
|
||||
{
|
||||
$lint_errors = [];
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Language\Language;
|
||||
|
||||
/**
|
||||
* This file was originally part of the Akelos Framework
|
||||
*/
|
||||
@@ -24,6 +26,7 @@ class Inflector
|
||||
public static function init()
|
||||
{
|
||||
if (empty(static::$plural)) {
|
||||
/** @var Language $language */
|
||||
$language = Grav::instance()['language'];
|
||||
static::$plural = $language->translate('GRAV.INFLECTOR_PLURALS', null, true) ?: [];
|
||||
static::$singular = $language->translate('GRAV.INFLECTOR_SINGULAR', null, true) ?: [];
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Grav\Common\Language;
|
||||
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Config;
|
||||
use Negotiation\AcceptLanguage;
|
||||
@@ -16,20 +17,22 @@ use Negotiation\LanguageNegotiator;
|
||||
|
||||
class Language
|
||||
{
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
protected $enabled = true;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $languages = [];
|
||||
protected $page_extensions = [];
|
||||
protected $fallback_languages = [];
|
||||
protected $default;
|
||||
protected $active = null;
|
||||
|
||||
/** @var Config $config */
|
||||
/** @var Config */
|
||||
protected $config;
|
||||
|
||||
protected $enabled = true;
|
||||
|
||||
/** @var array */
|
||||
protected $languages = [];
|
||||
protected $fallback_languages = [];
|
||||
protected $fallback_extensions = [];
|
||||
protected $page_extesions = [];
|
||||
protected $default;
|
||||
protected $active;
|
||||
|
||||
protected $http_accept_language;
|
||||
protected $lang_in_url = false;
|
||||
|
||||
@@ -42,7 +45,12 @@ class Language
|
||||
{
|
||||
$this->grav = $grav;
|
||||
$this->config = $grav['config'];
|
||||
$this->languages = $this->config->get('system.languages.supported', []);
|
||||
$languages = $this->config->get('system.languages.supported', []);
|
||||
foreach ($languages as &$language) {
|
||||
$language = (string)$language;
|
||||
}
|
||||
$this->languages = $languages;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
@@ -58,7 +66,7 @@ class Language
|
||||
$this->default = reset($this->languages);
|
||||
}
|
||||
|
||||
$this->page_extensions = null;
|
||||
$this->resetFallbackPageExtensions();
|
||||
|
||||
if (empty($this->languages)) {
|
||||
$this->enabled = false;
|
||||
@@ -93,17 +101,24 @@ class Language
|
||||
public function setLanguages($langs)
|
||||
{
|
||||
$this->languages = $langs;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pipe-separated string of available languages
|
||||
*
|
||||
* @param string|null $delimiter Delimiter to be quoted.
|
||||
* @return string
|
||||
*/
|
||||
public function getAvailable()
|
||||
public function getAvailable($delimiter = null)
|
||||
{
|
||||
$languagesArray = $this->languages; //Make local copy
|
||||
|
||||
$languagesArray = array_map(function($value) use ($delimiter) {
|
||||
return preg_quote($value, $delimiter);
|
||||
}, $languagesArray);
|
||||
|
||||
sort($languagesArray);
|
||||
|
||||
return implode('|', array_reverse($languagesArray));
|
||||
@@ -138,6 +153,7 @@ class Language
|
||||
*/
|
||||
public function setDefault($lang)
|
||||
{
|
||||
$lang = (string)$lang;
|
||||
if ($this->validate($lang)) {
|
||||
$this->default = $lang;
|
||||
|
||||
@@ -166,7 +182,12 @@ class Language
|
||||
*/
|
||||
public function setActive($lang)
|
||||
{
|
||||
$lang = (string)$lang;
|
||||
if ($this->validate($lang)) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addMessage('Active language set to ' . $lang, 'debug');
|
||||
|
||||
$this->active = $lang;
|
||||
|
||||
return $lang;
|
||||
@@ -191,7 +212,7 @@ class Language
|
||||
// Try setting language from prefix of URL (/en/blah/blah).
|
||||
if (preg_match($regex, $uri, $matches)) {
|
||||
$this->lang_in_url = true;
|
||||
$this->active = $matches[2];
|
||||
$this->setActive($matches[2]);
|
||||
$uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
|
||||
|
||||
// Store in session if language is different.
|
||||
@@ -205,7 +226,7 @@ class Language
|
||||
// Try getting language from the session, else no active.
|
||||
if (isset($this->grav['session']) && $this->grav['session']->isStarted() &&
|
||||
$this->config->get('system.languages.session_store_active', true)) {
|
||||
$this->active = $this->grav['session']->active_language ?: null;
|
||||
$this->setActive($this->grav['session']->active_language ?: null);
|
||||
}
|
||||
// if still null, try from http_accept_language header
|
||||
if ($this->active === null &&
|
||||
@@ -216,9 +237,9 @@ class Language
|
||||
$best_language = $negotiator->getBest($accept, $this->languages);
|
||||
|
||||
if ($best_language instanceof AcceptLanguage) {
|
||||
$this->active = $best_language->getType();
|
||||
$this->setActive($best_language->getType());
|
||||
} else {
|
||||
$this->active = $this->getDefault();
|
||||
$this->setActive($this->getDefault());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -229,7 +250,7 @@ class Language
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's a URL prefix based on configuration
|
||||
* Get a URL prefix based on configuration
|
||||
*
|
||||
* @param string|null $lang
|
||||
* @return string
|
||||
@@ -270,52 +291,67 @@ class Language
|
||||
return (bool) $this->lang_in_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full list of used language page extensions: [''=>'.md', 'en'=>'.en.md', ...]
|
||||
*
|
||||
* @param string|null $fileExtension
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPageExtensions($fileExtension = null)
|
||||
{
|
||||
$fileExtension = $fileExtension ?: CONTENT_EXT;
|
||||
|
||||
if (!isset($this->fallback_extensions[$fileExtension])) {
|
||||
$extensions[''] = $fileExtension;
|
||||
foreach ($this->languages as $code) {
|
||||
$extensions[$code] = ".{$code}{$fileExtension}";
|
||||
}
|
||||
|
||||
$this->fallback_extensions[$fileExtension] = $extensions;
|
||||
}
|
||||
|
||||
return $this->fallback_extensions[$fileExtension];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of valid extensions with active first, then fallback extensions
|
||||
*
|
||||
* @param string|null $file_ext
|
||||
*
|
||||
* @return array
|
||||
* @param string|null $fileExtension
|
||||
* @param string|null $languageCode
|
||||
* @param bool $assoc Return values in ['en' => '.en.md', ...] format.
|
||||
* @return array Key is the language code, value is the file extension to be used.
|
||||
*/
|
||||
public function getFallbackPageExtensions($file_ext = null)
|
||||
public function getFallbackPageExtensions(string $fileExtension = null, string $languageCode = null, bool $assoc = false)
|
||||
{
|
||||
if (empty($this->page_extensions)) {
|
||||
if (!$file_ext) {
|
||||
$file_ext = CONTENT_EXT;
|
||||
$fileExtension = $fileExtension ?: CONTENT_EXT;
|
||||
$key = $fileExtension . '-' . ($languageCode ?? 'default') . '-' . (int)$assoc;
|
||||
|
||||
if (!isset($this->fallback_extensions[$key])) {
|
||||
$all = $this->getPageExtensions($fileExtension);
|
||||
$list = [];
|
||||
$fallback = $this->getFallbackLanguages($languageCode, true);
|
||||
foreach ($fallback as $code) {
|
||||
$ext = $all[$code] ?? null;
|
||||
if (null !== $ext) {
|
||||
$list[$code] = $ext;
|
||||
}
|
||||
}
|
||||
if (!$assoc) {
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
if ($this->enabled()) {
|
||||
$valid_lang_extensions = [];
|
||||
foreach ($this->languages as $lang) {
|
||||
$valid_lang_extensions[] = '.' . $lang . $file_ext;
|
||||
}
|
||||
$this->fallback_extensions[$key] = $list;
|
||||
|
||||
if ($this->active) {
|
||||
$active_extension = '.' . $this->active . $file_ext;
|
||||
$key = \array_search($active_extension, $valid_lang_extensions, true);
|
||||
|
||||
// Default behavior is to find any language other than active
|
||||
if ($this->config->get('system.languages.pages_fallback_only')) {
|
||||
$slice = \array_slice($valid_lang_extensions, 0, $key+1);
|
||||
$valid_lang_extensions = array_reverse($slice);
|
||||
} else {
|
||||
unset($valid_lang_extensions[$key]);
|
||||
array_unshift($valid_lang_extensions, $active_extension);
|
||||
}
|
||||
}
|
||||
$valid_lang_extensions[] = $file_ext;
|
||||
$this->page_extensions = $valid_lang_extensions;
|
||||
} else {
|
||||
$this->page_extensions = (array)$file_ext;
|
||||
}
|
||||
/** @var Debugger $debugger */
|
||||
//$debugger = $this->grav['debugger'];
|
||||
//$debugger->addMessage("Language fallback extensions for {$languageCode}", 'debug', $list);
|
||||
}
|
||||
|
||||
return $this->page_extensions;
|
||||
return $this->fallback_extensions[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the page_extensions value.
|
||||
* Resets the fallback_languages value.
|
||||
*
|
||||
* Useful to re-initialize the pages and change site language at runtime, example:
|
||||
*
|
||||
@@ -327,33 +363,77 @@ class Language
|
||||
*/
|
||||
public function resetFallbackPageExtensions()
|
||||
{
|
||||
$this->page_extensions = null;
|
||||
$this->fallback_languages = [];
|
||||
$this->fallback_extensions = [];
|
||||
$this->page_extesions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of languages with active first, then fallback languages
|
||||
* Gets an array of languages with active first, then fallback languages.
|
||||
*
|
||||
*
|
||||
* @param string|null $languageCode
|
||||
* @param bool $includeDefault If true, list contains '', which can be used for default
|
||||
* @return array
|
||||
*/
|
||||
public function getFallbackLanguages()
|
||||
public function getFallbackLanguages(string $languageCode = null, bool $includeDefault = false)
|
||||
{
|
||||
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, true);
|
||||
unset($fallback_languages[$key]);
|
||||
array_unshift($fallback_languages, $active_extension);
|
||||
}
|
||||
$this->fallback_languages = $fallback_languages;
|
||||
}
|
||||
// always add english in case a translation doesn't exist
|
||||
$this->fallback_languages[] = 'en';
|
||||
// Handle default.
|
||||
if ($languageCode === '' || !$this->enabled()) {
|
||||
return [''];
|
||||
}
|
||||
|
||||
return $this->fallback_languages;
|
||||
$default = $this->getDefault() ?? 'en';
|
||||
$active = $languageCode ?? $this->getActive() ?? $default;
|
||||
$key = $active . '-' . (int)$includeDefault;
|
||||
|
||||
if (!isset($this->fallback_languages[$key])) {
|
||||
$fallback = $this->config->get('system.languages.content_fallback.' . $active);
|
||||
$fallback_languages = [];
|
||||
|
||||
if (null === $fallback && $this->config->get('system.languages.pages_fallback_only', false)) {
|
||||
// Special fallback list returns itself and all the previous items in reverse order:
|
||||
// active: 'v2', languages: ['v1', 'v2', 'v3', 'v4'] => ['v2', 'v1', '']
|
||||
if ($includeDefault) {
|
||||
$fallback_languages[''] = '';
|
||||
}
|
||||
foreach ($this->languages as $code) {
|
||||
$fallback_languages[$code] = $code;
|
||||
if ($code === $active) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fallback_languages = array_reverse($fallback_languages);
|
||||
|
||||
} else {
|
||||
if (null === $fallback) {
|
||||
$fallback = [$default];
|
||||
} elseif (!is_array($fallback)) {
|
||||
$fallback = is_string($fallback) && $fallback !== '' ? explode(',', $fallback) : [];
|
||||
}
|
||||
array_unshift($fallback, $active);
|
||||
$fallback = array_unique($fallback);
|
||||
|
||||
foreach ($fallback as $code) {
|
||||
// Default fallback list has active language followed by default language and extensionless file:
|
||||
// active: 'fi', default: 'en', languages: ['sv', 'en', 'de', 'fi'] => ['fi', 'en', '']
|
||||
$fallback_languages[$code] = $code;
|
||||
if ($includeDefault && $code === $default) {
|
||||
$fallback_languages[''] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fallback_languages = array_values($fallback_languages);
|
||||
|
||||
$this->fallback_languages[$key] = $fallback_languages;
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
//$debugger = $this->grav['debugger'];
|
||||
//$debugger->addMessage("Language fallback for {$active}", 'debug', $fallback_languages);
|
||||
}
|
||||
|
||||
return $this->fallback_languages[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,18 +469,12 @@ class Language
|
||||
}
|
||||
|
||||
if ($this->config->get('system.languages.translations', true)) {
|
||||
if ($this->enabled() && $lookup) {
|
||||
if (empty($languages)) {
|
||||
if ($this->config->get('system.languages.translations_fallback', true)) {
|
||||
$languages = $this->getFallbackLanguages();
|
||||
} else {
|
||||
$languages = (array)$this->getLanguage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$languages = ['en'];
|
||||
if ($this->enabled() && $lookup && empty($languages)) {
|
||||
$languages = $this->getTranslatedLanguages();
|
||||
}
|
||||
|
||||
$languages = $languages ?: ['en'];
|
||||
|
||||
foreach ((array)$languages as $lang) {
|
||||
$translation = $this->getTranslation($lang, $lookup, $array_support);
|
||||
|
||||
@@ -434,18 +508,12 @@ class Language
|
||||
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'];
|
||||
if ($this->enabled() && $key && empty($languages)) {
|
||||
$languages = $this->getTranslatedLanguages();
|
||||
}
|
||||
|
||||
$languages = $languages ?: ['en'];
|
||||
|
||||
foreach ((array)$languages as $lang) {
|
||||
$translation_array = (array)Grav::instance()['languages']->get($lang . '.' . $key, null);
|
||||
if ($translation_array && array_key_exists($index, $translation_array)) {
|
||||
@@ -529,4 +597,27 @@ class Language
|
||||
return LanguageCodes::get($code, $type);
|
||||
}
|
||||
|
||||
public function __debugInfo()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['grav'], $vars['config']);
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getTranslatedLanguages(): array
|
||||
{
|
||||
if ($this->config->get('system.languages.translations_fallback', true)) {
|
||||
$languages = $this->getFallbackLanguages();
|
||||
} else {
|
||||
$languages = [$this->getLanguage()];
|
||||
}
|
||||
|
||||
$languages[] = 'en';
|
||||
|
||||
return array_values(array_unique($languages));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,4 +202,13 @@ class LanguageCodes
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getList($native = true)
|
||||
{
|
||||
$list = [];
|
||||
foreach (static::$codes as $key => $names) {
|
||||
$list[$key] = $native ? $names['nativeName'] : $names['name'];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
|
||||
class Parsedown extends \Parsedown
|
||||
{
|
||||
@@ -18,12 +19,21 @@ class Parsedown extends \Parsedown
|
||||
/**
|
||||
* Parsedown constructor.
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @param Excerpts|null $excerpts
|
||||
* @param array|null $defaults
|
||||
*/
|
||||
public function __construct($page, $defaults)
|
||||
public function __construct($excerpts = null, $defaults = null)
|
||||
{
|
||||
$this->init($page, $defaults);
|
||||
if (!$excerpts || $excerpts instanceof PageInterface || null !== $defaults) {
|
||||
// Deprecated in Grav 1.6.10
|
||||
if ($defaults) {
|
||||
$defaults = ['markdown' => $defaults];
|
||||
}
|
||||
$excerpts = new Excerpts($excerpts, $defaults);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->init($excerpts, $defaults);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
|
||||
class ParsedownExtra extends \ParsedownExtra
|
||||
{
|
||||
@@ -18,14 +19,23 @@ class ParsedownExtra extends \ParsedownExtra
|
||||
/**
|
||||
* ParsedownExtra constructor.
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @param Excerpts|null $excerpts
|
||||
* @param array|null $defaults
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($page, $defaults)
|
||||
public function __construct($excerpts = null, $defaults = null)
|
||||
{
|
||||
if (!$excerpts || $excerpts instanceof PageInterface || null !== $defaults) {
|
||||
// Deprecated in Grav 1.6.10
|
||||
if ($defaults) {
|
||||
$defaults = ['markdown' => $defaults];
|
||||
}
|
||||
$excerpts = new Excerpts($excerpts, $defaults);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->init($page, $defaults);
|
||||
$this->init($excerpts, $defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,13 @@
|
||||
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\Excerpts;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
trait ParsedownGravTrait
|
||||
{
|
||||
/** @var PageInterface $page */
|
||||
protected $page;
|
||||
/** @var Excerpts */
|
||||
protected $excerpts;
|
||||
|
||||
protected $special_chars;
|
||||
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
|
||||
@@ -28,28 +26,49 @@ trait ParsedownGravTrait
|
||||
/**
|
||||
* Initialization function to setup key variables needed by the MarkdownGravLinkTrait
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @param PageInterface|Excerpts|null $excerpts
|
||||
* @param array|null $defaults
|
||||
*/
|
||||
protected function init($page, $defaults)
|
||||
protected function init($excerpts = null, $defaults = null)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->page = $page;
|
||||
$this->BlockTypes['{'] [] = 'TwigTag';
|
||||
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
|
||||
|
||||
if ($defaults === null) {
|
||||
$defaults = (array)Grav::instance()['config']->get('system.pages.markdown');
|
||||
if (!$excerpts || $excerpts instanceof PageInterface) {
|
||||
// Deprecated in Grav 1.6.10
|
||||
if ($defaults) {
|
||||
$defaults = ['markdown' => $defaults];
|
||||
}
|
||||
$this->excerpts = new Excerpts($excerpts, $defaults);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use ->init(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
|
||||
} else {
|
||||
$this->excerpts = $excerpts;
|
||||
}
|
||||
|
||||
$this->setBreaksEnabled($defaults['auto_line_breaks']);
|
||||
$this->setUrlsLinked($defaults['auto_url_links']);
|
||||
$this->setMarkupEscaped($defaults['escape_markup']);
|
||||
$this->setSpecialChars($defaults['special_chars']);
|
||||
$this->BlockTypes['{'][] = 'TwigTag';
|
||||
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
|
||||
|
||||
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this, 'page' => $page]));
|
||||
$defaults = $this->excerpts->getConfig();
|
||||
|
||||
if (isset($defaults['markdown']['auto_line_breaks'])) {
|
||||
$this->setBreaksEnabled($defaults['markdown']['auto_line_breaks']);
|
||||
}
|
||||
if (isset($defaults['markdown']['auto_url_links'])) {
|
||||
$this->setUrlsLinked($defaults['markdown']['auto_url_links']);
|
||||
}
|
||||
if (isset($defaults['markdown']['escape_markup'])) {
|
||||
$this->setMarkupEscaped($defaults['markdown']['escape_markup']);
|
||||
}
|
||||
if (isset($defaults['markdown']['special_chars'])) {
|
||||
$this->setSpecialChars($defaults['markdown']['special_chars']);
|
||||
}
|
||||
|
||||
$this->excerpts->fireInitializedEvent($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Excerpts
|
||||
*/
|
||||
public function getExcerpts()
|
||||
{
|
||||
return $this->excerpts;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +133,8 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
protected function isBlockContinuable($Type)
|
||||
{
|
||||
$continuable = \in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue');
|
||||
$continuable = \in_array($Type, $this->continuable_blocks, true)
|
||||
|| method_exists($this, 'block' . $Type . 'Continue');
|
||||
|
||||
return $continuable;
|
||||
}
|
||||
@@ -128,7 +148,8 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
protected function isBlockCompletable($Type)
|
||||
{
|
||||
$completable = \in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete');
|
||||
$completable = \in_array($Type, $this->completable_blocks, true)
|
||||
|| method_exists($this, 'block' . $Type . 'Complete');
|
||||
|
||||
return $completable;
|
||||
}
|
||||
@@ -210,7 +231,7 @@ trait ParsedownGravTrait
|
||||
|
||||
// if this is an image process it
|
||||
if (isset($excerpt['element']['attributes']['src'])) {
|
||||
$excerpt = Excerpts::processImageExcerpt($excerpt, $this->page);
|
||||
$excerpt = $this->excerpts->processImageExcerpt($excerpt);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
@@ -218,11 +239,7 @@ trait ParsedownGravTrait
|
||||
|
||||
protected function inlineLink($excerpt)
|
||||
{
|
||||
if (isset($excerpt['type'])) {
|
||||
$type = $excerpt['type'];
|
||||
} else {
|
||||
$type = 'link';
|
||||
}
|
||||
$type = $excerpt['type'] ?? 'link';
|
||||
|
||||
// do some trickery to get around Parsedown requirement for valid URL if its Twig in there
|
||||
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
|
||||
@@ -238,13 +255,15 @@ trait ParsedownGravTrait
|
||||
|
||||
// if this is a link
|
||||
if (isset($excerpt['element']['attributes']['href'])) {
|
||||
$excerpt = Excerpts::processLinkExcerpt($excerpt, $this->page, $type);
|
||||
$excerpt = $this->excerpts->processLinkExcerpt($excerpt, $type);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// For extending this class via plugins
|
||||
/**
|
||||
* For extending this class via plugins
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (isset($this->{$method}) === true) {
|
||||
|
||||
@@ -19,6 +19,7 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
trait MediaTrait
|
||||
{
|
||||
protected $media;
|
||||
protected $_loadMedia = true;
|
||||
|
||||
/**
|
||||
* Get filesystem path to the associated media.
|
||||
@@ -40,11 +41,14 @@ trait MediaTrait
|
||||
/**
|
||||
* Get URI ot the associated media. Method will return null if path isn't URI.
|
||||
*
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMediaUri()
|
||||
{
|
||||
$folder = $this->getMediaFolder();
|
||||
if (!$folder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strpos($folder, '://')) {
|
||||
return $folder;
|
||||
@@ -73,7 +77,7 @@ trait MediaTrait
|
||||
// Use cached media if possible.
|
||||
$cacheKey = md5('media' . $this->getCacheKey());
|
||||
if (!$media = $cache->get($cacheKey)) {
|
||||
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
|
||||
$media = new Media($this->getMediaFolder(), $this->getMediaOrder(), $this->_loadMedia);
|
||||
$cache->set($cacheKey, $media);
|
||||
}
|
||||
$this->media = $media;
|
||||
|
||||
@@ -11,10 +11,11 @@ namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
class Collection extends Iterator
|
||||
class Collection extends Iterator implements PageCollectionInterface
|
||||
{
|
||||
/**
|
||||
* @var Pages
|
||||
@@ -51,6 +52,20 @@ class Collection extends Iterator
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
@@ -94,10 +109,10 @@ class Collection extends Iterator
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(Collection $collection)
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
foreach($collection as $page) {
|
||||
$this->addPage($page);
|
||||
@@ -109,10 +124,10 @@ class Collection extends Iterator
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function intersect(Collection $collection)
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
$array1 = $this->items;
|
||||
$array2 = $collection->toArray();
|
||||
@@ -124,20 +139,6 @@ class Collection extends Iterator
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current page.
|
||||
*
|
||||
@@ -240,7 +241,7 @@ class Collection extends Iterator
|
||||
*
|
||||
* @return bool True if item is first.
|
||||
*/
|
||||
public function isFirst($path)
|
||||
public function isFirst($path): bool
|
||||
{
|
||||
return $this->items && $path === array_keys($this->items)[0];
|
||||
}
|
||||
@@ -252,7 +253,7 @@ class Collection extends Iterator
|
||||
*
|
||||
* @return bool True if item is last.
|
||||
*/
|
||||
public function isLast($path)
|
||||
public function isLast($path): bool
|
||||
{
|
||||
return $this->items && $path === array_keys($this->items)[\count($this->items) - 1];
|
||||
}
|
||||
@@ -301,7 +302,6 @@ class Collection extends Iterator
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,11 +309,13 @@ class Collection extends Iterator
|
||||
*
|
||||
* @param string $path the path the item
|
||||
*
|
||||
* @return int the index of the current page.
|
||||
* @return int|null The index of the current page, null if not found.
|
||||
*/
|
||||
public function currentPosition($path)
|
||||
public function currentPosition($path): ?int
|
||||
{
|
||||
return \array_search($path, \array_keys($this->items), true);
|
||||
$pos = \array_search($path, \array_keys($this->items), true);
|
||||
|
||||
return $pos !== false ? $pos : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,9 +10,18 @@
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
|
||||
|
||||
class Header implements \ArrayAccess
|
||||
class Header implements \ArrayAccess, ExportInterface, \JsonSerializable
|
||||
{
|
||||
use NestedArrayAccess, Constructor;
|
||||
use NestedArrayAccessWithGetters, Constructor, Export;
|
||||
|
||||
protected $items;
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
interface PageCollectionInterface extends \Traversable, \ArrayAccess, \Countable, \Serializable
|
||||
{
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function params();
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params);
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
* @param PageInterface $page
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addPage(PageInterface $page);
|
||||
|
||||
/**
|
||||
* Add a page with path and slug
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $slug
|
||||
* @return $this
|
||||
*/
|
||||
//public function add($path, $slug);
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a copy of this collection
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy();
|
||||
|
||||
/**
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection);
|
||||
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection);
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param int $size
|
||||
* @return PageCollectionInterface[]
|
||||
*/
|
||||
public function batch($size);
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
* @param PageInterface|string|null $key
|
||||
*
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
//public function remove($key = null);
|
||||
|
||||
/**
|
||||
* Reorder collection.
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @param string $sort_flags
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null);
|
||||
|
||||
/**
|
||||
* Check to see if this item is the first in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is first.
|
||||
*/
|
||||
public function isFirst($path): bool;
|
||||
|
||||
/**
|
||||
* Check to see if this item is the last in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is last.
|
||||
*/
|
||||
public function isLast($path): bool;
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface The previous item.
|
||||
*/
|
||||
public function prevSibling($path);
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface The next item.
|
||||
*/
|
||||
public function nextSibling($path);
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $direction either -1 or +1
|
||||
*
|
||||
* @return PageInterface|PageCollectionInterface The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1);
|
||||
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @param string $path the path the item
|
||||
*
|
||||
* @return int|null The index of the current page, null if not found.
|
||||
*/
|
||||
public function currentPosition($path): ?int;
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges of either the page date field (default) or
|
||||
* an arbitrary datetime page field where end date is optional
|
||||
* Dates can be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param string $startDate
|
||||
* @param bool $endDate
|
||||
* @param string|null $field
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false, $field = null);
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only visible pages
|
||||
*/
|
||||
public function visible();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible();
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only modular pages
|
||||
*/
|
||||
public function modular();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-modular pages
|
||||
*/
|
||||
public function nonModular();
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only published pages
|
||||
*/
|
||||
public function published();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished();
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only routable pages
|
||||
*/
|
||||
public function routable();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable();
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of the specified type
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofType($type);
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified types
|
||||
*
|
||||
* @param string[] $types
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseTypes($types);
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified access levels
|
||||
*
|
||||
* @param array $accessLevels
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseAccessLevels($accessLevels);
|
||||
|
||||
/**
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function toExtendedArray();
|
||||
}
|
||||
@@ -36,6 +36,13 @@ interface PageContentInterface
|
||||
*/
|
||||
public function summary($size = null, $textOnly = false);
|
||||
|
||||
/**
|
||||
* Sets the summary of the page
|
||||
*
|
||||
* @param string $summary Summary
|
||||
*/
|
||||
public function setSummary($summary);
|
||||
|
||||
/**
|
||||
* Gets and Sets the content based on content portion of the .md file
|
||||
*
|
||||
@@ -64,7 +71,7 @@ interface PageContentInterface
|
||||
*
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null
|
||||
* @return string
|
||||
*/
|
||||
public function rawMarkdown($var = null);
|
||||
|
||||
@@ -167,7 +174,7 @@ interface PageContentInterface
|
||||
*
|
||||
* @param int $var
|
||||
*
|
||||
* @return int|bool
|
||||
* @return string|bool
|
||||
*/
|
||||
public function order($var = null);
|
||||
|
||||
|
||||
30
system/src/Grav/Common/Page/Interfaces/PageFormInterface.php
Normal file
30
system/src/Grav/Common/Page/Interfaces/PageFormInterface.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
interface PageFormInterface
|
||||
{
|
||||
/**
|
||||
* Return all the forms which are associated to this page.
|
||||
*
|
||||
* Forms are returned as [name => blueprint, ...], where blueprint follows the regular form blueprint format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
//public function getForms(): array;
|
||||
|
||||
/**
|
||||
* Add forms to this page.
|
||||
*
|
||||
* @param array $new
|
||||
* @param bool $override
|
||||
* @return $this
|
||||
*/
|
||||
public function addForms(array $new/*, $override = true*/);
|
||||
|
||||
/**
|
||||
* Alias of $this->getForms();
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms();//: array;
|
||||
}
|
||||
@@ -14,6 +14,12 @@ use Grav\Common\Media\Interfaces\MediaInterface;
|
||||
/**
|
||||
* Class implements page interface.
|
||||
*/
|
||||
interface PageInterface extends PageContentInterface, PageRoutableInterface, PageTranslateInterface, MediaInterface, PageLegacyInterface
|
||||
interface PageInterface extends
|
||||
PageContentInterface,
|
||||
PageFormInterface,
|
||||
PageRoutableInterface,
|
||||
PageTranslateInterface,
|
||||
MediaInterface,
|
||||
PageLegacyInterface
|
||||
{
|
||||
}
|
||||
|
||||
@@ -51,13 +51,6 @@ interface PageLegacyInterface
|
||||
|
||||
public function httpHeaders();
|
||||
|
||||
/**
|
||||
* Sets the summary of the page
|
||||
*
|
||||
* @param string $summary Summary
|
||||
*/
|
||||
public function setSummary($summary);
|
||||
|
||||
/**
|
||||
* Get the contentMeta array and initialize content first if it's not already
|
||||
*
|
||||
@@ -69,7 +62,7 @@ interface PageLegacyInterface
|
||||
* Add an entry to the page's contentMeta array
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addContentMeta($name, $value);
|
||||
|
||||
@@ -229,9 +222,9 @@ interface PageLegacyInterface
|
||||
* Allows a page to override the output render format, usually the extension provided
|
||||
* in the URL. (e.g. `html`, `json`, `xml`, etc).
|
||||
*
|
||||
* @param null $var
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null
|
||||
* @return string
|
||||
*/
|
||||
public function templateFormat($var = null);
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ interface PageRoutableInterface
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @return int the index of the current page.
|
||||
* @return int|null The index of the current page.
|
||||
*/
|
||||
public function currentPosition();
|
||||
|
||||
|
||||
329
system/src/Grav/Common/Page/Markdown/Excerpts.php
Normal file
329
system/src/Grav/Common/Page/Markdown/Excerpts.php
Normal file
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Markdown;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Medium\Link;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Excerpts
|
||||
{
|
||||
/** @var PageInterface */
|
||||
protected $page;
|
||||
/** @var array */
|
||||
protected $config;
|
||||
|
||||
public function __construct(PageInterface $page = null, array $config = null)
|
||||
{
|
||||
$this->page = $page ?? Grav::instance()['page'] ?? null;
|
||||
|
||||
// Add defaults to the configuration.
|
||||
if (null === $config || !isset($config['markdown'], $config['images'])) {
|
||||
$c = Grav::instance()['config'];
|
||||
$config = $config ?? [];
|
||||
$config += [
|
||||
'markdown' => $c->get('system.pages.markdown', []),
|
||||
'images' => $c->get('system.images', [])
|
||||
];
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getPage(): PageInterface
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function fireInitializedEvent($markdown): void
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $markdown, 'page' => $this->page]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a Link excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function processLinkExcerpt(array $excerpt, string $type = 'link'): array
|
||||
{
|
||||
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
|
||||
|
||||
$url_parts = $this->parseUrl($url);
|
||||
|
||||
// If there is a query, then parse it and build action calls.
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(
|
||||
explode('&', $url_parts['query']),
|
||||
static function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
|
||||
$carry[$parts[0]] = $value;
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Valid attributes supported.
|
||||
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
|
||||
|
||||
// Unless told to not process, go through actions.
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
unset($actions['noprocess']);
|
||||
} else {
|
||||
// Loop through actions for the image and call them.
|
||||
foreach ($actions as $attrib => $value) {
|
||||
$key = $attrib;
|
||||
|
||||
if (in_array($attrib, $valid_attributes, true)) {
|
||||
// support both class and classes.
|
||||
if ($attrib === 'classes') {
|
||||
$attrib = 'class';
|
||||
}
|
||||
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
|
||||
unset($actions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
// If no query elements left, unset query.
|
||||
if (empty($url_parts['query'])) {
|
||||
unset ($url_parts['query']);
|
||||
}
|
||||
|
||||
// Set path to / if not set.
|
||||
if (empty($url_parts['path'])) {
|
||||
$url_parts['path'] = '';
|
||||
}
|
||||
|
||||
// If scheme isn't http(s)..
|
||||
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
|
||||
// Handle custom streams.
|
||||
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
|
||||
$grav = Grav::instance();
|
||||
$url_parts['path'] = $grav['base_url_relative'] . '/' . $this->resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
|
||||
unset($url_parts['stream'], $url_parts['scheme']);
|
||||
}
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// Handle paths and such.
|
||||
$url_parts = Uri::convertUrl($this->page, $url_parts, $type);
|
||||
|
||||
// Build the URL from the component parts and set it on the element.
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an image excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @return array
|
||||
*/
|
||||
public function processImageExcerpt(array $excerpt): array
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
|
||||
$url_parts = $this->parseUrl($url);
|
||||
|
||||
$media = null;
|
||||
$filename = null;
|
||||
|
||||
if (!empty($url_parts['stream'])) {
|
||||
$filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
|
||||
|
||||
$media = $this->page->getMedia();
|
||||
|
||||
} else {
|
||||
$grav = Grav::instance();
|
||||
|
||||
// File is also local if scheme is http(s) and host matches.
|
||||
$local_file = isset($url_parts['path'])
|
||||
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
|
||||
&& (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host());
|
||||
|
||||
if ($local_file) {
|
||||
$filename = basename($url_parts['path']);
|
||||
$folder = dirname($url_parts['path']);
|
||||
|
||||
// Get the local path to page media if possible.
|
||||
if ($this->page && $folder === $this->page->url(false, false, false)) {
|
||||
// Get the media objects for this page.
|
||||
$media = $this->page->getMedia();
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$base_url = rtrim($grav['base_url_relative'] . $grav['pages']->base(), '/');
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
|
||||
|
||||
/** @var PageInterface $ext_page */
|
||||
$ext_page = $grav['pages']->find($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->getMedia();
|
||||
} else {
|
||||
$grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a media file that matches the path referenced..
|
||||
if ($media && $filename && isset($media[$filename])) {
|
||||
// Get the medium object.
|
||||
/** @var Medium $medium */
|
||||
$medium = $media[$filename];
|
||||
|
||||
// Process operations
|
||||
$medium = $this->processMediaActions($medium, $url_parts);
|
||||
$element_excerpt = $excerpt['element']['attributes'];
|
||||
|
||||
$alt = $element_excerpt['alt'] ?? '';
|
||||
$title = $element_excerpt['title'] ?? '';
|
||||
$class = $element_excerpt['class'] ?? '';
|
||||
$id = $element_excerpt['id'] ?? '';
|
||||
|
||||
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
|
||||
|
||||
} else {
|
||||
// Not a current page media file, see if it needs converting to relative.
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process media actions
|
||||
*
|
||||
* @param Medium $medium
|
||||
* @param string|array $url
|
||||
* @return Medium|Link
|
||||
*/
|
||||
public function processMediaActions($medium, $url)
|
||||
{
|
||||
$url_parts = is_string($url) ? $this->parseUrl($url) : $url;
|
||||
$actions = [];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(
|
||||
explode('&', $url_parts['query']),
|
||||
static function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = $parts[1] ?? null;
|
||||
$carry[] = ['method' => $parts[0], 'params' => $value];
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
$config = $this->getConfig();
|
||||
if (!empty($config['images']['auto_fix_orientation'])) {
|
||||
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
|
||||
}
|
||||
|
||||
$defaults = $config['images']['defaults'] ?? [];
|
||||
if (count($defaults)) {
|
||||
foreach ($defaults as $method => $params) {
|
||||
$actions[] = [
|
||||
'method' => $method,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
|
||||
$args = [explode(',', $matches[1])];
|
||||
} else {
|
||||
$args = explode(',', $action['params']);
|
||||
}
|
||||
|
||||
$medium = call_user_func_array([$medium, $action['method']], $args);
|
||||
}
|
||||
|
||||
if (isset($url_parts['fragment'])) {
|
||||
$medium->urlHash($url_parts['fragment']);
|
||||
}
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variation of parse_url() which works also with local streams.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function parseUrl(string $url)
|
||||
{
|
||||
$url_parts = Utils::multibyteParseUrl($url);
|
||||
|
||||
if (isset($url_parts['scheme'])) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
// Special handling for the streams.
|
||||
if ($locator->schemeExists($url_parts['scheme'])) {
|
||||
if (isset($url_parts['host'])) {
|
||||
// Merge host and path into a path.
|
||||
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
|
||||
unset($url_parts['host']);
|
||||
}
|
||||
|
||||
$url_parts['stream'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $url_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function resolveStream(string $url)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($locator->isStream($url)) {
|
||||
return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -78,21 +78,21 @@ class Media extends AbstractMedia
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$config = Grav::instance()['config'];
|
||||
$locator = Grav::instance()['locator'];
|
||||
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
|
||||
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
|
||||
$path = $this->getPath();
|
||||
|
||||
// Handle special cases where page doesn't exist in filesystem.
|
||||
if (!is_dir($this->getPath())) {
|
||||
if (!$path || !is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$iterator = new \FilesystemIterator($this->getPath(), \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
|
||||
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
|
||||
|
||||
$media = [];
|
||||
|
||||
/** @var \DirectoryIterator $info */
|
||||
foreach ($iterator as $path => $info) {
|
||||
foreach ($iterator as $file => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
|
||||
continue;
|
||||
@@ -106,9 +106,9 @@ class Media extends AbstractMedia
|
||||
}
|
||||
|
||||
if ($type === 'alternative') {
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $path, 'size' => $info->getSize()];
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $file, 'size' => $info->getSize()];
|
||||
} else {
|
||||
$media["{$basename}.{$ext}"][$type] = ['file' => $path, 'size' => $info->getSize()];
|
||||
$media["{$basename}.{$ext}"][$type] = ['file' => $file, 'size' => $info->getSize()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ class Media extends AbstractMedia
|
||||
$alt['file']->set('size', $alt['size']);
|
||||
}
|
||||
}
|
||||
unset($alt);
|
||||
}
|
||||
|
||||
$file_path = null;
|
||||
|
||||
@@ -154,6 +154,9 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
*/
|
||||
public function add($name, $file)
|
||||
{
|
||||
if (!$file) {
|
||||
return;
|
||||
}
|
||||
$this->offsetSet($name, $file);
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
|
||||
@@ -109,7 +109,7 @@ class ImageFile extends Image
|
||||
* Gets the hash.
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @param [] $extras
|
||||
* @param array $extras
|
||||
* @return null
|
||||
*/
|
||||
public function getHash($type = 'guess', $quality = 80, $extras = [])
|
||||
|
||||
@@ -170,8 +170,7 @@ class ImageMedium extends Medium
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$image_path = $locator->findResource('cache://images', true);
|
||||
$image_dir = $locator->findResource('cache://images', false);
|
||||
$image_path = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
|
||||
$saved_image_path = $this->saveImage();
|
||||
|
||||
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
|
||||
@@ -181,6 +180,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
if (Utils::startsWith($output, $image_path)) {
|
||||
$image_dir = $locator->findResource('cache://images', false);
|
||||
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the ability to override the Inmage's Pretty name stored in cache
|
||||
* Allows the ability to override the image's pretty name stored in cache
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
|
||||
trait ParsedownHtmlTrait
|
||||
{
|
||||
@@ -33,7 +34,7 @@ trait ParsedownHtmlTrait
|
||||
$element = $this->parsedownElement($title, $alt, $class, $id, $reset);
|
||||
|
||||
if (!$this->parsedown) {
|
||||
$this->parsedown = new Parsedown(null, null);
|
||||
$this->parsedown = new Parsedown(new Excerpts());
|
||||
}
|
||||
|
||||
return $this->parsedown->elementToHtml($element);
|
||||
|
||||
@@ -15,24 +15,25 @@ use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Media\Traits\MediaTrait;
|
||||
use Grav\Common\Taxonomy;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Common\Page\Traits\PageFormTrait;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use Negotiation\Accept;
|
||||
use Negotiation\Negotiator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
|
||||
define('PAGE_ORDER_PREFIX_REGEX', '/^[0-9]+\./u');
|
||||
|
||||
class Page implements PageInterface
|
||||
{
|
||||
use PageFormTrait;
|
||||
use MediaTrait;
|
||||
|
||||
/**
|
||||
@@ -93,11 +94,9 @@ class Page implements PageInterface
|
||||
protected $ssl;
|
||||
protected $template_format;
|
||||
protected $debugger;
|
||||
/** @var array */
|
||||
protected $forms;
|
||||
|
||||
/**
|
||||
* @var PageInterface Unmodified (original) version of the page. Used for copying and moving the page.
|
||||
* @var PageInterface|null Unmodified (original) version of the page. Used for copying and moving the page.
|
||||
*/
|
||||
private $_original;
|
||||
|
||||
@@ -188,16 +187,32 @@ class Page implements PageInterface
|
||||
*/
|
||||
public function translatedLanguages($onlyPublished = false)
|
||||
{
|
||||
$filename = substr($this->name, 0, -(strlen($this->extension())));
|
||||
$config = Grav::instance()['config'];
|
||||
$languages = $config->get('system.languages.supported', []);
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$languages = $language->getLanguages();
|
||||
$defaultCode = $language->getDefault();
|
||||
|
||||
$name = substr($this->name, 0, -strlen($this->extension()));
|
||||
$translatedLanguages = [];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md';
|
||||
if (file_exists($path)) {
|
||||
foreach ($languages as $languageCode) {
|
||||
$languageExtension = ".{$languageCode}.md";
|
||||
$path = $this->path . DS . $this->folder . DS . $name . $languageExtension;
|
||||
$exists = file_exists($path);
|
||||
|
||||
// Default language may be saved without language file location.
|
||||
if (!$exists && $languageCode === $defaultCode) {
|
||||
$languageExtension = '.md';
|
||||
$path = $this->path . DS . $this->folder . DS . $name . $languageExtension;
|
||||
$exists = file_exists($path);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
$aPage = new Page();
|
||||
$aPage->init(new \SplFileInfo($path), $language . '.md');
|
||||
$aPage->init(new \SplFileInfo($path), $languageExtension);
|
||||
|
||||
$route = $aPage->header()->routes['default'] ?? $aPage->rawRoute();
|
||||
if (!$route) {
|
||||
@@ -208,7 +223,7 @@ class Page implements PageInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$translatedLanguages[$language] = $route;
|
||||
$translatedLanguages[$languageCode] = $route;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,22 +239,41 @@ class Page implements PageInterface
|
||||
*/
|
||||
public function untranslatedLanguages($includeUnpublished = false)
|
||||
{
|
||||
$filename = substr($this->name, 0, -strlen($this->extension()));
|
||||
$config = Grav::instance()['config'];
|
||||
$languages = $config->get('system.languages.supported', []);
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$languages = $language->getLanguages();
|
||||
$defaultCode = $language->getDefault();
|
||||
|
||||
$name = substr($this->name, 0, -strlen($this->extension()));
|
||||
$untranslatedLanguages = [];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md';
|
||||
if (file_exists($path)) {
|
||||
$aPage = new Page();
|
||||
$aPage->init(new \SplFileInfo($path), $language . '.md');
|
||||
if ($includeUnpublished && !$aPage->published()) {
|
||||
$untranslatedLanguages[] = $language;
|
||||
}
|
||||
} else {
|
||||
$untranslatedLanguages[] = $language;
|
||||
foreach ($languages as $languageCode) {
|
||||
$path = $this->path . DS . $this->folder . DS . $name . '.' . $languageCode . '.md';
|
||||
$exists = file_exists($path);
|
||||
|
||||
// Default language may be saved without language file location.
|
||||
if (!$exists && $languageCode === $defaultCode) {
|
||||
$path = $this->path . DS . $this->folder . DS . $name . '.md';
|
||||
$exists = file_exists($path);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
if ($includeUnpublished) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aPage = new Page();
|
||||
$aPage->init(new \SplFileInfo($path), $languageCode . '.md');
|
||||
|
||||
if (!$aPage->published()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$untranslatedLanguages[] = $languageCode;
|
||||
}
|
||||
|
||||
return $untranslatedLanguages;
|
||||
@@ -568,8 +602,7 @@ class Page implements PageInterface
|
||||
$content = $textOnly ? strip_tags($this->content()) : $this->content();
|
||||
$summary_size = $this->summary_size;
|
||||
} else {
|
||||
$content = strip_tags($this->summary);
|
||||
// Use mb_strwidth to deal with the 2 character widths characters
|
||||
$content = $textOnly ? strip_tags($this->summary) : $this->summary;
|
||||
$summary_size = mb_strwidth($content, 'utf-8');
|
||||
}
|
||||
|
||||
@@ -772,7 +805,7 @@ class Page implements PageInterface
|
||||
* Add an entry to the page's contentMeta array
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addContentMeta($name, $value)
|
||||
{
|
||||
@@ -784,16 +817,12 @@ class Page implements PageInterface
|
||||
*
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return string
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getContentMeta($name = null)
|
||||
{
|
||||
if ($name) {
|
||||
if (isset($this->content_meta[$name])) {
|
||||
return $this->content_meta[$name];
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->content_meta[$name] ?? null;
|
||||
}
|
||||
|
||||
return $this->content_meta;
|
||||
@@ -819,23 +848,31 @@ class Page implements PageInterface
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$defaults = (array)$config->get('system.pages.markdown');
|
||||
$markdownDefaults = (array)$config->get('system.pages.markdown');
|
||||
if (isset($this->header()->markdown)) {
|
||||
$defaults = array_merge($defaults, $this->header()->markdown);
|
||||
$markdownDefaults = array_merge($markdownDefaults, $this->header()->markdown);
|
||||
}
|
||||
|
||||
// pages.markdown_extra is deprecated, but still check it...
|
||||
if (!isset($defaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) {
|
||||
if (!isset($markdownDefaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) {
|
||||
user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED);
|
||||
|
||||
$defaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
|
||||
$markdownDefaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
|
||||
}
|
||||
|
||||
$extra = $markdownDefaults['extra'] ?? false;
|
||||
$defaults = [
|
||||
'markdown' => $markdownDefaults,
|
||||
'images' => $config->get('system.images', [])
|
||||
];
|
||||
|
||||
$excerpts = new Excerpts($this, $defaults);
|
||||
|
||||
// Initialize the preferred variant of Parsedown
|
||||
if ($defaults['extra']) {
|
||||
$parsedown = new ParsedownExtra($this, $defaults);
|
||||
if ($extra) {
|
||||
$parsedown = new ParsedownExtra($excerpts);
|
||||
} else {
|
||||
$parsedown = new Parsedown($this, $defaults);
|
||||
$parsedown = new Parsedown($excerpts);
|
||||
}
|
||||
|
||||
$this->content = $parsedown->text($this->content);
|
||||
@@ -914,13 +951,16 @@ class Page implements PageInterface
|
||||
return $this->slug();
|
||||
}
|
||||
if ($name === 'name') {
|
||||
$name = $this->name();
|
||||
$language = $this->language() ? '.' . $this->language() : '';
|
||||
$name_val = str_replace($language . '.md', '', $this->name());
|
||||
$pattern = '%(' . preg_quote($language, '%') . ')?\.md$%';
|
||||
$name = preg_replace($pattern, '', $name);
|
||||
|
||||
if ($this->modular()) {
|
||||
return 'modular/' . $name_val;
|
||||
return 'modular/' . $name;
|
||||
}
|
||||
|
||||
return $name_val;
|
||||
return $name;
|
||||
}
|
||||
if ($name === 'media') {
|
||||
return $this->media()->all();
|
||||
@@ -1197,93 +1237,12 @@ class Page implements PageInterface
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns normalized list of name => form pairs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms()
|
||||
{
|
||||
if (null === $this->forms) {
|
||||
$header = $this->header();
|
||||
|
||||
// Call event to allow filling the page header form dynamically (e.g. use case: Comments plugin)
|
||||
$grav = Grav::instance();
|
||||
$grav->fireEvent('onFormPageHeaderProcessed', new Event(['page' => $this, 'header' => $header]));
|
||||
|
||||
$rules = $header->rules ?? null;
|
||||
if (!\is_array($rules)) {
|
||||
$rules = [];
|
||||
}
|
||||
|
||||
$forms = [];
|
||||
|
||||
// First grab page.header.form
|
||||
$form = $this->normalizeForm($header->form ?? null, null, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
|
||||
// Append page.header.forms (override singular form if it clashes)
|
||||
$headerForms = $header->forms ?? null;
|
||||
if (\is_array($headerForms)) {
|
||||
foreach ($headerForms as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->forms = $forms;
|
||||
}
|
||||
|
||||
return $this->forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $new
|
||||
*/
|
||||
public function addForms(array $new)
|
||||
{
|
||||
// Initialize forms.
|
||||
$this->forms();
|
||||
|
||||
foreach ($new as $form) {
|
||||
$form = $this->normalizeForm($form);
|
||||
if ($form) {
|
||||
$this->forms[$form['name']] = $form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function normalizeForm($form, $name = null, array $rules = [])
|
||||
{
|
||||
if (!\is_array($form)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore numeric indexes on name.
|
||||
if (!$name || (string)(int)$name === (string)$name) {
|
||||
$name = null;
|
||||
}
|
||||
|
||||
$name = $name ?? $form['name'] ?? $this->slug();
|
||||
|
||||
$formRules = $form['rules'] ?? null;
|
||||
if (!\is_array($formRules)) {
|
||||
$formRules = [];
|
||||
}
|
||||
|
||||
return ['name' => $name, 'rules' => $rules + $formRules] + $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the associated media as found in the page folder.
|
||||
*
|
||||
* @param Media $var Representation of associated media.
|
||||
*
|
||||
* @return Media Representation of associated media.
|
||||
* @return MediaCollectionInterface|Media Representation of associated media.
|
||||
*/
|
||||
public function media($var = null)
|
||||
{
|
||||
@@ -1363,67 +1322,32 @@ class Page implements PageInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a page to override the output render format, usually the extension provided
|
||||
* in the URL. (e.g. `html`, `json`, `xml`, etc).
|
||||
* Allows a page to override the output render format, usually the extension provided in the URL.
|
||||
* (e.g. `html`, `json`, `xml`, etc).
|
||||
*
|
||||
* @param null $var
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null
|
||||
* @return string
|
||||
*/
|
||||
public function templateFormat($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->template_format = $var;
|
||||
return $this->template_format;
|
||||
if (null !== $var) {
|
||||
$this->template_format = is_string($var) ? $var : null;
|
||||
}
|
||||
|
||||
if (isset($this->template_format)) {
|
||||
return $this->template_format;
|
||||
if (!isset($this->template_format)) {
|
||||
$this->template_format = ltrim($this->header->append_url_extension ?? Utils::getPageFormat(), '.');
|
||||
}
|
||||
|
||||
// Set from URL extension set on page
|
||||
$page_extension = trim($this->header->append_url_extension ?? '' , '.');
|
||||
if (!empty($page_extension)) {
|
||||
$this->template_format = $page_extension;
|
||||
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
// Set from uri extension
|
||||
$uri_extension = Grav::instance()['uri']->extension();
|
||||
if (is_string($uri_extension)) {
|
||||
$this->template_format = $uri_extension;
|
||||
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
// Use content negotitation via the `accept:` header
|
||||
$http_accept = $_SERVER['HTTP_ACCEPT'] ?? false;
|
||||
if (is_string($http_accept)) {
|
||||
$negotiator = new Negotiator();
|
||||
|
||||
$supported_types = Grav::instance()['config']->get('system.pages.types', ['html', 'json']);
|
||||
$priorities = Utils::getMimeTypes($supported_types);
|
||||
|
||||
$media_type = $negotiator->getBest($http_accept, $priorities);
|
||||
$mimetype = $media_type instanceof Accept ? $media_type->getValue() : '';
|
||||
|
||||
$this->template_format = Utils::getExtensionByMime($mimetype);
|
||||
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
// Last chance set a default type
|
||||
$this->template_format = 'html';
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the extension field.
|
||||
*
|
||||
* @param null $var
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null|string
|
||||
* @return string
|
||||
*/
|
||||
public function extension($var = null)
|
||||
{
|
||||
@@ -1652,7 +1576,7 @@ class Page implements PageInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of the debugger override etting for this page
|
||||
* Returns the state of the debugger override setting for this page
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -1681,9 +1605,10 @@ class Page implements PageInterface
|
||||
|
||||
$this->metadata = [];
|
||||
|
||||
$metadata = [];
|
||||
// Set the Generator tag
|
||||
$metadata['generator'] = 'GravCMS';
|
||||
$metadata = [
|
||||
'generator' => 'GravCMS'
|
||||
];
|
||||
|
||||
// Get initial metadata for the page
|
||||
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata'));
|
||||
@@ -1776,7 +1701,7 @@ class Page implements PageInterface
|
||||
*
|
||||
* @param int $var
|
||||
*
|
||||
* @return int|bool
|
||||
* @return string|bool
|
||||
*/
|
||||
public function order($var = null)
|
||||
{
|
||||
@@ -2032,7 +1957,7 @@ class Page implements PageInterface
|
||||
*
|
||||
* @param string $var redirect url
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function redirect($var = null)
|
||||
{
|
||||
@@ -2040,7 +1965,7 @@ class Page implements PageInterface
|
||||
$this->redirect = $var;
|
||||
}
|
||||
|
||||
return $this->redirect;
|
||||
return $this->redirect ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2099,7 +2024,7 @@ class Page implements PageInterface
|
||||
$this->path = dirname($var, 2);
|
||||
}
|
||||
|
||||
return $this->path . '/' . $this->folder . '/' . ($this->name ?: '');
|
||||
return rtrim($this->path . '/' . $this->folder . '/' . ($this->name() ?: ''), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2341,7 +2266,7 @@ class Page implements PageInterface
|
||||
}
|
||||
}
|
||||
|
||||
return $this->modular_twig;
|
||||
return $this->modular_twig ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2489,7 +2414,7 @@ class Page implements PageInterface
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @return int the index of the current page.
|
||||
* @return int|null The index of the current page.
|
||||
*/
|
||||
public function currentPosition()
|
||||
{
|
||||
@@ -2530,7 +2455,7 @@ class Page implements PageInterface
|
||||
|
||||
if (isset($routes[$uri_path])) {
|
||||
/** @var PageInterface $child_page */
|
||||
$child_page = $pages->dispatch($uri->route())->parent();
|
||||
$child_page = $pages->find($uri->route())->parent();
|
||||
if ($child_page) {
|
||||
while (!$child_page->root()) {
|
||||
if ($this->path() === $child_page->path()) {
|
||||
@@ -2663,321 +2588,48 @@ class Page implements PageInterface
|
||||
public function collection($params = 'content', $pagination = true)
|
||||
{
|
||||
if (is_string($params)) {
|
||||
// Look into a page header field.
|
||||
$params = (array)$this->value('header.' . $params);
|
||||
} elseif (!is_array($params)) {
|
||||
throw new \InvalidArgumentException('Argument should be either header variable name or array of parameters');
|
||||
}
|
||||
|
||||
if (!isset($params['items'])) {
|
||||
return new Collection();
|
||||
if (!$pagination) {
|
||||
$params['pagination'] = false;
|
||||
}
|
||||
$context = [
|
||||
'pagination' => $pagination,
|
||||
'self' => $this
|
||||
];
|
||||
|
||||
// See if require published filter is set and use that, if assume published=true
|
||||
$only_published = true;
|
||||
if (isset($params['filter']['published']) && $params['filter']['published']) {
|
||||
$only_published = false;
|
||||
} elseif (isset($params['filter']['non-published']) && $params['filter']['non-published']) {
|
||||
$only_published = false;
|
||||
}
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
$collection = $this->evaluate($params['items'], $only_published);
|
||||
if (!$collection instanceof Collection) {
|
||||
$collection = new Collection();
|
||||
}
|
||||
$collection->setParams($params);
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = Grav::instance()['uri'];
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$process_taxonomy = $params['url_taxonomy_filters'] ?? $config->get('system.pages.url_taxonomy_filters');
|
||||
|
||||
if ($process_taxonomy) {
|
||||
foreach ((array)$config->get('site.taxonomies') as $taxonomy) {
|
||||
if ($uri->param(rawurlencode($taxonomy))) {
|
||||
$items = explode(',', $uri->param($taxonomy));
|
||||
$collection->setParams(['taxonomies' => [$taxonomy => $items]]);
|
||||
|
||||
foreach ($collection as $page) {
|
||||
// Don't filter modular pages
|
||||
if ($page->modular()) {
|
||||
continue;
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
$item = rawurldecode($item);
|
||||
if (empty($page->taxonomy[$taxonomy]) || !\in_array(htmlspecialchars_decode($item, ENT_QUOTES), $page->taxonomy[$taxonomy], true)
|
||||
) {
|
||||
$collection->remove($page->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a filter or filters are set, filter the collection...
|
||||
if (isset($params['filter'])) {
|
||||
|
||||
// remove any inclusive sets from filer:
|
||||
$sets = ['published', 'visible', 'modular', 'routable'];
|
||||
foreach ($sets as $type) {
|
||||
$var = "non-{$type}";
|
||||
if (isset($params['filter'][$type], $params['filter'][$var]) && $params['filter'][$type] && $params['filter'][$var]) {
|
||||
unset ($params['filter'][$type], $params['filter'][$var]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((array)$params['filter'] as $type => $filter) {
|
||||
switch ($type) {
|
||||
case 'published':
|
||||
if ((bool) $filter) {
|
||||
$collection->published();
|
||||
}
|
||||
break;
|
||||
case 'non-published':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonPublished();
|
||||
}
|
||||
break;
|
||||
case 'visible':
|
||||
if ((bool) $filter) {
|
||||
$collection->visible();
|
||||
}
|
||||
break;
|
||||
case 'non-visible':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonVisible();
|
||||
}
|
||||
break;
|
||||
case 'modular':
|
||||
if ((bool) $filter) {
|
||||
$collection->modular();
|
||||
}
|
||||
break;
|
||||
case 'non-modular':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonModular();
|
||||
}
|
||||
break;
|
||||
case 'routable':
|
||||
if ((bool) $filter) {
|
||||
$collection->routable();
|
||||
}
|
||||
break;
|
||||
case 'non-routable':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonRoutable();
|
||||
}
|
||||
break;
|
||||
case 'type':
|
||||
$collection->ofType($filter);
|
||||
break;
|
||||
case 'types':
|
||||
$collection->ofOneOfTheseTypes($filter);
|
||||
break;
|
||||
case 'access':
|
||||
$collection->ofOneOfTheseAccessLevels($filter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['dateRange'])) {
|
||||
$start = $params['dateRange']['start'] ?? 0;
|
||||
$end = $params['dateRange']['end'] ?? false;
|
||||
$field = $params['dateRange']['field'] ?? false;
|
||||
$collection->dateRange($start, $end, $field);
|
||||
}
|
||||
|
||||
if (isset($params['order'])) {
|
||||
$by = $params['order']['by'] ?? 'default';
|
||||
$dir = $params['order']['dir'] ?? 'asc';
|
||||
$custom = $params['order']['custom'] ?? null;
|
||||
$sort_flags = $params['order']['sort_flags'] ?? null;
|
||||
|
||||
if (is_array($sort_flags)) {
|
||||
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
|
||||
$sort_flags = array_reduce($sort_flags, function ($a, $b) {
|
||||
return $a | $b;
|
||||
}, 0); //merge constant values using bit or
|
||||
}
|
||||
|
||||
$collection->order($by, $dir, $custom, $sort_flags);
|
||||
}
|
||||
|
||||
/** @var Grav $grav */
|
||||
$grav = Grav::instance();
|
||||
|
||||
// New Custom event to handle things like pagination.
|
||||
$grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection]));
|
||||
|
||||
// Slice and dice the collection if pagination is required
|
||||
if ($pagination) {
|
||||
$params = $collection->params();
|
||||
|
||||
$limit = $params['limit'] ?? 0;
|
||||
$start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0;
|
||||
|
||||
if ($limit && $collection->count() > $limit) {
|
||||
$collection->slice($start, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
return $pages->getCollection($params, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $value
|
||||
* @param bool $only_published
|
||||
* @return mixed
|
||||
* @return Collection
|
||||
*/
|
||||
public function evaluate($value, $only_published = true)
|
||||
{
|
||||
// Parse command.
|
||||
if (is_string($value)) {
|
||||
// Format: @command.param
|
||||
$cmd = $value;
|
||||
$params = [];
|
||||
} elseif (is_array($value) && count($value) == 1 && !is_int(key($value))) {
|
||||
// Format: @command.param: { attr1: value1, attr2: value2 }
|
||||
$cmd = (string)key($value);
|
||||
$params = (array)current($value);
|
||||
} else {
|
||||
$result = [];
|
||||
foreach ((array)$value as $key => $val) {
|
||||
if (is_int($key)) {
|
||||
$result = $result + $this->evaluate($val)->toArray();
|
||||
} else {
|
||||
$result = $result + $this->evaluate([$key => $val])->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Collection($result);
|
||||
}
|
||||
$params = [
|
||||
'items' => $value,
|
||||
'published' => $only_published
|
||||
];
|
||||
$context = [
|
||||
'event' => false,
|
||||
'pagination' => false,
|
||||
'url_taxonomy_filters' => false,
|
||||
'self' => $this
|
||||
];
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
$parts = explode('.', $cmd);
|
||||
$current = array_shift($parts);
|
||||
|
||||
/** @var Collection $results */
|
||||
$results = new Collection();
|
||||
|
||||
switch ($current) {
|
||||
case 'self@':
|
||||
case '@self':
|
||||
if (!empty($parts)) {
|
||||
switch ($parts[0]) {
|
||||
case 'modular':
|
||||
// @self.modular: false (alternative to @self.children)
|
||||
if (!empty($params) && $params[0] === false) {
|
||||
$results = $this->children()->nonModular();
|
||||
break;
|
||||
}
|
||||
$results = $this->children()->modular();
|
||||
break;
|
||||
case 'children':
|
||||
$results = $this->children()->nonModular();
|
||||
break;
|
||||
case 'all':
|
||||
$results = $this->children();
|
||||
break;
|
||||
case 'parent':
|
||||
$collection = new Collection();
|
||||
$results = $collection->addPage($this->parent());
|
||||
break;
|
||||
case 'siblings':
|
||||
if (!$this->parent()) {
|
||||
return new Collection();
|
||||
}
|
||||
$results = $this->parent()->children()->remove($this->path());
|
||||
break;
|
||||
case 'descendants':
|
||||
$results = $pages->all($this)->remove($this->path())->nonModular();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
case 'page@':
|
||||
case '@page':
|
||||
$page = null;
|
||||
|
||||
if (!empty($params)) {
|
||||
$page = $this->find($params[0]);
|
||||
}
|
||||
|
||||
// safety check in case page is not found
|
||||
if (!isset($page)) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Handle a @page.descendants
|
||||
if (!empty($parts)) {
|
||||
switch ($parts[0]) {
|
||||
case 'modular':
|
||||
$results = new Collection();
|
||||
foreach ($page->children() as $child) {
|
||||
$results = $results->addPage($child);
|
||||
}
|
||||
$results->modular();
|
||||
break;
|
||||
case 'page':
|
||||
case 'self':
|
||||
$results = new Collection();
|
||||
$results = $results->addPage($page)->nonModular();
|
||||
break;
|
||||
|
||||
case 'descendants':
|
||||
$results = $pages->all($page)->remove($page->path())->nonModular();
|
||||
break;
|
||||
|
||||
case 'children':
|
||||
$results = $page->children()->nonModular();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$results = $page->children()->nonModular();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'root@':
|
||||
case '@root':
|
||||
if (!empty($parts) && $parts[0] === 'descendants') {
|
||||
$results = $pages->all($pages->root())->nonModular();
|
||||
} else {
|
||||
$results = $pages->root()->children()->nonModular();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'taxonomy@':
|
||||
case '@taxonomy':
|
||||
// Gets a collection of pages by using one of the following formats:
|
||||
// @taxonomy.category: blog
|
||||
// @taxonomy.category: [ blog, featured ]
|
||||
// @taxonomy: { category: [ blog, featured ], level: 1 }
|
||||
|
||||
/** @var Taxonomy $taxonomy_map */
|
||||
$taxonomy_map = Grav::instance()['taxonomy'];
|
||||
|
||||
if (!empty($parts)) {
|
||||
$params = [implode('.', $parts) => $params];
|
||||
}
|
||||
$results = $taxonomy_map->findTaxonomy($params);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($only_published) {
|
||||
$results = $results->published();
|
||||
}
|
||||
|
||||
return $results;
|
||||
return $pages->getCollection($params, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3162,7 +2814,7 @@ class Page implements PageInterface
|
||||
/**
|
||||
* Gets the action.
|
||||
*
|
||||
* @return string The Action string.
|
||||
* @return string|null The Action string.
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
|
||||
@@ -13,13 +13,18 @@ use Grav\Common\Cache;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Taxonomy;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Flex;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Plugin\Admin;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
@@ -28,78 +33,62 @@ use Collator;
|
||||
|
||||
class Pages
|
||||
{
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var array|PageInterface[]
|
||||
*/
|
||||
/** @var Flex */
|
||||
protected $flex;
|
||||
|
||||
/** @var array|PageInterface[] */
|
||||
protected $instances;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var array */
|
||||
protected $children;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $base = '';
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $baseRoute = [];
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $routes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var array */
|
||||
protected $sort;
|
||||
|
||||
/**
|
||||
* @var Blueprints
|
||||
*/
|
||||
/** @var Blueprints */
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var bool */
|
||||
protected $enable_pages = true;
|
||||
|
||||
/** @var int */
|
||||
protected $last_modified;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $ignore_files;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $ignore_folders;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
/** @var bool */
|
||||
protected $ignore_hidden;
|
||||
|
||||
/**
|
||||
* @var Types
|
||||
*/
|
||||
static protected $types;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
static protected $home_route;
|
||||
/** @var string */
|
||||
protected $check_method;
|
||||
|
||||
protected $pages_cache_id;
|
||||
|
||||
/** @var bool */
|
||||
protected $initialized = false;
|
||||
|
||||
/** @var Types */
|
||||
static protected $types;
|
||||
|
||||
/** @var string|null */
|
||||
static protected $home_route;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -110,6 +99,26 @@ class Pages
|
||||
$this->grav = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used in admin to disable frontend pages from being initialized.
|
||||
*/
|
||||
public function disablePages(): void
|
||||
{
|
||||
$this->enable_pages = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used in admin to later load frontend pages.
|
||||
*/
|
||||
public function enablePages(): void
|
||||
{
|
||||
if (!$this->enable_pages) {
|
||||
$this->enable_pages = true;
|
||||
|
||||
$this->buildPages();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set base path for the pages.
|
||||
*
|
||||
@@ -226,20 +235,36 @@ class Pages
|
||||
return $this->baseUrl($lang, $absolute) . Uri::filterPath($route);
|
||||
}
|
||||
|
||||
public function setCheckMethod($method)
|
||||
{
|
||||
$this->check_method = strtolower($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class initialization. Must be called before using this class.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if ($this->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $this->grav['config'];
|
||||
$this->ignore_files = $config->get('system.pages.ignore_files');
|
||||
$this->ignore_folders = $config->get('system.pages.ignore_folders');
|
||||
$this->ignore_hidden = $config->get('system.pages.ignore_hidden');
|
||||
if ($config->get('system.pages.type') === 'flex') {
|
||||
$this->flex = $this->grav['flex_objects'] ?? null;
|
||||
}
|
||||
|
||||
$this->instances = [];
|
||||
$this->children = [];
|
||||
$this->routes = [];
|
||||
|
||||
if (!$this->check_method) {
|
||||
$this->setCheckMethod($config->get('system.cache.check.method', 'file'));
|
||||
}
|
||||
|
||||
$this->buildPages();
|
||||
}
|
||||
|
||||
@@ -262,11 +287,23 @@ class Pages
|
||||
/**
|
||||
* Returns a list of all pages.
|
||||
*
|
||||
* @return array|PageInterface[]
|
||||
* @return PageInterface[]
|
||||
*/
|
||||
public function instances()
|
||||
{
|
||||
return $this->instances;
|
||||
if (!$this->flex) {
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
$list = [];
|
||||
foreach ($this->instances as $path => $instance) {
|
||||
if (!$instance instanceof PageInterface) {
|
||||
$instance = $this->flex->getObject($instance);
|
||||
}
|
||||
$list[$path] = $instance;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,18 +324,301 @@ class Pages
|
||||
*/
|
||||
public function addPage(PageInterface $page, $route = null)
|
||||
{
|
||||
if (!isset($this->instances[$page->path()])) {
|
||||
$this->instances[$page->path()] = $page;
|
||||
$path = $page->path() ?? '';
|
||||
if (!isset($this->instances[$path])) {
|
||||
$this->instances[$path] = $page;
|
||||
}
|
||||
$route = $page->route($route);
|
||||
if ($page->parent()) {
|
||||
$this->children[$page->parent()->path()][$page->path()] = ['slug' => $page->slug()];
|
||||
$parentPath = $page->parent()->path() ?? '';
|
||||
$this->children[$parentPath][$path] = ['slug' => $page->slug()];
|
||||
}
|
||||
$this->routes[$route] = $page->path();
|
||||
$this->routes[$route] = $path;
|
||||
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of pages in the given context.
|
||||
*
|
||||
* @param array $params
|
||||
* @param array $context
|
||||
* @return PageCollectionInterface|Collection
|
||||
*/
|
||||
public function getCollection(array $params = [], array $context = [])
|
||||
{
|
||||
if (!isset($params['items'])) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
$context += [
|
||||
'event' => true,
|
||||
'pagination' => true,
|
||||
'url_taxonomy_filters' => $config->get('system.pages.url_taxonomy_filters'),
|
||||
'taxonomies' => (array)$config->get('site.taxonomies'),
|
||||
'pagination_page' => 1,
|
||||
'self' => null,
|
||||
];
|
||||
|
||||
// Include taxonomies from the URL if requested.
|
||||
$process_taxonomy = $params['url_taxonomy_filters'] ?? $context['url_taxonomy_filters'];
|
||||
if ($process_taxonomy) {
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
foreach ($context['taxonomies'] as $taxonomy) {
|
||||
$param = $uri->param(rawurlencode($taxonomy));
|
||||
$items = $param ? explode(',', $param) : [];
|
||||
foreach ($items as $item) {
|
||||
$params['taxonomies'][$taxonomy][] = htmlspecialchars_decode(rawurldecode($item), ENT_QUOTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pagination = $params['pagination'] ?? $context['pagination'];
|
||||
if ($pagination && !isset($params['page'])) {
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
$context['pagination_page'] = $uri->currentPage();
|
||||
}
|
||||
|
||||
$collection = $this->evaluate($params['items'], $context['self']);
|
||||
$collection->setParams($params);
|
||||
|
||||
// Filter by taxonomies.
|
||||
foreach ($params['taxonomies'] ?? [] as $taxonomy => $items) {
|
||||
foreach ($collection as $page) {
|
||||
// Don't filter modular pages
|
||||
if ($page->modular()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$test = $page->taxonomy()[$taxonomy] ?? [];
|
||||
foreach ($items as $item) {
|
||||
if (!$test || !\in_array($item, $test, true)) {
|
||||
$collection->remove($page->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any inclusive sets from filter.
|
||||
$filters = $params['filter'] ?? [];
|
||||
|
||||
// Assume published=true if not set.
|
||||
if (!isset($filters['published']) && !isset($filters['non-published'])) {
|
||||
$filters['published'] = true;
|
||||
}
|
||||
foreach (['published', 'visible', 'modular', 'routable'] as $type) {
|
||||
$var = "non-{$type}";
|
||||
if (isset($filters[$type], $filters[$var]) && $filters[$type] && $filters[$var]) {
|
||||
unset($filters[$type], $filters[$var]);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the collection
|
||||
foreach ($filters as $type => $filter) {
|
||||
switch ($type) {
|
||||
case 'published':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->published();
|
||||
}
|
||||
break;
|
||||
case 'non-published':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonPublished();
|
||||
}
|
||||
break;
|
||||
case 'visible':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->visible();
|
||||
}
|
||||
break;
|
||||
case 'non-visible':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonVisible();
|
||||
}
|
||||
break;
|
||||
case 'modular':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->modular();
|
||||
}
|
||||
break;
|
||||
case 'non-modular':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonModular();
|
||||
}
|
||||
break;
|
||||
case 'routable':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->routable();
|
||||
}
|
||||
break;
|
||||
case 'non-routable':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonRoutable();
|
||||
}
|
||||
break;
|
||||
case 'type':
|
||||
$collection = $collection->ofType($filter);
|
||||
break;
|
||||
case 'types':
|
||||
$collection = $collection->ofOneOfTheseTypes($filter);
|
||||
break;
|
||||
case 'access':
|
||||
$collection = $collection->ofOneOfTheseAccessLevels($filter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['dateRange'])) {
|
||||
$start = $params['dateRange']['start'] ?? 0;
|
||||
$end = $params['dateRange']['end'] ?? false;
|
||||
$field = $params['dateRange']['field'] ?? false;
|
||||
$collection = $collection->dateRange($start, $end, $field);
|
||||
}
|
||||
|
||||
if (isset($params['order'])) {
|
||||
$by = $params['order']['by'] ?? 'default';
|
||||
$dir = $params['order']['dir'] ?? 'asc';
|
||||
$custom = $params['order']['custom'] ?? null;
|
||||
$sort_flags = $params['order']['sort_flags'] ?? null;
|
||||
|
||||
if (is_array($sort_flags)) {
|
||||
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
|
||||
$sort_flags = array_reduce($sort_flags, function ($a, $b) {
|
||||
return $a | $b;
|
||||
}, 0); //merge constant values using bit or
|
||||
}
|
||||
|
||||
$collection = $collection->order($by, $dir, $custom, $sort_flags);
|
||||
}
|
||||
|
||||
// New Custom event to handle things like pagination.
|
||||
if ($context['event']) {
|
||||
$this->grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection]));
|
||||
}
|
||||
|
||||
// Slice and dice the collection if pagination is required
|
||||
if ($pagination) {
|
||||
$params = $collection->params();
|
||||
|
||||
$limit = $params['limit'] ?? 0;
|
||||
$start = !empty($params['pagination']) ? (($params['page'] ?? $context['pagination_page']) - 1) * $limit : 0;
|
||||
|
||||
if ($limit && $collection->count() > $limit) {
|
||||
$collection->slice($start, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @param PageInterface|null $self
|
||||
* @return Collection
|
||||
*/
|
||||
protected function evaluate($value, PageInterface $self = null)
|
||||
{
|
||||
// Parse command.
|
||||
if (is_string($value)) {
|
||||
// Format: @command.param
|
||||
$cmd = $value;
|
||||
$params = [];
|
||||
} elseif (is_array($value) && count($value) === 1 && !is_int(key($value))) {
|
||||
// Format: @command.param: { attr1: value1, attr2: value2 }
|
||||
$cmd = (string)key($value);
|
||||
$params = (array)current($value);
|
||||
} else {
|
||||
$result = [];
|
||||
foreach ((array)$value as $key => $val) {
|
||||
if (is_int($key)) {
|
||||
$result = $result + $this->evaluate($val, $self)->toArray();
|
||||
} else {
|
||||
$result = $result + $this->evaluate([$key => $val], $self)->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Collection($result);
|
||||
}
|
||||
|
||||
$parts = explode('.', $cmd);
|
||||
$scope = array_shift($parts);
|
||||
$type = $parts[0] ?? null;
|
||||
|
||||
/** @var PageInterface|null $page */
|
||||
$page = null;
|
||||
switch ($scope) {
|
||||
case 'self@':
|
||||
case '@self':
|
||||
$page = $self;
|
||||
break;
|
||||
|
||||
case 'page@':
|
||||
case '@page':
|
||||
$page = isset($params[0]) ? $this->find($params[0]) : null;
|
||||
break;
|
||||
|
||||
case 'root@':
|
||||
case '@root':
|
||||
$page = $this->root();
|
||||
break;
|
||||
|
||||
case 'taxonomy@':
|
||||
case '@taxonomy':
|
||||
// Gets a collection of pages by using one of the following formats:
|
||||
// @taxonomy.category: blog
|
||||
// @taxonomy.category: [ blog, featured ]
|
||||
// @taxonomy: { category: [ blog, featured ], level: 1 }
|
||||
|
||||
/** @var Taxonomy $taxonomy_map */
|
||||
$taxonomy_map = Grav::instance()['taxonomy'];
|
||||
|
||||
if (!empty($parts)) {
|
||||
$params = [implode('.', $parts) => $params];
|
||||
}
|
||||
|
||||
return $taxonomy_map->findTaxonomy($params);
|
||||
}
|
||||
|
||||
if (!$page) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
// Handle '@page', '@page.modular: false', '@self' and '@self.modular: false'.
|
||||
if (null === $type || ($type === 'modular' && ($params[0] ?? null) === false)) {
|
||||
$type = 'children';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'all':
|
||||
return $page->children();
|
||||
case 'modular':
|
||||
return $page->children()->modular();
|
||||
case 'children':
|
||||
return $page->children()->nonModular();
|
||||
case 'page':
|
||||
case 'self':
|
||||
return (new Collection())->addPage($page);
|
||||
case 'parent':
|
||||
$parent = $page->parent();
|
||||
$collection = new Collection();
|
||||
return $parent ? $collection->addPage($parent) : $collection;
|
||||
case 'siblings':
|
||||
$parent = $page->parent();
|
||||
return $parent ? $parent->children()->remove($page->path()) : new Collection();
|
||||
case 'descendants':
|
||||
return $this->all($page)->remove($page->path())->nonModular();
|
||||
default:
|
||||
// Unknown type; return empty collection.
|
||||
return new Collection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort sub-pages in a page.
|
||||
*
|
||||
@@ -379,7 +699,16 @@ class Pages
|
||||
*/
|
||||
public function get($path)
|
||||
{
|
||||
return $this->instances[(string)$path] ?? null;
|
||||
$instance = $this->instances[(string)$path] ?? null;
|
||||
if (\is_string($instance)) {
|
||||
$instance = $this->flex ? $this->flex->getObject($instance) : null;
|
||||
$instance = $instance->hasTranslation() ? $instance->getTranslation() : $instance;
|
||||
}
|
||||
if ($instance && !$instance instanceof PageInterface) {
|
||||
throw new \RuntimeException('Routing failed on unknown type', 500);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,7 +736,7 @@ class Pages
|
||||
public function ancestor($route, $path = null)
|
||||
{
|
||||
if ($path !== null) {
|
||||
$page = $this->dispatch($route, true);
|
||||
$page = $this->find($route, true);
|
||||
|
||||
if ($page && $page->path() === $path) {
|
||||
return $page;
|
||||
@@ -434,7 +763,7 @@ class Pages
|
||||
{
|
||||
if ($field !== null) {
|
||||
|
||||
$page = $this->dispatch($route, true);
|
||||
$page = $this->find($route, true);
|
||||
|
||||
$parent = $page ? $page->parent() : null;
|
||||
if ($parent && $parent->value('header.' . $field) !== null) {
|
||||
@@ -495,13 +824,18 @@ class Pages
|
||||
|
||||
// fall back and check site based redirects
|
||||
if (!$page || ($page && !$page->routable())) {
|
||||
// Redirect to the first child (placeholder page)
|
||||
if ($redirect && $page && count($children = $page->children()->visible()) > 0) {
|
||||
$this->grav->redirectLangSafe($children->first()->route());
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// See if route matches one in the site configuration
|
||||
$site_route = $config->get("site.routes.{$route}");
|
||||
if ($site_route) {
|
||||
$page = $this->dispatch($site_route, $all);
|
||||
$page = $this->dispatch($site_route, $all, $redirect);
|
||||
} else {
|
||||
|
||||
/** @var Uri $uri */
|
||||
@@ -533,7 +867,7 @@ class Pages
|
||||
try {
|
||||
$found = preg_replace($pattern, $replace, $source_url);
|
||||
if ($found !== $source_url) {
|
||||
$page = $this->dispatch($found, $all);
|
||||
$page = $this->dispatch($found, $all, $redirect);
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
$this->grav['log']->error('site.routes: ' . $pattern . '-> ' . $e->getMessage());
|
||||
@@ -557,7 +891,7 @@ class Pages
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
return $this->instances[rtrim($locator->findResource('page://'), DS)];
|
||||
return $this->get(rtrim($locator->findResource('page://'), '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -695,13 +1029,9 @@ class Pages
|
||||
} else {
|
||||
$extra = $showSlug ? '(' . $current->slug() . ') ' : '';
|
||||
$option = str_repeat('—-', $level). '▸ ' . $extra . $current->title();
|
||||
|
||||
|
||||
}
|
||||
|
||||
$list[$route] = $option;
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ($limitLevels === false || ($level+1 < $limitLevels)) {
|
||||
@@ -806,20 +1136,23 @@ class Pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function pageTypes()
|
||||
public static function pageTypes($type = null)
|
||||
{
|
||||
if (isset(Grav::instance()['admin'])) {
|
||||
if (null === $type && isset(Grav::instance()['admin'])) {
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'];
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $admin->getPage($admin->route);
|
||||
$page = $admin->page();
|
||||
|
||||
if ($page && $page->modular()) {
|
||||
$type = $page && $page->modular() ? 'modular' : 'standard';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'standard':
|
||||
return static::types();
|
||||
case 'modular':
|
||||
return static::modularTypes();
|
||||
}
|
||||
|
||||
return static::types();
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -925,29 +1258,159 @@ class Pages
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function buildPages()
|
||||
protected function buildPages(): void
|
||||
{
|
||||
$this->sort = [];
|
||||
if ($this->enable_pages === false) {
|
||||
$page = $this->buildRootPage();
|
||||
$this->instances[$page->path()] = $page;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->startTimer('build-pages', 'Init frontend routes');
|
||||
|
||||
$directory = $this->flex ? $this->flex->getDirectory('grav-pages') : null;
|
||||
|
||||
if ($directory) {
|
||||
$this->buildFlexPages($directory);
|
||||
} else {
|
||||
$this->buildRegularPages();
|
||||
}
|
||||
$debugger->stopTimer('build-pages');
|
||||
}
|
||||
|
||||
protected function buildFlexPages(FlexDirectory $directory)
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// TODO: right now we are just emulating normal pages, it is inefficient and bad... but works!
|
||||
$collection = $directory->getIndex();
|
||||
$cache = $directory->getCache('index');
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
$this->pages_cache_id = 'pages-' . md5($collection->getCacheChecksum() . $language->getActive() . $config->checksum());
|
||||
|
||||
$cached = $cache->get($this->pages_cache_id);
|
||||
|
||||
if ($cached && $this->getVersion() === $cached[0]) {
|
||||
[, $this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort] = $cached;
|
||||
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding Flex Pages..');
|
||||
|
||||
$root = $this->buildRootPage();
|
||||
$root_path = $root->path();
|
||||
$this->instances = [$root_path => $root];
|
||||
$this->children = [];
|
||||
$this->sort = [];
|
||||
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onBuildPagesInitialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string $key
|
||||
* @var PageInterface|FlexObjectInterface $page
|
||||
*/
|
||||
foreach ($collection as $key => $page) {
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
|
||||
$path = $page->path();
|
||||
|
||||
// FIXME: We really need to do better than this.
|
||||
$translated = $page->hasTranslation() ? $page->getTranslation() : false;
|
||||
|
||||
if (!$translated || $path === $root_path) {
|
||||
continue;
|
||||
}
|
||||
$parent = dirname($path);
|
||||
|
||||
$this->instances[$path] = $page->getFlexKey();
|
||||
// FIXME: ... better...
|
||||
$this->children[$parent][$path] = ['slug' => $translated->slug()];
|
||||
if (!isset($this->children[$path])) {
|
||||
$this->children[$path] = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->children as $path => $list) {
|
||||
$page = $this->get($path);
|
||||
if (null === $page) {
|
||||
continue;
|
||||
}
|
||||
// Call onFolderProcessed event.
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
// Sort the children.
|
||||
$this->children[$path] = $this->sort($page);
|
||||
}
|
||||
|
||||
$this->buildRoutes();
|
||||
|
||||
// cache if needed
|
||||
if (isset($cache)) {
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
$taxonomy_map = $taxonomy->taxonomy();
|
||||
|
||||
// save pages, routes, taxonomy, and sort to cache
|
||||
$cache->set($this->pages_cache_id, [$this->getVersion(), $this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function buildRootPage()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
$page = new Page();
|
||||
$page->path($locator->findResource('page://'));
|
||||
$page->orderDir($config->get('system.pages.order.dir'));
|
||||
$page->orderBy($config->get('system.pages.order.by'));
|
||||
$page->modified(0);
|
||||
$page->routable(false);
|
||||
$page->template('default');
|
||||
$page->extension('.md');
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function buildRegularPages()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
$pages_dir = $locator->findResource('page://');
|
||||
|
||||
if ($config->get('system.cache.enabled')) {
|
||||
/** @var Cache $cache */
|
||||
$cache = $this->grav['cache'];
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
// how should we check for last modified? Default is by file
|
||||
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
|
||||
switch ($this->check_method) {
|
||||
case 'none':
|
||||
case 'off':
|
||||
$hash = 0;
|
||||
@@ -964,22 +1427,25 @@ class Pages
|
||||
|
||||
$this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($this->pages_cache_id);
|
||||
if (!$this->instances) {
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
/** @var Cache $cache */
|
||||
$cache = $this->grav['cache'];
|
||||
$cached = $cache->fetch($this->pages_cache_id);
|
||||
if ($cached && $this->getVersion() === $cached[0]) {
|
||||
[, $this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort] = $cached;
|
||||
|
||||
// recurse pages and cache result
|
||||
$this->resetPages($pages_dir);
|
||||
|
||||
} else {
|
||||
// If pages was found in cache, set the taxonomy
|
||||
$this->grav['debugger']->addMessage('Page cache hit.');
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
} else {
|
||||
$this->recurse($pages_dir);
|
||||
$this->buildRoutes();
|
||||
$this->grav['debugger']->addMessage('Page cache disabled, rebuilding pages..');
|
||||
}
|
||||
|
||||
$this->resetPages($pages_dir);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -989,6 +1455,7 @@ class Pages
|
||||
*/
|
||||
public function resetPages($pages_dir)
|
||||
{
|
||||
$this->sort = [];
|
||||
$this->recurse($pages_dir);
|
||||
$this->buildRoutes();
|
||||
|
||||
@@ -1000,7 +1467,7 @@ class Pages
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
|
||||
// save pages, routes, taxonomy, and sort to cache
|
||||
$cache->save($this->pages_cache_id, [$this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]);
|
||||
$cache->save($this->pages_cache_id, [$this->getVersion(), $this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,7 +1512,7 @@ class Pages
|
||||
if ($parent && $page->path()) {
|
||||
$this->children[$parent->path()][$page->path()] = ['slug' => $page->slug()];
|
||||
}
|
||||
} else {
|
||||
} elseif ($parent !== null) {
|
||||
throw new \RuntimeException('Fatal error when creating page instances.');
|
||||
}
|
||||
|
||||
@@ -1143,7 +1610,6 @@ class Pages
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!$content_exists) {
|
||||
// Set routability to false if no page found
|
||||
$page->routable(false);
|
||||
@@ -1185,44 +1651,49 @@ class Pages
|
||||
|
||||
// Get the home route
|
||||
$home = self::resetHomeRoute();
|
||||
|
||||
// Build routes and taxonomy map.
|
||||
/** @var PageInterface $page */
|
||||
foreach ($this->instances as $page) {
|
||||
if (!$page->root()) {
|
||||
// process taxonomy
|
||||
$taxonomy->addTaxonomy($page);
|
||||
foreach ($this->instances as $path => $page) {
|
||||
if (\is_string($page)) {
|
||||
$page = $this->get($path);
|
||||
}
|
||||
|
||||
$route = $page->route();
|
||||
$raw_route = $page->rawRoute();
|
||||
$page_path = $page->path();
|
||||
if (!$page || $page->root()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add regular route
|
||||
$this->routes[$route] = $page_path;
|
||||
// process taxonomy
|
||||
$taxonomy->addTaxonomy($page);
|
||||
|
||||
// add raw route
|
||||
if ($raw_route !== $route) {
|
||||
$this->routes[$raw_route] = $page_path;
|
||||
}
|
||||
$route = $page->route();
|
||||
$raw_route = $page->rawRoute();
|
||||
$page_path = $page->path();
|
||||
|
||||
// add canonical route
|
||||
$route_canonical = $page->routeCanonical();
|
||||
if ($route_canonical && ($route !== $route_canonical)) {
|
||||
$this->routes[$route_canonical] = $page_path;
|
||||
}
|
||||
// add regular route
|
||||
$this->routes[$route] = $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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alias and set default route to home page.
|
||||
$homeRoute = '/' . $home;
|
||||
$homeRoute = "/{$home}";
|
||||
if ($home && isset($this->routes[$homeRoute])) {
|
||||
$this->routes['/'] = $this->routes[$homeRoute];
|
||||
$this->get($this->routes[$homeRoute])->route('/');
|
||||
@@ -1254,7 +1725,7 @@ class Pages
|
||||
}
|
||||
|
||||
foreach ($pages as $key => $info) {
|
||||
$child = $this->instances[$key] ?? null;
|
||||
$child = $this->get($key);
|
||||
if (!$child) {
|
||||
throw new \RuntimeException("Page does not exist: {$key}");
|
||||
}
|
||||
@@ -1289,7 +1760,10 @@ class Pages
|
||||
$list[$key] = $child->folder();
|
||||
break;
|
||||
case (is_string($header_query[0])):
|
||||
$child_header = new Header((array)$child->header());
|
||||
$child_header = $child->header();
|
||||
if (!$child_header instanceof Header) {
|
||||
$child_header = new Header((array)$child_header);
|
||||
}
|
||||
$header_value = $child_header->get($header_query[0]);
|
||||
if (is_array($header_value)) {
|
||||
$list[$key] = implode(',',$header_value);
|
||||
@@ -1390,6 +1864,11 @@ class Pages
|
||||
return $new;
|
||||
}
|
||||
|
||||
protected function getVersion()
|
||||
{
|
||||
return $this->flex ? 'flex' : 'page';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Pages cache ID
|
||||
*
|
||||
|
||||
120
system/src/Grav/Common/Page/Traits/PageFormTrait.php
Normal file
120
system/src/Grav/Common/Page/Traits/PageFormTrait.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common\Page\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
trait PageFormTrait
|
||||
{
|
||||
private $_forms;
|
||||
|
||||
/**
|
||||
* Return all the forms which are associated to this page.
|
||||
*
|
||||
* Forms are returned as [name => blueprint, ...], where blueprint follows the regular form blueprint format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getForms(): array
|
||||
{
|
||||
if (null === $this->_forms) {
|
||||
$header = $this->header();
|
||||
|
||||
// Call event to allow filling the page header form dynamically (e.g. use case: Comments plugin)
|
||||
$grav = Grav::instance();
|
||||
$grav->fireEvent('onFormPageHeaderProcessed', new Event(['page' => $this, 'header' => $header]));
|
||||
|
||||
$rules = $header->rules ?? null;
|
||||
if (!\is_array($rules)) {
|
||||
$rules = [];
|
||||
}
|
||||
|
||||
$forms = [];
|
||||
|
||||
// First grab page.header.form
|
||||
$form = $this->normalizeForm($header->form ?? null, null, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
|
||||
// Append page.header.forms (override singular form if it clashes)
|
||||
$headerForms = $header->forms ?? null;
|
||||
if (\is_array($headerForms)) {
|
||||
foreach ($headerForms as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_forms = $forms;
|
||||
}
|
||||
|
||||
return $this->_forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add forms to this page.
|
||||
*
|
||||
* @param array $new
|
||||
* @param bool $override
|
||||
* @return $this
|
||||
*/
|
||||
public function addForms(array $new, $override = true)
|
||||
{
|
||||
// Initialize forms.
|
||||
$this->forms();
|
||||
|
||||
foreach ($new as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name);
|
||||
$name = $form['name'] ?? null;
|
||||
if ($name && ($override || !isset($this->_forms[$name]))) {
|
||||
$this->_forms[$name] = $form;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of $this->getForms();
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms(): array
|
||||
{
|
||||
return $this->getForms();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $form
|
||||
* @param string|null $name
|
||||
* @param array $rules
|
||||
* @return array|null
|
||||
*/
|
||||
protected function normalizeForm($form, $name = null, array $rules = []): ?array
|
||||
{
|
||||
if (!\is_array($form)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore numeric indexes on name.
|
||||
if (!$name || (string)(int)$name === (string)$name) {
|
||||
$name = null;
|
||||
}
|
||||
|
||||
$name = $name ?? $form['name'] ?? $this->slug();
|
||||
|
||||
$formRules = $form['rules'] ?? null;
|
||||
if (!\is_array($formRules)) {
|
||||
$formRules = [];
|
||||
}
|
||||
|
||||
return ['name' => $name, 'rules' => $rules + $formRules] + $form;
|
||||
}
|
||||
|
||||
abstract public function header($var = null);
|
||||
abstract public function slug($var = null);
|
||||
}
|
||||
@@ -151,15 +151,32 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
if (\is_string($params)) {
|
||||
$dispatcher->addListener($eventName, [$this, $params]);
|
||||
} elseif (\is_string($params[0])) {
|
||||
$dispatcher->addListener($eventName, [$this, $params[0]], $params[1] ?? 0);
|
||||
$dispatcher->addListener($eventName, [$this, $params[0]], $this->getPriority($params, $eventName));
|
||||
} else {
|
||||
foreach ($params as $listener) {
|
||||
$dispatcher->addListener($eventName, [$this, $listener[0]], $listener[1] ?? 0);
|
||||
$dispatcher->addListener($eventName, [$this, $listener[0]], $this->getPriority($listener, $eventName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @param string $eventName
|
||||
*/
|
||||
private function getPriority($params, $eventName)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$override = implode('.', ["priorities", $this->name, $eventName, $params[0]]);
|
||||
if ($grav['config']->get($override) !== null)
|
||||
{
|
||||
return $grav['config']->get($override);
|
||||
} elseif (isset($params[1])) {
|
||||
return $params[1];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $events
|
||||
*/
|
||||
|
||||
@@ -133,12 +133,25 @@ class Plugins extends Iterator
|
||||
*/
|
||||
public static function all()
|
||||
{
|
||||
$plugins = Grav::instance()['plugins'];
|
||||
$grav = Grav::instance();
|
||||
$plugins = $grav['plugins'];
|
||||
$list = [];
|
||||
|
||||
foreach ($plugins as $instance) {
|
||||
$name = $instance->name;
|
||||
$result = self::get($name);
|
||||
|
||||
try {
|
||||
$result = self::get($name);
|
||||
} catch (\Exception $e) {
|
||||
$exception = new \RuntimeException(sprintf('Plugin %s: %s', $name, $e->getMessage()), $e->getCode(), $e);
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $grav['debugger'];
|
||||
$debugger->addMessage("Plugin {$name} cannot be loaded, please check Exceptions tab", 'error');
|
||||
$debugger->addException($exception);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$list[$name] = $result;
|
||||
@@ -185,24 +198,31 @@ class Plugins extends Iterator
|
||||
$grav = Grav::instance();
|
||||
$locator = $grav['locator'];
|
||||
|
||||
$filePath = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT);
|
||||
if (!is_file($filePath)) {
|
||||
$file = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT);
|
||||
|
||||
if (is_file($file)) {
|
||||
// Local variables available in the file: $grav, $config, $name, $file
|
||||
$class = include_once $file;
|
||||
|
||||
$pluginClassFormat = [
|
||||
'Grav\\Plugin\\' . ucfirst($name). 'Plugin',
|
||||
'Grav\\Plugin\\' . Inflector::camelize($name) . 'Plugin'
|
||||
];
|
||||
|
||||
foreach ($pluginClassFormat as $pluginClass) {
|
||||
if (class_exists($pluginClass)) {
|
||||
$class = new $pluginClass($name, $grav);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$grav['log']->addWarning(
|
||||
sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $name)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
require_once $filePath;
|
||||
|
||||
$pluginClassName = 'Grav\\Plugin\\' . ucfirst($name) . 'Plugin';
|
||||
if (!class_exists($pluginClassName)) {
|
||||
$pluginClassName = 'Grav\\Plugin\\' . $grav['inflector']->camelize($name) . 'Plugin';
|
||||
if (!class_exists($pluginClassName)) {
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", $name));
|
||||
}
|
||||
}
|
||||
return new $pluginClassName($name, $grav);
|
||||
return $class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class AssetsProcessor extends ProcessorBase
|
||||
public $id = '_assets';
|
||||
public $title = 'Assets';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['assets']->init();
|
||||
|
||||
@@ -18,7 +18,7 @@ class BackupsProcessor extends ProcessorBase
|
||||
public $id = '_backups';
|
||||
public $title = 'Backups';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$backups = $this->container['backups'];
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ConfigurationProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_config';
|
||||
public $title = 'Configuration';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['config']->init();
|
||||
$this->container['plugins']->setup();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class DebuggerAssetsProcessor extends ProcessorBase
|
||||
public $id = 'debugger_assets';
|
||||
public $title = 'Debugger Assets';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['debugger']->addAssets();
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class DebuggerProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_debugger';
|
||||
public $title = 'Init Debugger';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['debugger']->init();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ErrorsProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_errors';
|
||||
public $title = 'Error Handlers Reset';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['errors']->resetHandlers();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -10,25 +10,119 @@
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Framework\Session\Exceptions\SessionException;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\SyslogHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class InitializeProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = 'init';
|
||||
public $id = '_init';
|
||||
public $title = 'Initialize';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$config = $this->initializeConfig();
|
||||
$this->initializeLogger($config);
|
||||
$this->initializeErrors();
|
||||
|
||||
$this->startTimer('_debugger', 'Init Debugger');
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger']->init();
|
||||
// Clockwork integration.
|
||||
$clockwork = $debugger->getClockwork();
|
||||
if ($clockwork) {
|
||||
$server = $request->getServerParams();
|
||||
// $baseUri = str_replace('\\', '/', dirname(parse_url($server['SCRIPT_NAME'], PHP_URL_PATH)));
|
||||
// if ($baseUri === '/') {
|
||||
// $baseUri = '';
|
||||
// }
|
||||
$requestTime = $server['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
|
||||
|
||||
$request = $request->withAttribute('request_time', $requestTime);
|
||||
|
||||
// Handle clockwork API calls.
|
||||
$uri = $request->getUri();
|
||||
if (Utils::contains($uri->getPath(), '/__clockwork/')) {
|
||||
return $debugger->debuggerRequest($request);
|
||||
}
|
||||
|
||||
$this->container['clockwork'] = $clockwork;
|
||||
}
|
||||
$this->stopTimer('_debugger');
|
||||
|
||||
$this->initialize($config);
|
||||
$this->initializeSession($config);
|
||||
|
||||
// Wrap call to next handler so that debugger can profile it.
|
||||
/** @var Response $response */
|
||||
$response = $debugger->profile(function () use ($handler, $request) {
|
||||
return $handler->handle($request);
|
||||
});
|
||||
|
||||
// Log both request and response and return the response.
|
||||
return $debugger->logRequest($request, $response);
|
||||
}
|
||||
|
||||
protected function initializeConfig(): Config
|
||||
{
|
||||
$this->startTimer('_config', 'Configuration');
|
||||
|
||||
// Initialize Configuration
|
||||
$grav = $this->container;
|
||||
/** @var Config $config */
|
||||
$config = $this->container['config'];
|
||||
$config->debug();
|
||||
$config = $grav['config'];
|
||||
$config->init();
|
||||
$grav['plugins']->setup();
|
||||
|
||||
$this->stopTimer('_config');
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function initializeLogger(Config $config): void
|
||||
{
|
||||
$this->startTimer('_logger', 'Logger');
|
||||
|
||||
// Initialize Logging
|
||||
$grav = $this->container;
|
||||
|
||||
switch ($config->get('system.log.handler', 'file')) {
|
||||
case 'syslog':
|
||||
$log = $grav['log'];
|
||||
$log->popHandler();
|
||||
|
||||
$facility = $config->get('system.log.syslog.facility', 'local6');
|
||||
$logHandler = new SyslogHandler('grav', $facility);
|
||||
$formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
|
||||
$logHandler->setFormatter($formatter);
|
||||
|
||||
$log->pushHandler($logHandler);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->stopTimer('_logger');
|
||||
}
|
||||
|
||||
protected function initializeErrors(): void
|
||||
{
|
||||
$this->startTimer('_errors', 'Error Handlers Reset');
|
||||
|
||||
// Initialize Error Handlers
|
||||
$this->container['errors']->resetHandlers();
|
||||
|
||||
$this->stopTimer('_errors');
|
||||
}
|
||||
|
||||
protected function initialize(Config $config): void
|
||||
{
|
||||
$this->startTimer('_init', 'Initialize');
|
||||
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
@@ -43,21 +137,6 @@ class InitializeProcessor extends ProcessorBase
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
|
||||
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
|
||||
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
|
||||
// TODO: remove in 2.0.
|
||||
$this->container['accounts'];
|
||||
|
||||
try {
|
||||
$this->container['session']->init();
|
||||
} catch (SessionException $e) {
|
||||
$this->container['session']->init();
|
||||
$message = 'Session corruption detected, restarting session...';
|
||||
$this->addMessage($message);
|
||||
$this->container['messages']->add($message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->container['uri'];
|
||||
$uri->init();
|
||||
@@ -73,8 +152,29 @@ class InitializeProcessor extends ProcessorBase
|
||||
}
|
||||
|
||||
$this->container->setLocale();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
$this->stopTimer('_init');
|
||||
}
|
||||
|
||||
protected function initializeSession(Config $config): void
|
||||
{
|
||||
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
|
||||
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
|
||||
$this->startTimer('_session', 'Start Session');
|
||||
|
||||
// TODO: remove in 2.0.
|
||||
$this->container['accounts'];
|
||||
|
||||
try {
|
||||
$this->container['session']->init();
|
||||
} catch (SessionException $e) {
|
||||
$this->container['session']->init();
|
||||
$message = 'Session corruption detected, restarting session...';
|
||||
$this->addMessage($message);
|
||||
$this->container['messages']->add($message, 'error');
|
||||
}
|
||||
|
||||
$this->stopTimer('_session');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\SyslogHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class LoggerProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_logger';
|
||||
public $title = 'Logger';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
$grav = $this->container;
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
switch ($config->get('system.log.handler', 'file')) {
|
||||
case 'syslog':
|
||||
$log = $grav['log'];
|
||||
$log->popHandler();
|
||||
|
||||
$facility = $config->get('system.log.syslog.facility', 'local6');
|
||||
$logHandler = new SyslogHandler('grav', $facility);
|
||||
$formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
|
||||
$logHandler->setFormatter($formatter);
|
||||
|
||||
$log->pushHandler($logHandler);
|
||||
break;
|
||||
}
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class PagesProcessor extends ProcessorBase
|
||||
public $id = 'pages';
|
||||
public $title = 'Pages';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class PluginsProcessor extends ProcessorBase
|
||||
public $id = 'plugins';
|
||||
public $title = 'Plugins';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
// TODO: remove in 2.0.
|
||||
|
||||
@@ -25,21 +25,21 @@ abstract class ProcessorBase implements ProcessorInterface
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
protected function startTimer($id = null, $title = null)
|
||||
protected function startTimer($id = null, $title = null): void
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger'];
|
||||
$debugger->startTimer($id ?? $this->id, $title ?? $this->title);
|
||||
}
|
||||
|
||||
protected function stopTimer($id = null)
|
||||
protected function stopTimer($id = null): void
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger'];
|
||||
$debugger->stopTimer($id ?? $this->id);
|
||||
}
|
||||
|
||||
protected function addMessage($message, $label = 'info', $isString = true)
|
||||
protected function addMessage($message, $label = 'info', $isString = true): void
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger'];
|
||||
|
||||
@@ -20,7 +20,7 @@ class RenderProcessor extends ProcessorBase
|
||||
public $id = 'render';
|
||||
public $title = 'Render';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class RequestProcessor extends ProcessorBase
|
||||
public $id = 'request';
|
||||
public $title = 'Request';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
@@ -30,10 +30,13 @@ class RequestProcessor extends ProcessorBase
|
||||
$request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true));
|
||||
}
|
||||
|
||||
$uri = $request->getUri();
|
||||
$ext = mb_strtolower(pathinfo($uri->getPath(), PATHINFO_EXTENSION));
|
||||
|
||||
$request = $request
|
||||
->withAttribute('grav', $this->container)
|
||||
->withAttribute('time', $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME)
|
||||
->withAttribute('route', Uri::getCurrentRoute())
|
||||
->withAttribute('route', Uri::getCurrentRoute()->withExtension($ext))
|
||||
->withAttribute('referrer', $this->container['uri']->referrer());
|
||||
|
||||
$event = new RequestHandlerEvent(['request' => $request, 'handler' => $handler]);
|
||||
|
||||
@@ -19,7 +19,7 @@ class SchedulerProcessor extends ProcessorBase
|
||||
public $id = '_scheduler';
|
||||
public $title = 'Scheduler';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$scheduler = $this->container['scheduler'];
|
||||
|
||||
@@ -19,7 +19,7 @@ class TasksProcessor extends ProcessorBase
|
||||
public $id = 'tasks';
|
||||
public $title = 'Tasks';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class ThemesProcessor extends ProcessorBase
|
||||
public $id = 'themes';
|
||||
public $title = 'Themes';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['themes']->init();
|
||||
|
||||
@@ -18,7 +18,7 @@ class TwigProcessor extends ProcessorBase
|
||||
public $id = 'twig';
|
||||
public $title = 'Twig';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['twig']->init();
|
||||
|
||||
@@ -21,12 +21,20 @@ class Scheduler
|
||||
/**
|
||||
* The queued jobs.
|
||||
*
|
||||
* @var array
|
||||
* @var Job[]
|
||||
*/
|
||||
private $jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $saved_jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $executed_jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $failed_jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $jobs_run = [];
|
||||
private $output_schedule = [];
|
||||
private $config;
|
||||
@@ -49,6 +57,8 @@ class Scheduler
|
||||
|
||||
/**
|
||||
* Load saved jobs from config/scheduler.yaml file
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function loadSavedJobs()
|
||||
{
|
||||
@@ -65,7 +75,7 @@ class Scheduler
|
||||
}
|
||||
|
||||
if (isset($j['output'])) {
|
||||
$mode = isset($j['output_mode']) && $j['output_mode'] === 'append' ? true : false;
|
||||
$mode = isset($j['output_mode']) && $j['output_mode'] === 'append';
|
||||
$job->output($j['output'], $mode);
|
||||
}
|
||||
|
||||
@@ -106,7 +116,7 @@ class Scheduler
|
||||
/**
|
||||
* Get all jobs if they are disabled or not as one array
|
||||
*
|
||||
* @return array
|
||||
* @return Job[]
|
||||
*/
|
||||
public function getAllJobs()
|
||||
{
|
||||
@@ -184,6 +194,8 @@ class Scheduler
|
||||
* Reset all collected data of last run.
|
||||
*
|
||||
* Call before run() if you call run() multiple times.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetRun()
|
||||
{
|
||||
@@ -199,7 +211,7 @@ class Scheduler
|
||||
* Get the scheduler verbose output.
|
||||
*
|
||||
* @param string $type Allowed: text, html, array
|
||||
* @return mixed The return depends on the requested $type
|
||||
* @return string|array The return depends on the requested $type
|
||||
*/
|
||||
public function getVerboseOutput($type = 'text')
|
||||
{
|
||||
@@ -217,6 +229,8 @@ class Scheduler
|
||||
|
||||
/**
|
||||
* Remove all queued Jobs.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clearJobs()
|
||||
{
|
||||
@@ -263,7 +277,7 @@ class Scheduler
|
||||
/**
|
||||
* Get the Job states file
|
||||
*
|
||||
* @return \RocketTheme\Toolbox\File\FileInterface|YamlFile
|
||||
* @return YamlFile
|
||||
*/
|
||||
public function getJobStates()
|
||||
{
|
||||
@@ -296,7 +310,6 @@ class Scheduler
|
||||
* Queue a job for execution in the correct queue.
|
||||
*
|
||||
* @param Job $job
|
||||
* @return void
|
||||
*/
|
||||
private function queueJob(Job $job)
|
||||
{
|
||||
@@ -309,7 +322,6 @@ class Scheduler
|
||||
* Add an entry to the scheduler verbose output array.
|
||||
*
|
||||
* @param string $string
|
||||
* @return void
|
||||
*/
|
||||
private function addSchedulerVerboseOutput($string)
|
||||
{
|
||||
|
||||
@@ -9,11 +9,35 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Grav\Common\Page\Pages;
|
||||
|
||||
class Security
|
||||
{
|
||||
|
||||
/**
|
||||
* Sanitize SVG for XSS code
|
||||
*
|
||||
* @param $file
|
||||
*/
|
||||
public static function sanitizeSVG($file)
|
||||
{
|
||||
if (Grav::instance()['config']->get('security.sanitize_svg') && file_exists($file)) {
|
||||
$sanitizer = new Sanitizer();
|
||||
$original_svg = file_get_contents($file);
|
||||
$clean_svg = $sanitizer->sanitize($original_svg);
|
||||
file_put_contents($file, $clean_svg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect XSS code in Grav pages
|
||||
*
|
||||
* @param Pages $pages
|
||||
* @param bool $route
|
||||
* @param callable|null $status
|
||||
* @return array
|
||||
*/
|
||||
public static function detectXssFromPages(Pages $pages, $route = true, callable $status = null)
|
||||
{
|
||||
$routes = $pages->routes();
|
||||
@@ -51,7 +75,6 @@ class Security
|
||||
} else {
|
||||
$list[$page->filePathClean()] = $results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@@ -63,6 +86,8 @@ class Security
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect XSS in an array or strings such as $_POST or $_GET
|
||||
*
|
||||
* @param array $array Array such as $_POST or $_GET
|
||||
* @param string $prefix Prefix for returned values.
|
||||
* @return array Returns flatten list of potentially dangerous input values, such as 'data.content'.
|
||||
@@ -89,6 +114,7 @@ class Security
|
||||
|
||||
/**
|
||||
* Determine if string potentially has a XSS attack. This simple function does not catch all XSS and it is likely to
|
||||
*
|
||||
* return false positives because of it tags all potentially dangerous HTML tags and attributes without looking into
|
||||
* their content.
|
||||
*
|
||||
|
||||
@@ -27,9 +27,10 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['accounts'] = function (Container $container) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $container['debugger'];
|
||||
if ($container['config']->get('system.accounts.type') === 'flex') {
|
||||
$type = strtolower(defined('GRAV_USER_INSTANCE') ? GRAV_USER_INSTANCE : $container['config']->get('system.accounts.type', 'data'));
|
||||
if ($type === 'flex') {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $container['debugger'];
|
||||
$debugger->addMessage('User Accounts: Flex Directory');
|
||||
return $this->flexAccounts($container);
|
||||
}
|
||||
@@ -46,7 +47,9 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
|
||||
protected function dataAccounts(Container $container)
|
||||
{
|
||||
define('GRAV_USER_INSTANCE', 'DATA');
|
||||
if (!defined('GRAV_USER_INSTANCE')) {
|
||||
define('GRAV_USER_INSTANCE', 'DATA');
|
||||
}
|
||||
|
||||
// Use User class for backwards compatibility.
|
||||
return new DataUser\UserCollection(User::class);
|
||||
@@ -54,7 +57,9 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
|
||||
protected function flexAccounts(Container $container)
|
||||
{
|
||||
define('GRAV_USER_INSTANCE', 'FLEX');
|
||||
if (!defined('GRAV_USER_INSTANCE')) {
|
||||
define('GRAV_USER_INSTANCE', 'FLEX');
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $container['config'];
|
||||
@@ -103,7 +108,8 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
'options' => [
|
||||
'formatter' => ['class' => YamlFormatter::class],
|
||||
'folder' => 'account://',
|
||||
'pattern' => '{FOLDER}/{KEY:2}/{KEY}/user.yaml',
|
||||
'file' => 'user',
|
||||
'pattern' => '{FOLDER}/{KEY:2}/{KEY}/{FILE}{EXT}',
|
||||
'key' => 'username',
|
||||
'indexed' => true
|
||||
],
|
||||
@@ -115,7 +121,7 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
'options' => [
|
||||
'formatter' => ['class' => YamlFormatter::class],
|
||||
'folder' => 'account://',
|
||||
'pattern' => '{FOLDER}/{KEY}.yaml',
|
||||
'pattern' => '{FOLDER}/{KEY}{EXT}',
|
||||
'key' => 'storage_key',
|
||||
'indexed' => true
|
||||
],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user