Compare commits

...

208 Commits

Author SHA1 Message Date
Andy Miller
d4a20c71c2 Merge branch 'release/1.6.31' 2020-12-14 21:28:53 -07:00
Andy Miller
d9c1445542 prepare for release 2020-12-14 21:28:42 -07:00
Djamil Legato
6a4686d17b Update composer first thing 2020-12-10 11:19:27 -08:00
Andy Miller
1c63f4bf46 Fix Twig svg_image() issue with classes applied to all elements #3068 2020-12-10 10:54:38 -07:00
Andy Miller
bc22c8d2b1 Allow all css and js to be seached via robots.txt by default 2020-12-10 10:41:42 -07:00
Matias Griese
db24d3e53e Fixed pages field escaping issues, needs admin update, too 2020-12-09 15:28:48 +02:00
Andy Miller
29bcbf042f Added slack on failure 2020-12-07 16:19:34 -07:00
Andy Miller
19719ecac1 Added slack to tests 2020-12-07 16:14:16 -07:00
Djamil Legato
47ab76c7e3 🎉 Moved Travis releases builds to Github Actions 🎉 2020-12-05 16:53:14 -08:00
Andy Miller
7b97cd0cf1 ignore versions.yaml 2020-12-04 12:21:42 -07:00
Djamil Legato
809767883b Disable develop branch from triggering travis
Temporary, while we move to GitHub Actions.
2020-12-04 10:07:48 -08:00
Andy Miller
6eb083bebe Merge tag '1.6.30' into develop
Release v1.6.30
2020-12-04 05:30:11 -07:00
Andy Miller
3a822f7bc4 Merge branch 'release/1.6.30' 2020-12-04 05:30:10 -07:00
Andy Miller
9a2268a54e prepare for release 2020-12-04 05:29:56 -07:00
Andy Miller
611171371b fix for travis build 2020-12-04 05:26:29 -07:00
Andy Miller
e1d4fe36f4 Merge branch 'release/1.6.30' 2020-12-04 03:42:37 -07:00
Andy Miller
079d8c19a4 Merge tag '1.6.30' into develop
Release v1.6.30
2020-12-04 03:42:37 -07:00
Andy Miller
418c6dd7f8 prepare for release 2020-12-04 03:42:02 -07:00
Andy Miller
08304d5064 Revert "Allow to set SameSite from system.yaml (#3063)"
This reverts commit 0b41eea2bb.
2020-12-04 03:34:26 -07:00
Andy Miller
5cdeb28e6b Revert "better handle cookie_samesite if null"
This reverts commit fd0c9823fa.
2020-12-04 03:33:59 -07:00
Andy Miller
c5efd17a9c GA Slack WIP 2020-12-03 22:16:56 -07:00
Andy Miller
6276ead820 removed deprecated values 2020-12-03 22:10:36 -07:00
Andy Miller
b5cdc12478 GA WIP 2020-12-03 22:02:46 -07:00
Andy Miller
ff511b8968 GA testing 2020-12-03 21:58:32 -07:00
Andy Miller
037a84c46f Added GA Test workflow 2020-12-03 20:49:02 -07:00
Andy Miller
3d0f29a172 removed deleted repo 2020-12-03 20:48:38 -07:00
Andy Miller
078c8d23d5 Merge branch 'release/1.6.29' 2020-12-03 14:22:52 -07:00
Andy Miller
8c845c77c1 Merge tag '1.6.29' into develop
Release v1.6.29
2020-12-03 14:22:52 -07:00
Andy Miller
cb373dae59 prepare for release 2020-12-03 14:22:38 -07:00
Matias Griese
00a7094802 Update getBackupDownloadUrl() method to not pass the path 2020-12-01 09:33:55 +02:00
Andy Miller
24b52c77fe update changelog 2020-11-30 16:13:51 -07:00
Andy Miller
75b74c4ab3 XSS fix for grav-ghsa-cvmr-6428-87w9 2020-11-30 16:10:56 -07:00
Matias Griese
76670e47a1 PHP 8 fixes 2020-11-27 13:49:10 +02:00
Matias Griese
07ee5b42f7 Added basic support for user/config/versions.yaml 2020-11-23 21:53:28 +02:00
Djamil Legato
e16b29c566 Better handling of missing repository index (grav-plugin-admin#1916) 2020-11-21 14:05:29 -08:00
1tsi
56ce4ab0f2 Update media.yaml (#3070)
fixed MIME types for .docx, .pptx and .xlsx
2020-11-18 12:10:23 -07:00
Andy Miller
fd0c9823fa better handle cookie_samesite if null 2020-11-17 15:24:08 -07:00
randoum
0b41eea2bb Allow to set SameSite from system.yaml (#3063)
* Update system.yaml

* Update SessionServiceProvider.php

* Update Session.php

* Update system.yaml
2020-11-12 13:40:39 -07:00
Stephan Strate
9b2b909139 Check exact extension in checkFilename utility (#3061)
* Fix uploads_dangerous_extensions checking (#3060)

* Remove redundant prefixing of `.` to extension (#3060)
2020-11-11 10:30:57 -07:00
Vilius Šumskas
54dccd11ef Fix failing example custom job. (#3050)
* Since Symfony 4.2 passing chained shell commands to the Process component is not supported anymore and a working directory needs to be set by passing it as a completely separate parameter.

Unless somebody finds a way to use Process() for this and fixes it in the code, rework example custom job.

Related info: https://symfony.com/blog/new-in-symfony-4-2-important-deprecations#deprecated-process-commands-as-strings

* One more place to fix Symfony 4.2 compatibility.
2020-11-04 15:40:32 -07:00
Djamil Legato
40216e310d Create SECURITY.md
Added security policy
2020-10-30 12:03:34 -07:00
Grant
6a6f99e9ae Add option for timeout in selfupgrade command (#3013)
* Add option for timeout in selfupgrade command

* Raise the default timeout
2020-10-30 07:48:03 -06:00
Djamil Legato
14ad7cf3ac Set grav_cli as referer when coming from CLI 2020-10-29 15:58:32 -07:00
Djamil Legato
ded7670ac3 Forward a sid to GPM when downloading a premium package via CLI 2020-10-29 15:49:36 -07:00
Matias Griese
325cb69a65 Merge remote-tracking branch 'origin/develop' into develop 2020-10-29 11:03:37 +02:00
Matias Griese
c3df9b6484 Added .htaccess rule to block attempts to use Twig in the request URL 2020-10-29 11:03:28 +02:00
Vilius Šumskas
1661dc9ef7 Fix compatibility with Symfony 4.2 and up. (#3048)
This enables running scheduled *shell* commands again because Symfony 4.2 deprecated passing commands as a string to the Process component: https://symfony.com/blog/new-in-symfony-4-2-important-deprecations#deprecated-process-commands-as-strings

One also needs to catch all possible exceptions from the Process component, though. These exceptions are never displayed or sent to any of the logs and I've spent hours trying to debug why my scheduled tasks are failing.
2020-10-23 14:40:35 -06:00
Andy Miller
38043ebade Merge branch 'develop' of github.com:getgrav/grav into develop
# Conflicts:
#	CHANGELOG.md
2020-10-18 15:10:01 -06:00
Andy Miller
9a694a8d3d updated jquery 3.x 2020-10-18 15:08:59 -06:00
Matias Griese
fb3efba204 Fixed hardcoded system folder in blueprints, config and language streams 2020-10-14 11:01:45 +03:00
Andy Miller
b375a543ec Merge tag '1.6.28' into develop
Release v1.6.28
2020-10-07 17:26:16 -06:00
Andy Miller
2c9d848af5 Merge branch 'release/1.6.28' 2020-10-07 17:26:15 -06:00
Andy Miller
8d65c5c2c0 prepare for release 2020-10-07 17:25:32 -06:00
Andy Miller
9d870b2c45 Backported folder::countChildren() from 1.7 2020-10-06 16:06:10 -06:00
Andy Miller
cfd5d9e209 improved compatibility 2020-10-05 18:44:59 -06:00
Matias Griese
23716ff729 Do not cache 404 [#3025] 2020-10-05 11:16:17 +03:00
Matias Griese
18921a4f14 Composer update 2020-10-05 10:57:02 +03:00
Cant_Aim
fdf884036d Update to Caddyfile using Caddy 2 changes. (#2963) 2020-10-04 16:35:37 -06:00
Matias Griese
f344a8166e Changelog update 2020-09-30 14:38:47 +03:00
Matias Griese
9532317928 Fixed fatal error in toggled fields 2020-09-30 14:38:29 +03:00
Djamil Legato
2108a902c2 Fixed Referer reference for GPM calls 2020-09-28 17:37:08 -07:00
Andy Miller
59b3b6cc02 initialize page blueprint first 2020-09-21 15:00:51 -06:00
Jeremy Gonyea
bdfec68340 Custom nginx-ddev-site.conf no longer required (#3011) 2020-09-18 20:43:51 -06:00
Andy Miller
88c0617279 update changelog 2020-09-10 15:55:33 -06:00
Andy Miller
b8b1bed7ed backported theme_var enhanced logic from 1.7 2020-09-10 15:52:52 -06:00
Andy Miller
03c6e74c4d backported {{ cache }} from 1.7 2020-09-10 15:49:25 -06:00
Andy Miller
71639de5ec Added Utils::fullPath() helper method 2020-09-10 15:43:57 -06:00
Andy Miller
39310cd4af Added svg_image twig function 2020-09-10 15:43:47 -06:00
Andy Miller
75cb4e392d Merge branch 'release/1.6.27' 2020-09-01 14:35:38 -06:00
Andy Miller
ec9dd14cb4 Merge tag '1.6.27' into develop
Release v1.6.27
2020-09-01 14:35:38 -06:00
Andy Miller
6e8c852bfa prepare for release 2020-09-01 14:35:29 -06:00
torohill
fc97e88928 Left pad scheduler times with zeros. (#2921) 2020-07-07 11:17:15 -06:00
Fabien Basmaison
00c5dba210 Use proper ellipsis for summary. (#2939) 2020-06-17 08:28:32 -06:00
Andy Miller
46d4f4a481 right-trim route for safety
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-09 10:43:21 -06:00
Andy Miller
fa791ed4ab Merge branch 'release/1.6.26' 2020-06-08 15:50:24 -06:00
Andy Miller
e29f8a9657 Merge tag '1.6.26' into develop
Release v1.6.26
2020-06-08 15:50:24 -06:00
Andy Miller
e66f6583b1 prepare for release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-08 15:49:51 -06:00
Andy Miller
12389b1e0d JSON Route of homepage with no ‘route’ set is valid [form#425]
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-07 19:46:18 -06:00
Florian Körner
ff6e5a20c3 Fix: case-insensitive search of location header (#2932)
looks good!
2020-06-07 15:42:29 -06:00
Andy Miller
7faaff304a PSR fix
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-05 12:26:30 -06:00
Andy Miller
bfbe4ce1b8 option to control supported attributes in markdown links
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-04 14:42:38 -06:00
Andy Miller
aaa636f357 cleaner handling of cropZoom
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-04 14:31:22 -06:00
Andy Miller
3bd4f9499a cherry picked #1925 fix from 1.7 to 1.6
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-26 08:35:49 -06:00
Andy Miller
d7b1689047 Merge tag '1.6.25' into develop
Release 1.6.25
2020-05-14 15:41:45 -06:00
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
a2ea6faf4d Merge tag '1.6.13' into develop
Release v1.6.13
2019-08-16 09:53:21 -06:00
81 changed files with 5136 additions and 1235 deletions

View File

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

64
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Release Builds
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.1
extensions: opcache, gd
coverage: none
- name: Install Dependencies
run: |
sudo apt-get -y update -qq < /dev/null > /dev/null
sudo apt-get -y install -qq git zip < /dev/null > /dev/null
- name: Retrieval of Builder Scripts
run: |
# Real Grav URL
curl --silent -H "Authorization: token ${{ secrets.GLOBAL_TOKEN }}" -H "Accept: application/vnd.github.v3.raw" ${{ secrets.BUILD_SCRIPT_URL }} --output build-grav.sh
# Development Local URL
# curl ${{ secrets.BUILD_SCRIPT_URL }} --output build-grav.sh
- name: Grav Builder
run: |
bash ./build-grav.sh
- name: Upload Grav Release Assets
id: upload-release-asset
uses: alexellis/upload-assets@0.2.2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
asset_paths: '["./grav-dist/*.zip"]'
slack:
name: Slack
needs: build
runs-on: ubuntu-latest
if: always()
steps:
- uses: technote-space/workflow-conclusion-action@v2
- uses: 8398a7/action-slack@v3
with:
status: failure
fields: repo,message,author,action
icon_emoji: ':octocat:'
author_name: 'Github Action Build'
text: '🚚 Automated Build Failure'
env:
GITHUB_TOKEN: ${{ github.token }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: env.WORKFLOW_CONCLUSION == 'failure'

70
.github/workflows/tests.yaml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: PHP Tests
on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
jobs:
unit-tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
php: [ 7.4, 7.3, 7.2 ]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: opcache, gd
coverage: none
- name: Update composer
run: composer update
- name: Validate composer.json and composer.lock
run: composer validate
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run test suite
run: vendor/bin/codecept run
slack:
name: Slack
needs: unit-tests
runs-on: ubuntu-latest
if: always()
steps:
- uses: technote-space/workflow-conclusion-action@v2
- uses: 8398a7/action-slack@v3
with:
status: failure
fields: repo,message,author,action
icon_emoji: ':octocat:'
author_name: 'Github Action Tests'
text: '💥 Automated Test Failure'
env:
GITHUB_TOKEN: ${{ github.token }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: env.WORKFLOW_CONCLUSION == 'failure'

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ tests/_support/_generated/*
tests/cache/*
tests/error.log
system/templates/testing/*
/user/config/versions.yaml

View File

@@ -27,6 +27,9 @@ RewriteEngine On
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to use twig tags in URL.
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.

View File

@@ -3,10 +3,9 @@ php:
- '7.1'
- '7.2'
- '7.3'
- '7.4'
branches:
only:
- develop
- master
- build_test
notifications:
email:
@@ -45,10 +44,11 @@ before_install:
- if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then
composer install --dev --prefer-dist;
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;
- |
if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "7.1" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
export TRAVIS_TAG=$(curl -H "Authorization: token ${GH_TOKEN}" --fail -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.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 +56,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,11 +1,215 @@
# v1.6.31
## 12/14/2020
1. [](#improved)
* Allow all CSS and JS via `robots.txt` [#2006](https://github.com/getgrav/grav/issues/2006) [#3067](https://github.com/getgrav/grav/issues/3067)
1. [](#bugfix)
* Fixed `pages` field escaping issues, needs admin update, too [admin#1990](https://github.com/getgrav/grav-plugin-admin/issues/1990)
* Fix `svg-image` issue with classes applied to all elements [#3068](https://github.com/getgrav/grav/issues/3068)
# v1.6.30
## 12/03/2020
1. [](#bugfix)
* Rollback `samesite` cookie logic as it causes issues with PHP < 7.3 [#309](https://github.com/getgrav/grav/issues/3089)
* Fixed issue with `.travis.yml` due to GitHub API deprecated functionality
# v1.6.29
## 12/02/2020
1. [](#new)
* Added basic support for `user/config/versions.yaml`
1. [](#improved)
* Updated bundled JQuery to latest version `3.5.1`
* Forward a `sid` to GPM when downloading a premium package via CLI
* Better handling of missing repository index [grav-plugin-admin#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916)
* Set `grav_cli` as referrer when using `Response` from CLI
* Add option for timeout in `self-upgrade` command [#3013](https://github.com/getgrav/grav/pull/3013)
* Allow to set SameSite from system.yaml [#3063](https://github.com/getgrav/grav/pull/3063)
* Update media.yaml with some MS Office mimetypes [#3070](https://github.com/getgrav/grav/pull/3070)
1. [](#bugfix)
* Fixed hardcoded system folder in blueprints, config and language streams
* Added `.htaccess` rule to block attempts to use Twig in the request URL
* Fix compatibility with Symfony 4.2 and up. [#3048](https://github.com/getgrav/grav/pull/3048)
* Fix failing example custom shceduled job. [#3050](https://github.com/getgrav/grav/pull/3050)
* Fix for XSS advisory [GHSA-cvmr-6428-87w9](https://github.com/getgrav/grav/security/advisories/GHSA-cvmr-6428-87w9)
* Fix uploads_dangerous_extensions checking [#3060](https://github.com/getgrav/grav/pull/3060)
* Remove redundant prefixing of `.` to extension [#3060](https://github.com/getgrav/grav/pull/3060)
* Check exact extension in checkFilename utility [#3061](https://github.com/getgrav/grav/pull/3061)
# v1.6.28
## 10/07/2020
1. [](#new)
* Back-ported twig `{% cache %}` tag from Grav 1.7
* Back-ported `Utils::fullPath()` helper function from Grav 1.7
* Back-ported `{{ svg_image() }}` Twig function from Grav 1.7
* Back-ported `Folder::countChildren()` function from Grav 1.7
1. [](#improved)
* Use new `{{ theme_var() }}` enhanced logic from Grav 1.7
* Improved `Excerpts` class with fixes and functionality from Grav 1.7
* Ensure `onBlueprintCreated()` is initialized first
* Do not cache default `404` error page
* Composer update of vendor libraries
* Switched `Caddyfile` to use new Caddy2 syntax + improved usability
1. [](#bugfix)
* Fixed Referer reference during GPM calls.
* Fixed fatal error with toggled lists
# v1.6.27
## 09/01/2020
1. [](#improved)
* Right trim route for safety
* Use the proper ellipsis for summary [#2939](https://github.com/getgrav/grav/pull/2939)
* Left pad schedule times with zeros [#2921](https://github.com/getgrav/grav/pull/2921)
# v1.6.26
## 06/08/2020
1. [](#improved)
* Added new configuration option to control the supported attributes in markdown links [#2882](https://github.com/getgrav/grav/issues/2882)
1. [](#bugfix)
* Fixed blueprint for `system.pages.hide_empty_folders` [#1925](https://github.com/getgrav/grav/issues/2925)
* JSON Route of homepage with no route set is valid
* Fix case-insensitive search of location header [form#425](https://github.com/getgrav/grav-plugin-form/issues/425)
# 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/12/2019
## 08/16/2019
1. [](#bugfix)
* Regression fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
# v1.6.12
## 08/11/2019
## 08/14/2019
1. [](#new)
* Added support for custom `FormFlash` save locations
@@ -13,7 +217,7 @@
* 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)
* 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)
@@ -32,7 +236,7 @@
* 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
@@ -63,7 +267,7 @@
* 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 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

15
SECURITY.md Normal file
View File

@@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
We are focusing our security updates on the following versions
| Version | Supported |
| ------- | ------------------ |
| 1.7.x | :white_check_mark: |
| 1.6.x | :white_check_mark: |
| < 1.6 | :x: |
## Reporting a Vulnerability
Please contact contact@getgrav.org with a detailed explaination of the security issue found and we will work with you to get it resolved as fast as possible.

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

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.40",
"erusev/parsedown": "1.6.4",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~4.2",
"symfony/console": "~4.2",
"symfony/event-dispatcher": "~4.2",
"symfony/var-dumper": "~4.2",
"symfony/process": "~4.2",
"symfony/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,15 +50,14 @@
"composer/ca-bundle": "^1.0",
"dragonmantank/cron-expression": "^1.2",
"phive/twig-extensions-deferred": "^1.0",
"willdurand/negotiation": "^2.3"
"willdurand/negotiation": "^3.0"
},
"require-dev": {
"codeception/codeception": "^2.4",
"phpstan/phpstan": "^0.11",
"phpstan/phpstan-deprecation-rules": "^0.11.0",
"phpunit/php-code-coverage": "~6.0",
"fzaninotto/faker": "^1.8",
"victorjonsson/markdowndocs": "dev-master"
"fzaninotto/faker": "^1.8"
},
"suggest": {
"ext-zend-opcache": "Recommended for better performance",
@@ -83,10 +82,15 @@
"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",
@@ -94,7 +98,7 @@
"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"
},

2382
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(

View File

@@ -10,3 +10,6 @@ Disallow: /user/
Allow: /user/pages/
Allow: /user/themes/
Allow: /user/images/
Allow: /
Allow: *.css$
Allow: *.js$

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

@@ -39,12 +39,13 @@ form:
.command:
type: text
label: PLUGIN_ADMIN.COMMAND
placeholder: 'cd ~;ls -lah;'
placeholder: 'ls'
validate:
required: true
.args:
type: text
label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
placeholder: '-lah'
.at:
type: cron
label: PLUGIN_ADMIN.SCHEDULER_RUNAT

View File

@@ -241,13 +241,15 @@ form:
type: commalist
pages.hide_empty_folders:
type: selectize
size: large
label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
classes: fancy
validate:
type: commalist
type: toggle
label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.url_taxonomy_filters:
type: toggle
@@ -513,6 +515,16 @@ form:
validate:
type: bool
pages.markdown.valid_link_attributes:
type: selectize
size: large
label: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES
help: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES_HELP
placeholder: "rel, target, id, class, classes"
classes: fancy
validate:
type: commalist
caching:
type: tab
title: PLUGIN_ADMIN.CACHING
@@ -1379,6 +1391,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

@@ -103,7 +103,7 @@ types:
docx:
type: file
thumb: media/thumb-docx.png
mime: application/msword
mime: application/vnd.openxmlformats-officedocument.wordprocessingml.document
xls:
type: file
thumb: media/thumb-xls.png
@@ -111,7 +111,7 @@ types:
xlsx:
type: file
thumb: media/thumb-xlsx.png
mime: application/vnd.ms-excel
mime: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
ppt:
type: file
thumb: media/thumb-ppt.png
@@ -119,7 +119,7 @@ types:
pptx:
type: file
thumb: media/thumb-pptx.png
mime: application/vnd.ms-powerpoint
mime: application/vnd.openxmlformats-officedocument.presentationml.presentation
pps:
type: file
thumb: media/thumb-pps.png

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]
@@ -54,6 +59,12 @@ pages:
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
valid_link_attributes: # Valid attributes to pass through via markdown links
- rel
- target
- id
- class
- classes
types: [html,htm,xml,txt,json,rss,atom] # list of valid page types
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)

View File

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

View File

@@ -2,4 +2,5 @@
title: Not Found
routable: false
notfound: true
expires: 0
---

View File

@@ -21,8 +21,7 @@ $grav_index = 'index.php';
/* Check the GRAV_BASEDIR environment variable and use if set */
$grav_basedir = getenv('GRAV_BASEDIR') ?: '';
if (isset($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);

View File

@@ -51,7 +51,11 @@ 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']);

View File

@@ -70,7 +70,7 @@ class Backups
public function getBackupDownloadUrl($backup, $base_url)
{
$param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
$download = urlencode(base64_encode($backup));
$download = urlencode(base64_encode(basename($backup)));
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim($base_url,
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');

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

@@ -59,13 +59,13 @@ class Setup extends Data
'blueprints' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'],
'' => ['environment://blueprints', 'user://blueprints', 'system://blueprints'],
]
],
'config' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://config', 'user://config', 'system/config'],
'' => ['environment://config', 'user://config', 'system://config'],
]
],
'plugins' => [
@@ -89,7 +89,7 @@ class Setup extends Data
'languages' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://languages', 'user://languages', 'system/languages'],
'' => ['environment://languages', 'user://languages', 'system://languages'],
]
],
'cache' => [

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));
@@ -232,8 +232,19 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
if (is_array($value)) {
// Special toggle handling for all the nested data.
$toggle = $toggles[$key] ?? [];
if (!is_array($toggle)) {
if (!$toggle) {
$data[$key] = null;
continue;
}
$toggle = [];
}
// Recursively fetch the items.
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggles[$key] ?? [], $value);
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $value);
} else {
$field = $this->get($value);
// Do not add the field if:

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

@@ -166,9 +166,18 @@ 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)
{
return (bool) $value;
$value = (string)$value;
$field_value = (string)($field['value'] ?? '1');
return $value === $field_value ? $value : null;
}
protected static function filterCommaList($value, array $params, array $field)

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()) {
@@ -494,4 +508,19 @@ abstract class Folder
return $include_target ? @rmdir($folder) : true;
}
/**
* Does a directory contain children
*
* @param string $directory
* @return int|false
*/
public static function countChildren($directory) {
if (!is_dir($directory)) {
return false;
}
$directories = glob($directory . '/*', GLOB_ONLYDIR);
return count($directories);
}
}

View File

@@ -233,6 +233,11 @@ class GPM extends Iterator
public function getUpdatablePlugins()
{
$items = [];
if (!$this->repository) {
return $items;
}
$repository = $this->repository['plugins'];
// local cache to speed things up
@@ -312,6 +317,11 @@ class GPM extends Iterator
public function getUpdatableThemes()
{
$items = [];
if (!$this->repository) {
return $items;
}
$repository = $this->repository['themes'];
// local cache to speed things up
@@ -359,6 +369,10 @@ class GPM extends Iterator
*/
public function getReleaseType($package_name)
{
if (!$this->repository) {
return null;
}
$repository = $this->repository['plugins'];
if (isset($repository[$package_name])) {
return $repository[$package_name]->release_type;
@@ -407,7 +421,7 @@ class GPM extends Iterator
*/
public function getRepositoryPlugin($slug)
{
return @$this->repository['plugins'][$slug];
return $this->repository['plugins'][$slug] ?? null;
}
/**
@@ -416,7 +430,7 @@ class GPM extends Iterator
*/
public function getRepositoryPlugins()
{
return $this->repository['plugins'];
return $this->repository['plugins'] ?? null;
}
/**
@@ -426,7 +440,7 @@ class GPM extends Iterator
*/
public function getRepositoryTheme($slug)
{
return @$this->repository['themes'][$slug];
return $this->repository['themes'][$slug] ?? null;
}
/**
@@ -435,7 +449,7 @@ class GPM extends Iterator
*/
public function getRepositoryThemes()
{
return $this->repository['themes'];
return $this->repository['themes'] ?? null;
}
/**

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

@@ -36,7 +36,6 @@ class Response
private static $defaults = [
'curl' => [
CURLOPT_REFERER => 'Grav GPM',
CURLOPT_USERAGENT => 'Grav GPM',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
@@ -285,6 +284,8 @@ class Response
$options['fopen']['notification'] = ['self', 'progress'];
}
$referer = \defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
$options['fopen']['header'] = 'Referer: ' . $referer;
if (isset($options['fopen']['ssl'])) {
$ssl = $options['fopen']['ssl'];
unset($options['fopen']['ssl']);
@@ -367,6 +368,9 @@ class Response
*/
private static function curlExecFollow($ch, $options, $callback)
{
$referer = \defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
curl_setopt_array($ch, [ CURLOPT_REFERER => $referer ]);
if ($callback) {
curl_setopt_array(
$ch,
@@ -409,7 +413,7 @@ class Response
} else {
$code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE);
if ($code === 301 || $code === 302 || $code === 303) {
preg_match('/Location:(.*?)\n/', $header, $matches);
preg_match('/(?:^|\n)Location:(.*?)\n/i', $header, $matches);
$uri = trim(array_pop($matches));
} else {
$code = 0;

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
*/
@@ -238,7 +261,7 @@ class Grav extends Container
);
$default = function (ServerRequestInterface $request) {
return new Response(404);
return new Response(404, ['Expires' => 0, 'Cache-Control' => 'no-cache, no-store, must-revalidate'], 'Not Found');
};
/** @var Debugger $debugger */
@@ -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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -32,7 +32,7 @@ class Excerpts
$excerpt = static::processLinkExcerpt($excerpt, $page, 'image');
$excerpt['element']['attributes']['src'] = $excerpt['element']['attributes']['href'];
unset ($excerpt['element']['attributes']['href']);
unset($excerpt['element']['attributes']['href']);
$excerpt = static::processImageExcerpt($excerpt, $page);
@@ -43,6 +43,26 @@ class Excerpts
return $html;
}
/**
* Process Grav page link URL from HTML tag
*
* @param string $html HTML tag e.g. `<a href="../foo">Page Link</a>`
* @param PageInterface|null $page Page, defaults to the current page object
* @return string Returns final HTML string
*/
public static function processLinkHtml($html, PageInterface $page = null)
{
$excerpt = static::getExcerptFromHtml($html, 'a');
$original_href = $excerpt['element']['attributes']['href'];
$excerpt = static::processLinkExcerpt($excerpt, $page, 'link');
$excerpt['element']['attributes']['data-href'] = $original_href;
$html = static::getHtmlFromExcerpt($excerpt);
return $html;
}
/**
* Get an Excerpt array from a chunk of HTML
*
@@ -52,22 +72,35 @@ class Excerpts
*/
public static function getExcerptFromHtml($html, $tag)
{
$doc = new \DOMDocument();
$doc->loadHTML($html);
$images = $doc->getElementsByTagName($tag);
$excerpt = null;
$doc = new \DOMDocument('1.0', 'UTF-8');
$internalErrors = libxml_use_internal_errors(true);
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
libxml_use_internal_errors($internalErrors);
foreach ($images as $image) {
$elements = $doc->getElementsByTagName($tag);
$excerpt = null;
$inner = [];
/** @var \DOMElement $element */
foreach ($elements as $element) {
$attributes = [];
foreach ($image->attributes as $name => $value) {
foreach ($element->attributes as $name => $value) {
$attributes[$name] = $value->value;
}
$excerpt = [
'element' => [
'name' => $image->tagName,
'name' => $element->tagName,
'attributes' => $attributes
]
];
foreach ($element->childNodes as $node) {
$inner[] = $doc->saveHTML($node);
}
$excerpt = array_merge_recursive($excerpt, ['element' => ['text' => implode('', $inner)]]);
}
return $excerpt;
@@ -95,7 +128,7 @@ class Excerpts
if (isset($element['text'])) {
$html .= '>';
$html .= $element['text'];
$html .= is_array($element['text']) ? static::getHtmlFromExcerpt(['element' => $element['text']]) : $element['text'];
$html .= '</'.$element['name'].'>';
} else {
$html .= ' />';

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

@@ -11,8 +11,9 @@ 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;

View File

@@ -11,8 +11,9 @@ 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;

View File

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

View File

@@ -87,7 +87,7 @@ class Excerpts
);
// Valid attributes supported.
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
$valid_attributes = Grav::instance()['config']->get('system.pages.markdown.valid_link_attributes');
// Unless told to not process, go through actions.
if (array_key_exists('noprocess', $actions)) {
@@ -232,6 +232,7 @@ class Excerpts
$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(

View File

@@ -516,6 +516,15 @@ class ImageMedium extends Medium
return $this;
}
/**
* Handle this commonly used variant
*/
public function cropZoom()
{
$this->__call('zoomCrop', func_get_args());
return $this;
}
/**
* Forward the call to the image processing method.
*
@@ -525,10 +534,6 @@ class ImageMedium extends Medium
*/
public function __call($method, $args)
{
if ($method === 'cropZoom') {
$method = 'zoomCrop';
}
if (!\in_array($method, self::$magic_actions, true)) {
return parent::__call($method, $args);
}

View File

@@ -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)) {
@@ -865,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]);
}
@@ -1200,7 +1200,7 @@ class Page implements PageInterface
/**
* @return string
*/
protected function getCacheKey()
public function getCacheKey(): string
{
return $this->id();
}
@@ -1694,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);
}
@@ -2009,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() ?: '';
@@ -2824,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) {
@@ -2855,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();
}
}

View File

@@ -95,6 +95,8 @@ class Pages
protected $initialized = false;
protected $active_lang;
/**
* @var Types
*/
@@ -143,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 */
@@ -236,6 +238,16 @@ class Pages
$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.
*/
@@ -598,8 +610,8 @@ class Pages
}
if (empty($blueprint->initialized)) {
$this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $type]));
$blueprint->initialized = true;
$this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $type]));
}
return $blueprint;
@@ -676,7 +688,7 @@ class Pages
}
/**
* Get list of route/title of all pages.
* Get list of route/title of all pages. Title is in HTML.
*
* @param PageInterface $current
* @param int $level
@@ -709,10 +721,10 @@ class Pages
}
if ($showFullpath) {
$option = $current->route();
$option = htmlspecialchars($current->route());
} else {
$extra = $showSlug ? '(' . $current->slug() . ') ' : '';
$option = str_repeat('&mdash;-', $level). '&rtrif; ' . $extra . $current->title();
$option = str_repeat('&mdash;-', $level). '&rtrif; ' . $extra . htmlspecialchars($current->title());
}
@@ -958,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'];
@@ -982,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);
@@ -1261,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) {
@@ -1306,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 {
@@ -1318,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

@@ -305,7 +305,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
// Create new config object and set it on the page object so it's cached for next time
$page->modifyHeader($class_name_merged, new Data($header));
} else if (isset($page_header->{$class_name_merged})) {
} elseif (isset($page_header->{$class_name_merged})) {
$merged = $page_header->{$class_name_merged};
$header = $merged->toArray();
}

View File

@@ -9,6 +9,8 @@
namespace Grav\Common\Processors;
use Grav\Framework\File\Formatter\YamlFormatter;
use Grav\Framework\File\YamlFile;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
@@ -21,8 +23,27 @@ class ConfigurationProcessor extends ProcessorBase
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$this->startTimer();
$this->container['config']->init();
$config = $this->container['config'];
$config->init();
$this->container['plugins']->setup();
if ($config->get('versions') === null) {
$filename = GRAV_ROOT . '/user/config/versions.yaml';
if (!is_file($filename)) {
$versions = [
'core' => [
'grav' => [
'version' => GRAV_VERSION,
'history' => ['version' => GRAV_VERSION, 'date' => gmdate('Y-m-d H:i:s')]
]
]
];
$config->set('versions', $versions);
$file = new YamlFile($filename, new YamlFormatter(['inline' => 4]));
$file->save($versions);
}
}
$this->stopTimer();
return $handler->handle($request);

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

@@ -69,7 +69,7 @@ class Cron
'name_year' => 'année',
'text_period' => 'Chaque %s',
'text_mins' => 'à %s minutes',
'text_time' => 'à %s:%s',
'text_time' => 'à %02s:%02s',
'text_dow' => 'le %s',
'text_month' => 'de %s',
'text_dom' => 'le %s',
@@ -86,7 +86,7 @@ class Cron
'name_year' => 'year',
'text_period' => 'Every %s',
'text_mins' => 'at %s minutes past the hour',
'text_time' => 'at %s:%s',
'text_time' => 'at %02s:%02s',
'text_dow' => 'on %s',
'text_month' => 'of %s',
'text_dom' => 'on the %s',

View File

@@ -298,8 +298,8 @@ class Job
if (is_callable($this->command)) {
$this->output = $this->exec();
} else {
$args = \is_string($this->args) ? $this->args : implode(' ', $this->args);
$command = $this->command . ' ' . $args;
$args = \is_string($this->args) ? explode(' ', $this->args) : $this->args;
$command = array_merge([$this->command], $args);
$process = new Process($command);
$this->process = $process;

View File

@@ -246,7 +246,7 @@ class Scheduler
*/
public function isCrontabSetup()
{
$process = new Process('crontab -l');
$process = new Process(['crontab', '-l']);
$process->run();
if ($process->isSuccessful()) {

View File

@@ -133,7 +133,7 @@ class Security
// Set the patterns we'll test against
$patterns = [
// Match any attribute starting with "on" or xmlns
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])(\son|\sxmlns)[a-z].*=>?#iUu',
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])([\s\/]on|\sxmlns)[a-z].*=>?#iUu',
// Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
'invalid_protocols' => '#(' . implode('|', $invalid_protocols) . '):.*?#iUu',

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

@@ -0,0 +1,56 @@
<?php
/**
* @package Grav\Common\Twig
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
class TwigNodeCache extends Node
{
/**
* @param string $key unique name for key
* @param int $lifetime in seconds
* @param Node $body
* @param integer $lineno
* @param string $tag
*/
public function __construct(string $key, int $lifetime, Node $body, $lineno, $tag = null)
{
parent::__construct(array('body' => $body), array( 'key' => $key, 'lifetime' => $lifetime), $lineno, $tag);
}
/**
* {@inheritDoc}
*/
public function compile(Compiler $compiler)
{
$boo = $this->getAttribute('key');
$compiler
->addDebugInfo($this)
->write("\$cache = \\Grav\\Common\\Grav::instance()['cache'];\n")
->write("\$key = \"twigcache-\" . \"" . $this->getAttribute('key') . "\";\n")
->write("\$lifetime = " . $this->getAttribute('lifetime') . ";\n")
->write("\$cache_body = \$cache->fetch(\$key);\n")
->write("if (\$cache_body === false) {\n")
->indent()
->write("ob_start();\n")
->indent()
->subcompile($this->getNode('body'))
->outdent()
->write("\n")
->write("\$cache_body = ob_get_clean();\n")
->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n")
->outdent()
->write("}\n")
->write("echo \$cache_body;\n")
;
}
}

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

@@ -0,0 +1,68 @@
<?php
/**
* @package Grav\Common\Twig
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\TokenParser;
use Grav\Common\Grav;
use Grav\Common\Twig\Node\TwigNodeCache;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
/**
* Adds ability to cache Twig between tags.
*
* {% cache 600 %}
* {{ some_complex_work() }}
* {% endcache %}
*
* Where the `600` is an optional lifetime in seconds
*/
class TwigTokenParserCache extends AbstractTokenParser
{
/**
* {@inheritDoc}
*/
public function parse(Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$key = $this->parser->getVarName() . $lineno;
$lifetime = Grav::instance()['cache']->getLifetime();
// Check for optional lifetime override
if (!$stream->test(Token::BLOCK_END_TYPE)) {
$lifetime_expr = $this->parser->getExpressionParser()->parseExpression();
$lifetime = $lifetime_expr->getAttribute('value');
}
$stream->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideCacheEnd'), true);
$stream->expect(Token::BLOCK_END_TYPE);
return new TwigNodeCache($key, $lifetime, $body, $lineno, $this->getTag());
}
/**
* Decide if current token marks end of cache block.
*
* @param Token $token
* @return bool
*/
public function decideCacheEnd(Token $token)
{
return $token->test('endcache');
}
/**
* {@inheritDoc}
*/
public function getTag()
{
return 'cache';
}
}

View File

@@ -11,10 +11,12 @@ namespace Grav\Common\Twig;
use Cron\CronExpression;
use Grav\Common\Config\Config;
use Grav\Common\Data\Data;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Collection;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Media;
use Grav\Common\Scheduler\Cron;
use Grav\Common\Security;
@@ -25,6 +27,7 @@ use Grav\Common\Twig\TokenParser\TwigTokenParserSwitch;
use Grav\Common\Twig\TokenParser\TwigTokenParserThrow;
use Grav\Common\Twig\TokenParser\TwigTokenParserTryCatch;
use Grav\Common\Twig\TokenParser\TwigTokenParserMarkdown;
use Grav\Common\Twig\TokenParser\TwigTokenParserCache;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Common\Yaml;
@@ -167,13 +170,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('exif', [$this, 'exifFunc']),
new \Twig_SimpleFunction('media_directory', [$this, 'mediaDirFunc']),
new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']),
new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc']),
new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc']),
new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc'], ['needs_context' => true]),
new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc'], ['needs_context' => true]),
new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']),
new \Twig_SimpleFunction('cron', [$this, 'cronFunc']),
new \Twig_SimpleFunction('svg_image', [$this, 'svgImageFunction']),
new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
@@ -201,6 +205,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new TwigTokenParserStyle(),
new TwigTokenParserMarkdown(),
new TwigTokenParserSwitch(),
new TwigTokenParserCache(),
];
}
@@ -613,10 +618,11 @@ 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($context = false, $string, $block = true)
public function markdownFunction($context, $string, $block = true)
{
$page = $context['page'] ?? null;
return Utils::processMarkdown($string, $block, $page);
@@ -1054,7 +1060,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);
}
/**
@@ -1280,17 +1286,64 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Get a theme variable
* Will try to get the variable for the current page, if not found, it tries it's parent page on up to root.
* If still not found, will use the theme's configuration value,
* If still not found, will use the $default value passed in
*
* @param string $var
* @param bool $default
* @param $context Twig Context
* @param string $var variable to be found (using dot notation)
* @param null $default the default value to be used as last resort
* @param null $page an optional page to use for the current page
* @param bool $exists toggle to simply return the page where the variable is set, else null
* @return string
*/
public function themeVarFunc($var, $default = null)
public function themeVarFunc($context, $var, $default = null, $page = null, $exists = false)
{
$header = $this->grav['page']->header();
$header_classes = $header->{$var} ?? null;
$page = $page ?? $context['page'] ?? Grav::instance()['page'] ?? null;
return $header_classes ?: $this->config->get('theme.' . $var, $default);
// Try to find var in the page headers
if ($page instanceof PageInterface && $page->exists()) {
// Loop over pages and look for header vars
while ($page && !$page->root()) {
$header = new Data((array)$page->header());
$value = $header->get($var);
if (isset($value)) {
if ($exists) {
return $page;
} else {
return $value;
}
}
$page = $page->parent();
}
}
if ($exists) {
return false;
} else {
return Grav::instance()['config']->get('theme.' . $var, $default);
}
}
/**
* Look for a page header variable in an array of pages working its way through until a value is found
*
* @param $context
* @param string $var the variable to look for in the page header
* @param string|string[]|null $pages array of pages to check (current page upwards if not null)
* @param bool $exists if true, return the page where the var is found, not the value
* @return mixed
* @deprecated 1.7 Use themeVarFunc() instead
*/
public function pageHeaderVarFunc($context, $var, $pages = null)
{
if (is_array($pages)) {
$page = array_shift($pages);
} else {
$page = null;
}
return $this->themeVarFunc($context, $var, null, $page);
}
/**
@@ -1318,41 +1371,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return $body_classes;
}
/**
* Look for a page header variable in an array of pages working its way through until a value is found
*
* @param string $var
* @param string|string[]|null $pages
* @return mixed
*/
public function pageHeaderVarFunc($var, $pages = null)
{
if ($pages === null) {
$pages = $this->grav['page'];
}
// Make sure pages are an array
if (!\is_array($pages)) {
$pages = [$pages];
}
// Loop over pages and look for header vars
foreach ($pages as $page) {
if (\is_string($page)) {
$page = $this->grav['pages']->find($page);
}
if ($page) {
$header = $page->header();
if (isset($header->{$var})) {
return $header->{$var};
}
}
}
return null;
}
/**
* Dump/Encode data into YAML format
*
@@ -1441,4 +1459,42 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
break;
}
}
/**
* Returns the content of an SVG image and adds extra classes as needed
*
* @param $path
* @param $classes
* @return string|string[]|null
*/
public static function svgImageFunction($path, $classes)
{
$path = Utils::fullPath($path);
if (file_exists($path)) {
$svg = file_get_contents($path);
$classes = " inline-block $classes";
$matched = false;
//Look for existing class
$svg = preg_replace_callback('/^<svg[^>]*(class=\")([^"]*)(\")[^>]*>/', function($matches) use ($classes, &$matched) {
if (isset($matches[2])) {
$new_classes = $matches[2] . $classes;
$matched = true;
return str_replace($matches[1], "class=\"$new_classes\"", $matches[0]);
}
return $matches[0];
}, $svg
);
// no matches found just add the class
if (!$matched) {
$classes = trim($classes);
$svg = str_replace('<svg ', "<svg class=\"$classes\" ", $svg);
}
return $svg;
}
}
}

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

