Compare commits

...

458 Commits

Author SHA1 Message Date
Andy Miller
7dafa2a207 prepare for beta.10 release 2019-10-03 14:31:28 -06:00
Matias Griese
5253aa6aef Fixed user avatar file is not being randomized 2019-10-03 22:40:03 +03:00
Matias Griese
1b34530a4a Merge remote-tracking branch 'origin/1.7' into 1.7 2019-10-03 22:11:24 +03:00
Matias Griese
ae41b6f5ff Fixed Flex User Avatar not fully backwards compatible with old user 2019-10-03 22:11:13 +03:00
Andy Miller
4d09a345a4 Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
2019-10-03 13:06:45 -06:00
Matias Griese
8ac3451fbc Revert chReverted setting language for every page during initialization 2019-10-03 21:41:58 +03:00
Matias Griese
15f1f2a03d Revert chReverted setting language for every page during initialization 2019-10-03 19:17:22 +03:00
Matias Griese
ffa9ef6a7e Grav 1.7: Fixed prev/next page missing pages if pagination was turned on in page header 2019-10-03 15:16:11 +03:00
Matias Griese
a75c0cbe62 Fixed wrong Grav param separator when using Route class 2019-10-03 10:56:05 +03:00
Andy Miller
c795ead402 Updated changelog 2019-10-01 17:52:14 -06:00
Andy Miller
91270c9c66 CSVFormatter null char support 2019-10-01 17:51:29 -06:00
Andy Miller
9259c2f660 Updated changelog 2019-10-01 17:51:17 -06:00
Andy Miller
138d1e93e3 CSVFormatter null char support 2019-10-01 17:51:02 -06:00
Matias Griese
5db9f16174 Flex: Removed extra exists check when creating object (messes up "non-existing" pages) 2019-09-27 13:26:12 +03:00
Andy Miller
342eac1047 Smarter CSV handling 2019-09-26 18:35:25 -06:00
Andy Miller
88c859fde2 move try outside for loop (less memory) 2019-09-26 15:10:35 -06:00
Andy Miller
8b5bce0b6d prepare for b.9 release 2019-09-26 13:37:09 -06:00
Andy Miller
09c4cf66f5 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-09-26 13:26:38 -06:00
Andy Miller
46b859b8df better error for badly formatted line in CsvFormatter::decode() 2019-09-26 13:26:33 -06:00
Matias Griese
a7c23c58e5 Merge remote-tracking branch 'origin/1.7' into 1.7 2019-09-26 14:54:01 +03:00
Matias Griese
d206711354 Flex storage changes 2019-09-26 14:53:52 +03:00
Matias Griese
0007100a97 Removed row copy/move logic from Flex object, it belongs to storage 2019-09-26 14:49:45 +03:00
Matias Griese
3d999501a0 Fixed Grav parameter handling in RouteFactory::createFromString() 2019-09-26 14:47:45 +03:00
Andy Miller
52f83775e3 clarification 2019-09-24 18:10:39 -06:00
Andy Miller
a35cf5a830 Added a new {% cache %} tag eliminating need for twigcache extension 2019-09-24 18:10:02 -06:00
Matias Griese
39837d0826 Improved Flex FolderStorage class to better hide storage specific logic 2019-09-20 18:33:07 +03:00
Matias Griese
66bd6a4046 Fixed error when activating Flex Accounts in GRAV system configuration (PHP 7.1) 2019-09-20 12:06:17 +03:00
Matias Griese
4e9d3395e0 Improve blueprint initialization in Flex Objects (fixes content aware fields) 2019-09-20 11:59:13 +03:00
Andy Miller
248c7764f0 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-09-19 16:44:01 -06:00
Andy Miller
f72eb1b002 Merge tag '1.6.16' into develop
Release v1.6.16

