Compare commits

...

248 Commits

Author SHA1 Message Date
Andy Miller
bd8396ba6e Merge branch 'release/1.6.25' 2020-05-14 15:41:44 -06:00
Andy Miller
859aff590b Prepare for release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-14 15:41:34 -06:00
Andy Miller
5db395799d Changelog updated
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-14 15:17:02 -06:00
Andy Miller
497ca2a5cd Advanced Customization options for HTTP_X_FORWARDED headers 2020-05-14 14:43:52 -06:00
Jesse Donat
58da1cd489 Updates donatj/phpuseragentparser to 1.0.0 2020-05-08 10:18:31 +03:00
Andy Miller
d53a594971 updated to 1.13 2020-04-30 17:33:41 -06:00
Djamil Legato
77bc8029bb Updated reference to github-release go app 2020-04-30 15:21:48 -07:00
Andy Miller
4ddc98b2b6 Merge branch 'release/1.6.24' 2020-04-27 16:08:40 -06:00
Andy Miller
c1f18f5ecf Merge tag '1.6.24' into develop
Release v1.6.24
2020-04-27 16:08:40 -06:00
Andy Miller
d16a88e731 prepare for release 2020-04-27 16:08:28 -06:00
A----
39d0d640e6 Support for the X-Forwarded-Host (#2891) 2020-04-27 10:38:36 -06:00
Andy Miller
586105907d disable XDebug for Travis builds 2020-03-19 14:56:03 -06:00
Andy Miller
15af5bfae7 Merge branch 'release/1.6.23' 2020-03-19 14:27:39 -06:00
Andy Miller
d550a016a9 Merge tag '1.6.23' into develop
Release v1.6.23
2020-03-19 14:27:39 -06:00
Andy Miller
9c8df27bf1 Updated changelog 2020-03-19 14:27:25 -06:00
Andy Miller
1ce0176ab6 created aliases for direct \Parsedown and \ParsedownExtra references 2020-03-19 14:25:08 -06:00
Andy Miller
ccac8a93bc Merge branch 'release/1.6.23' 2020-03-19 12:46:08 -06:00
Andy Miller
bcbfa0e32a Merge tag '1.6.23' into develop
Release v1.6.23
2020-03-19 12:46:08 -06:00
Andy Miller
453cd62a51 prepare for release 2020-03-19 12:45:58 -06:00
Andy Miller
56e1cbc78e updated composer to pickup toolbox 1.4.7 2020-03-19 12:39:36 -06:00
Andy Miller
db92c7b32d Upgraded jQuery to 3.4.1 - fixes #2859 2020-03-19 11:31:10 -06:00
Andy Miller
2eae104c7a Fix for user reported CVE path-based open redirect 2020-03-18 17:32:46 -06:00
Andy Miller
6f2be2a2d2 Updated changelog 2020-03-18 11:50:23 -06:00
Andy Miller
e75960fee3 php 7.4 fixes for ParsedownExtra 2020-03-18 11:44:23 -06:00
Andy Miller
ec71ccdd0d moved parsedown 1.6 and parsedown-extra 0.7 into framework for fixes 2020-03-18 11:39:08 -06:00
Andy Miller
152c987ed4 Merge branch 'release/1.6.22' 2020-03-05 10:39:00 -07:00
Andy Miller
19311c7ec1 Merge tag '1.6.22' into develop
Release v1.6.22
2020-03-05 10:39:00 -07:00
Andy Miller
eec2d122cc prepare for release 2020-03-05 10:38:50 -07:00
Andy Miller
62f39fe39c Negotiation update to fix #2513 2020-03-04 10:57:43 -07:00
Rotzbua
40bc980084 [travis] add php 7.4 to test 2020-02-19 10:46:49 +02:00
Matias Griese
1196e06dd6 Reverted validation: strict fix as it breaks sites, see [#1273] 2020-02-18 13:14:09 +02:00
Andy Miller
64b33d60f4 updated changelog 2020-02-14 08:36:04 -07:00
Andy Miller
8ccbcf0488 set baseRoute with activelang if available 2020-02-14 08:14:57 -07:00
Matias Griese
244c34a536 Page::getCacheKey() method should be public 2020-02-14 13:55:31 +02:00
Matias Griese
52a704e53d Fixed issue with search plugins not being able to switch between page translations 2020-02-14 13:55:06 +02:00
Andy Miller
addecbbb22 Merge branch 'release/1.6.21' 2020-02-11 17:17:29 -07:00
Andy Miller
ec8bf9357a Merge tag '1.6.21' into develop
Release v1.6.21
2020-02-11 17:17:29 -07:00
Andy Miller
432b4b1e68 prepare for release 2020-02-11 17:17:20 -07:00
Andy Miller
ea5935b1d1 vendor updates 2020-02-11 17:15:07 -07:00
Andy Miller
fa3c9095c7 caught silly bug that broke routing 2020-02-11 17:09:54 -07:00
Matias Griese
95442ef0b5 Fixed page initialization in CLI 2020-02-11 20:53:02 +02:00
Matias Griese
f81503dd70 Fixed page initialization in CLI 2020-02-11 20:27:57 +02:00
Matias Griese
48170d2fa0 Fixed bin/plugin CLI calling $themes->init() way too early
Added `ConsoleCommand::setLanguage()` method to set language to be used from CLI
Added `ConsoleCommand::initializeGrav()` method to properly set up Grav instance to be used from CLI
Added `ConsoleCommand::initializePlugins()`method to properly set up all plugins to be used from CLI
Added `ConsoleCommand::initializeThemes()`method to properly set up current theme to be used from CLI
Added `ConsoleCommand::initializePages()` method to properly set up pages to be used from CLI
2020-02-11 19:41:28 +02:00
Matias Griese
ef80e28d1d Fixed regression (#2154) 2020-02-10 17:41:13 +02:00
Matias Griese
e55b239536 Fixed encoding problems when PHP INI setting default_charset is not utf-8 [#2154] 2020-02-10 09:50:39 +02:00
Andreas Amos
c2f374f0db Truncator: encoding problems solves #2154
As I am not a developer somebody should review the change.
2020-02-10 09:28:13 +02:00
Florian Schlittenbauer
29c6a70611 [BUGFIX] .editorconfig not picking up .yml files (#2808) 2020-02-08 13:47:17 -07:00
Andy Miller
e507300134 Merge branch 'release/1.6.20' 2020-02-03 10:04:38 -07:00
Andy Miller
e38c5cac4a Merge tag '1.6.20' into develop
Release v1.6.20
2020-02-03 10:04:38 -07:00
Andy Miller
463a55897c prepare for release 2020-02-03 10:04:26 -07:00
Andy Miller
192cc4eb9b minor symfony updates 2020-02-03 10:00:40 -07:00
Matias Griese
a592f6fe0b Fixed unit test for Uri::currentPage(): it only returns integers now 2020-01-30 09:11:19 +02:00
Matias Griese
6b887a98cd Fixed site.metadata saving issues [#2615] 2020-01-29 18:55:44 +02:00
Matias Griese
18a26b42e2 Fixed Assets::addInlineJs() parameter type mismatch between v1.5 and v1.6 [#2659] 2020-01-29 18:40:58 +02:00
Matias Griese
a47e446b60 Composer update (and lock rockettheme/toolbox to 1.4.x) 2020-01-29 13:23:28 +02:00
Matias Griese
864a938f8d Fixed Data::filter() removing empty fields (such as empty list) by default [#2805] 2020-01-29 11:48:59 +02:00
Matias Griese
53bd1641bb Fixed fatal error with non-integer page param value [#2803] 2020-01-29 11:32:40 +02:00
Matias Griese
7c0dcd6808 Fixed special case [#1273] 2020-01-14 17:54:19 +02:00
Matias Griese
ea8b7b7a3a Oops, changelog update [#1273] 2020-01-14 14:48:02 +02:00
Matias Griese
8714aa9202 Fixed validation: strict not working in blueprints [#1273] 2020-01-14 14:46:19 +02:00
Matias Griese
3731d61b78 Fixed checkbox field not being saved, requires also Form v4.0.2 [#1225] 2020-01-14 11:40:39 +02:00
Matias Griese
7b5d6f7031 Fixed session not restarting if user was invalid (downgrading from Grav 1.7) 2019-12-13 13:13:52 +02:00
Matias Griese
a269d49392 Fixed filesystem iterator calls with non-existing folders 2019-12-10 18:59:58 +02:00
Matias Griese
3a8775f545 Fixed session cookie is being set twice in the HTTP header [#2745] 2019-12-10 16:06:54 +02:00
Matias Griese
842dc0d49e Incorrect routing caused by str_replace() in Uri::init() [#2754] 2019-12-10 14:52:16 +02:00
Andy Miller
1532de8f20 Merge branch 'release/1.6.19' 2019-12-04 16:31:16 -07:00
Andy Miller
95bd217c3c Merge tag '1.6.19' into develop
Release 1.6.18
2019-12-04 16:31:16 -07:00
Andy Miller
f633c921cc prepare for release 2019-12-04 16:31:04 -07:00
Andy Miller
e8c79ffd97 Sync with 1.7 approach 2019-12-04 16:27:47 -07:00
Andy Miller
e842eb9d9e Better fix for #2750 2019-12-04 16:11:08 -07:00
Matias Griese
3e8572dbe9 Merge remote-tracking branch 'origin/develop' into develop 2019-12-03 20:26:50 +02:00
Matias Griese
ad8d0a2ab1 Updated plugins phpstan to work with Gantry5 plugin 2019-12-03 19:29:04 +02:00
Andy Miller
4a1e16449d Updated changelog 2019-12-03 09:03:07 -07:00
Andy Miller
41d31cb5ea PHP 7.4 fix for #2750 2019-12-03 09:00:07 -07:00
Matias Griese
909e2cbf89 Catch PHP 7.4 deprecation messages and report them in debugbar instead of throwing fatal error 2019-12-03 13:06:44 +02:00
Matias Griese
1290503895 Fixed multiple issues when there are no pages in the site 2019-12-03 12:55:24 +02:00
Matias Griese
a204b24d78 Fixed fatal error when calling {{ grav.undefined }} 2019-12-03 12:47:01 +02:00
Matias Griese
86c969998f Merge remote-tracking branch 'origin/develop' into develop 2019-12-02 20:12:59 +02:00
Matias Griese
158874039a Fixed bad str_replace('...', null) parameter in CSVFormatter 2019-12-02 20:11:15 +02:00
Andy Miller
de6c35f4ab Merge branch 'release/1.6.18' 2019-12-02 10:33:06 -07:00
Andy Miller
46816a74e9 Merge tag '1.6.18' into develop
Release v1.6.18
2019-12-02 10:33:06 -07:00
Andy Miller
1111c3d1b1 prepare for release 2019-12-02 10:32:06 -07:00
Andy Miller
e919685ad3 minor vendor update 2019-12-02 10:29:41 -07:00
Andy Miller
238ba9b9b4 PHP 7.4 fix for Pages::buildSort() 2019-12-02 08:53:17 -07:00
Andy Miller
1d966a0c92 Vendor lib updates 2019-12-02 08:41:32 -07:00
Jacob Peck
e3a6436031 Fix rewrite rule for lighttpd config (#2721)
Just plain doesn't work without this!  Was probably omitted in error.
2019-11-18 06:13:51 -07:00
Matias Griese
f59441eb55 Fixed fatal error when $page->id() is null [#2731] 2019-11-14 12:21:28 +02:00
Xaver Maierhofer
fcc0c5e345 Correct download argument annotation (#2727) 2019-11-09 20:49:33 -07:00
Andy Miller
c862b0bc26 Merge branch 'release/1.6.17' 2019-11-06 17:51:32 -07:00
Andy Miller
575a1e4603 Merge tag '1.6.17' into develop
Release v1.6.17
2019-11-06 17:51:32 -07:00
Andy Miller
a74ccad282 fixed changelog date 2019-11-06 17:50:35 -07:00
Andy Miller
ffeb5648c6 Merge branch 'release/1.6.17' 2019-11-06 16:07:07 -07:00
Andy Miller
86c87929ec Merge tag '1.6.17' into develop
Release v1.6.17
2019-11-06 16:07:07 -07:00
Andy Miller
e0e92b843c prepare for release 2019-11-06 16:06:45 -07:00
Keith Bentrup
8678f22f6b do NOT ignore "." dirs OR ignore "." dirs and all children (#2581)
If you ignore any "files" beginning with "." including directories, then the all() method will exclude .somedir, but not .somedir/somefile. Subsequently, when trying to copy all files returned from all(), it will fail when the method tries to copy a file into a directory that has not yet been created because .somedir was omitted from the return array of all().
I found this bug when trying to install the admin plugin and ./tmp was a mount and thus rename() failed and self:copy() was invoked instead (line 365).
2019-10-23 15:39:05 -06:00
Jérôme Nadaud
8322a0cfa3 Make script name more explicit (#2637) 2019-10-17 05:57:25 -06:00
Jérôme Nadaud
ab6b82eaaa Fix cache image generation when using cropresize (#2639) 2019-10-17 05:56:46 -06:00
buzatuAda
b16e8066ca fix exception array_merge() when $this->header->metadata is not array (#2701)
Exception gets thrown when $this->header->metadata is not an array, added extra verification in order to make sure it is and array before doing array_merge()
2019-10-17 05:55:27 -06:00
Matias Griese
bc1dd2a7b4 Added working ETag (304 Not Modified) support based on the final rendered HTML 2019-10-16 23:40:08 +03:00
Djamil Legato
d11772b681 Change of Behavior: Inflector::hyphenize will now automatically trim dashes at beginning and end of a string. 2019-10-16 11:02:44 -07:00
Matias Griese
feeee9ef86 Fixed PHP 7.1 bug in Flex 2019-10-15 19:00:25 +03:00
Jeremy Gonyea
eb1b9567df Updated for latest ddev version (#2676) 2019-10-03 14:54:05 -06: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
342eac1047 Smarter CSV handling 2019-09-26 18:35:25 -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
5c9eb1cdb8 Fixed Badly encoded JSON data warning when uploading files [#2663] 2019-09-17 22:02:21 +03:00
Matias Griese
e30ab9a043 Fixed Flex user creation if file storage is being used [#2444] 2019-09-17 11:37:09 +03:00
Newb I the Newbd
651b354d3e Correct non-published collection filter evaluation (#2668) 2019-09-14 10:10:24 -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
e5cedd074b Fixed broken markdown Twig tag [#2635] 2019-08-19 10:46:03 +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
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
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
d241223aa3 update changelog 2019-08-16 07:45:40 -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
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
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
Andy Miller
618a59921a make markdown more page context aware 2019-08-09 18:23:05 -06:00
Andy Miller
039f71dc61 fix for var order in Validation.php 2019-08-09 17:16:31 -06:00
Andy Miller
27b8db4c10 Updated vendor libs 2019-08-09 13:33:35 -06:00
Matias Griese
afd53d76c2 Fixed CSV formatter not encoding strings with " and , properly 2019-07-18 16:11:46 +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
c8739c40a5 Fixed collections using page@.self: should allow modular pages if requested 2019-07-12 12:02:28 -06:00
Andy Miller
00ff9ac42d fix 2019-07-11 08:50:09 -06: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
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
Matias Griese
86b1f1fbac Workaround bug in flex forms 2019-07-02 22:01:38 +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
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
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
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
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
Newb I the Newbd
e16c81516e Make yaml_decode only return array, again 2019-06-25 10:02:40 +03:00
Matias Griese
140c9a941f Added support for custom FormFlash save locations 2019-06-24 13:15:27 +03: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
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
Matias Griese
85ec2ee3a0 Fixed error if user has no form flashes 2019-06-20 21:19:10 +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
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
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
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
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
Tracerneo
30cfe3bdfa Change minimal port number to 0 (unix socket) (#2452) 2019-06-11 08:39:26 -06:00
Andy Miller
d227a82056 Fixed regresssion issue of Utils::Url() not returning false on failure #2524 2019-05-27 11:58:53 +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
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
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
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
0411c3a98b Merge tag '1.6.8' into develop
Release v1.6.8
2019-04-23 15:02:10 -06:00
123 changed files with 5674 additions and 1648 deletions

View File

@@ -13,5 +13,5 @@ indent_style = space
indent_size = 4
# 2 space indentation
[*.{yaml,.yml}]
[*.{yaml,yml}]
indent_size = 2

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

@@ -3,6 +3,7 @@ php:
- '7.1'
- '7.2'
- '7.3'
- '7.4'
branches:
only:
- develop
@@ -47,8 +48,8 @@ before_install:
fi
- if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "7.1" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
export TRAVIS_TAG=$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4);
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.8 bash)";
go get github.com/aktau/github-release;
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.13 bash)";
go get github.com/github-release/github-release;
git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
if [ ! -z "$TRAVIS_TAG" ]; then
cd ${RT_DEVTOOLS};
@@ -56,7 +57,7 @@ before_install:
fi;
fi
before_script:
- if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-rm xdebug.ini; fi
- phpenv config-rm xdebug.ini
script:
- if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then
vendor/bin/codecept run;

View File

@@ -1,3 +1,225 @@
# v1.6.25
## 05/14/2020
1. [](#improved)
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
* Updated `PHPUserAgentParser` to 1.0.0
* Bump `Go` to version 1.13 in `travis.yaml`
# v1.6.24
## 04/27/2020
1. [](#improved)
* Added support for `X-Forwarded-Host` [#2891](https://github.com/getgrav/grav/pull/2891)
* Disable XDebug in Travis builds
# v1.6.23
## 03/19/2020
1. [](#new)
* Moved `Parsedown` 1.6 and `ParsedownExtra` 0.7 into `Grav\Framework\Parsedown` to allow fixes
* Added `aliases.php` with references to direct `\Parsedown` and `\ParsedownExtra` references
1. [](#improved)
* Upgraded `jQuery` to latest 3.4.1 version [#2859](https://github.com/getgrav/grav/issues/2859)
1. [](#bugfix)
* Fixed PHP 7.4 issue in ParsedownExtra [#2832](https://github.com/getgrav/grav/issues/2832)
* Fix for [user reported](https://twitter.com/OriginalSicksec) CVE path-based open redirect
* Fix for `stream_set_option` error with PHP 7.4 via Toolbox#28 [#2850](https://github.com/getgrav/grav/issues/2850)
# v1.6.22
## 03/05/2020
1. [](#new)
* Added `Pages::reset()` method
1. [](#improved)
* Updated Negotiation library to address issues [#2513](https://github.com/getgrav/grav/issues/2513)
1. [](#bugfix)
* Fixed issue with search plugins not being able to switch between page translations
* Fixed issues with `Pages::baseRoute()` not picking up active language reliably
* Reverted `validation: strict` fix as it breaks sites, see [#1273](https://github.com/getgrav/grav/issues/1273)
# v1.6.21
## 02/11/2020
1. [](#new)
* Added `ConsoleCommand::setLanguage()` method to set language to be used from CLI
* Added `ConsoleCommand::initializeGrav()` method to properly set up Grav instance to be used from CLI
* Added `ConsoleCommand::initializePlugins()`method to properly set up all plugins to be used from CLI
* Added `ConsoleCommand::initializeThemes()`method to properly set up current theme to be used from CLI
* Added `ConsoleCommand::initializePages()` method to properly set up pages to be used from CLI
1. [](#improved)
* Vendor updates
1. [](#bugfix)
* Fixed `bin/plugin` CLI calling `$themes->init()` way too early (removed it, use above methods instead)
* Fixed call to `$grav['page']` crashing CLI
* Fixed encoding problems when PHP INI setting `default_charset` is not `utf-8` [#2154](https://github.com/getgrav/grav/issues/2154)
# v1.6.20
## 02/03/2020
1. [](#bugfix)
* Fixed incorrect routing caused by `str_replace()` in `Uri::init()` [#2754](https://github.com/getgrav/grav/issues/2754)
* Fixed session cookie is being set twice in the HTTP header [#2745](https://github.com/getgrav/grav/issues/2745)
* Fixed session not restarting if user was invalid (downgrading from Grav 1.7)
* Fixed filesystem iterator calls with non-existing folders
* Fixed `checkbox` field not being saved, requires also Form v4.0.2 [#1225](https://github.com/getgrav/grav/issues/1225)
* Fixed `validation: strict` not working in blueprints [#1273](https://github.com/getgrav/grav/issues/1273)
* Fixed `Data::filter()` removing empty fields (such as empty list) by default [#2805](https://github.com/getgrav/grav/issues/2805)
* Fixed fatal error with non-integer page param value [#2803](https://github.com/getgrav/grav/issues/2803)
* Fixed `Assets::addInlineJs()` parameter type mismatch between v1.5 and v1.6 [#2659](https://github.com/getgrav/grav/issues/2659)
* Fixed `site.metadata` saving issues [#2615](https://github.com/getgrav/grav/issues/2615)
# v1.6.19
## 12/04/2019
1. [](#new)
* Catch PHP 7.4 deprecation messages and report them in debugbar instead of throwing fatal error
1. [](#bugfix)
* Fixed fatal error when calling `{{ grav.undefined }}`
* Fixed multiple issues when there are no pages in the site
* PHP 7.4 fix for [#2750](https://github.com/getgrav/grav/issues/2750)
# v1.6.18
## 12/02/2019
1. [](#bugfix)
* PHP 7.4 fix in `Pages::buildSort()`
* Updated vendor libraries for PHP 7.4 fixes in Twig and other libraries
* Fixed fatal error when `$page->id()` is null [#2731](https://github.com/getgrav/grav/pull/2731)
* Fixed cache conflicts on pages with no set id
* Fix rewrite rule for for `lighttpd` default config [#721](https://github.com/getgrav/grav/pull/2721)
# v1.6.17
## 11/06/2019
1. [](#new)
* Added working ETag (304 Not Modified) support based on the final rendered HTML
1. [](#improved)
* Safer file handling + customizable null char replacement in `CsvFormatter::decode()`
* Change of Behavior: `Inflector::hyphenize` will now automatically trim dashes at beginning and end of a string.
* Change in Behavior for `Folder::all()` so no longer fails if trying to copy non-existent dot file [#2581](https://github.com/getgrav/grav/pull/2581)
* renamed composer `test-plugins` script to `phpstan-plugins` to be more explicit [#2637](https://github.com/getgrav/grav/pull/2637)
1. [](#bugfix)
* Fixed PHP 7.1 bug in FlexMedia
* Fix cache image generation when using cropResize [#2639](https://github.com/getgrav/grav/pull/2639)
* Fix `array_merge()` exception with non-array page header metadata [#2701](https://github.com/getgrav/grav/pull/2701)
# 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
@@ -25,18 +247,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
@@ -97,7 +319,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
@@ -173,7 +395,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
@@ -194,7 +416,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)
@@ -219,7 +441,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
@@ -272,7 +494,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

@@ -28,6 +28,13 @@ if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
@@ -55,7 +62,7 @@ $grav->setup($environment);
$grav['config']->init();
$grav['uri']->init();
$grav['users'];
$grav['accounts'];
$app = new Application('Grav Package Manager', GRAV_VERSION);
$app->addCommands(array(

View File

@@ -25,6 +25,17 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
}
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
$climate = new League\CLImate\CLImate;
$climate->arguments->add([
'environment' => [
@@ -42,10 +53,6 @@ $environment = $climate->arguments->get('environment');
$grav = Grav::instance(array('loader' => $autoload));
$grav->setup($environment);
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}

View File

@@ -32,6 +32,13 @@ if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
@@ -51,12 +58,7 @@ $environment = $climate->arguments->get('environment');
$grav = Grav::instance(array('loader' => $autoload));
$grav->setup($environment);
$grav['config']->init();
$grav['uri']->init();
$grav['users'];
$grav['plugins']->init();
$grav['themes']->init();
$grav->initializeCli();
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
$pattern = '([A-Z]\w+Command\.php)';
@@ -79,19 +81,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 +112,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

@@ -2,7 +2,13 @@
"name": "getgrav/grav",
"type": "project",
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"],
"keywords": [
"cms",
"flat-file cms",
"flat cms",
"flatfile cms",
"php"
],
"homepage": "https://getgrav.org",
"license": "MIT",
"require": {
@@ -16,33 +22,27 @@
"symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-php72": "^1.9",
"symfony/polyfill-php73": "^1.9",
"psr/simple-cache": "^1.0",
"psr/http-message": "^1.0",
"psr/http-server-middleware": "^1.0",
"kodus/psr7-server": "*",
"nyholm/psr7": "^1.0",
"twig/twig": "~1.35",
"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",
"twig/twig": "~1.40",
"symfony/yaml": "~4.2.0",
"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",
"filp/whoops": "~2.2",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0",
"gregwar/image": "2.*",
"donatj/phpuseragentparser": "~0.10",
"donatj/phpuseragentparser": "~1.0",
"pimple/pimple": "~3.2",
"rockettheme/toolbox": "~1.4",
"rockettheme/toolbox": "~1.4.0",
"maximebf/debugbar": "~1.15",
"league/climate": "^3.4",
"antoligy/dom-string-iterators": "^1.0",
@@ -50,7 +50,7 @@
"composer/ca-bundle": "^1.0",
"dragonmantank/cron-expression": "^1.2",
"phive/twig-extensions-deferred": "^1.0",
"willdurand/negotiation": "^2.3"
"willdurand/negotiation": "2.x-dev"
},
"require-dev": {
"codeception/codeception": "^2.4",
@@ -83,16 +83,23 @@
"psr-4": {
"Grav\\": "system/src/Grav"
},
"files": ["system/defines.php"]
"files": [
"system/defines.php",
"system/aliases.php"
]
},
"archive": {
"exclude": ["VERSION"]
"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",
"test-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"phpstan-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit"
},

1257
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,16 @@ 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>");
}
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
// Ensure vendor libraries exist
$autoload = __DIR__ . '/vendor/autoload.php';
if (!is_file($autoload)) {
@@ -32,15 +42,6 @@ $loader = require $autoload;
use Grav\Common\Grav;
use RocketTheme\Toolbox\Event\Event;
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
// Set internal encoding if mbstring loaded
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
mb_internal_encoding('UTF-8');
// Get the Grav instance
$grav = Grav::instance(
array(

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

5
system/aliases.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
/** Moved from non-namespaced classes to Grav Framework */
class_alias(Grav\Framework\Parsedown\Parsedown::class, '\Parsedown');
class_alias(Grav\Framework\Parsedown\ParsedownExtra::class, '\ParsedownExtra');

File diff suppressed because one or more lines are too long

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

View File

@@ -1379,6 +1379,51 @@ form:
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
http_x_forwarded.protocol:
type: toggle
label: HTTP_X_FORWARDED_PROTO Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.host:
type: toggle
label: HTTP_X_FORWARDED_HOST Enabled
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.port:
type: toggle
label: HTTP_X_FORWARDED_PORT Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.ip:
type: toggle
label: HTTP_X_FORWARDED IP Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
accounts.type:
type: hidden

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,13 @@ config:
title: Accounts
icon: fa-users
authorize: ['admin.users', 'admin.accounts', 'admin.super']
form:
fields:
username:
flex-disabled@: exists
disabled: false
flex-readonly@: exists
readonly: false
validate:
required: true

View File

@@ -10,6 +10,11 @@ custom_base_url: '' # Set the base_url manually, e.
username_regex: '^[a-z0-9_-]{3,16}$' # Only lowercase chars, digits, dashes, underscores. 3 - 16 chars
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' # At least one number, one uppercase and lowercase letter, and be at least 8+ chars
intl_enabled: true # Special logic for PHP International Extension (mod_intl)
http_x_forwarded: # Configuration options for the various HTTP_X_FORWARD headers
protocol: true
host: false
port: true
ip: true
languages:
supported: [] # List of languages supported. eg: [en, fr, de]

View File

@@ -8,7 +8,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.6.8');
define('GRAV_VERSION', '1.6.25');
define('GRAV_TESTING', false);
define('DS', '/');

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,21 @@ 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);
@@ -49,12 +51,21 @@ trait LegacyAssetsTrait
// special case to handle old attributes being passed in
if (isset($arguments['attributes'])) {
$old_attributes = $arguments['attributes'];
$arguments = array_merge($arguments, $old_attributes);
if (is_array($old_attributes)) {
$arguments = array_merge($arguments, $old_attributes);
} else {
$arguments['type'] = $old_attributes;
}
}
unset($arguments['attributes']);
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

@@ -9,6 +9,8 @@
namespace Grav\Common;
use function donatj\UserAgent\parse_user_agent;
/**
* Internally uses the PhpUserAgent package https://github.com/donatj/PhpUserAgent
*/

View File

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

@@ -378,14 +378,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

@@ -136,7 +136,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
$messages = $this->checkRequired($data, $rules);
foreach ($data as $key => $field) {
foreach ($data as $key => $child) {
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null;
@@ -147,10 +147,10 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
$messages += Validation::validate($field, $rule);
} elseif (\is_array($field) && \is_array($val)) {
$messages += Validation::validate($child, $rule);
} elseif (\is_array($child) && \is_array($val)) {
// Array has been defined in blueprints.
$messages += $this->validateArray($field, $val);
$messages += $this->validateArray($child, $val);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
// Undefined/extra item.
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));

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

@@ -32,6 +32,12 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
/** @var File */
protected $storage;
/** @var bool */
private $missingValuesAsNull = false;
/** @var bool */
private $keepEmptyValues = true;
/**
* @param array $items
* @param Blueprint|callable $blueprints
@@ -42,6 +48,28 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
$this->blueprints = $blueprints;
}
/**
* @param bool $value
* @return $this
*/
public function setKeepEmptyValues(bool $value)
{
$this->keepEmptyValues = $value;
return $this;
}
/**
* @param bool $value
* @return $this
*/
public function setMissingValuesAsNull(bool $value)
{
$this->missingValuesAsNull = $value;
return $this;
}
/**
* Get value by using dot notation for nested arrays/objects.
*
@@ -202,8 +230,8 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
public function filter()
{
$args = func_get_args();
$missingValuesAsNull = (bool)(array_shift($args) ?: false);
$keepEmptyValues = (bool)(array_shift($args) ?: false);
$missingValuesAsNull = (bool)(array_shift($args) ?? $this->missingValuesAsNull);
$keepEmptyValues = (bool)(array_shift($args) ?? $this->keepEmptyValues);
$this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);

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,20 @@ class Validation
return (string) $value;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return string|null
*/
protected static function filterCheckbox($value, array $params, array $field)
{
$value = (string)$value;
$field_value = (string)($field['value'] ?? '1');
return $value === $field_value ? $value : null;
}
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 +586,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

@@ -347,7 +347,7 @@ class Debugger
*/
public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline)
{
if ($errno !== E_USER_DEPRECATED) {
if ($errno !== E_USER_DEPRECATED && $errno !== E_DEPRECATED) {
if ($this->errorHandler) {
return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
}

View File

@@ -22,6 +22,10 @@ abstract class Folder
*/
public static function lastModifiedFolder($path)
{
if (!file_exists($path)) {
return 0;
}
$last_modified = 0;
/** @var UniformResourceLocator $locator */
@@ -56,6 +60,10 @@ abstract class Folder
*/
public static function lastModifiedFile($path, $extensions = 'md|yaml')
{
if (!file_exists($path)) {
return 0;
}
$last_modified = 0;
/** @var UniformResourceLocator $locator */
@@ -92,21 +100,24 @@ abstract class Folder
*/
public static function hashAllFiles($path)
{
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
$files = [];
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($path)) {
$directory = $locator->getRecursiveIterator($path, $flags);
} else {
$directory = new \RecursiveDirectoryIterator($path, $flags);
}
if (file_exists($path)) {
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($path)) {
$directory = $locator->getRecursiveIterator($path, $flags);
} else {
$directory = new \RecursiveDirectoryIterator($path, $flags);
}
foreach ($iterator as $file) {
$files[] = $file->getPathname() . '?'. $file->getMTime();
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
$files[] = $file->getPathname() . '?'. $file->getMTime();
}
}
return md5(serialize($files));
@@ -199,6 +210,9 @@ abstract class Folder
if ($path === false) {
throw new \RuntimeException("Path doesn't exist.");
}
if (!file_exists($path)) {
return [];
}
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
$pattern = $params['pattern'] ?? null;
@@ -235,7 +249,7 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
// Ignore hidden files.
if (strpos($file->getFilename(), '.') === 0) {
if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) {
continue;
}
if (!$folders && $file->isDir()) {

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,8 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
class Package {
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

@@ -11,6 +11,7 @@ namespace Grav\Common\GPM\Local;
use Grav\Common\Data\Data;
use Grav\Common\GPM\Common\Package as BasePackage;
use Grav\Framework\Parsedown\Parsedown;
class Package extends BasePackage
{
@@ -23,7 +24,7 @@ class Package extends BasePackage
$this->settings = $package->toArray();
$html_description = \Parsedown::instance()->line($this->__get('description'));
$html_description = Parsedown::instance()->line($this->__get('description'));
$this->data->set('slug', $package->__get('slug'));
$this->data->set('description_html', $html_description);
$this->data->set('description_plain', strip_tags($html_description));

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

@@ -170,6 +170,29 @@ class Grav extends Container
return $this;
}
/**
* Initialize CLI environment.
*
* Call after `$grav->setup($environment)`
*
* - Load configuration
* - Disable debugger
* - Set timezone, locale
* - Load plugins
* - Set Users type to be used in the site
*
* This method WILL NOT initialize assets, twig or pages.
*
* @param string|null $environment
* @return $this
*/
public function initializeCli()
{
InitializeProcessor::initializeCli($this);
return $this;
}
/**
* Process a request
*/
@@ -247,9 +270,21 @@ class Grav extends Container
$collection = new RequestHandler($this->middleware, $default, $container);
$response = $collection->handle($this['request']);
$body = $response->getBody();
// Handle ETag and If-None-Match headers.
if ($response->getHeaderLine('ETag') === '1') {
$etag = md5($body);
$response = $response->withHeader('ETag', $etag);
if ($this['request']->getHeaderLine('If-None-Match') === $etag) {
$response = $response->withStatus(304);
$body = '';
}
}
$this->header($response);
echo $response->getBody();
echo $body;
$debugger->render();
@@ -281,7 +316,10 @@ class Grav extends Container
/** @var Uri $uri */
$uri = $this['uri'];
//Check for code in route
// Clean route for redirect
$route = preg_replace("#^\/[\\\/]+\/#", '/', $route);
// Check for code in route
$regex = '/.*(\[(30[1-7])\])$/';
preg_match($regex, $route, $matches);
if ($matches) {
@@ -427,11 +465,16 @@ class Grav extends Container
* Used to call closures.
*
* Source: http://stackoverflow.com/questions/419804/closures-as-class-members
*
* @param string $method
* @param array $args
* @return
*/
public function __call($method, $args)
{
$closure = $this->{$method};
\call_user_func_array($closure, $args);
$closure = $this->{$method} ?? null;
return is_callable($closure) ? $closure(...$args) : null;
}
/**

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

@@ -234,7 +234,7 @@ class Truncator {
}
/**
* @inheritDoc
*
*/
public function truncate(
$text,

View File

@@ -20,7 +20,8 @@ class YamlLinter
{
$errors = static::lintConfig();
$errors = $errors + static::lintPages();
$errors = $errors + static::lintBlueprints();
return $errors;
}
@@ -34,6 +35,18 @@ class YamlLinter
return static::recurseFolder('config://');
}
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

@@ -193,6 +193,8 @@ class Inflector
$regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
$regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
$regex4 = trim($regex4, '-');
return strtolower($regex4);
}

View File

@@ -104,6 +104,11 @@ class Language
public function getAvailable()
{
$languagesArray = $this->languages; //Make local copy
$languagesArray = array_map(function($value) {
return preg_quote($value);
}, $languagesArray);
sort($languagesArray);
return implode('|', array_reverse($languagesArray));
@@ -229,7 +234,7 @@ class Language
}
/**
* Get's a URL prefix based on configuration
* Get a URL prefix based on configuration
*
* @param string|null $lang
* @return string

View File

@@ -10,20 +10,31 @@
namespace Grav\Common\Markdown;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Markdown\Excerpts;
use Grav\Framework\Parsedown\Parsedown as ParsedownLib;
class Parsedown extends \Parsedown
class Parsedown extends ParsedownLib
{
use ParsedownGravTrait;
/**
* 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,22 +10,33 @@
namespace Grav\Common\Markdown;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Markdown\Excerpts;
use Grav\Framework\Parsedown\ParsedownExtra as ParsedownExtraLib;
class ParsedownExtra extends \ParsedownExtra
class ParsedownExtra extends ParsedownExtraLib
{
use ParsedownGravTrait;
/**
* 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

@@ -125,5 +125,5 @@ trait MediaTrait
/**
* @return string
*/
abstract protected function getCacheKey();
abstract protected function getCacheKey(): string;
}

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']->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 = $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

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

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

@@ -19,6 +19,7 @@ use Grav\Common\Markdown\Parsedown;
use Grav\Common\Markdown\ParsedownExtra;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Media\Traits\MediaTrait;
use Grav\Common\Page\Markdown\Excerpts;
use Grav\Common\Taxonomy;
use Grav\Common\Uri;
use Grav\Common\Utils;
@@ -27,7 +28,6 @@ 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');
@@ -529,9 +529,9 @@ class Page implements PageInterface
$headers['Last-Modified'] = $last_modified_date;
}
// Calculate ETag based on the raw file
// Ask Grav to calculate ETag from the final content.
if ($this->eTag()) {
$headers['ETag'] = '"' . md5($this->raw() . $this->modified()).'"';
$headers['ETag'] = '1';
}
// Set Vary: Accept-Encoding header
@@ -608,12 +608,12 @@ class Page implements PageInterface
return $content;
}
return mb_strimwidth($content, 0, $size, '...', 'utf-8');
return mb_strimwidth($content, 0, $size, '...', 'UTF-8');
}
$summary = Utils::truncateHtml($content, $size);
return html_entity_decode($summary);
return html_entity_decode($summary, ENT_COMPAT | ENT_HTML401, 'UTF-8');
}
/**
@@ -659,7 +659,7 @@ class Page implements PageInterface
// Load cached content
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
$cache_id = md5('page' . $this->id());
$cache_id = md5('page' . $this->getCacheKey());
$content_obj = $cache->fetch($cache_id);
if (is_array($content_obj)) {
@@ -819,23 +819,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);
@@ -857,7 +865,7 @@ class Page implements PageInterface
public function cachePageContent()
{
$cache = Grav::instance()['cache'];
$cache_id = md5('page' . $this->id());
$cache_id = md5('page' . $this->getCacheKey());
$cache->save($cache_id, ['content' => $this->content, 'content_meta' => $this->content_meta]);
}
@@ -1192,7 +1200,7 @@ class Page implements PageInterface
/**
* @return string
*/
protected function getCacheKey()
public function getCacheKey(): string
{
return $this->id();
}
@@ -1397,12 +1405,12 @@ class Page implements PageInterface
return $this->template_format;
}
// Use content negotitation via the `accept:` header
$http_accept = $_SERVER['HTTP_ACCEPT'] ?? false;
// Use content negotiation via the `accept:` header
$http_accept = $_SERVER['HTTP_ACCEPT'] ?? null;
if (is_string($http_accept)) {
$negotiator = new Negotiator();
$supported_types = Grav::instance()['config']->get('system.pages.types', ['html', 'json']);
$supported_types = Utils::getSupportPageTypes(['html', 'json']);
$priorities = Utils::getMimeTypes($supported_types);
$media_type = $negotiator->getBest($http_accept, $priorities);
@@ -1686,9 +1694,9 @@ class Page implements PageInterface
$metadata['generator'] = 'GravCMS';
// Get initial metadata for the page
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata'));
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata', []));
if (isset($this->header->metadata)) {
if (isset($this->header->metadata) && is_array($this->header->metadata)) {
// Merge any site.metadata settings in with page metadata
$metadata = array_merge($metadata, $this->header->metadata);
}
@@ -2001,6 +2009,10 @@ class Page implements PageInterface
*/
public function id($var = null)
{
if (null === $this->id) {
// We need to set unique id to avoid potential cache conflicts between pages.
$var = time() . md5($this->filePath());
}
if ($var !== null) {
// store unique per language
$active_lang = Grav::instance()['language']->getLanguage() ?: '';
@@ -2816,7 +2828,7 @@ class Page implements PageInterface
if ($pagination) {
$params = $collection->params();
$limit = $params['limit'] ?? 0;
$limit = (int)($params['limit'] ?? 0);
$start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0;
if ($limit && $collection->count() > $limit) {
@@ -2847,9 +2859,9 @@ class Page implements PageInterface
$result = [];
foreach ((array)$value as $key => $val) {
if (is_int($key)) {
$result = $result + $this->evaluate($val)->toArray();
$result = $result + $this->evaluate($val, $only_published)->toArray();
} else {
$result = $result + $this->evaluate([$key => $val])->toArray();
$result = $result + $this->evaluate([$key => $val], $only_published)->toArray();
}
}
@@ -2930,7 +2942,7 @@ class Page implements PageInterface
case 'page':
case 'self':
$results = new Collection();
$results = $results->addPage($page)->nonModular();
$results = $results->addPage($page);
break;
case 'descendants':

View File

@@ -88,6 +88,15 @@ class Pages
*/
protected $ignore_hidden;
/** @var string */
protected $check_method;
protected $pages_cache_id;
protected $initialized = false;
protected $active_lang;
/**
* @var Types
*/
@@ -98,8 +107,6 @@ class Pages
*/
static protected $home_route;
protected $pages_cache_id;
/**
* Constructor
*
@@ -138,7 +145,7 @@ class Pages
*/
public function baseRoute($lang = null)
{
$key = $lang ?: 'default';
$key = $lang ?: $this->active_lang ?: 'default';
if (!isset($this->baseRoute[$key])) {
/** @var Language $language */
@@ -226,11 +233,30 @@ class Pages
return $this->baseUrl($lang, $absolute) . Uri::filterPath($route);
}
public function setCheckMethod($method)
{
$this->check_method = strtolower($method);
}
/**
* Reset pages (used in search indexing etc).
*/
public function reset()
{
$this->initialized = false;
$this->init();
}
/**
* 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');
@@ -240,6 +266,10 @@ class Pages
$this->children = [];
$this->routes = [];
if (!$this->check_method) {
$this->setCheckMethod($config->get('system.cache.check.method', 'file'));
}
$this->buildPages();
}
@@ -940,6 +970,9 @@ class Pages
$pages_dir = $locator->findResource('page://');
// Set active language
$this->active_lang = $language->getActive();
if ($config->get('system.cache.enabled')) {
/** @var Cache $cache */
$cache = $this->grav['cache'];
@@ -947,7 +980,7 @@ class Pages
$taxonomy = $this->grav['taxonomy'];
// 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,17 +997,19 @@ 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) {
$cached = $cache->fetch($this->pages_cache_id);
if ($cached) {
$this->grav['debugger']->addMessage('Page cache hit.');
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cached;
// If pages was found in cache, set the taxonomy
$taxonomy->taxonomy($taxonomy_map);
} else {
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
// 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.');
$taxonomy->taxonomy($taxonomy_map);
}
} else {
$this->recurse($pages_dir);
@@ -1243,14 +1278,13 @@ class Pages
{
$list = [];
$header_default = null;
$header_query = null;
$header_query = [];
// do this header query work only once
if (strpos($order_by, 'header.') === 0) {
$header_query = explode('|', str_replace('header.', '', $order_by));
if (isset($header_query[1])) {
$header_default = $header_query[1];
}
$query = explode('|', str_replace('header.', '', $order_by), 2);
$header_query = array_shift($query) ?? '';
$header_default = array_shift($query);
}
foreach ($pages as $key => $info) {
@@ -1288,11 +1322,17 @@ class Pages
case 'folder':
$list[$key] = $child->folder();
break;
case (is_string($header_query[0])):
$child_header = new Header((array)$child->header());
$header_value = $child_header->get($header_query[0]);
case 'manual':
case 'default':
default:
if (is_string($header_query)) {
$child_header = $child->header();
if (!$child_header instanceof Header) {
$child_header = new Header((array)$child_header);
}
$header_value = $child_header->get($header_query);
if (is_array($header_value)) {
$list[$key] = implode(',',$header_value);
$list[$key] = implode(',', $header_value);
} elseif ($header_value) {
$list[$key] = $header_value;
} else {
@@ -1300,11 +1340,9 @@ class Pages
}
$sort_flags = $sort_flags ?: SORT_REGULAR;
break;
case 'manual':
case 'default':
default:
$list[$key] = $key;
$sort_flags = $sort_flags ?: SORT_REGULAR;
}
$list[$key] = $key;
$sort_flags = $sort_flags ?: SORT_REGULAR;
}
}

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

@@ -10,6 +10,7 @@
namespace Grav\Common\Processors;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Framework\Session\Exceptions\SessionException;
@@ -22,6 +23,22 @@ class InitializeProcessor extends ProcessorBase
public $id = 'init';
public $title = 'Initialize';
/** @var bool */
private static $cli_initialized = false;
/**
* @param Grav $grav
*/
public static function initializeCli(Grav $grav)
{
if (!static::$cli_initialized) {
static::$cli_initialized = true;
$instance = new static($grav);
$instance->processCli();
}
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$this->startTimer();
@@ -77,4 +94,35 @@ class InitializeProcessor extends ProcessorBase
return $handler->handle($request);
}
public function processCli(): void
{
// Load configuration.
$this->container['config']->init();
$this->container['plugins']->setup();
// Disable debugger.
$this->container['debugger']->enabled(false);
// Set timezone, locale.
/** @var Config $config */
$config = $this->container['config'];
$timezone = $config->get('system.timezone');
if ($timezone) {
date_default_timezone_set($timezone);
}
$this->container->setLocale();
// Load plugins.
$this->container['plugins']->init();
// Initialize URI.
/** @var Uri $uri */
$uri = $this->container['uri'];
$uri->init();
// Load accounts.
// TODO: remove in 2.0.
$this->container['accounts'];
}
}

View File

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

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

View File

@@ -26,6 +26,19 @@ class PagesServiceProvider implements ServiceProviderInterface
return new Pages($c);
};
if (\defined('GRAV_CLI')) {
$container['page'] = static function ($c) {
$path = $c['locator']->findResource('system://pages/notfound.md');
$page = new Page();
$page->init(new \SplFileInfo($path));
$page->routable(false);
return $page;
};
return;
}
$container['page'] = function ($c) {
/** @var Grav $c */

View File

@@ -31,8 +31,8 @@ class RequestServiceProvider implements ServiceProviderInterface
return $creator->fromGlobals();
};
$container['route'] = function() {
return Uri::getCurrentRoute();
};
$container['route'] = $container->factory(function() {
return clone Uri::getCurrentRoute();
});
}
}

View File

@@ -13,6 +13,7 @@ use Grav\Common\Config\Config;
use Grav\Common\Debugger;
use Grav\Common\Session;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\Session\Message;
@@ -49,14 +50,17 @@ class SessionServiceProvider implements ServiceProviderInterface
// Activate admin if we're inside the admin path.
$is_admin = false;
if ($config->get('plugins.admin.enabled')) {
$base = '/' . trim($config->get('plugins.admin.route'), '/');
$admin_base = '/' . trim($config->get('plugins.admin.route'), '/');
// Uri::route() is not processed yet, let's quickly get what we need.
$current_route = str_replace(Uri::filterPath($uri->rootUrl(false)), '', parse_url($uri->url(true), PHP_URL_PATH));
// Test to see if path starts with a supported language + admin base
$lang = Utils::pathPrefixedByLangCode($current_route);
$lang_admin_base = '/' . $lang . $admin_base;
// Check no language, simple language prefix (en) and region specific language prefix (en-US).
$pos = strpos($current_route, $base);
if ($pos === 0 || $pos === 3 || $pos === 6) {
if (Utils::startsWith($current_route, $admin_base) || Utils::startsWith($current_route, $lang_admin_base)) {
$cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
$enabled = $is_admin = true;
}

View File

@@ -100,7 +100,19 @@ class Themes extends Iterator
}
$theme = $directory->getFilename();
$result = $this->get($theme);
try {
$result = $this->get($theme);
} catch (\Exception $e) {
$exception = new \RuntimeException(sprintf('Theme %s: %s', $theme, $e->getMessage()), $e->getCode(), $e);
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
$debugger->addMessage("Theme {$theme} cannot be loaded, please check Exceptions tab", 'error');
$debugger->addException($exception);
continue;
}
if ($result) {
$list[$theme] = $result;
@@ -196,8 +208,7 @@ class Themes extends Iterator
foreach ($themeClassFormat as $themeClass) {
if (class_exists($themeClass)) {
$themeClassName = $themeClass;
$class = new $themeClassName($grav, $config, $name);
$class = new $themeClass($grav, $config, $name);
break;
}
}

View File

@@ -41,6 +41,6 @@ class TwigNodeMarkdown extends Node implements NodeOutputInterface
->write('$lines = explode("\n", $content);' . PHP_EOL)
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
->write('$content = join("\n", $content);' . PHP_EOL)
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($content);' . PHP_EOL);
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($context, $content);' . PHP_EOL);
}
}

View File

@@ -84,7 +84,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]),
new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
@@ -455,7 +455,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
* Gets a human readable output for cron sytnax
* Gets a human readable output for cron syntax
*
* @param $at
* @return string
@@ -613,12 +613,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* @param string $string
*
* @param array $context
* @param bool $block Block or Line processing
* @return mixed|string
*/
public function markdownFunction($string, $block = true)
public function markdownFunction($context, $string, $block = true)
{
return Utils::processMarkdown($string, $block);
$page = $context['page'] ?? null;
return Utils::processMarkdown($string, $block, $page);
}
/**
@@ -1004,10 +1006,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function authorize($action)
{
/** @var UserInterface $user */
$user = $this->grav['user'];
/** @var UserInterface|null $user */
$user = $this->grav['user'] ?? null;
if (!$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
if (!$user || !$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
return false;
}
@@ -1053,7 +1055,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0)
{
return json_decode(html_entity_decode($str), $assoc, $depth, $options);
return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options);
}
/**
@@ -1136,7 +1138,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
* Get's the Exif data for a file
* Get the Exif data for a file
*
* @param string $image
* @param bool $raw
@@ -1154,7 +1156,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$exif_reader = $this->grav['exif']->getReader();
if ($image & file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
if ($image && file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
$exif_data = $exif_reader->read($image);

View File

@@ -151,7 +151,7 @@ class Uri
$this->url = $this->base . $this->uri;
$uri = str_replace(static::filterPath($this->root), '', $this->url);
$uri = Utils::replaceFirstOccurrence(static::filterPath($this->root), '', $this->url);
// remove the setup.php based base if set:
$setup_base = $grav['pages']->base();
@@ -195,7 +195,7 @@ class Uri
// set the new url
$this->url = $this->root . $path;
$this->path = static::cleanPath($path);
$this->content_path = trim(str_replace($this->base, '', $this->path), '/');
$this->content_path = trim(Utils::replaceFirstOccurrence($this->base, '', $this->path), '/');
if ($this->content_path !== '') {
$this->paths = explode('/', $this->content_path);
}
@@ -306,7 +306,7 @@ class Uri
public function param($id)
{
if (isset($this->params[$id])) {
return html_entity_decode(rawurldecode($this->params[$id]));
return html_entity_decode(rawurldecode($this->params[$id]), ENT_COMPAT | ENT_HTML401, 'UTF-8');
}
return false;
@@ -340,7 +340,7 @@ class Uri
return $this->url;
}
$url = str_replace($this->base, '', rtrim($this->url, '/'));
$url = Utils::replaceFirstOccurrence($this->base, '', rtrim($this->url, '/'));
return $url ?: '/';
}
@@ -489,7 +489,7 @@ class Uri
return $this->uri;
}
return str_replace($this->root_path, '', $this->uri);
return Utils::replaceFirstOccurrence($this->root_path, '', $this->uri);
}
/**
@@ -531,7 +531,7 @@ class Uri
return $this->root;
}
return str_replace($this->base, '', $this->root);
return Utils::replaceFirstOccurrence($this->base, '', $this->root);
}
/**
@@ -541,7 +541,9 @@ class Uri
*/
public function currentPage()
{
return $this->params['page'] ?? 1;
$page = (int)($this->params['page'] ?? 1);
return max(1, $page);
}
/**
@@ -629,9 +631,9 @@ class Uri
{
if (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
} elseif (getenv('HTTP_X_FORWARDED_FOR')) {
} elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
$ip = getenv('HTTP_X_FORWARDED_FOR');
} elseif (getenv('HTTP_X_FORWARDED')) {
} elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
$ip = getenv('HTTP_X_FORWARDED');
} elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR');
@@ -783,7 +785,7 @@ class Uri
}
// special check to see if path checking is required.
$just_path = str_replace($normalized_url, '', $normalized_path);
$just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
if ($normalized_url === '/' || $just_path === $page->path()) {
$url_path = $normalized_url;
} else {
@@ -852,7 +854,7 @@ class Uri
}
// strip base from this path
$target_path = str_replace($uri->rootUrl(), '', $target_path);
$target_path = Utils::replaceFirstOccurrence($uri->rootUrl(), '', $target_path);
// set to / if root
if (empty($target_path)) {
@@ -877,7 +879,7 @@ class Uri
// Handle route only
if ($route_only) {
$url_path = str_replace(static::filterPath($base_url), '', $url_path);
$url_path = Utils::replaceFirstOccurrence(static::filterPath($base_url), '', $url_path);
}
// transform back to string/array as needed
@@ -998,7 +1000,7 @@ class Uri
}
// special check to see if path checking is required.
$just_path = str_replace($normalized_url, '', $normalized_path);
$just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
if ($just_path === $page->path()) {
return $normalized_url;
}
@@ -1148,13 +1150,13 @@ class Uri
protected function createFromEnvironment(array $env)
{
// Build scheme.
if (isset($env['HTTP_X_FORWARDED_PROTO'])) {
if (isset($env['HTTP_X_FORWARDED_PROTO']) && Grav::instance()['config']->get('system.http_x_forwarded.protocol')) {
$this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
} elseif (isset($env['X-FORWARDED-PROTO'])) {
$this->scheme = $env['X-FORWARDED-PROTO'];
} elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
$this->scheme = $env['HTTP_CLOUDFRONT_FORWARDED_PROTO'];
} elseif (isset($env['REQUEST_SCHEME'])) {
} elseif (isset($env['REQUEST_SCHEME']) && empty($env['HTTPS'])) {
$this->scheme = $env['REQUEST_SCHEME'];
} else {
$https = $env['HTTPS'] ?? '';
@@ -1166,11 +1168,14 @@ class Uri
$this->password = $env['PHP_AUTH_PW'] ?? null;
// Build host.
$hostname = 'localhost';
if (isset($env['HTTP_HOST'])) {
if (isset($env['HTTP_X_FORWARDED_HOST']) && Grav::instance()['config']->get('system.http_x_forwarded.host')) {
$hostname = $env['HTTP_X_FORWARDED_HOST'];
} else if (isset($env['HTTP_HOST'])) {
$hostname = $env['HTTP_HOST'];
} elseif (isset($env['SERVER_NAME'])) {
$hostname = $env['SERVER_NAME'];
} else {
$hostname = 'localhost';
}
// Remove port from HTTP_HOST generated $hostname
$hostname = Utils::substrToString($hostname, ':');
@@ -1178,7 +1183,7 @@ class Uri
$this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
// Build port.
if (isset($env['HTTP_X_FORWARDED_PORT'])) {
if (isset($env['HTTP_X_FORWARDED_PORT']) && Grav::instance()['config']->get('system.http_x_forwarded.port')) {
$this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
} elseif (isset($env['X-FORWARDED-PORT'])) {
$this->port = (int)$env['X-FORWARDED-PORT'];
@@ -1286,7 +1291,7 @@ class Uri
}
/**
* Get's post from either $_POST or JSON response object
* Get post from either $_POST or JSON response object
* By default returns all data, or can return a single item
*
* @param string $element
@@ -1345,7 +1350,7 @@ class Uri
*/
public function isValidExtension($extension)
{
$valid_page_types = implode('|', Grav::instance()['config']->get('system.pages.types'));
$valid_page_types = implode('|', Utils::getSupportPageTypes());
// Strip the file extension for valid page types
if (preg_match('/(' . $valid_page_types . ')/', $extension)) {

View File

@@ -118,4 +118,13 @@ class UserCollection implements UserCollectionInterface
return $file_path && unlink($file_path);
}
public function count(): int
{
// check for existence of a user account
$account_dir = $file_path = Grav::instance()['locator']->findResource('account://');
$accounts = glob($account_dir . '/*.yaml') ?: [];
return count($accounts);
}
}

View File

@@ -9,6 +9,7 @@
namespace Grav\Common\User\FlexUser;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Page\Media;
@@ -21,6 +22,7 @@ use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\Formatter\YamlFormatter;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Storage\FileStorage;
use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
use Grav\Framework\Flex\Traits\FlexMediaTrait;
use Grav\Framework\Form\FormFlashFile;
@@ -381,6 +383,31 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
return $this->getBlueprint()->extra($this->toArray());
}
/**
* @param string $name
* @return Blueprint
*/
public function getBlueprint(string $name = '')
{
$blueprint = clone parent::getBlueprint($name);
$blueprint->addDynamicHandler('flex', function (array &$field, $property, array &$call) {
$params = (array)$call['params'];
$method = array_shift($params);
if (method_exists($this, $method)) {
$value = $this->{$method}(...$params);
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
$field[$property] = array_merge_recursive($field[$property], $value);
} else {
$field[$property] = $value;
}
}
});
return $blueprint->init();
}
/**
* Return unmodified data as raw string.
*
@@ -420,6 +447,15 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
*/
public function save()
{
// TODO: We may want to handle this in the storage layer in the future.
$key = $this->getStorageKey();
if (!$key || strpos($key, '@@')) {
$storage = $this->getFlexDirectory()->getStorage();
if ($storage instanceof FileStorage) {
$this->setStorageKey($this->getKey());
}
}
$password = $this->getProperty('password');
if (null !== $password) {
$this->unsetProperty('password');
@@ -431,6 +467,20 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
return parent::save();
}
public function isAuthorized(string $action, string $scope = null, UserInterface $user = null): bool
{
if (null === $user) {
/** @var UserInterface $user */
$user = Grav::instance()['user'] ?? null;
}
if ($user instanceof User && $user->getStorageKey() === $this->getStorageKey()) {
return true;
}
return parent::isAuthorized($action, $scope, $user);
}
/**
* @return array
*/

View File

@@ -9,7 +9,7 @@
namespace Grav\Common\User\Interfaces;
interface UserCollectionInterface
interface UserCollectionInterface extends \Countable
{
/**
* Load user account.

View File

@@ -148,12 +148,13 @@ trait UserTrait
// Try looking for provider.
$provider = $this->get('provider');
if (\is_array($provider)) {
if (isset($provider['avatar_url']) && \is_string($provider['avatar_url'])) {
return $provider['avatar_url'];
$provider_options = $this->get($provider);
if (\is_array($provider_options)) {
if (isset($provider_options['avatar_url']) && \is_string($provider_options['avatar_url'])) {
return $provider_options['avatar_url'];
}
if (isset($provider['avatar']) && \is_string($provider['avatar'])) {
return $provider['avatar'];
if (isset($provider_options['avatar']) && \is_string($provider_options['avatar'])) {
return $provider_options['avatar'];
}
}

View File

@@ -13,6 +13,7 @@ use Grav\Common\Helpers\Truncator;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Markdown\Parsedown;
use Grav\Common\Markdown\ParsedownExtra;
use Grav\Common\Page\Markdown\Excerpts;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
@@ -27,55 +28,103 @@ abstract class Utils
/**
* Simple helper method to make getting a Grav URL easier
*
* @param string $input
* @param string|object $input
* @param bool $domain
* @param bool $fail_gracefully
* @return bool|null|string
*/
public static function url($input, $domain = false)
public static function url($input, $domain = false, $fail_gracefully = false)
{
if (!trim((string)$input)) {
$input = '/';
if ((!is_string($input) && !method_exists($input, '__toString')) || !trim($input)) {
if ($fail_gracefully) {
$input = '/';
} else {
return false;
}
}
if (Grav::instance()['config']->get('system.absolute_urls', false)) {
$domain = true;
}
$input = (string)$input;
if (Grav::instance()['uri']->isExternal($input)) {
if (Uri::isExternal($input)) {
return $input;
}
$grav = Grav::instance();
/** @var Uri $uri */
$uri = Grav::instance()['uri'];
$uri = $grav['uri'];
$root = $uri->rootUrl();
$input = Utils::replaceFirstOccurrence($root, '', $input);
$input = ltrim((string)$input, '/');
if (Utils::contains((string)$input, '://')) {
if (static::contains((string)$input, '://')) {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$locator = $grav['locator'];
$parts = Uri::parseUrl($input);
if ($parts) {
$resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
if (is_array($parts)) {
// Make sure we always have scheme, host, port and path.
$scheme = $parts['scheme'] ?? '';
$host = $parts['host'] ?? '';
$port = $parts['port'] ?? '';
$path = $parts['path'] ?? '';
if (isset($parts['query'])) {
$resource = $resource . '?' . $parts['query'];
if ($scheme && !$port) {
// If URL has a scheme, we need to check if it's one of Grav streams.
if (!$locator->schemeExists($scheme)) {
// If scheme does not exists as a stream, assume it's external.
return str_replace(' ', '%20', $input);
}
// Attempt to find the resource (because of parse_url() we need to put host back to path).
$resource = $locator->findResource("{$scheme}://{$host}{$path}", false);
if ($resource === false) {
if (!$fail_gracefully) {
return false;
}
// Return location where the file would be if it was saved.
$resource = $locator->findResource("{$scheme}://{$host}{$path}", false, true);
}
} elseif ($host || $port) {
// If URL doesn't have scheme but has host or port, it is external.
return str_replace(' ', '%20', $input);
}
if (!empty($resource)) {
// Add query string back.
if (isset($parts['query'])) {
$resource .= '?' . $parts['query'];
}
// Add fragment back.
if (isset($parts['fragment'])) {
$resource .= '#' . $parts['fragment'];
}
}
} else {
// Not a valid URL (can still be a stream).
$resource = $locator->findResource($input, false);
}
} else {
$root = $uri->rootUrl();
if (static::startsWith($input, $root)) {
$input = static::replaceFirstOccurrence($root, '', $input);
}
$input = ltrim($input, '/');
$resource = $input;
}
if (!$fail_gracefully && $resource === false) {
return false;
}
$domain = $domain ?: $grav['config']->get('system.absolute_urls', false);
return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
}
@@ -274,6 +323,35 @@ abstract class Utils
return (object)array_merge((array)$obj1, (array)$obj2);
}
/**
* Lowercase an entire array. Useful when combined with `in_array()`
*
* @param array $a
* @return array|false
*/
public static function arrayLower(Array $a)
{
return array_map('mb_strtolower', $a);
}
/**
* Simple function to remove item/s in an array by value
*
* @param $search array
* @param $value string|array
* @return array
*/
public static function arrayRemoveValue(Array $search, $value)
{
foreach ((array) $value as $val) {
$key = array_search($val, $search);
if ($key !== false) {
unset($search[$key]);
}
}
return $search;
}
/**
* Recursive Merge with uniqueness
*
@@ -974,17 +1052,19 @@ abstract class Utils
*
* @param string $string The path
*
* @return bool
* @return bool|string Either false or the language
*
*/
public static function pathPrefixedByLangCode($string)
{
if (strlen($string) <= 3) {
return false;
$languages_enabled = Grav::instance()['config']->get('system.languages.supported', []);
$parts = explode('/', trim($string, '/'));
if (count($parts) > 0 && in_array($parts[0], $languages_enabled)) {
return $parts[0];
}
$languages_enabled = Grav::instance()['config']->get('system.languages.supported', []);
return $string[0] === '/' && $string[3] === '/' && \in_array(substr($string, 1, 2), $languages_enabled, true);
return false;
}
/**
@@ -1054,12 +1134,9 @@ abstract class Utils
*/
private static function generateNonceString($action, $previousTick = false)
{
$username = '';
if (isset(Grav::instance()['user'])) {
$user = Grav::instance()['user'];
$username = $user->username;
}
$grav = Grav::instance();
$username = isset($grav['user']) ? $grav['user']->username : '';
$token = session_id();
$i = self::nonceTick();
@@ -1067,7 +1144,7 @@ abstract class Utils
$i--;
}
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . $grav['config']->get('security.salt'));
}
/**
@@ -1281,7 +1358,7 @@ abstract class Utils
}
/**
* Get's path based on a token
* Get path based on a token
*
* @param string $path
* @param PageInterface|null $page
@@ -1341,6 +1418,8 @@ abstract class Utils
$post_max_size = static::parseSize(ini_get('post_max_size'));
if ($post_max_size > 0) {
$max_size = $post_max_size;
} else {
$max_size = 0;
}
$upload_max = static::parseSize(ini_get('upload_max_filesize'));
@@ -1388,7 +1467,7 @@ abstract class Utils
$pow = min($pow, count($units) - 1);
// Uncomment one of the following alternatives
$bytes /= pow(1024, $pow);
$bytes /= 1024 ** $pow;
// $bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
@@ -1404,11 +1483,12 @@ abstract class Utils
{
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
$size = preg_replace('/[^0-9\.]/', '', $size);
if ($unit) {
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
} else {
return round($size);
$size = $size * pow(1024, stripos('bkmgtpezy', $unit[0]));
}
return (int) abs(round($size));
}
/**
@@ -1446,19 +1526,28 @@ abstract class Utils
*
* @param string $string
*
* @param bool $block Block or Line processing
* @param bool $block Block or Line processing
* @param null $page
* @return string
* @throws \Exception
*/
public static function processMarkdown($string, $block = true)
public static function processMarkdown($string, $block = true, $page = null)
{
$page = Grav::instance()['page'] ?? null;
$defaults = Grav::instance()['config']->get('system.pages.markdown');
$grav = Grav::instance();
$page = $page ?? $grav['page'] ?? null;
$defaults = [
'markdown' => $grav['config']->get('system.pages.markdown', []),
'images' => $grav['config']->get('system.images', [])
];
$extra = $defaults['markdown']['extra'] ?? false;
$excerpts = new Excerpts($page, $defaults);
// Initialize the preferred variant of Parsedown
if ($defaults['extra']) {
$parsedown = new ParsedownExtra($page, $defaults);
if ($extra) {
$parsedown = new ParsedownExtra($excerpts);
} else {
$parsedown = new Parsedown($page, $defaults);
$parsedown = new Parsedown($excerpts);
}
if ($block) {
@@ -1477,12 +1566,11 @@ abstract class Utils
* @param int $prefix
*
* @return string
* @throws \InvalidArgumentException if provided an invalid IP
*/
public static function getSubnet($ip, $prefix = 64)
{
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
throw new \InvalidArgumentException('Invalid IP: ' . $ip);
return $ip;
}
// Packed representation of IP
@@ -1509,4 +1597,23 @@ abstract class Utils
return $subnet;
}
/**
* Wrapper to ensure html, htm in the front of the supported page types
*
* @param array|null $defaults
* @return array|mixed
*/
public static function getSupportPageTypes(array $defaults = null)
{
$types = Grav::instance()['config']->get('system.pages.types', $defaults);
// remove html/htm
$types = static::arrayRemoveValue($types, ['html', 'htm']);
// put them back at the front
$types = array_merge(['html', 'htm'], $types);
return $types;
}
}

View File

@@ -21,6 +21,7 @@ class ClearCacheCommand extends ConsoleCommand
->setName('cache')
->setAliases(['clearcache', 'cache-clear'])
->setDescription('Clears Grav cache')
->addOption('invalidate', null, InputOption::VALUE_NONE, 'Invalidate cache, but do not remove any files')
->addOption('purge', null, InputOption::VALUE_NONE, 'If set purge old caches')
->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all including compiled, twig, doctrine caches')
->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*')
@@ -64,6 +65,8 @@ class ClearCacheCommand extends ConsoleCommand
$remove = 'cache-only';
} elseif ($this->input->getOption('tmp-only')) {
$remove = 'tmp-only';
} elseif ($this->input->getOption('invalidate')) {
$remove = 'invalidate';
} else {
$remove = 'standard';
}

View File

@@ -61,6 +61,15 @@ class YamlLinterCommand extends ConsoleCommand
$this->displayErrors($errors, $io);
}
$io->section('Page Blueprints');
$errors = YamlLinter::lintBlueprints();
if (empty($errors)) {
$io->success('No YAML Linting issues with blueprints');
} else {
$this->displayErrors($errors, $io);
}
}
protected function displayErrors($errors, $io)

View File

@@ -10,6 +10,10 @@
namespace Grav\Console;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Processors\InitializeProcessor;
use RocketTheme\Toolbox\Event\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -18,6 +22,13 @@ class ConsoleCommand extends Command
{
use ConsoleTrait;
/** @var bool */
private $plugins_initialized = false;
/** @var bool */
private $themes_initialized = false;
/** @var bool */
private $pages_initialized = false;
/**
* @param InputInterface $input
* @param OutputInterface $output
@@ -31,12 +42,140 @@ class ConsoleCommand extends Command
}
/**
*
* Override with your implementation.
*/
protected function serve()
{
}
/**
* Initialize Grav.
*
* - Load configuration
* - Disable debugger
* - Set timezone, locale
* - Load plugins
* - Set Users type to be used in the site
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializeGrav()
{
InitializeProcessor::initializeCli(Grav::instance());
return $this;
}
/**
* Set language to be used in CLI.
*
* @param string|null $code
*/
final protected function setLanguage(string $code = null)
{
$this->initializeGrav();
$grav = Grav::instance();
/** @var Language $language */
$language = $grav['language'];
if ($language->enabled()) {
if ($code && $language->validate($code)) {
$language->setActive($code);
} else {
$language->setActive($language->getDefault());
}
}
}
/**
* Properly initialize plugins.
*
* - call $this->initializeGrav()
* - call onPluginsInitialized event
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializePlugins()
{
if (!$this->plugins_initialized) {
$this->plugins_initialized = true;
$this->initializeGrav();
// Initialize plugins.
$grav = Grav::instance();
$grav->fireEvent('onPluginsInitialized');
}
return $this;
}
/**
* Properly initialize themes.
*
* - call $this->initializePlugins()
* - initialize theme (call onThemeInitialized event)
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializeThemes()
{
if (!$this->themes_initialized) {
$this->themes_initialized = true;
$this->initializePlugins();
// Initialize themes.
$grav = Grav::instance();
$grav['themes']->init();
}
return $this;
}
/**
* Properly initialize pages.
*
* - call $this->initializeThemes()
* - initialize assets (call onAssetsInitialized event)
* - initialize twig (calls the twig events)
* - initialize pages (calls onPagesInitialized event)
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializePages()
{
if (!$this->pages_initialized) {
$this->pages_initialized = true;
$this->initializeThemes();
$grav = Grav::instance();
// Initialize assets.
$grav['assets']->init();
$grav->fireEvent('onAssetsInitialized');
// Initialize twig.
$grav['twig']->init();
// Initialize pages.
$pages = $grav['pages'];
$pages->init();
$grav->fireEvent('onPagesInitialized', new Event(['pages' => $pages]));
}
return $this;
}
protected function displayGPMRelease()
{
$this->output->writeln('');

View File

@@ -114,7 +114,7 @@ class InfoCommand extends ConsoleCommand
if ($info === 'date') {
$name = 'Last Update';
$data = date('D, j M Y, H:i:s, P ', strtotime('2014-09-16T00:07:16Z'));
$data = date('D, j M Y, H:i:s, P ', strtotime($data));
}
$name = str_pad($name, 12);

View File

@@ -372,7 +372,7 @@ class InstallCommand extends ConsoleCommand
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
$answer = $helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln(" '- <red>Skipped!</red> ");

View File

@@ -177,7 +177,7 @@ class SelfupgradeCommand extends ConsoleCommand
}
/**
* @param Package $package
* @param array $package
*
* @return string
*/

View File

@@ -437,7 +437,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
}
/**
* Implementes JsonSerializable interface.
* Implements JsonSerializable interface.
*
* @return array
*/

View File

@@ -84,7 +84,7 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
}
/**
* Implementes JsonSerializable interface.
* Implements JsonSerializable interface.
*
* @return array
*/

View File

@@ -53,11 +53,11 @@ class CsvFormatter extends AbstractFormatter
$header = array_keys(reset($data));
// Encode the field names
$string = implode($delimiter, $header). "\n";
$string = $this->encodeLine($header, $delimiter);
// Encode the data
foreach ($data as $row) {
$string .= implode($delimiter, $row). "\n";
$string .= $this->encodeLine($row, $delimiter);
}
return $string;
@@ -79,12 +79,49 @@ class CsvFormatter extends AbstractFormatter
// Get the field names
$header = str_getcsv(array_shift($lines), $delimiter);
// Allow for replacing a null string with null/empty value
$null_replace = $this->getConfig('null');
// Get the data
$list = [];
foreach ($lines as $line) {
$list[] = array_combine($header, str_getcsv($line, $delimiter));
$line = null;
try {
foreach ($lines as $line) {
if (!empty($line)) {
$csv_line = str_getcsv($line, $delimiter);
if ($null_replace) {
array_walk($csv_line, function(&$el) use ($null_replace) {
$el = str_replace($null_replace, "\0", $el);
});
}
$list[] = array_combine($header, $csv_line);
}
}
} catch (\Exception $e) {
throw new \Exception('Badly formatted CSV line: ' . $line);
}
return $list;
}
protected function encodeLine(array $line, $delimiter = null): string
{
foreach ($line as $key => &$value) {
$value = $this->escape((string)$value);
}
unset($value);
return implode($delimiter, $line). "\n";
}
protected function escape(string $value)
{
if (preg_match('/[,"\r\n]/u', $value)) {
$value = '"' . preg_replace('/"/', '""', $value) . '"';
}
return $value;
}
}

View File

@@ -97,12 +97,12 @@ class YamlFormatter extends AbstractFormatter
@ini_set('yaml.decode_php', $saved);
if ($decoded !== false) {
return $decoded;
return (array) $decoded;
}
}
try {
return YamlParser::parse($data);
return (array) YamlParser::parse($data);
} catch (ParseException $e) {
if ($this->useCompatibleDecoder()) {
return (array) FallbackYamlParser::parse($data);

View File

@@ -91,7 +91,7 @@ class Flex implements \Countable
// Return the directories in the given order.
$directories = [];
foreach ($types as $type) {
$directories = $this->types[$type] ?? null;
$directories[$type] = $this->types[$type] ?? null;
}
return $keepMissing ? $directories : array_filter($directories);

View File

@@ -9,6 +9,7 @@
namespace Grav\Framework\Flex;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Grav\Common\Debugger;
use Grav\Common\Grav;
@@ -125,7 +126,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
/**
* @param array $filters
* @return FlexCollectionInterface
* @return FlexCollectionInterface|Collection
*/
public function filterBy(array $filters)
{
@@ -490,7 +491,12 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
$twig = $grav['twig'];
try {
return $twig->twig()->resolveTemplate(["flex-objects/layouts/{$this->getFlexType()}/collection/{$layout}.html.twig"]);
return $twig->twig()->resolveTemplate(
[
"flex-objects/layouts/{$this->getFlexType()}/collection/{$layout}.html.twig",
"flex-objects/layouts/_default/collection/{$layout}.html.twig"
]
);
} catch (LoaderError $e) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];

View File

@@ -315,15 +315,16 @@ class FlexDirectory implements FlexAuthorizeInterface
$gravCache = $grav['cache'];
$config = $this->getConfig('cache.' . $namespace);
if (empty($config['enabled'])) {
throw new \RuntimeException(sprintf('Flex: %s %s cache not enabled', $this->type, $namespace));
}
$timeout = $config['timeout'] ?? 60;
$cache = new MemoryCache('flex-objects-' . $this->getFlexType());
} else {
$timeout = $config['timeout'] ?? 60;
$key = $gravCache->getKey();
if (Utils::isAdminPlugin()) {
$key = substr($key, 0, -1);
$key = $gravCache->getKey();
if (Utils::isAdminPlugin()) {
$key = substr($key, 0, -1);
}
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $timeout);
}
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $timeout);
} catch (\Exception $e) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
@@ -519,7 +520,9 @@ class FlexDirectory implements FlexAuthorizeInterface
// Store updated rows to the cache.
if ($updated) {
try {
$debugger->addMessage(sprintf('Flex: Caching %d %s: %s', \count($updated), $this->type, implode(', ', array_keys($updated))), 'debug');
if (!$cache instanceof MemoryCache) {
$debugger->addMessage(sprintf('Flex: Caching %d %s: %s', \count($updated), $this->type, implode(', ', array_keys($updated))), 'debug');
}
$cache->setMultiple($updated);
} catch (InvalidArgumentException $e) {
$debugger->addException($e);
@@ -640,7 +643,10 @@ class FlexDirectory implements FlexAuthorizeInterface
/** @var string|FlexIndexInterface $className */
$className = $this->getIndexClass();
$keys = $className::loadEntriesFromStorage($storage);
$debugger->addMessage(sprintf('Flex: Caching %s index of %d objects', $this->type, \count($keys)), 'debug');
if (!$cache instanceof MemoryCache) {
$debugger->addMessage(sprintf('Flex: Caching %s index of %d objects', $this->type, \count($keys)),
'debug');
}
try {
$cache->set('__keys', $keys);
} catch (InvalidArgumentException $e) {

View File

@@ -20,6 +20,7 @@ use Grav\Framework\Form\Traits\FormTrait;
use Grav\Framework\Route\Route;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
use Twig\Template;
use Twig\TemplateWrapper;
/**
@@ -82,7 +83,7 @@ class FlexForm implements FlexFormInterface
}
/**
* @return Data|FlexObjectInterface
* @return Data|FlexObjectInterface|object
*/
public function getData()
{
@@ -103,7 +104,7 @@ class FlexForm implements FlexFormInterface
$value = $this->data ? $this->data[$name] : null;
// Return the form data or fall back to the object property.
return $value ?? $this->getObject()->value($name);
return $value ?? $this->getObject()->getFormValue($name);
}
public function getDefaultValue(string $name)
@@ -277,7 +278,7 @@ class FlexForm implements FlexFormInterface
/**
* @param string $layout
* @return TemplateWrapper
* @return Template|TemplateWrapper
* @throws LoaderError
* @throws SyntaxError
*/

View File

@@ -312,9 +312,10 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
// Ordering can be done by using index only.
$previous = null;
foreach (array_reverse($orderings) as $field => $ordering) {
$field = (string)$field;
if ($this->getKeyField() === $field) {
$keys = $this->getKeys();
$search = array_combine($keys, $keys);
$search = array_combine($keys, $keys) ?: [];
} elseif ($field === 'flex_key') {
$search = $this->getFlexKeys();
} else {
@@ -462,7 +463,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
$first = reset($entries);
if ($first) {
$keys = array_keys($first);
$keys = array_combine($keys, $keys);
$keys = array_combine($keys, $keys) ?: [];
} else {
$keys = [];
}

View File

@@ -31,6 +31,7 @@ use Psr\SimpleCache\InvalidArgumentException;
use RocketTheme\Toolbox\Event\Event;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
use Twig\Template;
use Twig\TemplateWrapper;
/**
@@ -162,6 +163,13 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
{
$options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
$properties = $properties ?? $this->getFlexDirectory()->getConfig('data.search.fields', []);
if (!$properties) {
foreach ($this->getFlexDirectory()->getConfig('admin.list.fields', []) as $property => $value) {
if (!empty($value['link'])) {
$properties[] = $property;
}
}
}
$weight = 0;
foreach ((array)$properties as $property) {
@@ -273,7 +281,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
return (float)$options['ends_with'];
}
if ((!$tested || !empty($options['contains'])) && Utils::contains($value, $search, $options['case_sensitive'] ?? false)) {
return (float)$options['contains'];
return (float)($options['contains'] ?? 1);
}
return 0;
@@ -384,7 +392,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
}
try {
$data = $cache ? $cache->get($key) : null;
$data = $cache && $key ? $cache->get($key) : null;
$block = $data ? HtmlBlock::fromArray($data) : null;
} catch (InvalidArgumentException $e) {
@@ -403,7 +411,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
}
if (!$block) {
$block = HtmlBlock::create($key);
$block = HtmlBlock::create($key ?: null);
$block->setChecksum($checksum);
if ($key === false) {
$block->disableCache();
@@ -427,7 +435,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
$block->setContent($output);
try {
$cache && $block->isCached() && $cache->set($key, $block->toArray());
$cache && $key && $block->isCached() && $cache->set($key, $block->toArray());
} catch (InvalidArgumentException $e) {
$debugger->addException($e);
}
@@ -534,7 +542,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
$result = $this->getFlexDirectory()->getStorage()->replaceRows([$this->getStorageKey() => $this->prepareStorage()]);
$value = reset($result);
$storageKey = key($result);
$storageKey = (string)key($result);
if ($value && $storageKey) {
$this->setStorageKey($storageKey);
if (!$this->hasKey()) {
@@ -542,6 +550,15 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
}
}
// FIXME: For some reason locator caching isn't cleared for the file, investigate!
$locator = Grav::instance()['locator'];
$locator->clearCache();
// Make sure that the object exists before continuing (just in case).
if (!$this->exists()) {
throw new \RuntimeException('Saving failed: Object does not exist!');
}
if (method_exists($this, 'saveUpdatedMedia')) {
$this->saveUpdatedMedia();
}
@@ -551,7 +568,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
if (method_exists($this, 'clearMediaCache')) {
$this->clearMediaCache();
}
} catch (InvalidArgumentException $e) {
} catch (\Exception $e) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$debugger->addException($e);
@@ -579,7 +596,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
if (method_exists($this, 'clearMediaCache')) {
$this->clearMediaCache();
}
} catch (InvalidArgumentException $e) {
} catch (\Exception $e) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$debugger->addException($e);
@@ -729,7 +746,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
}
$grav = Grav::instance();
/** @var Flex $flex */
/** @var Flex|null $flex */
$flex = $grav['flex_objects'] ?? null;
$directory = $flex ? $flex->getDirectory($type) : null;
if (!$directory) {
@@ -800,7 +817,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
/**
* @param string $layout
* @return TemplateWrapper
* @return Template|TemplateWrapper
* @throws LoaderError
* @throws SyntaxError
*/
@@ -812,7 +829,12 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
$twig = $grav['twig'];
try {
return $twig->twig()->resolveTemplate(["flex-objects/layouts/{$this->getFlexType()}/object/{$layout}.html.twig"]);
return $twig->twig()->resolveTemplate(
[
"flex-objects/layouts/{$this->getFlexType()}/object/{$layout}.html.twig",
"flex-objects/layouts/_default/object/{$layout}.html.twig"
]
);
} catch (LoaderError $e) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];

Some files were not shown because too many files have changed in this diff Show More