View File

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

View File

@@ -129,6 +129,28 @@ abstract class Utils
return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
}
/**
* Helper method to find the full path to a file, be it a stream, a relative path, or
* already a full path
*
* @param $path
* @return string
*/
public static function fullPath($path)
{
$locator = Grav::instance()['locator'];
if ($locator->isStream($path)) {
$path = $locator->findResource($path, true);
} elseif (!Utils::startsWith($path, GRAV_ROOT)) {
$base_url = Grav::instance()['base_url'];
$path = GRAV_ROOT . '/' . ltrim(Utils::replaceFirstOccurrence($base_url, '', $path), '/');
}
return $path;
}
/**
* Check if the $haystack string starts with the substring $needle
*
@@ -837,11 +859,7 @@ abstract class Utils
public static function checkFilename($filename)
{
$dangerous_extensions = Grav::instance()['config']->get('security.uploads_dangerous_extensions', []);
array_walk($dangerous_extensions, function(&$val) {
$val = '.' . $val;
});
$extension = '.' . pathinfo($filename, PATHINFO_EXTENSION);
$extension = pathinfo($filename, PATHINFO_EXTENSION);
return !(
// Empty filenames are not allowed.
@@ -850,8 +868,8 @@ abstract class Utils
|| strtr($filename, "\t\v\n\r\0\\/", '_______') !== $filename
// Filename should not start or end with dot or space.
|| trim($filename, '. ') !== $filename
// Filename should not contain .php in it.
|| static::contains($extension, $dangerous_extensions)
// File extension should not be part of configured dangerous extensions
|| in_array($extension, $dangerous_extensions)
);
}

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

@@ -445,7 +445,7 @@ class InstallCommand extends ConsoleCommand
} else {
$repo_dir = $matches[2];
}
$paths = (array) $paths;
foreach ($paths as $repo) {
$path = rtrim($repo, '/') . '/' . $repo_dir;
@@ -570,7 +570,8 @@ class InstallCommand extends ConsoleCommand
[
'slug' => $package->slug,
'filename' => $package->premium['filename'],
'license_key' => $license
'license_key' => $license,
'sid' => md5(GRAV_ROOT)
]
));

View File

@@ -46,6 +46,9 @@ class SelfupgradeCommand extends ConsoleCommand
protected $overwrite;
/** @var int */
protected $timeout;
protected function configure()
{
$this
@@ -69,6 +72,13 @@ class SelfupgradeCommand extends ConsoleCommand
InputOption::VALUE_NONE,
'Option to overwrite packages if they already exist'
)
->addOption(
'timeout',
't',
InputOption::VALUE_OPTIONAL,
'Option to set the timeout in seconds when downloading the update (0 for no timeout)',
30
)
->setDescription('Detects and performs an update of Grav itself when available')
->setHelp('The <info>update</info> command updates Grav itself when a new version is available');
}
@@ -78,6 +88,7 @@ class SelfupgradeCommand extends ConsoleCommand
$this->upgrader = new Upgrader($this->input->getOption('force'));
$this->all_yes = $this->input->getOption('all-yes');
$this->overwrite = $this->input->getOption('overwrite');
$this->timeout = (int) $this->input->getOption('timeout');
$this->displayGPMRelease();
@@ -116,7 +127,6 @@ class SelfupgradeCommand extends ConsoleCommand
$questionHelper = $this->getHelper('question');
$this->output->writeln("Grav v<cyan>{$remote}</cyan> is now available [release date: {$release}].");
$this->output->writeln('You are currently using v<cyan>' . GRAV_VERSION . '</cyan>.');
@@ -177,7 +187,7 @@ class SelfupgradeCommand extends ConsoleCommand
}
/**
* @param Package $package
* @param array $package
*
* @return string
*/
@@ -185,7 +195,16 @@ class SelfupgradeCommand extends ConsoleCommand
{
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$this->tmp = $tmp_dir . '/Grav-' . uniqid('', false);
$output = Response::get($package['download'], [], [$this, 'progress']);
$options = [
'curl' => [
CURLOPT_TIMEOUT => $this->timeout,
],
'fopen' => [
'timeout' => $this->timeout,
],
];
$output = Response::get($package['download'], $options, [$this, 'progress']);
Folder::create($this->tmp);

View File

@@ -79,10 +79,28 @@ 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;

View File

@@ -342,5 +342,5 @@ trait FlexMediaTrait
abstract public function getFlexDirectory(): FlexDirectory;
abstract public function getStorageKey();
abstract public function getStorageKey(): string;
}