# gpg: Signature made Thu Sep 19 16:42:43 2019 MDT
# gpg:                using RSA key 6AA7DB4F23BD1A7280C4CC63E82B8D0EAB94EFB9
# gpg: Good signature from "Andy Miller <rhuk@mac.com>" [ultimate]
2019-09-19 16:42:44 -06:00
Andy Miller
25caa5138a Merge branch 'release/1.6.16' 2019-09-19 16:42:43 -06:00
Andy Miller
dffb227df6 prepare for release 2019-09-19 16:42:21 -06:00
Matias Griese
7c161d5cbc Comment out couple of language debug messages 2019-09-19 22:47:53 +03:00
Matias Griese
7b2313ef0b Fixed initial Flex Object state when creating a new objects in a form 2019-09-19 21:58:28 +03:00
Matias Griese
1ad2a3f212 Make flex to skip missing flex types 2019-09-19 21:06:32 +03:00
Matias Griese
1f2363b623 Minor improvement for the new Route::withLanguate() method 2019-09-18 12:42:05 +03:00
Andy Miller
d1c069c2ee composer updates 2019-09-17 13:21:27 -06:00
Matias Griese
67e772a5ce Merge remote-tracking branch 'origin/1.7' into 1.7 2019-09-17 22:08:10 +03:00
Matias Griese
59bb167bd4 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
2019-09-17 22:07:32 +03:00
Matias Griese
5c9eb1cdb8 Fixed Badly encoded JSON data warning when uploading files [#2663] 2019-09-17 22:02:21 +03:00
Andy Miller
94a54d2a82 Using phpdebugbar format to log messages (clockwork compatible) 2019-09-17 11:21:05 -06:00
Matias Griese
f201d48112 Fixed Flex Object saving causing the key to be lost for some types 2019-09-17 19:42:28 +03:00
Matias Griese
df51b64b35 Fixed bug in object saving if using streams 2019-09-17 19:03:48 +03:00
Matias Griese
93ff915737 Fixed avatars not being displayed with flex users [#2431] 2019-09-17 15:19:42 +03:00
Matias Griese
e30ab9a043 Fixed Flex user creation if file storage is being used [#2444] 2019-09-17 11:37:09 +03:00
Matias Griese
8e59a08f9e Pages::buildFlexPages(): better compatibility to old pages 2019-09-17 11:18:30 +03:00
Andy Miller
5a874006b4 Merge branch 'develop' into 1.7
# Conflicts:
#	system/src/Grav/Common/Page/Page.php
2019-09-16 16:01:22 -06:00
Matias Griese
5d639cc633 Improved language support for Route class 2019-09-16 15:50:40 +03:00
Matias Griese
ab7038b49e Flex media: set timestamps only when files were uploaded 2019-09-16 12:43:53 +03:00
Newb I the Newbd
651b354d3e Correct non-published collection filter evaluation (#2668) 2019-09-14 10:10:24 -06:00
Matias Griese
90c2079529 Fixed wrong Pages::dispatch() calls (with redirect) when we really meant to call Pages::find() 2019-09-13 22:39:46 +03:00
Matias Griese
fa064301a2 Fixed 404 error when you click to non-routable menu item with children: redirect to the first child instead 2019-09-13 22:27:38 +03:00
Matias Griese
5424047d02 Fixed Page::modular() and Page::modularTwig() returning null for folders and other non-initialized pages 2019-09-13 21:50:06 +03:00
Matias Griese
ce2b80aeb9 Added bin/grav page-system-validator [-r|--record] [-c|--check] to test Flex Pages 2019-09-13 21:45:54 +03:00
Matias Griese
e91ae5542d Made Common\Page\Header class serializable 2019-09-13 11:17:54 +03:00
Andy Miller
922f263005 refactored the code 2019-09-12 11:15:21 -06:00
Andy Miller
01af81a0a6 fixed array types 2019-09-12 10:37:41 -06:00
Andy Miller
bd8c274f38 Support Clockwork and PHPDebugBar for dump() 2019-09-12 10:36:08 -06:00
Andy Miller
05fb69daa7 Fixed broken Twig dump() 2019-09-11 21:34:17 -06:00
Matias Griese
21f95eba53 Added Route::withLanguage() method 2019-09-11 22:36:49 +03:00
Matias Griese
4ef56054ee Minor fix on ControllerResponseTrait::createJsonResponse() 2019-09-10 11:15:47 +03:00
Matias Griese
bec9292a5c Flex folder storage improvements 2019-09-09 14:28:27 +03:00
Matias Griese
0fa6328816 Fix undefined variable when there are no translations 2019-09-09 14:08:36 +03:00
Matias Griese
b30240c340 Improve language fallback logic 2019-09-06 22:14:37 +03:00
Matias Griese
59eb3b4cb2 Flex Pages: Quick and dirty multi-language support in frontend 2019-09-06 21:43:43 +03:00
Matias Griese
5fa047c1b7 Fixed sorting in Flex Pages 2019-09-06 21:00:24 +03:00
Matias Griese
30481da51d Regression: Fixed translations when language code is used for non-language purposes 2019-09-06 15:21:09 +03:00
Matias Griese
beb8f09a9d Flex FolderStorage: Added support for partial delete 2019-09-05 17:20:08 +03:00
Matias Griese
603bb6c878 Make it possible to save multiple variations of the Flex Object 2019-09-05 16:54:59 +03:00
Matias Griese
dac3e57fd4 Fixed error in Session::getFlashObject() if Flex Form is being used 2019-09-05 15:45:29 +03:00
Andy Miller
b601d2c8fd check for file existence 2019-09-03 12:18:32 -06:00
Andy Miller
ab75201f11 Added a new sanitize_svg security config option [default: true] 2019-09-03 12:07:47 -06:00
Andy Miller
45b1b0a2ef Added Security::sanitizeSVG function + update CHANGELOG 2019-09-03 11:53:25 -06:00
Matias Griese
ca2f657c98 Oops, remove print_r() 2019-09-03 14:20:39 +03:00
Matias Griese
5c33882f5b Added extra master key parameter for FlexObjectInterface::getStorageKey() 2019-09-03 13:46:50 +03:00
Matias Griese
0f85214693 Minor fix/improvement on loading Flex objects 2019-09-03 13:41:53 +03:00
Matias Griese
4570c00041 Added FlexTranslateInterface 2019-09-03 13:40:53 +03:00
Matias Griese
4a0c8846e2 Regression: Fixed language fallback 2019-09-03 13:36:19 +03:00
Andy Miller
41e51cd86d Support StaticImageMedium formats for avatars 2019-08-31 23:26:08 -06:00
Andy Miller
e2ed3098a3 prepare for beta.7 release 2019-08-30 12:10:53 -06:00
Matias Griese
4e9ca82a0f FlexForm: Fixed some compatibility issues with Form plugin 2019-08-30 15:42:00 +03:00
Matias Griese
f032f310b5 Improved language support 2019-08-30 10:38:27 +03:00
Andy Miller
6b665b112c prepare for b.6 release 2019-08-29 11:48:57 -06:00
Matias Griese
d9dbe5520d Added version support to Flex index file 2019-08-29 15:04:45 +03:00
Matias Griese
5b8674122a Fixed blueprint values for system.accounts.storage 2019-08-29 14:22:14 +03:00
Matias Griese
2e245cd36f Flex improvements 2019-08-29 12:25:06 +03:00
Matias Griese
e36a2ea1b0 FlexFolderStorage: fixed error on deleting file 2019-08-28 18:09:33 +03:00
Matias Griese
53b7c95b0d Flex: Improve copy logic 2019-08-28 14:12:02 +03:00
Matias Griese
46975cca22 Keep interfaces backwards compatible with Grav 1.6 2019-08-28 10:04:08 +03:00
Matias Griese
a418acc32b Fixed unpublished blog posts being displayed on the front-end [#2650] 2019-08-27 11:52:42 +03:00
Matias Griese
eeb35fc521 Prioritize Accounts menu item in the admin menu 2019-08-27 10:58:11 +03:00
Matias Griese
90ca9f9d49 Fixed/improved Flex caching 2019-08-26 22:51:59 +03:00
Matias Griese
ba267a389e Flex: Implemented copy 2019-08-26 18:27:04 +03:00
Matias Griese
0ab99806db Fixed compatibility regression 2019-08-26 10:51:34 +03:00
Matias Griese
c300a3b8f8 JSON: Display trace information only in debug mode 2019-08-26 10:14:01 +03:00
Matias Griese
8480fb68ac Added custom data and flex handlers to all flex objects 2019-08-26 10:07:39 +03:00
Matias Griese
8e82056afa ControllerResponseTrait: Better error response 2019-08-24 12:31:04 +03:00
Matias Griese
3e34d54b9a Added type parameter to Pages::PageTypes() 2019-08-24 12:29:43 +03:00
Matias Griese
9ad7b208ba Blueprint: Added object support for custom handlers 2019-08-24 12:28:31 +03:00
Matias Griese
1fa62d2bdc FlexIndex: Added option to return non-existing entries in the index 2019-08-23 15:13:39 +03:00
Matias Griese
b659c56aec Added ControllerResponseTrait class 2019-08-23 12:53:18 +03:00
Matias Griese
3bd02b95fe Put back cache clear on flex save 2019-08-22 22:04:43 +03:00
Matias Griese
9e5ad84a48 Fixed another issue with flex keys 2019-08-22 20:28:18 +03:00
Matias Griese
40a6c4bf72 Greatly improve Flex Pages performance, memory use 2019-08-22 19:00:55 +03:00
Matias Griese
22acffac5c Update Pages unit tests 2019-08-22 15:16:27 +03:00
Matias Griese
ede749821d Flex Pages: Added missing page events 2019-08-22 10:36:00 +03:00
Matias Griese
f26e518c03 Work around cache clear (needs better solution) 2019-08-21 17:06:12 +03:00
Matias Griese
0f6a517589 Flex: Clear only index cache on save 2019-08-21 16:08:01 +03:00
Matias Griese
3530f4fdef Further improved speed of loading objects in Flex collections, now with cache turned on 2019-08-21 15:52:20 +03:00
Matias Griese
b92476d40d Greatly improved speed of loading Flex collections multiple times 2019-08-21 14:18:35 +03:00
Matias Griese
0d0bb2c229 Better follow protected Object Property interfaces in traits 2019-08-21 12:28:05 +03:00
Matias Griese
de59bad0f8 Keep storage key and timestamp inside FlexObject (allows diff check against stored object) 2019-08-21 12:25:06 +03:00
Matias Griese
72ad49610c Added FlectIndex::loadIndex() with metadata (including timestamp) 2019-08-21 12:22:59 +03:00
Matias Griese
dcd1f3b10d Added object meta caching in Flex FolderStorage 2019-08-21 12:21:34 +03:00
Matias Griese
52cf554ea2 Added support for not instantiating pages, useful to speed up tasks 2019-08-21 10:47:55 +03:00
Andy Miller
f30f6485ba Merge branch 'develop' into 1.7
# Conflicts:
#	composer.json
#	composer.lock
#	system/defines.php
2019-08-20 17:23:04 -06:00
Andy Miller
dd8b503aa0 Merge tag '1.6.15' into develop
Release v1.6.15
2019-08-20 17:22:26 -06:00
Andy Miller
dab30673e0 Merge branch 'release/1.6.15' 2019-08-20 17:22:25 -06:00
Andy Miller
13689c2065 prepare for release 2019-08-20 17:22:16 -06:00
Andy Miller
6e23627f26 update changelog 2019-08-20 17:21:32 -06:00
Andy Miller
7db85cc79c Force Symfony 4.2 2019-08-20 17:06:19 -06:00
Matias Griese
9b6f218f33 Make Flex Pages to work with Header 2019-08-19 20:58:46 +03:00
Matias Griese
829da9ee3a Improved bin/grav yamllinter CLI command by adding an option to find YAML Linting issues from the whole site or custom folder 2019-08-19 13:18:39 +03:00
Matias Griese
033b54104e Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-08-19 10:53:27 +03:00
Matias Griese
e5cedd074b Fixed broken markdown Twig tag [#2635] 2019-08-19 10:46:03 +03:00
Matias Griese
a6741cb761 Fixed broken taxonomies [#2633] 2019-08-19 10:21:24 +03:00
Matias Griese
8cbc2a27cd Fixed enabling PHP Debug Bar causes fatal error in Gantry [#2634] 2019-08-19 10:04:59 +03:00
Matias Griese
5f1639dc63 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-08-19 09:53:55 +03:00
Daithí Seán Ó Foghlú
ed87faad92 Update robots.txt (#2632)
I have found that Bing/Yahoo/DuckDuckGo, Yandex and Google report crawl errors when using the default robots.txt. Specifically their bots will not crawl the the path '/' or any sub-paths. I agree that the current robots.txt should work and properly implements the specification. However it still does not work.

In my experience explicitly permitting the path '/' by adding the directive Allow: / resolves the issue.

More details can be found in a blog post about the issue here: https://www.dfoley.ie/blog/starting-with-the-indieweb
2019-08-18 11:22:33 -06:00
Andy Miller
8d8b803e66 updated with composer 1.9.0 2019-08-18 10:00:37 -06:00
Andy Miller
e4ed00d84a Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
#	system/router.php
2019-08-18 09:54:50 -06:00
Andy Miller
239f34d40c Merge branch 'release/1.6.14' 2019-08-18 09:52:55 -06:00
Andy Miller
20b9ca56fa Merge tag '1.6.14' into develop
Release v1.6.14
2019-08-18 09:52:55 -06:00
Andy Miller
647ae0fda3 prepare for release 2019-08-18 09:52:45 -06:00
Andy Miller
806dbd9ee5 refactor 2019-08-18 09:51:10 -06:00
Andy Miller
1ab8442630 add fix 2019-08-18 09:50:56 -06:00
Andy Miller
040c34d693 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-08-16 09:53:44 -06:00
Andy Miller
505661404b Merge branch 'release/1.6.13' 2019-08-16 09:53:21 -06:00
Andy Miller
a2ea6faf4d Merge tag '1.6.13' into develop
Release v1.6.13
2019-08-16 09:53:21 -06:00
Andy Miller
ce51491b4d prepare for release 2019-08-16 09:53:12 -06:00
Andy Miller
8942aa8afc Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
2019-08-16 07:46:32 -06:00
Andy Miller
d241223aa3 update changelog 2019-08-16 07:45:40 -06:00
Matias Griese
256cbe3f12 Added experimental support for Flex Pages (**Flex Objects** plugin required) 2019-08-16 15:53:18 +03:00
Matias Griese
8b31ee173e Added configuration option for Flex Page, enabled experimental options in Admin Plugin 2019-08-16 15:49:58 +03:00
Andy Miller
182970eb78 Fix for bad check on $grav_basedir 2019-08-15 14:24:40 -06:00
Matias Griese
9ed3da3df2 Fixed $page->summary() always striping HTML tags if the summary was set by $page->setSummary() 2019-08-15 19:37:19 +03:00
Matias Griese
14eaa4d00a Moved setSummary() from PageLegacyInterface to PageContentInterface 2019-08-15 19:23:05 +03:00
Matias Griese
e134e3dbd9 Fixed some docblocks 2019-08-15 13:49:30 +03:00
Andy Miller
5bfb168cd7 Don’t override the system config setting 2019-08-14 16:28:06 -06:00
Andy Miller
5aef09a410 prepare for beta release 2019-08-14 16:21:07 -06:00
Andy Miller
76732ab671 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-08-14 16:20:27 -06:00
Andy Miller
d16f83fdd8 Merge tag '1.6.12' into develop
Release v1.6.12
2019-08-14 16:19:44 -06:00
Andy Miller
02e10ff8fe Merge branch 'release/1.6.12' 2019-08-14 16:19:43 -06:00
Andy Miller
6a44d8f286 prepare for release 2019-08-14 16:19:21 -06:00
Andy Miller
4b614d871f update changelog 2019-08-14 16:18:38 -06:00
Matias Griese
f54d9af758 Fixed regression in non-cached pages showing 404 2019-08-14 23:02:06 +03:00
Matias Griese
f883191d99 FormTrait: better debug messages on what went wrong on form submit 2019-08-14 21:40:47 +03:00
Matias Griese
de5ead78d1 Greatly simplify and generalize Pages::evaluate() method 2019-08-14 14:03:41 +03:00
Matias Griese
44bbdf7e39 Moved collection() and evaluate() logic from Page class into Pages class 2019-08-14 13:22:30 +03:00
Matias Griese
4b4eedf467 FormTrait: better debug messages on what went wrong on form submit 2019-08-14 13:16:33 +03:00
Matias Griese
bb477fd3b1 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-08-14 11:01:46 +03:00
Matias Griese
758e316a65 Merge remote-tracking branch 'origin/1.7' into 1.7 2019-08-14 11:01:17 +03:00
Matias Griese
2c38e24d00 Make FlexForm::setName() more robust 2019-08-14 11:01:07 +03:00
Matias Griese
3286d70092 Lock Grav user class once GRAV_USER_INSTANCE has been defined 2019-08-14 10:12:00 +03:00
Thomas Threadgold
9fc37e46fa check if GRAV_USER_INSTANCE constants are already defined (#2621) 2019-08-13 15:30:20 -06:00
Matias Griese
f304f429c5 Fix bad check in CSV escaping 2019-08-13 18:19:28 +03:00
Andy Miller
ca24e63d22 Merge branch 'develop' into 1.7 2019-08-12 16:06:27 -06:00
Andy Miller
65c73f639f Update changelog 2019-08-12 16:06:16 -06:00
Aaron Dalton
d2833a1997 Allow users to override plugin handler priorities (#2165) 2019-08-12 15:22:21 -06:00
ale rimoldi
aa8f764436 run route.php when grav is not at the root of the server (#2541) 2019-08-12 15:11:41 -06:00
Matias Griese
f1909d80db Added $grav->close() method to properly terminate the request with a response 2019-08-12 17:11:39 +03:00
Matias Griese
7718dd7e98 Improve Flex forms for better configurability 2019-08-12 11:50:16 +03:00
Matias Griese
cc66070e85 Move Page::templateFormat() content negotiation logic into Utils::getPageFormat() 2019-08-12 11:40:22 +03:00
Matias Griese
bbdc54b406 Create PageFormInterface and PageFormTrait 2019-08-12 11:37:31 +03:00
Matias Griese
c013f63b26 Create PageCollectionInterface and use it 2019-08-12 11:34:42 +03:00
Andy Miller
aa007badb5 Merge branch 'develop' into 1.7
# Conflicts:
#	system/src/Grav/Common/Twig/TwigExtension.php
2019-08-09 18:29:22 -06:00
Andy Miller
618a59921a make markdown more page context aware 2019-08-09 18:23:05 -06:00
Andy Miller
bb2e7a720b Merge branch 'develop' into 1.7 2019-08-09 17:16:41 -06:00
Andy Miller
039f71dc61 fix for var order in Validation.php 2019-08-09 17:16:31 -06:00
Andy Miller
c36e6abd66 Add language codes method 2019-08-09 16:34:08 -06:00
Andy Miller
c2b1142b7a Merge branch 'develop' into 1.7
# Conflicts:
#	composer.lock
2019-08-09 13:35:39 -06:00
Andy Miller
e03fb200a6 update vendor libs 2019-08-09 13:35:25 -06:00
Andy Miller
27b8db4c10 Updated vendor libs 2019-08-09 13:33:35 -06:00
Matias Griese
a964a34a6f Minor fix to make SimpleStorage to update modified time on save (now for real) 2019-07-30 13:59:23 +03:00
Matias Griese
1c28fd4c4c Minor fix to make SimpleStorage to update modified time on save 2019-07-30 13:54:38 +03:00
Matias Griese
e8529e7d0b Make flex storage classes to return storage metadata along with the data 2019-07-30 13:51:15 +03:00
Matias Griese
a6032af594 Add support for {EXT} in Flex FolderStorage pattern 2019-07-30 13:11:33 +03:00
Matias Griese
ea49415e14 Make FlexDirectory::loadObjects() more robust against missing values 2019-07-26 10:15:11 +03:00
Matias Griese
7b26022f9f Added Language::getPageExtensions() to get full list of supported page language extensions 2019-07-26 10:13:24 +03:00
Matias Griese
443fecfeb6 Added Language::getPageExtensions() to get full list of supported page language extensions 2019-07-25 14:40:38 +03:00
Matias Griese
c3324e3702 Added FlexStorage::getMetaData() to get updated object meta information for listed keys, storage improvements 2019-07-25 14:39:06 +03:00
Matias Griese
9e60408769 Flex form: Undo unique id dependency to form name 2019-07-19 16:11:52 +03:00
Matias Griese
3737bc9371 Flex form: Allow custom form layouts in admin, too (page raw mode) 2019-07-19 12:44:51 +03:00
Matias Griese
3d2360c995 Fix bad check in CSV escaping 2019-07-18 20:44:04 +03:00
Matias Griese
08b8505b6d Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-18 16:12:18 +03:00
Matias Griese
afd53d76c2 Fixed CSV formatter not encoding strings with " and , properly 2019-07-18 16:11:46 +03:00
Andy Miller
9607a99a7d Updated clockwork to 4.0.4 2019-07-16 13:04:40 -06:00
Matias Griese
7e63935001 Deprecated FlexDirectory::update() and FlexDirectory::remove() 2019-07-16 17:40:33 +03:00
Matias Griese
3d767a4d25 Flex objects no longer return temporary key if they do not have one; empty key is returned instead 2019-07-16 17:39:58 +03:00
Matias Griese
ee53e1be6e Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-16 10:11:47 +03:00
Matias Griese
57c65ad881 Fixed FlexObject::exists() failing sometimes just after the object has been saved 2019-07-16 09:50:30 +03:00
Matias Griese
a372ae90c2 Fixed an error when trying to delete a file from non-existing Flex Object 2019-07-16 09:11:11 +03:00
Andy Miller
676924cce5 Merge branch 'develop' into 1.7 2019-07-12 12:02:46 -06:00
Andy Miller
c8739c40a5 Fixed collections using page@.self: should allow modular pages if requested 2019-07-12 12:02:28 -06:00
Matias Griese
36a3a95ed9 Added getFlexFeatures() method to return all features that FlexObject or FlexCollection implements 2019-07-12 13:45:02 +03:00
Matias Griese
95c58c8361 FlexDirectory::getObject() can now be called without any parameters to create a new object 2019-07-12 12:40:23 +03:00
Matias Griese
66c17a8f53 Added hasFlexFeature() method to test if FlexObject or FlexCollection implements a given feature 2019-07-12 12:39:05 +03:00
Andy Miller
00ff9ac42d fix 2019-07-11 08:50:09 -06:00
Andy Miller
7172da8ed6 fix 2019-07-11 08:49:54 -06:00
Andy Miller
c55ea919ef Merge branch 'develop' into 1.7 2019-07-11 08:40:33 -06:00
Matias Griese
53216631a6 * Fixed FlexForm to allow multiple form instances with non-existing objects 2019-07-11 15:24:35 +03:00
Matias Griese
69d6b52a0e Fixed Form not to use deleted flash object until the end of the request fixing issues with reset 2019-07-11 15:23:50 +03:00
Andy Miller
e13a8304e6 Added arrayLower() method 2019-07-10 11:01:17 -06:00
Matias Griese
3c2b17853c Fixed non-existing Flex object having a bad media folder 2019-07-10 12:30:59 +03:00
Andy Miller
ea1e0a76c1 vendor updates 2019-07-09 22:00:43 -06:00
Andy Miller
833fe8b729 Added a new bin/grav server CLI command 2019-07-09 15:54:39 -06:00
Matias Griese
23d508b390 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-09 14:01:45 +03:00
Matias Griese
288b2a1953 Fixed fatal error 'Expiration date must be an integer, a DateInterval or null, "double" given' [#2529] 2019-07-09 13:43:51 +03:00
Andy Miller
e8b24479b9 Option to show/hide sensitive data 2019-07-08 15:53:25 -06:00
Matias Griese
1485c23aba Make Route objects immutable 2019-07-04 13:18:43 +03:00
Matias Griese
d43357f366 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-04 11:50:03 +03:00
Matias Griese
86b1f1fbac Workaround bug in flex forms 2019-07-02 22:01:38 +03:00
Matias Griese
c8e5aa05f9 Merge remote-tracking branch 'origin/1.7' into 1.7 2019-07-02 22:01:07 +03:00
Matias Griese
04ccce1f67 Workaround bug in flex forms 2019-07-02 22:00:56 +03:00
Andy Miller
5826821895 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-07-02 11:32:15 -06:00
Andy Miller
3ed341304b updated readme 2019-07-02 11:32:10 -06:00
Matias Griese
025e73affd Merge remote-tracking branch 'origin/1.7' into 1.7 2019-07-02 20:29:04 +03:00
Matias Griese
5635ba2bb7 Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-02 20:28:55 +03:00
Matias Griese
b5e26133a7 Fixed new Flex User creation not being possible because of username could not be given 2019-07-02 20:27:12 +03:00
Andy Miller
95637a243c Better support for Symfony local server symfony server:start 2019-07-02 11:23:32 -06:00
Andy Miller
cd417a1509 prepare for release 2019-07-01 14:54:49 -06:00
Andy Miller
631ae3d3d5 Merge branch '1.7' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-01 13:45:42 -06:00
Andy Miller
7c34224304 Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-01 13:45:03 -06:00
Matias Griese
6062e47377 Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-01 22:26:31 +03:00
Matias Griese
c97faa0238 ixed Flex User to have permissions to save and delete his own user 2019-07-01 22:25:29 +03:00
Matias Griese
a94abb4fb2 Added configuration option to set fallback content languages individually for every language 2019-07-01 14:46:10 +03:00
Matias Griese
8ab317b49a Merge remote-tracking branch 'origin/1.7' into 1.7 2019-06-29 14:09:57 +03:00
Matias Griese
44dda3d607 Fixed Language::getFallbackPageExtensions() logic to what it should have been 2019-06-29 14:09:46 +03:00
Andy Miller
75ed986437 Updated vendor libs 2019-06-28 14:48:12 -06:00
Andy Miller
3ac785b9ce Fix for changed location of phpdebug CSS 2019-06-28 14:47:13 -06:00
Matias Griese
de367e1558 FIx wrong nesting level in system.yamlL file 2019-06-28 20:52:15 +03:00
Matias Griese
4fead303d1 Changelog update 2019-06-28 15:39:44 +03:00
Matias Griese
41898af46f Merge branch 'feature/multilang' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/blueprints/config/system.yaml
2019-06-28 15:30:41 +03:00
Matias Griese
fe8833876c Changelog update 2019-06-28 15:27:27 +03:00
Matias Griese
e116998914 Added new system.debugger.censored configuration option to hide potentially sensitive information 2019-06-28 15:26:31 +03:00
Matias Griese
053f96dec1 Make Debugger::addException() to accept \Throwable 2019-06-28 13:39:04 +03:00
Matias Griese
94494c3c96 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-06-28 13:37:39 +03:00
Matias Griese
69b39b4b21 Fixed multiple url() issues with streams
* 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
2019-06-28 13:36:37 +03:00
Matias Griese
5afae3c3f2 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-06-27 18:11:58 +03:00
Matias Griese
eed3d84a10 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-06-27 18:11:24 +03:00
Matias Griese
e7b996104f Merge branches 'develop' and 'feature/multilang' of github.com:getgrav/grav into feature/multilang
# Conflicts:
#	CHANGELOG.md
2019-06-27 18:10:36 +03:00
Matias Griese
02f544f813 Merge remote-tracking branch 'origin/develop' into develop 2019-06-27 18:08:21 +03:00
Matias Griese
69b5a779e4 Fixed error when calling Media::add($name, null) 2019-06-27 18:08:12 +03:00
Matias Griese
3f176c1924 Changelog update 2019-06-27 17:59:05 +03:00
Matias Griese
91d20d8840 Changelog update 2019-06-27 14:47:05 +03:00
Matias Griese
6998505e3c Merge branch 'develop' of github.com:getgrav/grav into feature/multilang
# Conflicts:
#	CHANGELOG.md
2019-06-27 14:46:28 +03:00
Matias Griese
ffa2e0a6f6 Added new configuration option system.languages.include_default_lang_file_extension to keep default language in .md files if set to false 2019-06-27 14:44:35 +03:00
Matias Griese
b54e4fb71b Better form value handling for page name 2019-06-27 13:49:31 +03:00
Matias Griese
8f391d327a Fixed another bug in Page::untranslatedLanguages() 2019-06-27 13:01:11 +03:00
Matias Griese
e0e29f468b Fixed Language::getFallbackPageExtensions() to append .md file after the default language extension 2019-06-27 12:06:07 +03:00
Andy Miller
89b9cc5367 Merge branch 'develop' into 1.7 2019-06-26 09:06:19 -06:00
Andy Miller
fa5c1e495d Use new Utils::getSupportedPageTypes() to enforce html,htm at the front of the list #2531 2019-06-26 08:59:24 -06:00
Matias Griese
7fdb2c10cb Minor code cleanup 2019-06-26 12:14:57 +03:00
Keith Bentrup
e422eebd3c fixed typos in comments / API docs 2019-06-26 11:46:08 +03:00
Keith Bentrup
3dca7e3539 fixed typos in comments / API docs 2019-06-26 11:46:08 +03:00
Matias Griese
75210b102e Changelog update 2019-06-26 10:48:06 +03:00
Matias Griese
f0e97a7277 Fixed some potential issues when $grav['user'] is not set 2019-06-26 10:38:38 +03:00
Matias Griese
f6c30cbeae Fixed .md page to be assigned to the default language and to be listed in translated/untranslated page list 2019-06-26 10:18:14 +03:00
Matias Griese
cca7b6b1d4 Minor cleanup in InitializeProcessor 2019-06-25 10:45:59 +03:00
Newb I the Newbd
e16c81516e Make yaml_decode only return array, again 2019-06-25 10:02:40 +03:00
Andy Miller
c765787102 vendor updates 2019-06-24 17:43:26 -06:00
Andy Miller
dff3872b43 prepare for release 2019-06-24 17:41:20 -06:00
Andy Miller
3c91cea232 Updated changelog 2019-06-24 16:59:03 -06:00
Andy Miller
a2e9c013ad Fixed Clockwork on Windows machines 2019-06-24 16:58:46 -06:00
Andy Miller
dd82ab45bc updated changelog 2019-06-24 16:28:33 -06:00
Andy Miller
46b710b435 Fix for clockwork in subdomains - Don’t rely on base_uri 2019-06-24 16:26:37 -06:00
Matias Griese
a6d3e1ee8e Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-06-24 20:41:28 +03:00
Matias Griese
140c9a941f Added support for custom FormFlash save locations 2019-06-24 13:15:27 +03:00
Andy Miller
2b29b17044 Prepare for release 2019-06-21 15:20:27 -06:00
Andy Miller
5316f0f28c Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-06-21 15:19:50 -06:00
Andy Miller
eb58fe9e97 Merge branch 'release/1.6.11' 2019-06-21 15:19:04 -06:00
Andy Miller
5d4ea87402 Merge tag '1.6.11' into develop
Release v1.6.11
2019-06-21 15:19:04 -06:00
Andy Miller
9f1d7240a9 Prepare for release 2019-06-21 15:18:54 -06:00
Matias Griese
ac4d6cc8d0 Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7 2019-06-21 23:00:14 +03:00
Matias Griese
84894274f0 Fixed some FormFlash bugs, cleaned up the code 2019-06-21 22:59:54 +03:00
Matias Griese
ea09002012 Changed FormFlashInterface constructor to take $config array 2019-06-21 13:19:01 +03:00
Matias Griese
601ec5cb7a Added FormFlashInterface 2019-06-21 11:47:21 +03:00
Andy Miller
565947e074 Tweak clockwork message #2558 2019-06-20 21:07:04 -06:00
Andy Miller
25d1767e6c updated changelog 2019-06-20 19:48:04 -06:00
Andy Miller
c079f9b95b Merge branch 'develop' into 1.7 2019-06-20 19:46:26 -06:00
Matias Griese
85ec2ee3a0 Fixed error if user has no form flashes 2019-06-20 21:19:10 +03:00
Matias Griese
20cfb45c14 Fixed error if user has no form flashes 2019-06-20 21:18:44 +03:00
Matias Griese
5314558a8e Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7 2019-06-20 21:12:31 +03:00
Matias Griese
965f69f680 Fixed issue with FormFlash objects when there is no session initialized 2019-06-20 21:10:48 +03:00
Matias Griese
02b93d510a Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	system/src/Grav/Common/Page/Pages.php
2019-06-20 20:26:49 +03:00
Matias Griese
13a56dd4da Initialize pages only once 2019-06-20 19:21:52 +03:00
Matias Griese
4cf1b8c400 Better fix for Route class 2019-06-20 19:21:35 +03:00
Matias Griese
9c805a4317 Added created timestamp to FormFlash 2019-06-20 12:18:55 +03:00
Matias Griese
b7b1182e14 Added timestamp to FormFlash objects 2019-06-20 11:47:23 +03:00
Matias Griese
8dfb0ca993 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-06-20 01:05:07 +03:00
Matias Griese
f695aaaea4 Added FormTrait::getAllFlashes() method to get all the available form flash objects for the form 2019-06-20 00:51:20 +03:00
Matias Griese
b398d04d96 Fixed a bug in Route::withParam() method 2019-06-20 00:45:59 +03:00
Matias Griese
574df5cf80 Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-06-19 21:52:28 +03:00
Matias Griese
ac9ef4da76 Fixed empty form flash name after update 2019-06-19 20:56:23 +03:00
Matias Griese
a10893eaad Fixed error in ImageMedium::url() if the image cache folder does not exist 2019-06-19 11:23:12 +03:00
Andy Miller
34dcd8c346 prepare for beta release 2019-06-14 14:45:29 -06:00
Andy Miller
5079077e1d Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
2019-06-14 14:43:00 -06:00
Andy Miller
0e0d5b23be Merge branch 'release/1.6.10' 2019-06-14 13:59:12 -06:00
Andy Miller
e6c8b30882 Merge tag '1.6.10' into develop
Release v1.6.10
2019-06-14 13:59:12 -06:00
Andy Miller
a222e353ba prepare for release 2019-06-14 13:59:01 -06:00
Andy Miller
1f120a0127 set to beta.1 2019-06-14 13:43:35 -06:00
Andy Miller
fe05c9b87b clockwork debugger by default 2019-06-14 13:43:24 -06:00
Andy Miller
f795a634b5 minor vendor updates 2019-06-14 13:43:13 -06:00
Andy Miller
888b93926c updated changelog 2019-06-14 13:42:47 -06:00
Matias Griese
687f29f912 Profiler: Remove even more noise 2019-06-14 15:17:15 +03:00
Matias Griese
273bc9d970 Profiler: remove some noise 2019-06-14 15:12:48 +03:00
Matias Griese
d593d5a392 Clockwork: Order profiler calls by the time used, some extra filtering 2019-06-14 15:00:17 +03:00
Matias Griese
18fb688cde Clockwork: Added xhprof profiler tab 2019-06-14 14:43:27 +03:00
Matias Griese
d8fccb0edd Clockwork: Added call traces to deprecated messages 2019-06-14 13:48:45 +03:00
Andy Miller
5843347c46 tidy up 2019-06-13 10:04:08 -06:00
Tracerneo
30cfe3bdfa Change minimal port number to 0 (unix socket) (#2452) 2019-06-11 08:39:26 -06:00
Andy Miller
fedb0625b8 z-index fix 2019-06-06 12:09:04 -06:00
Andy Miller
72d012e401 minified 2 2019-06-04 20:11:41 -06:00
Andy Miller
8d77b50055 optimized clockwork css 2019-06-04 20:09:59 -06:00
Andy Miller
42eefbd34c Reworked debugger assets a little 2019-06-04 17:24:56 -06:00
Andy Miller
e5e5bf1bd8 minor twig update 2019-06-04 14:14:16 -06:00
Andy Miller
01ec334127 Merge branch '1.7' into feature/clockwork-twig 2019-06-04 14:07:43 -06:00
Matias Griese
916469a903 Fixed missing timer titles in clockwork 2019-06-04 10:09:57 +03:00
Matias Griese
55f205c801 Composer update 2019-06-04 10:03:54 +03:00
Matias Griese
8a3fc57cd8 Merge branch '1.7' into feature/clockwork-twig
# Conflicts:
#	composer.lock
2019-06-04 10:01:47 +03:00
Andy Miller
6cd1cca4fc Accidentally commited Twig 2 2019-06-03 15:32:28 -06:00
Andy Miller
1c9926ff38 Roll back to Symfony 4.2 until we have some deprecated errors sorted 2019-06-03 15:31:15 -06:00
Andy Miller
638477b06d Roll back to Symfony 4.2 until we have some deprecated errors sorted 2019-06-03 15:31:04 -06:00
Andy Miller
7e3ca73b0e removed unused use 2019-06-03 10:49:25 -06:00
Andy Miller
57a3a20868 Merge branch '1.7' into feature/clockwork-twig
# Conflicts:
#	system/src/Grav/Common/Debugger.php
2019-06-03 10:49:10 -06:00
Matias Griese
4e45c5be95 Properly handle termination in twig redirect and if theme does not exist 2019-06-03 13:05:15 +03:00
Matias Griese
ca89156c4f Added $grav->exit() method to properly terminate the request with a response 2019-06-03 12:10:43 +03:00
Matias Griese
dc5390b3dc Some code cleanup (debugger) 2019-06-03 11:14:22 +03:00
Matias Griese
0e8c7eae99 Added PHP profiling support for redirect responses 2019-06-03 11:08:47 +03:00
Matias Griese
115bdb7e10 Processor cleanup, moved logRequest() and debuggerRequest() into Debugger class 2019-06-03 10:44:10 +03:00
Matias Griese
9738c55633 Clockwork: Added request logging for Grav redirects 2019-06-03 10:29:59 +03:00
Matias Griese
23921c1a35 Some phpstan level 3 fixes 2019-06-03 08:53:25 +03:00
Andy Miller
1f3547b15b Merge branch '1.7' into feature/clockwork-twig 2019-06-02 14:23:40 -06:00
Matias Griese
6974a24669 Some phpstan fixes 2019-06-02 20:30:56 +03:00
Matias Griese
2cf35ec2c7 Composer update, fixed deprecated Twig (1.40 compatible) 2019-06-02 15:19:21 +03:00
Andy Miller
d21bb6b8c7 Merge branch '1.7' into feature/clockwork-twig 2019-05-31 10:59:12 -06:00
Matias Griese
9b8b480c8c Added basic overridable support for the fields 2019-05-31 13:39:23 +03:00
Matias Griese
ac654d56d0 Improved debugger message support 2019-05-31 13:24:06 +03:00
Matias Griese
db9e1a197e Minor fix to support toggleable on required form fields 2019-05-31 13:22:49 +03:00
Matias Griese
19bb9a6966 Regression: Fixed missing timeline name in debugbar 2019-05-30 14:44:02 +03:00
Matias Griese
b7a1c7b72a Added support for Tideways XHProf PHP Extension for profiling requests 2019-05-30 11:59:26 +03:00
Andy Miller
042486bc73 Merge branch 'feature/clockwork-twig' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-30 05:43:48 +02:00
Andy Miller
42cc1c6b01 tweaked arrows 2019-05-30 00:06:45 +02:00
Matias Griese
407d2c8323 Merge branch 'feature/clockwork-twig' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-29 13:54:18 +03:00
Matias Griese
8ab14c0639 Merge branch '1.7' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-29 13:54:07 +03:00
Matias Griese
48c281024f Fixed Twig 2.10 errors 2019-05-29 13:53:29 +03:00
Andy Miller
f7e1bec0cf Very rought first working profiling 2019-05-29 12:33:24 +02:00
Matias Griese
41c7973fc7 Merge branches '1.7' and 'feature/clockwork-twig' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-29 12:07:49 +03:00
Andy Miller
bc93e70d11 Force Twig 2.0 for now (testing and compatibility) 2019-05-29 10:17:59 +02:00
Andy Miller
320ab41435 Initial twig profiler integration..no output yet 2019-05-29 10:16:33 +02:00
Andy Miller
7213c393fd Force Twig 2.0 for now (testing and compatibility) 2019-05-29 10:16:03 +02:00
Andy Miller
6caadc8396 Merge branch '1.7' into feature/clockwork-twig 2019-05-29 09:48:32 +02:00
Andy Miller
e0d1385061 Added default to Utils::param() 2019-05-28 19:52:08 +02:00
Andy Miller
e1eed973a2 field sizes 2019-05-27 19:33:18 +03:00
Andy Miller
f4645fc77e Debugbar/Clockwork Toggle. 2019-05-27 19:20:21 +03:00
Andy Miller
136ec07450 1.7 version in defines 2019-05-27 14:54:16 +03:00
Andy Miller
e01605de55 Merge branch 'feature/clockwork' into feature/clockwork-twig 2019-05-27 13:45:16 +03:00
Andy Miller
5cc48c4e01 initial stuff 2019-05-27 13:45:00 +03:00
Matias Griese
5e2545c606 Improve debugger to have less impact when it's not enabled 2019-05-27 13:42:43 +03:00
Andy Miller
d227a82056 Fixed regresssion issue of Utils::Url() not returning false on failure #2524 2019-05-27 11:58:53 +03:00
Andy Miller
0fd2627cea Merge branch 'feature/clockwork' of github.com:getgrav/grav into feature/clockwork 2019-05-27 11:58:37 +03:00
Andy Miller
cfcd955cc2 Fixed regresssion issue of Utils::Url() not returning false on failure #2524 2019-05-27 11:58:31 +03:00
Matias Griese
7d59b69709 Debugger/Clockwork: add configuration, plugins and streams to the log 2019-05-27 11:26:34 +03:00
Matias Griese
e8eb1d9a44 Debugger: Fixed error if xdebug isn't installed 2019-05-26 17:25:59 +03:00
Andy Miller
2117748f18 updated composer.lock 2019-05-26 17:05:12 +03:00
Matias Griese
e76733e8c3 Optimization: Initialize debugbar only after the configuration has been loaded
Optimization: Combine some early Grav processors into a single one
2019-05-26 16:55:24 +03:00
Matias Griese
2da5f90e9c Merge branch 'develop' of github.com:getgrav/grav into feature/clockwork
# Conflicts:
#	CHANGELOG.md
2019-05-26 16:48:15 +03:00
Matias Griese
81b100cd3b Merge remote-tracking branch 'origin/feature/clockwork' into feature/clockwork 2019-05-26 16:42:56 +03:00
Matias Griese
19be8f5104 Fixed some phpstan level 2 issues 2019-05-26 16:42:36 +03:00
Matias Griese
9825daa79b Merge remote-tracking branch 'origin/develop' into develop 2019-05-26 16:27:53 +03:00
Matias Griese
10d405112c Fixed Excerpts::processMediaActions() return value 2019-05-26 16:27:43 +03:00
Matias Griese
ad64a9d857 Fixed reutrn type of processMediaActions 2019-05-26 16:24:58 +03:00
Andy Miller
c4c70d082c Fixes #2510 and #2511 2019-05-26 13:36:12 +03:00
Andy Miller
23a928c5b7 Fixed bitwise operator in TwigExtension::exifFunc() #2518 2019-05-26 13:33:24 +03:00
Andy Miller
8a7f624558 return the found language rather than just ‘true’ 2019-05-26 13:23:08 +03:00
Andy Miller
331f416e36 Fixed bitwise operator in TwigExtension::exifFunc() #2518 2019-05-26 11:37:18 +03:00
Matias Griese
3a48c79b21 Merge branch 'develop' of github.com:getgrav/grav into feature/clockwork 2019-05-24 08:39:35 +03:00
Matias Griese
0c66de25c4 Improve clockwork debug messages, make it easier to disable debugbar or clockwork 2019-05-23 23:12:55 +03:00
Djamil Legato
92e8dbf4ea Added Funding.yml 2019-05-23 09:44:47 -07:00
y-u-s-s-u-f-i-s-l-a-m
aca04cee6b Create now.json (#2515)
* Create now.json

* Update now.json
2019-05-23 07:43:27 -07:00
Matias Griese
0bd227c22d Add basic clockwork support 2019-05-23 15:39:44 +03:00
Matias Griese
7bc45bd9e0 Minor code cleanup/refactoring on flex caching 2019-05-23 10:04:09 +03:00
Matias Griese
9e5363b4df Merge branch 'develop' of github.com:getgrav/grav into develop 2019-05-22 11:56:00 +03:00
Matias Griese
37f69d89cc Some phpstan level 6 fixes 2019-05-20 10:52:32 +03:00
Andy Miller
30e96aa4ff address deprecated message 2019-05-18 11:12:58 -06:00
Djamil Legato
7e1d3b260e Fixed regression with bin/plugin not listing the plugins available (1c725c0) 2019-05-17 23:01:54 -07:00
Andy Miller
be558ccac9 fixes #2507 2019-05-17 11:10:15 -06:00
Matias Griese
e991056106 Minor constructor change in Parsedown classes 2019-05-17 20:06:49 +03:00
Matias Griese
b795155345 Framework: Fix all phpstan level 5 issues 2019-05-17 15:18:41 +03:00
Matias Griese
99d0c7cb3e Generalized markdown classes so they can be used outside of Page scope with a custom Excerpts class instance 2019-05-17 14:48:12 +03:00
Matias Griese
01b85f19bc Fixed GPM errors from blueprints not being logged [#2505] 2019-05-16 11:51:30 +03:00
Andy Miller
65ba214494 size of summary chars field 2019-05-15 15:48:26 -06:00
Andy Miller
ccff51144e Fixes #2493 2019-05-15 13:45:59 -06:00
Andy Miller
c8f6e7b0e1 Merge branch 'develop' of github.com:getgrav/grav into develop 2019-05-14 15:44:39 -06:00
Andy Miller
7cccf8e237 vendor updates 2019-05-14 15:44:33 -06:00
Ole Vik
e1f646c308 Remove Gitter, Slack (#2502)
Replace with redirecting link to chat.getgrav.org, also link to forum again.
2019-05-14 11:44:27 -06:00
Andy Miller
2a49a2ba7c Added page blueprints to YamlLinter 2019-05-12 11:24:50 -06:00
Andy Miller
e9bbd1e0b0 Optimizations for Plugin/Theme loading 2019-05-10 17:17:06 -06:00
Andy Miller
84e259f0f1 minor optimization 2019-05-10 17:01:52 -06:00
Andy Miller
b3f4461f34 Merge tag '1.6.9' into develop
Release v1.6.9
2019-05-09 10:49:11 -06:00
Andy Miller
1df6b76e25 Merge branch 'release/1.6.9' 2019-05-09 10:49:10 -06:00
Andy Miller
2153e20bc5 prepare for release 2019-05-09 10:49:01 -06:00
Andy Miller
0cbc98ab53 Merge branch 'develop' of github.com:getgrav/grav into develop 2019-05-08 20:57:11 -06:00
Andy Miller
0915a0413d Added composer scripts api-16 and api-15 2019-05-08 20:57:06 -06:00
Andy Miller
f4d3d302f4 thowing api error 2019-05-08 20:54:42 -06:00
Riley Sommerville
5d5e2264c0 Update default.md (#2496)
Correct an error that ignored the "Typography" menu item in the default Grav installation and caused a discrepancy between the install page and the corresponding tutorial on getgrav/grav-learn. Related to PR https://github.com/getgrav/grav-learn/pull/681#issue-270418391 and solves issue https://github.com/getgrav/grav-learn/issues/650#issue-365078790.
2019-05-08 17:39:09 -06:00
Andy Miller
b39fc72bd2 Update languages 2019-05-08 14:22:47 -06:00
Matias Griese
d6d50c4b66 Fixed empty $grav['request']->getAttribute('route')->getExtension() 2019-05-07 19:07:09 +03:00
Matias Griese
791ef8ad88 Changelog update 2019-05-07 17:58:10 +03:00
Matias Griese
e0861e5505 Merge branch 'develop' of github.com:getgrav/grav into develop 2019-05-07 17:55:59 +03:00
Newb I the Newbd
48c9176d90 Make yaml_decode only return array to avoid errors (#2494) 2019-05-07 17:53:42 +03:00
Matias Griese
33cb20561e Fixed backwards compatibility to select field with selectize.create set to true [git-sync#141] 2019-05-07 14:05:55 +03:00
Andy Miller
0acb38f586 Checkbox getting interpreted as string 2019-05-06 16:22:31 -06:00
Andy Miller
1a41e00a4f Merge branch 'develop' of github.com:getgrav/grav into develop 2019-05-03 10:43:17 -06:00
Andy Miller
48ef93e495 more tidy 2019-05-03 10:43:13 -06:00
Matias Griese
3f7da86711 Missed lock file 2019-05-03 08:54:51 +03:00
Matias Griese
11aa2314d5 Fixed typo in composer.json 2019-05-03 08:52:53 +03:00
Andy Miller
179c5065ca minor rounding stuff 2019-05-02 16:06:08 -06:00
Andy Miller
7c60f73942 vendor libs 2019-05-02 15:05:01 -06:00
Andy Miller
7095c665b7 Output correct "Last Updated" in bin/gpm info command 2019-05-02 15:03:01 -06:00
Matias Griese
c6d94885e0 Fixed exception in Flex::getDirectories() if the first parameter is set 2019-05-02 16:37:35 +03:00
Matias Griese
d9ddab8239 Merge remote-tracking branch 'origin/develop' into develop 2019-05-02 14:11:35 +03:00
Matias Griese
a118d45177 Flex collection and object now fall back to the default template if template file doesn't exist 2019-05-02 14:11:22 +03:00
Matias Griese
416c400367 Made UserCollectionInderface to extend Countable to get the count of existing users 2019-05-02 14:10:09 +03:00
Raphaël Droz
e6839530d8 When using Grav behind an HTTPS-endpoint, we would probably have either one of (#2486)
* `HTTPS=on`
* `X-Forwarded-Proto https`
Still, the latest virtualhost would still provide `REQUEST_SCHEME` which,
contrary to HTTPS environment, would not be overriden (eg: Apache only
creates `REDIRECT_REQUEST_SCHEME` with the requested value).

This change gives priority to non-empty `HTTPS` over `REQUEST_SCHEME`.
2019-04-30 12:18:20 -06:00
Matias Griese
fea9e53be3 Flex admin: added default search options for flex objects 2019-04-30 10:49:59 +03:00
Matias Griese
c5b3792a60 Change cache touch parameter to invalidate, added CLI option for it 2019-04-30 10:48:35 +03:00
Andy Miller
4f83b5da5b non-standard lang code length fixes 2019-04-29 17:31:56 -06:00
Andy Miller
504c8faf4c Merge branch 'develop' of github.com:getgrav/grav into develop 2019-04-26 13:11:32 -06:00
Andy Miller
4d6db5b334 Fix for avatar_url provided by 3rd party providers 2019-04-26 13:11:28 -06:00
Matias Griese
ec1fc1f1e3 Change cache touch parameter to invalidate, added CLI option for it 2019-04-26 09:54:14 +03:00
Matias Griese
563cf8900c Merge remote-tracking branch 'origin/develop' into develop 2019-04-25 21:08:16 +03:00
Matias Griese
0850c2f362 Added Cache::clearCache('touch') parameter for just forcing simple cache clear 2019-04-25 21:08:07 +03:00
Andy Miller
f30334d80f Fixed Assets options array mixed with standalone priority #2477 2019-04-25 11:53:11 -06:00
Matias Griese
9057a804a2 Added Pages::setCheckMethod() method to override page configuration in Admin Plugin 2019-04-25 16:07:25 +03:00
Matias Griese
4c5c26033a Added support for configurable actions in Flex 2019-04-24 17:14:44 +03:00
Matias Griese
a4f679adcf Added Route::withoutParams() methods 2019-04-24 14:48:37 +03:00
Matias Griese
e9e12392ac Fixed $grav['route'] from being modified when the route instance gets modified 2019-04-24 14:03:55 +03:00
Andy Miller
6b4663c2ff Merge branch 'release/1.6.8' 2019-04-23 15:02:10 -06:00
Andy Miller
0411c3a98b Merge tag '1.6.8' into develop
Release v1.6.8
2019-04-23 15:02:10 -06:00
Andy Miller
972a758ac9 prepaer for release 2019-04-23 15:01:59 -06:00
Andy Miller
05d72306c6 updated changelog 2019-04-23 14:58:42 -06:00
Andy Miller
18b7c0955d updated clean command 2019-04-23 14:46:51 -06:00
Andy Miller
71cbbf4e1e remove hipchat 2019-04-23 14:46:35 -06:00
Matias Griese
0606e12872 Added FlexCollection::filterBy() method 2019-04-23 13:24:45 +03:00
Matias Griese
afc7cac5ab Improve null result handling in Flex 2019-04-23 11:13:29 +03:00
Matias Griese
471e3d8954 Revert "Use Null Coalesce Operator (#2466)"
This reverts commit be8eb639
2019-04-23 10:54:26 +03:00
Andy Miller
74988f1254 Merge tag '1.6.7' into develop
Release v1.6.7
2019-04-22 15:20:18 -06:00
189 changed files with 9536 additions and 4402 deletions

8
.github/FUNDING.yml vendored Normal file
View 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

View File

@@ -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:

View File

@@ -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

View File

@@ -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.

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View File

@@ -0,0 +1,4 @@
{
"version": 2,
"builds": [{ "src": "*.php", "use": "@now/php" }]
}

View File

@@ -10,3 +10,4 @@ Disallow: /user/
Allow: /user/pages/
Allow: /user/themes/
Allow: /user/images/
Allow: /

View File

@@ -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;
}

View 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==)}

View 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)});

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -36,3 +36,4 @@ uploads_dangerous_extensions:
- htm
- js
- exe
sanitize_svg: true

View File

@@ -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

View File

@@ -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')) {

View File

@@ -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

View File

@@ -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 />

View File

@@ -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'

View File

@@ -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ð

View File

@@ -35,3 +35,12 @@ GRAV:
- 'Outubro'
- 'Novembro'
- 'Dezembro'
INFLECTOR_UNCOUNTABLE:
- 'equipment'
- 'information'
- 'arroz'
- 'money'
- 'species'
- 'series'
- 'fish'
- 'sheep'

View File

@@ -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

View File

@@ -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

View File

@@ -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:
- '一月'
- '二月'

View File

@@ -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: 无法识别表达式

View File

@@ -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;

View File

@@ -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];

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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';

View File

@@ -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';
}
}

View File

@@ -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);
}
}
}

View File

@@ -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]);
}
}

View File

@@ -11,8 +11,11 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
class Package {
/**
* @property string $name
*/
class Package
{
/**
* @var Data
*/

View File

@@ -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);

View File

@@ -103,7 +103,7 @@ class Licenses
}
/**
* Get's the License File object
* Get the License File object
*
* @return \RocketTheme\Toolbox\File\FileInterface
*/

View File

@@ -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);

View File

@@ -25,6 +25,7 @@ class Plugins extends AbstractPackageCollection
{
/** @var \Grav\Common\Plugins $plugins */
$plugins = Grav::instance()['plugins'];
parent::__construct($plugins->all());
}
}

View File

@@ -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');

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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 . '>';

View File

@@ -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 = [];

View File

@@ -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) ?: [];

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;
}
/**

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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);

View 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;
}

View File

@@ -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
{
}

View File

@@ -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);

View File

@@ -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();

View 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;
}
}

View File

@@ -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;

View File

@@ -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':

View File

@@ -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 = [])

View File

@@ -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
*/

View File

@@ -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);

View File

@@ -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()
{

View File

@@ -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('&mdash;-', $level). '&rtrif; ' . $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
*

View 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);
}

View File

@@ -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
*/

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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'];

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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');
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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.

View File

@@ -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'];

View File

@@ -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();

View File

@@ -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]);

View File

@@ -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'];

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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)
{

View File

@@ -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.
*

View File

@@ -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