Compare commits

...

244 Commits

Author SHA1 Message Date
Andy Miller
e762c3add9 Merge branch 'release/1.5.10' 2019-03-21 14:16:21 -06:00
Andy Miller
2fed02affa Prepare for release 2019-03-21 14:16:10 -06:00
Andy Miller
fcbd819f48 Added deferred twig extension 2019-03-20 16:31:19 -06:00
Andy Miller
88a3e874aa Merge branch 'release/1.5.9' 2019-03-20 14:01:21 -06:00
Andy Miller
87814039bc Merge tag '1.5.9' into develop
Release v1.5.9
2019-03-20 14:01:21 -06:00
Andy Miller
af72951671 prepare for release 2019-03-20 14:01:10 -06:00
Andy Miller
5f2dfc8221 Use toolbox 1.4.2 in develop branch for now 2019-03-20 13:58:00 -06:00
Andy Miller
c706756d19 changelog updated 2019-03-20 13:52:27 -06:00
Andy Miller
a4801ead6a Fix for streams with EXIF 2019-03-20 13:52:04 -06:00
Andy Miller
ab23d071a4 Merge branch 'release/1.5.9' 2019-03-20 12:04:51 -06:00
Andy Miller
3e6c4e9c5e Merge tag '1.5.9' into develop
Release 1.5.9
2019-03-20 12:04:51 -06:00
Andy Miller
28db98c95d Prepare for release 2019-03-20 12:04:26 -06:00
Andy Miller
95583dbbcd updated vendor libs 2019-03-20 11:50:00 -06:00
Andy Miller
9d8fc4a065 New onPageContent() event 2019-03-20 11:16:18 -06:00
Matias Griese
b4a4b60871 Fixed phpdoc generation 2019-03-13 16:00:00 +02:00
Kirsten Roschanski
c80a3f5568 Update User.php (#2403) 2019-03-12 14:15:03 -06:00
Robbert
409b4028d0 fix dead link + add http > https changes to site urls (#2369) 2019-02-12 12:14:28 -07:00
Andy Miller
88121aaab4 Merge tag '1.5.8' into develop
Release v1.5.8
2019-02-07 16:04:56 -07:00
Andy Miller
8f588aca99 Merge branch 'release/1.5.8' 2019-02-07 16:04:55 -07:00
Andy Miller
5b34a9bfc4 prepare for release 2019-02-07 16:04:31 -07:00
Andy Miller
ef55d2615f update changelog 2019-02-07 16:03:49 -07:00
Matias Griese
2fa5021a0c Improved User unserialize to not to break the object if serialized data is not what expected 2019-02-01 09:04:01 +02:00
Hamilton Turner
b91574e5c2 Remove unused parameter (#2357) 2019-01-30 13:42:34 -07:00
Stefan Bauer
6d6689b431 Update CHANGELOG.md (#2355) 2019-01-29 10:21:39 +02:00
Andy Miller
95a4ecb0f1 Merge branch 'release/1.5.7' 2019-01-25 14:38:52 -07:00
Andy Miller
ff8cc83b3a Merge tag '1.5.7' into develop
Release v1.5.7
2019-01-25 14:38:52 -07:00
Andy Miller
fbd3bbdbf9 Prepare for release 2019-01-25 14:38:31 -07:00
Andy Miller
f0e58adfbc Updated vendor libraries 2019-01-25 14:37:43 -07:00
Raphaël Droz
f1363877d8 preserve accents in fields containing Twig expr. using unicode (#2279)
When a fields contain accentuated characters, reduce the risk of messing with it by passing unicode characters unescaped.
Twig will deal with them. And fewer backslash-escaping problems will arise.
2019-01-09 13:05:32 -07:00
Basile Trujillo [L0gIn]
e8825beae5 Added support for AWS Cloudfront forwarded scheme header (#2297)
AWS Cloudfront does not provide HTTP_X_FORWARDED_PROTO header but provide a HTTP_CLOUDFRONT_FORWARDED_PROTO header instead
2019-01-09 13:04:32 -07:00
ranitham
394dfad566 Small bugfix for responsive images (#2300)
* Replace spaces in image filename with %20 to avoid parse errors with the srcset attribute

* Update system/src/Grav/Common/Page/Medium/ImageMedium.php

Co-Authored-By: ranitham <ranitha.m@gmail.com>
2019-01-07 21:34:00 -07:00
Rotzbua
680bfef2c3 http to https (#2299) 2019-01-07 21:28:56 -07:00
Andy Miller
28bd4027cd Merge branch 'release/1.5.6' 2018-12-14 15:07:22 -07:00
Andy Miller
108541e0d0 Merge tag '1.5.6' into develop
Release v1.5.6
2018-12-14 15:07:22 -07:00
Andy Miller
54a177279f prepare for release 2018-12-14 15:07:12 -07:00
Andy Miller
b3e9974e95 updated changelog 2018-12-14 15:06:39 -07:00
Matias Griese
708c79cef8 Improved user serialization to use less memory in the session 2018-11-27 10:15:55 +02:00
Stephan Diehl
b6c582ad3a Update InitializeProcessor.php (#2268)
fix redirect_trailing_slash in a multi language page
2018-11-23 23:01:47 -07:00
Andy Miller
5e1980d770 New Discord reference 2018-11-15 16:30:21 -07:00
Andy Miller
5e7eb6b2f4 Better discord badge 2018-11-15 16:29:08 -07:00
Andy Miller
4daa07fd14 Discord Chat button 2018-11-15 16:21:51 -07:00
Djamil Legato
9a8b47872e Updated opencollective backers/sponsors list 2018-11-13 23:05:18 -08:00
Andy Miller
9405418572 Merge branch 'release/1.5.5' 2018-11-12 15:56:13 -07:00
Andy Miller
59ccd662c7 Merge tag '1.5.5' into develop
Release v1.5.5
2018-11-12 15:56:13 -07:00
Andy Miller
377751416b Prepare for release 2018-11-12 15:56:04 -07:00
Andy Miller
116c279f01 update changelog 2018-11-12 15:55:29 -07:00
Aaron Dalton
bf86b5a924 Propagate error code if between 400 and 600 for production sites (errors:display = false or -1) (#2181) 2018-11-11 21:22:47 -07:00
Emil Hesslow
d0b34d114d Register theme prefixes as namespaces in twig (#2210) 2018-11-11 21:18:57 -07:00
Makara Sok
b9dc2baef1 Remove hardcoded 302 when redirecting trailing slash (#2155)
When `system.pages.redirect_trailing_slash` is enabled, it's always a 302 even though `redirect_default_code` is set to something else. 

By removing it, it works as intended.
2018-11-09 22:30:35 -07:00
Andy Miller
72cc5b9d07 Merge tag '1.5.4' into develop
Release v1.5.4
2018-11-05 15:42:04 -07:00
Andy Miller
b9c28c5a7c Merge branch 'release/1.5.4' 2018-11-05 15:42:03 -07:00
Andy Miller
c56d7ac793 prepare for release 2018-11-05 15:41:54 -07:00
Andy Miller
000bac8cfc Updated changelog 2018-11-05 15:41:19 -07:00
Scott Hamper
e7d660149e Fixed markdown parsing for telephone links (#2235)
Telephone links use the `+` character to specify a country code, but Grav was replacing the `+` with a space character.
2018-11-03 14:57:02 -06:00
John Hamrick
829638c143 Update default.md (#2245)
Making documentation consistent between sources:  Issue Typo! #650

In (grav-learn/pages/01.basics/04.basic-tutorial/docs.md) the text under the heading Adding a New Page is 02.mypage 
In (grav/user/pages/01.home/default.md) the text under the heading Adding a New Page is 03.mypage
2018-11-03 14:56:21 -06:00
Matias Griese
d8a627898e Fixed fatal error if calling $session->invalidate() when there's no active session 2018-10-25 16:29:53 +03:00
MattAppleton
a3caa13c23 fix .webm typo (#2220)
Media type should be 'video' not file!
2018-10-16 20:18:32 -06:00
Andy Miller
9944486c17 Merge branch 'release/1.5.3' 2018-10-08 17:41:28 -06:00
Andy Miller
da5c9e415f Merge tag '1.5.3' into develop
Release v1.5.3
2018-10-08 17:41:28 -06:00
Andy Miller
7b5a1b2c14 Prepare for release 2018-10-08 17:41:18 -06:00
Andy Miller
235a5cc765 vendor updates 2018-10-08 17:33:49 -06:00
Andy Miller
073d601b67 Updated changelog 2018-10-06 16:37:11 -06:00
Andy Miller
ad1bbba0b3 Added configurable dangerous upload extensions 2018-10-06 16:35:54 -06:00
Matias Griese
b6b5e329aa Added Utils::getMimeByFilename(), Utils::getMimeByLocalFile() and Utils::checkFilename() methods 2018-10-04 14:44:04 +03:00
Andy Miller
0e973dab07 Merge branch 'release/1.5.2' 2018-10-01 15:33:13 -06:00
Andy Miller
15e371564a Merge tag '1.5.2' into develop
Release v1.5.2
2018-10-01 15:33:13 -06:00
Andy Miller
f0e33dc242 prepare for release 2018-10-01 15:32:29 -06:00
Andy Miller
e67c3c1091 updated changelog 2018-10-01 15:31:39 -06:00
Andy Miller
d5ce0bd93c updated vendor libs 2018-10-01 15:26:44 -06:00
Andy Miller
44dbcdf2b1 Added new XSS Twig function 2018-10-01 14:07:14 -06:00
Andy Miller
3216442946 Merge branch 'develop' of github.com:getgrav/grav into develop 2018-10-01 12:34:14 -06:00
Andy Miller
9d4471b196 Security refactor 2018-10-01 12:34:09 -06:00
Matias Griese
c48107acd9 Merge remote-tracking branch 'origin/develop' into develop 2018-10-01 21:02:11 +03:00
Matias Griese
4671518409 Fixed missing slug in Page::init() 2018-10-01 21:02:04 +03:00
Andy Miller
41bf943f49 get raw content for all pages 2018-09-30 21:11:46 -06:00
Andy Miller
f40c6a8617 Changelog updated 2018-09-30 18:37:42 -06:00
Andy Miller
fb98ca7b19 Added a new Security CLI command 2018-09-30 18:34:53 -06:00
Andy Miller
451ec49d9c refactor 2018-09-30 17:45:45 -06:00
Andy Miller
1709eb038c Fix for array method 2018-09-30 15:24:01 -06:00
Andy Miller
e69d6cefee ordering 2018-09-30 00:10:44 -06:00
Andy Miller
7abe01ed8c vertical style 2018-09-30 00:10:04 -06:00
Andy Miller
17a371d86a lang stuff 2018-09-29 21:37:01 -06:00
Andy Miller
5b787d56e6 Add default XSS security config 2018-09-29 21:24:58 -06:00
Andy Miller
33d98114ba XSS enhancements 2018-09-29 21:24:21 -06:00
Andy Miller
51f29e112a updated composer.json 2018-09-19 13:56:09 -06:00
Matias Griese
ca8805683d Added onHttpPostFilter event to allow plugins to globally clean up XSS in the forms and tasks 2018-09-19 12:09:32 +03:00
Matias Griese
8295bd8243 Added Utils::detectXssFromArray() and Utils::detectXss() methods 2018-09-19 12:06:49 +03:00
Matias Griese
da95d1bb1e Session expires in 30 mins independent from config settings (https://github.com/getgrav/grav-plugin-login/issues/178) 2018-09-13 17:31:11 +03:00
Matias Griese
bbc4fd6c79 Allow twig tags {% script %}, {% style %} and {% switch %} to be placed outside of blocks 2018-09-07 13:13:33 +03:00
Matias Griese
732ff8ecab Fixed nicetime() twig function 2018-09-07 10:36:56 +03:00
Matias Griese
41b7aadbda Fixed duplicate language strings (Yaml 4.1) 2018-09-06 12:34:50 +03:00
Matias Griese
834d6938db Fixed is_safe twig filter option 2018-09-06 10:28:40 +03:00
Andy Miller
dfabceb3d2 Fix for Page::translatedLanguages() #2163 2018-09-05 19:08:42 -06:00
Matias Griese
1808fd3d6e Allow $page->slug() to be called before $page->init() without breaking the page 2018-08-29 15:46:46 +03:00
Matias Griese
0b5c1dcfa7 Deprecation handling fixes 2018-08-26 11:01:17 +03:00
Matias Griese
1369f941f2 Commented out deprecation error on twig for now 2018-08-25 22:08:59 +03:00
Matias Griese
2101c6d0dc Further improve deprecated notices handling 2018-08-24 19:12:59 +03:00
Matias Griese
1993fc6a2c Better detect deprecation notices 2018-08-24 18:26:50 +03:00
Matias Griese
b9b43d1f05 Fixed notice on new deprecation logic 2018-08-24 18:22:07 +03:00
Matias Griese
8d53cf3c77 Add backtraces to the deprecation messages 2018-08-24 14:51:05 +03:00
Matias Griese
756ddaa97d Added Deprecated tab to DebugBar to catch future incompatibilities with later Grav versions 2018-08-24 11:31:51 +03:00
Djamil Legato
89f64e423d Fixed error message 2018-08-23 15:28:16 -07:00
Djamil Legato
ec5596b1a3 Fixed check for install command with symlinks, erroring out when no symlink available 2018-08-23 15:25:57 -07:00
Andy Miller
2de89e31c0 Merge branch 'release/1.5.1' 2018-08-23 13:02:49 -06:00
Andy Miller
9ca5598b6f Merge tag '1.5.1' into develop
Release v1.5.1
2018-08-23 13:02:49 -06:00
Andy Miller
05863276ef prepare for release 2018-08-23 13:02:37 -06:00
Andy Miller
5ac518f311 cast inline/indent to int 2018-08-22 12:54:45 -06:00
Andy Miller
41f488f8da Switch to Grav YAML wrapper that supports native and fallback YAML libs 2018-08-22 12:42:45 -06:00
Matias Griese
6cc6e51878 Added static Grav\Common\Yaml class which should be used instead of Symfony\Component\Yaml\Yaml 2018-08-22 20:59:00 +03:00
Andy Miller
78bcf84127 Merge branch 'develop' of github.com:getgrav/grav into develop
# Conflicts:
#	CHANGELOG.md
2018-08-21 14:10:02 -06:00
Andy Miller
6b224823f1 typo 2018-08-21 14:09:33 -06:00
Andy Miller
2734b2f605 Broken handling of user folder in Grav URI object #2151 2018-08-21 14:09:25 -06:00
Matias Griese
1ee88d5836 Updated deprecated Twig code so it works in both in Twig 1.34+ and Twig 2.4+ 2018-08-20 10:51:58 +03:00
Andy Miller
33fffa6a50 Merge tag '1.5.0' into develop
Release v1.5.0
2018-08-17 11:24:56 -06:00
Andy Miller
dbd825f0b6 Merge branch 'release/1.5.0' 2018-08-17 11:24:55 -06:00
Andy Miller
8ab0078d5a Prepare for release 2018-08-17 11:24:43 -06:00
Andy Miller
c381bc8304 PHP 7.2 by default now 2018-08-16 14:58:19 -06:00
Andy Miller
fb20b58369 changelog update 2018-08-15 17:12:20 -06:00
Andy Miller
906017e0c1 Added system blueprint for strict_mode settings 2018-08-15 17:12:10 -06:00
Andy Miller
266369ee04 unified 1.5.0 changelog entry for clarity 2018-08-15 16:15:37 -06:00
Andy Miller
308ac14dbe Updated changelog 2018-08-15 16:11:47 -06:00
Andy Miller
2a9da76512 Merge branch 'develop' into 1.5 2018-08-15 16:08:42 -06:00
Andy Miller
8e43550841 Updated changelog 2018-08-15 15:52:31 -06:00
Djamil Legato
75ac0201d8 Added support for multiple repos lookup (as array) in .grav/config
This will allow to keep clones of repositories on different folders and still be able to symlink them.

Example of ~/.grav/config:

```
github_repos:
    - /Users/my_user/Projects/grav/
    - /Users/my_user/Projects/personal/
    - /Users/my_user/Projects/work/
```
2018-08-15 13:38:18 -07:00
Andy Miller
8d9efe4ff7 Extra semicolon 2018-08-14 19:47:16 -06:00
Andy Miller
593400743a Fix for plugin order 2018-08-14 19:46:52 -06:00
Matias Griese
42ff8eaeb0 Make ObjectTrait::serialize() overrides easier 2018-08-13 22:28:12 +03:00
Matias Griese
5c2f9946f8 Merge branch 'develop' of https://github.com/getgrav/grav into 1.5 2018-08-13 09:36:07 +03:00
Jascha Geerds
63161e62a2 Fix broken nounce handling (#2121)
* Remove deprecated "getNonceOldStyle" function

This commit removes the following functions:

- getNonceOldStyle
- generateNonceStringOldStyle

The functions have been replaced in newer versions of
grav. It seems to me that they only existed in order to make a
upgrade to a newer version of grav painless (i.e. accept both types of
nonce tokens). Nowadays, existing old style nonces are expired long
time ago so it should be save to delete the deprecated funtions.

* Fix caching of nonces in static class variable

Currently, the behavior of `getNonce` is broken because it saves the
generated nonce in an array and only use the $action as the
key. However, the generated nonce does not only depend on the $action,
but also on $plusOneTick.

* Fix broken "plusOneTick" for nonces

It looks to me that there is a bug in the current implemention of
verifyNonce. Here is an example:

- 2018-08-01 10:00: We respond to a request and generate a nonce. The
  current tick is at 35489

- 2018-08-01 10:05: We use the previously generated nonce to make
  another request. We compare the given nounce with a new generated
  one (based on the same tick). The result is exactly the same and the
  request succeeds.

- 2018-08-01 14:00: We're now one tick ahead. Remember: A day (24
  hours) is separated into two ticks (each 12 hours). A request comes
  in, we compare the given nounce with a newly generated one based on
  the current tick (now at 35490). They don't match (which is totally
  okay).

  If the comparison fails, we then compare the given nounce with a
  another, newly generated one. This time, we pass "plusOneTick", to
  the function, which increases the current tick by one. Our tick is
  now at 35491. We generate a nonce based on that tick and of course,
  it still does not match the given nonce.

  Instead of increasing the tick, we should rather decreasing it by
  one (i.e. use the previous tick). If the first comparison fails, we
  use the current tick (35490), decrease it by one (35489) and then
  compare it again. 35489 is the same tick as in the very first
  request.

This bug leads to a maximum life time of 12 hours for a nonce and in
worst case only a few seconds (!)

I would like to prove the bug with an unit test but I'm too unexperienced
in PHP. Furthermore it seems that we need some kind of library which
is able to mock builtin functions (like "time"). Maybe
<https://github.com/Codeception/AspectMock> would be a good canditate?
2018-08-09 16:05:24 -06:00
lucaswillering
c84983ad5b Add muted and playsinline attributes (#2124)
Fixes: #2099

To be able to add videos to sites that behave as GIFs, two attributes are needed for the videos to be properly handled on iOS and in Chrome: muted and playsinline.

Muted
Chrome only allows videos to autoplay when the contain the muted attribute. Non-muted videos will not autoplay unless the user has interacted with the site. More details here:  https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#new-behaviors

Playsinline
The playsinline attribute allows developers to specify videos on iPhone should play inline and not automatically enter fullscreen mode when playback begins. More details here: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#new-behaviors
2018-08-09 15:32:53 -06:00
atyner
3cee53508e Typo 'Subscxripted' on default Typography page (#2136) 2018-08-09 15:32:34 -06:00
Matias Griese
fde75e1ed5 Composer update (toolbox) 2018-08-08 21:09:25 +03:00
Matias Griese
16d2f607c8 Fixed Uri::parseUrl($url) with no path (part 2) 2018-08-07 23:12:38 +03:00
Matias Griese
816a3ebd93 Fixed Uri::parseUrl($url) with no path 2018-08-07 22:46:23 +03:00
Matias Griese
d59fe2fa3c Display better exception message if Grav fails to initialize 2018-08-07 22:32:29 +03:00
Matias Griese
ef55e7d219 Added option to disable SimpleCache key validation 2018-08-07 22:30:45 +03:00
Andy Miller
424da520cf Fix #2134 - inheritance of theme classes that include digits in camelcase 2018-08-06 15:37:59 -06:00
Andy Miller
08cb311e5e Fix truncator tests 2018-08-06 15:36:38 -06:00
Andy Miller
e1b5875c5b Update vendor libs 2018-08-06 15:22:55 -06:00
Andy Miller
7d27206fec Fixed #2133 - uppercase fallback media urls 2018-08-06 14:56:00 -06:00
Andy Miller
18d405d798 Improved Utils::url() to support query strings 2018-08-06 13:09:15 -06:00
Matias Griese
34fa50fcf0 Added FormatterInterface::getDefaultFileExtension() 2018-08-03 13:19:26 +03:00
Matias Griese
ca3cf2ea3c Added FormatterInterface::getSupportedFileExtensions() method, deprecated getFileExtension() 2018-08-03 13:03:51 +03:00
Matias Griese
76fb11366b Added Uri::method() to get current HTTP method (GET/POST etc) 2018-08-02 22:41:54 +03:00
Andy Miller
e4f2808870 prepare for release 2018-07-31 11:27:49 -06:00
Andy Miller
f7496b5341 Merge branch 'develop' into 1.5
# Conflicts:
#	CHANGELOG.md
#	composer.lock
#	system/defines.php
2018-07-31 11:27:08 -06:00
Andy Miller
2f0d600e86 Merge tag '1.4.8' into develop
Release v1.4.8
2018-07-31 11:23:36 -06:00
Andy Miller
fa7e6be95a Merge branch 'release/1.4.8' 2018-07-31 11:23:35 -06:00
Andy Miller
cea43a2d21 Prepare for release 2018-07-31 11:23:24 -06:00
Andy Miller
b7387c8741 vendor updates 2018-07-31 11:23:15 -06:00
Andy Miller
c83852f4e1 update changelog 2018-07-31 11:20:49 -06:00
Andy Miller
ce271cf389 Merge branch 'develop' of github.com:getgrav/grav into develop 2018-07-31 11:17:09 -06:00
Matias Griese
ead125d599 Merge branch 'develop' of https://github.com/getgrav/grav into 1.5 2018-07-30 12:10:24 +03:00
Andy Miller
8ee367e52e Don't allow null to be set as Page content 2018-07-27 15:05:45 -06:00
Jeremy Gonyea
db03091cff Added nginx config for hosting in a ddev project (#2117) 2018-07-27 14:23:35 -06:00
Matias Griese
6b5849b207 Added MediaTrait::getMediaCache() to allow custom caching 2018-07-20 22:54:22 +03:00
Matias Griese
ba0a8c4092 Added MediaTrait::clearMediaCache() to allow cache to be cleared 2018-07-20 22:25:22 +03:00
Matias Griese
c8ab5d34f7 Merge branch 'develop' into 1.5
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
2018-07-19 10:05:26 +03:00
Hugh Barnes
c9367ba4f3 Add Grav version to debug bar Messages tab (#2106) 2018-07-18 19:03:25 -06:00
Matias Griese
a754f697d7 Added twig filters for casting values: |string, |int, |bool, |float, |array
Made `|markdown` filter HTML safe
2018-07-14 13:22:35 +03:00
Matias Griese
dd75ce515f Code style fix on Twig tags 2018-07-14 12:12:53 +03:00
Andy Miller
ea83b46bfb Prepare for release 2018-07-13 16:54:32 -06:00
Andy Miller
e7f628233d Update changelog 2018-07-13 16:47:49 -06:00
Andy Miller
24edf15e16 Merge branch 'release/1.4.7' 2018-07-13 16:38:56 -06:00
Andy Miller
70e65129d7 Merge tag '1.4.7' into develop
Release v1.4.7
2018-07-13 16:38:56 -06:00
Andy Miller
a5e97ef846 Prepare for release 2018-07-13 16:38:46 -06:00
Andy Miller
8a1f0d4932 update changelog 2018-07-13 16:38:04 -06:00
Andy Miller
f29997a5cf Minor vendor updates 2018-07-13 16:35:51 -06:00
Matias Griese
4daec6908c Merge branch 'develop' of https://github.com/getgrav/grav into 1.5 2018-07-06 09:17:41 +03:00
Timothy Cyrus
79bff58021 Change getBasename to getFilename where possible (#2087)
* Update Pages.php

* Update Themes.php

* Update Installer.php

* Update Plugins.php

* Update ConfigFileFinder.php
2018-07-05 14:14:57 -06:00
Matias Griese
05028d0d9b Changelog update 2018-07-05 13:14:17 +03:00
Matias Griese
b4148804e1 Criteria: Added support for LENGTH(), LOWER(), UPPER(), LTRIM(), RTRIM() and TRIM() 2018-07-05 13:12:59 +03:00
Matias Griese
07f8dfb1c5 Made ObjectCollection::matching() criteria expressions to behave more like in Twig 2018-07-05 12:27:28 +03:00
Matias Griese
f3c559f1c7 Composer update (Fixes blueprint issues) 2018-07-05 10:13:36 +03:00
Matias Griese
48a3228efd Merge remote-tracking branch 'origin/1.5' into 1.5
# Conflicts:
#	CHANGELOG.md
2018-07-05 10:07:14 +03:00
Matias Griese
be661e8685 Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	CHANGELOG.md
2018-07-05 10:06:27 +03:00
Timothy Cyrus
a0918dfc4f Update Media.php (#2083)
Fixes getgrav/grav-plugin-admin#1330
2018-07-02 23:46:44 +01:00
Andy Miller
d214080974 Merge branch 'develop' into 1.5
# Conflicts:
#	CHANGELOG.md
2018-07-02 16:05:56 -06:00
Andy Miller
a09c6b1088 Fix for Page::routeCanonical accpeting string #2069 2018-06-22 11:59:22 +01:00
Andy Miller
dfed333e1b Set html in admin not in here… 2018-06-20 23:38:41 +01:00
Andy Miller
578e12940b Fix for modular page preview #2066 2018-06-20 22:56:27 +01:00
Andy Miller
7d215f95cf Merge tag '1.4.6' into develop
Release v1.4.6
2018-06-20 19:38:40 +01:00
Matias Griese
68a9552877 Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	composer.json
#	composer.lock
2018-06-20 11:56:55 +03:00
Matias Griese
7aa688ecbb Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
2018-06-20 10:51:52 +03:00
Andy Miller
794db2e3e5 set version as beta.1 2018-06-19 19:15:22 +01:00
Andy Miller
ba457f7bf3 composer update 2018-06-19 19:11:22 +01:00
Andy Miller
64715573a1 Update changelog 2018-06-19 18:46:42 +01:00
Andy Miller
8288551531 Merge tag '1.4.6' into develop
Release v1.4.6

# Conflicts:
#	system/defines.php
2018-06-19 18:45:32 +01:00
Matias Griese
9816b538f9 Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	composer.lock
2018-06-15 09:22:46 +03:00
Matias Griese
bbfbdec483 Added setting to disable sessions from the site [#2013] 2018-06-14 19:23:35 +03:00
Matias Griese
9ca427e369 Composer update (rockettheme/toolbox => 1.4) 2018-06-14 12:46:27 +03:00
Matias Griese
228757a5ba Composer update (rockettheme/toolbox => dev-develop) 2018-06-12 13:06:58 +03:00
Matias Griese
027a760ce2 Added function Session::clear() 2018-06-12 12:06:14 +03:00
Matias Griese
0a3cadc6b2 Composer update 2018-06-12 10:55:38 +03:00
Matias Griese
04ea069280 Fixed routing issues with multisite and multilanguage (Grav 1.5 edition) (#2046)
* Fixed routing issues with multisite and multilanguage (#1501)
2018-06-12 10:48:13 +03:00
Matias Griese
280d54057c Merge branch 'develop' of https://github.com/getgrav/grav into 1.5 2018-06-12 10:40:42 +03:00
Matias Griese
9f7534153e Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	CHANGELOG.md
2018-06-01 12:37:13 +03:00
Matias Griese
62a8d8b203 Add method MediaInterface::getMediaOrder() and implement it 2018-05-31 20:04:16 +03:00
Matias Griese
d7bd0bf1df Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	CHANGELOG.md
2018-05-31 12:28:19 +03:00
Matias Griese
3ccadded97 Fixed blueprint field validation: Allow numeric inputs in text fields 2018-05-22 10:42:30 +03:00
Andy Miller
b82c17ea56 Fixed typo in trucate #1943 2018-05-20 12:01:38 -06:00
Matias Griese
a0946c67b9 Implement SessionInterface 2018-05-17 10:46:13 +03:00
Matias Griese
00376d3118 Session code cleanup, add changelog entries 2018-05-17 10:22:14 +03:00
Matias Griese
e8fd5405a7 Merge branch 'feature/session' of https://github.com/getgrav/grav into 1.5 2018-05-17 10:14:10 +03:00
Matias Griese
eae017a30a Merge branch 'develop' of https://github.com/getgrav/grav into feature/session 2018-05-17 10:10:16 +03:00
Andy Miller
718dfa9b5d Merge branch 'develop' into 1.5
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
#	system/src/Grav/Common/User/User.php
2018-05-15 17:55:50 -06:00
Andy Miller
11266ce8f8 Revert "Add special handling for User authenticated and authorized properties"
This reverts commit 8e0e3e8718.
2018-05-14 18:25:36 -06:00
Matias Griese
dab595f571 Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	system/defines.php
2018-05-14 21:25:35 +03:00
Matias Griese
5ab956a8ec Fixed Route::withQueryParam() to accept array values 2018-05-14 12:30:30 +03:00
Matias Griese
2c82e15fa1 Added authorized support (2FA) 2018-05-10 19:56:56 +03:00
Andy Miller
2c7d866724 alignment of parens 2018-05-09 22:43:26 -06:00
Andy Miller
a977023e45 minor vendor updates 2018-05-09 14:43:28 -06:00
Djamil Legato
9c7008e225 Enable markdown support for 2FA account field description 2018-05-09 10:41:23 -07:00
Matias Griese
342aa0ff04 Added MediaTrait::getMediaUri() 2018-05-09 14:01:20 +03:00
Matias Griese
d434d51d42 Merge remote-tracking branch 'origin/1.5' into 1.5 2018-05-08 21:00:24 +03:00
Matias Griese
f03eb693e6 Rename object key back to _key 2018-05-08 21:00:16 +03:00
Andy Miller
18928d6962 Revert "fire onPluginsInitialized event"
This reverts commit f7832e78dc.
2018-05-08 09:48:56 -06:00
Andy Miller
f7832e78dc fire onPluginsInitialized event 2018-05-07 21:32:00 -06:00
Matias Griese
68428a714a Change exception type in ContentBlock::fromArray() 2018-05-07 15:24:43 +03:00
Matias Griese
58db31a7d8 Added support for ContentBlock checksums 2018-05-07 14:58:50 +03:00
Matias Griese
2917345b56 Improve ObjectTrait 2018-05-04 21:43:12 +03:00
Matias Griese
1cfd3482bb Added Grav\Common\Media interfaces and trait; use those in Page and Media classes
Added `Grav\Common\Page` interface to allow custom page types in the future
2018-05-04 19:06:37 +03:00
Matias Griese
4d690938a8 Merge branch 'develop' of https://github.com/getgrav/grav into 1.5 2018-04-30 12:37:01 +03:00
Andy Miller
0b1c18d63e Added Uri::post() and Uri::getConentType() 2018-04-27 16:44:38 -06:00
Andy Miller
f681f1c60b resolve streams earlier 2018-04-27 16:18:16 -06:00
Andy Miller
27df27d1df resolve streams earlier 2018-04-27 16:17:52 -06:00
Matias Griese
91e98cd32e Added RouteFactory::createFromString() rename MarkdownFormatter configuration option 2018-04-27 20:41:05 +03:00
Matias Griese
1cef2a182a Added Grav\Common\Media interfaces and trait; use those in Page and Media classes
Added `Grav\Common\Page` interface to allow custom page types in the future
2018-04-27 20:38:57 +03:00
Matias Griese
ad8764897a New classes have wrong namespace 2018-04-24 15:47:31 +03:00
Matias Griese
d2e700eea2 Formatters: Better error handling, allow custom file extension 2018-04-24 12:22:06 +03:00
Matias Griese
895e145d82 Added new Grav\Framework\File\Formatter classes for encoding/decoding YAML, MarkDown, JSON, INI and PHP serialized formats 2018-04-24 11:41:55 +03:00
Matias Griese
78ab2aa476 Minor improvement on session check (using PHP 5.4+ way) 2018-04-23 09:40:31 +03:00
Matias Griese
b9a7341e5e Composer update to fix YAML issues 2018-04-22 19:09:05 +03:00
Matias Griese
261ea62472 Added compatibility mode to fall back to Symfony YAML 2.8 when needed 2018-04-20 14:03:49 +03:00
Matias Griese
fae2aa4582 Added compatibility mode to fall back to Symfony YAML 2.8 when needed 2018-04-20 11:18:01 +03:00
Matias Griese
fb7230ec9a Merge branch 'develop' of https://github.com/getgrav/grav into 1.5
# Conflicts:
#	CHANGELOG.md
2018-04-20 10:24:06 +03:00
Matias Griese
830c9524eb Merge branch 'develop' of https://github.com/getgrav/grav into feature/session 2018-04-16 09:33:07 +03:00
Matias Griese
ce1e635451 Minimum PHP requirement was 5.6.4, not 5.6.0 2018-04-14 17:42:13 +03:00
Matias Griese
8b0c1b7937 Updated Symfony Components to 3.4, causing some compatibility issues with YAML files 2018-04-14 13:23:54 +03:00
Matias Griese
87b0d80de0 PHP 5.6.0 minimum, updated to Doctrine Collections 1.4 2018-04-14 12:28:00 +03:00
Matias Griese
b8c61e34c9 Improve session error if it fails to start 2018-04-14 11:50:22 +03:00
Matias Griese
02555ba3f5 Merge branch 'develop' of https://github.com/getgrav/grav into feature/session 2018-04-13 21:04:40 +03:00
Matias Griese
2b17bf70de Merge branch 'develop' of https://github.com/getgrav/grav into feature/session 2018-04-10 09:47:59 +03:00
Matias Griese
f31f7f0962 Added Grav\Framework\Session class to replace RocketTheme\Toolbox\Session\Session
Improved session handling, allow all session configuration options in `system.session.options`
2018-03-21 12:45:57 +02:00
102 changed files with 3757 additions and 886 deletions

1
.gitignore vendored
View File

@@ -42,3 +42,4 @@ tests/_output/*
tests/_support/_generated/*
tests/cache/*
tests/error.log
/system/templates/testing

View File

@@ -1,6 +1,5 @@
language: php
php:
- '5.5'
- '5.6'
- '7.0.21'
- '7.1'

View File

@@ -1,3 +1,165 @@
# v1.5.10
## 03/21/2019
1. [](#new)
* Added new `deferred` Twig extension
# v1.5.9
## 03/20/2019
1. [](#new)
* Added new `onPageContent()` event for every call to `Page::content()`
1. [](#improved)
* Fixed phpdoc generation
* Updated vendor libraries
* Force Toolbox v1.4.2
1. [](#bugfix)
* EXIF fix for streams
* Fix for User avatar not working due to uppercase or spaces in email [#2403](https://github.com/getgrav/grav/pull/2403)
# v1.5.8
## 02/07/2019
1. [](#improved)
* Improved `User` unserialize to not to break the object if serialized data is not what expected
* Removed unused parameter [#2357](https://github.com/getgrav/grav/pull/2357)
# v1.5.7
## 01/25/2019
1. [](#new)
* Support for AWS Cloudfront forwarded scheme header [#2297](https://github.com/getgrav/grav/pull/2297)
1. [](#improved)
* Set homepage with `https://` protocol [#2299](https://github.com/getgrav/grav/pull/2299)
* Preserve accents in fields containing Twig expr. using unicode [#2279](https://github.com/getgrav/grav/pull/2279)
* Updated vendor libraries
1. [](#bugfix)
* Support spaces with filenames in responsive images [#2300](https://github.com/getgrav/grav/pull/2300)
# v1.5.6
## 12/14/2018
1. [](#improved)
* Updated InitializeProcessor.php to use lang-safe redirect [#2268](https://github.com/getgrav/grav/pull/2268)
* Improved user serialization to use less memory in the session
# v1.5.5
## 11/12/2018
1. [](#new)
* Register theme prefixes as namespaces in Twig [#2210](https://github.com/getgrav/grav/pull/2210)
1. [](#improved)
* Propogate error code between 400 and 600 for production sites [#2181](https://github.com/getgrav/grav/pull/2181)
1. [](#bugfix)
* Remove hardcoded `302` when redirecting trailing slash [#2155](https://github.com/getgrav/grav/pull/2155)
# v1.5.4
## 11/05/2018
1. [](#improved)
* Updated default page `index.md` with some consistency fixes [#2245](https://github.com/getgrav/grav/pull/2245)
1. [](#bugfix)
* Fixed fatal error if calling `$session->invalidate()` when there's no active session
* Fixed typo in media.yaml for `webm` extension [#2220](https://github.com/getgrav/grav/pull/2220)
* Fixed markdown processing for telephone links [#2235](https://github.com/getgrav/grav/pull/2235)
# v1.5.3
## 10/08/2018
1. [](#new)
* Added `Utils::getMimeByFilename()`, `Utils::getMimeByLocalFile()` and `Utils::checkFilename()` methods
* Added configurable dangerous upload extensions in `security.yaml`
1. [](#improved)
* Updated vendor libraries to latest
# v1.5.2
## 10/01/2018
1. [](#new)
* Added new `Security` class for Grav security functionality including XSS checks
* Added new `bin/grav security` command to scan for security issues
* Added new `xss()` Twig function to allow for XSS checks on strings and arrays
* Added `onHttpPostFilter` event to allow plugins to globally clean up XSS in the forms and tasks
* Added `Deprecated` tab to DebugBar to catch future incompatibilities with later Grav versions
* Added deprecation notices for features which will be removed in Grav 2.0
1. [](#improved)
* Updated vendor libraries to latest
1. [](#bugfix)
* Allow `$page->slug()` to be called before `$page->init()` without breaking the page
* Fix for `Page::translatedLanguages()` to use routes always [#2163](https://github.com/getgrav/grav/issues/2163)
* Fixed `nicetime()` twig function
* Allow twig tags `{% script %}`, `{% style %}` and `{% switch %}` to be placed outside of blocks
* Session expires in 30 mins independent from config settings [login#178](https://github.com/getgrav/grav-plugin-login/issues/178)
# v1.5.1
## 08/23/2018
1. [](#new)
* Added static `Grav\Common\Yaml` class which should be used instead of `Symfony\Component\Yaml\Yaml`
1. [](#improved)
* Updated deprecated Twig code so it works in both in Twig 1.34+ and Twig 2.4+
* Switched to new Grav Yaml class to support Native + Fallback YAML libraries
1. [](#bugfix)
* Broken handling of user folder in Grav URI object [#2151](https://github.com/getgrav/grav/issues/2151)
# v1.5.0
## 08/17/2018
1. [](#new)
* Set minimum requirements to [PHP 5.6.4](https://getgrav.org/blog/raising-php-requirements-2018)
* Updated Doctrine Collections to 1.4
* Updated Symfony Components to 3.4 (with compatibility mode to fall back to Symfony YAML 2.8)
* Added `Uri::method()` to get current HTTP method (GET/POST etc)
* `FormatterInterface`: Added `getSupportedFileExtensions()` and `getDefaultFileExtension()` methods
* Added option to disable `SimpleCache` key validation
* Added support for multiple repo locations for `bin/grav install` command
* Added twig filters for casting values: `|string`, `|int`, `|bool`, `|float`, `|array`
* Made `ObjectCollection::matching()` criteria expressions to behave more like in Twig
* Criteria: Added support for `LENGTH()`, `LOWER()`, `UPPER()`, `LTRIM()`, `RTRIM()` and `TRIM()`
* Added `Grav\Framework\File\Formatter` classes for encoding/decoding YAML, Markdown, JSON, INI and PHP serialized strings
* Added `Grav\Framework\Session` class to replace `RocketTheme\Toolbox\Session\Session`
* Added `Grav\Common\Media` interfaces and trait; use those in `Page` and `Media` classes
* Added `Grav\Common\Page` interface to allow custom page types in the future
* Added setting to disable sessions from the site [#2013](https://github.com/getgrav/grav/issues/2013)
* Added new `strict_mode` settings in `system.yaml` for compatibility
1. [](#improved)
* Improved `Utils::url()` to support query strings
* Display better exception message if Grav fails to initialize
* Added `muted` and `playsinline` support to videos [#2124](https://github.com/getgrav/grav/pull/2124)
* Added `MediaTrait::clearMediaCache()` to allow cache to be cleared
* Added `MediaTrait::getMediaCache()` to allow custom caching
* Improved session handling, allow all session configuration options in `system.session.options`
1. [](#bugfix)
* Fix broken form nonce logic [#2121](https://github.com/getgrav/grav/pull/2121)
* Fixed issue with uppercase extensions and fallback media URLs [#2133](https://github.com/getgrav/grav/issues/2133)
* Fixed theme inheritance issue with `camel-case` that includes numbers [#2134](https://github.com/getgrav/grav/issues/2134)
* Typo in demo typography page [#2136](https://github.com/getgrav/grav/pull/2136)
* Fix for incorrect plugin order in debugger panel
* Made `|markdown` filter HTML safe
* Fixed bug in `ContentBlock` serialization
* Fixed `Route::withQueryParam()` to accept array values
* Fixed typo in truncate function [#1943](https://github.com/getgrav/grav/issues/1943)
* Fixed blueprint field validation: Allow numeric inputs in text fields
# v1.4.8
## 07/31/2018
1. [](#improved)
* Add Grav version to debug bar messages tab [#2106](https://github.com/getgrav/grav/pull/2106)
* Add Nginx config for ddev project to `webserver-configs` [#2117](https://github.com/getgrav/grav/pull/2117)
* Vendor library updates
1. [](#bugfix)
* Don't allow `null` to be set as Page content
# v1.4.7
## 07/13/2018
1. [](#improved)
* Use `getFilename` instead of `getBasename` [#2087](https://github.com/getgrav/grav/issues/2087)
1. [](#bugfix)
* Fix for modular page preview [#2066](https://github.com/getgrav/grav/issues/2066)
* `Page::routeCanonical()` should be string not array [#2069](https://github.com/getgrav/grav/issues/2069)
# v1.4.6
## 06/20/2018
@@ -23,7 +185,7 @@
* Fixed an issue with some users getting **2FA** prompt after upgrade [admin#1442](https://github.com/getgrav/grav-plugin-admin/issues/1442)
* Do not crash when generating URLs with arrays as parameters [#2018](https://github.com/getgrav/grav/pull/2018)
* Utils::truncateHTML removes whitespace when generating summaries [#2004](https://github.com/getgrav/grav/pull/2004)
# v1.4.4
## 05/11/2018
@@ -32,8 +194,8 @@
* Added a new `Medium:thumbnailExists()` function [#1966](https://github.com/getgrav/grav/issues/1966)
* Added `authorized` support for 2FA
1. [](#improved)
* Added default configuration for images [#1979](https://github.com/getgrav/grav/pull/1979)
* Added dedicated PHPUnit assertions [#1990](https://github.com/getgrav/grav/pull/1990)
* Added default configuration for images [#1979](https://github.com/getgrav/grav/pull/1979)
* Added dedicated PHPUnit assertions [#1990](https://github.com/getgrav/grav/pull/1990)
1. [](#bugfix)
* Use `array_key_exists` instead of `in_array + array_keys` [#1991](https://github.com/getgrav/grav/pull/1991)
* Fixed an issue with `custom_base_url` always causing 404 errors
@@ -59,13 +221,13 @@
* Added new `|nicefilesize` Twig filter for pretty file (auto converts to bytes, kB, MB, GB, etc)
* Added new `regex_filter()` Twig function to values in arrays
1. [](#improved)
* Added bosnian to lang codes [#1917](https://github.com/getgrav/grav/issues/1917)
* Improved Zip extraction error codes [#1922](https://github.com/getgrav/grav/issues/1922)
* Added bosnian to lang codes [#1917](https://github.com/getgrav/grav/issues/1917)
* Improved Zip extraction error codes [#1922](https://github.com/getgrav/grav/issues/1922)
1. [](#bugfix)
* Fixed an issue with Markdown Video and Audio that broke after Parsedown 1.7.0 Security updates [#1924](https://github.com/getgrav/grav/issues/1924)
* Fix for case-sensitive page metadata [admin#1370](https://github.com/getgrav/grav-plugin-admin/issues/1370)
* Fixed missing composer requirements for the new `Grav\Framework\Uri` classes
* Added missing PSR-7 vendor library required for URI additions in Grav 1.4.0
* Added missing PSR-7 vendor library required for URI additions in Grav 1.4.0
# v1.4.1
## 03/11/2018

View File

@@ -1,24 +1,24 @@
# ![](https://avatars1.githubusercontent.com/u/8237355?v=2&s=50) Grav
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Slack](https://grav-chat.now.sh/badge.svg)](https://chat.getgrav.org) [![Build Status](https://travis-ci.org/getgrav/grav.svg?branch=develop)](https://travis-ci.org/getgrav/grav) [![OpenCollective](https://opencollective.com/grav/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/grav/sponsors/badge.svg)](#sponsors)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Discord](https://img.shields.io/discord/501836936584101899.svg?logo=discord&colorB=728ADA&label=Discord%20Chat)](https://chat.getgrav.org) [![Build Status](https://travis-ci.org/getgrav/grav.svg?branch=develop)](https://travis-ci.org/getgrav/grav) [![OpenCollective](https://opencollective.com/grav/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/grav/sponsors/badge.svg)](#sponsors)
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principles to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
The underlying architecture of Grav is designed to use well-established and _best-in-class_ technologies to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
* [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface
* [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation
* [YAML](http://yaml.org): for simple configuration
* [Parsedown](http://parsedown.org/): for fast Markdown and Markdown Extra support
* [Doctrine Cache](http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/caching.html): layer for performance
* [Pimple Dependency Injection Container](http://pimple.sensiolabs.org/): for extensibility and maintainability
* [Symfony Event Dispatcher](http://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
* [Symfony Console](http://symfony.com/doc/current/components/console/introduction.html): for CLI interface
* [Twig Templating](https://twig.sensiolabs.org/): for powerful control of the user interface
* [Markdown](https://en.wikipedia.org/wiki/Markdown): for easy content creation
* [YAML](https://yaml.org): for simple configuration
* [Parsedown](https://parsedown.org/): for fast Markdown and Markdown Extra support
* [Doctrine Cache](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/caching.html): layer for performance
* [Pimple Dependency Injection Container](https://pimple.sensiolabs.org/): for extensibility and maintainability
* [Symfony Event Dispatcher](https://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
* [Symfony Console](https://symfony.com/doc/current/components/console/introduction.html): for CLI interface
* [Gregwar Image Library](https://github.com/Gregwar/Image): for dynamic image manipulation
# Requirements
- PHP 5.5.9 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
- PHP 5.6.4 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
- Check the [Apache](https://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](https://learn.getgrav.org/basics/requirements#iis-requirements) requirements
# QuickStart
@@ -94,7 +94,7 @@ If you discover a possible security issue related to Grav or one of its plugins,
* [Install](https://learn.getgrav.org/basics/installation) Grav in few seconds
* Understand the [Configuration](https://learn.getgrav.org/basics/grav-configuration)
* Take a peek at our available free [Skeletons](https://getgrav.org/downloads/skeletons)
* If you have questions, jump on our [Slack Room](https://getgrav.org/slack)!
* If you have questions, jump on our [Discord Chat Server](https://chat.getgrav.org)!
* Have fun!
# Exploring More
@@ -107,71 +107,12 @@ If you discover a possible security issue related to Grav or one of its plugins,
# Backers
Support Grav with a monthly donation to help us continue development. [[Become a backer](https://opencollective.com/grav#backer)]
<a href="https://opencollective.com/grav/backer/0/website" target="_blank"><img src="https://opencollective.com/grav/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/1/website" target="_blank"><img src="https://opencollective.com/grav/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/2/website" target="_blank"><img src="https://opencollective.com/grav/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/3/website" target="_blank"><img src="https://opencollective.com/grav/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/4/website" target="_blank"><img src="https://opencollective.com/grav/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/5/website" target="_blank"><img src="https://opencollective.com/grav/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/6/website" target="_blank"><img src="https://opencollective.com/grav/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/7/website" target="_blank"><img src="https://opencollective.com/grav/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/8/website" target="_blank"><img src="https://opencollective.com/grav/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/9/website" target="_blank"><img src="https://opencollective.com/grav/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/10/website" target="_blank"><img src="https://opencollective.com/grav/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/11/website" target="_blank"><img src="https://opencollective.com/grav/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/12/website" target="_blank"><img src="https://opencollective.com/grav/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/13/website" target="_blank"><img src="https://opencollective.com/grav/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/14/website" target="_blank"><img src="https://opencollective.com/grav/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/15/website" target="_blank"><img src="https://opencollective.com/grav/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/16/website" target="_blank"><img src="https://opencollective.com/grav/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/17/website" target="_blank"><img src="https://opencollective.com/grav/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/18/website" target="_blank"><img src="https://opencollective.com/grav/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/19/website" target="_blank"><img src="https://opencollective.com/grav/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/20/website" target="_blank"><img src="https://opencollective.com/grav/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/21/website" target="_blank"><img src="https://opencollective.com/grav/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/22/website" target="_blank"><img src="https://opencollective.com/grav/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/23/website" target="_blank"><img src="https://opencollective.com/grav/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/24/website" target="_blank"><img src="https://opencollective.com/grav/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/25/website" target="_blank"><img src="https://opencollective.com/grav/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/26/website" target="_blank"><img src="https://opencollective.com/grav/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/27/website" target="_blank"><img src="https://opencollective.com/grav/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/28/website" target="_blank"><img src="https://opencollective.com/grav/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/29/website" target="_blank"><img src="https://opencollective.com/grav/backer/29/avatar.svg"></a>
<img src="https://opencollective.com/grav/tiers/backers.svg?avatarHeight=36&width=600" />
# Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/grav#sponsor)]
<a href="https://opencollective.com/grav/sponsor/0/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/1/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/2/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/3/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/4/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/5/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/6/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/7/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/8/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/9/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/10/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/11/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/12/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/13/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/14/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/15/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/16/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/17/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/18/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/19/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/20/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/21/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/22/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/23/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/24/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/25/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/26/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/27/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/28/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/29/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/29/avatar.svg"></a>
<img src="https://opencollective.com/grav/tiers/sponsors.svg?avatarHeight=36&width=600" />
# License
@@ -183,7 +124,7 @@ See [LICENSE](LICENSE.txt)
# Running Tests
First install the dev dependencies by running `composer update` from the Grav root.
Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.
Windows users should use the `composer test-windows` command.
First install the dev dependencies by running `composer update` from the Grav root.
Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.
Windows users should use the `composer test-windows` command.
You can also run a single unit test file, e.g. `composer test tests/unit/Grav/Common/AssetsTest.php`

View File

@@ -41,5 +41,6 @@ $app->addCommands(array(
new \Grav\Console\Cli\ClearCacheCommand(),
new \Grav\Console\Cli\BackupCommand(),
new \Grav\Console\Cli\NewProjectCommand(),
new \Grav\Console\Cli\SecurityCommand(),
));
$app->run();

View File

@@ -3,20 +3,20 @@
"type": "project",
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"],
"homepage": "http://getgrav.org",
"homepage": "https://getgrav.org",
"license": "MIT",
"require": {
"php": ">=5.5.9",
"php": ">=5.6.4",
"twig/twig": "~1.24",
"erusev/parsedown": "1.6.4",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~2.8",
"symfony/console": "~2.8",
"symfony/event-dispatcher": "~2.8",
"symfony/var-dumper": "~2.8",
"symfony/yaml": "~3.4",
"symfony/console": "~3.4",
"symfony/event-dispatcher": "~3.4",
"symfony/var-dumper": "~3.4",
"symfony/polyfill-iconv": "~1.0",
"doctrine/cache": "^1.6",
"doctrine/collections": "1.3",
"doctrine/collections": "^1.4",
"psr/simple-cache": "^1.0",
"psr/http-message": "^1.0",
"guzzlehttp/psr7": "^1.4",
@@ -26,16 +26,18 @@
"gregwar/image": "2.*",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.2",
"rockettheme/toolbox": "~1.3.9",
"rockettheme/toolbox": "1.4.2",
"maximebf/debugbar": "~1.10",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-zip": "*",
"ext-json": "*",
"league/climate": "^3.2",
"antoligy/dom-string-iterators": "^1.0",
"miljar/php-exif": "^0.6.3",
"composer/ca-bundle": "^1.0"
"composer/ca-bundle": "^1.0",
"phive/twig-extensions-deferred": "^1.0"
},
"require-dev": {
"codeception/codeception": "^2.1",
@@ -45,7 +47,7 @@
},
"config": {
"platform": {
"php": "5.5.9"
"php": "5.6.4"
}
},
"repositories": [

633
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
<?php
/**
* @package Grav.Core
*
@@ -7,7 +8,8 @@
*/
namespace Grav;
define('GRAV_PHP_MIN', '5.5.9');
define('GRAV_PHP_MIN', '5.6.4');
// Ensure vendor libraries exist
$autoload = __DIR__ . '/vendor/autoload.php';
@@ -15,7 +17,7 @@ if (!is_file($autoload)) {
die("Please run: <i>bin/grav install</i>");
}
if (PHP_SAPI == 'cli-server') {
if (PHP_SAPI === 'cli-server') {
if (!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>");
}
@@ -29,7 +31,7 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
}
// Register the auto-loader.
$loader = require_once $autoload;
$loader = require $autoload;
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());

View File

@@ -0,0 +1,99 @@
title: PLUGIN_ADMIN.SECURITY
form:
validation: loose
fields:
xss_section:
type: section
title: PLUGIN_ADMIN.XSS_SECURITY
underline: true
xss_whitelist:
type: selectize
size: large
label: PLUGIN_ADMIN.XSS_WHITELIST_PERMISSIONS
help: PLUGIN_ADMIN.XSS_WHITELIST_PERMISSIONS_HELP
placeholder: 'admin.super'
classes: fancy
validate:
type: commalist
xss_enabled.on_events:
type: toggle
label: PLUGIN_ADMIN.XSS_ON_EVENTS
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
xss_enabled.invalid_protocols:
type: toggle
label: PLUGIN_ADMIN.XSS_INVALID_PROTOCOLS
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
xss_enabled.moz_binding:
type: toggle
label: PLUGIN_ADMIN.XSS_MOZ_BINDINGS
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
xss_enabled.html_inline_styles:
type: toggle
label: PLUGIN_ADMIN.XSS_HTML_INLINE_STYLES
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
xss_enabled.dangerous_tags:
type: toggle
label: PLUGIN_ADMIN.XSS_DANGEROUS_TAGS
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
xss_dangerous_tags:
type: selectize
size: large
label: PLUGIN_ADMIN.XSS_DANGEROUS_TAGS_LIST
classes: fancy
validate:
type: commalist
uploads_section:
type: section
title: PLUGIN_ADMIN.UPLOADS_SECURITY
underline: true
uploads_dangerous_extensions:
type: selectize
size: large
label: PLUGIN_ADMIN.UPLOADS_DANGEROUS_EXTENSIONS
help: PLUGIN_ADMIN.UPLOADS_DANGEROUS_EXTENSIONS_HELP
classes: fancy
validate:
type: commalist

View File

@@ -996,6 +996,18 @@ form:
validate:
type: bool
session.initialize:
type: toggle
label: PLUGIN_ADMIN.SESSION_INITIALIZE
help: PLUGIN_ADMIN.SESSION_INITIALIZE_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
session.timeout:
type: text
size: small
@@ -1206,3 +1218,27 @@ form:
placeholder: "e.g. http://yoursite.com/yourpath"
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
strict_mode.yaml_compat:
type: toggle
label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
highlight: 1
default: 1
help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
strict_mode.twig_compat:
type: toggle
label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
highlight: 1
default: 1
help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool

View File

@@ -21,6 +21,9 @@ form:
title: PLUGIN_ADMIN.CONTENT
fields:
xss_check:
type: xss
header.title:
type: text
autofocus: true

View File

@@ -94,6 +94,7 @@ form:
twofa_secret:
type: 2fa_secret
outerclasses: 'twofa-secret'
markdown: true
label: PLUGIN_ADMIN.2FA_SECRET
sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP

View File

@@ -53,7 +53,7 @@ types:
thumb: media/thumb-flv.png
mime: video/x-flv
webm:
type: file
type: video
thumb: media/thumb-webm.png
mime: video/webm
ogv:

View File

@@ -0,0 +1,31 @@
xss_whitelist: [admin.super] # Whitelist of user access that should 'skip' XSS checking
xss_enabled:
on_events: true
invalid_protocols: true
moz_binding: true
html_inline_styles: true
dangerous_tags: true
xss_dangerous_tags:
- applet
- meta
- xml
- blink
- link
- style
- script
- embed
- object
- iframe
- frame
- frameset
- ilayer
- layer
- bgsound
- title
- base
uploads_dangerous_extensions:
- php
- html
- htm
- js
- exe

View File

@@ -88,7 +88,7 @@ twig:
cache: true # Set to true to enable Twig caching
debug: true # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
autoescape: false # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
@@ -146,3 +146,7 @@ gpm:
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
strict_mode:
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
twig_compat: true # Grav 1.5+: Enables deprecated Twig autoescape setting (autoescape: false)

View File

@@ -8,12 +8,12 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.4.6');
define('GRAV_VERSION', '1.5.10');
define('GRAV_TESTING', false);
define('DS', '/');
if (!defined('GRAV_PHP_MIN')) {
define('GRAV_PHP_MIN', '5.5.9');
define('GRAV_PHP_MIN', '5.6.4');
}
// Directories and Paths

View File

@@ -72,7 +72,6 @@ NICETIME:
SEC: sec
MIN: min
HR: hr
DAY: day
WK: wk
MO: mo
YR: yr
@@ -88,7 +87,6 @@ NICETIME:
SEC_PLURAL: secs
MIN_PLURAL: mins
HR_PLURAL: hrs
DAY_PLURAL: days
WK_PLURAL: wks
MO_PLURAL: mos
YR_PLURAL: yrs

View File

@@ -30,7 +30,6 @@ NICETIME:
SEC:
MIN:
HR:
DAY:
WK:
MO:
YR:
@@ -46,7 +45,6 @@ NICETIME:
SEC_PLURAL:
MIN_PLURAL:
HR_PLURAL:
DAY_PLURAL:
WK_PLURAL:
MO_PLURAL:
YR_PLURAL:

View File

@@ -109,6 +109,8 @@ class Config extends Data
*/
public function getLanguages()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use Grav::instance()[\'languages\'] instead', E_USER_DEPRECATED);
return Grav::instance()['languages'];
}
}

View File

@@ -207,7 +207,7 @@ class ConfigFileFinder
continue;
}
$name = $directory->getBasename();
$name = $directory->getFilename();
$find = ($lookup ?: $name) . '.yaml';
$filename = "{$path}/{$name}/{$find}";

View File

@@ -262,18 +262,22 @@ class Setup extends Data
);
}
if (!$locator->findResource('environment://config', true)) {
// If environment does not have its own directory, remove it from the lookup.
$this->set('streams.schemes.environment.prefixes', ['config' => []]);
$this->initializeLocator($locator);
}
try {
if (!$locator->findResource('environment://config', true)) {
// If environment does not have its own directory, remove it from the lookup.
$this->set('streams.schemes.environment.prefixes', ['config' => []]);
$this->initializeLocator($locator);
}
// Create security.yaml if it doesn't exist.
$filename = $locator->findResource('config://security.yaml', true, true);
$file = YamlFile::instance($filename);
if (!$file->exists()) {
$file->save(['salt' => Utils::generateRandomString(14)]);
$file->free();
// Create security.yaml if it doesn't exist.
$filename = $locator->findResource('config://security.yaml', true, true);
$file = YamlFile::instance($filename);
if (!$file->exists()) {
$file->save(['salt' => Utils::generateRandomString(14)]);
$file->free();
}
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
}
}
}

View File

@@ -10,9 +10,8 @@ namespace Grav\Common\Data;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
use Grav\Common\Yaml;
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYaml;
class Validation
{
@@ -107,7 +106,7 @@ class Validation
$method = 'filter' . ucfirst(strtr($type, '-', '_'));
// If this is a YAML field validate/filter as such
if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
$method = 'filterYaml';
}
@@ -128,10 +127,12 @@ class Validation
*/
public static function typeText($value, array $params, array $field)
{
if (!is_string($value)) {
if (!is_string($value) && !is_numeric($value)) {
return false;
}
$value = (string)$value;
if (isset($params['min']) && strlen($value) < $params['min']) {
return false;
}
@@ -643,15 +644,12 @@ class Validation
public static function filterYaml($value, $params)
{
try {
if (is_string($value)) {
return (array) Yaml::parse($value);
} else {
return $value;
}
} catch (ParseException $e) {
if (!is_string($value)) {
return $value;
}
return (array) Yaml::parse($value);
}
/**

View File

@@ -9,6 +9,7 @@
namespace Grav\Common;
use DebugBar\DataCollector\ConfigCollector;
use DebugBar\DataCollector\MessagesCollector;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
use Grav\Common\Config\Config;
@@ -31,6 +32,11 @@ class Debugger
protected $timers = [];
/** @var string[] $deprecations */
protected $deprecations = [];
protected $errorHandler;
/**
* Debugger constructor.
*/
@@ -41,6 +47,9 @@ class Debugger
$this->debugbar = new StandardDebugBar();
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
// Set deprecation collector.
$this->setErrorHandler();
}
/**
@@ -58,8 +67,15 @@ class Debugger
$this->enabled = $this->config->get('system.debugger.enabled');
if ($this->enabled()) {
$plugins_config = (array)$this->config->get('plugins');
ksort($plugins_config);
$this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
$this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('plugins'), 'Plugins'));
$this->debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
$this->addMessage('Grav v' . GRAV_VERSION);
}
return $this;
@@ -121,9 +137,9 @@ class Debugger
return $this;
}
public function getCaller($ignore = 2)
public function getCaller($limit = 2)
{
$trace = debug_backtrace(false, $ignore);
$trace = debug_backtrace(false, $limit);
return array_pop($trace);
}
@@ -170,6 +186,8 @@ class Debugger
return $this;
}
$this->addDeprecations();
echo $this->renderer->render();
}
@@ -184,6 +202,7 @@ class Debugger
public function sendDataInHeaders()
{
if ($this->enabled()) {
$this->addDeprecations();
$this->debugbar->sendDataInHeaders();
}
@@ -201,6 +220,7 @@ class Debugger
return null;
}
$this->addDeprecations();
$this->timers = [];
return $this->debugbar->getData();
@@ -272,4 +292,152 @@ class Debugger
return $this;
}
public function setErrorHandler()
{
$this->errorHandler = set_error_handler(
[$this, 'deprecatedErrorHandler']
);
}
/**
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return bool
*/
public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline)
{
if ($errno !== E_USER_DEPRECATED) {
if ($this->errorHandler) {
return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
}
return true;
}
if (!$this->enabled()) {
return true;
}
$backtrace = debug_backtrace(false);
// Skip current call.
array_shift($backtrace);
// Skip vendor libraries and the method where error was triggered.
while ($current = array_shift($backtrace)) {
if (isset($current['file']) && strpos($current['file'], 'vendor') !== false) {
continue;
}
if (isset($current['function']) && ($current['function'] === 'user_error' || $current['function'] === 'trigger_error')) {
$current = array_shift($backtrace);
}
break;
}
// Add back last call.
array_unshift($backtrace, $current);
// Filter arguments.
foreach ($backtrace as &$current) {
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
$args[] = "'" . $arg . "'";
} elseif (\is_bool($arg)) {
$args[] = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
$args[] = $arg;
} elseif (\is_object($arg)) {
$args[] = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
$args[] = '$array';
} else {
$args[] = '$object';
}
}
$current['args'] = $args;
}
}
unset($current);
$this->deprecations[] = [
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'trace' => $backtrace,
];
// Do not pass forward.
return true;
}
protected function addDeprecations()
{
if (!$this->deprecations) {
return;
}
$collector = new MessagesCollector('deprecated');
$this->addCollector($collector);
$collector->addMessage('Your site is using following deprecated features:');
/** @var array $deprecated */
foreach ($this->deprecations as $deprecated) {
list($message, $scope) = $this->getDepracatedMessage($deprecated);
$collector->addMessage($message, $scope);
}
}
protected function getDepracatedMessage($deprecated)
{
$scope = 'unknown';
if (stripos($deprecated['message'], 'grav') !== false) {
$scope = 'grav';
} elseif (!isset($deprecated['file'])) {
$scope = 'unknown';
} elseif (stripos($deprecated['file'], 'twig') !== false) {
$scope = 'twig';
} elseif (stripos($deprecated['file'], 'yaml') !== false) {
$scope = 'yaml';
} elseif (stripos($deprecated['file'], 'vendor') !== false) {
$scope = 'vendor';
}
$trace = [];
foreach ($deprecated['trace'] as $current) {
$class = isset($current['class']) ? $current['class'] : '';
$type = isset($current['type']) ? $current['type'] : '';
$function = $this->getFunction($current);
if (isset($current['file'])) {
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
}
unset($current['class'], $current['type'], $current['function'], $current['args']);
$trace[] = ['call' => $class . $type . $function] + $current;
}
return [
[
'message' => $deprecated['message'],
'trace' => $trace
],
$scope
];
}
protected function getFunction($trace)
{
if (!isset($trace['function'])) {
return '';
}
return $trace['function'] . '(' . implode(', ', $trace['args']) . ')';
}
}

View File

@@ -18,6 +18,13 @@ class BareHandler extends Handler
*/
public function handle()
{
$inspector = $this->getInspector();
$code = $inspector->getException()->getCode();
if ( ($code >= 400) && ($code < 600) )
{
$this->getRun()->sendHttpCode($code);
}
return Handler::QUIT;
}

View File

@@ -74,5 +74,8 @@ class Errors
}
$whoops->register();
// Re-register deprecation handler.
$grav['debugger']->setErrorHandler();
}
}

View File

@@ -35,6 +35,10 @@ class SimplePageHandler extends Handler
$cssFile = $this->getResource("error.css");
$code = $inspector->getException()->getCode();
if ( ($code >= 400) && ($code < 600) )
{
$this->getRun()->sendHttpCode($code);
}
$message = $inspector->getException()->getMessage();
if ($inspector->getException() instanceof \ErrorException) {

View File

@@ -20,9 +20,6 @@ trait CompiledFile
*/
public function content($var = null)
{
// Set some options
$this->settings(['native' => true, 'compat' => true]);
try {
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
if ($var === null && $this->raw === null && $this->content === null) {
@@ -85,4 +82,28 @@ trait CompiledFile
return parent::content($var);
}
/**
* Serialize file.
*/
public function __sleep()
{
return [
'filename',
'extension',
'raw',
'content',
'settings'
];
}
/**
* Unserialize file.
*/
public function __wakeup()
{
if (!isset(static::$instances[$this->filename])) {
static::$instances[$this->filename] = $this;
}
}
}

View File

@@ -13,7 +13,7 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
use Grav\Common\Iterator;
use Grav\Common\Utils;
use Symfony\Component\Yaml\Yaml;
use RocketTheme\Toolbox\File\YamlFile;
class GPM extends Iterator
{
@@ -624,7 +624,10 @@ class GPM extends Iterator
return false;
}
$blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
$file = YamlFile::instance($blueprint_file);
$blueprint = (array)$file->content();
$file->free();
return $blueprint;
}
@@ -873,7 +876,9 @@ class GPM extends Iterator
// get currently installed version
$locator = Grav::instance()['locator'];
$blueprints_path = $locator->findResource('plugins://' . $dependency_slug . DS . 'blueprints.yaml');
$package_yaml = Yaml::parse(file_get_contents($blueprints_path));
$file = YamlFile::instance($blueprints_path);
$package_yaml = $file->content();
$file->free();
$currentlyInstalledVersion = $package_yaml['version'];
// if requirement is next significant release, check is compatible with currently installed version, might not be

View File

@@ -296,17 +296,17 @@ class Installer
{
foreach (new \DirectoryIterator($source_path) as $file) {
if ($file->isLink() || $file->isDot() || in_array($file->getBasename(),$ignores)) {
if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores)) {
continue;
}
$path = $install_path . DS . $file->getBasename();
$path = $install_path . DS . $file->getFilename();
if ($file->isDir()) {
Folder::delete($path);
Folder::move($file->getPathname(), $path);
if ($file->getBasename() == 'bin') {
if ($file->getFilename() === 'bin') {
foreach (glob($path . DS . '*') as $bin_file) {
@chmod($bin_file, 0755);
}

View File

@@ -114,7 +114,7 @@ class Licenses
{
if (!isset(self::$file)) {
$path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';;
$path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';
if (!file_exists($path)) {
touch($path);
}

View File

@@ -9,7 +9,6 @@
namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Language\Language;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Page;
@@ -205,11 +204,8 @@ class Grav extends Container
*/
public function redirectLangSafe($route, $code = null)
{
/** @var Language $language */
$language = $this['language'];
if (!$this['uri']->isExternal($route) && $language->enabled() && $language->isIncludeDefaultLanguage()) {
$this->redirect($language->getLanguage() . $route, $code);
if (!$this['uri']->isExternal($route)) {
$this->redirect($this['pages']->route($route), $code);
} else {
$this->redirect($route, $code);
}
@@ -443,7 +439,7 @@ class Grav extends Container
/** @var Config $config */
$config = $this['config'];
$uri_extension = $uri->extension();
$uri_extension = strtolower($uri->extension());
$fallback_types = $config->get('system.media.allowed_fallback_types', null);
$supported_types = $config->get('media.types');

View File

@@ -9,7 +9,7 @@
namespace Grav\Common;
/**
* @deprecated 2.0
* @deprecated 1.4 Use Grav::instance() instead
*/
trait GravTrait
{
@@ -24,8 +24,7 @@ trait GravTrait
self::$grav = Grav::instance();
}
$caller = self::$grav['debugger']->getCaller();
self::$grav['debugger']->addMessage("Deprecated GravTrait used in {$caller['file']}", 'deprecated');
user_error(__TRAIT__ . ' is deprecated since Grav 1.4, use Grav::instance() instead', E_USER_DEPRECATED);
return self::$grav;
}

View File

@@ -117,7 +117,7 @@ class Excerpts
*/
public static function processLinkExcerpt($excerpt, Page $page, $type = 'link')
{
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['href']));
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
$url_parts = static::parseUrl($url);

View File

@@ -190,10 +190,11 @@ class Inflector
public function hyphenize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1);
$regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex2);
$regex2 = preg_replace('/([a-z])([A-Z])/', '\1-\2', $regex1);
$regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
$regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
return strtolower($regex3);
return strtolower($regex4);
}
/**

View File

@@ -181,7 +181,7 @@ class Language
$uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
// Store in session if language is different.
if (isset($this->grav['session']) && $this->grav['session']->started()
if (isset($this->grav['session']) && $this->grav['session']->isStarted()
&& $this->config->get('system.languages.session_store_active', true)
&& $this->grav['session']->active_language != $this->active
) {
@@ -189,7 +189,7 @@ class Language
}
} else {
// Try getting language from the session, else no active.
if (isset($this->grav['session']) && $this->grav['session']->started()
if (isset($this->grav['session']) && $this->grav['session']->isStarted()
&& $this->config->get('system.languages.session_store_active', true)) {
$this->active = $this->grav['session']->active_language ?: null;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media collection interface.
*/
interface MediaCollectionInterface
{
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media interface.
*/
interface MediaInterface
{
/**
* Gets the associated media collection.
*
* @return MediaCollectionInterface Collection of associated media.
*/
public function getMedia();
/**
* Get filesystem path to the associated media.
*
* @return string|null Media path or null if the object doesn't have media folder.
*/
public function getMediaFolder();
/**
* Get display order for the associated media.
*
* @return array Empty array means default ordering.
*/
public function getMediaOrder();
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media object interface.
*/
interface MediaObjectInterface
{
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Grav\Common\Media\Traits;
use Grav\Common\Cache;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Page\Media;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
trait MediaTrait
{
protected $media;
/**
* Get filesystem path to the associated media.
*
* @return string|null
*/
abstract public function getMediaFolder();
/**
* Get display order for the associated media.
*
* @return array Empty array means default ordering.
*/
abstract public function getMediaOrder();
/**
* Get URI ot the associated media. Method will return null if path isn't URI.
*
* @return null|string
*/
public function getMediaUri()
{
$folder = $this->getMediaFolder();
if (strpos($folder, '://')) {
return $folder;
}
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$user = $locator->findResource('user://');
if (strpos($folder, $user) === 0) {
return 'user://' . substr($folder, strlen($user)+1);
}
return null;
}
/**
* Gets the associated media collection.
*
* @return MediaCollectionInterface Representation of associated media.
*/
public function getMedia()
{
$cache = $this->getMediaCache();
if ($this->media === null) {
// Use cached media if possible.
$cacheKey = md5('media' . $this->getCacheKey());
if (!$media = $cache->fetch($cacheKey)) {
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
$cache->save($cacheKey, $media);
}
$this->media = $media;
}
return $this->media;
}
/**
* Sets the associated media collection.
*
* @param MediaCollectionInterface $media Representation of associated media.
* @return $this
*/
protected function setMedia(MediaCollectionInterface $media)
{
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->save($cacheKey, $media);
$this->media = $media;
return $this;
}
/**
* Clear media cache.
*/
protected function clearMediaCache()
{
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->delete($cacheKey);
}
/**
* @return Cache
*/
protected function getMediaCache()
{
return Grav::instance()['cache'];
}
/**
* @return string
*/
abstract protected function getCacheKey();
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\Page\Interfaces;
/**
* Class implements page interface.
*/
interface PageInterface
{
}

View File

@@ -9,11 +9,11 @@
namespace Grav\Common\Page;
use Grav\Common\Grav;
use Grav\Common\Yaml;
use Grav\Common\Page\Medium\AbstractMedia;
use Grav\Common\Page\Medium\GlobalMedia;
use Grav\Common\Page\Medium\MediumFactory;
use RocketTheme\Toolbox\File\File;
use Symfony\Component\Yaml\Yaml;
class Media extends AbstractMedia
{
@@ -24,11 +24,13 @@ class Media extends AbstractMedia
protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
/**
* @param $path
* @param string $path
* @param array $media_order
*/
public function __construct($path)
public function __construct($path, array $media_order = null)
{
$this->path = $path;
$this->media_order = $media_order;
$this->__wakeup();
$this->init();
@@ -71,6 +73,7 @@ class Media extends AbstractMedia
protected function init()
{
$config = Grav::instance()['config'];
$locator = Grav::instance()['locator'];
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
@@ -86,7 +89,7 @@ class Media extends AbstractMedia
/** @var \DirectoryIterator $info */
foreach ($iterator as $path => $info) {
// Ignore folders and Markdown files.
if (!$info->isFile() || $info->getExtension() === 'md' || $info->getBasename()[0] === '.') {
if (!$info->isFile() || $info->getExtension() === 'md' || $info->getFilename()[0] === '.') {
continue;
}
@@ -153,7 +156,11 @@ class Media extends AbstractMedia
$meta_data = $meta->getData();
$meta_trimmed = array_diff_key($meta_data, array_flip($this->standard_exif));
if ($meta_trimmed) {
$file = File::instance($meta_path);
if ($locator->isStream($meta_path)) {
$file = File::instance($locator->findResource($meta_path, true, true));
} else {
$file = File::instance($meta_path);
}
$file->save(Yaml::dump($meta_trimmed));
$types['meta']['file'] = $meta_path;
}

View File

@@ -10,9 +10,11 @@ namespace Grav\Common\Page\Medium;
use Grav\Common\Getters;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
use Grav\Common\Utils;
abstract class AbstractMedia extends Getters
abstract class AbstractMedia extends Getters implements MediaCollectionInterface
{
protected $gettersVariable = 'instances';
@@ -21,6 +23,7 @@ abstract class AbstractMedia extends Getters
protected $videos = [];
protected $audios = [];
protected $files = [];
protected $media_order;
/**
* Get medium by filename.
@@ -62,7 +65,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function all()
{
@@ -74,7 +77,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all image media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function images()
{
@@ -85,7 +88,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all video media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function videos()
{
@@ -96,7 +99,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all audio media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function audios()
{
@@ -107,7 +110,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all file media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function files()
{
@@ -117,7 +120,7 @@ abstract class AbstractMedia extends Getters
/**
* @param string $name
* @param Medium $file
* @param MediaObjectInterface $file
*/
protected function add($name, $file)
{
@@ -145,14 +148,20 @@ abstract class AbstractMedia extends Getters
*/
protected function orderMedia($media)
{
$page = Grav::instance()['pages']->get($this->path);
if (null === $this->media_order) {
$page = Grav::instance()['pages']->get($this->path);
if ($page && isset($page->header()->media_order)) {
$media_order = array_map('trim', explode(',', $page->header()->media_order));
$media = Utils::sortArrayByArray($media, $media_order);
if ($page && isset($page->header()->media_order)) {
$this->media_order = array_map('trim', explode(',', $page->header()->media_order));
}
}
if (!empty($this->media_order) && is_array($this->media_order)) {
$media = Utils::sortArrayByArray($media, $this->media_order);
} else {
ksort($media, SORT_NATURAL | SORT_FLAG_CASE);
}
return $media;
}

View File

@@ -11,6 +11,7 @@ namespace Grav\Common\Page\Medium;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class ImageMedium extends Medium
{
@@ -164,12 +165,18 @@ class ImageMedium extends Medium
*/
public function url($reset = true)
{
$image_path = Grav::instance()['locator']->findResource('cache://images', true);
$image_dir = Grav::instance()['locator']->findResource('cache://images', false);
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$image_path = $locator->findResource('cache://images', true);
$image_dir = $locator->findResource('cache://images', false);
$saved_image_path = $this->saveImage();
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
if ($locator->isStream($output)) {
$output = $locator->findResource($output, false);
}
if (Utils::startsWith($output, $image_path)) {
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
}
@@ -216,7 +223,7 @@ class ImageMedium extends Medium
foreach ($this->alternatives as $ratio => $medium) {
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
}
$srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w';
$srcset[] = str_replace(' ', '%20', $this->url($reset)) . ' ' . $this->get('width') . 'w';
return implode(', ', $srcset);
}

View File

@@ -12,9 +12,9 @@ use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\Data\Data;
use Grav\Common\Data\Blueprint;
use Grav\Common\Utils;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
class Medium extends Data implements RenderableInterface
class Medium extends Data implements RenderableInterface, MediaObjectInterface
{
use ParsedownHtmlTrait;
@@ -199,7 +199,12 @@ class Medium extends Data implements RenderableInterface
*/
public function url($reset = true)
{
$output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->get('filepath'));
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $this->get('filepath'));
$locator = Grav::instance()['locator'];
if ($locator->isStream($output)) {
$output = $locator->findResource($output, false);
}
if ($reset) {
$this->reset();

View File

@@ -94,6 +94,40 @@ class VideoMedium extends Medium
return $this;
}
/**
* Allows to set the playsinline attribute
*
* @param bool $status
* @return $this
*/
public function playsinline($status = false)
{
if($status) {
$this->attributes['playsinline'] = true;
} else {
unset($this->attributes['playsinline']);
}
return $this;
}
/**
* Allows to set the muted attribute
*
* @param bool $status
* @return $this
*/
public function muted($status = false)
{
if($status) {
$this->attributes['muted'] = true;
} else {
unset($this->attributes['muted']);
}
return $this;
}
/**
* Reset medium.
*

View File

@@ -12,23 +12,26 @@ use Exception;
use Grav\Common\Cache;
use Grav\Common\Config\Config;
use Grav\Common\Data\Blueprint;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Markdown\Parsedown;
use Grav\Common\Markdown\ParsedownExtra;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Media\Traits\MediaTrait;
use Grav\Common\Taxonomy;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Common\Yaml;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\MarkdownFile;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
define('PAGE_ORDER_PREFIX_REGEX', '/^[0-9]+\./u');
class Page
class Page implements PageInterface
{
use MediaTrait;
/**
* @var string Filename. Leave as null if page is folder.
*/
@@ -65,7 +68,6 @@ class Page
protected $summary;
protected $raw_content;
protected $pagination;
protected $media;
protected $metadata;
protected $title;
protected $max_count;
@@ -135,7 +137,7 @@ class Page
$this->metadata();
$this->url();
$this->visible();
$this->modularTwig($this->slug[0] === '_');
$this->modularTwig(strpos($this->slug(), '_') === 0);
$this->setPublishState();
$this->published();
$this->urlExtension();
@@ -166,7 +168,7 @@ class Page
unset($process_fields[$field]);
}
}
$text_header = Grav::instance()['twig']->processString(json_encode($process_fields), ['page' => $this]);
$text_header = Grav::instance()['twig']->processString(json_encode($process_fields, JSON_UNESCAPED_UNICODE), ['page' => $this]);
$this->header((object)(json_decode($text_header, true) + $ignored_fields));
}
}
@@ -193,7 +195,7 @@ class Page
$route = isset($aPage->header()->routes['default']) ? $aPage->header()->routes['default'] : $aPage->rawRoute();
if (!$route) {
$route = $aPage->slug();
$route = $aPage->route();
}
if ($onlyPublished && !$aPage->published()) {
@@ -318,8 +320,6 @@ class Page
if (!$this->header) {
$file = $this->file();
if ($file) {
// Set some options
$file->settings(['native' => true, 'compat' => true]);
try {
$this->raw_content = $file->markdown();
$this->frontmatter = $file->frontmatter();
@@ -328,11 +328,12 @@ class Page
if (!Utils::isAdminPlugin()) {
// If there's a `frontmatter.yaml` file merge that in with the page header
// note page's own frontmatter has precedence and will overwrite any defaults
$frontmatter_file = $this->path . '/' . $this->folder . '/frontmatter.yaml';
if (file_exists($frontmatter_file)) {
$frontmatter_data = (array)Yaml::parse(file_get_contents($frontmatter_file));
$frontmatterFile = CompiledYamlFile::instance($this->path . '/' . $this->folder . '/frontmatter.yaml');
if ($frontmatterFile->exists()) {
$frontmatter_data = (array)$frontmatterFile->content();
$this->header = (object)array_replace_recursive($frontmatter_data,
(array)$this->header);
$frontmatterFile->free();
}
// Process frontmatter with Twig if enabled
if (Grav::instance()['config']->get('system.pages.frontmatter.process_twig') === true) {
@@ -686,6 +687,8 @@ class Page
$this->content = str_replace("<p>{$delimiter}</p>", '', $this->content);
}
// Fire event when Page::content() is called
Grav::instance()->fireEvent('onPageContent', new Event(['page' => $this]));
}
return $this->content;
@@ -763,6 +766,8 @@ class Page
// pages.markdown_extra is deprecated, but still check it...
if (!isset($defaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) {
user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED);
$defaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
}
@@ -813,6 +818,8 @@ class Page
*/
public function setRawContent($content)
{
$content = $content === null ? '': $content;
$this->content = $content;
}
@@ -1122,6 +1129,14 @@ class Page
return json_encode($this->toArray());
}
/**
* @return string
*/
protected function getCacheKey()
{
return $this->id();
}
/**
* Gets and sets the associated media as found in the page folder.
*
@@ -1131,23 +1146,33 @@ class Page
*/
public function media($var = null)
{
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
if ($var) {
$this->media = $var;
}
if ($this->media === null) {
// Use cached media if possible.
$media_cache_id = md5('media' . $this->id());
if (!$media = $cache->fetch($media_cache_id)) {
$media = new Media($this->path());
$cache->save($media_cache_id, $media);
}
$this->media = $media;
$this->setMedia($var);
}
return $this->media;
return $this->getMedia();
}
/**
* Get filesystem path to the associated media.
*
* @return string|null
*/
public function getMediaFolder()
{
return $this->path();
}
/**
* Get display order for the associated media.
*
* @return array Empty array means default ordering.
*/
public function getMediaOrder()
{
$header = $this->header();
return isset($header->media_order) ? array_map('trim', explode(',', (string)$header->media_order)) : [];
}
/**
@@ -1561,7 +1586,7 @@ class Page
}
if (empty($this->slug)) {
$this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
$this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder)) ?: null;
}
@@ -1626,14 +1651,19 @@ class Page
* Gets the url for the Page.
*
* @param bool $include_host Defaults false, but true would include http://yourhost.com
* @param bool $canonical true to return the canonical URL
* @param bool $include_lang
* @param bool $canonical True to return the canonical URL
* @param bool $include_base Include base url on multisite as well as language code
* @param bool $raw_route
*
* @return string The url.
*/
public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false)
public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false)
{
// Override any URL when external_url is set
if (isset($this->external_url)) {
return $this->external_url;
}
$grav = Grav::instance();
/** @var Pages $pages */
@@ -1642,41 +1672,25 @@ class Page
/** @var Config $config */
$config = $grav['config'];
/** @var Language $language */
$language = $grav['language'];
/** @var Uri $uri */
$uri = $grav['uri'];
// Override any URL when external_url is set
if (isset($this->external_url)) {
return $this->external_url;
}
// get pre-route
if ($include_lang && $language->enabled()) {
$pre_route = $language->getLanguageURLPrefix();
} else {
$pre_route = '';
}
// get base route (multisite base and language)
$route = $include_base ? $pages->baseRoute() : '';
// add full route if configured to do so
if ($config->get('system.absolute_urls', false)) {
if (!$include_host && $config->get('system.absolute_urls', false)) {
$include_host = true;
}
// get canonical route if requested
if ($canonical) {
$route = $pre_route . $this->routeCanonical();
$route .= $this->routeCanonical();
} elseif ($raw_route) {
$route = $pre_route . $this->rawRoute();
$route .= $this->rawRoute();
} else {
$route = $pre_route . $this->route();
$route .= $this->route();
}
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
$url = $rootUrl . '/' . trim($route, '/') . $this->urlExtension();
/** @var Uri $uri */
$uri = $grav['uri'];
$url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension();
// trim trailing / if not root
if ($url !== '/') {
@@ -1790,7 +1804,7 @@ class Page
public function routeCanonical($var = null)
{
if ($var !== null) {
$this->routes['canonical'] = (array)$var;
$this->routes['canonical'] = $var;
}
if (!empty($this->routes) && isset($this->routes['canonical'])) {

View File

@@ -49,7 +49,7 @@ class Pages
/**
* @var array|string[]
*/
protected $baseUrl = [];
protected $baseRoute = [];
/**
* @var array|string[]
@@ -120,47 +120,69 @@ class Pages
if ($path !== null) {
$path = trim($path, '/');
$this->base = $path ? '/' . $path : null;
$this->baseUrl = [];
$this->baseRoute = [];
}
return $this->base;
}
/**
*
* Get base route for Grav pages.
*
* @param string $lang Optional language code for multilingual routes.
*
* @return string
*/
public function baseRoute($lang = null)
{
$key = $lang ?: 'default';
if (!isset($this->baseRoute[$key])) {
/** @var Language $language */
$language = $this->grav['language'];
$path_base = rtrim($this->base(), '/');
$path_lang = $language->enabled() ? $language->getLanguageURLPrefix($lang) : '';
$this->baseRoute[$key] = $path_base . $path_lang;
}
return $this->baseRoute[$key];
}
/**
*
* Get route for Grav site.
*
* @param string $route Optional route to the page.
* @param string $lang Optional language code for multilingual links.
*
* @return string
*/
public function route($route = '/', $lang = null)
{
if (!$route || $route === '/') {
return $this->baseRoute($lang) ?: '/';
}
return $this->baseRoute($lang) . $route;
}
/**
*
* Get base URL for Grav pages.
*
* @param string $lang Optional language code for multilingual links.
* @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
* @param string $lang Optional language code for multilingual links.
* @param bool|null $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
*
* @return string
*/
public function baseUrl($lang = null, $absolute = null)
{
$lang = (string) $lang;
$type = $absolute === null ? 'base_url' : ($absolute ? 'base_url_absolute' : 'base_url_relative');
$key = "{$lang} {$type}";
if (!isset($this->baseUrl[$key])) {
/** @var Config $config */
$config = $this->grav['config'];
/** @var Language $language */
$language = $this->grav['language'];
if (!$lang) {
$lang = $language->getActive();
}
$path_append = rtrim($this->grav['pages']->base(), '/');
if ($language->getDefault() !== $lang || $config->get('system.languages.include_default_lang') === true) {
$path_append .= $lang ? '/' . $lang : '';
}
$this->baseUrl[$key] = $this->grav[$type] . $path_append;
}
return $this->baseUrl[$key];
return $this->grav[$type] . $this->baseRoute($lang);
}
/**
@@ -179,7 +201,7 @@ class Pages
/**
*
* Get home URL for Grav site.
* Get URL for Grav site.
*
* @param string $route Optional route to the page.
* @param string $lang Optional language code for multilingual links.
@@ -189,7 +211,7 @@ class Pages
*/
public function url($route = '/', $lang = null, $absolute = null)
{
if ($route === '/') {
if (!$route || $route === '/') {
return $this->homeUrl($lang, $absolute);
}
@@ -924,7 +946,7 @@ class Pages
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
// recurse pages and cache result
$this->resetPages($pages_dir, $this->pages_cache_id);
$this->resetPages($pages_dir);
} else {
// If pages was found in cache, set the taxonomy
@@ -1041,7 +1063,7 @@ class Pages
}
// Ignore all files in ignore list.
if (\in_array($file->getBasename(), $this->ignore_files, true)) {
if (\in_array($filename, $this->ignore_files, true)) {
continue;
}

View File

@@ -33,7 +33,7 @@ class Plugins extends Iterator
if (!$directory->isDir()) {
continue;
}
$plugins[] = $directory->getBasename();
$plugins[] = $directory->getFilename();
}
natsort($plugins);

View File

@@ -8,6 +8,10 @@
namespace Grav\Common\Processors;
use Grav\Common\Config\Config;
use Grav\Common\Uri;
use Grav\Common\Utils;
class InitializeProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'init';
@@ -15,29 +19,36 @@ class InitializeProcessor extends ProcessorBase implements ProcessorInterface
public function process()
{
$this->container['config']->debug();
/** @var Config $config */
$config = $this->container['config'];
$config->debug();
// Use output buffering to prevent headers from being sent too early.
ob_start();
if ($this->container['config']->get('system.cache.gzip')) {
if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) {
// Enable zip/deflate with a fallback in case of if browser does not support compressing.
if (!@ob_start("ob_gzhandler")) {
ob_start();
}
ob_start();
}
// Initialize the timezone.
if ($this->container['config']->get('system.timezone')) {
if ($config->get('system.timezone')) {
date_default_timezone_set($this->container['config']->get('system.timezone'));
}
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
if ($this->container['config']->get('system.session.initialize', 1) && isset($this->container['session'])) {
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
$this->container['session']->init();
}
// Initialize uri.
$this->container['uri']->init();
/** @var Uri $uri */
$uri = $this->container['uri'];
$uri->init();
// Redirect pages with trailing slash if configured to do so.
$path = $uri->path() ?: '/';
if ($path !== '/' && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($path, '/')) {
$this->container->redirectLangSafe(rtrim($path, '/'));
}
$this->container->setLocale();
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* @package Grav.Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common;
class Security
{
public static function detectXssFromPages($pages, callable $status = null)
{
$routes = $pages->routes();
// Remove duplicate for homepage
unset($routes['/']);
$list = [];
// // This needs Symfony 4.1 to work
// $status && $status([
// 'type' => 'count',
// 'steps' => count($routes),
// ]);
foreach ($routes as $path) {
$status && $status([
'type' => 'progress',
]);
try {
$page = $pages->get($path);
// call the content to load/cache it
$header = (array) $page->header();
$content = $page->value('content');
$data = ['header' => $header, 'content' => $content];
$results = Security::detectXssFromArray($data);
if (!empty($results)) {
$list[$page->filePathClean()] = $results;
}
} catch (\Exception $e) {
continue;
}
}
return $list;
}
/**
* @param array $array Array such as $_POST or $_GET
* @param string $prefix Prefix for returned values.
* @return array Returns flatten list of potentially dangerous input values, such as 'data.content'.
*/
public static function detectXssFromArray(array $array, $prefix = '')
{
$list = [];
foreach ($array as $key => $value) {
if (\is_array($value)) {
$list[] = static::detectXssFromArray($value, $prefix . $key . '.');
}
if ($result = static::detectXss($value)) {
$list[] = [$prefix . $key => $result];
}
}
if (!empty($list)) {
return array_merge(...$list);
}
return $list;
}
/**
* Determine if string potentially has a XSS attack. This simple function does not catch all XSS and it is likely to
* return false positives because of it tags all potentially dangerous HTML tags and attributes without looking into
* their content.
*
* @param string $string The string to run XSS detection logic on
* @return boolean|string Type of XSS vector if the given `$string` may contain XSS, false otherwise.
*
* Copies the code from: https://github.com/symphonycms/xssfilter/blob/master/extension.driver.php#L138
*/
public static function detectXss($string)
{
// Skip any null or non string values
if (null === $string || !\is_string($string) || empty($string)) {
return false;
}
// Keep a copy of the original string before cleaning up
$orig = $string;
// URL decode
$string = urldecode($string);
// Convert Hexadecimals
$string = (string)preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!u', function($m) {
return \chr(hexdec($m[2]));
}, $string);
// Clean up entities
$string = preg_replace('!(&#0+[0-9]+)!u','$1;', $string);
// Decode entities
$string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
// Strip whitespace characters
$string = preg_replace('!\s!u','', $string);
$config = Grav::instance()['config'];
$dangerous_tags = $config->get('security.xss_dangerous_tags');
$dangerous_tags = array_map('preg_quote', array_map("trim", $dangerous_tags));
$enabled_rules = $config->get('security.xss_enabled');
// 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',
// Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
'invalid_protocols' => '#((java|live|vb)script|mocha|feed|data):.*?#iUu',
// Match -moz-bindings
'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u',
// Match style attributes
'html_inline_styles' => '#(<[^>]+[a-z\x00-\x20\"\'\/])(style=[^>]*(url\:|x\:expression).*)>?#iUu',
// Match potentially dangerous tags
'dangerous_tags' => '#</*(' . implode('|', $dangerous_tags ) . ')[^>]*>?#ui'
];
// Iterate over rules and return label if fail
foreach ((array) $patterns as $name => $regex) {
if ($enabled_rules[$name] === true) {
if (preg_match($regex, $string) || preg_match($regex, $orig)) {
return $name;
}
}
}
return false;
}
}

View File

@@ -16,6 +16,7 @@ use Grav\Common\Config\ConfigFileFinder;
use Grav\Common\Config\Setup;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\File\YamlFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class ConfigServiceProvider implements ServiceProviderInterface
@@ -31,7 +32,14 @@ class ConfigServiceProvider implements ServiceProviderInterface
};
$container['config'] = function ($c) {
return static::load($c);
$config = static::load($c);
// After configuration has been loaded, we can disable YAML compatibility if strict mode has been enabled.
if (!$config->get('system.strict_mode.yaml_compat', true)) {
YamlFile::globalSettings(['compat' => false, 'native' => true]);
}
return $config;
};
$container['languages'] = function ($c) {
@@ -65,6 +73,10 @@ class ConfigServiceProvider implements ServiceProviderInterface
return $blueprints->name("master-{$setup->environment}")->load();
}
/**
* @param Container $container
* @return Config
*/
public static function load(Container $container)
{
/** Setup $setup */

View File

@@ -8,6 +8,7 @@
namespace Grav\Common\Service;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
@@ -26,35 +27,33 @@ class PageServiceProvider implements ServiceProviderInterface
/** @var Pages $pages */
$pages = $c['pages'];
/** @var Config $config */
$config = $c['config'];
/** @var Uri $uri */
$uri = $c['uri'];
$path = $uri->path(); // Don't trim to support trailing slash default routes
$path = $path ?: '/';
$path = $uri->path() ?: '/'; // Don't trim to support trailing slash default routes
$page = $pages->dispatch($path);
// Redirection tests
if ($page) {
/** @var Language $language */
$language = $c['language'];
// some debugger override logic
if ($page->debugger() === false) {
$c['debugger']->enabled(false);
}
if ($c['config']->get('system.force_ssl')) {
if (!isset($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on") {
$url = "https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
if ($config->get('system.force_ssl')) {
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
$url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$c->redirect($url);
}
}
$url = $page->route();
$url = $pages->route($page->route());
if ($uri->params()) {
if ($url == '/') { //Avoid double slash
if ($url === '/') { //Avoid double slash
$url = $uri->params();
} else {
$url .= $uri->params();
@@ -67,18 +66,16 @@ class PageServiceProvider implements ServiceProviderInterface
$url .= '#' . $uri->fragment();
}
/** @var Language $language */
$language = $c['language'];
// Language-specific redirection scenarios
if ($language->enabled()) {
if ($language->isLanguageInUrl() && !$language->isIncludeDefaultLanguage()) {
$c->redirect($url);
}
if (!$language->isLanguageInUrl() && $language->isIncludeDefaultLanguage()) {
$c->redirectLangSafe($url);
}
if ($language->enabled() && ($language->isLanguageInUrl() xor $language->isIncludeDefaultLanguage())) {
$c->redirect($url);
}
// Default route test and redirect
if ($c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) {
$c->redirectLangSafe($url);
if ($config->get('system.pages.redirect_default_route') && $page->route() !== $path) {
$c->redirect($url);
}
}

View File

@@ -29,21 +29,22 @@ class SessionServiceProvider implements ServiceProviderInterface
/** @var Uri $uri */
$uri = $c['uri'];
// Get session parameters.
$session_timeout = (int)$config->get('system.session.timeout', 1800);
$session_path = $config->get('system.session.path');
if (null === $session_path) {
$session_path = '/' . ltrim(Uri::filterPath($uri->rootUrl(false)), '/');
}
$domain = $uri->host();
if ($domain === 'localhost') {
$domain = '';
}
// Get session options.
$secure = (bool)$config->get('system.session.secure', false);
$httponly = (bool)$config->get('system.session.httponly', true);
$enabled = (bool)$config->get('system.session.enabled', false);
$cookie_secure = (bool)$config->get('system.session.secure', false);
$cookie_httponly = (bool)$config->get('system.session.httponly', true);
$cookie_lifetime = (int)$config->get('system.session.timeout', 1800);
$cookie_path = $config->get('system.session.path');
if (null === $cookie_path) {
$cookie_path = '/' . trim(Uri::filterPath($uri->rootUrl(false)), '/');
}
// Session cookie path requires trailing slash.
$cookie_path = rtrim($cookie_path, '/') . '/';
$cookie_domain = $uri->host();
if ($cookie_domain === 'localhost') {
$cookie_domain = '';
}
// Activate admin if we're inside the admin path.
$is_admin = false;
@@ -56,14 +57,14 @@ class SessionServiceProvider implements ServiceProviderInterface
// Check no language, simple language prefix (en) and region specific language prefix (en-US).
$pos = strpos($current_route, $base);
if ($pos === 0 || $pos === 3 || $pos === 6) {
$session_timeout = $config->get('plugins.admin.session.timeout', 1800);
$cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
$enabled = $is_admin = true;
}
}
// Fix for HUGE session timeouts.
if ($session_timeout > 99999999999) {
$session_timeout = 9999999999;
if ($cookie_lifetime > 99999999999) {
$cookie_lifetime = 9999999999;
}
$inflector = new Inflector();
@@ -73,10 +74,16 @@ class SessionServiceProvider implements ServiceProviderInterface
}
// Define session service.
$session = new Session($session_timeout, $session_path, $domain);
$session->setName($session_name);
$session->setSecure($secure);
$session->setHttpOnly($httponly);
$options = [
'name' => $session_name,
'cookie_lifetime' => $cookie_lifetime,
'cookie_path' => $cookie_path,
'cookie_domain' => $cookie_domain,
'cookie_secure' => $cookie_secure,
'cookie_httponly' => $cookie_httponly
] + (array) $config->get('system.session.options');
$session = new Session($options);
$session->setAutoStart($enabled);
return $session;
@@ -84,7 +91,7 @@ class SessionServiceProvider implements ServiceProviderInterface
// Define session message service.
$container['messages'] = function ($c) {
if (!isset($c['session']) || !$c['session']->started()) {
if (!isset($c['session']) || !$c['session']->isStarted()) {
/** @var Debugger $debugger */
$debugger = $c['debugger'];
$debugger->addMessage('Inactive session: session messages may disappear', 'warming');

View File

@@ -8,34 +8,20 @@
namespace Grav\Common;
use RocketTheme\Toolbox\Session\Session as BaseSession;
class Session extends BaseSession
class Session extends \Grav\Framework\Session\Session
{
/** @var bool */
protected $autoStart = false;
protected $lifetime;
protected $path;
protected $domain;
protected $secure;
protected $httpOnly;
/**
* @param int $lifetime Defaults to 1800 seconds.
* @param string $path Cookie path.
* @param string $domain Optional, domain for the session
* @throws \RuntimeException
* @return \Grav\Framework\Session\Session
* @deprecated 1.5 Use getInstance() method instead
*/
public function __construct($lifetime, $path, $domain = null)
public static function instance()
{
$this->lifetime = $lifetime;
$this->path = $path;
$this->domain = $domain;
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getInstance() method instead', E_USER_DEPRECATED);
if (php_sapi_name() !== 'cli') {
parent::__construct($lifetime, $path, $domain);
}
return static::getInstance();
}
/**
@@ -48,9 +34,6 @@ class Session extends BaseSession
if ($this->autoStart) {
$this->start();
// TODO: This setcookie shouldn't be here, session should by itself be able to update its cookie.
setcookie(session_name(), session_id(), $this->lifetime ? time() + $this->lifetime : 0, $this->path, $this->domain, $this->secure, $this->httpOnly);
$this->autoStart = false;
}
}
@@ -67,27 +50,29 @@ class Session extends BaseSession
}
/**
* @param bool $secure
* @return $this
* Returns attributes.
*
* @return array Attributes
* @deprecated 1.5 Use getAll() method instead
*/
public function setSecure($secure)
public function all()
{
$this->secure = $secure;
ini_set('session.cookie_secure', (bool)$secure);
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getAll() method instead', E_USER_DEPRECATED);
return $this;
return $this->getAll();
}
/**
* @param bool $httpOnly
* @return $this
* Checks if the session was started.
*
* @return Boolean
* @deprecated 1.5 Use isStarted() method instead
*/
public function setHttpOnly($httpOnly)
public function started()
{
$this->httpOnly = $httpOnly;
ini_set('session.cookie_httponly', (bool)$httpOnly);
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use isStarted() method instead', E_USER_DEPRECATED);
return $this;
return $this->isStarted();
}
/**

View File

@@ -98,7 +98,7 @@ class Themes extends Iterator
continue;
}
$theme = $directory->getBasename();
$theme = $directory->getFilename();
$result = self::get($theme);
if ($result) {

View File

@@ -12,7 +12,7 @@ class TwigNodeMarkdown extends \Twig_Node implements \Twig_NodeOutputInterface
{
public function __construct(\Twig_Node $body, $lineno, $tag = 'markdown')
{
parent::__construct(array('body' => $body), array(), $lineno, $tag);
parent::__construct(['body' => $body], [], $lineno, $tag);
}
/**
* Compiles the node to PHP.

View File

@@ -8,13 +8,13 @@
namespace Grav\Common\Twig\Node;
class TwigNodeScript extends \Twig_Node implements \Twig_NodeOutputInterface
class TwigNodeScript extends \Twig_Node implements \Twig_NodeCaptureInterface
{
protected $tagName = 'script';
/**
* TwigNodeScript constructor.
* @param \Twig_NodeInterface|null $body
* @param \Twig_Node|null $body
* @param \Twig_Node_Expression|null $file
* @param \Twig_Node_Expression|null $group
* @param \Twig_Node_Expression|null $priority
@@ -23,12 +23,12 @@ class TwigNodeScript extends \Twig_Node implements \Twig_NodeOutputInterface
* @param string|null $tag
*/
public function __construct(
\Twig_NodeInterface $body = null,
\Twig_Node $body = null,
\Twig_Node_Expression $file = null,
\Twig_Node_Expression $group = null,
\Twig_Node_Expression $priority = null,
\Twig_Node_Expression $attributes = null,
$lineno,
$lineno = 0,
$tag = null
)
{

View File

@@ -8,24 +8,24 @@
namespace Grav\Common\Twig\Node;
class TwigNodeStyle extends \Twig_Node implements \Twig_NodeOutputInterface
class TwigNodeStyle extends \Twig_Node implements \Twig_NodeCaptureInterface
{
protected $tagName = 'style';
/**
* TwigNodeAssets constructor.
* @param \Twig_NodeInterface|null $body
* @param \Twig_Node|null $body
* @param \Twig_Node_Expression|null $attributes
* @param int $lineno
* @param null $tag
*/
public function __construct(
\Twig_NodeInterface $body = null,
\Twig_Node $body = null,
\Twig_Node_Expression $file = null,
\Twig_Node_Expression $group = null,
\Twig_Node_Expression $priority = null,
\Twig_Node_Expression $attributes = null,
$lineno,
$lineno = 0,
$tag = null
)
{

View File

@@ -8,9 +8,15 @@
namespace Grav\Common\Twig\Node;
class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
class TwigNodeSwitch extends \Twig_Node
{
public function __construct(\Twig_NodeInterface $value, \Twig_NodeInterface $cases, \Twig_NodeInterface $default = null, $lineno, $tag = null)
public function __construct(
\Twig_Node $value,
\Twig_Node $cases,
\Twig_Node $default = null,
$lineno = 0,
$tag = null
)
{
parent::__construct(array('value' => $value, 'cases' => $cases, 'default' => $default), array(), $lineno, $tag);
}
@@ -24,20 +30,17 @@ class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
{
$compiler
->addDebugInfo($this)
->write("switch (")
->write('switch (')
->subcompile($this->getNode('value'))
->raw(") {\n")
->indent();
foreach ($this->getNode('cases') as $case)
{
if (!$case->hasNode('body'))
{
foreach ($this->getNode('cases') as $case) {
if (!$case->hasNode('body')) {
continue;
}
foreach ($case->getNode('values') as $value)
{
foreach ($case->getNode('values') as $value) {
$compiler
->write('case ')
->subcompile($value)
@@ -53,8 +56,7 @@ class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
->write("}\n");
}
if ($this->hasNode('default') && $this->getNode('default') !== null)
{
if ($this->hasNode('default') && $this->getNode('default') !== null) {
$compiler
->write("default:\n")
->write("{\n")

View File

@@ -10,7 +10,12 @@ namespace Grav\Common\Twig\Node;
class TwigNodeTryCatch extends \Twig_Node
{
public function __construct(\Twig_NodeInterface $try, \Twig_NodeInterface $catch = null, $lineno, $tag = null)
public function __construct(
\Twig_Node $try,
\Twig_Node $catch = null,
$lineno = 0,
$tag = null
)
{
parent::__construct(array('try' => $try, 'catch' => $catch), array(), $lineno, $tag);
}

View File

@@ -27,7 +27,7 @@ class TwigTokenParserScript extends \Twig_TokenParser
*
* @param \Twig_Token $token A Twig_Token instance
*
* @return \Twig_NodeInterface A Twig_NodeInterface instance
* @return \Twig_Node A Twig_Node instance
*/
public function parse(\Twig_Token $token)
{

View File

@@ -26,7 +26,7 @@ class TwigTokenParserStyle extends \Twig_TokenParser
*
* @param \Twig_Token $token A Twig_Token instance
*
* @return \Twig_NodeInterface A Twig_NodeInterface instance
* @return \Twig_Node A Twig_Node instance
*/
public function parse(\Twig_Token $token)
{

View File

@@ -37,8 +37,7 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
// There can be some whitespace between the {% switch %} and first {% case %} tag.
while ($stream->getCurrent()->getType() == \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) == '')
{
while ($stream->getCurrent()->getType() === \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
$stream->next();
}
@@ -47,56 +46,45 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
$expressionParser = $this->parser->getExpressionParser();
$default = null;
$cases = array();
$cases = [];
$end = false;
while (!$end)
{
while (!$end) {
$next = $stream->next();
switch ($next->getValue())
{
switch ($next->getValue()) {
case 'case':
{
$values = array();
$values = [];
while (true)
{
$values[] = $expressionParser->parsePrimaryExpression();
// Multiple allowed values?
if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or'))
{
$stream->next();
}
else
{
break;
}
while (true) {
$values[] = $expressionParser->parsePrimaryExpression();
// Multiple allowed values?
if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or')) {
$stream->next();
} else {
break;
}
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
$cases[] = new \Twig_Node([
'values' => new \Twig_Node($values),
'body' => $body
]);
break;
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
$cases[] = new \Twig_Node(array(
'values' => new \Twig_Node($values),
'body' => $body
));
break;
}
case 'default':
{
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
case 'endswitch':
{
$end = true;
break;
}
$end = true;
break;
default:
{
throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
}
throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
}
}
@@ -127,7 +115,6 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
return $token->test(array('endswitch'));
}
/**
* {@inheritdoc}
*/

View File

@@ -28,7 +28,7 @@ class TwigTokenParserTryCatch extends \Twig_TokenParser
*
* @param \Twig_Token $token A Twig_Token instance
*
* @return \Twig_NodeInterface A Twig_NodeInterface instance
* @return \Twig_Node A Twig_Node instance
*/
public function parse(\Twig_Token $token)
{

View File

@@ -16,6 +16,7 @@ use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Event\Event;
use Phive\Twig\Extensions\Deferred\DeferredExtension;
class Twig
{
@@ -102,6 +103,28 @@ class Twig
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
// Register all other prefixes as namespaces in twig
foreach ($locator->getPaths('theme') as $prefix => $_) {
if ($prefix === '') {
continue;
}
$twig_paths = [];
// handle language templates if available
if ($language->enabled()) {
$lang_templates = $locator->findResource('theme://'.$prefix.'templates/' . ($active_language ? $active_language : $language->getDefault()));
if ($lang_templates) {
$twig_paths[] = $lang_templates;
}
}
$twig_paths = array_merge($twig_paths, $locator->findResources('theme://'.$prefix.'templates'));
$namespace = trim($prefix, '/');
$this->loader->setPaths($twig_paths, $namespace);
}
$this->grav->fireEvent('onTwigLoader');
$this->loaderArray = new \Twig_Loader_Array([]);
@@ -113,8 +136,15 @@ class Twig
$params['cache'] = new \Twig_Cache_Filesystem($cachePath, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION);
}
if (!empty($this->autoescape)) {
$params['autoescape'] = $this->autoescape;
if (!$config->get('system.strict_mode.twig_compat', true)) {
// Force autoescape on for all files if in strict mode.
$params['autoescape'] = 'html';
} elseif (!empty($this->autoescape)) {
$params['autoescape'] = $this->autoescape ? 'html' : false;
}
if (empty($params['autoescape'])) {
user_error('Grav 2.0 will have Twig auto-escaping forced on (can be emulated by turning off \'system.strict_mode.twig_compat\' setting in your configuration)', E_USER_DEPRECATED);
}
$this->twig = new TwigEnvironment($loader_chain, $params);
@@ -122,10 +152,10 @@ class Twig
if ($config->get('system.twig.undefined_functions')) {
$this->twig->registerUndefinedFunctionCallback(function ($name) {
if (function_exists($name)) {
return new \Twig_Function_Function($name);
return new \Twig_SimpleFunction($name, $name);
}
return new \Twig_Function_Function(function () {
return new \Twig_SimpleFunction($name, function () {
});
});
}
@@ -133,10 +163,10 @@ class Twig
if ($config->get('system.twig.undefined_filters')) {
$this->twig->registerUndefinedFilterCallback(function ($name) {
if (function_exists($name)) {
return new \Twig_Filter_Function($name);
return new \Twig_SimpleFilter($name, $name);
}
return new \Twig_Filter_Function(function () {
return new \Twig_SimpleFilter($name, function () {
});
});
}
@@ -145,13 +175,14 @@ class Twig
// set default date format if set in config
if ($config->get('system.pages.dateformat.long')) {
$this->twig->getExtension('core')->setDateFormat($config->get('system.pages.dateformat.long'));
$this->twig->getExtension('Twig_Extension_Core')->setDateFormat($config->get('system.pages.dateformat.long'));
}
// enable the debug extension if required
if ($config->get('system.twig.debug')) {
$this->twig->addExtension(new \Twig_Extension_Debug());
}
$this->twig->addExtension(new TwigExtension());
$this->twig->addExtension(new DeferredExtension());
$this->grav->fireEvent('onTwigExtensions');
@@ -159,7 +190,7 @@ class Twig
$pages = $this->grav['pages'];
// Set some standard variables for twig
$this->twig_vars = $this->twig_vars + [
$this->twig_vars += [
'config' => $config,
'system' => $config->get('system'),
'theme' => $config->get('theme'),
@@ -238,7 +269,7 @@ class Twig
// Process Modular Twig
if ($item->modularTwig()) {
$twig_vars['content'] = $content;
$extension = $this->grav['uri']->extension();
$extension = $item->templateFormat();
$extension = $extension ? ".{$extension}.twig" : TEMPLATE_EXT;
$template = $item->template() . $extension;
$output = $content = $local_twig->render($template, $twig_vars);
@@ -408,8 +439,14 @@ class Twig
* Overrides the autoescape setting
*
* @param boolean $state
* @deprecated 1.5
*/
public function setAutoescape($state) {
public function setAutoescape($state)
{
if (!$state) {
user_error(__CLASS__ . '::' . __FUNCTION__ . '(false) is deprecated since Grav 1.5', E_USER_DEPRECATED);
}
$this->autoescape = (bool) $state;
}
}

View File

@@ -11,6 +11,7 @@ namespace Grav\Common\Twig;
use Grav\Common\Grav;
use Grav\Common\Page\Collection;
use Grav\Common\Page\Media;
use Grav\Common\Security;
use Grav\Common\Twig\TokenParser\TwigTokenParserScript;
use Grav\Common\Twig\TokenParser\TwigTokenParserStyle;
use Grav\Common\Twig\TokenParser\TwigTokenParserSwitch;
@@ -18,11 +19,11 @@ use Grav\Common\Twig\TokenParser\TwigTokenParserTryCatch;
use Grav\Common\Twig\TokenParser\TwigTokenParserMarkdown;
use Grav\Common\User\User;
use Grav\Common\Utils;
use Grav\Common\Yaml;
use Grav\Common\Markdown\Parsedown;
use Grav\Common\Markdown\ParsedownExtra;
use Grav\Common\Helpers\Base32;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\Yaml\Yaml;
class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
{
@@ -72,7 +73,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]),
new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
@@ -88,9 +89,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']),
new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
new \Twig_SimpleFilter('t', [$this, 'translate']),
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
@@ -100,6 +98,18 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('print_r', 'print_r'),
new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
// Translations
new \Twig_SimpleFilter('t', [$this, 'translate']),
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
// Casting values
new \Twig_SimpleFilter('string', [$this, 'stringFilter']),
new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]),
new \Twig_SimpleFilter('bool', [$this, 'boolFilter']),
new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]),
new \Twig_SimpleFilter('array', [$this, 'arrayFilter']),
];
}
@@ -111,7 +121,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
public function getFunctions()
{
return [
new \Twig_SimpleFunction('array', [$this, 'arrayFunc']),
new \Twig_SimpleFunction('array', [$this, 'arrayFilter']),
new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'),
new \Twig_SimpleFunction('array_unique', 'array_unique'),
@@ -132,9 +142,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
@@ -149,8 +156,13 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFilter']),
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']),
new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
// Translations
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
];
}
@@ -520,6 +532,27 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return "$difference $periods[$j] {$tense}";
}
/**
* Allow quick check of a string for XSS Vulnerabilities
*
* @param $string
* @return bool|string|array
*/
public function xssFunc($data)
{
if (is_array($data)) {
$results = Security::detectXssFromArray($data);
} else {
return Security::detectXss($data);
}
$results_parts = array_map(function($value, $key) {
return $key.': \''.$value . '\'';
}, array_values($results), array_keys($results));
return implode(', ', $results_parts);
}
/**
* @param $string
*
@@ -617,6 +650,62 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return ltrim($value, $chars);
}
/**
* Casts input to string.
*
* @param mixed $input
* @return string
*/
public function stringFilter($input)
{
return (string) $input;
}
/**
* Casts input to int.
*
* @param mixed $input
* @return int
*/
public function intFilter($input)
{
return (int) $input;
}
/**
* Casts input to bool.
*
* @param mixed $input
* @return bool
*/
public function boolFilter($input)
{
return (bool) $input;
}
/**
* Casts input to float.
*
* @param mixed $input
* @return float
*/
public function floatFilter($input)
{
return (float) $input;
}
/**
* Casts input to array.
*
* @param mixed $input
* @return array
*/
public function arrayFilter($input)
{
return (array) $input;
}
/**
* @return mixed
*/
@@ -693,7 +782,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$template = $env->createTemplate($twig);
return $template->render($context);
;
}
/**
@@ -748,7 +836,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* Output a Gist
*
* @param string $id
* @param string $file
* @param string|bool $file
*
* @return string
*/
@@ -788,19 +876,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
}
/**
* Cast a value to array
*
* @param $value
*
* @return array
*/
public function arrayFunc($value)
{
return (array)$value;
}
/**
* Workaround for twig associative array initialization
* Returns a key => val array
@@ -976,7 +1051,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
public function redirectFunc($url, $statusCode = 303)
{
header('Location: ' . $url, true, $statusCode);
die();
exit();
}
/**
@@ -1060,7 +1135,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
if (file_exists($filepath)) {
return file_get_contents($filepath);
}
}
return false;
}
@@ -1245,11 +1320,12 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* Dump/Encode data into YAML format
*
* @param $data
* @param $inline integer number of levels of inline syntax
* @return mixed
*/
public function yamlEncodeFilter($data)
public function yamlEncodeFilter($data, $inline = 10)
{
return Yaml::dump($data, 10);
return Yaml::dump($data, $inline);
}
/**

View File

@@ -11,9 +11,11 @@ namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Framework\Route\RouteFactory;
use Grav\Framework\Uri\UriFactory;
use Grav\Framework\Uri\UriPartsFilter;
use RocketTheme\Toolbox\Event\Event;
class Uri
{
@@ -156,12 +158,6 @@ class Uri
$uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri);
}
// If configured to, redirect trailing slash URI's with a 302 redirect
$redirect = str_replace($this->root, '', rtrim($uri, '/'));
if ($redirect && $uri !== '/' && $redirect !== $this->base() && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($uri, '/')) {
$grav->redirect($redirect, 302);
}
// process params
$uri = $this->processParams($uri, $config->get('system.param_sep'));
@@ -206,9 +202,9 @@ class Uri
}
// Set some Grav stuff
$grav['base_url_absolute'] = $grav['config']->get('system.custom_base_url') ?: $this->rootUrl(true);
$grav['base_url_absolute'] = $config->get('system.custom_base_url') ?: $this->rootUrl(true);
$grav['base_url_relative'] = $this->rootUrl(false);
$grav['base_url'] = $grav['config']->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
$grav['base_url'] = $config->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
RouteFactory::setRoot($this->root_path);
RouteFactory::setLanguage($language->getLanguageURLPrefix());
@@ -376,6 +372,17 @@ class Uri
return $this->extension;
}
public function method()
{
$method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
if ($method === 'POST' && isset($_SERVER['X-HTTP-METHOD-OVERRIDE'])) {
$method = strtoupper($_SERVER['X-HTTP-METHOD-OVERRIDE']);
}
return $method;
}
/**
* Return the scheme of the URI
*
@@ -481,11 +488,9 @@ class Uri
{
if ($include_root) {
return $this->uri;
} else {
$uri = str_replace($this->root_path, '', $this->uri);
return $uri;
}
return str_replace($this->root_path, '', $this->uri);
}
/**
@@ -508,16 +513,10 @@ class Uri
{
$grav = Grav::instance();
// Link processing should prepend language
$language = $grav['language'];
$language_append = '';
if ($language->enabled()) {
$language_append = $language->getLanguageURLPrefix();
}
/** @var Pages $pages */
$pages = $grav['pages'];
$base = $grav['base_url_relative'];
return rtrim($base . $grav['pages']->base(), '/') . $language_append;
return $pages->baseUrl(null, false);
}
/**
@@ -633,10 +632,9 @@ class Uri
}
return $ip;
}
/**
/**
* Returns current Uri.
*
* @return \Grav\Framework\Uri\Uri
@@ -883,7 +881,26 @@ class Uri
public static function parseUrl($url)
{
$grav = Grav::instance();
$parts = parse_url($url);
$encodedUrl = preg_replace_callback(
'%[^:/@?&=#]+%usD',
function ($matches) { return rawurlencode($matches[0]); },
$url
);
$parts = parse_url($encodedUrl);
if (false === $parts) {
return false;
}
foreach($parts as $name => $value) {
$parts[$name] = rawurldecode($value);
}
if (!isset($parts['path'])) {
$parts['path'] = '';
}
list($stripped_path, $params) = static::extractParams($parts['path'], $grav['config']->get('system.param_sep'));
@@ -1123,6 +1140,8 @@ class Uri
$this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
} elseif (isset($env['X-FORWARDED-PROTO'])) {
$this->scheme = $env['X-FORWARDED-PROTO'];
} elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
$this->scheme = $env['HTTP_CLOUDFRONT_FORWARDED_PROTO'];
} elseif (isset($env['REQUEST_SCHEME'])) {
$this->scheme = $env['REQUEST_SCHEME'];
} else {
@@ -1151,6 +1170,10 @@ class Uri
$this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
} elseif (isset($env['X-FORWARDED-PORT'])) {
$this->port = (int)$env['X-FORWARDED-PORT'];
} elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
// Since AWS Cloudfront does not provide a forwarded port header,
// we have to build the port using the scheme.
$this->port = $this->port();
} elseif (isset($env['SERVER_PORT'])) {
$this->port = (int)$env['SERVER_PORT'];
} else {
@@ -1262,15 +1285,18 @@ class Uri
{
if (!$this->post) {
$content_type = $this->getContentType();
if ($content_type == 'application/json') {
if ($content_type === 'application/json') {
$json = file_get_contents('php://input');
$this->post = json_decode($json, true);
} elseif (!empty($_POST)) {
$this->post = (array)$_POST;
}
$event = new Event(['post' => &$this->post]);
Grav::instance()->fireEvent('onHttpPostFilter', $event);
}
if ($this->post && !is_null($element)) {
if ($this->post && null !== $element) {
$item = Utils::getDotNotation($this->post, $element);
if ($filter_type) {
$item = filter_var($item, $filter_type);
@@ -1320,11 +1346,6 @@ class Uri
$scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']);
$rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/'));
// check if userdir in the path and workaround PHP bug with PHP_SELF
if (strpos($this->uri, '/~') !== false && strpos($scriptPath, '/~') === false) {
$rootPath = substr($this->uri, 0, strpos($this->uri, '/', 1)) . $rootPath;
}
return $rootPath;
}

View File

@@ -266,6 +266,8 @@ class User extends Data
*/
public function authorise($action)
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use authorize() method instead', E_USER_DEPRECATED);
return $this->authorize($action);
}
@@ -282,6 +284,35 @@ class User extends Data
return Grav::instance()['base_url'] . '/' . $avatar['path'];
}
return 'https://www.gravatar.com/avatar/' . md5($this->email);
return 'https://www.gravatar.com/avatar/' . md5( strtolower( trim($this->email) ) );
}
/**
* Serialize user.
*/
public function __sleep()
{
return [
'items',
'storage'
];
}
/**
* Unserialize user.
*/
public function __wakeup()
{
$this->gettersVariable = 'items';
$this->nestedSeparator = '.';
if (null === $this->items) {
$this->items = [];
}
if (null === $this->blueprints) {
$blueprints = new Blueprints;
$this->blueprints = $blueprints->get('user/account');
}
}
}

View File

@@ -45,8 +45,20 @@ abstract class Utils
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
// Get relative path to the resource (or false if not found).
$resource = $locator->findResource($input, false);
$parts = Uri::parseUrl($input);
if ($parts) {
$resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
if (isset($parts['query'])) {
$resource = $resource . '?' . $parts['query'];
}
} else {
// Not a valid URL (can still be a stream).
$resource = $locator->findResource($input, false);
}
} else {
$resource = $input;
}
@@ -262,7 +274,7 @@ abstract class Utils
// is $break present between $limit and the end of the string?
if ($up_to_break && false !== ($breakpoint = mb_strpos($string, $break, $limit))) {
if ($breakpoint < mb_strlen($string) - 1) {
$string = mb_substr($string, 0, $breakpoint) . $break;
$string = mb_substr($string, 0, $breakpoint) . $pad;
}
} else {
$string = mb_substr($string, 0, $limit) . $pad;
@@ -466,6 +478,51 @@ abstract class Utils
return $default;
}
/**
* Return the mimetype based on filename
*
* @param string $filename Filename or path to file
* @param string $default default value
*
* @return string
*/
public static function getMimeByFilename($filename, $default = 'application/octet-stream')
{
return static::getMimeByExtension(pathinfo($filename, PATHINFO_EXTENSION), $default);
}
/**
* Return the mimetype based on existing local file
*
* @param string $filename Path to the file
*
* @return string|bool
*/
public static function getMimeByLocalFile($filename, $default = 'application/octet-stream')
{
$type = false;
// For local files we can detect type by the file content.
if (!stream_is_local($filename) || !file_exists($filename)) {
return false;
}
// Prefer using finfo if it exists.
if (\extension_loaded('fileinfo')) {
$finfo = finfo_open(FILEINFO_SYMLINK | FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $filename);
finfo_close($finfo);
} else {
// Fall back to use getimagesize() if it is available (not recommended, but better than nothing)
$info = @getimagesize($filename);
if ($info) {
$type = $info['mime'];
}
}
return $type ?: static::getMimeByFilename($filename, $default);
}
/**
* Return the mimetype based on filename extension
*
@@ -508,6 +565,33 @@ abstract class Utils
return $default;
}
/**
* Returns true if filename is considered safe.
*
* @param string $filename
* @return bool
*/
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);
return !(
// Empty filenames are not allowed.
!$filename
// Filename should not contain horizontal/vertical tabs, newlines, nils or back/forward slashes.
|| 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)
);
}
/**
* Normalize path by processing relative `.` and `..` syntax and merging path
*
@@ -684,6 +768,8 @@ abstract class Utils
*/
public static function resolve(array $array, $path, $default = null)
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDotNotation() method instead', E_USER_DEPRECATED);
return static::getDotNotation($array, $path, $default);
}
@@ -705,11 +791,11 @@ abstract class Utils
* with reverse proxy setups.
*
* @param string $action
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
*
* @return string the nonce string
*/
private static function generateNonceString($action, $plusOneTick = false)
private static function generateNonceString($action, $previousTick = false)
{
$username = '';
if (isset(Grav::instance()['user'])) {
@@ -720,29 +806,8 @@ abstract class Utils
$token = session_id();
$i = self::nonceTick();
if ($plusOneTick) {
$i++;
}
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
}
//Added in version 1.0.8 to ensure that existing nonces are not broken.
private static function generateNonceStringOldStyle($action, $plusOneTick = false)
{
if (isset(Grav::instance()['user'])) {
$user = Grav::instance()['user'];
$username = $user->username;
if (isset($_SERVER['REMOTE_ADDR'])) {
$username .= $_SERVER['REMOTE_ADDR'];
}
} else {
$username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
}
$token = session_id();
$i = self::nonceTick();
if ($plusOneTick) {
$i++;
if ($previousTick) {
$i--;
}
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
@@ -768,33 +833,20 @@ abstract class Utils
* action is the same for 12 hours.
*
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
*
* @return string the nonce
*/
public static function getNonce($action, $plusOneTick = false)
public static function getNonce($action, $previousTick = false)
{
// Don't regenerate this again if not needed
if (isset(static::$nonces[$action])) {
return static::$nonces[$action];
if (isset(static::$nonces[$action][$previousTick])) {
return static::$nonces[$action][$previousTick];
}
$nonce = md5(self::generateNonceString($action, $plusOneTick));
static::$nonces[$action] = $nonce;
$nonce = md5(self::generateNonceString($action, $previousTick));
static::$nonces[$action][$previousTick] = $nonce;
return static::$nonces[$action];
}
//Added in version 1.0.8 to ensure that existing nonces are not broken.
public static function getNonceOldStyle($action, $plusOneTick = false)
{
// Don't regenerate this again if not needed
if (isset(static::$nonces[$action])) {
return static::$nonces[$action];
}
$nonce = md5(self::generateNonceStringOldStyle($action, $plusOneTick));
static::$nonces[$action] = $nonce;
return static::$nonces[$action];
return static::$nonces[$action][$previousTick];
}
/**
@@ -818,20 +870,8 @@ abstract class Utils
}
//Nonce generated 12-24 hours ago
$plusOneTick = true;
if ($nonce === self::getNonce($action, $plusOneTick)) {
return true;
}
//Added in version 1.0.8 to ensure that existing nonces are not broken.
//Nonce generated 0-12 hours ago
if ($nonce === self::getNonceOldStyle($action)) {
return true;
}
//Nonce generated 12-24 hours ago
$plusOneTick = true;
if ($nonce === self::getNonceOldStyle($action, $plusOneTick)) {
$previousTick = true;
if ($nonce === self::getNonce($action, $previousTick)) {
return true;
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* @package Grav.Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common;
use Grav\Framework\File\Formatter\YamlFormatter;
abstract class Yaml
{
/** @var YamlFormatter */
private static $yaml;
public static function parse($data)
{
if (null === static::$yaml) {
static::init();
}
return static::$yaml->decode($data);
}
public static function dump($data, $inline = null, $indent = null)
{
if (null === static::$yaml) {
static::init();
}
return static::$yaml->encode($data, $inline, $indent);
}
private static function init()
{
$config = [
'inline' => 5,
'indent' => 2,
'native' => true,
'compat' => true
];
static::$yaml = new YamlFormatter($config);
}
}

View File

@@ -9,9 +9,9 @@
namespace Grav\Console\Cli;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Yaml\Yaml;
class InstallCommand extends ConsoleCommand
{
@@ -71,20 +71,23 @@ class InstallCommand extends ConsoleCommand
// Look for dependencies file in ROOT and USER dir
if (file_exists($this->user_path . $dependencies_file)) {
$this->config = Yaml::parse(file_get_contents($this->user_path . $dependencies_file));
$file = YamlFile::instance($this->user_path . $dependencies_file);
} elseif (file_exists($this->destination . $dependencies_file)) {
$this->config = Yaml::parse(file_get_contents($this->destination . $dependencies_file));
$file = YamlFile::instance($this->destination . $dependencies_file);
} else {
$this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
if ($this->input->getArgument('destination')) {
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install a plugin or a theme? Make sure you use <cyan>bin/gpm install <something></cyan>, not <cyan>bin/grav install</cyan>. This command is only used to install Grav skeletons.');
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install a plugin or a theme? Make sure you use <cyan>bin/gpm install <something></cyan>, not <cyan>bin/grav install</cyan>. This command is only used to install Grav skeletons.');
} else {
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.');
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.');
}
return;
}
$this->config = $file->content();
$file->free();
// If yaml config, process
if ($this->config) {
if (!$this->input->getOption('symlink')) {
@@ -153,10 +156,22 @@ class InstallCommand extends ConsoleCommand
exec('cd ' . $this->destination);
foreach ($this->config['links'] as $repo => $data) {
$from = $this->local_config[$data['scm'] . '_repos'] . $data['src'];
$repos = (array) $this->local_config[$data['scm'] . '_repos'];
$from = false;
$to = $this->destination . $data['path'];
if (file_exists($from)) {
foreach ($repos as $repo) {
$path = $repo . $data['src'];
if (file_exists($path)) {
$from = $path;
continue;
}
}
if (!$from) {
$this->output->writeln('<red>source for ' . $data['src'] . ' does not exists, skipping...</red>');
$this->output->writeln('');
} else {
if (!file_exists($to)) {
symlink($from, $to);
$this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
@@ -165,11 +180,7 @@ class InstallCommand extends ConsoleCommand
$this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
$this->output->writeln('');
}
} else {
$this->output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
$this->output->writeln('');
}
}
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Cli;
use Grav\Common\Grav;
use Grav\Common\Security;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Style\SymfonyStyle;
class SecurityCommand extends ConsoleCommand
{
/** @var ProgressBar $progress */
protected $progress;
/**
*
*/
protected function configure()
{
$this
->setName("security")
->setDescription("Capable of running various Security checks")
->setHelp('The <info>security</info> runs various security checks on your Grav site');
$this->source = getcwd();
}
/**
* @return int|null|void
*/
protected function serve()
{
/** @var Grav $grav */
$grav = Grav::instance();
$grav['uri']->init();
$grav['config']->init();
$grav['debugger']->enabled(false);
$grav['streams'];
$grav['plugins']->init();
$grav['themes']->init();
$grav['twig']->init();
$grav['pages']->init();
$this->progress = new ProgressBar($this->output, (count($grav['pages']->routes()) - 1));
$this->progress->setFormat('Scanning <cyan>%current%</cyan> pages [<green>%bar%</green>] <white>%percent:3s%%</white> %elapsed:6s%');
$this->progress->setBarWidth(100);
$io = new SymfonyStyle($this->input, $this->output);
$io->title('Grav Security Check');
$output = Security::detectXssFromPages($grav['pages'], [$this, 'outputProgress']);
$io->newline(2);
if (!empty($output)) {
$counter = 1;
foreach ($output as $route => $results) {
$results_parts = array_map(function($value, $key) {
return $key.': \''.$value . '\'';
}, array_values($results), array_keys($results));
$io->writeln($counter++ .' - <cyan>' . $route . '</cyan> → <red>' . implode(', ', $results_parts) . '</red>');
}
$io->error('Security Scan complete: ' . count($output) . ' potential XSS issues found...');
} else {
$io->success('Security Scan complete: No issues found...');
}
$io->newline(1);
}
/**
* @param $args
*/
public function outputProgress($args)
{
switch ($args['type']) {
case 'count':
$steps = $args['steps'];
$freq = intval($steps > 100 ? round($steps / 100) : $steps);
$this->progress->setMaxSteps($steps);
$this->progress->setRedrawFrequency($freq);
break;
case 'progress':
if (isset($args['complete']) && $args['complete']) {
$this->progress->finish();
} else {
$this->progress->advance();
}
break;
}
}
}

View File

@@ -12,11 +12,11 @@ use Grav\Common\Grav;
use Grav\Common\Composer;
use Grav\Common\GravTrait;
use Grav\Console\Cli\ClearCacheCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
trait ConsoleTrait
{
@@ -123,7 +123,9 @@ trait ConsoleTrait
$local_config_file = $home_folder . '/.grav/config';
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse(file_get_contents($local_config_file));
$file = YamlFile::instance($local_config_file);
$this->local_config = $file->content();
$file->free();
return $local_config_file;
}

View File

@@ -444,18 +444,21 @@ class InstallCommand extends ConsoleCommand
{
$matches = $this->getGitRegexMatches($package);
foreach ($this->local_config as $path) {
foreach ($this->local_config as $paths) {
if (Utils::endsWith($matches[2], '.git')) {
$repo_dir = preg_replace('/\.git$/', '', $matches[2]);
} else {
$repo_dir = $matches[2];
}
$from = rtrim($path, '/') . '/' . $repo_dir;
if (file_exists($from)) {
return $from;
$paths = (array) $paths;
foreach ($paths as $repo) {
$path = rtrim($repo, '/') . '/' . $repo_dir;
if (file_exists($path)) {
return $path;
}
}
}
return false;

View File

@@ -11,9 +11,9 @@ namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Upgrader;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Yaml\Yaml;
class VersionCommand extends ConsoleCommand
{
@@ -84,7 +84,10 @@ class VersionCommand extends ConsoleCommand
}
}
$package_yaml = Yaml::parse(file_get_contents($blueprints_path));
$file = YamlFile::instance($blueprints_path);
$package_yaml = $file->content();
$file->free();
$version = $package_yaml['version'];
if (!$version) {

View File

@@ -27,9 +27,11 @@ class FileCache extends AbstractCache
/**
* @inheritdoc
*/
public function __construct($namespace = '', $defaultLifetime = null)
public function __construct($namespace = '', $defaultLifetime = null, $folder = null)
{
parent::__construct($namespace, $defaultLifetime ?: 31557600); // = 1 year
$this->initFileCache($namespace, $folder ?? '');
}
/**
@@ -136,9 +138,9 @@ class FileCache extends AbstractCache
/**
* @param string $namespace
* @param string $directory
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
*/
private function init($namespace, $directory)
protected function initFileCache($namespace, $directory)
{
if (!isset($directory[0])) {
$directory = sys_get_temp_dir() . '/grav-cache';
@@ -153,9 +155,7 @@ class FileCache extends AbstractCache
$directory .= DIRECTORY_SEPARATOR . $namespace;
}
if (!file_exists($directory)) {
@mkdir($directory, 0777, true);
}
$this->mkdir($directory);
$directory .= DIRECTORY_SEPARATOR;
// On Windows the whole path is limited to 258 chars

View File

@@ -16,21 +16,18 @@ use Grav\Framework\Cache\Exception\InvalidArgumentException;
*/
trait CacheTrait
{
/**
* @var string
*/
/** @var string */
private $namespace = '';
/**
* @var int|null
*/
/** @var int|null */
private $defaultLifetime = null;
/**
* @var \stdClass
*/
/** @var \stdClass */
private $miss;
/** @var bool */
private $validation = true;
/**
* Always call from constructor.
*
@@ -45,6 +42,14 @@ trait CacheTrait
$this->miss = new \stdClass;
}
/**
* @param $validation
*/
public function setValidation($validation)
{
$this->validation = (bool) $validation;
}
/**
* @return string
*/
@@ -307,6 +312,10 @@ trait CacheTrait
*/
protected function validateKeys($keys)
{
if (!$this->validation) {
return;
}
foreach ($keys as $key) {
$this->validateKey($key);
}

View File

@@ -24,11 +24,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
*/
public function reverse()
{
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
return new static(array_reverse($this->toArray()));
}
return $this->createFrom(array_reverse($this->toArray()));
}
@@ -42,11 +37,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
$keys = $this->getKeys();
shuffle($keys);
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
return new static(array_replace(array_flip($keys), $this->toArray()));
}
return $this->createFrom(array_replace(array_flip($keys), $this->toArray()));
}

View File

@@ -27,6 +27,7 @@ class ContentBlock implements ContentBlockInterface
protected $tokenTemplate = '@@BLOCK-%s@@';
protected $content = '';
protected $blocks = [];
protected $checksum;
/**
* @param string $id
@@ -40,6 +41,7 @@ class ContentBlock implements ContentBlockInterface
/**
* @param array $serialized
* @return ContentBlockInterface
* @throws \InvalidArgumentException
*/
public static function fromArray(array $serialized)
{
@@ -48,14 +50,14 @@ class ContentBlock implements ContentBlockInterface
$id = isset($serialized['id']) ? $serialized['id'] : null;
if (!$type || !$id || !is_a($type, 'Grav\Framework\ContentBlock\ContentBlockInterface', true)) {
throw new \RuntimeException('Bad data');
throw new \InvalidArgumentException('Bad data');
}
/** @var ContentBlockInterface $instance */
$instance = new $type($id);
$instance->build($serialized);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
throw new \InvalidArgumentException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
}
return $instance;
@@ -104,9 +106,13 @@ class ContentBlock implements ContentBlockInterface
$array = [
'_type' => get_class($this),
'_version' => $this->version,
'id' => $this->id,
'id' => $this->id
];
if ($this->checksum) {
$array['checksum'] = $this->checksum;
}
if ($this->content) {
$array['content'] = $this->content;
}
@@ -158,6 +164,7 @@ class ContentBlock implements ContentBlockInterface
$this->checkVersion($serialized);
$this->id = isset($serialized['id']) ? $serialized['id'] : $this->generateId();
$this->checksum = isset($serialized['checksum']) ? $serialized['checksum'] : null;
if (isset($serialized['content'])) {
$this->setContent($serialized['content']);
@@ -169,6 +176,25 @@ class ContentBlock implements ContentBlockInterface
}
}
/**
* @param string $checksum
* @return $this
*/
public function setChecksum($checksum)
{
$this->checksum = $checksum;
return $this;
}
/**
* @return string
*/
public function getChecksum()
{
return $this->checksum;
}
/**
* @param string $content
* @return $this
@@ -222,7 +248,7 @@ class ContentBlock implements ContentBlockInterface
*/
protected function checkVersion(array $serialized)
{
$version = isset($serialized['_version']) ? (string) $serialized['_version'] : '1';
$version = isset($serialized['_version']) ? (int) $serialized['_version'] : 1;
if ($version !== $this->version) {
throw new \RuntimeException(sprintf('Unsupported version %s', $version));
}

View File

@@ -61,6 +61,17 @@ interface ContentBlockInterface extends \Serializable
*/
public function build(array $serialized);
/**
* @param string $checksum
* @return $this
*/
public function setChecksum($checksum);
/**
* @return string
*/
public function getChecksum();
/**
* @param string $content
* @return $this

View File

@@ -15,6 +15,7 @@ namespace Grav\Framework\ContentBlock;
*/
class HtmlBlock extends ContentBlock implements HtmlBlockInterface
{
protected $version = 1;
protected $frameworks = [];
protected $styles = [];
protected $scripts = [];

View File

@@ -0,0 +1,44 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
interface FormatterInterface
{
/**
* Get default file extension from current formatter (with dot).
*
* Default file extension is the first defined extension.
*
* @return string File extension (can be empty).
*/
public function getDefaultFileExtension();
/**
* Get file extensions supported by current formatter (with dot).
*
* @return string[]
*/
public function getSupportedFileExtensions();
/**
* Encode data into a string.
*
* @param array $data
* @return string
*/
public function encode($data);
/**
* Decode a string into data.
*
* @param string $data
* @return array
*/
public function decode($data);
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class IniFormatter implements FormatterInterface
{
/** @var array */
private $config;
/**
* IniFormatter constructor.
* @param array $config
*/
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.ini'
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
$string = '';
foreach ($data as $key => $value) {
$string .= $key . '="' . preg_replace(
['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"],
['\"', '\\\\', '\t', '\n', '\r'],
$value
) . "\"\n";
}
return $string;
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$decoded = @parse_ini_string($data);
if ($decoded === false) {
throw new \RuntimeException('Decoding INI failed');
}
return $decoded;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class JsonFormatter implements FormatterInterface
{
/** @var array */
private $config;
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.json',
'encode_options' => 0,
'decode_assoc' => true
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
$encoded = @json_encode($data, $this->config['encode_options']);
if ($encoded === false) {
throw new \RuntimeException('Encoding JSON failed');
}
return $encoded;
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$decoded = @json_decode($data, $this->config['decode_assoc']);
if ($decoded === false) {
throw new \RuntimeException('Decoding JSON failed');
}
return $decoded;
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class MarkdownFormatter implements FormatterInterface
{
/** @var array */
private $config;
/** @var FormatterInterface */
private $headerFormatter;
public function __construct(array $config = [], FormatterInterface $headerFormatter = null)
{
$this->config = $config + [
'file_extension' => '.md',
'header' => 'header',
'body' => 'markdown',
'raw' => 'frontmatter',
'yaml' => ['inline' => 20]
];
$this->headerFormatter = $headerFormatter ?: new YamlFormatter($this->config['yaml']);
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
$headerVar = $this->config['header'];
$bodyVar = $this->config['body'];
$header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
$body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';
// Create Markdown file with YAML header.
$encoded = '';
if ($header) {
$encoded = "---\n" . trim($this->headerFormatter->encode($data['header'])) . "\n---\n\n";
}
$encoded .= $body;
// Normalize line endings to Unix style.
$encoded = preg_replace("/(\r\n|\r)/", "\n", $encoded);
return $encoded;
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$headerVar = $this->config['header'];
$bodyVar = $this->config['body'];
$rawVar = $this->config['raw'];
$content = [
$headerVar => [],
$bodyVar => ''
];
$headerRegex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis";
// Normalize line endings to Unix style.
$data = preg_replace("/(\r\n|\r)/", "\n", $data);
// Parse header.
preg_match($headerRegex, ltrim($data), $matches);
if(empty($matches)) {
$content[$bodyVar] = $data;
} else {
// Normalize frontmatter.
$frontmatter = preg_replace("/\n\t/", "\n ", $matches[1]);
if ($rawVar) {
$content[$rawVar] = $frontmatter;
}
$content[$headerVar] = $this->headerFormatter->decode($frontmatter);
$content[$bodyVar] = $matches[2];
}
return $content;
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class SerializeFormatter implements FormatterInterface
{
/** @var array */
private $config;
/**
* IniFormatter constructor.
* @param array $config
*/
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.ser'
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
return serialize($this->preserveLines($data, ["\n", "\r"], ['\\n', '\\r']));
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$decoded = @unserialize($data);
if ($decoded === false) {
throw new \RuntimeException('Decoding serialized data failed');
}
return $this->preserveLines($decoded, ['\\n', '\\r'], ["\n", "\r"]);
}
/**
* Preserve new lines, recursive function.
*
* @param mixed $data
* @param array $search
* @param array $replace
* @return mixed
*/
protected function preserveLines($data, $search, $replace)
{
if (is_string($data)) {
$data = str_replace($search, $replace, $data);
} elseif (is_array($data)) {
foreach ($data as &$value) {
$value = $this->preserveLines($value, $search, $replace);
}
unset($value);
}
return $data;
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml as YamlParser;
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYamlParser;
class YamlFormatter implements FormatterInterface
{
/** @var array */
private $config;
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.yaml',
'inline' => 5,
'indent' => 2,
'native' => true,
'compat' => true
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data, $inline = null, $indent = null)
{
try {
return (string) YamlParser::dump(
$data,
$inline ? (int) $inline : $this->config['inline'],
$indent ? (int) $indent : $this->config['indent'],
YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE
);
} catch (DumpException $e) {
throw new \RuntimeException('Encoding YAML failed: ' . $e->getMessage(), 0, $e);
}
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
// Try native PECL YAML PHP extension first if available.
if ($this->config['native'] && function_exists('yaml_parse')) {
// Safely decode YAML.
$saved = @ini_get('yaml.decode_php');
@ini_set('yaml.decode_php', 0);
$decoded = @yaml_parse($data);
@ini_set('yaml.decode_php', $saved);
if ($decoded !== false) {
return (array) $decoded;
}
}
try {
return (array) YamlParser::parse($data);
} catch (ParseException $e) {
if ($this->config['compat']) {
return (array) FallbackYamlParser::parse($data);
}
throw new \RuntimeException('Decoding YAML failed: ' . $e->getMessage(), 0, $e);
}
}
}

View File

@@ -32,11 +32,6 @@ trait ObjectCollectionTrait
$list[$key] = is_object($value) ? clone $value : $value;
}
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
return new static($list);
}
return $this->createFrom($list);
}
@@ -170,12 +165,7 @@ trait ObjectCollectionTrait
{
$collections = [];
foreach ($this->group($property) as $id => $elements) {
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
$collection = new static($elements);
} else {
$collection = $this->createFrom($elements);
}
$collection = $this->createFrom($elements);
$collections[$id] = $collection;
}

View File

@@ -15,7 +15,7 @@ namespace Grav\Framework\Object\Base;
*/
trait ObjectTrait
{
static protected $prefix;
/** @var string */
static protected $type;
/**
@@ -23,18 +23,28 @@ trait ObjectTrait
*/
private $_key;
/**
* @return string
*/
protected function getTypePrefix()
{
return '';
}
/**
* @param bool $prefix
* @return string
*/
public function getType($prefix = true)
{
$type = $prefix ? $this->getTypePrefix() : '';
if (static::$type) {
return ($prefix ? static::$prefix : '') . static::$type;
return $type . static::$type;
}
$class = get_class($this);
return ($prefix ? static::$prefix : '') . strtolower(substr($class, strrpos($class, '\\') + 1));
return $type . strtolower(substr($class, strrpos($class, '\\') + 1));
}
/**
@@ -108,7 +118,7 @@ trait ObjectTrait
*/
public function serialize()
{
return serialize($this->jsonSerialize());
return serialize($this->doSerialize());
}
/**
@@ -124,6 +134,14 @@ trait ObjectTrait
$this->doUnserialize($data);
}
/**
* @return array
*/
protected function doSerialize()
{
return $this->jsonSerialize();
}
/**
* @param array $serialized
*/
@@ -159,10 +177,13 @@ trait ObjectTrait
/**
* @param string $key
* @return $this
*/
protected function setKey($key)
{
$this->_key = (string) $key;
return $this;
}
abstract protected function doHasProperty($property);

View File

@@ -0,0 +1,198 @@
<?php
/**
* @package Grav\Framework\Object
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Object\Collection;
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
use Doctrine\Common\Collections\Expr\Comparison;
class ObjectExpressionVisitor extends ClosureExpressionVisitor
{
/**
* Accesses the field of a given object.
*
* @param object $object
* @param string $field
*
* @return mixed
*/
public static function getObjectFieldValue($object, $field)
{
$op = $value = null;
$pos = strpos($field, '(');
if (false !== $pos) {
list ($op, $field) = explode('(', $field, 2);
$field = rtrim($field, ')');
}
if (isset($object[$field])) {
$value = $object[$field];
} else {
$accessors = array('', 'get', 'is');
foreach ($accessors as $accessor) {
$accessor .= $field;
if (!method_exists($object, $accessor)) {
continue;
}
$value = $object->{$accessor}();
break;
}
}
if ($op) {
$function = 'filter' . ucfirst(strtolower($op));
if (method_exists(static::class, $function)) {
$value = static::$function($value);
}
}
return $value;
}
public static function filterLower($str)
{
return mb_strtolower($str);
}
public static function filterUpper($str)
{
return mb_strtoupper($str);
}
public static function filterLength($str)
{
return mb_strlen($str);
}
public static function filterLtrim($str)
{
return ltrim($str);
}
public static function filterRtrim($str)
{
return rtrim($str);
}
public static function filterTrim($str)
{
return trim($str);
}
/**
* Helper for sorting arrays of objects based on multiple fields + orientations.
*
* @param string $name
* @param int $orientation
* @param \Closure $next
*
* @return \Closure
*/
public static function sortByField($name, $orientation = 1, \Closure $next = null)
{
if (!$next) {
$next = function() {
return 0;
};
}
return function ($a, $b) use ($name, $next, $orientation) {
$aValue = static::getObjectFieldValue($a, $name);
$bValue = static::getObjectFieldValue($b, $name);
if ($aValue === $bValue) {
return $next($a, $b);
}
return (($aValue > $bValue) ? 1 : -1) * $orientation;
};
}
/**
* {@inheritDoc}
*/
public function walkComparison(Comparison $comparison)
{
$field = $comparison->getField();
$value = $comparison->getValue()->getValue(); // shortcut for walkValue()
switch ($comparison->getOperator()) {
case Comparison::EQ:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) === $value;
};
case Comparison::NEQ:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) !== $value;
};
case Comparison::LT:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) < $value;
};
case Comparison::LTE:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) <= $value;
};
case Comparison::GT:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) > $value;
};
case Comparison::GTE:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) >= $value;
};
case Comparison::IN:
return function ($object) use ($field, $value) {
return \in_array(static::getObjectFieldValue($object, $field), $value, true);
};
case Comparison::NIN:
return function ($object) use ($field, $value) {
return !\in_array(static::getObjectFieldValue($object, $field), $value, true);
};
case Comparison::CONTAINS:
return function ($object) use ($field, $value) {
return false !== strpos(static::getObjectFieldValue($object, $field), $value);
};
case Comparison::MEMBER_OF:
return function ($object) use ($field, $value) {
$fieldValues = static::getObjectFieldValue($object, $field);
if (!is_array($fieldValues)) {
$fieldValues = iterator_to_array($fieldValues);
}
return \in_array($value, $fieldValues, true);
};
case Comparison::STARTS_WITH:
return function ($object) use ($field, $value) {
return 0 === strpos(static::getObjectFieldValue($object, $field), $value);
};
case Comparison::ENDS_WITH:
return function ($object) use ($field, $value) {
return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value));
};
default:
throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
}
}
}

View File

@@ -8,13 +8,14 @@
namespace Grav\Framework\Object\Interfaces;
use Doctrine\Common\Collections\Selectable;
use Grav\Framework\Collection\CollectionInterface;
/**
* ObjectCollection Interface
* @package Grav\Framework\Collection
*/
interface ObjectCollectionInterface extends CollectionInterface, ObjectInterface
interface ObjectCollectionInterface extends CollectionInterface, Selectable, ObjectInterface
{
/**
* Create a copy from this collection by cloning all objects in the collection.

View File

@@ -8,9 +8,11 @@
namespace Grav\Framework\Object;
use Doctrine\Common\Collections\Criteria;
use Grav\Framework\Collection\ArrayCollection;
use Grav\Framework\Object\Access\NestedPropertyCollectionTrait;
use Grav\Framework\Object\Base\ObjectCollectionTrait;
use Grav\Framework\Object\Collection\ObjectExpressionVisitor;
use Grav\Framework\Object\Interfaces\NestedObjectInterface;
use Grav\Framework\Object\Interfaces\ObjectCollectionInterface;
@@ -36,6 +38,39 @@ class ObjectCollection extends ArrayCollection implements ObjectCollectionInterf
$this->setKey($key);
}
/**
* {@inheritDoc}
*/
public function matching(Criteria $criteria)
{
$expr = $criteria->getWhereExpression();
$filtered = $this->getElements();
if ($expr) {
$visitor = new ObjectExpressionVisitor();
$filter = $visitor->dispatch($expr);
$filtered = array_filter($filtered, $filter);
}
if ($orderings = $criteria->getOrderings()) {
$next = null;
foreach (array_reverse($orderings) as $field => $ordering) {
$next = ObjectExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
}
uasort($filtered, $next);
}
$offset = $criteria->getFirstResult();
$length = $criteria->getMaxResults();
if ($offset || $length) {
$filtered = array_slice($filtered, (int)$offset, $length);
}
return $this->createFrom($filtered);
}
protected function getElements()
{
return $this->toArray();

View File

@@ -95,10 +95,10 @@ trait ObjectPropertyTrait
}
/**
* @param string $property Object property to be fetched.
* @param mixed $default Default value if property has not been set.
* @param bool $doCreate Set true to create variable.
* @return mixed Property value.
* @param string $property Object property to be fetched.
* @param mixed $default Default value if property has not been set.
* @param callable|bool $doCreate Set true to create variable.
* @return mixed Property value.
*/
protected function &doGetProperty($property, $default = null, $doCreate = false)
{

View File

@@ -178,7 +178,7 @@ class Route
*/
public function withGravParam($param, $value)
{
return $this->withParam('gravParams', $param, $value);
return $this->withParam('gravParams', $param, null !== $value ? (string)$value : null);
}
/**
@@ -222,17 +222,16 @@ class Route
protected function withParam($type, $param, $value)
{
$oldValue = isset($this->{$type}[$param]) ? $this->{$type}[$param] : null;
$newValue = null !== $value ? (string)$value : null;
if ($oldValue === $newValue) {
if ($oldValue === $value) {
return $this;
}
$new = clone $this;
if ($newValue === null) {
if ($value === null) {
unset($new->{$type}[$param]);
} else {
$new->{$type}[$param] = $newValue;
$new->{$type}[$param] = $value;
}
return $new;

View File

@@ -28,6 +28,23 @@ class RouteFactory
return new Route($parts);
}
public static function createFromString($path)
{
$path = ltrim($path, '/');
$parts = [
'path' => $path,
'query' => '',
'query_params' => [],
'grav' => [
'root' => self::$root,
'language' => self::$language,
'route' => $path,
'params' => ''
],
];
return new Route($parts);
}
public static function getRoot()
{
return self::$root;

View File

@@ -0,0 +1,345 @@
<?php
/**
* @package Grav\Framework\Session
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Session;
/**
* Class Session
* @package Grav\Framework\Session
*/
class Session implements SessionInterface
{
protected $options;
/**
* @var bool
*/
protected $started = false;
/**
* @var Session
*/
protected static $instance;
/**
* @inheritdoc
*/
public static function getInstance()
{
if (null === self::$instance) {
throw new \RuntimeException("Session hasn't been initialized.", 500);
}
return self::$instance;
}
public function __construct(array $options = [])
{
// Session is a singleton.
if (\PHP_SAPI === 'cli') {
self::$instance = $this;
return;
}
if (null !== self::$instance) {
throw new \RuntimeException('Session has already been initialized.', 500);
}
// Destroy any existing sessions started with session.auto_start
if ($this->isSessionStarted()) {
session_unset();
session_destroy();
}
// Set default options.
$options += array(
'cache_limiter' => 'nocache',
'use_trans_sid' => 0,
'use_cookies' => 1,
'lazy_write' => 1,
'use_strict_mode' => 1
);
$this->setOptions($options);
session_register_shutdown();
self::$instance = $this;
}
/**
* @inheritdoc
*/
public function getId()
{
return session_id();
}
/**
* @inheritdoc
*/
public function setId($id)
{
session_id($id);
return $this;
}
/**
* @inheritdoc
*/
public function getName()
{
return session_name();
}
/**
* @inheritdoc
*/
public function setName($name)
{
session_name($name);
return $this;
}
/**
* @inheritdoc
*/
public function setOptions(array $options)
{
if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
return;
}
$allowedOptions = [
'save_path' => true,
'name' => true,
'save_handler' => true,
'gc_probability' => true,
'gc_divisor' => true,
'gc_maxlifetime' => true,
'serialize_handler' => true,
'cookie_lifetime' => true,
'cookie_path' => true,
'cookie_domain' => true,
'cookie_secure' => true,
'cookie_httponly' => true,
'use_strict_mode' => true,
'use_cookies' => true,
'use_only_cookies' => true,
'referer_check' => true,
'cache_limiter' => true,
'cache_expire' => true,
'use_trans_sid' => true,
'trans_sid_tags' => true, // PHP 7.1
'trans_sid_hosts' => true, // PHP 7.1
'sid_length' => true, // PHP 7.1
'sid_bits_per_character' => true, // PHP 7.1
'upload_progress.enabled' => true,
'upload_progress.cleanup' => true,
'upload_progress.prefix' => true,
'upload_progress.name' => true,
'upload_progress.freq' => true,
'upload_progress.min-freq' => true,
'lazy_write' => true,
'url_rewriter.tags' => true, // Not used in PHP 7.1
'hash_function' => true, // Not used in PHP 7.1
'hash_bits_per_character' => true, // Not used in PHP 7.1
'entropy_file' => true, // Not used in PHP 7.1
'entropy_length' => true, // Not used in PHP 7.1
];
foreach ($options as $key => $value) {
if (is_array($value)) {
// Allow nested options.
foreach ($value as $key2 => $value2) {
$ckey = "{$key}.{$key2}";
if (isset($value2, $allowedOptions[$ckey])) {
$this->ini_set("session.{$ckey}", $value2);
}
}
} elseif (isset($value, $allowedOptions[$key])) {
$this->ini_set("session.{$key}", $value);
}
}
}
/**
* @inheritdoc
*/
public function start($readonly = false)
{
// 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()]);
}
$options = $this->options;
if ($readonly) {
$options['read_and_close'] = '1';
}
$success = @session_start($options);
if (!$success) {
$last = error_get_last();
$error = $last ? $last['message'] : 'Unknown error';
throw new \RuntimeException('Failed to start session: ' . $error, 500);
}
$params = session_get_cookie_params();
setcookie(
session_name(),
session_id(),
time() + $params['lifetime'],
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
$this->started = true;
return $this;
}
/**
* @inheritdoc
*/
public function invalidate()
{
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
if ($this->isSessionStarted()) {
session_unset();
session_destroy();
}
$this->started = false;
return $this;
}
/**
* @inheritdoc
*/
public function close()
{
if ($this->started) {
session_write_close();
}
$this->started = false;
return $this;
}
/**
* @inheritdoc
*/
public function clear()
{
session_unset();
return $this;
}
/**
* @inheritdoc
*/
public function getAll()
{
return $_SESSION;
}
/**
* @inheritdoc
*/
public function getIterator()
{
return new \ArrayIterator($_SESSION);
}
/**
* @inheritdoc
*/
public function isStarted()
{
return $this->started;
}
/**
* @inheritdoc
*/
public function __isset($name)
{
return isset($_SESSION[$name]);
}
/**
* @inheritdoc
*/
public function __get($name)
{
return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
}
/**
* @inheritdoc
*/
public function __set($name, $value)
{
$_SESSION[$name] = $value;
}
/**
* @inheritdoc
*/
public function __unset($name)
{
unset($_SESSION[$name]);
}
/**
* http://php.net/manual/en/function.session-status.php#113468
* Check if session is started nicely.
* @return bool
*/
protected function isSessionStarted()
{
return \PHP_SAPI !== 'cli' ? \PHP_SESSION_ACTIVE === session_status() : false;
}
/**
* @param string $key
* @param mixed $value
*/
protected function ini_set($key, $value)
{
if (!is_string($value)) {
if (is_bool($value)) {
$value = $value ? '1' : '0';
}
$value = (string)$value;
}
$this->options[$key] = $value;
ini_set($key, $value);
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* @package Grav\Framework\Session
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Session;
/**
* Class Session
* @package Grav\Framework\Session
*/
interface SessionInterface extends \IteratorAggregate
{
/**
* Get current session instance.
*
* @return Session
* @throws \RuntimeException
*/
public static function getInstance();
/**
* Get session ID
*
* @return string|null Session ID
*/
public function getId();
/**
* Set session ID
*
* @param string $id Session ID
*
* @return $this
*/
public function setId($id);
/**
* Get session name
*
* @return string|null
*/
public function getName();
/**
* Set session name
*
* @param string $name
*
* @return $this
*/
public function setName($name);
/**
* Sets session.* ini variables.
*
* @param array $options
*
* @see http://php.net/session.configuration
*/
public function setOptions(array $options);
/**
* Starts the session storage
*
* @param bool $readonly
* @return $this
* @throws \RuntimeException
*/
public function start($readonly = false);
/**
* Invalidates the current session.
*
* @return $this
*/
public function invalidate();
/**
* Force the session to be saved and closed
*
* @return $this
*/
public function close();
/**
* Free all session variables.
*
* @return $this
*/
public function clear();
/**
* Returns all session variables.
*
* @return array
*/
public function getAll();
/**
* Retrieve an external iterator
*
* @return \ArrayIterator Return an ArrayIterator of $_SESSION
*/
public function getIterator();
/**
* Checks if the session was started.
*
* @return Boolean
*/
public function isStarted();
/**
* Checks if session variable is defined.
*
* @param string $name
* @return bool
*/
public function __isset($name);
/**
* Returns session variable.
*
* @param string $name
* @return mixed
*/
public function __get($name);
/**
* Sets session variable.
*
* @param string $name
* @param mixed $value
*/
public function __set($name, $value);
/**
* Removes session variable.
*
* @param string $name
*/
public function __unset($name);
}

View File

@@ -109,16 +109,16 @@ class UtilsTest extends \Codeception\TestCase\Test
$this->assertEquals('engli' . '...', Utils::truncate('english', 5, true, " ", "..."));
$this->assertEquals('english', Utils::truncate('english'));
$this->assertEquals('This is a string to truncate', Utils::truncate('This is a string to truncate'));
$this->assertEquals('This ', Utils::truncate('This is a string to truncate', 3, true));
$this->assertEquals('<input ', Utils::truncate('<input type="file" id="file" multiple />', 6, true));
$this->assertEquals('This' . '&hellip;', Utils::truncate('This is a string to truncate', 3, true));
$this->assertEquals('<input' . '&hellip;', Utils::truncate('<input type="file" id="file" multiple />', 6, true));
}
public function testSafeTruncate()
{
$this->assertEquals('This ', Utils::safeTruncate('This is a string to truncate', 1));
$this->assertEquals('This ', Utils::safeTruncate('This is a string to truncate', 4));
$this->assertEquals('This is ', Utils::safeTruncate('This is a string to truncate', 5));
$this->assertEquals('This' . '&hellip;', Utils::safeTruncate('This is a string to truncate', 1));
$this->assertEquals('This' . '&hellip;', Utils::safeTruncate('This is a string to truncate', 4));
$this->assertEquals('This is' . '&hellip;', Utils::safeTruncate('This is a string to truncate', 5));
}
public function testTruncateHtml()

View File

@@ -26,7 +26,7 @@ To edit this page, simply navigate to the folder you installed **Grav** into, an
Creating a new page is a simple affair in **Grav**. Simply follow these simple steps:
1. Navigate to your pages folder: `user/pages/` and create a new folder. In this example, we will use [explicit default ordering](http://learn.getgrav.org/content/content-pages) and call the folder `03.mypage`.
1. Navigate to your pages folder: `user/pages/` and create a new folder. In this example, we will use [explicit default ordering](http://learn.getgrav.org/content/content-pages) and call the folder `02.mypage`.
2. Launch your text editor and paste in the following sample code:
---
@@ -36,7 +36,7 @@ Creating a new page is a simple affair in **Grav**. Simply follow these simple
This is the body of **my new page** and I can easily use _Markdown_ syntax here.
3. Save this file in the `user/pages/03.mypage/` folder as `default.md`. This will tell **Grav** to render the page using the **default** template.
3. Save this file in the `user/pages/02.mypage/` folder as `default.md`. This will tell **Grav** to render the page using the **default** template.
4. That is it! Reload your browser to see your new page in the menu.
! NOTE: The page will automatically show up in the Menu after the "Home" menu item. If you wish to change the name that shows up in the Menu, simple add: `menu: My Page` between the dashes in the page content. This is called the YAML front matter, and it is where you configure page-specific options.

View File

@@ -53,7 +53,7 @@ _Italic_ `_Italic_`
Text<sup>Superscripted</sup> `<sup>`
Text<sub>Subscxripted</sub> `<sub>`
Text<sub>Subscripted</sub> `<sub>`
<u>Underlined</u> `<u>`

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