View File

@@ -640,7 +640,7 @@ trait FormTrait
foreach ($data as $key => &$value) {
if (\is_array($value)) {
$value = $this->jsonDecode($value);
} elseif ($value === '') {
} elseif (trim($value) === '') {
unset($data[$key]);
} else {
$value = json_decode($value, true);

View File

@@ -63,9 +63,10 @@ trait ObjectCollectionTrait
/**
* @param string $property Object property to be fetched.
* @param mixed $default Default value if not set.
* @param bool $doCreate Not being used.
* @return mixed[] Key/Value pairs of the properties.
*/
public function doGetProperty($property, $default = null)
public function &doGetProperty($property, $default = null, $doCreate = false)
{
$list = [];

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,532 @@
<?php
/**
* @package Grav\Framework\Parsedown
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Parsedown;
/*
* Parsedown Extra
* http://parsedown.org
*
* (c) Emanuil Rusev
* http://erusev.com
*
* This file ported from officiall ParsedownExtra repo and kept for compatibility.
*/
class ParsedownExtra extends Parsedown
{
# ~
const version = '0.7.0';
# ~
function __construct()
{
if (parent::version < '1.5.0')
{
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
$this->BlockTypes[':'] []= 'DefinitionList';
$this->BlockTypes['*'] []= 'Abbreviation';
# identify footnote definitions before reference definitions
array_unshift($this->BlockTypes['['], 'Footnote');
# identify footnote markers before before links
array_unshift($this->InlineTypes['['], 'FootnoteMarker');
}
#
# ~
function text($text)
{
$markup = parent::text($text);
# merge consecutive dl elements
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
# add footnotes
if (isset($this->DefinitionData['Footnote']))
{
$Element = $this->buildFootnoteElement();
$markup .= "\n" . $this->element($Element);
}
return $markup;
}
#
# Blocks
#
#
# Abbreviation
protected function blockAbbreviation($Line)
{
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
{
$this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
$Block = array(
'hidden' => true,
);
return $Block;
}
}
#
# Footnote
protected function blockFootnote($Line)
{
if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
{
$Block = array(
'label' => $matches[1],
'text' => $matches[2],
'hidden' => true,
);
return $Block;
}
}
protected function blockFootnoteContinue($Line, $Block)
{
if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
{
return;
}
if (isset($Block['interrupted']))
{
if ($Line['indent'] >= 4)
{
$Block['text'] .= "\n\n" . $Line['text'];
return $Block;
}
}
else
{
$Block['text'] .= "\n" . $Line['text'];
return $Block;
}
}
protected function blockFootnoteComplete($Block)
{
$this->DefinitionData['Footnote'][$Block['label']] = array(
'text' => $Block['text'],
'count' => null,
'number' => null,
);
return $Block;
}
#
# Definition List
protected function blockDefinitionList($Line, $Block)
{
if ( ! isset($Block) or isset($Block['type']))
{
return;
}
$Element = array(
'name' => 'dl',
'handler' => 'elements',
'text' => array(),
);
$terms = explode("\n", $Block['element']['text']);
foreach ($terms as $term)
{
$Element['text'] []= array(
'name' => 'dt',
'handler' => 'line',
'text' => $term,
);
}
$Block['element'] = $Element;
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
protected function blockDefinitionListContinue($Line, array $Block)
{
if ($Line['text'][0] === ':')
{
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
else
{
if (isset($Block['interrupted']) and $Line['indent'] === 0)
{
return;
}
if (isset($Block['interrupted']))
{
$Block['dd']['handler'] = 'text';
$Block['dd']['text'] .= "\n\n";
unset($Block['interrupted']);
}
$text = substr($Line['body'], min($Line['indent'], 4));
$Block['dd']['text'] .= "\n" . $text;
return $Block;
}
}
#
# Header
protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
}
return $Block;
}
#
# Markup
protected function blockMarkupComplete($Block)
{
if ( ! isset($Block['void']))
{
$Block['markup'] = $this->processTag($Block['markup']);
}
return $Block;
}
#
# Setext
protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
}
return $Block;
}
#
# Inline Elements
#
#
# Footnote Marker
protected function inlineFootnoteMarker($Excerpt)
{
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
{
$name = $matches[1];
if ( ! isset($this->DefinitionData['Footnote'][$name]))
{
return;
}
$this->DefinitionData['Footnote'][$name]['count'] ++;
if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
{
$this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
}
$Element = array(
'name' => 'sup',
'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
'handler' => 'element',
'text' => array(
'name' => 'a',
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
'text' => $this->DefinitionData['Footnote'][$name]['number'],
),
);
return array(
'extent' => strlen($matches[0]),
'element' => $Element,
);
}
}
private $footnoteCount = 0;
#
# Link
protected function inlineLink($Excerpt)
{
$Link = parent::inlineLink($Excerpt);
$remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
{
$Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
$Link['extent'] += strlen($matches[0]);
}
return $Link;
}
#
# ~
#
protected function unmarkedText($text)
{
$text = parent::unmarkedText($text);
if (isset($this->DefinitionData['Abbreviation']))
{
foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
{
$pattern = '/\b'.preg_quote($abbreviation, '/').'\b/';
$text = preg_replace($pattern, '<abbr title="'.$meaning.'">'.$abbreviation.'</abbr>', $text);
}
}
return $text;
}
#
# Util Methods
#
protected function addDdElement(array $Line, array $Block)
{
$text = substr($Line['text'], 1);
$text = trim($text);
unset($Block['dd']);
$Block['dd'] = array(
'name' => 'dd',
'handler' => 'line',
'text' => $text,
);
if (isset($Block['interrupted']))
{
$Block['dd']['handler'] = 'text';
unset($Block['interrupted']);
}
$Block['element']['text'] []= & $Block['dd'];
return $Block;
}
protected function buildFootnoteElement()
{
$Element = array(
'name' => 'div',
'attributes' => array('class' => 'footnotes'),
'handler' => 'elements',
'text' => array(
array(
'name' => 'hr',
),
array(
'name' => 'ol',
'handler' => 'elements',
'text' => array(),
),
),
);
uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
{
if ( ! isset($DefinitionData['number']))
{
continue;
}
$text = $DefinitionData['text'];
$text = parent::text($text);
$numbers = range(1, $DefinitionData['count']);
$backLinksMarkup = '';
foreach ($numbers as $number)
{
$backLinksMarkup .= ' <a href="#fnref'.$number.':'.$definitionId.'" rev="footnote" class="footnote-backref">&#8617;</a>';
}
$backLinksMarkup = substr($backLinksMarkup, 1);
if (substr($text, - 4) === '</p>')
{
$backLinksMarkup = '&#160;'.$backLinksMarkup;
$text = substr_replace($text, $backLinksMarkup.'</p>', - 4);
}
else
{
$text .= "\n".'<p>'.$backLinksMarkup.'</p>';
}
$Element['text'][1]['text'] []= array(
'name' => 'li',
'attributes' => array('id' => 'fn:'.$definitionId),
'text' => "\n".$text."\n",
);
}
return $Element;
}
# ~
protected function parseAttributeData($attributeString)
{
$Data = array();
$attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
foreach ($attributes as $attribute)
{
if ($attribute[0] === '#')
{
$Data['id'] = substr($attribute, 1);
}
else # "."
{
$classes []= substr($attribute, 1);
}
}
if (isset($classes))
{
$Data['class'] = implode(' ', $classes);
}
return $Data;
}
# ~
protected function processTag($elementMarkup) # recursive
{
# http://stackoverflow.com/q/1148928/200145
libxml_use_internal_errors(true);
$DOMDocument = new \DOMDocument;
# http://stackoverflow.com/q/11309194/200145
$elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
# http://stackoverflow.com/q/4879946/200145
$DOMDocument->loadHTML($elementMarkup);
$DOMDocument->removeChild($DOMDocument->doctype);
$DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
$elementText = '';
if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$elementText .= $DOMDocument->saveHTML($Node);
}
$DOMDocument->documentElement->removeAttribute('markdown');
$elementText = "\n".$this->text($elementText)."\n";
}
else
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$nodeMarkup = $DOMDocument->saveHTML($Node);
if ($Node instanceof \DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
{
$elementText .= $this->processTag($nodeMarkup);
}
else
{
$elementText .= $nodeMarkup;
}
}
}
# because we don't want for markup to get encoded
$DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
$markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
$markup = str_replace('placeholder\x1A', $elementText, $markup);
return $markup;
}
# ~
protected function sortFootnotes($A, $B) # callback
{
return $A['number'] - $B['number'];
}
#
# Fields
#
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
}

View File

@@ -286,7 +286,7 @@ class Route
$url .= '?' . $this->getUriQuery();
}
return $url;
return rtrim($url,'/');
}
/**
@@ -344,9 +344,8 @@ class Route
$parts[] = $this->language;
}
if ($this->route !== '') {
$parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route;
}
$parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route;
if ($this->gravParams) {
$parts[] = RouteFactory::buildParams($this->gravParams);

View File

@@ -9,6 +9,7 @@
namespace Grav\Framework\Session;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Framework\Session\Exceptions\SessionException;
/**
@@ -17,16 +18,13 @@ use Grav\Framework\Session\Exceptions\SessionException;
*/
class Session implements SessionInterface
{
protected $options;
/** @var array */
protected $options = [];
/**
* @var bool
*/
/** @var bool */
protected $started = false;
/**
* @var Session
*/
/** @var Session */
protected static $instance;
/**
@@ -178,9 +176,13 @@ class Session implements SessionInterface
return $this;
}
$sessionName = session_name();
$sessionExists = isset($_COOKIE[$sessionName]);
// Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) {
unset($_COOKIE[session_name()]);
if ($sessionExists && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[$sessionName])) {
unset($_COOKIE[$sessionName]);
$sessionExists = false;
}
$options = $this->options;
@@ -197,24 +199,28 @@ class Session implements SessionInterface
throw new SessionException('Failed to start session: ' . $error, 500);
}
if ($user && !$user->isValid()) {
$this->clear();
throw new SessionException('User Invalid', 500);
$this->started = true;
if ($user && (!$user instanceof UserInterface || !$user->isValid())) {
$this->invalidate();
throw new SessionException('Invalid User object, session destroyed.', 500);
}
$params = session_get_cookie_params();
// Extend the lifetime of the session.
if ($sessionExists) {
$params = session_get_cookie_params();
setcookie(
session_name(),
session_id(),
time() + $params['lifetime'],
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
$this->started = true;
setcookie(
$sessionName,
session_id(),
time() + $params['lifetime'],
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
return $this;
}

View File

@@ -39,3 +39,11 @@ foreach($iterator as $directory) {
require $autoloader;
}
}
define('GANTRY_DEBUGGER', true);
define('GANTRY5_DEBUG', true);
define('GANTRY5_PLATFORM', 'grav');
define('GANTRY5_ROOT', rtrim(ROOT_DIR, '/'));
define('GANTRY5_VERSION', '@version@');
define('GANTRY5_VERSION_DATE', '@versiondate@');
define('GANTRYADMIN_PATH', '');

View File

@@ -7,6 +7,8 @@ parameters:
excludes_analyse:
- %currentWorkingDirectory%/user/plugins/*/vendor/*
- %currentWorkingDirectory%/user/plugins/*/tests/*
- %currentWorkingDirectory%/user/plugins/gantry5/src/platforms
- %currentWorkingDirectory%/user/plugins/gantry5/src/classes/Gantry/Framework/Services/ErrorServiceProvider.php
bootstrap: tests/phpstan/plugins-bootstrap.php
reportUnmatchedIgnoredErrors: true
universalObjectCratesClasses:
@@ -18,3 +20,4 @@ parameters:
- Grav\Common\GPM\Local\Package
- Grav\Common\GPM\Remote\Package
- Grav\Common\Session
- Gantry\Component\Config\Config

View File

@@ -110,6 +110,7 @@ class ParsedownTest extends \Codeception\TestCase\Test
public function testImagesSubDir()
{
$this->config->set('system.images.cache_all', false);
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init();
$this->assertRegexp('|<p><img alt="" src="\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',

View File

@@ -188,7 +188,7 @@ class UriTest extends \Codeception\TestCase\Test
'environment' => 'localhost',
'basename' => 'it',
'base' => 'http://localhost:8080',
'currentPage' => '',
'currentPage' => 1,
'rootUrl' => 'http://localhost:8080',
'extension' => null,
'addNonce' => 'http://localhost:8080/grav/it/ueper:xxx/page:/test:yyy/nonce:{{nonce}}',
@@ -298,7 +298,7 @@ class UriTest extends \Codeception\TestCase\Test
'environment' => 'api.getgrav.com',
'basename' => '128',
'base' => 'https://api.getgrav.com:4040',
'currentPage' => 'x',
'currentPage' => 1,
'rootUrl' => 'https://api.getgrav.com:4040',
'extension' => null,
'addNonce' => 'https://username:password@api.getgrav.com:4040/v1/post/128/page:x/nonce:{{nonce}}?all=1',
@@ -1073,7 +1073,7 @@ class UriTest extends \Codeception\TestCase\Test
$this->runTestSet($this->tests, 'currentPage');
$this->uri->initializeWithURL('http://localhost:8080/a-page/page:2')->init();
$this->assertSame('2', $this->uri->currentPage());
$this->assertSame(2, $this->uri->currentPage());
}
public function testReferrer()

View File

@@ -523,4 +523,20 @@ class UtilsTest extends \Codeception\TestCase\Test
$this->assertSame('//foo.com', Utils::url('//foo.com'));
$this->assertSame('//foo.com?param=x', Utils::url('//foo.com?param=x'));
}
public function testCheckFilename()
{
// configure extension for consistent results
/** @var \Grav\Common\Config\Config $config */
$config = $this->grav['config'];
$config->set('security.uploads_dangerous_extensions', ['php', 'html', 'htm', 'exe', 'js']);
$this->assertFalse(Utils::checkFilename('foo.php'));
$this->assertFalse(Utils::checkFilename('bar.js'));
$this->assertTrue(Utils::checkFilename('foo.json'));
$this->assertTrue(Utils::checkFilename('foo.xml'));
$this->assertTrue(Utils::checkFilename('foo.yaml'));
$this->assertTrue(Utils::checkFilename('foo.yml'));
}
}

View File

@@ -1,33 +1,24 @@
:8080
gzip
fastcgi / 127.0.0.1:9000 php
encode gzip
root * /path/to/grav/root
php_fastcgi unix//run/php/php7.3-fpm.sock
file_server
# Begin - Security
# deny all direct access for these folders
rewrite {
r /(\.git|cache|bin|logs|backups|tests)/.*$
to /403
}
# deny running scripts inside core system folders
rewrite {
r /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$
to /403
}
# deny running scripts inside user folder
rewrite {
r /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$
to /403
}
# deny access to specific files in the root folder
rewrite {
r /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess)
to /403
}
rewrite /(\.git|cache|bin|logs|backups|tests)/.* /403
status 403 /403
# deny running scripts inside core system folders
rewrite /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ /403
# deny running scripts inside user folder
rewrite /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ /403
# deny access to specific files in the root folder
rewrite /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) /403
respond /403 403
## End - Security
# global rewrite should come last.
rewrite {
to {path} {path}/ /index.php?_url={uri}&{query}
}
try_files {path} {path}/ /index.php?_url={uri}&{query}

View File

@@ -27,6 +27,9 @@ RewriteEngine On
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to use twig tags in URL.
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.

View File

@@ -23,7 +23,7 @@ $HTTP["querystring"] =~ "_REQUEST(=|\[|\%[0-9A-Z])" {
#REROUTING TO THE INDEX PAGE
url.rewrite-if-not-file = (
"^/grav_path/(.*)$" => "/grav_path/index.php$1"
"^/grav_path/(.*)$" => "/grav_path/index.php?$1"
)
#IMPROVING SECURITY

View File

@@ -1,118 +0,0 @@
# ddev GravCMS config
# You can override ddev's configuration by placing an edited copy
# of this config (or one of the other ones) in .ddev/nginx-site.conf
# See https://ddev.readthedocs.io/en/latest/users/extend/customization-extendibility/#providing-custom-nginx-configuration
# Set https to 'on' if x-forwarded-proto is https
map $http_x_forwarded_proto $fcgi_https {
default off;
https on;
}
server {
listen 80; ## listen for ipv4; this line is default and implied
listen [::]:80 default ipv6only=on; ## listen for ipv6
# The NGINX_DOCROOT variable is substituted with
# its value when the container is started.
root $NGINX_DOCROOT;
index index.php index.htm index.html;
# Make site accessible from http://localhost/
server_name _;
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;
error_log /var/log/nginx/error.log info;
access_log /var/log/nginx/access.log;
location / {
absolute_redirect off;
try_files $uri $uri/ /index.php?$query_string;
}
# pass the PHP scripts to FastCGI server listening on socket
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm.sock;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_intercept_errors on;
# fastcgi_read_timeout should match max_execution_time in php.ini
fastcgi_read_timeout 10m;
fastcgi_param SERVER_NAME $host;
fastcgi_param HTTPS $fcgi_https;
}
# Expire rules for static content
# Feed
location ~* \.(?:rss|atom|cache)$ {
expires 1h;
}
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
add_header Cache-Control "public";
}
# Prevent clients from accessing hidden files (starting with a dot)
# This is particularly important if you store .htpasswd files in the site hierarchy
# Access to `/.well-known/` is allowed.
# https://www.mnot.net/blog/2010/04/07/well-known
# https://tools.ietf.org/html/rfc5785
location ~* /\.(?!well-known\/) {
deny all;
}
# Prevent clients from accessing to backup/config/source files
location ~* (?:\.(?:bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$ {
deny all;
}
## Begin - Security
# deny all direct access for these folders
location ~* /(\.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
# deny running scripts inside core system folders
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny access to specific files in the root folder
location ~ /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) { return 403; }
## End - Security
## provide a health check endpoint
location /healthcheck {
access_log off;
stub_status on;
keepalive_timeout 0; # Disable HTTP keepalive
return 200;
}
error_page 400 401 /40x.html;
location = /40x.html {
root /usr/share/nginx/html;
}
location ~ ^/(fpmstatus|ping)$ {
access_log off;
stub_status on;
keepalive_timeout 0; # Disable HTTP keepalive
allow 127.0.0.1;
allow all;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm.sock;
}
}